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