reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
ConfigParse.cxx
Go to the documentation of this file.
1#include <ctype.h>
2#include <iostream>
3#include <fstream>
4#include <iterator>
5#include <stdexcept>
6#include <sstream>
7#include <map>
8
10#include "rutil/Log.hxx"
11#include "rutil/Logger.hxx"
12#include "rutil/ParseBuffer.hxx"
14
15using namespace resip;
16using namespace std;
17
18#define RESIPROCATE_SUBSYSTEM Subsystem::SIP
19
20namespace resip
21{
22
26
30
31void
32ConfigParse::parseConfig(int argc, char** argv, const resip::Data& defaultConfigFilename)
33{
34 ConfigParse::parseConfig(argc, argv, defaultConfigFilename, 0);
35}
36
37void
38ConfigParse::parseConfig(int argc, char** argv, const resip::Data& defaultConfigFilename, int skipCount)
39{
40 parseCommandLine(argc, argv, skipCount); // will fill in mCmdLineConfigFilename if present
42 {
43 parseConfigFile(defaultConfigFilename);
44 }
45 else
46 {
48 }
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 for(
53 ConfigValuesMap::iterator it = mCmdLineConfigValues.begin();
54 it != mCmdLineConfigValues.end();
55 it++)
56 {
57 if(mConfigValues.find(it->first) != mConfigValues.end())
58 {
59 mConfigValues.erase(it->first);
60 }
61 mConfigValues.insert(ConfigValuesMap::value_type(it->first, it->second));
62 }
63}
64
65void
66ConfigParse::parseCommandLine(int argc, char** argv, int skipCount)
67{
68 int startingArgForNameValuePairs = 1 + skipCount;
69 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 if(argc >= (startingArgForNameValuePairs + 1) && firstArg[0] != '-')
75#endif
76 {
77 mCmdLineConfigFilename = firstArg;
78 startingArgForNameValuePairs++;
79 }
80
81 // Loop through command line arguments and process them
82 for(int i = startingArgForNameValuePairs; i < argc; i++)
83 {
84 Data argData(argv[i]);
85
86 // Process all commandNames that don't take values
87 if(isEqualNoCase(argData, "-?") ||
88 isEqualNoCase(argData, "--?") ||
89 isEqualNoCase(argData, "--help") ||
90 isEqualNoCase(argData, "/?"))
91 {
92 printHelpText(argc, argv);
93 throw Exception("Help text requested - process stopping", __FILE__, __LINE__);
94 }
95 else if(argData.at(0) == '-' || argData.at(0) == '/')
96 {
97 Data name;
98 Data value;
99 ParseBuffer pb(argData);
100
101 try
102 {
103 pb.skipChars(Data::toBitset("-/")); // Skip any leading -'s or /'s
104 const char * anchor = pb.position();
105 pb.skipToOneOf("=:");
106 if(!pb.eof())
107 {
108 pb.data(name, anchor);
109 pb.skipChar();
110 anchor = pb.position();
111 pb.skipToEnd();
112 pb.data(value, anchor);
113
114 //cout << "Command line Name='" << name << "' value='" << value << "'" << endl;
115 insertConfigValue("command line", mCmdLineConfigValues, name, value);
116 }
117 else
118 {
119 cerr << "Invalid command line parameters:" << endl;
120 cerr << " Name/Value pairs must contain an = or a : between the name and the value" << endl;
121 cerr << " Bad argument: " << argData << endl;
122 Data exceptionString("Name/Value pairs must contain an = or a : between the name and the value (Bad argument: " + argData + ")");
123 throw Exception(exceptionString, __FILE__, __LINE__);
124 }
125 }
126 catch(BaseException& ex)
127 {
128 cerr << "Invalid command line parameters:" << endl;
129 cerr << " Exception parsing Name/Value pairs: " << ex << endl;
130 cerr << " Bad argument: " << argData << endl;
131 throw;
132 }
133 }
134 else
135 {
136 cerr << "Invalid command line parameters:" << endl;
137 cerr << " Name/Value pairs must be prefixed with either a -, --, or a /" << endl;
138 cerr << " Bad argument: " << argData << endl;
139 Data exceptionString("Name/Value pairs must be prefixed with either a -, --, or a / (Bad argument: " + argData + ")");
140 throw Exception(exceptionString, __FILE__, __LINE__);
141 }
142 }
143}
144
145void
147{
148 // Store off base config path
149 ParseBuffer pb(filename);
150 const char* anchor = pb.start();
151 pb.skipToEnd();
152 pb.skipBackToOneOf("/\\");
153 if(!pb.bof())
154 {
155 mConfigBasePath = pb.data(pb.start());
156 }
157
158 ifstream configFile(filename.c_str());
159
160 if(!configFile)
161 {
162 Data exceptionString("Error opening/reading configuration file: " + filename);
163 throw Exception(exceptionString, __FILE__, __LINE__);
164 }
165
166 string sline;
167 while(getline(configFile, sline))
168 {
169 Data name;
170 Data value;
171 ParseBuffer pb(sline.c_str(), sline.size());
172
173 pb.skipWhitespace();
174 anchor = pb.position();
175 if(pb.eof() || *anchor == '#') continue; // if line is a comment or blank then skip it
176
177 // Look for end of name
178 pb.skipToOneOf("= \t");
179 pb.data(name,anchor);
180 if(*pb.position()!='=')
181 {
182 pb.skipToChar('=');
183 }
184 pb.skipChar('=');
185 pb.skipWhitespace();
186 anchor = pb.position();
187 if(!pb.eof())
188 {
189 pb.skipToOneOf("\r\n");
190 pb.data(value, anchor);
191 }
192 //cout << "Config file Name='" << name << "' value='" << value << "'" << endl;
193 Data lowerName(name);
194 lowerName.lowercase();
195 if(lowerName == "include")
196 {
197 parseConfigFile(value);
198 }
199 else
200 {
201 insertConfigValue("config file", mFileConfigValues, name, value);
202 }
203 }
204}
205
206void
207ConfigParse::getConfigIndexKeys(const resip::Data& indexName, std::set<Data>& keys) const
208{
209 Data::size_type numPos = indexName.size();
210 Data indexNameLower(indexName);
211 indexNameLower.lowercase();
212 ConfigValuesMap::const_iterator it = mConfigValues.begin();
213 for(; it != mConfigValues.end(); it++)
214 {
215 const Data& keyName = it->first;
216 if(keyName.prefix(indexNameLower) && keyName.size() > numPos
217 && isdigit(static_cast< unsigned char >(keyName[numPos])))
218 {
219 Data::size_type i = numPos + 1;
220 while(i < keyName.size() && isdigit(static_cast< unsigned char >(keyName[i])))
221 {
222 i++;
223 }
224 Data indexFullName = keyName.substr(0, i);
225 if(keys.find(indexFullName) == keys.end())
226 {
227 keys.insert(indexFullName);
228 }
229 }
230 }
231}
232
233bool
235{
236 Data lowerName(name); lowerName.lowercase();
237 ConfigValuesMap::const_iterator it = mConfigValues.find(lowerName);
238 if(it != mConfigValues.end())
239 {
240 value = it->second;
241 return true;
242 }
243 // Not found
244 return false;
245}
246
247Data
248ConfigParse::getConfigData(const resip::Data& name, const resip::Data& defaultValue, bool useDefaultIfEmpty) const
249{
250 Data ret(defaultValue);
251 if(getConfigValue(name, ret) && ret.empty() && useDefaultIfEmpty)
252 {
253 return defaultValue;
254 }
255 return ret;
256}
257
258bool
259ConfigParse::getConfigValue(const resip::Data& name, bool &value) const
260{
261 Data lowerName(name); lowerName.lowercase();
262 ConfigValuesMap::const_iterator it = mConfigValues.find(lowerName);
263 if(it != mConfigValues.end())
264 {
265 if(it->second == "1" ||
266 isEqualNoCase(it->second, "true") ||
267 isEqualNoCase(it->second, "on") ||
268 isEqualNoCase(it->second, "enable"))
269 {
270 value = true;
271 return true;
272 }
273 else if(it->second == "0" ||
274 isEqualNoCase(it->second, "false") ||
275 isEqualNoCase(it->second, "off") ||
276 isEqualNoCase(it->second, "disable"))
277 {
278 value = false;
279 return true;
280 }
281 cerr << "Invalid boolean setting: " << name << " = " << it->second << ": Valid values are: 1,true,on,enable,0,false,off or disable" << endl;
282 return false;
283 }
284 // Not found
285 return false;
286}
287
288bool
289ConfigParse::getConfigBool(const resip::Data& name, bool defaultValue) const
290{
291 bool ret = defaultValue;
292 getConfigValue(name, ret);
293 return ret;
294}
295
296bool
297ConfigParse::getConfigValue(const resip::Data& name, unsigned long &value) const
298{
299 Data lowerName(name); lowerName.lowercase();
300 ConfigValuesMap::const_iterator it = mConfigValues.find(lowerName);
301 if(it != mConfigValues.end())
302 {
303 value = it->second.convertUnsignedLong();
304 return true;
305 }
306 // Not found
307 return false;
308}
309
310unsigned long
311ConfigParse::getConfigUnsignedLong(const resip::Data& name, unsigned long defaultValue) const
312{
313 unsigned long ret = defaultValue;
314 getConfigValue(name, ret);
315 return ret;
316}
317
318bool
319ConfigParse::getConfigValue(const resip::Data& name, int &value) const
320{
321 Data lowerName(name); lowerName.lowercase();
322 ConfigValuesMap::const_iterator it = mConfigValues.find(lowerName);
323 if(it != mConfigValues.end())
324 {
325 value = it->second.convertInt();
326 return true;
327 }
328 // Not found
329 return false;
330}
331
332
333int
334ConfigParse::getConfigInt(const resip::Data& name, int defaultValue) const
335{
336 int ret = defaultValue;
337 getConfigValue(name, ret);
338 return ret;
339}
340
341bool
342ConfigParse::getConfigValue(const resip::Data& name, unsigned short &value) const
343{
344 Data lowerName(name); lowerName.lowercase();
345 ConfigValuesMap::const_iterator it = mConfigValues.find(lowerName);
346 if(it != mConfigValues.end())
347 {
348 value = it->second.convertInt();
349 return true;
350 }
351 // Not found
352 return false;
353}
354
355
356unsigned short
357ConfigParse::getConfigUnsignedShort(const resip::Data& name, int defaultValue) const
358{
359 int ret = defaultValue;
360 getConfigValue(name, ret);
361 return ret;
362}
363
364bool
365ConfigParse::getConfigValue(const resip::Data& name, std::vector<resip::Data> &value) const
366{
367 Data lowerName(name); lowerName.lowercase();
368 std::pair<ConfigValuesMap::const_iterator,ConfigValuesMap::const_iterator> valuesIts = mConfigValues.equal_range(lowerName);
369 bool found = false;
370 for (ConfigValuesMap::const_iterator it=valuesIts.first; it!=valuesIts.second; ++it)
371 {
372 found = true;
373 ParseBuffer pb(it->second);
374 Data item;
375 while(!it->second.empty() && !pb.eof())
376 {
377 pb.skipWhitespace();
378 const char *start = pb.position();
379 pb.skipToOneOf(ParseBuffer::Whitespace, ","); // allow white space
380 pb.data(item, start);
381 value.push_back(item);
382 if(!pb.eof())
383 {
384 pb.skipChar();
385 }
386 }
387 }
388
389 return found;
390}
391
392bool
393ConfigParse::getConfigValue(const resip::Data& name, std::vector<int> &value) const
394{
395 std::vector<Data> _value;
396 if(!getConfigValue(name, _value))
397 {
398 return false;
399 }
400
401 for(auto v : _value)
402 {
403 value.push_back(v.convertInt());
404 }
405
406 return true;
407}
408
409bool
410ConfigParse::getConfigValue(const resip::Data& name, std::set<resip::Data> &value) const
411{
412 Data lowerName(name); lowerName.lowercase();
413 std::pair<ConfigValuesMap::const_iterator,ConfigValuesMap::const_iterator> valuesIts = mConfigValues.equal_range(lowerName);
414 bool found = false;
415 for (ConfigValuesMap::const_iterator it=valuesIts.first; it!=valuesIts.second; ++it)
416 {
417 found = true;
418 ParseBuffer pb(it->second);
419 Data item;
420 while(!it->second.empty() && !pb.eof())
421 {
422 pb.skipWhitespace();
423 const char *start = pb.position();
424 pb.skipToOneOf(ParseBuffer::Whitespace, ","); // allow white space
425 pb.data(item, start);
426 value.insert(item);
427 if(!pb.eof())
428 {
429 pb.skipChar();
430 }
431 }
432 }
433
434 return found;
435}
436
439{
441 Data::size_type numPos = mapsPrefix.size();
442 Data mapsPrefixLower(mapsPrefix);
443 mapsPrefixLower.lowercase();
444 ConfigValuesMap::const_iterator it = mConfigValues.begin();
445 for(; it != mConfigValues.end(); it++)
446 {
447 const Data& keyName = it->first;
448 if(keyName.prefix(mapsPrefixLower) && keyName.size() > numPos
449 && isdigit(static_cast< unsigned char >(keyName[numPos])))
450 {
451 Data::size_type i = numPos + 1;
452 while(i < keyName.size() && isdigit(static_cast< unsigned char >(keyName[i])))
453 {
454 i++;
455 }
456 if(keyName.size() - i < 1)
457 {
458 stringstream err_text;
459 err_text << "Configuration key " << keyName << " missing subkey name";
460 Data err_data(err_text.str());
461 throw Exception(err_data, __FILE__, __LINE__);
462 }
463 Data index = keyName.substr(numPos, i - numPos);
464 Data nestedKey = keyName.substr(i, keyName.size() - i);
465 NestedConfigParse& nested = m[index.convertInt()];
466 nested.insertConfigValue(nestedKey, it->second);
467 }
468 }
469 return m;
470}
471
472void
473ConfigParse::insertConfigValue(const Data& source, ConfigValuesMap& configValues, const resip::Data& name, const resip::Data& value)
474{
475 resip::Data lowerName(name);
476 lowerName.lowercase();
477 if(configValues.find(lowerName) != configValues.end())
478 {
479 stringstream err_text;
480 err_text << "Duplicate configuration key " << name << " while parsing " << source;
481 Data err_data(err_text.str());
482 throw Exception(err_data, __FILE__, __LINE__);
483 }
484 configValues.insert(ConfigValuesMap::value_type(lowerName, value));
485}
486
487void
489{
490 insertConfigValue("manually added setting", mConfigValues, name, value);
491}
492
494ConfigParse::removePath(const resip::Data& fileAndPath) const
495{
496 Data filenameOnly;
497 ParseBuffer pb(fileAndPath);
498 const char* anchor = pb.position();
499 while(pb.skipToOneOf("/\\") && !pb.eof())
500 {
501 pb.skipChar();
502 anchor = pb.position();
503 }
504 pb.data(filenameOnly, anchor);
505 return filenameOnly;
506}
507
508bool
510{
511 if(!filename.empty())
512 {
513 // If filename already has a path specified, then don't touch it
514 ParseBuffer pb(filename);
515 pb.skipToOneOf("/\\");
516 if(pb.eof())
517 {
518 // No slashes in filename, so no path present
519 filename = mConfigBasePath + filename;
520 return true;
521 }
522 }
523 return false;
524}
525
527operator<<(EncodeStream& strm, const ConfigParse& config)
528{
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 std::multimap<Data, Data> sortedMap;
532 ConfigParse::ConfigValuesMap::const_iterator it = config.mConfigValues.begin();
533 for(; it != config.mConfigValues.end(); it++)
534 {
535 sortedMap.insert(std::multimap<Data, Data>::value_type(it->first, it->second));
536 }
537 std::multimap<Data, Data>::const_iterator it2 = sortedMap.begin();
538 for(; it2 != sortedMap.end(); it2++)
539 {
540 strm << it2->first << " = " << it2->second << endl;
541 }
542 return strm;
543}
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 */
The abstract base-class for all exceptions thrown by resip code.
unsigned short getConfigUnsignedShort(const resip::Data &name, int defaultValue) const
ConfigValuesMap mConfigValues
bool AddBasePathIfRequired(Data &filename) const
virtual void parseConfig(int argc, char **argv, const resip::Data &defaultConfigFilename)
void insertConfigValue(const resip::Data &name, const resip::Data &value)
HashMultiMap< resip::Data, resip::Data > ConfigValuesMap
resip::Data mConfigBasePath
ConfigValuesMap mCmdLineConfigValues
virtual void printHelpText(int argc, char **argv)=0
virtual void parseCommandLine(int argc, char **argv, int skipCount=0)
int getConfigInt(const resip::Data &name, int defaultValue) const
virtual void parseConfigFile(const resip::Data &filename)
NestedConfigMap getConfigNested(const resip::Data &mapsPrefix) const
resip::Data removePath(const resip::Data &fileAndPath) const
void getConfigIndexKeys(const resip::Data &indexName, std::set< resip::Data > &keys) const
resip::Data getConfigData(const resip::Data &name, const resip::Data &defaultValue, bool useDefaultIfEmpty=false) const
resip::Data mCmdLineConfigFilename
HashMap< int, NestedConfigParse > NestedConfigMap
bool getConfigValue(const resip::Data &name, resip::Data &value) const
unsigned long getConfigUnsignedLong(const resip::Data &name, unsigned long defaultValue) const
ConfigValuesMap mFileConfigValues
bool getConfigBool(const resip::Data &name, bool defaultValue) const
An alternative to std::string, encapsulates an arbitrary buffer of bytes.
Definition Data.hxx:90
const char * c_str() const
Returns a null-terminated string representing.
Definition Data.cxx:1097
char & at(size_type p)
Returns the character at the specified position.
Definition Data.cxx:988
bool prefix(const Data &pre) const noexcept
Returns true if this Data starts with the bytes indicated by the passed-in Data.
Definition Data.cxx:1888
Data substr(size_type first, size_type count=Data::npos) const
Copies a portion of this Data into a new Data.
Definition Data.cxx:1932
bool empty() const noexcept
Checks whether the Data is empty.
Definition Data.hxx:591
Data & lowercase()
Converts this Data to lowercase.
Definition Data.cxx:1635
size_t size_type
Definition Data.hxx:94
size_type size() const noexcept
Returns the number of bytes in this Data.
Definition Data.hxx:599
size_type find(const Data &match, size_type start=0) const
Finds a specified sequence of bytes in this Data.
Definition Data.cxx:1947
const char * begin() const noexcept
Returns a pointer to the beginning of the buffer used by the Data.
Definition Data.hxx:642
int convertInt() const
Takes the contents of the Data and converts them to an integer.
Definition Data.cxx:1684
static std::bitset< 256 > toBitset(const resip::Data &chars)
Creates a bitset reflecting the contents of this data (as a set) ie.
Definition Data.cxx:2477
Provides string-parsing functionality with protection from buffer overflow.
Pointer start() const
CurrentPosition skipToEnd()
CurrentPosition skipWhitespace()
CurrentPosition skipToChar(char c)
CurrentPosition position() const
CurrentPosition skipChars(const char *cs)
CurrentPosition skipToOneOf(const char *cs)
CurrentPosition skipChar()
void data(Data &data, const char *start) const
make the passed in data share memory with the buffer (uses Data::Share)
const char * skipBackToOneOf(const char *cs)
static const char * Whitespace
dcm! – add UnusedChecking(_enum) below;
bool isEqualNoCase(const Data &left, const Data &right)
Definition Data.hxx:1116
EncodeStream & operator<<(EncodeStream &strm, const BaseException &e)
#define EncodeStream