]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ConfigParser.cc
Maintenance: Update astyle version to 3.1 (#841)
[thirdparty/squid.git] / src / ConfigParser.cc
1 /*
2 * Copyright (C) 1996-2021 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 "cache_cf.h"
13 #include "ConfigParser.h"
14 #include "Debug.h"
15 #include "fatal.h"
16 #include "globals.h"
17 #include "sbuf/Stream.h"
18
19 bool ConfigParser::RecognizeQuotedValues = true;
20 bool ConfigParser::StrictMode = true;
21 std::stack<ConfigParser::CfgFile *> ConfigParser::CfgFiles;
22 ConfigParser::TokenType ConfigParser::LastTokenType = ConfigParser::SimpleToken;
23 const char *ConfigParser::CfgLine = NULL;
24 const char *ConfigParser::CfgPos = NULL;
25 std::queue<char *> ConfigParser::CfgLineTokens_;
26 std::queue<std::string> ConfigParser::Undo_;
27 bool ConfigParser::AllowMacros_ = false;
28 bool ConfigParser::ParseQuotedOrToEol_ = false;
29 bool ConfigParser::ParseKvPair_ = false;
30 ConfigParser::ParsingStates ConfigParser::KvPairState_ = ConfigParser::atParseKey;
31 bool ConfigParser::RecognizeQuotedPair_ = false;
32 bool ConfigParser::PreviewMode_ = false;
33
34 static const char *SQUID_ERROR_TOKEN = "[invalid token]";
35
36 void
37 ConfigParser::destruct()
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",
60 cfg_filename, config_lineno, config_input_line);
61 }
62
63 void
64 ConfigParser::TokenPutBack(const char *tok)
65 {
66 assert(tok);
67 Undo_.push(tok);
68 }
69
70 char *
71 ConfigParser::Undo()
72 {
73 static char undoToken[CONFIG_LINE_LIMIT];
74 if (!Undo_.empty()) {
75 xstrncpy(undoToken, Undo_.front().c_str(), sizeof(undoToken));
76 undoToken[sizeof(undoToken) - 1] = '\0';
77 if (!PreviewMode_)
78 Undo_.pop();
79 return undoToken;
80 }
81 return NULL;
82 }
83
84 char *
85 ConfigParser::strtokFile()
86 {
87 if (RecognizeQuotedValues)
88 return ConfigParser::NextToken();
89
90 static int fromFile = 0;
91 static FILE *wordFile = NULL;
92
93 char *t;
94 static char buf[CONFIG_LINE_LIMIT];
95
96 if ((t = ConfigParser::Undo()))
97 return t;
98
99 do {
100
101 if (!fromFile) {
102 ConfigParser::TokenType tokenType;
103 t = ConfigParser::NextElement(tokenType);
104 if (!t) {
105 return NULL;
106 } else if (*t == '\"' || *t == '\'') {
107 /* quote found, start reading from file */
108 debugs(3, 8,"Quoted token found : " << t);
109 char *fn = ++t;
110
111 while (*t && *t != '\"' && *t != '\'')
112 ++t;
113
114 *t = '\0';
115
116 if ((wordFile = fopen(fn, "r")) == NULL) {
117 debugs(3, DBG_CRITICAL, "ERROR: Can not open file " << fn << " for reading");
118 return NULL;
119 }
120
121 #if _SQUID_WINDOWS_
122 setmode(fileno(wordFile), O_TEXT);
123 #endif
124
125 fromFile = 1;
126 } else {
127 return t;
128 }
129 }
130
131 /* fromFile */
132 if (fgets(buf, sizeof(buf), wordFile) == NULL) {
133 /* stop reading from file */
134 fclose(wordFile);
135 wordFile = NULL;
136 fromFile = 0;
137 return NULL;
138 } else {
139 char *t2, *t3;
140 t = buf;
141 /* skip leading and trailing white space */
142 t += strspn(buf, w_space);
143 t2 = t + strcspn(t, w_space);
144 t3 = t2 + strspn(t2, w_space);
145
146 while (*t3 && *t3 != '#') {
147 t2 = t3 + strcspn(t3, w_space);
148 t3 = t2 + strspn(t2, w_space);
149 }
150
151 *t2 = '\0';
152 }
153
154 /* skip comments */
155 /* skip blank lines */
156 } while ( *t == '#' || !*t );
157
158 return t;
159 }
160
161 char *
162 ConfigParser::UnQuote(const char *token, const char **next)
163 {
164 const char *errorStr = NULL;
165 const char *errorPos = NULL;
166 char quoteChar = *token;
167 assert(quoteChar == '"' || quoteChar == '\'');
168 LOCAL_ARRAY(char, UnQuoted, CONFIG_LINE_LIMIT);
169 const char *s = token + 1;
170 char *d = UnQuoted;
171 /* scan until the end of the quoted string, handling escape sequences*/
172 while (*s && *s != quoteChar && !errorStr && (size_t)(d - UnQuoted) < sizeof(UnQuoted)) {
173 if (*s == '\\') {
174 s++;
175 switch (*s) {
176 case 'r':
177 *d = '\r';
178 break;
179 case 'n':
180 *d = '\n';
181 break;
182 case 't':
183 *d = '\t';
184 break;
185 default:
186 if (isalnum(*s)) {
187 errorStr = "Unsupported escape sequence";
188 errorPos = s;
189 }
190 *d = *s;
191 break;
192 }
193 } else
194 *d = *s;
195 ++s;
196 ++d;
197 }
198
199 if (*s != quoteChar && !errorStr) {
200 errorStr = "missing quote char at the end of quoted string";
201 errorPos = s - 1;
202 }
203 // The end of token
204 *d = '\0';
205
206 // We are expecting a separator after quoted string, space or one of "()#"
207 if (*(s + 1) != '\0' && !strchr(w_space "()#", *(s + 1)) && !errorStr) {
208 errorStr = "Expecting space after the end of quoted token";
209 errorPos = token;
210 }
211
212 if (errorStr) {
213 if (PreviewMode_)
214 xstrncpy(UnQuoted, SQUID_ERROR_TOKEN, sizeof(UnQuoted));
215 else {
216 debugs(3, DBG_CRITICAL, "FATAL: " << errorStr << ": " << errorPos);
217 self_destruct();
218 }
219 }
220
221 if (next)
222 *next = s + 1;
223 return UnQuoted;
224 }
225
226 void
227 ConfigParser::SetCfgLine(char *line)
228 {
229 CfgLine = line;
230 CfgPos = line;
231 while (!CfgLineTokens_.empty()) {
232 char *token = CfgLineTokens_.front();
233 CfgLineTokens_.pop();
234 free(token);
235 }
236 }
237
238 SBuf
239 ConfigParser::CurrentLocation()
240 {
241 return ToSBuf(SourceLocation(cfg_directive, cfg_filename, config_lineno));
242 }
243
244 char *
245 ConfigParser::TokenParse(const char * &nextToken, ConfigParser::TokenType &type)
246 {
247 if (!nextToken || *nextToken == '\0')
248 return NULL;
249 type = ConfigParser::SimpleToken;
250 nextToken += strspn(nextToken, w_space);
251
252 if (*nextToken == '#')
253 return NULL;
254
255 if (ConfigParser::RecognizeQuotedValues && (*nextToken == '"' || *nextToken == '\'')) {
256 type = ConfigParser::QuotedToken;
257 char *token = xstrdup(UnQuote(nextToken, &nextToken));
258 CfgLineTokens_.push(token);
259 return token;
260 }
261
262 const char *tokenStart = nextToken;
263 const char *sep;
264 if (ConfigParser::ParseKvPair_) {
265 if (ConfigParser::KvPairState_ == ConfigParser::atParseKey)
266 sep = "=";
267 else
268 sep = w_space;
269 } else if (ConfigParser::ParseQuotedOrToEol_)
270 sep = "\n";
271 else if (ConfigParser::RecognizeQuotedPair_)
272 sep = w_space "\\";
273 else if (!ConfigParser::RecognizeQuotedValues || *nextToken == '(')
274 sep = w_space;
275 else
276 sep = w_space "(";
277 nextToken += strcspn(nextToken, sep);
278
279 while (ConfigParser::RecognizeQuotedPair_ && *nextToken == '\\') {
280 // NP: do not permit \0 terminator to be escaped.
281 if (*(nextToken+1) && *(nextToken+1) != '\r' && *(nextToken+1) != '\n') {
282 nextToken += 2; // skip the quoted-pair (\-escaped) character
283 nextToken += strcspn(nextToken, sep);
284 } else {
285 debugs(3, DBG_CRITICAL, "FATAL: Unescaped '\' character in regex pattern: " << tokenStart);
286 self_destruct();
287 }
288 }
289
290 if (ConfigParser::RecognizeQuotedValues && *nextToken == '(') {
291 if (strncmp(tokenStart, "parameters", nextToken - tokenStart) == 0)
292 type = ConfigParser::FunctionParameters;
293 else {
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: Unknown cfg function: " << tokenStart);
300 self_destruct();
301 }
302 }
303 } else
304 type = ConfigParser::SimpleToken;
305
306 char *token = NULL;
307 if (nextToken - tokenStart) {
308 if (ConfigParser::StrictMode && type == ConfigParser::SimpleToken) {
309 bool tokenIsNumber = true;
310 for (const char *s = tokenStart; s != nextToken; ++s) {
311 const bool isValidChar = isalnum(*s) || strchr(".,()-=_/:+", *s) ||
312 (tokenIsNumber && *s == '%' && (s + 1 == nextToken));
313
314 if (!isdigit(*s))
315 tokenIsNumber = false;
316
317 if (!isValidChar) {
318 if (PreviewMode_) {
319 char *err = xstrdup(SQUID_ERROR_TOKEN);
320 CfgLineTokens_.push(err);
321 return err;
322 } else {
323 debugs(3, DBG_CRITICAL, "FATAL: Not alphanumeric character '"<< *s << "' in unquoted token " << tokenStart);
324 self_destruct();
325 }
326 }
327 }
328 }
329 token = xstrndup(tokenStart, nextToken - tokenStart + 1);
330 CfgLineTokens_.push(token);
331 }
332
333 if (*nextToken != '\0' && *nextToken != '#') {
334 ++nextToken;
335 }
336
337 return token;
338 }
339
340 char *
341 ConfigParser::NextElement(ConfigParser::TokenType &type)
342 {
343 const char *pos = CfgPos;
344 char *token = TokenParse(pos, type);
345 // If not in preview mode the next call of this method should start
346 // parsing after the end of current token.
347 // For function "parameters(...)" we need always to update current parsing
348 // position to allow parser read the arguments of "parameters(..)"
349 if (!PreviewMode_ || type == FunctionParameters)
350 CfgPos = pos;
351 // else next call will read the same token
352 return token;
353 }
354
355 char *
356 ConfigParser::NextToken()
357 {
358 char *token = NULL;
359 if ((token = ConfigParser::Undo())) {
360 debugs(3, 6, "TOKEN (undone): " << token);
361 return token;
362 }
363
364 do {
365 while (token == NULL && !CfgFiles.empty()) {
366 ConfigParser::CfgFile *wordfile = CfgFiles.top();
367 token = wordfile->parse(LastTokenType);
368 if (!token) {
369 assert(!wordfile->isOpen());
370 CfgFiles.pop();
371 debugs(3, 4, "CfgFiles.pop " << wordfile->filePath);
372 delete wordfile;
373 }
374 }
375
376 if (!token)
377 token = NextElement(LastTokenType);
378
379 if (token && LastTokenType == ConfigParser::FunctionParameters) {
380 //Disable temporary preview mode, we need to parse function parameters
381 const bool savePreview = ConfigParser::PreviewMode_;
382 ConfigParser::PreviewMode_ = false;
383
384 char *path = NextToken();
385 if (LastTokenType != ConfigParser::QuotedToken) {
386 debugs(3, DBG_CRITICAL, "FATAL: Quoted filename missing: " << token);
387 self_destruct();
388 return NULL;
389 }
390
391 // The next token in current cfg file line must be a ")"
392 char *end = NextToken();
393 ConfigParser::PreviewMode_ = savePreview;
394 if (LastTokenType != ConfigParser::SimpleToken || strcmp(end, ")") != 0) {
395 debugs(3, DBG_CRITICAL, "FATAL: missing ')' after " << token << "(\"" << path << "\"");
396 self_destruct();
397 return NULL;
398 }
399
400 if (CfgFiles.size() > 16) {
401 debugs(3, DBG_CRITICAL, "FATAL: can't open %s for reading parameters: includes are nested too deeply (>16)!\n" << path);
402 self_destruct();
403 return NULL;
404 }
405
406 ConfigParser::CfgFile *wordfile = new ConfigParser::CfgFile();
407 if (!path || !wordfile->startParse(path)) {
408 debugs(3, DBG_CRITICAL, "FATAL: Error opening config file: " << token);
409 delete wordfile;
410 self_destruct();
411 return NULL;
412 }
413 CfgFiles.push(wordfile);
414 token = NULL;
415 }
416 } while (token == NULL && !CfgFiles.empty());
417
418 return token;
419 }
420
421 char *
422 ConfigParser::PeekAtToken()
423 {
424 PreviewMode_ = true;
425 char *token = NextToken();
426 PreviewMode_ = false;
427 return token;
428 }
429
430 char *
431 ConfigParser::NextQuotedOrToEol()
432 {
433 ParseQuotedOrToEol_ = true;
434 char *token = NextToken();
435 ParseQuotedOrToEol_ = false;
436
437 // Assume end of current config line
438 // Close all open configuration files for this config line
439 while (!CfgFiles.empty()) {
440 ConfigParser::CfgFile *wordfile = CfgFiles.top();
441 CfgFiles.pop();
442 delete wordfile;
443 }
444
445 return token;
446 }
447
448 bool
449 ConfigParser::optionalKvPair(char * &key, char * &value)
450 {
451 key = nullptr;
452 value = nullptr;
453
454 if (const char *currentToken = PeekAtToken()) {
455 // NextKvPair() accepts "a = b" and skips "=" or "a=". To avoid
456 // misinterpreting the admin intent, we use strict checks.
457 if (const auto middle = strchr(currentToken, '=')) {
458 if (middle == currentToken)
459 throw TextException(ToSBuf("missing key in a key=value option: ", currentToken), Here());
460 if (middle + 1 == currentToken + strlen(currentToken))
461 throw TextException(ToSBuf("missing value in a key=value option: ", currentToken), Here());
462 } else
463 return false; // not a key=value token
464
465 if (!NextKvPair(key, value)) // may still fail (e.g., bad value quoting)
466 throw TextException(ToSBuf("invalid key=value option: ", currentToken), Here());
467
468 return true;
469 }
470
471 return false; // end of directive or input
472 }
473
474 bool
475 ConfigParser::NextKvPair(char * &key, char * &value)
476 {
477 key = value = NULL;
478 ParseKvPair_ = true;
479 KvPairState_ = ConfigParser::atParseKey;
480 if ((key = NextToken()) != NULL) {
481 KvPairState_ = ConfigParser::atParseValue;
482 value = NextQuotedToken();
483 }
484 ParseKvPair_ = false;
485
486 if (!key)
487 return false;
488 if (!value) {
489 debugs(3, DBG_CRITICAL, "Error while parsing key=value token. Value missing after: " << key);
490 return false;
491 }
492
493 return true;
494 }
495
496 char *
497 ConfigParser::RegexStrtokFile()
498 {
499 if (ConfigParser::RecognizeQuotedValues) {
500 debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
501 self_destruct();
502 }
503 ConfigParser::RecognizeQuotedPair_ = true;
504 char * token = strtokFile();
505 ConfigParser::RecognizeQuotedPair_ = false;
506 return token;
507 }
508
509 char *
510 ConfigParser::RegexPattern()
511 {
512 if (ConfigParser::RecognizeQuotedValues) {
513 debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
514 self_destruct();
515 }
516 ConfigParser::RecognizeQuotedPair_ = true;
517 char * token = NextToken();
518 ConfigParser::RecognizeQuotedPair_ = false;
519 return token;
520 }
521
522 char *
523 ConfigParser::NextQuotedToken()
524 {
525 const bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues;
526 ConfigParser::RecognizeQuotedValues = true;
527 char *token = NextToken();
528 ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues;
529 return token;
530 }
531
532 const char *
533 ConfigParser::QuoteString(const String &var)
534 {
535 static String quotedStr;
536 const char *s = var.termedBuf();
537 bool needQuote = false;
538
539 for (const char *l = s; !needQuote && *l != '\0'; ++l )
540 needQuote = !isalnum(*l);
541
542 if (!needQuote)
543 return s;
544
545 quotedStr.clean();
546 quotedStr.append('"');
547 for (; *s != '\0'; ++s) {
548 if (*s == '"' || *s == '\\')
549 quotedStr.append('\\');
550 quotedStr.append(*s);
551 }
552 quotedStr.append('"');
553 return quotedStr.termedBuf();
554 }
555
556 void
557 ConfigParser::rejectDuplicateDirective()
558 {
559 assert(cfg_directive);
560 throw TextException("duplicate configuration directive", Here());
561 }
562
563 void
564 ConfigParser::closeDirective()
565 {
566 assert(cfg_directive);
567 if (const auto garbage = PeekAtToken())
568 throw TextException(ToSBuf("trailing garbage at the end of a configuration directive: ", garbage), Here());
569 // TODO: cfg_directive = nullptr; // currently in generated code
570 }
571
572 SBuf
573 ConfigParser::token(const char *expectedTokenDescription)
574 {
575 if (const auto extractedToken = NextToken()) {
576 debugs(3, 5, CurrentLocation() << ' ' << expectedTokenDescription << ": " << extractedToken);
577 return SBuf(extractedToken);
578 }
579 throw TextException(ToSBuf("missing ", expectedTokenDescription), Here());
580 }
581
582 bool
583 ConfigParser::skipOptional(const char *keyword)
584 {
585 assert(keyword);
586 if (const auto nextToken = PeekAtToken()) {
587 if (strcmp(nextToken, keyword) == 0) {
588 (void)NextToken();
589 return true;
590 }
591 return false; // the next token on the line is not the optional keyword
592 }
593 return false; // no more tokens (i.e. we are at the end of the line)
594 }
595
596 Acl::Tree *
597 ConfigParser::optionalAclList()
598 {
599 if (!skipOptional("if"))
600 return nullptr; // OK: the directive has no ACLs
601
602 Acl::Tree *acls = nullptr;
603 const auto aclCount = aclParseAclList(*this, &acls, cfg_directive);
604 assert(acls);
605 if (aclCount <= 0)
606 throw TextException("missing ACL name(s) after 'if' keyword", Here());
607 return acls;
608 }
609
610 bool
611 ConfigParser::CfgFile::startParse(char *path)
612 {
613 assert(wordFile == NULL);
614 debugs(3, 3, "Parsing from " << path);
615 if ((wordFile = fopen(path, "r")) == NULL) {
616 debugs(3, DBG_CRITICAL, "WARNING: file :" << path << " not found");
617 return false;
618 }
619
620 #if _SQUID_WINDOWS_
621 setmode(fileno(wordFile), O_TEXT);
622 #endif
623
624 filePath = path;
625 return getFileLine();
626 }
627
628 bool
629 ConfigParser::CfgFile::getFileLine()
630 {
631 // Else get the next line
632 if (fgets(parseBuffer, CONFIG_LINE_LIMIT, wordFile) == NULL) {
633 /* stop reading from file */
634 fclose(wordFile);
635 wordFile = NULL;
636 parseBuffer[0] = '\0';
637 return false;
638 }
639 parsePos = parseBuffer;
640 currentLine = parseBuffer;
641 lineNo++;
642 return true;
643 }
644
645 char *
646 ConfigParser::CfgFile::parse(ConfigParser::TokenType &type)
647 {
648 if (!wordFile)
649 return NULL;
650
651 if (!*parseBuffer)
652 return NULL;
653
654 char *token;
655 while (!(token = nextElement(type))) {
656 if (!getFileLine())
657 return NULL;
658 }
659 return token;
660 }
661
662 char *
663 ConfigParser::CfgFile::nextElement(ConfigParser::TokenType &type)
664 {
665 const char *pos = parsePos;
666 char *token = TokenParse(pos, type);
667 if (!PreviewMode_ || type == FunctionParameters)
668 parsePos = pos;
669 // else next call will read the same token;
670 return token;
671 }
672
673 ConfigParser::CfgFile::~CfgFile()
674 {
675 if (wordFile)
676 fclose(wordFile);
677 }
678