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