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