Squid Web Cache master
Loading...
Searching...
No Matches
ConfigParser.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2025 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 return nullptr;
115 } else {
116 char *t2, *t3;
117 t = buf;
118 /* skip leading and trailing white space */
119 t += strspn(buf, w_space);
120 t2 = t + strcspn(t, w_space);
121 t3 = t2 + strspn(t2, w_space);
122
123 while (*t3 && *t3 != '#') {
124 t2 = t3 + strcspn(t3, w_space);
125 t3 = t2 + strspn(t2, w_space);
126 }
127
128 *t2 = '\0';
129 }
130
131 /* skip comments */
132 /* skip blank lines */
133 } while ( *t == '#' || !*t );
134
135 return t;
136}
137
138char *
139ConfigParser::UnQuote(const char *token, const char **next)
140{
141 const char *errorStr = nullptr;
142 const char *errorPos = nullptr;
143 char quoteChar = *token;
144 assert(quoteChar == '"' || quoteChar == '\'');
145 LOCAL_ARRAY(char, UnQuoted, CONFIG_LINE_LIMIT);
146 const char *s = token + 1;
147 char *d = UnQuoted;
148 /* scan until the end of the quoted string, handling escape sequences*/
149 while (*s && *s != quoteChar && !errorStr && (size_t)(d - UnQuoted) < sizeof(UnQuoted) - 1) {
150 if (*s == '\\') {
151 if (s[1] == '\0') {
152 errorStr = "Unterminated escape sequence";
153 errorPos = s;
154 break;
155 }
156 s++;
157 switch (*s) {
158 case 'r':
159 *d = '\r';
160 break;
161 case 'n':
162 *d = '\n';
163 break;
164 case 't':
165 *d = '\t';
166 break;
167 default:
168 if (isalnum(*s)) {
169 errorStr = "Unsupported escape sequence";
170 errorPos = s;
171 }
172 *d = *s;
173 break;
174 }
175 } else
176 *d = *s;
177 ++s;
178 ++d;
179 }
180
181 if (*s != quoteChar && !errorStr) {
182 errorStr = "missing quote char at the end of quoted string";
183 errorPos = s - 1;
184 }
185 // The end of token
186 *d = '\0';
187
188 // We are expecting a separator after quoted string, space or one of "()#"
189 if (!errorStr && *(s + 1) != '\0' && !strchr(w_space "()#", *(s + 1))) {
190 errorStr = "Expecting space after the end of quoted token";
191 errorPos = token;
192 }
193
194 if (errorStr) {
195 if (PreviewMode_)
196 xstrncpy(UnQuoted, SQUID_ERROR_TOKEN, sizeof(UnQuoted));
197 else {
198 debugs(3, DBG_CRITICAL, "FATAL: " << errorStr << ": " << errorPos);
200 }
201 }
202
203 if (next)
204 *next = s + 1;
205 return UnQuoted;
206}
207
208void
210{
211 CfgLine = line;
212 CfgPos = line;
213 while (!CfgLineTokens_.empty()) {
214 char *token = CfgLineTokens_.front();
215 CfgLineTokens_.pop();
216 free(token);
217 }
218}
219
220SBuf
225
226char *
228{
229 if (!nextToken || *nextToken == '\0')
230 return nullptr;
232 nextToken += strspn(nextToken, w_space);
233
234 if (*nextToken == '#')
235 return nullptr;
236
237 if (ConfigParser::RecognizeQuotedValues && (*nextToken == '"' || *nextToken == '\'')) {
239 char *token = xstrdup(UnQuote(nextToken, &nextToken));
240 CfgLineTokens_.push(token);
241 return token;
242 }
243
244 const char *tokenStart = nextToken;
245 const char *sep;
248 sep = "=";
249 else
250 sep = w_space;
252 sep = "\n";
254 sep = w_space "\\";
255 else if (!ConfigParser::RecognizeQuotedValues || *nextToken == '(')
256 sep = w_space;
257 else
258 sep = w_space "(";
259 nextToken += strcspn(nextToken, sep);
260
261 while (ConfigParser::RecognizeQuotedPair_ && *nextToken == '\\') {
262 // NP: do not permit \0 terminator to be escaped.
263 if (*(nextToken+1) && *(nextToken+1) != '\r' && *(nextToken+1) != '\n') {
264 nextToken += 2; // skip the quoted-pair (\-escaped) character
265 nextToken += strcspn(nextToken, sep);
266 } else {
267 debugs(3, DBG_CRITICAL, "FATAL: Unescaped '\' character in regex pattern: " << tokenStart);
269 }
270 }
271
272 if (ConfigParser::RecognizeQuotedValues && *nextToken == '(') {
273 if (strncmp(tokenStart, "parameters", nextToken - tokenStart) == 0)
275 else {
276 if (PreviewMode_) {
277 char *err = xstrdup(SQUID_ERROR_TOKEN);
278 CfgLineTokens_.push(err);
279 return err;
280 } else {
281 debugs(3, DBG_CRITICAL, "FATAL: Unknown cfg function: " << tokenStart);
283 }
284 }
285 } else
287
288 char *token = nullptr;
289 if (nextToken - tokenStart) {
291 bool tokenIsNumber = true;
292 for (const char *s = tokenStart; s != nextToken; ++s) {
293 const bool isValidChar = isalnum(*s) || strchr(".,()-=_/:+", *s) ||
294 (tokenIsNumber && *s == '%' && (s + 1 == nextToken));
295
296 if (!isdigit(*s))
297 tokenIsNumber = false;
298
299 if (!isValidChar) {
300 if (PreviewMode_) {
301 char *err = xstrdup(SQUID_ERROR_TOKEN);
302 CfgLineTokens_.push(err);
303 return err;
304 } else {
305 debugs(3, DBG_CRITICAL, "FATAL: Not alphanumeric character '"<< *s << "' in unquoted token " << tokenStart);
307 }
308 }
309 }
310 }
311 token = xstrndup(tokenStart, nextToken - tokenStart + 1);
312 CfgLineTokens_.push(token);
313 }
314
315 if (*nextToken != '\0' && *nextToken != '#') {
316 ++nextToken;
317 }
318
319 return token;
320}
321
322char *
324{
325 const char *pos = CfgPos;
326 char *token = TokenParse(pos, type);
327 // If not in preview mode the next call of this method should start
328 // parsing after the end of current token.
329 // For function "parameters(...)" we need always to update current parsing
330 // position to allow parser read the arguments of "parameters(..)"
331 if (!PreviewMode_ || type == FunctionParameters)
332 CfgPos = pos;
333 // else next call will read the same token
334 return token;
335}
336
337char *
339{
340 char *token = nullptr;
341
342 do {
343 while (token == nullptr && !CfgFiles.empty()) {
344 ConfigParser::CfgFile *wordfile = CfgFiles.top();
345 token = wordfile->parse(LastTokenType);
346 if (!token) {
347 assert(!wordfile->isOpen());
348 CfgFiles.pop();
349 debugs(3, 4, "CfgFiles.pop " << wordfile->filePath);
350 delete wordfile;
351 }
352 }
353
354 if (!token)
356
358 //Disable temporary preview mode, we need to parse function parameters
359 const bool savePreview = ConfigParser::PreviewMode_;
361
362 char *path = NextToken();
364 debugs(3, DBG_CRITICAL, "FATAL: Quoted filename missing: " << token);
366 return nullptr;
367 }
368
369 // The next token in current cfg file line must be a ")"
370 char *end = NextToken();
371 ConfigParser::PreviewMode_ = savePreview;
372 if (LastTokenType != ConfigParser::SimpleToken || strcmp(end, ")") != 0) {
373 debugs(3, DBG_CRITICAL, "FATAL: missing ')' after " << token << "(\"" << path << "\"");
375 return nullptr;
376 }
377
378 if (CfgFiles.size() > 16) {
379 debugs(3, DBG_CRITICAL, "FATAL: can't open %s for reading parameters: includes are nested too deeply (>16)!\n" << path);
381 return nullptr;
382 }
383
385 if (!path || !wordfile->startParse(path)) {
386 debugs(3, DBG_CRITICAL, "FATAL: Error opening config file: " << token);
387 delete wordfile;
389 return nullptr;
390 }
391 CfgFiles.push(wordfile);
392 token = nullptr;
393 }
394 } while (token == nullptr && !CfgFiles.empty());
395
396 return token;
397}
398
399char *
401{
402 PreviewMode_ = true;
403 char *token = NextToken();
404 PreviewMode_ = false;
405 return token;
406}
407
408char *
410{
411 ParseQuotedOrToEol_ = true;
412 char *token = NextToken();
413 ParseQuotedOrToEol_ = false;
414
415 // Assume end of current config line
416 // Close all open configuration files for this config line
417 while (!CfgFiles.empty()) {
418 ConfigParser::CfgFile *wordfile = CfgFiles.top();
419 CfgFiles.pop();
420 delete wordfile;
421 }
422
423 return token;
424}
425
426bool
427ConfigParser::optionalKvPair(char * &key, char * &value)
428{
429 key = nullptr;
430 value = nullptr;
431
432 if (const char *currentToken = PeekAtToken()) {
433 // NextKvPair() accepts "a = b" and skips "=" or "a=". To avoid
434 // misinterpreting the admin intent, we use strict checks.
435 if (const auto middle = strchr(currentToken, '=')) {
436 if (middle == currentToken)
437 throw TextException(ToSBuf("missing key in a key=value option: ", currentToken), Here());
438 if (middle + 1 == currentToken + strlen(currentToken))
439 throw TextException(ToSBuf("missing value in a key=value option: ", currentToken), Here());
440 } else
441 return false; // not a key=value token
442
443 if (!NextKvPair(key, value)) // may still fail (e.g., bad value quoting)
444 throw TextException(ToSBuf("invalid key=value option: ", currentToken), Here());
445
446 return true;
447 }
448
449 return false; // end of directive or input
450}
451
452bool
453ConfigParser::NextKvPair(char * &key, char * &value)
454{
455 key = value = nullptr;
456 ParseKvPair_ = true;
458 if ((key = NextToken()) != nullptr) {
460 value = NextQuotedToken();
461 }
462 ParseKvPair_ = false;
463
464 if (!key)
465 return false;
466 if (!value) {
467 debugs(3, DBG_CRITICAL, "ERROR: Failure while parsing key=value token. Value missing after: " << key);
468 return false;
469 }
470
471 return true;
472}
473
474char *
476{
478 debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
480 }
482 char * token = strtokFile();
484 return token;
485}
486
487std::unique_ptr<RegexPattern>
488ConfigParser::regex(const char *expectedRegexDescription)
489{
491 throw TextException("Cannot read regex expression while configuration_includes_quoted_values is enabled", Here());
492
493 SBuf pattern;
494 int flags = REG_EXTENDED | REG_NOSUB;
495
497 const auto flagOrPattern = token(expectedRegexDescription);
498 if (flagOrPattern.cmp("-i") == 0) {
499 flags |= REG_ICASE;
500 pattern = token(expectedRegexDescription);
501 } else if (flagOrPattern.cmp("+i") == 0) {
502 flags &= ~REG_ICASE;
503 pattern = token(expectedRegexDescription);
504 } else {
505 pattern = flagOrPattern;
506 }
508
509 return std::unique_ptr<RegexPattern>(new RegexPattern(pattern, flags));
510}
511
512CachePeer &
513ConfigParser::cachePeer(const char *peerNameTokenDescription)
514{
515 if (const auto name = NextToken()) {
516 debugs(3, 5, CurrentLocation() << ' ' << peerNameTokenDescription << ": " << name);
517
518 if (const auto p = findCachePeerByName(name))
519 return *p;
520
521 throw TextException(ToSBuf("Cannot find a previously declared cache_peer referred to by ",
522 peerNameTokenDescription, " as ", name), Here());
523 }
524
525 throw TextException(ToSBuf("Missing ", peerNameTokenDescription), Here());
526}
527
528char *
530{
531 const bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues;
533 char *token = NextToken();
534 ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues;
535 return token;
536}
537
538const char *
540{
541 static String quotedStr;
542 const char *s = var.termedBuf();
543 bool needQuote = false;
544
545 for (const char *l = s; !needQuote && *l != '\0'; ++l )
546 needQuote = !isalnum(*l);
547
548 if (!needQuote)
549 return s;
550
551 quotedStr.clean();
552 quotedStr.append('"');
553 for (; *s != '\0'; ++s) {
554 if (*s == '"' || *s == '\\')
555 quotedStr.append('\\');
556 quotedStr.append(*s);
557 }
558 quotedStr.append('"');
559 return quotedStr.termedBuf();
560}
561
562void
564{
566 throw TextException("duplicate configuration directive", Here());
567}
568
569void
571{
573 if (const auto garbage = PeekAtToken())
574 throw TextException(ToSBuf("trailing garbage at the end of a configuration directive: ", garbage), Here());
575 // TODO: cfg_directive = nullptr; // currently in generated code
576}
577
578SBuf
579ConfigParser::token(const char *expectedTokenDescription)
580{
581 if (const auto extractedToken = NextToken()) {
582 debugs(3, 5, CurrentLocation() << ' ' << expectedTokenDescription << ": " << extractedToken);
583 return SBuf(extractedToken);
584 }
585 throw TextException(ToSBuf("missing ", expectedTokenDescription), Here());
586}
587
588bool
589ConfigParser::skipOptional(const char *keyword)
590{
591 assert(keyword);
592 if (const auto nextToken = PeekAtToken()) {
593 if (strcmp(nextToken, keyword) == 0) {
594 (void)NextToken();
595 return true;
596 }
597 return false; // the next token on the line is not the optional keyword
598 }
599 return false; // no more tokens (i.e. we are at the end of the line)
600}
601
602ACLList *
604{
605 if (!skipOptional("if"))
606 return nullptr; // OK: the directive has no ACLs
607
608 ACLList *acls = nullptr;
609 const auto aclCount = aclParseAclList(*this, &acls, cfg_directive);
610 assert(acls);
611 if (aclCount <= 0)
612 throw TextException("missing ACL name(s) after 'if' keyword", Here());
613 return acls;
614}
615
616bool
618{
619 assert(wordFile == nullptr);
620 debugs(3, 3, "Parsing from " << path);
621 if ((wordFile = fopen(path, "r")) == nullptr) {
622 debugs(3, DBG_CRITICAL, "WARNING: file :" << path << " not found");
623 return false;
624 }
625
626#if _SQUID_WINDOWS_
627 setmode(fileno(wordFile), O_TEXT);
628#endif
629
630 filePath = path;
631 return getFileLine();
632}
633
634bool
636{
637 // Else get the next line
638 if (fgets(parseBuffer, CONFIG_LINE_LIMIT, wordFile) == nullptr) {
639 /* stop reading from file */
640 fclose(wordFile);
641 wordFile = nullptr;
642 parseBuffer[0] = '\0';
643 return false;
644 }
645 parsePos = parseBuffer;
646 currentLine = parseBuffer;
647 lineNo++;
648 return true;
649}
650
651char *
653{
654 if (!wordFile)
655 return nullptr;
656
657 if (!*parseBuffer)
658 return nullptr;
659
660 char *token;
661 while (!(token = nextElement(type))) {
662 if (!getFileLine())
663 return nullptr;
664 }
665 return token;
666}
667
668char *
670{
671 const char *pos = parsePos;
672 char *token = TokenParse(pos, type);
673 if (!PreviewMode_ || type == FunctionParameters)
674 parsePos = pos;
675 // else next call will read the same token;
676 return token;
677}
678
680{
681 if (wordFile)
682 fclose(wordFile);
683}
684
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:93
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