]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ConfigParser.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / ConfigParser.cc
CommitLineData
d295d770 1/*
ef57eb7b 2 * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
d295d770 3 *
bbc27441
AJ
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.
d295d770 7 */
8
f7f3304a 9#include "squid.h"
8a01b99e 10#include "cache_cf.h"
d295d770 11#include "ConfigParser.h"
582c2af2 12#include "Debug.h"
9ce629cf 13#include "fatal.h"
d295d770 14#include "globals.h"
15
bde7a8ce
CT
16bool ConfigParser::RecognizeQuotedValues = true;
17bool ConfigParser::StrictMode = true;
2eceb328
CT
18std::stack<ConfigParser::CfgFile *> ConfigParser::CfgFiles;
19ConfigParser::TokenType ConfigParser::LastTokenType = ConfigParser::SimpleToken;
bde7a8ce
CT
20const char *ConfigParser::CfgLine = NULL;
21const char *ConfigParser::CfgPos = NULL;
22std::queue<char *> ConfigParser::CfgLineTokens_;
2eceb328
CT
23std::queue<std::string> ConfigParser::Undo_;
24bool ConfigParser::AllowMacros_ = false;
bde7a8ce 25bool ConfigParser::ParseQuotedOrToEol_ = false;
32fd6d8a
CT
26bool ConfigParser::ParseKvPair_ = false;
27ConfigParser::ParsingStates ConfigParser::KvPairState_ = ConfigParser::atParseKey;
61a31961 28bool ConfigParser::RecognizeQuotedPair_ = false;
bde7a8ce
CT
29bool ConfigParser::PreviewMode_ = false;
30
31static const char *SQUID_ERROR_TOKEN = "[invalid token]";
33810b1d 32
d295d770 33void
a9f20260 34ConfigParser::destruct()
d295d770 35{
36 shutting_down = 1;
2eceb328
CT
37 if (!CfgFiles.empty()) {
38 std::ostringstream message;
39 CfgFile *f = CfgFiles.top();
40 message << "Bungled " << f->filePath << " line " << f->lineNo <<
f53969cc 41 ": " << f->currentLine << std::endl;
2eceb328
CT
42 CfgFiles.pop();
43 delete f;
44 while (!CfgFiles.empty()) {
45 f = CfgFiles.top();
46 message << " included from " << f->filePath << " line " <<
f53969cc 47 f->lineNo << ": " << f->currentLine << std::endl;
2eceb328
CT
48 CfgFiles.pop();
49 delete f;
50 }
51 message << " included from " << cfg_filename << " line " <<
f53969cc 52 config_lineno << ": " << config_input_line << std::endl;
2eceb328
CT
53 std::string msg = message.str();
54 fatalf("%s", msg.c_str());
55 } else
56 fatalf("Bungled %s line %d: %s",
57 cfg_filename, config_lineno, config_input_line);
d295d770 58}
59
33810b1d 60void
2eceb328 61ConfigParser::TokenPutBack(const char *tok)
33810b1d
CT
62{
63 assert(tok);
2eceb328 64 Undo_.push(tok);
33810b1d
CT
65}
66
d295d770 67char *
2eceb328 68ConfigParser::Undo()
d295d770 69{
cfd861ab 70 static char undoToken[CONFIG_LINE_LIMIT];
2eceb328 71 if (!Undo_.empty()) {
cfd861ab 72 xstrncpy(undoToken, Undo_.front().c_str(), sizeof(undoToken));
2eceb328 73 undoToken[sizeof(undoToken) - 1] = '\0';
bde7a8ce
CT
74 if (!PreviewMode_)
75 Undo_.pop();
2eceb328
CT
76 return undoToken;
77 }
78 return NULL;
79}
80
81char *
82ConfigParser::strtokFile()
83{
84 if (RecognizeQuotedValues)
85 return ConfigParser::NextToken();
86
d295d770 87 static int fromFile = 0;
88 static FILE *wordFile = NULL;
89
2eceb328 90 char *t;
cfd861ab 91 static char buf[CONFIG_LINE_LIMIT];
d295d770 92
bde7a8ce
CT
93 if ((t = ConfigParser::Undo()))
94 return t;
33810b1d 95
f32cd13e 96 do {
d295d770 97
f32cd13e 98 if (!fromFile) {
2eceb328 99 ConfigParser::TokenType tokenType;
bde7a8ce 100 t = ConfigParser::NextElement(tokenType);
2eceb328 101 if (!t) {
26ac0430 102 return NULL;
bde7a8ce 103 } else if (*t == '\"' || *t == '\'') {
f32cd13e 104 /* quote found, start reading from file */
2eceb328 105 debugs(3, 8,"Quoted token found : " << t);
bde7a8ce 106 char *fn = ++t;
d295d770 107
bde7a8ce
CT
108 while (*t && *t != '\"' && *t != '\'')
109 ++t;
110
111 *t = '\0';
112
113 if ((wordFile = fopen(fn, "r")) == NULL) {
39952fbb 114 debugs(3, DBG_CRITICAL, "ERROR: Can not open file " << fn << " for reading");
4a4b58d5 115 return NULL;
f32cd13e 116 }
d295d770 117
be266cb2 118#if _SQUID_WINDOWS_
f32cd13e 119 setmode(fileno(wordFile), O_TEXT);
d295d770 120#endif
121
f32cd13e
AJ
122 fromFile = 1;
123 } else {
bde7a8ce 124 return t;
f32cd13e 125 }
d295d770 126 }
f32cd13e
AJ
127
128 /* fromFile */
cfd861ab 129 if (fgets(buf, sizeof(buf), wordFile) == NULL) {
f32cd13e
AJ
130 /* stop reading from file */
131 fclose(wordFile);
132 wordFile = NULL;
133 fromFile = 0;
134 return NULL;
135 } else {
136 char *t2, *t3;
137 t = buf;
138 /* skip leading and trailing white space */
139 t += strspn(buf, w_space);
140 t2 = t + strcspn(t, w_space);
d295d770 141 t3 = t2 + strspn(t2, w_space);
d295d770 142
f32cd13e
AJ
143 while (*t3 && *t3 != '#') {
144 t2 = t3 + strcspn(t3, w_space);
145 t3 = t2 + strspn(t2, w_space);
146 }
d295d770 147
f32cd13e
AJ
148 *t2 = '\0';
149 }
d295d770 150
f32cd13e 151 /* skip comments */
d295d770 152 /* skip blank lines */
26ac0430 153 } while ( *t == '#' || !*t );
d295d770 154
bde7a8ce 155 return t;
71be37e0
CT
156}
157
2eceb328 158char *
bde7a8ce 159ConfigParser::UnQuote(const char *token, const char **next)
71be37e0 160{
bde7a8ce
CT
161 const char *errorStr = NULL;
162 const char *errorPos = NULL;
2eceb328
CT
163 char quoteChar = *token;
164 assert(quoteChar == '"' || quoteChar == '\'');
bde7a8ce
CT
165 LOCAL_ARRAY(char, UnQuoted, CONFIG_LINE_LIMIT);
166 const char *s = token + 1;
167 char *d = UnQuoted;
168 /* scan until the end of the quoted string, handling escape sequences*/
a59fd1df 169 while (*s && *s != quoteChar && !errorStr && (size_t)(d - UnQuoted) < sizeof(UnQuoted)) {
bde7a8ce
CT
170 if (*s == '\\') {
171 s++;
172 switch (*s) {
173 case 'r':
174 *d = '\r';
175 break;
176 case 'n':
177 *d = '\n';
178 break;
179 case 't':
180 *d = '\t';
181 break;
182 default:
183 if (isalnum(*s)) {
184 errorStr = "Unsupported escape sequence";
185 errorPos = s;
186 }
187 *d = *s;
188 break;
189 }
190#if 0
2eceb328 191 } else if (*s == '$' && quoteChar == '"') {
bde7a8ce
CT
192 errorStr = "Unsupported cfg macro";
193 errorPos = s;
194#endif
1ee7b53a 195#if 0
2eceb328 196 } else if (*s == '%' && quoteChar == '"' && (!AllowMacros_ )) {
bde7a8ce
CT
197 errorStr = "Macros are not supported here";
198 errorPos = s;
1ee7b53a 199#endif
bde7a8ce
CT
200 } else
201 *d = *s;
95dc7ff4 202 ++s;
bde7a8ce 203 ++d;
71be37e0
CT
204 }
205
bde7a8ce
CT
206 if (*s != quoteChar && !errorStr) {
207 errorStr = "missing quote char at the end of quoted string";
208 errorPos = s - 1;
71be37e0 209 }
bde7a8ce
CT
210 // The end of token
211 *d = '\0';
212
213 // We are expecting a separator after quoted string, space or one of "()#"
214 if (*(s + 1) != '\0' && !strchr(w_space "()#", *(s + 1)) && !errorStr) {
215 errorStr = "Expecting space after the end of quoted token";
216 errorPos = token;
217 }
218
219 if (errorStr) {
220 if (PreviewMode_)
cfd861ab 221 xstrncpy(UnQuoted, SQUID_ERROR_TOKEN, sizeof(UnQuoted));
bde7a8ce 222 else {
39952fbb 223 debugs(3, DBG_CRITICAL, "FATAL: " << errorStr << ": " << errorPos);
bde7a8ce
CT
224 self_destruct();
225 }
226 }
227
228 if (next)
229 *next = s + 1;
230 return UnQuoted;
2eceb328
CT
231}
232
233void
234ConfigParser::SetCfgLine(char *line)
235{
236 CfgLine = line;
237 CfgPos = line;
bde7a8ce
CT
238 while (!CfgLineTokens_.empty()) {
239 char *token = CfgLineTokens_.front();
240 CfgLineTokens_.pop();
241 free(token);
242 }
2eceb328
CT
243}
244
245char *
bde7a8ce 246ConfigParser::TokenParse(const char * &nextToken, ConfigParser::TokenType &type)
2eceb328
CT
247{
248 if (!nextToken || *nextToken == '\0')
249 return NULL;
250 type = ConfigParser::SimpleToken;
251 nextToken += strspn(nextToken, w_space);
bde7a8ce
CT
252
253 if (*nextToken == '#')
254 return NULL;
255
256 if (ConfigParser::RecognizeQuotedValues && (*nextToken == '"' || *nextToken == '\'')) {
2eceb328 257 type = ConfigParser::QuotedToken;
bde7a8ce
CT
258 char *token = xstrdup(UnQuote(nextToken, &nextToken));
259 CfgLineTokens_.push(token);
2eceb328
CT
260 return token;
261 }
262
bde7a8ce 263 const char *tokenStart = nextToken;
2eceb328 264 const char *sep;
32fd6d8a
CT
265 if (ConfigParser::ParseKvPair_) {
266 if (ConfigParser::KvPairState_ == ConfigParser::atParseKey)
267 sep = "=";
268 else
269 sep = w_space;
270 } else if (ConfigParser::ParseQuotedOrToEol_)
bde7a8ce 271 sep = "\n";
61a31961
AJ
272 else if (ConfigParser::RecognizeQuotedPair_)
273 sep = w_space "\\";
c8b68657
AJ
274 else if (!ConfigParser::RecognizeQuotedValues || *nextToken == '(')
275 sep = w_space;
2eceb328
CT
276 else
277 sep = w_space "(";
278 nextToken += strcspn(nextToken, sep);
279
c8b68657
AJ
280 while (ConfigParser::RecognizeQuotedPair_ && *nextToken == '\\') {
281 // NP: do not permit \0 terminator to be escaped.
282 if (*(nextToken+1) && *(nextToken+1) != '\r' && *(nextToken+1) != '\n') {
f53969cc
SM
283 nextToken += 2; // skip the quoted-pair (\-escaped) character
284 nextToken += strcspn(nextToken, sep);
c8b68657
AJ
285 } else {
286 debugs(3, DBG_CRITICAL, "FATAL: Unescaped '\' character in regex pattern: " << tokenStart);
287 self_destruct();
288 }
61a31961
AJ
289 }
290
bde7a8ce
CT
291 if (ConfigParser::RecognizeQuotedValues && *nextToken == '(') {
292 if (strncmp(tokenStart, "parameters", nextToken - tokenStart) == 0)
293 type = ConfigParser::FunctionParameters;
294 else {
295 if (PreviewMode_) {
296 char *err = xstrdup(SQUID_ERROR_TOKEN);
297 CfgLineTokens_.push(err);
298 return err;
299 } else {
39952fbb 300 debugs(3, DBG_CRITICAL, "FATAL: Unknown cfg function: " << tokenStart);
bde7a8ce
CT
301 self_destruct();
302 }
303 }
304 } else
2eceb328
CT
305 type = ConfigParser::SimpleToken;
306
bde7a8ce
CT
307 char *token = NULL;
308 if (nextToken - tokenStart) {
309 if (ConfigParser::StrictMode && type == ConfigParser::SimpleToken) {
310 bool tokenIsNumber = true;
311 for (const char *s = tokenStart; s != nextToken; ++s) {
312 const bool isValidChar = isalnum(*s) || strchr(".,()-=_/:", *s) ||
313 (tokenIsNumber && *s == '%' && (s + 1 == nextToken));
314
315 if (!isdigit(*s))
316 tokenIsNumber = false;
317
318 if (!isValidChar) {
319 if (PreviewMode_) {
320 char *err = xstrdup(SQUID_ERROR_TOKEN);
321 CfgLineTokens_.push(err);
322 return err;
323 } else {
39952fbb 324 debugs(3, DBG_CRITICAL, "FATAL: Not alphanumeric character '"<< *s << "' in unquoted token " << tokenStart);
bde7a8ce
CT
325 self_destruct();
326 }
327 }
328 }
329 }
330 token = xstrndup(tokenStart, nextToken - tokenStart + 1);
331 CfgLineTokens_.push(token);
2eceb328
CT
332 }
333
bde7a8ce
CT
334 if (*nextToken != '\0' && *nextToken != '#') {
335 ++nextToken;
336 }
2eceb328
CT
337
338 return token;
339}
340
341char *
bde7a8ce 342ConfigParser::NextElement(ConfigParser::TokenType &type)
2eceb328 343{
bde7a8ce
CT
344 const char *pos = CfgPos;
345 char *token = TokenParse(pos, type);
346 // If not in preview mode the next call of this method should start
347 // parsing after the end of current token.
348 // For function "parameters(...)" we need always to update current parsing
349 // position to allow parser read the arguments of "parameters(..)"
350 if (!PreviewMode_ || type == FunctionParameters)
351 CfgPos = pos;
352 // else next call will read the same token
2eceb328
CT
353 return token;
354}
355
356char *
357ConfigParser::NextToken()
358{
2eceb328 359 char *token = NULL;
bde7a8ce
CT
360 if ((token = ConfigParser::Undo())) {
361 debugs(3, 6, "TOKEN (undone): " << token);
362 return token;
363 }
364
2eceb328
CT
365 do {
366 while (token == NULL && !CfgFiles.empty()) {
367 ConfigParser::CfgFile *wordfile = CfgFiles.top();
368 token = wordfile->parse(LastTokenType);
369 if (!token) {
370 assert(!wordfile->isOpen());
371 CfgFiles.pop();
bde7a8ce 372 debugs(3, 4, "CfgFiles.pop " << wordfile->filePath);
2eceb328
CT
373 delete wordfile;
374 }
375 }
376
377 if (!token)
378 token = NextElement(LastTokenType);
71be37e0 379
bde7a8ce
CT
380 if (token && LastTokenType == ConfigParser::FunctionParameters) {
381 //Disable temporary preview mode, we need to parse function parameters
382 const bool savePreview = ConfigParser::PreviewMode_;
383 ConfigParser::PreviewMode_ = false;
384
2eceb328
CT
385 char *path = NextToken();
386 if (LastTokenType != ConfigParser::QuotedToken) {
39952fbb 387 debugs(3, DBG_CRITICAL, "FATAL: Quoted filename missing: " << token);
2eceb328
CT
388 self_destruct();
389 return NULL;
390 }
391
392 // The next token in current cfg file line must be a ")"
393 char *end = NextToken();
bde7a8ce 394 ConfigParser::PreviewMode_ = savePreview;
2eceb328 395 if (LastTokenType != ConfigParser::SimpleToken || strcmp(end, ")") != 0) {
39952fbb 396 debugs(3, DBG_CRITICAL, "FATAL: missing ')' after " << token << "(\"" << path << "\"");
2eceb328
CT
397 self_destruct();
398 return NULL;
399 }
400
401 if (CfgFiles.size() > 16) {
39952fbb 402 debugs(3, DBG_CRITICAL, "FATAL: can't open %s for reading parameters: includes are nested too deeply (>16)!\n" << path);
2eceb328
CT
403 self_destruct();
404 return NULL;
405 }
406
407 ConfigParser::CfgFile *wordfile = new ConfigParser::CfgFile();
408 if (!path || !wordfile->startParse(path)) {
39952fbb 409 debugs(3, DBG_CRITICAL, "FATAL: Error opening config file: " << token);
2eceb328
CT
410 delete wordfile;
411 self_destruct();
412 return NULL;
413 }
414 CfgFiles.push(wordfile);
415 token = NULL;
2eceb328
CT
416 }
417 } while (token == NULL && !CfgFiles.empty());
418
bde7a8ce
CT
419 return token;
420}
421
422char *
423ConfigParser::PeekAtToken()
424{
425 PreviewMode_ = true;
426 char *token = NextToken();
427 PreviewMode_ = false;
428 return token;
2eceb328
CT
429}
430
431char *
432ConfigParser::NextQuotedOrToEol()
433{
bde7a8ce
CT
434 ParseQuotedOrToEol_ = true;
435 char *token = NextToken();
436 ParseQuotedOrToEol_ = false;
437
438 // Assume end of current config line
439 // Close all open configuration files for this config line
440 while (!CfgFiles.empty()) {
441 ConfigParser::CfgFile *wordfile = CfgFiles.top();
442 CfgFiles.pop();
443 delete wordfile;
444 }
2eceb328 445
bde7a8ce
CT
446 return token;
447}
32fd6d8a
CT
448
449bool
450ConfigParser::NextKvPair(char * &key, char * &value)
451{
452 key = value = NULL;
453 ParseKvPair_ = true;
454 KvPairState_ = ConfigParser::atParseKey;
455 if ((key = NextToken()) != NULL) {
456 KvPairState_ = ConfigParser::atParseValue;
457 value = NextQuotedToken();
458 }
459 ParseKvPair_ = false;
460
461 if (!key)
462 return false;
463 if (!value) {
464 debugs(3, DBG_CRITICAL, "Error while parsing key=value token. Value missing after: " << key);
465 return false;
466 }
467
468 return true;
469}
bde7a8ce
CT
470
471char *
472ConfigParser::RegexStrtokFile()
473{
474 if (ConfigParser::RecognizeQuotedValues) {
39952fbb 475 debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
2eceb328 476 self_destruct();
2eceb328 477 }
61a31961 478 ConfigParser::RecognizeQuotedPair_ = true;
bde7a8ce 479 char * token = strtokFile();
61a31961 480 ConfigParser::RecognizeQuotedPair_ = false;
bde7a8ce
CT
481 return token;
482}
2eceb328 483
bde7a8ce
CT
484char *
485ConfigParser::RegexPattern()
486{
487 if (ConfigParser::RecognizeQuotedValues) {
39952fbb 488 debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
bde7a8ce
CT
489 self_destruct();
490 }
61a31961 491 ConfigParser::RecognizeQuotedPair_ = true;
bde7a8ce 492 char * token = NextToken();
61a31961 493 ConfigParser::RecognizeQuotedPair_ = false;
bde7a8ce
CT
494 return token;
495}
496
497char *
498ConfigParser::NextQuotedToken()
499{
500 const bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues;
501 ConfigParser::RecognizeQuotedValues = true;
502 char *token = NextToken();
503 ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues;
504 return token;
71be37e0
CT
505}
506
507const char *
b92a47f2 508ConfigParser::QuoteString(const String &var)
71be37e0
CT
509{
510 static String quotedStr;
511 const char *s = var.termedBuf();
512 bool needQuote = false;
513
95dc7ff4 514 for (const char *l = s; !needQuote && *l != '\0'; ++l )
71ee0835 515 needQuote = !isalnum(*l);
71be37e0
CT
516
517 if (!needQuote)
518 return s;
71ee0835 519
71be37e0
CT
520 quotedStr.clean();
521 quotedStr.append('"');
95dc7ff4 522 for (; *s != '\0'; ++s) {
71ee0835 523 if (*s == '"' || *s == '\\')
71be37e0
CT
524 quotedStr.append('\\');
525 quotedStr.append(*s);
526 }
527 quotedStr.append('"');
528 return quotedStr.termedBuf();
529}
2eceb328
CT
530
531bool
532ConfigParser::CfgFile::startParse(char *path)
533{
534 assert(wordFile == NULL);
bde7a8ce 535 debugs(3, 3, "Parsing from " << path);
2eceb328 536 if ((wordFile = fopen(path, "r")) == NULL) {
39952fbb 537 debugs(3, DBG_CRITICAL, "WARNING: file :" << path << " not found");
2eceb328
CT
538 return false;
539 }
540
541#if _SQUID_WINDOWS_
542 setmode(fileno(wordFile), O_TEXT);
543#endif
544
545 filePath = path;
546 return getFileLine();
547}
548
549bool
550ConfigParser::CfgFile::getFileLine()
551{
552 // Else get the next line
553 if (fgets(parseBuffer, CONFIG_LINE_LIMIT, wordFile) == NULL) {
554 /* stop reading from file */
555 fclose(wordFile);
556 wordFile = NULL;
557 parseBuffer[0] = '\0';
558 return false;
559 }
560 parsePos = parseBuffer;
561 currentLine = parseBuffer;
562 lineNo++;
563 return true;
564}
565
566char *
567ConfigParser::CfgFile::parse(ConfigParser::TokenType &type)
568{
569 if (!wordFile)
570 return NULL;
571
572 if (!*parseBuffer)
573 return NULL;
574
575 char *token;
576 while (!(token = nextElement(type))) {
577 if (!getFileLine())
578 return NULL;
579 }
580 return token;
581}
582
583char *
584ConfigParser::CfgFile::nextElement(ConfigParser::TokenType &type)
585{
bde7a8ce
CT
586 const char *pos = parsePos;
587 char *token = TokenParse(pos, type);
588 if (!PreviewMode_ || type == FunctionParameters)
589 parsePos = pos;
590 // else next call will read the same token;
591 return token;
2eceb328
CT
592}
593
594ConfigParser::CfgFile::~CfgFile()
595{
596 if (wordFile)
597 fclose(wordFile);
598}
f53969cc 599