ConfigParser.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2022 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9#include "squid.h"
10#include "acl/Gadgets.h"
11#include "base/Here.h"
12#include "base/RegexPattern.h"
13#include "cache_cf.h"
14#include "ConfigParser.h"
15#include "debug/Stream.h"
16#include "fatal.h"
17#include "globals.h"
18#include "sbuf/Stream.h"
19
21bool ConfigParser::StrictMode = true;
22std::stack<ConfigParser::CfgFile *> ConfigParser::CfgFiles;
24const char *ConfigParser::CfgLine = nullptr;
25const char *ConfigParser::CfgPos = nullptr;
26std::queue<char *> ConfigParser::CfgLineTokens_;
33
34static const char *SQUID_ERROR_TOKEN = "[invalid token]";
35
36void
38{
39 shutting_down = 1;
40 if (!CfgFiles.empty()) {
41 std::ostringstream message;
42 CfgFile *f = CfgFiles.top();
43 message << "Bungled " << f->filePath << " line " << f->lineNo <<
44 ": " << f->currentLine << std::endl;
45 CfgFiles.pop();
46 delete f;
47 while (!CfgFiles.empty()) {
48 f = CfgFiles.top();
49 message << " included from " << f->filePath << " line " <<
50 f->lineNo << ": " << f->currentLine << std::endl;
51 CfgFiles.pop();
52 delete f;
53 }
54 message << " included from " << cfg_filename << " line " <<
55 config_lineno << ": " << config_input_line << std::endl;
56 std::string msg = message.str();
57 fatalf("%s", msg.c_str());
58 } else
59 fatalf("Bungled %s line %d: %s",
61}
62
63char *
65{
68
69 static int fromFile = 0;
70 static FILE *wordFile = nullptr;
71
72 char *t;
73 static char buf[CONFIG_LINE_LIMIT];
74
75 do {
76
77 if (!fromFile) {
79 t = ConfigParser::NextElement(tokenType);
80 if (!t) {
81 return nullptr;
82 } else if (*t == '\"' || *t == '\'') {
83 /* quote found, start reading from file */
84 debugs(3, 8,"Quoted token found : " << t);
85 char *fn = ++t;
86
87 while (*t && *t != '\"' && *t != '\'')
88 ++t;
89
90 *t = '\0';
91
92 if ((wordFile = fopen(fn, "r")) == nullptr) {
93 debugs(3, DBG_CRITICAL, "ERROR: Can not open file " << fn << " for reading");
94 return nullptr;
95 }
96
97#if _SQUID_WINDOWS_
98 setmode(fileno(wordFile), O_TEXT);
99#endif
100
101 fromFile = 1;
102 } else {
103 return t;
104 }
105 }
106
107 /* fromFile */
108 if (fgets(buf, sizeof(buf), wordFile) == nullptr) {
109 /* stop reading from file */
110 fclose(wordFile);
111 wordFile = nullptr;
112 fromFile = 0;
113 return nullptr;
114 } else {
115 char *t2, *t3;
116 t = buf;
117 /* skip leading and trailing white space */
118 t += strspn(buf, w_space);
119 t2 = t + strcspn(t, w_space);
120 t3 = t2 + strspn(t2, w_space);
121
122 while (*t3 && *t3 != '#') {
123 t2 = t3 + strcspn(t3, w_space);
124 t3 = t2 + strspn(t2, w_space);
125 }
126
127 *t2 = '\0';
128 }
129
130 /* skip comments */
131 /* skip blank lines */
132 } while ( *t == '#' || !*t );
133
134 return t;
135}
136
137char *
138ConfigParser::UnQuote(const char *token, const char **next)
139{
140 const char *errorStr = nullptr;
141 const char *errorPos = nullptr;
142 char quoteChar = *token;
143 assert(quoteChar == '"' || quoteChar == '\'');
144 LOCAL_ARRAY(char, UnQuoted, CONFIG_LINE_LIMIT);
145 const char *s = token + 1;
146 char *d = UnQuoted;
147 /* scan until the end of the quoted string, handling escape sequences*/
148 while (*s && *s != quoteChar && !errorStr && (size_t)(d - UnQuoted) < sizeof(UnQuoted)) {
149 if (*s == '\\') {
150 s++;
151 switch (*s) {
152 case 'r':
153 *d = '\r';
154 break;
155 case 'n':
156 *d = '\n';
157 break;
158 case 't':
159 *d = '\t';
160 break;
161 default:
162 if (isalnum(*s)) {
163 errorStr = "Unsupported escape sequence";
164 errorPos = s;
165 }
166 *d = *s;
167 break;
168 }
169 } else
170 *d = *s;
171 ++s;
172 ++d;
173 }
174
175 if (*s != quoteChar && !errorStr) {
176 errorStr = "missing quote char at the end of quoted string";
177 errorPos = s - 1;
178 }
179 // The end of token
180 *d = '\0';
181
182 // We are expecting a separator after quoted string, space or one of "()#"
183 if (*(s + 1) != '\0' && !strchr(w_space "()#", *(s + 1)) && !errorStr) {
184 errorStr = "Expecting space after the end of quoted token";
185 errorPos = token;
186 }
187
188 if (errorStr) {
189 if (PreviewMode_)
190 xstrncpy(UnQuoted, SQUID_ERROR_TOKEN, sizeof(UnQuoted));
191 else {
192 debugs(3, DBG_CRITICAL, "FATAL: " << errorStr << ": " << errorPos);
194 }
195 }
196
197 if (next)
198 *next = s + 1;
199 return UnQuoted;
200}
201
202void
204{
205 CfgLine = line;
206 CfgPos = line;
207 while (!CfgLineTokens_.empty()) {
208 char *token = CfgLineTokens_.front();
209 CfgLineTokens_.pop();
210 free(token);
211 }
212}
213
214SBuf
216{
218}
219
220char *
222{
223 if (!nextToken || *nextToken == '\0')
224 return nullptr;
226 nextToken += strspn(nextToken, w_space);
227
228 if (*nextToken == '#')
229 return nullptr;
230
231 if (ConfigParser::RecognizeQuotedValues && (*nextToken == '"' || *nextToken == '\'')) {
233 char *token = xstrdup(UnQuote(nextToken, &nextToken));
234 CfgLineTokens_.push(token);
235 return token;
236 }
237
238 const char *tokenStart = nextToken;
239 const char *sep;
242 sep = "=";
243 else
244 sep = w_space;
246 sep = "\n";
248 sep = w_space "\\";
249 else if (!ConfigParser::RecognizeQuotedValues || *nextToken == '(')
250 sep = w_space;
251 else
252 sep = w_space "(";
253 nextToken += strcspn(nextToken, sep);
254
255 while (ConfigParser::RecognizeQuotedPair_ && *nextToken == '\\') {
256 // NP: do not permit \0 terminator to be escaped.
257 if (*(nextToken+1) && *(nextToken+1) != '\r' && *(nextToken+1) != '\n') {
258 nextToken += 2; // skip the quoted-pair (\-escaped) character
259 nextToken += strcspn(nextToken, sep);
260 } else {
261 debugs(3, DBG_CRITICAL, "FATAL: Unescaped '\' character in regex pattern: " << tokenStart);
263 }
264 }
265
266 if (ConfigParser::RecognizeQuotedValues && *nextToken == '(') {
267 if (strncmp(tokenStart, "parameters", nextToken - tokenStart) == 0)
269 else {
270 if (PreviewMode_) {
271 char *err = xstrdup(SQUID_ERROR_TOKEN);
272 CfgLineTokens_.push(err);
273 return err;
274 } else {
275 debugs(3, DBG_CRITICAL, "FATAL: Unknown cfg function: " << tokenStart);
277 }
278 }
279 } else
281
282 char *token = nullptr;
283 if (nextToken - tokenStart) {
285 bool tokenIsNumber = true;
286 for (const char *s = tokenStart; s != nextToken; ++s) {
287 const bool isValidChar = isalnum(*s) || strchr(".,()-=_/:+", *s) ||
288 (tokenIsNumber && *s == '%' && (s + 1 == nextToken));
289
290 if (!isdigit(*s))
291 tokenIsNumber = false;
292
293 if (!isValidChar) {
294 if (PreviewMode_) {
295 char *err = xstrdup(SQUID_ERROR_TOKEN);
296 CfgLineTokens_.push(err);
297 return err;
298 } else {
299 debugs(3, DBG_CRITICAL, "FATAL: Not alphanumeric character '"<< *s << "' in unquoted token " << tokenStart);
301 }
302 }
303 }
304 }
305 token = xstrndup(tokenStart, nextToken - tokenStart + 1);
306 CfgLineTokens_.push(token);
307 }
308
309 if (*nextToken != '\0' && *nextToken != '#') {
310 ++nextToken;
311 }
312
313 return token;
314}
315
316char *
318{
319 const char *pos = CfgPos;
320 char *token = TokenParse(pos, type);
321 // If not in preview mode the next call of this method should start
322 // parsing after the end of current token.
323 // For function "parameters(...)" we need always to update current parsing
324 // position to allow parser read the arguments of "parameters(..)"
326 CfgPos = pos;
327 // else next call will read the same token
328 return token;
329}
330
331char *
333{
334 char *token = nullptr;
335
336 do {
337 while (token == nullptr && !CfgFiles.empty()) {
338 ConfigParser::CfgFile *wordfile = CfgFiles.top();
339 token = wordfile->parse(LastTokenType);
340 if (!token) {
341 assert(!wordfile->isOpen());
342 CfgFiles.pop();
343 debugs(3, 4, "CfgFiles.pop " << wordfile->filePath);
344 delete wordfile;
345 }
346 }
347
348 if (!token)
350
352 //Disable temporary preview mode, we need to parse function parameters
353 const bool savePreview = ConfigParser::PreviewMode_;
355
356 char *path = NextToken();
358 debugs(3, DBG_CRITICAL, "FATAL: Quoted filename missing: " << token);
360 return nullptr;
361 }
362
363 // The next token in current cfg file line must be a ")"
364 char *end = NextToken();
365 ConfigParser::PreviewMode_ = savePreview;
366 if (LastTokenType != ConfigParser::SimpleToken || strcmp(end, ")") != 0) {
367 debugs(3, DBG_CRITICAL, "FATAL: missing ')' after " << token << "(\"" << path << "\"");
369 return nullptr;
370 }
371
372 if (CfgFiles.size() > 16) {
373 debugs(3, DBG_CRITICAL, "FATAL: can't open %s for reading parameters: includes are nested too deeply (>16)!\n" << path);
375 return nullptr;
376 }
377
379 if (!path || !wordfile->startParse(path)) {
380 debugs(3, DBG_CRITICAL, "FATAL: Error opening config file: " << token);
381 delete wordfile;
383 return nullptr;
384 }
385 CfgFiles.push(wordfile);
386 token = nullptr;
387 }
388 } while (token == nullptr && !CfgFiles.empty());
389
390 return token;
391}
392
393char *
395{
396 PreviewMode_ = true;
397 char *token = NextToken();
398 PreviewMode_ = false;
399 return token;
400}
401
402char *
404{
405 ParseQuotedOrToEol_ = true;
406 char *token = NextToken();
407 ParseQuotedOrToEol_ = false;
408
409 // Assume end of current config line
410 // Close all open configuration files for this config line
411 while (!CfgFiles.empty()) {
412 ConfigParser::CfgFile *wordfile = CfgFiles.top();
413 CfgFiles.pop();
414 delete wordfile;
415 }
416
417 return token;
418}
419
420bool
421ConfigParser::optionalKvPair(char * &key, char * &value)
422{
423 key = nullptr;
424 value = nullptr;
425
426 if (const char *currentToken = PeekAtToken()) {
427 // NextKvPair() accepts "a = b" and skips "=" or "a=". To avoid
428 // misinterpreting the admin intent, we use strict checks.
429 if (const auto middle = strchr(currentToken, '=')) {
430 if (middle == currentToken)
431 throw TextException(ToSBuf("missing key in a key=value option: ", currentToken), Here());
432 if (middle + 1 == currentToken + strlen(currentToken))
433 throw TextException(ToSBuf("missing value in a key=value option: ", currentToken), Here());
434 } else
435 return false; // not a key=value token
436
437 if (!NextKvPair(key, value)) // may still fail (e.g., bad value quoting)
438 throw TextException(ToSBuf("invalid key=value option: ", currentToken), Here());
439
440 return true;
441 }
442
443 return false; // end of directive or input
444}
445
446bool
447ConfigParser::NextKvPair(char * &key, char * &value)
448{
449 key = value = nullptr;
450 ParseKvPair_ = true;
452 if ((key = NextToken()) != nullptr) {
454 value = NextQuotedToken();
455 }
456 ParseKvPair_ = false;
457
458 if (!key)
459 return false;
460 if (!value) {
461 debugs(3, DBG_CRITICAL, "ERROR: Failure while parsing key=value token. Value missing after: " << key);
462 return false;
463 }
464
465 return true;
466}
467
468char *
470{
472 debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
474 }
476 char * token = strtokFile();
478 return token;
479}
480
481std::unique_ptr<RegexPattern>
482ConfigParser::regex(const char *expectedRegexDescription)
483{
485 throw TextException("Cannot read regex expression while configuration_includes_quoted_values is enabled", Here());
486
487 SBuf pattern;
488 int flags = REG_EXTENDED | REG_NOSUB;
489
491 const auto flagOrPattern = token(expectedRegexDescription);
492 if (flagOrPattern.cmp("-i") == 0) {
493 flags |= REG_ICASE;
494 pattern = token(expectedRegexDescription);
495 } else if (flagOrPattern.cmp("+i") == 0) {
496 flags &= ~REG_ICASE;
497 pattern = token(expectedRegexDescription);
498 } else {
499 pattern = flagOrPattern;
500 }
502
503 return std::unique_ptr<RegexPattern>(new RegexPattern(pattern, flags));
504}
505
506char *
508{
509 const bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues;
511 char *token = NextToken();
512 ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues;
513 return token;
514}
515
516const char *
518{
519 static String quotedStr;
520 const char *s = var.termedBuf();
521 bool needQuote = false;
522
523 for (const char *l = s; !needQuote && *l != '\0'; ++l )
524 needQuote = !isalnum(*l);
525
526 if (!needQuote)
527 return s;
528
529 quotedStr.clean();
530 quotedStr.append('"');
531 for (; *s != '\0'; ++s) {
532 if (*s == '"' || *s == '\\')
533 quotedStr.append('\\');
534 quotedStr.append(*s);
535 }
536 quotedStr.append('"');
537 return quotedStr.termedBuf();
538}
539
540void
542{
544 throw TextException("duplicate configuration directive", Here());
545}
546
547void
549{
551 if (const auto garbage = PeekAtToken())
552 throw TextException(ToSBuf("trailing garbage at the end of a configuration directive: ", garbage), Here());
553 // TODO: cfg_directive = nullptr; // currently in generated code
554}
555
556SBuf
557ConfigParser::token(const char *expectedTokenDescription)
558{
559 if (const auto extractedToken = NextToken()) {
560 debugs(3, 5, CurrentLocation() << ' ' << expectedTokenDescription << ": " << extractedToken);
561 return SBuf(extractedToken);
562 }
563 throw TextException(ToSBuf("missing ", expectedTokenDescription), Here());
564}
565
566bool
567ConfigParser::skipOptional(const char *keyword)
568{
569 assert(keyword);
570 if (const auto nextToken = PeekAtToken()) {
571 if (strcmp(nextToken, keyword) == 0) {
572 (void)NextToken();
573 return true;
574 }
575 return false; // the next token on the line is not the optional keyword
576 }
577 return false; // no more tokens (i.e. we are at the end of the line)
578}
579
580Acl::Tree *
582{
583 if (!skipOptional("if"))
584 return nullptr; // OK: the directive has no ACLs
585
586 Acl::Tree *acls = nullptr;
587 const auto aclCount = aclParseAclList(*this, &acls, cfg_directive);
588 assert(acls);
589 if (aclCount <= 0)
590 throw TextException("missing ACL name(s) after 'if' keyword", Here());
591 return acls;
592}
593
594bool
596{
597 assert(wordFile == nullptr);
598 debugs(3, 3, "Parsing from " << path);
599 if ((wordFile = fopen(path, "r")) == nullptr) {
600 debugs(3, DBG_CRITICAL, "WARNING: file :" << path << " not found");
601 return false;
602 }
603
604#if _SQUID_WINDOWS_
605 setmode(fileno(wordFile), O_TEXT);
606#endif
607
608 filePath = path;
609 return getFileLine();
610}
611
612bool
614{
615 // Else get the next line
616 if (fgets(parseBuffer, CONFIG_LINE_LIMIT, wordFile) == nullptr) {
617 /* stop reading from file */
618 fclose(wordFile);
619 wordFile = nullptr;
620 parseBuffer[0] = '\0';
621 return false;
622 }
623 parsePos = parseBuffer;
624 currentLine = parseBuffer;
625 lineNo++;
626 return true;
627}
628
629char *
631{
632 if (!wordFile)
633 return nullptr;
634
635 if (!*parseBuffer)
636 return nullptr;
637
638 char *token;
639 while (!(token = nextElement(type))) {
640 if (!getFileLine())
641 return nullptr;
642 }
643 return token;
644}
645
646char *
648{
649 const char *pos = parsePos;
650 char *token = TokenParse(pos, type);
652 parsePos = pos;
653 // else next call will read the same token;
654 return token;
655}
656
658{
659 if (wordFile)
660 fclose(wordFile);
661}
662
static const char * SQUID_ERROR_TOKEN
Definition: ConfigParser.cc:34
#define CONFIG_LINE_LIMIT
Definition: ConfigParser.h:32
#define REG_ICASE
Definition: GnuRegex.h:230
#define REG_EXTENDED
Definition: GnuRegex.h:226
#define REG_NOSUB
Definition: GnuRegex.h:239
#define Here()
source code location of the caller
Definition: Here.h:15
size_t aclParseAclList(ConfigParser &, Acl::Tree **treep, const char *label)
Definition: Gadgets.cc:191
#define assert(EX)
Definition: assert.h:19
const char * cfg_directive
During parsing, the name of the current squid.conf directive being parsed.
Definition: cache_cf.cc:270
char config_input_line[BUFSIZ]
Definition: cache_cf.cc:273
const char * cfg_filename
Definition: cache_cf.cc:271
int config_lineno
Definition: cache_cf.cc:272
void self_destruct(void)
Definition: cache_cf.cc:276
Definition: Tree.h:21
char * nextElement(TokenType &type)
std::string currentLine
The current line to parse.
Definition: ConfigParser.h:197
std::string filePath
The file path.
Definition: ConfigParser.h:196
FILE * wordFile
Pointer to the file.
Definition: ConfigParser.h:192
char * parse(TokenType &type)
bool getFileLine()
Read the next line from the file.
bool startParse(char *path)
bool isOpen()
True if the configuration file is open.
Definition: ConfigParser.h:169
int lineNo
Current line number.
Definition: ConfigParser.h:198
bool optionalKvPair(char *&key, char *&value)
static const char * CfgLine
The current line to parse.
Definition: ConfigParser.h:220
static TokenType LastTokenType
The type of last parsed element.
Definition: ConfigParser.h:219
static SBuf CurrentLocation()
Acl::Tree * optionalAclList()
parses an [if [!]<acl>...] construct
static char * RegexStrtokFile()
static const char * CfgPos
Pointer to the next element in cfgLine string.
Definition: ConfigParser.h:221
static bool RecognizeQuotedPair_
The next tokens may contain quoted-pair (-escaped) characters.
Definition: ConfigParser.h:225
void rejectDuplicateDirective()
rejects configuration due to a repeated directive
static char * NextQuotedToken()
static enum ConfigParser::ParsingStates KvPairState_
Parsing state while parsing kv-pair tokens.
Definition: ConfigParser.cc:30
static char * NextQuotedOrToEol()
static bool StrictMode
Definition: ConfigParser.h:156
std::unique_ptr< RegexPattern > regex(const char *expectedRegexDescription)
extracts and returns a regex (including any optional flags)
static std::queue< char * > CfgLineTokens_
Store the list of tokens for current configuration line.
Definition: ConfigParser.h:222
static bool ParseKvPair_
The next token will be handled as kv-pair token.
Definition: ConfigParser.h:227
static char * NextElement(TokenType &type)
Wrapper method for TokenParse.
static bool RecognizeQuotedValues
configuration_includes_quoted_values in squid.conf
Definition: ConfigParser.h:148
static char * PeekAtToken()
static std::stack< CfgFile * > CfgFiles
The stack of open cfg files.
Definition: ConfigParser.h:218
static bool ParseQuotedOrToEol_
The next tokens will be handled as quoted or to_eol token.
Definition: ConfigParser.h:224
bool skipOptional(const char *keyword)
either extracts the given (optional) token or returns false
void closeDirective()
stops parsing the current configuration directive
static char * UnQuote(const char *token, const char **next=nullptr)
static bool PreviewMode_
The next token will not popped from cfg files, will just previewd.
Definition: ConfigParser.h:226
static bool NextKvPair(char *&key, char *&value)
static bool AllowMacros_
Definition: ConfigParser.h:223
static void SetCfgLine(char *line)
Set the configuration file line to parse.
static char * NextToken()
static char * strtokFile()
Definition: ConfigParser.cc:64
SBuf token(const char *expectedTokenDescription)
extracts and returns a required token
static const char * QuoteString(const String &var)
static char * TokenParse(const char *&nextToken, TokenType &type)
void destruct()
Definition: ConfigParser.cc:37
Definition: SBuf.h:94
a source code location that is cheap to create, copy, and store
Definition: Here.h:30
void clean()
Definition: String.cc:118
char const * termedBuf() const
Definition: SquidString.h:92
void append(char const *buf, int len)
Definition: String.cc:149
an std::runtime_error with thrower location info
Definition: TextException.h:21
#define w_space
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:196
#define DBG_CRITICAL
Definition: Stream.h:40
#define O_TEXT
Definition: defines.h:133
int type
Definition: errorpage.cc:152
void fatalf(const char *fmt,...)
Definition: fatal.cc:68
int shutting_down
#define xstrdup
SBuf ToSBuf(Args &&... args)
slowly stream-prints all arguments into a freshly allocated SBuf
Definition: Stream.h:63
#define LOCAL_ARRAY(type, name, size)
Definition: squid.h:68
char * xstrncpy(char *dst, const char *src, size_t n)
Definition: xstring.cc:37
char * xstrndup(const char *s, size_t n)
Definition: xstring.cc:56

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors