]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ConfigParser.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / ConfigParser.cc
1 /*
2 * Copyright (C) 1996-2015 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 "cache_cf.h"
11 #include "ConfigParser.h"
12 #include "Debug.h"
13 #include "fatal.h"
14 #include "globals.h"
15
16 bool ConfigParser::RecognizeQuotedValues = true;
17 bool ConfigParser::StrictMode = true;
18 std::stack<ConfigParser::CfgFile *> ConfigParser::CfgFiles;
19 ConfigParser::TokenType ConfigParser::LastTokenType = ConfigParser::SimpleToken;
20 const char *ConfigParser::CfgLine = NULL;
21 const char *ConfigParser::CfgPos = NULL;
22 std::queue<char *> ConfigParser::CfgLineTokens_;
23 std::queue<std::string> ConfigParser::Undo_;
24 bool ConfigParser::AllowMacros_ = false;
25 bool ConfigParser::ParseQuotedOrToEol_ = false;
26 bool ConfigParser::ParseKvPair_ = false;
27 ConfigParser::ParsingStates ConfigParser::KvPairState_ = ConfigParser::atParseKey;
28 bool ConfigParser::RecognizeQuotedPair_ = false;
29 bool ConfigParser::PreviewMode_ = false;
30
31 static const char *SQUID_ERROR_TOKEN = "[invalid token]";
32
33 void
34 ConfigParser::destruct()
35 {
36 shutting_down = 1;
37 if (!CfgFiles.empty()) {
38 std::ostringstream message;
39 CfgFile *f = CfgFiles.top();
40 message << "Bungled " << f->filePath << " line " << f->lineNo <<
41 ": " << f->currentLine << std::endl;
42 CfgFiles.pop();
43 delete f;
44 while (!CfgFiles.empty()) {
45 f = CfgFiles.top();
46 message << " included from " << f->filePath << " line " <<
47 f->lineNo << ": " << f->currentLine << std::endl;
48 CfgFiles.pop();
49 delete f;
50 }
51 message << " included from " << cfg_filename << " line " <<
52 config_lineno << ": " << config_input_line << std::endl;
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);
58 }
59
60 void
61 ConfigParser::TokenPutBack(const char *tok)
62 {
63 assert(tok);
64 Undo_.push(tok);
65 }
66
67 char *
68 ConfigParser::Undo()
69 {
70 LOCAL_ARRAY(char, undoToken, CONFIG_LINE_LIMIT);
71 if (!Undo_.empty()) {
72 strncpy(undoToken, Undo_.front().c_str(), sizeof(undoToken));
73 undoToken[sizeof(undoToken) - 1] = '\0';
74 if (!PreviewMode_)
75 Undo_.pop();
76 return undoToken;
77 }
78 return NULL;
79 }
80
81 char *
82 ConfigParser::strtokFile()
83 {
84 if (RecognizeQuotedValues)
85 return ConfigParser::NextToken();
86
87 static int fromFile = 0;
88 static FILE *wordFile = NULL;
89
90 char *t;
91 LOCAL_ARRAY(char, buf, CONFIG_LINE_LIMIT);
92
93 if ((t = ConfigParser::Undo()))
94 return t;
95
96 do {
97
98 if (!fromFile) {
99 ConfigParser::TokenType tokenType;
100 t = ConfigParser::NextElement(tokenType);
101 if (!t) {
102 return NULL;
103 } else if (*t == '\"' || *t == '\'') {
104 /* quote found, start reading from file */
105 debugs(3, 8,"Quoted token found : " << t);
106 char *fn = ++t;
107
108 while (*t && *t != '\"' && *t != '\'')
109 ++t;
110
111 *t = '\0';
112
113 if ((wordFile = fopen(fn, "r")) == NULL) {
114 debugs(3, DBG_CRITICAL, "ERROR: Can not open file " << fn << " for reading");
115 return NULL;
116 }
117
118 #if _SQUID_WINDOWS_
119 setmode(fileno(wordFile), O_TEXT);
120 #endif
121
122 fromFile = 1;
123 } else {
124 return t;
125 }
126 }
127
128 /* fromFile */
129 if (fgets(buf, CONFIG_LINE_LIMIT, wordFile) == NULL) {
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);
141 t3 = t2 + strspn(t2, w_space);
142
143 while (*t3 && *t3 != '#') {
144 t2 = t3 + strcspn(t3, w_space);
145 t3 = t2 + strspn(t2, w_space);
146 }
147
148 *t2 = '\0';
149 }
150
151 /* skip comments */
152 /* skip blank lines */
153 } while ( *t == '#' || !*t );
154
155 return t;
156 }
157
158 char *
159 ConfigParser::UnQuote(const char *token, const char **next)
160 {
161 const char *errorStr = NULL;
162 const char *errorPos = NULL;
163 char quoteChar = *token;
164 assert(quoteChar == '"' || quoteChar == '\'');
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*/
169 while (*s && *s != quoteChar && !errorStr && (size_t)(d - UnQuoted) < sizeof(UnQuoted)) {
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
191 } else if (*s == '$' && quoteChar == '"') {
192 errorStr = "Unsupported cfg macro";
193 errorPos = s;
194 #endif
195 #if 0
196 } else if (*s == '%' && quoteChar == '"' && (!AllowMacros_ )) {
197 errorStr = "Macros are not supported here";
198 errorPos = s;
199 #endif
200 } else
201 *d = *s;
202 ++s;
203 ++d;
204 }
205
206 if (*s != quoteChar && !errorStr) {
207 errorStr = "missing quote char at the end of quoted string";
208 errorPos = s - 1;
209 }
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_)
221 strncpy(UnQuoted, SQUID_ERROR_TOKEN, sizeof(UnQuoted));
222 else {
223 debugs(3, DBG_CRITICAL, "FATAL: " << errorStr << ": " << errorPos);
224 self_destruct();
225 }
226 }
227
228 if (next)
229 *next = s + 1;
230 return UnQuoted;
231 }
232
233 void
234 ConfigParser::SetCfgLine(char *line)
235 {
236 CfgLine = line;
237 CfgPos = line;
238 while (!CfgLineTokens_.empty()) {
239 char *token = CfgLineTokens_.front();
240 CfgLineTokens_.pop();
241 free(token);
242 }
243 }
244
245 char *
246 ConfigParser::TokenParse(const char * &nextToken, ConfigParser::TokenType &type)
247 {
248 if (!nextToken || *nextToken == '\0')
249 return NULL;
250 type = ConfigParser::SimpleToken;
251 nextToken += strspn(nextToken, w_space);
252
253 if (*nextToken == '#')
254 return NULL;
255
256 if (ConfigParser::RecognizeQuotedValues && (*nextToken == '"' || *nextToken == '\'')) {
257 type = ConfigParser::QuotedToken;
258 char *token = xstrdup(UnQuote(nextToken, &nextToken));
259 CfgLineTokens_.push(token);
260 return token;
261 }
262
263 const char *tokenStart = nextToken;
264 const char *sep;
265 if (ConfigParser::ParseKvPair_) {
266 if (ConfigParser::KvPairState_ == ConfigParser::atParseKey)
267 sep = "=";
268 else
269 sep = w_space;
270 } else if (ConfigParser::ParseQuotedOrToEol_)
271 sep = "\n";
272 else if (ConfigParser::RecognizeQuotedPair_)
273 sep = w_space "\\";
274 else if (!ConfigParser::RecognizeQuotedValues || *nextToken == '(')
275 sep = w_space;
276 else
277 sep = w_space "(";
278 nextToken += strcspn(nextToken, sep);
279
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') {
283 nextToken += 2; // skip the quoted-pair (\-escaped) character
284 nextToken += strcspn(nextToken, sep);
285 } else {
286 debugs(3, DBG_CRITICAL, "FATAL: Unescaped '\' character in regex pattern: " << tokenStart);
287 self_destruct();
288 }
289 }
290
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 {
300 debugs(3, DBG_CRITICAL, "FATAL: Unknown cfg function: " << tokenStart);
301 self_destruct();
302 }
303 }
304 } else
305 type = ConfigParser::SimpleToken;
306
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 {
324 debugs(3, DBG_CRITICAL, "FATAL: Not alphanumeric character '"<< *s << "' in unquoted token " << tokenStart);
325 self_destruct();
326 }
327 }
328 }
329 }
330 token = xstrndup(tokenStart, nextToken - tokenStart + 1);
331 CfgLineTokens_.push(token);
332 }
333
334 if (*nextToken != '\0' && *nextToken != '#') {
335 ++nextToken;
336 }
337
338 return token;
339 }
340
341 char *
342 ConfigParser::NextElement(ConfigParser::TokenType &type)
343 {
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
353 return token;
354 }
355
356 char *
357 ConfigParser::NextToken()
358 {
359 char *token = NULL;
360 if ((token = ConfigParser::Undo())) {
361 debugs(3, 6, "TOKEN (undone): " << token);
362 return token;
363 }
364
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();
372 debugs(3, 4, "CfgFiles.pop " << wordfile->filePath);
373 delete wordfile;
374 }
375 }
376
377 if (!token)
378 token = NextElement(LastTokenType);
379
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
385 char *path = NextToken();
386 if (LastTokenType != ConfigParser::QuotedToken) {
387 debugs(3, DBG_CRITICAL, "FATAL: Quoted filename missing: " << token);
388 self_destruct();
389 return NULL;
390 }
391
392 // The next token in current cfg file line must be a ")"
393 char *end = NextToken();
394 ConfigParser::PreviewMode_ = savePreview;
395 if (LastTokenType != ConfigParser::SimpleToken || strcmp(end, ")") != 0) {
396 debugs(3, DBG_CRITICAL, "FATAL: missing ')' after " << token << "(\"" << path << "\"");
397 self_destruct();
398 return NULL;
399 }
400
401 if (CfgFiles.size() > 16) {
402 debugs(3, DBG_CRITICAL, "FATAL: can't open %s for reading parameters: includes are nested too deeply (>16)!\n" << path);
403 self_destruct();
404 return NULL;
405 }
406
407 ConfigParser::CfgFile *wordfile = new ConfigParser::CfgFile();
408 if (!path || !wordfile->startParse(path)) {
409 debugs(3, DBG_CRITICAL, "FATAL: Error opening config file: " << token);
410 delete wordfile;
411 self_destruct();
412 return NULL;
413 }
414 CfgFiles.push(wordfile);
415 token = NULL;
416 }
417 } while (token == NULL && !CfgFiles.empty());
418
419 return token;
420 }
421
422 char *
423 ConfigParser::PeekAtToken()
424 {
425 PreviewMode_ = true;
426 char *token = NextToken();
427 PreviewMode_ = false;
428 return token;
429 }
430
431 char *
432 ConfigParser::NextQuotedOrToEol()
433 {
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 }
445
446 return token;
447 }
448
449 bool
450 ConfigParser::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 }
470
471 char *
472 ConfigParser::RegexStrtokFile()
473 {
474 if (ConfigParser::RecognizeQuotedValues) {
475 debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
476 self_destruct();
477 }
478 ConfigParser::RecognizeQuotedPair_ = true;
479 char * token = strtokFile();
480 ConfigParser::RecognizeQuotedPair_ = false;
481 return token;
482 }
483
484 char *
485 ConfigParser::RegexPattern()
486 {
487 if (ConfigParser::RecognizeQuotedValues) {
488 debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
489 self_destruct();
490 }
491 ConfigParser::RecognizeQuotedPair_ = true;
492 char * token = NextToken();
493 ConfigParser::RecognizeQuotedPair_ = false;
494 return token;
495 }
496
497 char *
498 ConfigParser::NextQuotedToken()
499 {
500 const bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues;
501 ConfigParser::RecognizeQuotedValues = true;
502 char *token = NextToken();
503 ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues;
504 return token;
505 }
506
507 const char *
508 ConfigParser::QuoteString(const String &var)
509 {
510 static String quotedStr;
511 const char *s = var.termedBuf();
512 bool needQuote = false;
513
514 for (const char *l = s; !needQuote && *l != '\0'; ++l )
515 needQuote = !isalnum(*l);
516
517 if (!needQuote)
518 return s;
519
520 quotedStr.clean();
521 quotedStr.append('"');
522 for (; *s != '\0'; ++s) {
523 if (*s == '"' || *s == '\\')
524 quotedStr.append('\\');
525 quotedStr.append(*s);
526 }
527 quotedStr.append('"');
528 return quotedStr.termedBuf();
529 }
530
531 bool
532 ConfigParser::CfgFile::startParse(char *path)
533 {
534 assert(wordFile == NULL);
535 debugs(3, 3, "Parsing from " << path);
536 if ((wordFile = fopen(path, "r")) == NULL) {
537 debugs(3, DBG_CRITICAL, "WARNING: file :" << path << " not found");
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
549 bool
550 ConfigParser::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
566 char *
567 ConfigParser::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
583 char *
584 ConfigParser::CfgFile::nextElement(ConfigParser::TokenType &type)
585 {
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;
592 }
593
594 ConfigParser::CfgFile::~CfgFile()
595 {
596 if (wordFile)
597 fclose(wordFile);
598 }
599