Coverage Report

Created: 2025-11-16 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/resiprocate/rutil/ConfigParse.cxx
Line
Count
Source
1
#include <ctype.h>
2
#include <iostream>
3
#include <fstream>
4
#include <iterator>
5
#include <stdexcept>
6
#include <sstream>
7
#include <map>
8
9
#include "rutil/ConfigParse.hxx"
10
#include "rutil/Log.hxx"
11
#include "rutil/Logger.hxx"
12
#include "rutil/ParseBuffer.hxx"
13
#include "rutil/WinLeakCheck.hxx"
14
15
using namespace resip;
16
using namespace std;
17
18
#define RESIPROCATE_SUBSYSTEM Subsystem::SIP
19
20
namespace resip
21
{
22
23
ConfigParse::ConfigParse()
24
0
{
25
0
}
26
27
ConfigParse::~ConfigParse()
28
0
{
29
0
}
30
31
void
32
ConfigParse::parseConfig(int argc, char** argv, const resip::Data& defaultConfigFilename)
33
0
{
34
0
   ConfigParse::parseConfig(argc, argv, defaultConfigFilename, 0);
35
0
}
36
37
void 
38
ConfigParse::parseConfig(int argc, char** argv, const resip::Data& defaultConfigFilename, int skipCount)
39
0
{
40
0
   parseCommandLine(argc, argv, skipCount);  // will fill in mCmdLineConfigFilename if present
41
0
   if(mCmdLineConfigFilename.empty())
42
0
   {
43
0
      parseConfigFile(defaultConfigFilename);
44
0
   }
45
0
   else
46
0
   {
47
0
      parseConfigFile(mCmdLineConfigFilename);
48
0
   }
49
0
   mConfigValues = mFileConfigValues;
50
   // Overlay the command line config options on top of the config options from the file
51
   // The command line config options take precedence / override anything in the file
52
0
   for(
53
0
      ConfigValuesMap::iterator it = mCmdLineConfigValues.begin();
54
0
      it != mCmdLineConfigValues.end();
55
0
      it++)
56
0
   {
57
0
      if(mConfigValues.find(it->first) != mConfigValues.end())
58
0
      {
59
0
         mConfigValues.erase(it->first);
60
0
      }
61
0
      mConfigValues.insert(ConfigValuesMap::value_type(it->first, it->second));
62
0
   } 
63
0
}
64
65
void 
66
ConfigParse::parseCommandLine(int argc, char** argv, int skipCount)
67
0
{
68
0
   int startingArgForNameValuePairs = 1 + skipCount;
69
0
   char *firstArg = argv[startingArgForNameValuePairs];
70
   // First argument is the configuration filename - it is optional and is never proceeded with a - or /
71
#ifdef WIN32
72
   if(argc >= (startingArgForNameValuePairs + 1) && firstArg[0] != '-' && firstArg[0] != '/')
73
#else
74
0
   if(argc >= (startingArgForNameValuePairs + 1) && firstArg[0] != '-')
75
0
#endif
76
0
   {
77
0
      mCmdLineConfigFilename = firstArg;
78
0
      startingArgForNameValuePairs++;
79
0
   }
80
81
   // Loop through command line arguments and process them
82
0
   for(int i = startingArgForNameValuePairs; i < argc; i++)
83
0
   {
84
0
      Data argData(argv[i]);
85
86
      // Process all commandNames that don't take values
87
0
      if(isEqualNoCase(argData, "-?") || 
88
0
         isEqualNoCase(argData, "--?") ||
89
0
         isEqualNoCase(argData, "--help") ||
90
0
         isEqualNoCase(argData, "/?"))
91
0
      {
92
0
         printHelpText(argc, argv);
93
0
         throw Exception("Help text requested - process stopping", __FILE__, __LINE__);
94
0
      }
95
0
      else if(argData.at(0) == '-' || argData.at(0) == '/')
96
0
      {
97
0
         Data name;
98
0
         Data value;
99
0
         ParseBuffer pb(argData);
100
101
0
         try
102
0
         {
103
0
            pb.skipChars(Data::toBitset("-/"));  // Skip any leading -'s or /'s
104
0
            const char * anchor = pb.position();
105
0
            pb.skipToOneOf("=:");
106
0
            if(!pb.eof())
107
0
            {
108
0
               pb.data(name, anchor);
109
0
               pb.skipChar();
110
0
               anchor = pb.position();
111
0
               pb.skipToEnd();
112
0
               pb.data(value, anchor);
113
114
               //cout << "Command line Name='" << name << "' value='" << value << "'" << endl;
115
0
               insertConfigValue("command line", mCmdLineConfigValues, name, value);
116
0
            }
117
0
            else
118
0
            {
119
0
               cerr << "Invalid command line parameters:"  << endl;
120
0
               cerr << " Name/Value pairs must contain an = or a : between the name and the value" << endl;
121
0
               cerr << " Bad argument: " << argData << endl;
122
0
               Data exceptionString("Name/Value pairs must contain an = or a : between the name and the value (Bad argument: " + argData + ")");
123
0
               throw Exception(exceptionString, __FILE__, __LINE__);
124
0
            }
125
0
         }
126
0
         catch(BaseException& ex)
127
0
         {
128
0
            cerr << "Invalid command line parameters:"  << endl;
129
0
            cerr << " Exception parsing Name/Value pairs: " << ex << endl;
130
0
            cerr << " Bad argument: " << argData << endl;
131
0
            throw;
132
0
         }
133
0
      }
134
0
      else
135
0
      {
136
0
         cerr << "Invalid command line parameters:"  << endl;
137
0
         cerr << " Name/Value pairs must be prefixed with either a -, --, or a /" << endl;
138
0
         cerr << " Bad argument: " << argData << endl;
139
0
         Data exceptionString("Name/Value pairs must be prefixed with either a -, --, or a / (Bad argument: " + argData + ")");
140
0
         throw Exception(exceptionString,  __FILE__, __LINE__);
141
0
      }
142
0
   }
143
0
}
144
145
void
146
ConfigParse::parseConfigFile(const Data& filename)
147
0
{
148
   // Store off base config path
149
0
   ParseBuffer pb(filename);
150
0
   const char* anchor = pb.start();
151
0
   pb.skipToEnd();
152
0
   pb.skipBackToOneOf("/\\");
153
0
   if(!pb.bof())
154
0
   {
155
0
      mConfigBasePath = pb.data(pb.start());
156
0
   }
157
158
0
   ifstream configFile(filename.c_str());
159
   
160
0
   if(!configFile)
161
0
   {
162
0
      Data exceptionString("Error opening/reading configuration file: " + filename);
163
0
      throw Exception(exceptionString,  __FILE__, __LINE__);
164
0
   }
165
166
0
   string sline;
167
0
   while(getline(configFile, sline)) 
168
0
   {
169
0
      Data name;
170
0
      Data value;
171
0
      ParseBuffer pb(sline.c_str(), sline.size());
172
173
0
      pb.skipWhitespace();
174
0
      anchor = pb.position();
175
0
      if(pb.eof() || *anchor == '#') continue;  // if line is a comment or blank then skip it
176
177
      // Look for end of name
178
0
      pb.skipToOneOf("= \t");
179
0
      pb.data(name,anchor);
180
0
      if(*pb.position()!='=') 
181
0
      {
182
0
         pb.skipToChar('=');
183
0
      }
184
0
      pb.skipChar('=');
185
0
      pb.skipWhitespace();
186
0
      anchor = pb.position();
187
0
      if(!pb.eof())
188
0
      {
189
0
         pb.skipToOneOf("\r\n");
190
0
         pb.data(value, anchor);
191
0
      }
192
      //cout << "Config file Name='" << name << "' value='" << value << "'" << endl;
193
0
      Data lowerName(name);
194
0
      lowerName.lowercase();
195
0
      if(lowerName == "include")
196
0
      {
197
0
         parseConfigFile(value);
198
0
      }
199
0
      else
200
0
      {
201
0
         insertConfigValue("config file", mFileConfigValues, name, value);
202
0
      }
203
0
   }
204
0
}
205
206
void
207
ConfigParse::getConfigIndexKeys(const resip::Data& indexName, std::set<Data>& keys) const
208
0
{
209
0
   Data::size_type numPos = indexName.size();
210
0
   Data indexNameLower(indexName);
211
0
   indexNameLower.lowercase();
212
0
   ConfigValuesMap::const_iterator it = mConfigValues.begin();
213
0
   for(; it != mConfigValues.end(); it++)
214
0
   {
215
0
      const Data& keyName = it->first;
216
0
      if(keyName.prefix(indexNameLower) && keyName.size() > numPos
217
0
         && isdigit(static_cast< unsigned char >(keyName[numPos])))
218
0
      {
219
0
         Data::size_type i = numPos + 1;
220
0
         while(i < keyName.size() && isdigit(static_cast< unsigned char >(keyName[i])))
221
0
         {
222
0
            i++;
223
0
         }
224
0
         Data indexFullName = keyName.substr(0, i);
225
0
         if(keys.find(indexFullName) == keys.end())
226
0
         {
227
0
            keys.insert(indexFullName);
228
0
         }
229
0
      }
230
0
   }
231
0
}
232
233
bool 
234
ConfigParse::getConfigValue(const resip::Data& name, resip::Data &value) const
235
0
{
236
0
   Data lowerName(name);  lowerName.lowercase();
237
0
   ConfigValuesMap::const_iterator it = mConfigValues.find(lowerName);
238
0
   if(it != mConfigValues.end())
239
0
   {
240
0
      value = it->second;
241
0
      return true;
242
0
   }
243
   // Not found
244
0
   return false;
245
0
}
246
247
Data 
248
ConfigParse::getConfigData(const resip::Data& name, const resip::Data& defaultValue, bool useDefaultIfEmpty) const
249
0
{
250
0
   Data ret(defaultValue);
251
0
   if(getConfigValue(name, ret) && ret.empty() && useDefaultIfEmpty)
252
0
   {
253
0
      return defaultValue;
254
0
   }
255
0
   return ret;
256
0
}
257
258
bool 
259
ConfigParse::getConfigValue(const resip::Data& name, bool &value) const
260
0
{
261
0
   Data lowerName(name);  lowerName.lowercase();
262
0
   ConfigValuesMap::const_iterator it = mConfigValues.find(lowerName);
263
0
   if(it != mConfigValues.end())
264
0
   {
265
0
      if(it->second == "1" || 
266
0
         isEqualNoCase(it->second, "true") || 
267
0
         isEqualNoCase(it->second, "on") || 
268
0
         isEqualNoCase(it->second, "enable"))
269
0
      {
270
0
         value = true;
271
0
         return true;
272
0
      }
273
0
      else if(it->second == "0" ||
274
0
              isEqualNoCase(it->second, "false") || 
275
0
              isEqualNoCase(it->second, "off") || 
276
0
              isEqualNoCase(it->second, "disable"))
277
0
      {
278
0
         value = false;
279
0
         return true;
280
0
      }
281
0
      cerr << "Invalid boolean setting:  " << name << " = " << it->second << ": Valid values are: 1,true,on,enable,0,false,off or disable" << endl;
282
0
      return false;
283
0
   }
284
   // Not found
285
0
   return false;
286
0
}
287
288
bool 
289
ConfigParse::getConfigBool(const resip::Data& name, bool defaultValue) const
290
0
{
291
0
   bool ret = defaultValue;
292
0
   getConfigValue(name, ret);
293
0
   return ret;
294
0
}
295
296
bool 
297
ConfigParse::getConfigValue(const resip::Data& name, unsigned long &value) const
298
0
{
299
0
   Data lowerName(name);  lowerName.lowercase();
300
0
   ConfigValuesMap::const_iterator it = mConfigValues.find(lowerName);
301
0
   if(it != mConfigValues.end())
302
0
   {
303
0
      value = it->second.convertUnsignedLong();
304
0
      return true;
305
0
   }
306
   // Not found
307
0
   return false;
308
0
}
309
310
unsigned long 
311
ConfigParse::getConfigUnsignedLong(const resip::Data& name, unsigned long defaultValue) const
312
0
{
313
0
   unsigned long ret = defaultValue;
314
0
   getConfigValue(name, ret);
315
0
   return ret;
316
0
}
317
318
bool 
319
ConfigParse::getConfigValue(const resip::Data& name, int &value) const
320
0
{
321
0
   Data lowerName(name);  lowerName.lowercase();
322
0
   ConfigValuesMap::const_iterator it = mConfigValues.find(lowerName);
323
0
   if(it != mConfigValues.end())
324
0
   {
325
0
      value = it->second.convertInt();
326
0
      return true;
327
0
   }
328
   // Not found
329
0
   return false;
330
0
}
331
332
333
int 
334
ConfigParse::getConfigInt(const resip::Data& name, int defaultValue) const
335
0
{
336
0
   int ret = defaultValue;
337
0
   getConfigValue(name, ret);
338
0
   return ret;
339
0
}
340
341
bool
342
ConfigParse::getConfigValue(const resip::Data& name, unsigned short &value) const
343
0
{
344
0
   Data lowerName(name);  lowerName.lowercase();
345
0
   ConfigValuesMap::const_iterator it = mConfigValues.find(lowerName);
346
0
   if(it != mConfigValues.end())
347
0
   {
348
0
      value = it->second.convertInt();
349
0
      return true;
350
0
   }
351
   // Not found
352
0
   return false;
353
0
}
354
355
356
unsigned short
357
ConfigParse::getConfigUnsignedShort(const resip::Data& name, int defaultValue) const
358
0
{
359
0
   int ret = defaultValue;
360
0
   getConfigValue(name, ret);
361
0
   return ret;
362
0
}
363
364
bool 
365
ConfigParse::getConfigValue(const resip::Data& name, std::vector<resip::Data> &value) const
366
0
{
367
0
   Data lowerName(name);  lowerName.lowercase();
368
0
   std::pair<ConfigValuesMap::const_iterator,ConfigValuesMap::const_iterator> valuesIts = mConfigValues.equal_range(lowerName);
369
0
   bool found = false;
370
0
   for (ConfigValuesMap::const_iterator it=valuesIts.first; it!=valuesIts.second; ++it)
371
0
   {
372
0
      found = true;
373
0
      ParseBuffer pb(it->second);
374
0
      Data item;
375
0
      while(!it->second.empty() && !pb.eof())
376
0
      {
377
0
         pb.skipWhitespace();
378
0
         const char *start = pb.position();
379
0
         pb.skipToOneOf(ParseBuffer::Whitespace, ",");  // allow white space 
380
0
         pb.data(item, start);
381
0
         value.push_back(item);
382
0
         if(!pb.eof())
383
0
         {
384
0
            pb.skipChar();
385
0
         }
386
0
      }
387
0
   }
388
389
0
   return found;
390
0
}
391
392
bool
393
ConfigParse::getConfigValue(const resip::Data& name, std::vector<int> &value) const
394
0
{
395
0
   std::vector<Data> _value;
396
0
   if(!getConfigValue(name, _value))
397
0
   {
398
0
      return false;
399
0
   }
400
401
0
   for(auto v : _value)
402
0
   {
403
0
      value.push_back(v.convertInt());
404
0
   }
405
406
0
   return true;
407
0
}
408
409
bool
410
ConfigParse::getConfigValue(const resip::Data& name, std::set<resip::Data> &value) const
411
0
{
412
0
   Data lowerName(name);  lowerName.lowercase();
413
0
   std::pair<ConfigValuesMap::const_iterator,ConfigValuesMap::const_iterator> valuesIts = mConfigValues.equal_range(lowerName);
414
0
   bool found = false;
415
0
   for (ConfigValuesMap::const_iterator it=valuesIts.first; it!=valuesIts.second; ++it)
416
0
   {
417
0
      found = true;
418
0
      ParseBuffer pb(it->second);
419
0
      Data item;
420
0
      while(!it->second.empty() && !pb.eof())
421
0
      {
422
0
         pb.skipWhitespace();
423
0
         const char *start = pb.position();
424
0
         pb.skipToOneOf(ParseBuffer::Whitespace, ",");  // allow white space
425
0
         pb.data(item, start);
426
0
         value.insert(item);
427
0
         if(!pb.eof())
428
0
         {
429
0
            pb.skipChar();
430
0
         }
431
0
      }
432
0
   }
433
434
0
   return found;
435
0
}
436
437
ConfigParse::NestedConfigMap
438
ConfigParse::getConfigNested(const resip::Data& mapsPrefix) const
439
0
{
440
0
   NestedConfigMap m;
441
0
   Data::size_type numPos = mapsPrefix.size();
442
0
   Data mapsPrefixLower(mapsPrefix);
443
0
   mapsPrefixLower.lowercase();
444
0
   ConfigValuesMap::const_iterator it = mConfigValues.begin();
445
0
   for(; it != mConfigValues.end(); it++)
446
0
   {
447
0
      const Data& keyName = it->first;
448
0
      if(keyName.prefix(mapsPrefixLower) && keyName.size() > numPos
449
0
         && isdigit(static_cast< unsigned char >(keyName[numPos])))
450
0
      {
451
0
         Data::size_type i = numPos + 1;
452
0
         while(i < keyName.size() && isdigit(static_cast< unsigned char >(keyName[i])))
453
0
         {
454
0
            i++;
455
0
         }
456
0
         if(keyName.size() - i < 1)
457
0
         {
458
0
            stringstream err_text;
459
0
            err_text << "Configuration key " << keyName << " missing subkey name";
460
0
            Data err_data(err_text.str());
461
0
            throw Exception(err_data, __FILE__, __LINE__);
462
0
         }
463
0
         Data index = keyName.substr(numPos, i - numPos);
464
0
         Data nestedKey = keyName.substr(i, keyName.size() - i);
465
0
         NestedConfigParse& nested = m[index.convertInt()];
466
0
         nested.insertConfigValue(nestedKey, it->second);
467
0
      }
468
0
   }
469
0
   return m;
470
0
}
471
472
void 
473
ConfigParse::insertConfigValue(const Data& source, ConfigValuesMap& configValues, const resip::Data& name, const resip::Data& value)
474
0
{
475
0
   resip::Data lowerName(name);
476
0
   lowerName.lowercase();
477
0
   if(configValues.find(lowerName) != configValues.end())
478
0
   {
479
0
      stringstream err_text;
480
0
      err_text << "Duplicate configuration key " << name << " while parsing " << source;
481
0
      Data err_data(err_text.str());
482
0
      throw Exception(err_data, __FILE__, __LINE__);
483
0
   }
484
0
   configValues.insert(ConfigValuesMap::value_type(lowerName, value));
485
0
}
486
487
void 
488
ConfigParse::insertConfigValue(const resip::Data& name, const resip::Data& value)
489
0
{
490
0
    insertConfigValue("manually added setting", mConfigValues, name, value);
491
0
}
492
493
resip::Data
494
ConfigParse::removePath(const resip::Data& fileAndPath) const
495
0
{
496
0
   Data filenameOnly;
497
0
   ParseBuffer pb(fileAndPath);
498
0
   const char* anchor = pb.position();
499
0
   while(pb.skipToOneOf("/\\") && !pb.eof())
500
0
   {
501
0
      pb.skipChar();
502
0
      anchor = pb.position();
503
0
   }
504
0
   pb.data(filenameOnly, anchor);
505
0
   return filenameOnly;
506
0
}
507
508
bool 
509
ConfigParse::AddBasePathIfRequired(Data& filename) const
510
0
{
511
0
   if(!filename.empty())
512
0
   {
513
      // If filename already has a path specified, then don't touch it
514
0
      ParseBuffer pb(filename);
515
0
      pb.skipToOneOf("/\\");
516
0
      if(pb.eof())
517
0
      {
518
         // No slashes in filename, so no path present
519
0
         filename = mConfigBasePath + filename;
520
0
         return true;
521
0
      }
522
0
   }
523
0
   return false;
524
0
}
525
526
EncodeStream& 
527
operator<<(EncodeStream& strm, const ConfigParse& config)
528
0
{
529
   // Yes this is horribly inefficient - however it's only used when a user requests it
530
   // and we want to see the items in a sorted list and hash_maps are not sorted.
531
0
   std::multimap<Data, Data> sortedMap;
532
0
   ConfigParse::ConfigValuesMap::const_iterator it = config.mConfigValues.begin();
533
0
   for(; it != config.mConfigValues.end(); it++)
534
0
   {
535
0
      sortedMap.insert(std::multimap<Data, Data>::value_type(it->first, it->second));
536
0
   }
537
0
   std::multimap<Data, Data>::const_iterator it2 = sortedMap.begin();
538
0
   for(; it2 != sortedMap.end(); it2++)
539
0
   {
540
0
      strm << it2->first << " = " << it2->second << endl;
541
0
   }
542
0
   return strm;
543
0
}
544
545
}
546
547
/* ====================================================================
548
 * The Vovida Software License, Version 1.0 
549
 * 
550
 * Copyright (c) 2000 Vovida Networks, Inc.  All rights reserved.
551
 * Copyright (C) 2013-2023 Daniel Pocock https://danielpocock.com
552
 * Copyright (C) 2023 Software Freedom Institute LLC https://softwarefreedom.institute
553
 * 
554
 * Redistribution and use in source and binary forms, with or without
555
 * modification, are permitted provided that the following conditions
556
 * are met:
557
 * 
558
 * 1. Redistributions of source code must retain the above copyright
559
 *    notice, this list of conditions and the following disclaimer.
560
 * 
561
 * 2. Redistributions in binary form must reproduce the above copyright
562
 *    notice, this list of conditions and the following disclaimer in
563
 *    the documentation and/or other materials provided with the
564
 *    distribution.
565
 * 
566
 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
567
 *    and "Vovida Open Communication Application Library (VOCAL)" must
568
 *    not be used to endorse or promote products derived from this
569
 *    software without prior written permission. For written
570
 *    permission, please contact vocal@vovida.org.
571
 *
572
 * 4. Products derived from this software may not be called "VOCAL", nor
573
 *    may "VOCAL" appear in their name, without prior written
574
 *    permission of Vovida Networks, Inc.
575
 * 
576
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
577
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
578
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
579
 * NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL VOVIDA
580
 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
581
 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
582
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
583
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
584
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
585
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
586
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
587
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
588
 * DAMAGE.
589
 * 
590
 * ====================================================================
591
 * 
592
 * This software consists of voluntary contributions made by Vovida
593
 * Networks, Inc. and many individuals on behalf of Vovida Networks,
594
 * Inc.  For more information on Vovida Networks, Inc., please see
595
 * <http://www.vovida.org/>.
596
 *
597
 */