]> git.saurik.com Git - apt.git/blame - apt-pkg/contrib/configuration.cc
testcases runable as root
[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 256vector<string> Configuration::FindVector(const char *Name, std::string const &Default) const
50c409c4
DK
257{
258 vector<string> Vec;
259 const Item *Top = Lookup(Name);
260 if (Top == NULL)
a5414e56
DK
261 return VectorizeString(Default, ',');
262
263 if (Top->Value.empty() == false)
264 return VectorizeString(Top->Value, ',');
50c409c4
DK
265
266 Item *I = Top->Child;
267 while(I != NULL)
268 {
269 Vec.push_back(I->Value);
270 I = I->Next;
271 }
a5414e56
DK
272 if (Vec.empty() == true)
273 return VectorizeString(Default, ',');
274
50c409c4
DK
275 return Vec;
276}
277 /*}}}*/
6c139d6e
AL
278// Configuration::FindI - Find an integer value /*{{{*/
279// ---------------------------------------------------------------------
280/* */
ce857f32 281int Configuration::FindI(const char *Name,int const &Default) const
6c139d6e 282{
b2e465d6 283 const Item *Itm = Lookup(Name);
6c139d6e
AL
284 if (Itm == 0 || Itm->Value.empty() == true)
285 return Default;
286
287 char *End;
288 int Res = strtol(Itm->Value.c_str(),&End,0);
289 if (End == Itm->Value.c_str())
290 return Default;
291
08e8f724
AL
292 return Res;
293}
294 /*}}}*/
295// Configuration::FindB - Find a boolean type /*{{{*/
296// ---------------------------------------------------------------------
297/* */
ce857f32 298bool Configuration::FindB(const char *Name,bool const &Default) const
08e8f724 299{
b2e465d6 300 const Item *Itm = Lookup(Name);
08e8f724
AL
301 if (Itm == 0 || Itm->Value.empty() == true)
302 return Default;
303
3b5421b4 304 return StringToBool(Itm->Value,Default);
6c139d6e
AL
305}
306 /*}}}*/
b2e465d6
AL
307// Configuration::FindAny - Find an arbitrary type /*{{{*/
308// ---------------------------------------------------------------------
309/* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
310string Configuration::FindAny(const char *Name,const char *Default) const
311{
312 string key = Name;
313 char type = 0;
314
315 if (key.size() > 2 && key.end()[-2] == '/')
316 {
317 type = key.end()[-1];
318 key.resize(key.size() - 2);
319 }
320
321 switch (type)
322 {
323 // file
324 case 'f':
325 return FindFile(key.c_str(), Default);
326
327 // directory
328 case 'd':
329 return FindDir(key.c_str(), Default);
330
331 // bool
332 case 'b':
333 return FindB(key, Default) ? "true" : "false";
334
335 // int
336 case 'i':
337 {
338 char buf[16];
cc99a102 339 snprintf(buf, sizeof(buf)-1, "%d", FindI(key, Default ? atoi(Default) : 0 ));
b2e465d6
AL
340 return buf;
341 }
342 }
343
344 // fallback
345 return Find(Name, Default);
346}
347 /*}}}*/
348// Configuration::CndSet - Conditinal Set a value /*{{{*/
349// ---------------------------------------------------------------------
350/* This will not overwrite */
171c75f1 351void Configuration::CndSet(const char *Name,const string &Value)
b2e465d6
AL
352{
353 Item *Itm = Lookup(Name,true);
354 if (Itm == 0)
355 return;
356 if (Itm->Value.empty() == true)
357 Itm->Value = Value;
358}
359 /*}}}*/
78485ab2
DK
360// Configuration::Set - Set an integer value /*{{{*/
361// ---------------------------------------------------------------------
362/* */
363void Configuration::CndSet(const char *Name,int const Value)
364{
365 Item *Itm = Lookup(Name,true);
366 if (Itm == 0 || Itm->Value.empty() == false)
367 return;
368 char S[300];
369 snprintf(S,sizeof(S),"%i",Value);
370 Itm->Value = S;
371}
372 /*}}}*/
6c139d6e
AL
373// Configuration::Set - Set a value /*{{{*/
374// ---------------------------------------------------------------------
375/* */
171c75f1 376void Configuration::Set(const char *Name,const string &Value)
6c139d6e
AL
377{
378 Item *Itm = Lookup(Name,true);
379 if (Itm == 0)
380 return;
381 Itm->Value = Value;
382}
383 /*}}}*/
384// Configuration::Set - Set an integer value /*{{{*/
385// ---------------------------------------------------------------------
386/* */
0df8c3dc 387void Configuration::Set(const char *Name,int const &Value)
6c139d6e
AL
388{
389 Item *Itm = Lookup(Name,true);
390 if (Itm == 0)
391 return;
392 char S[300];
393 snprintf(S,sizeof(S),"%i",Value);
394 Itm->Value = S;
75ef8f14
MV
395}
396 /*}}}*/
397// Configuration::Clear - Clear an single value from a list /*{{{*/
398// ---------------------------------------------------------------------
399/* */
ce857f32 400void Configuration::Clear(string const &Name, int const &Value)
75ef8f14
MV
401{
402 char S[300];
403 snprintf(S,sizeof(S),"%i",Value);
404 Clear(Name, S);
405}
406 /*}}}*/
407// Configuration::Clear - Clear an single value from a list /*{{{*/
408// ---------------------------------------------------------------------
409/* */
ce857f32 410void Configuration::Clear(string const &Name, string const &Value)
75ef8f14
MV
411{
412 Item *Top = Lookup(Name.c_str(),false);
413 if (Top == 0 || Top->Child == 0)
414 return;
415
416 Item *Tmp, *Prev, *I;
417 Prev = I = Top->Child;
418
419 while(I != NULL)
420 {
421 if(I->Value == Value)
422 {
423 Tmp = I;
424 // was first element, point parent to new first element
425 if(Top->Child == Tmp)
426 Top->Child = I->Next;
427 I = I->Next;
428 Prev->Next = I;
429 delete Tmp;
430 } else {
431 Prev = I;
432 I = I->Next;
433 }
434 }
435
d8a06f6e
MV
436}
437 /*}}}*/
438// Configuration::Clear - Clear everything /*{{{*/
439// ---------------------------------------------------------------------
440void Configuration::Clear()
441{
442 const Configuration::Item *Top = Tree(0);
443 while( Top != 0 )
444 {
445 Clear(Top->FullTag());
446 Top = Top->Next;
447 }
6c139d6e
AL
448}
449 /*}}}*/
b2e465d6
AL
450// Configuration::Clear - Clear an entire tree /*{{{*/
451// ---------------------------------------------------------------------
452/* */
ce857f32 453void Configuration::Clear(string const &Name)
b2e465d6
AL
454{
455 Item *Top = Lookup(Name.c_str(),false);
75ef8f14 456 if (Top == 0)
b2e465d6 457 return;
75ef8f14 458
171c75f1 459 Top->Value.clear();
b2e465d6
AL
460 Item *Stop = Top;
461 Top = Top->Child;
462 Stop->Child = 0;
463 for (; Top != 0;)
464 {
465 if (Top->Child != 0)
466 {
467 Top = Top->Child;
468 continue;
469 }
470
471 while (Top != 0 && Top->Next == 0)
472 {
473 Item *Tmp = Top;
474 Top = Top->Parent;
475 delete Tmp;
476
477 if (Top == Stop)
478 return;
479 }
480
481 Item *Tmp = Top;
482 if (Top != 0)
483 Top = Top->Next;
484 delete Tmp;
485 }
486}
487 /*}}}*/
08e8f724
AL
488// Configuration::Exists - Returns true if the Name exists /*{{{*/
489// ---------------------------------------------------------------------
490/* */
b2e465d6 491bool Configuration::Exists(const char *Name) const
08e8f724 492{
b2e465d6 493 const Item *Itm = Lookup(Name);
08e8f724
AL
494 if (Itm == 0)
495 return false;
496 return true;
497}
498 /*}}}*/
b2e465d6
AL
499// Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
500// ---------------------------------------------------------------------
501/* qualified by /[fdbi] exists */
502bool Configuration::ExistsAny(const char *Name) const
503{
504 string key = Name;
505
8d998026 506 if (key.size() > 2 && key.end()[-2] == '/')
ff6bf1be 507 {
8d998026
AL
508 if (key.find_first_of("fdbi",key.size()-1) < key.size())
509 {
510 key.resize(key.size() - 2);
511 if (Exists(key.c_str()))
512 return true;
513 }
514 else
515 {
69a63027 516 _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]);
8d998026 517 }
ff6bf1be 518 }
b2e465d6
AL
519 return Exists(Name);
520}
521 /*}}}*/
93bf083d
AL
522// Configuration::Dump - Dump the config /*{{{*/
523// ---------------------------------------------------------------------
524/* Dump the entire configuration space */
ff2a211a 525void Configuration::Dump(ostream& str)
93bf083d 526{
2f416270
DK
527 Dump(str, NULL, "%f \"%v\";\n", true);
528}
529void Configuration::Dump(ostream& str, char const * const root,
530 char const * const formatstr, bool const emptyValue)
531{
532 const Configuration::Item* Top = Tree(root);
533 if (Top == 0)
534 return;
535 const Configuration::Item* const Root = (root == NULL) ? NULL : Top;
536 std::vector<std::string> const format = VectorizeString(formatstr, '%');
537
538 /* Write out all of the configuration directives by walking the
93bf083d 539 configuration tree */
2f416270
DK
540 do {
541 if (emptyValue == true || Top->Value.empty() == emptyValue)
542 {
543 std::vector<std::string>::const_iterator f = format.begin();
544 str << *f;
545 for (++f; f != format.end(); ++f)
546 {
547 if (f->empty() == true)
548 {
549 ++f;
550 str << '%' << *f;
551 continue;
552 }
553 char const type = (*f)[0];
554 if (type == 'f')
555 str << Top->FullTag();
556 else if (type == 't')
557 str << Top->Tag;
558 else if (type == 'v')
559 str << Top->Value;
560 else if (type == 'F')
561 str << QuoteString(Top->FullTag(), "=\"\n");
562 else if (type == 'T')
563 str << QuoteString(Top->Tag, "=\"\n");
564 else if (type == 'V')
565 str << QuoteString(Top->Value, "=\"\n");
566 else if (type == 'n')
567 str << "\n";
568 else if (type == 'N')
569 str << "\t";
570 else
571 str << '%' << type;
572 str << f->c_str() + 1;
573 }
574 }
575
93bf083d
AL
576 if (Top->Child != 0)
577 {
578 Top = Top->Child;
579 continue;
580 }
2f416270 581
93bf083d
AL
582 while (Top != 0 && Top->Next == 0)
583 Top = Top->Parent;
584 if (Top != 0)
585 Top = Top->Next;
2f416270
DK
586
587 if (Root != NULL)
588 {
589 const Configuration::Item* I = Top;
590 while(I != 0)
591 {
592 if (I == Root)
593 break;
594 else
595 I = I->Parent;
596 }
597 if (I == 0)
598 break;
599 }
600 } while (Top != 0);
93bf083d
AL
601}
602 /*}}}*/
08e8f724 603
0a8a80e5
AL
604// Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
605// ---------------------------------------------------------------------
b2e465d6
AL
606/* Stop sets an optional max recursion depth if this item is being viewed as
607 part of a sub tree. */
608string Configuration::Item::FullTag(const Item *Stop) const
0a8a80e5 609{
b2e465d6 610 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
0a8a80e5 611 return Tag;
b2e465d6 612 return Parent->FullTag(Stop) + "::" + Tag;
0a8a80e5
AL
613}
614 /*}}}*/
615
08e8f724
AL
616// ReadConfigFile - Read a configuration file /*{{{*/
617// ---------------------------------------------------------------------
618/* The configuration format is very much like the named.conf format
b2e465d6
AL
619 used in bind8, in fact this routine can parse most named.conf files.
620 Sectional config files are like bind's named.conf where there are
621 sections like 'zone "foo.org" { .. };' This causes each section to be
622 added in with a tag like "zone::foo.org" instead of being split
6df63aa6 623 tag/value. AsSectional enables Sectional parsing.*/
ce857f32
DK
624bool ReadConfigFile(Configuration &Conf,const string &FName,bool const &AsSectional,
625 unsigned const &Depth)
b2e465d6 626{
08e8f724 627 // Open the stream for reading
851a45a8 628 ifstream F(FName.c_str(),ios::in);
08e8f724 629 if (!F != 0)
b2e465d6 630 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
3f807f6c 631
08e8f724 632 string LineBuffer;
a4e87467
AL
633 string Stack[100];
634 unsigned int StackPos = 0;
08e8f724
AL
635
636 // Parser state
637 string ParentTag;
638
639 int CurLine = 0;
640 bool InComment = false;
641 while (F.eof() == false)
642 {
3f807f6c
DB
643 // The raw input line.
644 std::string Input;
645 // The input line with comments stripped.
646 std::string Fragment;
8a27f5da
DB
647
648 // Grab the next line of F and place it in Input.
3f807f6c
DB
649 do
650 {
651 char *Buffer = new char[1024];
652
653 F.clear();
654 F.getline(Buffer,sizeof(Buffer) / 2);
655
656 Input += Buffer;
8aea8c3f 657 delete[] Buffer;
3f807f6c
DB
658 }
659 while (F.fail() && !F.eof());
660
8a27f5da
DB
661 // Expand tabs in the input line and remove leading and trailing
662 // whitespace.
3f807f6c 663 {
3f807f6c
DB
664 const int BufferSize = Input.size() * 8 + 1;
665 char *Buffer = new char[BufferSize];
666 try
667 {
668 memcpy(Buffer, Input.c_str(), Input.size() + 1);
669
670 _strtabexpand(Buffer, BufferSize);
671 _strstrip(Buffer);
672 Input = Buffer;
673 }
674 catch(...)
675 {
676 delete[] Buffer;
677 throw;
678 }
679 delete[] Buffer;
680 }
08e8f724 681 CurLine++;
5e14c452 682
8a27f5da
DB
683 // Now strip comments; if the whole line is contained in a
684 // comment, skip this line.
685
3f807f6c
DB
686 // The first meaningful character in the current fragment; will
687 // be adjusted below as we remove bytes from the front.
688 std::string::const_iterator Start = Input.begin();
689 // The last meaningful character in the current fragment.
690 std::string::const_iterator End = Input.end();
08e8f724
AL
691
692 // Multi line comment
693 if (InComment == true)
694 {
3f807f6c
DB
695 for (std::string::const_iterator I = Start;
696 I != End; ++I)
08e8f724 697 {
3f807f6c 698 if (*I == '*' && I + 1 != End && I[1] == '/')
08e8f724 699 {
3f807f6c 700 Start = I + 2;
08e8f724
AL
701 InComment = false;
702 break;
703 }
704 }
705 if (InComment == true)
706 continue;
707 }
708
709 // Discard single line comments
bfd22fc0 710 bool InQuote = false;
3f807f6c
DB
711 for (std::string::const_iterator I = Start;
712 I != End; ++I)
08e8f724 713 {
bfd22fc0
AL
714 if (*I == '"')
715 InQuote = !InQuote;
716 if (InQuote == true)
717 continue;
c914647f
DK
718
719 if ((*I == '/' && I + 1 != End && I[1] == '/') ||
720 (*I == '#' && strcmp(string(I,I+6).c_str(),"#clear") != 0 &&
721 strcmp(string(I,I+8).c_str(),"#include") != 0))
722 {
3f807f6c 723 End = I;
08e8f724
AL
724 break;
725 }
726 }
7834cb57 727
3f807f6c
DB
728 // Look for multi line comments and build up the
729 // fragment.
730 Fragment.reserve(End - Start);
7834cb57 731 InQuote = false;
3f807f6c
DB
732 for (std::string::const_iterator I = Start;
733 I != End; ++I)
08e8f724 734 {
bfd22fc0
AL
735 if (*I == '"')
736 InQuote = !InQuote;
737 if (InQuote == true)
3f807f6c
DB
738 Fragment.push_back(*I);
739 else if (*I == '/' && I + 1 != End && I[1] == '*')
08e8f724
AL
740 {
741 InComment = true;
3f807f6c
DB
742 for (std::string::const_iterator J = I;
743 J != End; ++J)
08e8f724 744 {
3f807f6c 745 if (*J == '*' && J + 1 != End && J[1] == '/')
08e8f724 746 {
3f807f6c
DB
747 // Pretend we just finished walking over the
748 // comment, and don't add anything to the output
749 // fragment.
750 I = J + 1;
08e8f724
AL
751 InComment = false;
752 break;
753 }
754 }
755
756 if (InComment == true)
3f807f6c 757 break;
08e8f724 758 }
3f807f6c
DB
759 else
760 Fragment.push_back(*I);
08e8f724 761 }
3f807f6c
DB
762
763 // Skip blank lines.
764 if (Fragment.empty())
08e8f724
AL
765 continue;
766
8a27f5da 767 // The line has actual content; interpret what it means.
7834cb57 768 InQuote = false;
3f807f6c
DB
769 Start = Fragment.begin();
770 End = Fragment.end();
771 for (std::string::const_iterator I = Start;
772 I != End; ++I)
08e8f724 773 {
7834cb57
AL
774 if (*I == '"')
775 InQuote = !InQuote;
776
777 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
08e8f724 778 {
b2e465d6 779 // Put the last fragment into the buffer
a4dcbf1c
DB
780 std::string::const_iterator NonWhitespaceStart = Start;
781 std::string::const_iterator NonWhitespaceStop = I;
f7f0d6c7 782 for (; NonWhitespaceStart != I && isspace(*NonWhitespaceStart) != 0; ++NonWhitespaceStart)
3f807f6c 783 ;
f7f0d6c7 784 for (; NonWhitespaceStop != NonWhitespaceStart && isspace(NonWhitespaceStop[-1]) != 0; --NonWhitespaceStop)
3f807f6c 785 ;
a4dcbf1c 786 if (LineBuffer.empty() == false && NonWhitespaceStop - NonWhitespaceStart != 0)
08e8f724 787 LineBuffer += ' ';
a4dcbf1c 788 LineBuffer += string(NonWhitespaceStart, NonWhitespaceStop);
3f807f6c
DB
789
790 // Drop this from the input string, saving the character
791 // that terminated the construct we just closed. (i.e., a
792 // brace or a semicolon)
08e8f724 793 char TermChar = *I;
3f807f6c 794 Start = I + 1;
08e8f724
AL
795
796 // Syntax Error
797 if (TermChar == '{' && LineBuffer.empty() == true)
b2e465d6 798 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
08e8f724 799
b2e465d6 800 // No string on this line
08e8f724 801 if (LineBuffer.empty() == true)
b2e465d6
AL
802 {
803 if (TermChar == '}')
804 {
805 if (StackPos == 0)
806 ParentTag = string();
807 else
808 ParentTag = Stack[--StackPos];
809 }
08e8f724 810 continue;
b2e465d6
AL
811 }
812
08e8f724 813 // Parse off the tag
7f25bdff
AL
814 string Tag;
815 const char *Pos = LineBuffer.c_str();
816 if (ParseQuoteWord(Pos,Tag) == false)
db0db9fe 817 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
b2e465d6
AL
818
819 // Parse off the word
820 string Word;
bcae6dd4 821 bool NoWord = false;
b2e465d6
AL
822 if (ParseCWord(Pos,Word) == false &&
823 ParseQuoteWord(Pos,Word) == false)
824 {
825 if (TermChar != '{')
826 {
827 Word = Tag;
828 Tag = "";
bcae6dd4 829 }
4ae405e9
AL
830 else
831 NoWord = true;
b2e465d6
AL
832 }
833 if (strlen(Pos) != 0)
834 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
835
08e8f724
AL
836 // Go down a level
837 if (TermChar == '{')
838 {
26d5b68a 839 if (StackPos < sizeof(Stack)/sizeof(std::string))
a4e87467 840 Stack[StackPos++] = ParentTag;
b2e465d6
AL
841
842 /* Make sectional tags incorperate the section into the
843 tag string */
844 if (AsSectional == true && Word.empty() == false)
845 {
846 Tag += "::" ;
847 Tag += Word;
848 Word = "";
849 }
850
08e8f724
AL
851 if (ParentTag.empty() == true)
852 ParentTag = Tag;
853 else
854 ParentTag += string("::") + Tag;
855 Tag = string();
856 }
bfd22fc0 857
08e8f724
AL
858 // Generate the item name
859 string Item;
860 if (ParentTag.empty() == true)
861 Item = Tag;
862 else
863 {
7f25bdff 864 if (TermChar != '{' || Tag.empty() == false)
08e8f724 865 Item = ParentTag + "::" + Tag;
7f25bdff
AL
866 else
867 Item = ParentTag;
08e8f724
AL
868 }
869
b2e465d6
AL
870 // Specials
871 if (Tag.length() >= 1 && Tag[0] == '#')
872 {
873 if (ParentTag.empty() == false)
874 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
875 Tag.erase(Tag.begin());
876 if (Tag == "clear")
877 Conf.Clear(Word);
878 else if (Tag == "include")
879 {
880 if (Depth > 10)
881 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
882 if (Word.length() > 2 && Word.end()[-1] == '/')
883 {
884 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
885 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
886 }
887 else
888 {
889 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
890 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
891 }
892 }
893 else
894 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
895 }
c3a3a1b1
DK
896 else if (Tag.empty() == true && NoWord == false && Word == "#clear")
897 return _error->Error(_("Syntax error %s:%u: clear directive requires an option tree as argument"),FName.c_str(),CurLine);
b2e465d6
AL
898 else
899 {
900 // Set the item in the configuration class
bcae6dd4
AL
901 if (NoWord == false)
902 Conf.Set(Item,Word);
b2e465d6
AL
903 }
904
08e8f724 905 // Empty the buffer
171c75f1 906 LineBuffer.clear();
b2e465d6
AL
907
908 // Move up a tag, but only if there is no bit to parse
909 if (TermChar == '}')
910 {
911 if (StackPos == 0)
171c75f1 912 ParentTag.clear();
b2e465d6
AL
913 else
914 ParentTag = Stack[--StackPos];
915 }
916
08e8f724 917 }
08e8f724
AL
918 }
919
3f807f6c
DB
920 // Store the remaining text, if any, in the current line buffer.
921
922 // NB: could change this to use string-based operations; I'm
923 // using strstrip now to ensure backwards compatibility.
924 // -- dburrows 2008-04-01
925 {
926 char *Buffer = new char[End - Start + 1];
927 try
928 {
929 std::copy(Start, End, Buffer);
930 Buffer[End - Start] = '\0';
931
932 const char *Stripd = _strstrip(Buffer);
933 if (*Stripd != 0 && LineBuffer.empty() == false)
934 LineBuffer += " ";
935 LineBuffer += Stripd;
936 }
937 catch(...)
938 {
939 delete[] Buffer;
940 throw;
941 }
942 delete[] Buffer;
943 }
08e8f724 944 }
b2e465d6
AL
945
946 if (LineBuffer.empty() == false)
947 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
948 return true;
949}
950 /*}}}*/
951// ReadConfigDir - Read a directory of config files /*{{{*/
952// ---------------------------------------------------------------------
953/* */
a74cd17a 954bool ReadConfigDir(Configuration &Conf,const string &Dir,
be4eec61 955 bool const &AsSectional, unsigned const &Depth)
52643bec 956{
e29a6bb1 957 vector<string> const List = GetListOfFilesInDir(Dir, "conf", true, true);
b2e465d6
AL
958
959 // Read the files
f7f0d6c7 960 for (vector<string>::const_iterator I = List.begin(); I != List.end(); ++I)
b2e465d6
AL
961 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
962 return false;
08e8f724
AL
963 return true;
964}
965 /*}}}*/
1f2933a8
DK
966// MatchAgainstConfig Constructor /*{{{*/
967Configuration::MatchAgainstConfig::MatchAgainstConfig(char const * Config)
968{
969 std::vector<std::string> const strings = _config->FindVector(Config);
970 for (std::vector<std::string>::const_iterator s = strings.begin();
971 s != strings.end(); ++s)
972 {
973 regex_t *p = new regex_t;
974 if (regcomp(p, s->c_str(), REG_EXTENDED | REG_ICASE | REG_NOSUB) == 0)
975 patterns.push_back(p);
976 else
977 {
978 regfree(p);
979 delete p;
44edc41e
MV
980 _error->Warning("Invalid regular expression '%s' in configuration "
981 "option '%s' will be ignored.",
982 s->c_str(), Config);
983 continue;
1f2933a8 984 }
b093a199 985 }
69c2ecbd 986 if (strings.empty() == true)
b093a199 987 patterns.push_back(NULL);
1f2933a8
DK
988}
989 /*}}}*/
990// MatchAgainstConfig Destructor /*{{{*/
991Configuration::MatchAgainstConfig::~MatchAgainstConfig()
b093a199
DK
992{
993 clearPatterns();
994}
995void Configuration::MatchAgainstConfig::clearPatterns()
1f2933a8
DK
996{
997 for(std::vector<regex_t *>::const_iterator p = patterns.begin();
998 p != patterns.end(); ++p)
999 {
b093a199 1000 if (*p == NULL) continue;
1f2933a8
DK
1001 regfree(*p);
1002 delete *p;
1003 }
44edc41e 1004 patterns.clear();
1f2933a8
DK
1005}
1006 /*}}}*/
1007// MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/
1008bool Configuration::MatchAgainstConfig::Match(char const * str) const
1009{
1010 for(std::vector<regex_t *>::const_iterator p = patterns.begin();
1011 p != patterns.end(); ++p)
b093a199 1012 if (*p != NULL && regexec(*p, str, 0, 0, 0) == 0)
1f2933a8
DK
1013 return true;
1014
1015 return false;
1016}
1017 /*}}}*/