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