ConfigParser.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2023 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)) {
150 if (*s == '\\') {
151 s++;
152 switch (*s) {
153 case 'r':
154 *d = '\r';
155 break;
156 case 'n':
157 *d = '\n';
158 break;
159 case 't':
160 *d = '\t';
161 break;
162 default:
163 if (isalnum(*s)) {
164 errorStr = "Unsupported escape sequence";
165 errorPos = s;
166 }
167 *d = *s;
168 break;
169 }
170 } else
171 *d = *s;
172 ++s;
173 ++d;
174 }
175
176 if (*s != quoteChar && !errorStr) {
177 errorStr = "missing quote char at the end of quoted string";
178 errorPos = s - 1;
179 }
180 // The end of token
181 *d = '\0';
182
183 // We are expecting a separator after quoted string, space or one of "()#"
184 if (*(s + 1) != '\0' && !strchr(w_space "()#", *(s + 1)) && !errorStr) {
185 errorStr = "Expecting space after the end of quoted token";
186 errorPos = token;
187 }
188
189 if (errorStr) {
190 if (PreviewMode_)
191 xstrncpy(UnQuoted, SQUID_ERROR_TOKEN, sizeof(UnQuoted));
192 else {
193 debugs(3, DBG_CRITICAL, "FATAL: " << errorStr << ": " << errorPos);
195 }
196 }
197
198 if (next)
199 *next = s + 1;
200 return UnQuoted;
201}
202
203void
205{
206 CfgLine = line;
207 CfgPos = line;
208 while (!CfgLineTokens_.empty()) {
209 char *token = CfgLineTokens_.front();
210 CfgLineTokens_.pop();
211 free(token);
212 }
213}
214
215SBuf
217{
219}
220
221char *
223{
224 if (!nextToken || *nextToken == '\0')
225 return nullptr;
227 nextToken += strspn(nextToken, w_space);
228
229 if (*nextToken == '#')
230 return nullptr;
231
232 if (ConfigParser::RecognizeQuotedValues && (*nextToken == '"' || *nextToken == '\'')) {
234 char *token = xstrdup(UnQuote(nextToken, &nextToken));
235 CfgLineTokens_.push(token);
236 return token;
237 }
238
239 const char *tokenStart = nextToken;
240 const char *sep;
243 sep = "=";
244 else
245 sep = w_space;
247 sep = "\n";
249 sep = w_space "\\";
250 else if (!ConfigParser::RecognizeQuotedValues || *nextToken == '(')
251 sep = w_space;
252 else
253 sep = w_space "(";
254 nextToken += strcspn(nextToken, sep);
255
256 while (ConfigParser::RecognizeQuotedPair_ && *nextToken == '\\') {
257 // NP: do not permit \0 terminator to be escaped.
258 if (*(nextToken+1) && *(nextToken+1) != '\r' && *(nextToken+1) != '\n') {
259 nextToken += 2; // skip the quoted-pair (\-escaped) character
260 nextToken += strcspn(nextToken, sep);
261 } else {
262 debugs(3, DBG_CRITICAL, "FATAL: Unescaped '\' character in regex pattern: " << tokenStart);
264 }
265 }
266
267 if (ConfigParser::RecognizeQuotedValues && *nextToken == '(') {
268 if (strncmp(tokenStart, "parameters", nextToken - tokenStart) == 0)
270 else {
271 if (PreviewMode_) {
272 char *err = xstrdup(SQUID_ERROR_TOKEN);
273 CfgLineTokens_.push(err);
274 return err;
275 } else {
276 debugs(3, DBG_CRITICAL, "FATAL: Unknown cfg function: " << tokenStart);
278 }
279 }
280 } else
282
283 char *token = nullptr;
284 if (nextToken - tokenStart) {
286 bool tokenIsNumber = true;
287 for (const char *s = tokenStart; s != nextToken; ++s) {
288 const bool isValidChar = isalnum(*s) || strchr(".,()-=_/:+", *s) ||
289 (tokenIsNumber && *s == '%' && (s + 1 == nextToken));
290
291 if (!isdigit(*s))
292 tokenIsNumber = false;
293
294 if (!isValidChar) {
295 if (PreviewMode_) {
296 char *err = xstrdup(SQUID_ERROR_TOKEN);
297 CfgLineTokens_.push(err);
298 return err;
299 } else {
300 debugs(3, DBG_CRITICAL, "FATAL: Not alphanumeric character '"<< *s << "' in unquoted token " << tokenStart);
302 }
303 }
304 }
305 }
306 token = xstrndup(tokenStart, nextToken - tokenStart + 1);
307 CfgLineTokens_.push(token);
308 }
309
310 if (*nextToken != '\0' && *nextToken != '#') {
311 ++nextToken;
312 }
313
314 return token;
315}
316
317char *
319{
320 const char *pos = CfgPos;
321 char *token = TokenParse(pos, type);
322 // If not in preview mode the next call of this method should start
323 // parsing after the end of current token.
324 // For function "parameters(...)" we need always to update current parsing
325 // position to allow parser read the arguments of "parameters(..)"
326 if (!PreviewMode_ || type == FunctionParameters)
327 CfgPos = pos;
328 // else next call will read the same token
329 return token;
330}
331
332char *
334{
335 char *token = nullptr;
336
337 do {
338 while (token == nullptr && !CfgFiles.empty()) {
339 ConfigParser::CfgFile *wordfile = CfgFiles.top();
340 token = wordfile->parse(LastTokenType);
341 if (!token) {
342 assert(!wordfile->isOpen());
343 CfgFiles.pop();
344 debugs(3, 4, "CfgFiles.pop " << wordfile->filePath);
345 delete wordfile;
346 }
347 }
348
349 if (!token)
351
353 //Disable temporary preview mode, we need to parse function parameters
354 const bool savePreview = ConfigParser::PreviewMode_;
356
357 char *path = NextToken();
359 debugs(3, DBG_CRITICAL, "FATAL: Quoted filename missing: " << token);
361 return nullptr;
362 }
363
364 // The next token in current cfg file line must be a ")"
365 char *end = NextToken();
366 ConfigParser::PreviewMode_ = savePreview;
367 if (LastTokenType != ConfigParser::SimpleToken || strcmp(end, ")") != 0) {
368 debugs(3, DBG_CRITICAL, "FATAL: missing ')' after " << token << "(\"" << path << "\"");
370 return nullptr;
371 }
372
373 if (CfgFiles.size() > 16) {
374 debugs(3, DBG_CRITICAL, "FATAL: can't open %s for reading parameters: includes are nested too deeply (>16)!\n" << path);
376 return nullptr;
377 }
378
380 if (!path || !wordfile->startParse(path)) {
381 debugs(3, DBG_CRITICAL, "FATAL: Error opening config file: " << token);
382 delete wordfile;
384 return nullptr;
385 }
386 CfgFiles.push(wordfile);
387 token = nullptr;
388 }
389 } while (token == nullptr && !CfgFiles.empty());
390
391 return token;
392}
393
394char *
396{
397 PreviewMode_ = true;
398 char *token = NextToken();
399 PreviewMode_ = false;
400 return token;
401}
402
403char *
405{
406 ParseQuotedOrToEol_ = true;
407 char *token = NextToken();
408 ParseQuotedOrToEol_ = false;
409
410 // Assume end of current config line
411 // Close all open configuration files for this config line
412 while (!CfgFiles.empty()) {
413 ConfigParser::CfgFile *wordfile = CfgFiles.top();
414 CfgFiles.pop();
415 delete wordfile;
416 }
417
418 return token;
419}
420
421bool
422ConfigParser::optionalKvPair(char * &key, char * &value)
423{
424 key = nullptr;
425 value = nullptr;
426
427 if (const char *currentToken = PeekAtToken()) {
428 // NextKvPair() accepts "a = b" and skips "=" or "a=". To avoid
429 // misinterpreting the admin intent, we use strict checks.
430 if (const auto middle = strchr(currentToken, '=')) {
431 if (middle == currentToken)
432 throw TextException(ToSBuf("missing key in a key=value option: ", currentToken), Here());
433 if (middle + 1 == currentToken + strlen(currentToken))
434 throw TextException(ToSBuf("missing value in a key=value option: ", currentToken), Here());
435 } else
436 return false; // not a key=value token
437
438 if (!NextKvPair(key, value)) // may still fail (e.g., bad value quoting)
439 throw TextException(ToSBuf("invalid key=value option: ", currentToken), Here());
440
441 return true;
442 }
443
444 return false; // end of directive or input
445}
446
447bool
448ConfigParser::NextKvPair(char * &key, char * &value)
449{
450 key = value = nullptr;
451 ParseKvPair_ = true;
453 if ((key = NextToken()) != nullptr) {
455 value = NextQuotedToken();
456 }
457 ParseKvPair_ = false;
458
459 if (!key)
460 return false;
461 if (!value) {
462 debugs(3, DBG_CRITICAL, "ERROR: Failure while parsing key=value token. Value missing after: " << key);
463 return false;
464 }
465
466 return true;
467}
468
469char *
471{
473 debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
475 }
477 char * token = strtokFile();
479 return token;
480}
481
482std::unique_ptr<RegexPattern>
483ConfigParser::regex(const char *expectedRegexDescription)
484{
486 throw TextException("Cannot read regex expression while configuration_includes_quoted_values is enabled", Here());
487
488 SBuf pattern;
489 int flags = REG_EXTENDED | REG_NOSUB;
490
492 const auto flagOrPattern = token(expectedRegexDescription);
493 if (flagOrPattern.cmp("-i") == 0) {
494 flags |= REG_ICASE;
495 pattern = token(expectedRegexDescription);
496 } else if (flagOrPattern.cmp("+i") == 0) {
497 flags &= ~REG_ICASE;
498 pattern = token(expectedRegexDescription);
499 } else {
500 pattern = flagOrPattern;
501 }
503
504 return std::unique_ptr<RegexPattern>(new RegexPattern(pattern, flags));
505}
506
507CachePeer &
508ConfigParser::cachePeer(const char *peerNameTokenDescription)
509{
510 if (const auto name = NextToken()) {
511 debugs(3, 5, CurrentLocation() << ' ' << peerNameTokenDescription << ": " << name);
512
513 if (const auto p = findCachePeerByName(name))
514 return *p;
515
516 throw TextException(ToSBuf("Cannot find a previously declared cache_peer referred to by ",
517 peerNameTokenDescription, " as ", name), Here());
518 }
519
520 throw TextException(ToSBuf("Missing ", peerNameTokenDescription), Here());
521}
522
523char *
525{
526 const bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues;
528 char *token = NextToken();
529 ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues;
530 return token;
531}
532
533const char *
535{
536 static String quotedStr;
537 const char *s = var.termedBuf();
538 bool needQuote = false;
539
540 for (const char *l = s; !needQuote && *l != '\0'; ++l )
541 needQuote = !isalnum(*l);
542
543 if (!needQuote)
544 return s;
545
546 quotedStr.clean();
547 quotedStr.append('"');
548 for (; *s != '\0'; ++s) {
549 if (*s == '"' || *s == '\\')
550 quotedStr.append('\\');
551 quotedStr.append(*s);
552 }
553 quotedStr.append('"');
554 return quotedStr.termedBuf();
555}
556
557void
559{
561 throw TextException("duplicate configuration directive", Here());
562}
563
564void
566{
568 if (const auto garbage = PeekAtToken())
569 throw TextException(ToSBuf("trailing garbage at the end of a configuration directive: ", garbage), Here());
570 // TODO: cfg_directive = nullptr; // currently in generated code
571}
572
573SBuf
574ConfigParser::token(const char *expectedTokenDescription)
575{
576 if (const auto extractedToken = NextToken()) {
577 debugs(3, 5, CurrentLocation() << ' ' << expectedTokenDescription << ": " << extractedToken);
578 return SBuf(extractedToken);
579 }
580 throw TextException(ToSBuf("missing ", expectedTokenDescription), Here());
581}
582
583bool
584ConfigParser::skipOptional(const char *keyword)
585{
586 assert(keyword);
587 if (const auto nextToken = PeekAtToken()) {
588 if (strcmp(nextToken, keyword) == 0) {
589 (void)NextToken();
590 return true;
591 }
592 return false; // the next token on the line is not the optional keyword
593 }
594 return false; // no more tokens (i.e. we are at the end of the line)
595}
596
597Acl::Tree *
599{
600 if (!skipOptional("if"))
601 return nullptr; // OK: the directive has no ACLs
602
603 Acl::Tree *acls = nullptr;
604 const auto aclCount = aclParseAclList(*this, &acls, cfg_directive);
605 assert(acls);
606 if (aclCount <= 0)
607 throw TextException("missing ACL name(s) after 'if' keyword", Here());
608 return acls;
609}
610
611bool
613{
614 assert(wordFile == nullptr);
615 debugs(3, 3, "Parsing from " << path);
616 if ((wordFile = fopen(path, "r")) == nullptr) {
617 debugs(3, DBG_CRITICAL, "WARNING: file :" << path << " not found");
618 return false;
619 }
620
621#if _SQUID_WINDOWS_
622 setmode(fileno(wordFile), O_TEXT);
623#endif
624
625 filePath = path;
626 return getFileLine();
627}
628
629bool
631{
632 // Else get the next line
633 if (fgets(parseBuffer, CONFIG_LINE_LIMIT, wordFile) == nullptr) {
634 /* stop reading from file */
635 fclose(wordFile);
636 wordFile = nullptr;
637 parseBuffer[0] = '\0';
638 return false;
639 }
640 parsePos = parseBuffer;
641 currentLine = parseBuffer;
642 lineNo++;
643 return true;
644}
645
646char *
648{
649 if (!wordFile)
650 return nullptr;
651
652 if (!*parseBuffer)
653 return nullptr;
654
655 char *token;
656 while (!(token = nextElement(type))) {
657 if (!getFileLine())
658 return nullptr;
659 }
660 return token;
661}
662
663char *
665{
666 const char *pos = parsePos;
667 char *token = TokenParse(pos, type);
668 if (!PreviewMode_ || type == FunctionParameters)
669 parsePos = pos;
670 // else next call will read the same token;
671 return token;
672}
673
675{
676 if (wordFile)
677 fclose(wordFile);
678}
679
static const char * SQUID_ERROR_TOKEN
Definition: ConfigParser.cc:35
#define CONFIG_LINE_LIMIT
Definition: ConfigParser.h:33
#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:188
#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:271
char config_input_line[BUFSIZ]
Definition: cache_cf.cc:274
const char * cfg_filename
Definition: cache_cf.cc:272
int config_lineno
Definition: cache_cf.cc:273
void self_destruct(void)
Definition: cache_cf.cc:277
Definition: Tree.h:21
char * nextElement(TokenType &type)
std::string currentLine
The current line to parse.
Definition: ConfigParser.h:201
std::string filePath
The file path.
Definition: ConfigParser.h:200
FILE * wordFile
Pointer to the file.
Definition: ConfigParser.h:196
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:173
int lineNo
Current line number.
Definition: ConfigParser.h:202
bool optionalKvPair(char *&key, char *&value)
static const char * CfgLine
The current line to parse.
Definition: ConfigParser.h:224
static TokenType LastTokenType
The type of last parsed element.
Definition: ConfigParser.h:223
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:225
static bool RecognizeQuotedPair_
The next tokens may contain quoted-pair (-escaped) characters.
Definition: ConfigParser.h:229
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:31
static char * NextQuotedOrToEol()
static bool StrictMode
Definition: ConfigParser.h:160
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:226
static bool ParseKvPair_
The next token will be handled as kv-pair token.
Definition: ConfigParser.h:231
static char * NextElement(TokenType &type)
Wrapper method for TokenParse.
static bool RecognizeQuotedValues
configuration_includes_quoted_values in squid.conf
Definition: ConfigParser.h:152
static char * PeekAtToken()
static std::stack< CfgFile * > CfgFiles
The stack of open cfg files.
Definition: ConfigParser.h:222
static bool ParseQuotedOrToEol_
The next tokens will be handled as quoted or to_eol token.
Definition: ConfigParser.h:228
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:230
static bool NextKvPair(char *&key, char *&value)
static bool AllowMacros_
Definition: ConfigParser.h:227
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()
Definition: ConfigParser.cc:65
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:38
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:103
char const * termedBuf() const
Definition: SquidString.h:92
void append(char const *buf, int len)
Definition: String.cc:130
an std::runtime_error with thrower location info
Definition: TextException.h:21
#define w_space
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:194
#define DBG_CRITICAL
Definition: Stream.h:37
#define O_TEXT
Definition: defines.h:133
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)
Definition: neighbors.cc:1050
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