Merge remote-tracking branch 'upstream/debian/sid' into debian/sid
[ntk/apt.git] / apt-pkg / contrib / configuration.cc
CommitLineData
6c139d6e
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
5e14c452 3// $Id: configuration.cc,v 1.28 2004/04/30 04:00:15 mdz Exp $
6c139d6e
AL
4/* ######################################################################
5
6 Configuration Class
7
8 This class provides a configuration file and command line parser
9 for a tree-oriented configuration environment. All runtime configuration
10 is stored in here.
7da2b375
AL
11
12 This source is placed in the Public Domain, do with it what you will
13 It was originally written by Jason Gunthorpe <jgg@debian.org>.
6c139d6e
AL
14
15 ##################################################################### */
16 /*}}}*/
17// Include files /*{{{*/
ea542140
DK
18#include <config.h>
19
094a497d 20#include <apt-pkg/configuration.h>
08e8f724 21#include <apt-pkg/error.h>
cdcc6d34 22#include <apt-pkg/strutl.h>
b2e465d6 23#include <apt-pkg/fileutl.h>
453b82a3 24#include <apt-pkg/macros.h>
6c139d6e 25
453b82a3
DK
26#include <ctype.h>
27#include <regex.h>
28#include <stddef.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <algorithm>
33#include <string>
b2e465d6 34#include <vector>
b2e465d6 35#include <fstream>
851a45a8 36
ea542140
DK
37#include <apti18n.h>
38
851a45a8 39using namespace std;
6c139d6e 40 /*}}}*/
9c14e3d6
AL
41
42Configuration *_config = new Configuration;
6c139d6e
AL
43
44// Configuration::Configuration - Constructor /*{{{*/
45// ---------------------------------------------------------------------
46/* */
b2e465d6 47Configuration::Configuration() : ToFree(true)
6c139d6e
AL
48{
49 Root = new Item;
b2e465d6
AL
50}
51Configuration::Configuration(const Item *Root) : Root((Item *)Root), ToFree(false)
52{
d3e8fbb3 53}
b2e465d6
AL
54 /*}}}*/
55// Configuration::~Configuration - Destructor /*{{{*/
56// ---------------------------------------------------------------------
57/* */
58Configuration::~Configuration()
59{
60 if (ToFree == false)
61 return;
62
63 Item *Top = Root;
64 for (; Top != 0;)
65 {
66 if (Top->Child != 0)
67 {
68 Top = Top->Child;
69 continue;
70 }
71
72 while (Top != 0 && Top->Next == 0)
73 {
74 Item *Parent = Top->Parent;
75 delete Top;
76 Top = Parent;
77 }
78 if (Top != 0)
79 {
80 Item *Next = Top->Next;
81 delete Top;
82 Top = Next;
83 }
84 }
6c139d6e
AL
85}
86 /*}}}*/
0a8a80e5 87// Configuration::Lookup - Lookup a single item /*{{{*/
6c139d6e
AL
88// ---------------------------------------------------------------------
89/* This will lookup a single item by name below another item. It is a
90 helper function for the main lookup function */
91Configuration::Item *Configuration::Lookup(Item *Head,const char *S,
ce857f32 92 unsigned long const &Len,bool const &Create)
6c139d6e
AL
93{
94 int Res = 1;
95 Item *I = Head->Child;
96 Item **Last = &Head->Child;
9c14e3d6 97
7f25bdff
AL
98 // Empty strings match nothing. They are used for lists.
99 if (Len != 0)
100 {
101 for (; I != 0; Last = &I->Next, I = I->Next)
851a45a8 102 if ((Res = stringcasecmp(I->Tag,S,S + Len)) == 0)
7f25bdff
AL
103 break;
104 }
105 else
106 for (; I != 0; Last = &I->Next, I = I->Next);
107
6c139d6e
AL
108 if (Res == 0)
109 return I;
110 if (Create == false)
111 return 0;
112
113 I = new Item;
171c75f1 114 I->Tag.assign(S,Len);
6c139d6e 115 I->Next = *Last;
9c14e3d6 116 I->Parent = Head;
6c139d6e
AL
117 *Last = I;
118 return I;
119}
120 /*}}}*/
121// Configuration::Lookup - Lookup a fully scoped item /*{{{*/
122// ---------------------------------------------------------------------
123/* This performs a fully scoped lookup of a given name, possibly creating
124 new items */
ce857f32 125Configuration::Item *Configuration::Lookup(const char *Name,bool const &Create)
6c139d6e 126{
0a8a80e5
AL
127 if (Name == 0)
128 return Root->Child;
129
6c139d6e
AL
130 const char *Start = Name;
131 const char *End = Start + strlen(Name);
132 const char *TagEnd = Name;
133 Item *Itm = Root;
7f25bdff 134 for (; End - TagEnd >= 2; TagEnd++)
6c139d6e
AL
135 {
136 if (TagEnd[0] == ':' && TagEnd[1] == ':')
137 {
138 Itm = Lookup(Itm,Start,TagEnd - Start,Create);
139 if (Itm == 0)
140 return 0;
141 TagEnd = Start = TagEnd + 2;
142 }
143 }
144
7f25bdff
AL
145 // This must be a trailing ::, we create unique items in a list
146 if (End - Start == 0)
147 {
148 if (Create == false)
149 return 0;
150 }
151
6c139d6e 152 Itm = Lookup(Itm,Start,End - Start,Create);
6c139d6e
AL
153 return Itm;
154}
155 /*}}}*/
156// Configuration::Find - Find a value /*{{{*/
157// ---------------------------------------------------------------------
158/* */
b2e465d6 159string Configuration::Find(const char *Name,const char *Default) const
6c139d6e 160{
b2e465d6 161 const Item *Itm = Lookup(Name);
6c139d6e 162 if (Itm == 0 || Itm->Value.empty() == true)
9c14e3d6
AL
163 {
164 if (Default == 0)
171c75f1 165 return "";
9c14e3d6
AL
166 else
167 return Default;
168 }
169
6c139d6e
AL
170 return Itm->Value;
171}
172 /*}}}*/
3b5421b4 173// Configuration::FindFile - Find a Filename /*{{{*/
9c14e3d6
AL
174// ---------------------------------------------------------------------
175/* Directories are stored as the base dir in the Parent node and the
3b5421b4 176 sub directory in sub nodes with the final node being the end filename
9c14e3d6 177 */
b2e465d6 178string Configuration::FindFile(const char *Name,const char *Default) const
9c14e3d6 179{
db2cca11 180 const Item *RootItem = Lookup("RootDir");
ec76891f
DK
181 std::string result = (RootItem == 0) ? "" : RootItem->Value;
182 if(result.empty() == false && result[result.size() - 1] != '/')
183 result.push_back('/');
db2cca11 184
b2e465d6 185 const Item *Itm = Lookup(Name);
9c14e3d6
AL
186 if (Itm == 0 || Itm->Value.empty() == true)
187 {
ec76891f
DK
188 if (Default != 0)
189 result.append(Default);
9c14e3d6 190 }
ec76891f 191 else
017f9fd6 192 {
ec76891f
DK
193 string val = Itm->Value;
194 while (Itm->Parent != 0)
017f9fd6 195 {
ec76891f
DK
196 if (Itm->Parent->Value.empty() == true)
197 {
198 Itm = Itm->Parent;
199 continue;
200 }
017f9fd6 201
ec76891f
DK
202 // Absolute
203 if (val.length() >= 1 && val[0] == '/')
af13d143
DK
204 {
205 if (val.compare(0, 9, "/dev/null") == 0)
206 val.erase(9);
ec76891f 207 break;
af13d143 208 }
b2e465d6 209
ec76891f
DK
210 // ~/foo or ./foo
211 if (val.length() >= 2 && (val[0] == '~' || val[0] == '.') && val[1] == '/')
212 break;
213
214 // ../foo
215 if (val.length() >= 3 && val[0] == '.' && val[1] == '.' && val[2] == '/')
216 break;
217
218 if (Itm->Parent->Value.end()[-1] != '/')
219 val.insert(0, "/");
220
221 val.insert(0, Itm->Parent->Value);
222 Itm = Itm->Parent;
223 }
224 result.append(val);
b2e465d6
AL
225 }
226
ec76891f
DK
227 // do some normalisation by removing // and /./ from the path
228 size_t found = string::npos;
229 while ((found = result.find("/./")) != string::npos)
230 result.replace(found, 3, "/");
231 while ((found = result.find("//")) != string::npos)
232 result.replace(found, 2, "/");
233
234 return result;
9c14e3d6
AL
235}
236 /*}}}*/
3b5421b4
AL
237// Configuration::FindDir - Find a directory name /*{{{*/
238// ---------------------------------------------------------------------
239/* This is like findfile execept the result is terminated in a / */
b2e465d6 240string Configuration::FindDir(const char *Name,const char *Default) const
3b5421b4
AL
241{
242 string Res = FindFile(Name,Default);
243 if (Res.end()[-1] != '/')
af13d143
DK
244 {
245 size_t const found = Res.rfind("/dev/null");
246 if (found != string::npos && found == Res.size() - 9)
247 return Res; // /dev/null returning
3b5421b4 248 return Res + '/';
af13d143 249 }
3b5421b4
AL
250 return Res;
251}
252 /*}}}*/
50c409c4
DK
253// Configuration::FindVector - Find a vector of values /*{{{*/
254// ---------------------------------------------------------------------
255/* Returns a vector of config values under the given item */
a5414e56
DK
256#if (APT_PKG_MAJOR >= 4 && APT_PKG_MINOR < 13)
257vector<string> Configuration::FindVector(const char *Name) const { return FindVector(Name, ""); }
258#endif
259vector<string> Configuration::FindVector(const char *Name, std::string const &Default) const
50c409c4
DK
260{
261 vector<string> Vec;
262 const Item *Top = Lookup(Name);
263 if (Top == NULL)
a5414e56
DK
264 return VectorizeString(Default, ',');
265
266 if (Top->Value.empty() == false)
267 return VectorizeString(Top->Value, ',');
50c409c4
DK
268
269 Item *I = Top->Child;
270 while(I != NULL)
271 {
272 Vec.push_back(I->Value);
273 I = I->Next;
274 }
a5414e56
DK
275 if (Vec.empty() == true)
276 return VectorizeString(Default, ',');
277
50c409c4
DK
278 return Vec;
279}
280 /*}}}*/
6c139d6e
AL
281// Configuration::FindI - Find an integer value /*{{{*/
282// ---------------------------------------------------------------------
283/* */
ce857f32 284int Configuration::FindI(const char *Name,int const &Default) const
6c139d6e 285{
b2e465d6 286 const Item *Itm = Lookup(Name);
6c139d6e
AL
287 if (Itm == 0 || Itm->Value.empty() == true)
288 return Default;
289
290 char *End;
291 int Res = strtol(Itm->Value.c_str(),&End,0);
292 if (End == Itm->Value.c_str())
293 return Default;
294
08e8f724
AL
295 return Res;
296}
297 /*}}}*/
298// Configuration::FindB - Find a boolean type /*{{{*/
299// ---------------------------------------------------------------------
300/* */
ce857f32 301bool Configuration::FindB(const char *Name,bool const &Default) const
08e8f724 302{
b2e465d6 303 const Item *Itm = Lookup(Name);
08e8f724
AL
304 if (Itm == 0 || Itm->Value.empty() == true)
305 return Default;
306
3b5421b4 307 return StringToBool(Itm->Value,Default);
6c139d6e
AL
308}
309 /*}}}*/
b2e465d6
AL
310// Configuration::FindAny - Find an arbitrary type /*{{{*/
311// ---------------------------------------------------------------------
312/* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
313string Configuration::FindAny(const char *Name,const char *Default) const
314{
315 string key = Name;
316 char type = 0;
317
318 if (key.size() > 2 && key.end()[-2] == '/')
319 {
320 type = key.end()[-1];
321 key.resize(key.size() - 2);
322 }
323
324 switch (type)
325 {
326 // file
327 case 'f':
328 return FindFile(key.c_str(), Default);
329
330 // directory
331 case 'd':
332 return FindDir(key.c_str(), Default);
333
334 // bool
335 case 'b':
336 return FindB(key, Default) ? "true" : "false";
337
338 // int
339 case 'i':
340 {
341 char buf[16];
cc99a102 342 snprintf(buf, sizeof(buf)-1, "%d", FindI(key, Default ? atoi(Default) : 0 ));
b2e465d6
AL
343 return buf;
344 }
345 }
346
347 // fallback
348 return Find(Name, Default);
349}
350 /*}}}*/
351// Configuration::CndSet - Conditinal Set a value /*{{{*/
352// ---------------------------------------------------------------------
353/* This will not overwrite */
171c75f1 354void Configuration::CndSet(const char *Name,const string &Value)
b2e465d6
AL
355{
356 Item *Itm = Lookup(Name,true);
357 if (Itm == 0)
358 return;
359 if (Itm->Value.empty() == true)
360 Itm->Value = Value;
361}
362 /*}}}*/
78485ab2
DK
363// Configuration::Set - Set an integer value /*{{{*/
364// ---------------------------------------------------------------------
365/* */
366void Configuration::CndSet(const char *Name,int const Value)
367{
368 Item *Itm = Lookup(Name,true);
369 if (Itm == 0 || Itm->Value.empty() == false)
370 return;
371 char S[300];
372 snprintf(S,sizeof(S),"%i",Value);
373 Itm->Value = S;
374}
375 /*}}}*/
6c139d6e
AL
376// Configuration::Set - Set a value /*{{{*/
377// ---------------------------------------------------------------------
378/* */
171c75f1 379void Configuration::Set(const char *Name,const string &Value)
6c139d6e
AL
380{
381 Item *Itm = Lookup(Name,true);
382 if (Itm == 0)
383 return;
384 Itm->Value = Value;
385}
386 /*}}}*/
387// Configuration::Set - Set an integer value /*{{{*/
388// ---------------------------------------------------------------------
389/* */
0df8c3dc 390void Configuration::Set(const char *Name,int const &Value)
6c139d6e
AL
391{
392 Item *Itm = Lookup(Name,true);
393 if (Itm == 0)
394 return;
395 char S[300];
396 snprintf(S,sizeof(S),"%i",Value);
397 Itm->Value = S;
75ef8f14
MV
398}
399 /*}}}*/
400// Configuration::Clear - Clear an single value from a list /*{{{*/
401// ---------------------------------------------------------------------
402/* */
ce857f32 403void Configuration::Clear(string const &Name, int const &Value)
75ef8f14
MV
404{
405 char S[300];
406 snprintf(S,sizeof(S),"%i",Value);
407 Clear(Name, S);
408}
409 /*}}}*/
410// Configuration::Clear - Clear an single value from a list /*{{{*/
411// ---------------------------------------------------------------------
412/* */
ce857f32 413void Configuration::Clear(string const &Name, string const &Value)
75ef8f14
MV
414{
415 Item *Top = Lookup(Name.c_str(),false);
416 if (Top == 0 || Top->Child == 0)
417 return;
418
419 Item *Tmp, *Prev, *I;
420 Prev = I = Top->Child;
421
422 while(I != NULL)
423 {
424 if(I->Value == Value)
425 {
426 Tmp = I;
427 // was first element, point parent to new first element
428 if(Top->Child == Tmp)
429 Top->Child = I->Next;
430 I = I->Next;
431 Prev->Next = I;
432 delete Tmp;
433 } else {
434 Prev = I;
435 I = I->Next;
436 }
437 }
438
d8a06f6e
MV
439}
440 /*}}}*/
441// Configuration::Clear - Clear everything /*{{{*/
442// ---------------------------------------------------------------------
443void Configuration::Clear()
444{
445 const Configuration::Item *Top = Tree(0);
446 while( Top != 0 )
447 {
448 Clear(Top->FullTag());
449 Top = Top->Next;
450 }
6c139d6e
AL
451}
452 /*}}}*/
b2e465d6
AL
453// Configuration::Clear - Clear an entire tree /*{{{*/
454// ---------------------------------------------------------------------
455/* */
ce857f32 456void Configuration::Clear(string const &Name)
b2e465d6
AL
457{
458 Item *Top = Lookup(Name.c_str(),false);
75ef8f14 459 if (Top == 0)
b2e465d6 460 return;
75ef8f14 461
171c75f1 462 Top->Value.clear();
b2e465d6
AL
463 Item *Stop = Top;
464 Top = Top->Child;
465 Stop->Child = 0;
466 for (; Top != 0;)
467 {
468 if (Top->Child != 0)
469 {
470 Top = Top->Child;
471 continue;
472 }
473
474 while (Top != 0 && Top->Next == 0)
475 {
476 Item *Tmp = Top;
477 Top = Top->Parent;
478 delete Tmp;
479
480 if (Top == Stop)
481 return;
482 }
483
484 Item *Tmp = Top;
485 if (Top != 0)
486 Top = Top->Next;
487 delete Tmp;
488 }
489}
490 /*}}}*/
08e8f724
AL
491// Configuration::Exists - Returns true if the Name exists /*{{{*/
492// ---------------------------------------------------------------------
493/* */
b2e465d6 494bool Configuration::Exists(const char *Name) const
08e8f724 495{
b2e465d6 496 const Item *Itm = Lookup(Name);
08e8f724
AL
497 if (Itm == 0)
498 return false;
499 return true;
500}
501 /*}}}*/
b2e465d6
AL
502// Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
503// ---------------------------------------------------------------------
504/* qualified by /[fdbi] exists */
505bool Configuration::ExistsAny(const char *Name) const
506{
507 string key = Name;
508
8d998026 509 if (key.size() > 2 && key.end()[-2] == '/')
ff6bf1be 510 {
8d998026
AL
511 if (key.find_first_of("fdbi",key.size()-1) < key.size())
512 {
513 key.resize(key.size() - 2);
514 if (Exists(key.c_str()))
515 return true;
516 }
517 else
518 {
69a63027 519 _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]);
8d998026 520 }
ff6bf1be 521 }
b2e465d6
AL
522 return Exists(Name);
523}
524 /*}}}*/
93bf083d
AL
525// Configuration::Dump - Dump the config /*{{{*/
526// ---------------------------------------------------------------------
527/* Dump the entire configuration space */
ff2a211a 528void Configuration::Dump(ostream& str)
93bf083d 529{
2f416270
DK
530 Dump(str, NULL, "%f \"%v\";\n", true);
531}
532void Configuration::Dump(ostream& str, char const * const root,
533 char const * const formatstr, bool const emptyValue)
534{
535 const Configuration::Item* Top = Tree(root);
536 if (Top == 0)
537 return;
538 const Configuration::Item* const Root = (root == NULL) ? NULL : Top;
539 std::vector<std::string> const format = VectorizeString(formatstr, '%');
540
541 /* Write out all of the configuration directives by walking the
93bf083d 542 configuration tree */
2f416270
DK
543 do {
544 if (emptyValue == true || Top->Value.empty() == emptyValue)
545 {
546 std::vector<std::string>::const_iterator f = format.begin();
547 str << *f;
548 for (++f; f != format.end(); ++f)
549 {
550 if (f->empty() == true)
551 {
552 ++f;
553 str << '%' << *f;
554 continue;
555 }
556 char const type = (*f)[0];
557 if (type == 'f')
558 str << Top->FullTag();
559 else if (type == 't')
560 str << Top->Tag;
561 else if (type == 'v')
562 str << Top->Value;
563 else if (type == 'F')
564 str << QuoteString(Top->FullTag(), "=\"\n");
565 else if (type == 'T')
566 str << QuoteString(Top->Tag, "=\"\n");
567 else if (type == 'V')
568 str << QuoteString(Top->Value, "=\"\n");
569 else if (type == 'n')
570 str << "\n";
571 else if (type == 'N')
572 str << "\t";
573 else
574 str << '%' << type;
575 str << f->c_str() + 1;
576 }
577 }
578
93bf083d
AL
579 if (Top->Child != 0)
580 {
581 Top = Top->Child;
582 continue;
583 }
2f416270 584
93bf083d
AL
585 while (Top != 0 && Top->Next == 0)
586 Top = Top->Parent;
587 if (Top != 0)
588 Top = Top->Next;
2f416270
DK
589
590 if (Root != NULL)
591 {
592 const Configuration::Item* I = Top;
593 while(I != 0)
594 {
595 if (I == Root)
596 break;
597 else
598 I = I->Parent;
599 }
600 if (I == 0)
601 break;
602 }
603 } while (Top != 0);
93bf083d
AL
604}
605 /*}}}*/
08e8f724 606
0a8a80e5
AL
607// Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
608// ---------------------------------------------------------------------
b2e465d6
AL
609/* Stop sets an optional max recursion depth if this item is being viewed as
610 part of a sub tree. */
611string Configuration::Item::FullTag(const Item *Stop) const
0a8a80e5 612{
b2e465d6 613 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
0a8a80e5 614 return Tag;
b2e465d6 615 return Parent->FullTag(Stop) + "::" + Tag;
0a8a80e5
AL
616}
617 /*}}}*/
618
08e8f724
AL
619// ReadConfigFile - Read a configuration file /*{{{*/
620// ---------------------------------------------------------------------
621/* The configuration format is very much like the named.conf format
b2e465d6
AL
622 used in bind8, in fact this routine can parse most named.conf files.
623 Sectional config files are like bind's named.conf where there are
624 sections like 'zone "foo.org" { .. };' This causes each section to be
625 added in with a tag like "zone::foo.org" instead of being split
6df63aa6 626 tag/value. AsSectional enables Sectional parsing.*/
ce857f32
DK
627bool ReadConfigFile(Configuration &Conf,const string &FName,bool const &AsSectional,
628 unsigned const &Depth)
b2e465d6 629{
08e8f724 630 // Open the stream for reading
851a45a8 631 ifstream F(FName.c_str(),ios::in);
08e8f724 632 if (!F != 0)
b2e465d6 633 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
3f807f6c 634
08e8f724 635 string LineBuffer;
a4e87467
AL
636 string Stack[100];
637 unsigned int StackPos = 0;
08e8f724
AL
638
639 // Parser state
640 string ParentTag;
641
642 int CurLine = 0;
643 bool InComment = false;
644 while (F.eof() == false)
645 {
3f807f6c
DB
646 // The raw input line.
647 std::string Input;
648 // The input line with comments stripped.
649 std::string Fragment;
8a27f5da
DB
650
651 // Grab the next line of F and place it in Input.
3f807f6c
DB
652 do
653 {
654 char *Buffer = new char[1024];
655
656 F.clear();
657 F.getline(Buffer,sizeof(Buffer) / 2);
658
659 Input += Buffer;
8aea8c3f 660 delete[] Buffer;
3f807f6c
DB
661 }
662 while (F.fail() && !F.eof());
663
8a27f5da
DB
664 // Expand tabs in the input line and remove leading and trailing
665 // whitespace.
3f807f6c 666 {
3f807f6c
DB
667 const int BufferSize = Input.size() * 8 + 1;
668 char *Buffer = new char[BufferSize];
669 try
670 {
671 memcpy(Buffer, Input.c_str(), Input.size() + 1);
672
673 _strtabexpand(Buffer, BufferSize);
674 _strstrip(Buffer);
675 Input = Buffer;
676 }
677 catch(...)
678 {
679 delete[] Buffer;
680 throw;
681 }
682 delete[] Buffer;
683 }
08e8f724 684 CurLine++;
5e14c452 685
8a27f5da
DB
686 // Now strip comments; if the whole line is contained in a
687 // comment, skip this line.
688
3f807f6c
DB
689 // The first meaningful character in the current fragment; will
690 // be adjusted below as we remove bytes from the front.
691 std::string::const_iterator Start = Input.begin();
692 // The last meaningful character in the current fragment.
693 std::string::const_iterator End = Input.end();
08e8f724
AL
694
695 // Multi line comment
696 if (InComment == true)
697 {
3f807f6c
DB
698 for (std::string::const_iterator I = Start;
699 I != End; ++I)
08e8f724 700 {
3f807f6c 701 if (*I == '*' && I + 1 != End && I[1] == '/')
08e8f724 702 {
3f807f6c 703 Start = I + 2;
08e8f724
AL
704 InComment = false;
705 break;
706 }
707 }
708 if (InComment == true)
709 continue;
710 }
711
712 // Discard single line comments
bfd22fc0 713 bool InQuote = false;
3f807f6c
DB
714 for (std::string::const_iterator I = Start;
715 I != End; ++I)
08e8f724 716 {
bfd22fc0
AL
717 if (*I == '"')
718 InQuote = !InQuote;
719 if (InQuote == true)
720 continue;
c914647f
DK
721
722 if ((*I == '/' && I + 1 != End && I[1] == '/') ||
723 (*I == '#' && strcmp(string(I,I+6).c_str(),"#clear") != 0 &&
724 strcmp(string(I,I+8).c_str(),"#include") != 0))
725 {
3f807f6c 726 End = I;
08e8f724
AL
727 break;
728 }
729 }
7834cb57 730
3f807f6c
DB
731 // Look for multi line comments and build up the
732 // fragment.
733 Fragment.reserve(End - Start);
7834cb57 734 InQuote = false;
3f807f6c
DB
735 for (std::string::const_iterator I = Start;
736 I != End; ++I)
08e8f724 737 {
bfd22fc0
AL
738 if (*I == '"')
739 InQuote = !InQuote;
740 if (InQuote == true)
3f807f6c
DB
741 Fragment.push_back(*I);
742 else if (*I == '/' && I + 1 != End && I[1] == '*')
08e8f724
AL
743 {
744 InComment = true;
3f807f6c
DB
745 for (std::string::const_iterator J = I;
746 J != End; ++J)
08e8f724 747 {
3f807f6c 748 if (*J == '*' && J + 1 != End && J[1] == '/')
08e8f724 749 {
3f807f6c
DB
750 // Pretend we just finished walking over the
751 // comment, and don't add anything to the output
752 // fragment.
753 I = J + 1;
08e8f724
AL
754 InComment = false;
755 break;
756 }
757 }
758
759 if (InComment == true)
3f807f6c 760 break;
08e8f724 761 }
3f807f6c
DB
762 else
763 Fragment.push_back(*I);
08e8f724 764 }
3f807f6c
DB
765
766 // Skip blank lines.
767 if (Fragment.empty())
08e8f724
AL
768 continue;
769
8a27f5da 770 // The line has actual content; interpret what it means.
7834cb57 771 InQuote = false;
3f807f6c
DB
772 Start = Fragment.begin();
773 End = Fragment.end();
774 for (std::string::const_iterator I = Start;
775 I != End; ++I)
08e8f724 776 {
7834cb57
AL
777 if (*I == '"')
778 InQuote = !InQuote;
779
780 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
08e8f724 781 {
b2e465d6 782 // Put the last fragment into the buffer
a4dcbf1c
DB
783 std::string::const_iterator NonWhitespaceStart = Start;
784 std::string::const_iterator NonWhitespaceStop = I;
f7f0d6c7 785 for (; NonWhitespaceStart != I && isspace(*NonWhitespaceStart) != 0; ++NonWhitespaceStart)
3f807f6c 786 ;
f7f0d6c7 787 for (; NonWhitespaceStop != NonWhitespaceStart && isspace(NonWhitespaceStop[-1]) != 0; --NonWhitespaceStop)
3f807f6c 788 ;
a4dcbf1c 789 if (LineBuffer.empty() == false && NonWhitespaceStop - NonWhitespaceStart != 0)
08e8f724 790 LineBuffer += ' ';
a4dcbf1c 791 LineBuffer += string(NonWhitespaceStart, NonWhitespaceStop);
3f807f6c
DB
792
793 // Drop this from the input string, saving the character
794 // that terminated the construct we just closed. (i.e., a
795 // brace or a semicolon)
08e8f724 796 char TermChar = *I;
3f807f6c 797 Start = I + 1;
08e8f724
AL
798
799 // Syntax Error
800 if (TermChar == '{' && LineBuffer.empty() == true)
b2e465d6 801 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
08e8f724 802
b2e465d6 803 // No string on this line
08e8f724 804 if (LineBuffer.empty() == true)
b2e465d6
AL
805 {
806 if (TermChar == '}')
807 {
808 if (StackPos == 0)
809 ParentTag = string();
810 else
811 ParentTag = Stack[--StackPos];
812 }
08e8f724 813 continue;
b2e465d6
AL
814 }
815
08e8f724 816 // Parse off the tag
7f25bdff
AL
817 string Tag;
818 const char *Pos = LineBuffer.c_str();
819 if (ParseQuoteWord(Pos,Tag) == false)
db0db9fe 820 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
b2e465d6
AL
821
822 // Parse off the word
823 string Word;
bcae6dd4 824 bool NoWord = false;
b2e465d6
AL
825 if (ParseCWord(Pos,Word) == false &&
826 ParseQuoteWord(Pos,Word) == false)
827 {
828 if (TermChar != '{')
829 {
830 Word = Tag;
831 Tag = "";
bcae6dd4 832 }
4ae405e9
AL
833 else
834 NoWord = true;
b2e465d6
AL
835 }
836 if (strlen(Pos) != 0)
837 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
838
08e8f724
AL
839 // Go down a level
840 if (TermChar == '{')
841 {
26d5b68a 842 if (StackPos < sizeof(Stack)/sizeof(std::string))
a4e87467 843 Stack[StackPos++] = ParentTag;
b2e465d6
AL
844
845 /* Make sectional tags incorperate the section into the
846 tag string */
847 if (AsSectional == true && Word.empty() == false)
848 {
849 Tag += "::" ;
850 Tag += Word;
851 Word = "";
852 }
853
08e8f724
AL
854 if (ParentTag.empty() == true)
855 ParentTag = Tag;
856 else
857 ParentTag += string("::") + Tag;
858 Tag = string();
859 }
bfd22fc0 860
08e8f724
AL
861 // Generate the item name
862 string Item;
863 if (ParentTag.empty() == true)
864 Item = Tag;
865 else
866 {
7f25bdff 867 if (TermChar != '{' || Tag.empty() == false)
08e8f724 868 Item = ParentTag + "::" + Tag;
7f25bdff
AL
869 else
870 Item = ParentTag;
08e8f724
AL
871 }
872
b2e465d6
AL
873 // Specials
874 if (Tag.length() >= 1 && Tag[0] == '#')
875 {
876 if (ParentTag.empty() == false)
877 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
878 Tag.erase(Tag.begin());
879 if (Tag == "clear")
880 Conf.Clear(Word);
881 else if (Tag == "include")
882 {
883 if (Depth > 10)
884 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
885 if (Word.length() > 2 && Word.end()[-1] == '/')
886 {
887 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
888 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
889 }
890 else
891 {
892 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
893 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
894 }
895 }
896 else
897 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
898 }
c3a3a1b1
DK
899 else if (Tag.empty() == true && NoWord == false && Word == "#clear")
900 return _error->Error(_("Syntax error %s:%u: clear directive requires an option tree as argument"),FName.c_str(),CurLine);
b2e465d6
AL
901 else
902 {
903 // Set the item in the configuration class
bcae6dd4
AL
904 if (NoWord == false)
905 Conf.Set(Item,Word);
b2e465d6
AL
906 }
907
08e8f724 908 // Empty the buffer
171c75f1 909 LineBuffer.clear();
b2e465d6
AL
910
911 // Move up a tag, but only if there is no bit to parse
912 if (TermChar == '}')
913 {
914 if (StackPos == 0)
171c75f1 915 ParentTag.clear();
b2e465d6
AL
916 else
917 ParentTag = Stack[--StackPos];
918 }
919
08e8f724 920 }
08e8f724
AL
921 }
922
3f807f6c
DB
923 // Store the remaining text, if any, in the current line buffer.
924
925 // NB: could change this to use string-based operations; I'm
926 // using strstrip now to ensure backwards compatibility.
927 // -- dburrows 2008-04-01
928 {
929 char *Buffer = new char[End - Start + 1];
930 try
931 {
932 std::copy(Start, End, Buffer);
933 Buffer[End - Start] = '\0';
934
935 const char *Stripd = _strstrip(Buffer);
936 if (*Stripd != 0 && LineBuffer.empty() == false)
937 LineBuffer += " ";
938 LineBuffer += Stripd;
939 }
940 catch(...)
941 {
942 delete[] Buffer;
943 throw;
944 }
945 delete[] Buffer;
946 }
08e8f724 947 }
b2e465d6
AL
948
949 if (LineBuffer.empty() == false)
950 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
951 return true;
952}
953 /*}}}*/
954// ReadConfigDir - Read a directory of config files /*{{{*/
955// ---------------------------------------------------------------------
956/* */
a74cd17a 957bool ReadConfigDir(Configuration &Conf,const string &Dir,
be4eec61 958 bool const &AsSectional, unsigned const &Depth)
52643bec 959{
e29a6bb1 960 vector<string> const List = GetListOfFilesInDir(Dir, "conf", true, true);
b2e465d6
AL
961
962 // Read the files
f7f0d6c7 963 for (vector<string>::const_iterator I = List.begin(); I != List.end(); ++I)
b2e465d6
AL
964 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
965 return false;
08e8f724
AL
966 return true;
967}
968 /*}}}*/
1f2933a8
DK
969// MatchAgainstConfig Constructor /*{{{*/
970Configuration::MatchAgainstConfig::MatchAgainstConfig(char const * Config)
971{
972 std::vector<std::string> const strings = _config->FindVector(Config);
973 for (std::vector<std::string>::const_iterator s = strings.begin();
974 s != strings.end(); ++s)
975 {
976 regex_t *p = new regex_t;
977 if (regcomp(p, s->c_str(), REG_EXTENDED | REG_ICASE | REG_NOSUB) == 0)
978 patterns.push_back(p);
979 else
980 {
981 regfree(p);
982 delete p;
44edc41e
MV
983 _error->Warning("Invalid regular expression '%s' in configuration "
984 "option '%s' will be ignored.",
985 s->c_str(), Config);
986 continue;
1f2933a8 987 }
b093a199 988 }
69c2ecbd 989 if (strings.empty() == true)
b093a199 990 patterns.push_back(NULL);
1f2933a8
DK
991}
992 /*}}}*/
993// MatchAgainstConfig Destructor /*{{{*/
994Configuration::MatchAgainstConfig::~MatchAgainstConfig()
b093a199
DK
995{
996 clearPatterns();
997}
998void Configuration::MatchAgainstConfig::clearPatterns()
1f2933a8
DK
999{
1000 for(std::vector<regex_t *>::const_iterator p = patterns.begin();
1001 p != patterns.end(); ++p)
1002 {
b093a199 1003 if (*p == NULL) continue;
1f2933a8
DK
1004 regfree(*p);
1005 delete *p;
1006 }
44edc41e 1007 patterns.clear();
1f2933a8
DK
1008}
1009 /*}}}*/
1010// MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/
1011bool Configuration::MatchAgainstConfig::Match(char const * str) const
1012{
1013 for(std::vector<regex_t *>::const_iterator p = patterns.begin();
1014 p != patterns.end(); ++p)
b093a199 1015 if (*p != NULL && regexec(*p, str, 0, 0, 0) == 0)
1f2933a8
DK
1016 return true;
1017
1018 return false;
1019}
1020 /*}}}*/