]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ConfigParser.cc
NoNewGlobals for MapLabel (#1746)
[thirdparty/squid.git] / src / ConfigParser.cc
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
21 bool ConfigParser::RecognizeQuotedValues = true;
22 bool ConfigParser::StrictMode = true;
23 std::stack<ConfigParser::CfgFile *> ConfigParser::CfgFiles;
24 ConfigParser::TokenType ConfigParser::LastTokenType = ConfigParser::SimpleToken;
25 const char *ConfigParser::CfgLine = nullptr;
26 const char *ConfigParser::CfgPos = nullptr;
27 std::queue<char *> ConfigParser::CfgLineTokens_;
28 bool ConfigParser::AllowMacros_ = false;
29 bool ConfigParser::ParseQuotedOrToEol_ = false;
30 bool ConfigParser::ParseKvPair_ = false;
31 ConfigParser::ParsingStates ConfigParser::KvPairState_ = ConfigParser::atParseKey;
32 bool ConfigParser::RecognizeQuotedPair_ = false;
33 bool ConfigParser::PreviewMode_ = false;
34
35 static const char *SQUID_ERROR_TOKEN = "[invalid token]";
36
37 void
38 ConfigParser::destruct()
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",
61 cfg_filename, config_lineno, config_input_line);
62 }
63
64 char *
65 ConfigParser::strtokFile()
66 {
67 if (RecognizeQuotedValues)
68 return ConfigParser::NextToken();
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) {
79 ConfigParser::TokenType tokenType;
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
138 char *
139 ConfigParser::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);
194 self_destruct();
195 }
196 }
197
198 if (next)
199 *next = s + 1;
200 return UnQuoted;
201 }
202
203 void
204 ConfigParser::SetCfgLine(char *line)
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
215 SBuf
216 ConfigParser::CurrentLocation()
217 {
218 return ToSBuf(SourceLocation(cfg_directive, cfg_filename, config_lineno));
219 }
220
221 char *
222 ConfigParser::TokenParse(const char * &nextToken, ConfigParser::TokenType &type)
223 {
224 if (!nextToken || *nextToken == '\0')
225 return nullptr;
226 type = ConfigParser::SimpleToken;
227 nextToken += strspn(nextToken, w_space);
228
229 if (*nextToken == '#')
230 return nullptr;
231
232 if (ConfigParser::RecognizeQuotedValues && (*nextToken == '"' || *nextToken == '\'')) {
233 type = ConfigParser::QuotedToken;
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;
241 if (ConfigParser::ParseKvPair_) {
242 if (ConfigParser::KvPairState_ == ConfigParser::atParseKey)
243 sep = "=";
244 else
245 sep = w_space;
246 } else if (ConfigParser::ParseQuotedOrToEol_)
247 sep = "\n";
248 else if (ConfigParser::RecognizeQuotedPair_)
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);
263 self_destruct();
264 }
265 }
266
267 if (ConfigParser::RecognizeQuotedValues && *nextToken == '(') {
268 if (strncmp(tokenStart, "parameters", nextToken - tokenStart) == 0)
269 type = ConfigParser::FunctionParameters;
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);
277 self_destruct();
278 }
279 }
280 } else
281 type = ConfigParser::SimpleToken;
282
283 char *token = nullptr;
284 if (nextToken - tokenStart) {
285 if (ConfigParser::StrictMode && type == ConfigParser::SimpleToken) {
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);
301 self_destruct();
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
317 char *
318 ConfigParser::NextElement(ConfigParser::TokenType &type)
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
332 char *
333 ConfigParser::NextToken()
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)
350 token = NextElement(LastTokenType);
351
352 if (token && LastTokenType == ConfigParser::FunctionParameters) {
353 //Disable temporary preview mode, we need to parse function parameters
354 const bool savePreview = ConfigParser::PreviewMode_;
355 ConfigParser::PreviewMode_ = false;
356
357 char *path = NextToken();
358 if (LastTokenType != ConfigParser::QuotedToken) {
359 debugs(3, DBG_CRITICAL, "FATAL: Quoted filename missing: " << token);
360 self_destruct();
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 << "\"");
369 self_destruct();
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);
375 self_destruct();
376 return nullptr;
377 }
378
379 ConfigParser::CfgFile *wordfile = new ConfigParser::CfgFile();
380 if (!path || !wordfile->startParse(path)) {
381 debugs(3, DBG_CRITICAL, "FATAL: Error opening config file: " << token);
382 delete wordfile;
383 self_destruct();
384 return nullptr;
385 }
386 CfgFiles.push(wordfile);
387 token = nullptr;
388 }
389 } while (token == nullptr && !CfgFiles.empty());
390
391 return token;
392 }
393
394 char *
395 ConfigParser::PeekAtToken()
396 {
397 PreviewMode_ = true;
398 char *token = NextToken();
399 PreviewMode_ = false;
400 return token;
401 }
402
403 char *
404 ConfigParser::NextQuotedOrToEol()
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
421 bool
422 ConfigParser::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
447 bool
448 ConfigParser::NextKvPair(char * &key, char * &value)
449 {
450 key = value = nullptr;
451 ParseKvPair_ = true;
452 KvPairState_ = ConfigParser::atParseKey;
453 if ((key = NextToken()) != nullptr) {
454 KvPairState_ = ConfigParser::atParseValue;
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
469 char *
470 ConfigParser::RegexStrtokFile()
471 {
472 if (ConfigParser::RecognizeQuotedValues) {
473 debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
474 self_destruct();
475 }
476 ConfigParser::RecognizeQuotedPair_ = true;
477 char * token = strtokFile();
478 ConfigParser::RecognizeQuotedPair_ = false;
479 return token;
480 }
481
482 std::unique_ptr<RegexPattern>
483 ConfigParser::regex(const char *expectedRegexDescription)
484 {
485 if (RecognizeQuotedValues)
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
491 ConfigParser::RecognizeQuotedPair_ = true;
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 }
502 ConfigParser::RecognizeQuotedPair_ = false;
503
504 return std::unique_ptr<RegexPattern>(new RegexPattern(pattern, flags));
505 }
506
507 CachePeer &
508 ConfigParser::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
523 char *
524 ConfigParser::NextQuotedToken()
525 {
526 const bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues;
527 ConfigParser::RecognizeQuotedValues = true;
528 char *token = NextToken();
529 ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues;
530 return token;
531 }
532
533 const char *
534 ConfigParser::QuoteString(const String &var)
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
557 void
558 ConfigParser::rejectDuplicateDirective()
559 {
560 assert(cfg_directive);
561 throw TextException("duplicate configuration directive", Here());
562 }
563
564 void
565 ConfigParser::closeDirective()
566 {
567 assert(cfg_directive);
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
573 SBuf
574 ConfigParser::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
583 bool
584 ConfigParser::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
597 Acl::Tree *
598 ConfigParser::optionalAclList()
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
611 bool
612 ConfigParser::CfgFile::startParse(char *path)
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
629 bool
630 ConfigParser::CfgFile::getFileLine()
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
646 char *
647 ConfigParser::CfgFile::parse(ConfigParser::TokenType &type)
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
663 char *
664 ConfigParser::CfgFile::nextElement(ConfigParser::TokenType &type)
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
674 ConfigParser::CfgFile::~CfgFile()
675 {
676 if (wordFile)
677 fclose(wordFile);
678 }
679