1 /*
2  *  config.cpp - implementation of the main configuration class.
3  *  Copyright (C) 2001, Stefanus Du Toit, Joseph Zupko
4  *            (C) 2003-2006 Alistair Riddoch
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2.1 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  *  Contact:  Joseph Zupko
21  *            jaz147@psu.edu
22  *
23  *            189 Reese St.
24  *            Old Forge, PA 18518
25  */
26 
27 #include <varconf/config.h>
28 
29 #include <cstdio>
30 #include <iostream>
31 #include <fstream>
32 #include <string>
33 
34 #ifdef __WIN32__
35 #include <tchar.h>
36 #define snprintf _snprintf
37 #include <cstdlib>
38 #else // __WIN32__
39 
40 extern char** environ;
41 
42 
43 // on OS-X, the CRT doesn't expose the environ symbol. The following
44 // code (found on Google) provides a value to link against, and a
45 // further tweak in getEnv gets the actual value using _NS evil.
46 #if defined(__APPLE__)
47     #include <crt_externs.h>
48     char **environ = NULL;
49 #endif
50 
51 #endif // __WIN32__
52 
53 namespace {
54   enum state_t {
55     S_EXPECT_NAME, // Expect the start of a name/section/comment
56     S_SECTION, // Parsing a section name
57     S_NAME, // Parsing an item name
58     S_COMMENT, // Parsing a comment
59     S_EXPECT_EQ, // Expect an equal sign
60     S_EXPECT_VALUE, // Expect the start of a value
61     S_VALUE, // Parsing a value
62     S_QUOTED_VALUE, // Parsing a "quoted" value
63     S_EXPECT_EOL // Expect the end of the line
64   };
65 
66   enum ctype_t {
67     C_SPACE, // Whitespace
68     C_NUMERIC, // 0-9
69     C_ALPHA, // a-z, A-Z
70     C_DASH, // '-' and '_'
71     C_EQ, // '='
72     C_QUOTE, // '"'
73     C_SQUARE_OPEN, // '['
74     C_SQUARE_CLOSE, // ']'
75     C_HASH, // '#'
76     C_ESCAPE, // '\' (an "escape")
77     C_EOL, // End of the line
78     C_OTHER // Anything else
79   };
80 
ctype(char c)81   ctype_t ctype(char c)
82   {
83     if (c=='\n') return C_EOL;
84     if (isspace(c)) return C_SPACE;
85     if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) return C_ALPHA;
86     if (isdigit(c)) return C_NUMERIC;
87     if (c == '-' || c == '_') return C_DASH;
88     if (c == '=') return C_EQ;
89     if (c == '"') return C_QUOTE;
90     if (c == '[') return C_SQUARE_OPEN;
91     if (c == ']') return C_SQUARE_CLOSE;
92     if (c == '#') return C_HASH;
93     if (c == '\\') return C_ESCAPE;
94     return C_OTHER;
95   }
96 }
97 
98 namespace varconf {
99 
100 Config* Config::m_instance = 0;
101 
inst()102 Config* Config::inst()
103 {
104   if (m_instance == NULL)
105     m_instance = new Config;
106 
107   return m_instance;
108 }
109 
Config(const Config & conf)110 Config::Config(const Config & conf)
111 {
112   m_conf = conf.m_conf;
113   m_par_lookup = conf.m_par_lookup;
114 }
115 
~Config()116 Config::~Config()
117 {
118   if (m_instance == this)
119     m_instance = NULL;
120 }
121 
operator <<(std::ostream & out,Config & conf)122 std::ostream & operator <<(std::ostream & out, Config & conf)
123 {
124   if (!conf.writeToStream(out, USER)) {
125     conf.sige.emit("\nVarconf Error: error while trying to write "
126                     "configuration data to output stream.\n");
127   }
128 
129   return out;
130 }
131 
operator >>(std::istream & in,Config & conf)132 std::istream & operator >>(std::istream & in, Config & conf)
133 {
134   try {
135     conf.parseStream(in, USER);
136   }
137   catch (ParseError p) {
138     char buf[1024];
139     std::string p_str = p;
140     snprintf(buf, 1024, "\nVarconf Error: parser exception throw while "
141                          "parsing input stream.\n%s", p_str.c_str());
142     conf.sige.emit(buf);
143   }
144 
145   return in;
146 }
147 
operator ==(const Config & one,const Config & two)148 bool operator ==(const Config & one, const Config & two)
149 {
150   if (one.m_conf == two.m_conf && one.m_par_lookup == two.m_par_lookup) {
151     return true;
152   } else {
153     return false;
154   }
155 }
156 
clean(std::string & str)157 void Config::clean(std::string & str)
158 {
159   ctype_t c;
160 
161   for (size_t i = 0; i < str.size(); i++) {
162     c = ctype(str[i]);
163 
164     if (c != C_NUMERIC && c != C_ALPHA && c != C_DASH) {
165       str[i] = '_';
166     } else {
167       str[i] = (char) tolower(str[i]);
168     }
169   }
170 }
171 
erase(const std::string & section,const std::string & key)172 bool Config::erase(const std::string & section, const std::string & key)
173 {
174   if (find(section)) {
175     if (key == "") {
176       m_conf.erase(section);
177       return true;
178     } else if (find(section, key)) {
179       m_conf[section].erase(key);
180       return true;
181     }
182   }
183 
184   return false;
185 }
186 
find(const std::string & section,const std::string & key) const187 bool Config::find(const std::string & section, const std::string & key) const
188 {
189   conf_map::const_iterator I = m_conf.find(section);
190   if (I != m_conf.end()) {
191     if (key == "") {
192       return true;
193     }
194     const sec_map & sectionRef = I->second;
195     sec_map::const_iterator J = sectionRef.find(key);
196     if (J != sectionRef.end()) {
197       return true;
198     }
199   }
200 
201   return false;
202 }
203 
findSection(const std::string & section) const204 bool Config::findSection(const std::string & section) const
205 {
206   return find(section);
207 }
208 
findItem(const std::string & section,const std::string & key) const209 bool Config::findItem(const std::string & section, const std::string & key) const
210 {
211   return find(section, key);
212 }
213 
getCmdline(int argc,char ** argv,Scope scope)214 int Config::getCmdline(int argc, char** argv, Scope scope)
215 {
216   int optind = 1;
217 
218   for (int i = 1; i < argc; i++) {
219     if (argv[i][0] != '-' ) {
220        continue;
221     }
222 
223     std::string section, name, value, arg;
224     bool fnd_sec = false, fnd_nam = false;
225     size_t mark = 2;
226     if (argv[i][1] == '-' && argv[i][2] != '\0') {
227       // long argument
228       arg = argv[i];
229 
230       for (size_t j = 2; j < arg.size(); j++) {
231         if (arg[j] == ':' && arg[j+1] != '\0' && !fnd_sec && !fnd_nam) {
232           section = arg.substr(mark, (j - mark));
233           fnd_sec = true;
234           mark = j + 1;
235         }
236         else if (arg[j] == '=' && (j - mark) > 1) {
237           name = arg.substr(mark, (j - mark));
238           fnd_nam = true;
239           value = arg.substr((j + 1), (arg.size() - (j + 1)));
240           break;
241         }
242       }
243 
244       if (!fnd_nam && (arg.size() - mark) > 0)  {
245         name = arg.substr(mark, (arg.size() - mark));
246       }
247 
248     } else if (argv[i][1] != '-' && argv[i][1] != '\0') {
249       // short argument
250       parameter_map::iterator I = m_par_lookup.find(argv[i][1]);
251 
252       if (I != m_par_lookup.end()) {
253         name = ((*I).second).first;
254         bool needs_value = ((*I).second).second;
255 
256         if (needs_value && (i+1) < argc && argv[i+1][0] != 0
257                         && argv[i+1][0] != '-') {
258           value = argv[++i];
259         }
260         else {
261           char buf[1024];
262           snprintf(buf, 1024, "\nVarconf Warning: short argument \"%s\""
263                                " given on command-line expects a value"
264                                " but none was given.\n", argv[i]);
265           sige.emit(buf);
266         }
267       }
268       else {
269         char buf[1024];
270         snprintf(buf, 1024, "\nVarconf Warning: short argument \"%s\""
271                              " given on command-line does not exist in"
272                              " the lookup table.\n", argv[i]);
273         sige.emit(buf);
274       }
275     }
276 
277     if (!name.empty()) {
278       setItem(section, name, value, scope);
279       optind = i + 1;
280     }
281   }
282   return optind;
283 }
284 
getEnv(const std::string & prefix,Scope scope)285 void Config::getEnv(const std::string & prefix, Scope scope)
286 {
287   std::string name = "", value = "", section = "", env = "";
288   size_t eq_pos = 0;
289 
290 #if defined(__APPLE__)
291   if (environ == NULL)
292       environ = *_NSGetEnviron();
293 #endif
294 
295   for (size_t i = 0; environ[i] != NULL; i++) {
296     env = environ[i];
297 
298     if (env.substr(0, prefix.size()) == prefix) {
299       eq_pos = env.find('=');
300 
301       if (eq_pos != std::string::npos) {
302         name = env.substr(prefix.size(), (eq_pos - prefix.size()));
303         value = env.substr((eq_pos + 1), (env.size() - (eq_pos + 1)));
304       }
305       else {
306         name = env.substr(prefix.size(), (env.size() - prefix.size()));
307         value = "";
308       }
309 
310       setItem(section, name, value, scope);
311     }
312   }
313 }
314 
getSection(const std::string & section)315 const sec_map & Config::getSection(const std::string & section)
316 {
317   // TODO: This will create a new section in the config file. Is really the
318   //  desired behaviour?
319   return m_conf[section];
320 }
321 
getItem(const std::string & section,const std::string & key) const322 Variable Config::getItem(const std::string & section, const std::string & key) const
323 {
324   conf_map::const_iterator I = m_conf.find(section);
325   if (I != m_conf.end()) {
326     std::map<std::string, Variable>::const_iterator J = I->second.find(key);
327     if (J != I->second.end()) {
328       return J->second;
329     }
330   }
331   return Variable();
332 }
333 
getSections() const334 const conf_map& Config::getSections() const
335 {
336 	return m_conf;
337 }
338 
339 
parseStream(std::istream & in,Scope scope)340 void Config::parseStream(std::istream & in, Scope scope) throw (ParseError)
341 {
342   char c;
343   bool escaped = false;
344   size_t line = 1, col = 0;
345   std::string name = "", value = "", section = "";
346   state_t state = S_EXPECT_NAME;
347 
348   while (in.get(c)) {
349     col++;
350     switch (state) {
351       case S_EXPECT_NAME :
352         switch (ctype(c)) {
353           case C_ALPHA:
354           case C_NUMERIC:
355           case C_DASH:
356             state = S_NAME;
357             name = c;
358             break;
359           case C_SQUARE_OPEN:
360             section = "";
361             state = S_SECTION;
362             break;
363           case C_SPACE:
364           case C_EOL:
365             break;
366           case C_HASH:
367             state = S_COMMENT;
368             break;
369           default:
370             throw ParseError("item name", (int) line, (int) col);
371             break;
372         }
373         break;
374       case S_SECTION :
375         switch (ctype(c)) {
376           case C_ALPHA:
377           case C_NUMERIC:
378           case C_DASH:
379             section += c;
380             break;
381           case C_SQUARE_CLOSE:
382             state = S_EXPECT_EOL;
383             break;
384           default:
385             throw ParseError("']'", (int) line, (int) col);
386             break;
387         }
388         break;
389       case S_NAME :
390         switch (ctype(c)) {
391           case C_ALPHA:
392           case C_NUMERIC:
393           case C_DASH:
394             name += c;
395             break;
396           case C_EQ:
397             state = S_EXPECT_VALUE;
398             break;
399           case C_SPACE:
400             state = S_EXPECT_EQ;
401             break;
402           default:
403             throw ParseError("'='", (int) line, (int) col);
404             break;
405         }
406         break;
407       case S_COMMENT :
408         switch (ctype(c)) {
409           case C_EOL:
410             state = S_EXPECT_NAME;
411             break;
412           default:
413             break;
414         }
415         break;
416       case S_EXPECT_EQ:
417         switch (ctype(c)) {
418           case C_SPACE:
419             break;
420           case C_EQ:
421             state = S_EXPECT_VALUE;
422             break;
423           default:
424             throw ParseError("'='", (int) line, (int) col);
425             break;
426         }
427         break;
428       case S_EXPECT_VALUE:
429         switch (ctype(c)) {
430           case C_ALPHA:
431           case C_NUMERIC:
432           case C_DASH:
433             state = S_VALUE;
434             value = c;
435             break;
436           case C_QUOTE:
437             value = "";
438             state = S_QUOTED_VALUE;
439             break;
440           case C_EOL:
441             value = "";
442             state = S_EXPECT_NAME;
443             setItem(section, name, value, scope);
444             break;
445           case C_SPACE:
446             break;
447           default:
448             throw ParseError("value", (int) line, (int) col);
449             break;
450         }
451         break;
452       case S_VALUE:
453         switch (ctype(c)) {
454           case C_QUOTE:
455             throw ParseError("value", (int) line, (int) col);
456           case C_SPACE:
457             state = S_EXPECT_EOL;
458             setItem(section, name, value, scope);
459             break;
460           case C_EOL:
461             state = S_EXPECT_NAME;
462             setItem(section, name, value, scope);
463             break;
464           case C_HASH:
465             state = S_COMMENT;
466             setItem(section, name, value, scope);
467             break;
468           default:
469             value += c;
470             break;
471         }
472         break;
473       case S_QUOTED_VALUE:
474         if (escaped) {
475           value += c;
476           escaped = false;
477         } else {
478           switch (ctype(c)) {
479             case C_QUOTE:
480               state = S_EXPECT_EOL;
481               setItem(section, name, value, scope);
482               break;
483             case C_ESCAPE:
484               escaped = true;
485               break;
486             default:
487               value += c;
488               break;
489           }
490         }
491         break;
492       case S_EXPECT_EOL:
493         switch (ctype(c)) {
494           case C_HASH:
495             state = S_COMMENT;
496             break;
497           case C_EOL:
498             state = S_EXPECT_NAME;
499             break;
500           case C_SPACE:
501             break;
502           default:
503             throw ParseError("end of line", (int) line, (int) col);
504             break;
505         }
506         break;
507       default:
508         break;
509     }
510     if (c == '\n') {
511       line++;
512       col = 0;
513     }
514   } // while (in.get(c))
515 
516   if (state == S_QUOTED_VALUE) {
517     throw ParseError("\"", (int) line, (int) col);
518   }
519 
520   if (state == S_VALUE) {
521     setItem(section, name, value, scope);
522   } else if (state == S_EXPECT_VALUE) {
523     setItem(section, name, "", scope);
524   }
525 }
526 
readFromFile(const std::string & filename,Scope scope)527 bool Config::readFromFile(const std::string & filename, Scope scope)
528 {
529   std::ifstream fin(filename.c_str());
530 
531   if (fin.fail()) {
532     char buf[1024];
533     snprintf(buf, 1024, "\nVarconf Error: could not open configuration file"
534                          " \"%s\" for input.\n", filename.c_str());
535     sige.emit(buf);
536 
537     return false;
538   }
539 
540   try {
541     parseStream(fin, scope);
542   }
543   catch (ParseError p) {
544     char buf[1024];
545     std::string p_str = p;
546     snprintf(buf, 1024, "\nVarconf Error: parsing exception thrown while "
547                        "parsing \"%s\".\n%s", filename.c_str(), p_str.c_str());
548     sige.emit(buf);
549     return false;
550   }
551 
552   return true;
553 }
554 
setItem(const std::string & section,const std::string & key,const Variable & item,Scope scope)555 void Config::setItem(const std::string & section,
556                      const std::string & key,
557                      const Variable & item,
558                      Scope scope)
559 {
560   if (key.empty()) {
561     char buf[1024];
562     snprintf(buf, 1024, "\nVarconf Warning: blank key under section \"%s\""
563                          " sent to setItem() method.\n", section.c_str());
564     sige.emit(buf);
565   }
566   else {
567     std::string sec_clean = section;
568     std::string key_clean = key;
569 
570     clean(sec_clean);
571     clean(key_clean);
572 
573     item->setScope(scope);
574     std::map<std::string, Variable> & section_map = m_conf[sec_clean];
575     std::map<std::string, Variable>::const_iterator I = section_map.find(key_clean);
576     if (I == section_map.end() || I->second != item) {
577       section_map[key_clean] = item;
578     }
579 
580     sig.emit();
581     sigv.emit(sec_clean, key_clean);
582     sigsv.emit(sec_clean, key_clean, *this);
583   }
584 }
585 
setParameterLookup(char s_name,const std::string & l_name,bool value)586 void Config::setParameterLookup(char s_name, const std::string & l_name, bool value)
587 {
588     m_par_lookup[s_name] = std::pair<std::string, bool>(l_name, value);
589 }
590 
writeToFile(const std::string & filename,Scope scope_mask) const591 bool Config::writeToFile(const std::string & filename, Scope scope_mask) const
592 {
593   std::ofstream fout(filename.c_str());
594 
595   if (fout.fail()) {
596     char buf[1024];
597     snprintf(buf, 1024, "\nVarconf Error: could not open configuration file"
598                          " \"%s\" for output.\n", filename.c_str());
599     sige.emit(buf);
600 
601     return false;
602   }
603 
604   return writeToStream(fout, scope_mask);
605 }
606 
writeToStream(std::ostream & out,Scope scope_mask) const607 bool Config::writeToStream(std::ostream & out, Scope scope_mask) const
608 {
609   conf_map::const_iterator I;
610   sec_map::const_iterator J;
611 
612   for (I = m_conf.begin(); I != m_conf.end(); I++) {
613     out << std::endl << "[" << (*I).first << "]\n\n";
614 
615     for (J = (*I).second.begin(); J != (*I).second.end(); J++) {
616       if (J->second->scope() & scope_mask) {
617         out << (*J).first << " = \"" << (*J).second << "\"\n";
618       }
619     }
620   }
621 
622   return true;
623 }
624 
625 } // namespace varconf
626 
627