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