Coverage Report

Created: 2024-09-08 06:24

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