4 * SQUID Web Proxy Cache http://www.squid-cache.org/
5 * ----------------------------------------------------------
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.
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.
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.
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.
31 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
36 #include "ConfigParser.h"
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;
53 static const char *SQUID_ERROR_TOKEN
= "[invalid token]";
56 ConfigParser::destruct()
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
;
66 while (!CfgFiles
.empty()) {
68 message
<< " included from " << f
->filePath
<< " line " <<
69 f
->lineNo
<< ": " << f
->currentLine
<< std::endl
;
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());
78 fatalf("Bungled %s line %d: %s",
79 cfg_filename
, config_lineno
, config_input_line
);
83 ConfigParser::TokenPutBack(const char *tok
)
92 LOCAL_ARRAY(char, undoToken
, CONFIG_LINE_LIMIT
);
94 strncpy(undoToken
, Undo_
.front().c_str(), sizeof(undoToken
));
95 undoToken
[sizeof(undoToken
) - 1] = '\0';
104 ConfigParser::strtokFile()
106 if (RecognizeQuotedValues
)
107 return ConfigParser::NextToken();
109 static int fromFile
= 0;
110 static FILE *wordFile
= NULL
;
113 LOCAL_ARRAY(char, buf
, CONFIG_LINE_LIMIT
);
115 if ((t
= ConfigParser::Undo()))
121 ConfigParser::TokenType tokenType
;
122 t
= ConfigParser::NextElement(tokenType
);
125 } else if (*t
== '\"' || *t
== '\'') {
126 /* quote found, start reading from file */
127 debugs(3, 8,"Quoted token found : " << t
);
130 while (*t
&& *t
!= '\"' && *t
!= '\'')
135 if ((wordFile
= fopen(fn
, "r")) == NULL
) {
136 debugs(3, DBG_CRITICAL
, "Can not open file " << t
<< " for reading");
141 setmode(fileno(wordFile
), O_TEXT
);
151 if (fgets(buf
, CONFIG_LINE_LIMIT
, wordFile
) == NULL
) {
152 /* stop reading from file */
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
);
165 while (*t3
&& *t3
!= '#') {
166 t2
= t3
+ strcspn(t3
, w_space
);
167 t3
= t2
+ strspn(t2
, w_space
);
174 /* skip blank lines */
175 } while ( *t
== '#' || !*t
);
181 ConfigParser::UnQuote(const char *token
, const char **next
)
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;
190 /* scan until the end of the quoted string, handling escape sequences*/
191 while (*s
&& *s
!= quoteChar
&& !errorStr
&& (size_t)(d
- UnQuoted
) < sizeof(UnQuoted
)) {
206 errorStr
= "Unsupported escape sequence";
213 } else if (*s
== '$' && quoteChar
== '"') {
214 errorStr
= "Unsupported cfg macro";
218 } else if (*s
== '%' && quoteChar
== '"' && (!AllowMacros_
)) {
219 errorStr
= "Macros are not supported here";
228 if (*s
!= quoteChar
&& !errorStr
) {
229 errorStr
= "missing quote char at the end of quoted string";
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";
243 strncpy(UnQuoted
, SQUID_ERROR_TOKEN
, sizeof(UnQuoted
));
245 debugs(3, DBG_CRITICAL
, errorStr
<< ": " << errorPos
);
256 ConfigParser::SetCfgLine(char *line
)
260 while (!CfgLineTokens_
.empty()) {
261 char *token
= CfgLineTokens_
.front();
262 CfgLineTokens_
.pop();
268 ConfigParser::TokenParse(const char * &nextToken
, ConfigParser::TokenType
&type
)
270 if (!nextToken
|| *nextToken
== '\0')
272 type
= ConfigParser::SimpleToken
;
273 nextToken
+= strspn(nextToken
, w_space
);
275 if (*nextToken
== '#')
278 if (ConfigParser::RecognizeQuotedValues
&& (*nextToken
== '"' || *nextToken
== '\'')) {
279 type
= ConfigParser::QuotedToken
;
280 char *token
= xstrdup(UnQuote(nextToken
, &nextToken
));
281 CfgLineTokens_
.push(token
);
285 const char *tokenStart
= nextToken
;
287 if (ConfigParser::ParseQuotedOrToEol_
)
289 else if (!ConfigParser::RecognizeQuotedValues
|| *nextToken
== '(')
293 nextToken
+= strcspn(nextToken
, sep
);
295 if (ConfigParser::RecognizeQuotedValues
&& *nextToken
== '(') {
296 if (strncmp(tokenStart
, "parameters", nextToken
- tokenStart
) == 0)
297 type
= ConfigParser::FunctionParameters
;
300 char *err
= xstrdup(SQUID_ERROR_TOKEN
);
301 CfgLineTokens_
.push(err
);
304 debugs(3, DBG_CRITICAL
, "Unknown cfg function: " << tokenStart
);
309 type
= ConfigParser::SimpleToken
;
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
));
320 tokenIsNumber
= false;
324 char *err
= xstrdup(SQUID_ERROR_TOKEN
);
325 CfgLineTokens_
.push(err
);
328 debugs(3, DBG_CRITICAL
, "Not alphanumeric character '"<< *s
<< "' in unquoted token " << tokenStart
);
334 token
= xstrndup(tokenStart
, nextToken
- tokenStart
+ 1);
335 CfgLineTokens_
.push(token
);
338 if (*nextToken
!= '\0' && *nextToken
!= '#') {
346 ConfigParser::NextElement(ConfigParser::TokenType
&type
)
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
)
356 // else next call will read the same token
361 ConfigParser::NextToken()
364 if ((token
= ConfigParser::Undo())) {
365 debugs(3, 6, "TOKEN (undone): " << token
);
370 while (token
== NULL
&& !CfgFiles
.empty()) {
371 ConfigParser::CfgFile
*wordfile
= CfgFiles
.top();
372 token
= wordfile
->parse(LastTokenType
);
374 assert(!wordfile
->isOpen());
376 debugs(3, 4, "CfgFiles.pop " << wordfile
->filePath
);
382 token
= NextElement(LastTokenType
);
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;
389 char *path
= NextToken();
390 if (LastTokenType
!= ConfigParser::QuotedToken
) {
391 debugs(3, DBG_CRITICAL
, "Quoted filename missing: " << token
);
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
<< "\"");
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
);
411 ConfigParser::CfgFile
*wordfile
= new ConfigParser::CfgFile();
412 if (!path
|| !wordfile
->startParse(path
)) {
413 debugs(3, DBG_CRITICAL
, "Error opening config file: " << token
);
418 CfgFiles
.push(wordfile
);
421 } while (token
== NULL
&& !CfgFiles
.empty());
427 ConfigParser::PeekAtToken()
430 char *token
= NextToken();
431 PreviewMode_
= false;
436 ConfigParser::NextQuotedOrToEol()
438 ParseQuotedOrToEol_
= true;
439 char *token
= NextToken();
440 ParseQuotedOrToEol_
= false;
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();
454 ConfigParser::RegexStrtokFile()
456 if (ConfigParser::RecognizeQuotedValues
) {
457 debugs(3, DBG_CRITICAL
, "Can not read regex expresion while configuration_includes_quoted_values is enabled");
460 char * token
= strtokFile();
465 ConfigParser::RegexPattern()
467 if (ConfigParser::RecognizeQuotedValues
) {
468 debugs(3, DBG_CRITICAL
, "Can not read regex expresion while configuration_includes_quoted_values is enabled");
472 char * token
= NextToken();
477 ConfigParser::NextQuotedToken()
479 const bool saveRecognizeQuotedValues
= ConfigParser::RecognizeQuotedValues
;
480 ConfigParser::RecognizeQuotedValues
= true;
481 char *token
= NextToken();
482 ConfigParser::RecognizeQuotedValues
= saveRecognizeQuotedValues
;
487 ConfigParser::QuoteString(const String
&var
)
489 static String quotedStr
;
490 const char *s
= var
.termedBuf();
491 bool needQuote
= false;
493 for (const char *l
= s
; !needQuote
&& *l
!= '\0'; ++l
)
494 needQuote
= !isalnum(*l
);
500 quotedStr
.append('"');
501 for (; *s
!= '\0'; ++s
) {
502 if (*s
== '"' || *s
== '\\')
503 quotedStr
.append('\\');
504 quotedStr
.append(*s
);
506 quotedStr
.append('"');
507 return quotedStr
.termedBuf();
511 ConfigParser::CfgFile::startParse(char *path
)
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");
521 setmode(fileno(wordFile
), O_TEXT
);
525 return getFileLine();
529 ConfigParser::CfgFile::getFileLine()
531 // Else get the next line
532 if (fgets(parseBuffer
, CONFIG_LINE_LIMIT
, wordFile
) == NULL
) {
533 /* stop reading from file */
536 parseBuffer
[0] = '\0';
539 parsePos
= parseBuffer
;
540 currentLine
= parseBuffer
;
546 ConfigParser::CfgFile::parse(ConfigParser::TokenType
&type
)
555 while (!(token
= nextElement(type
))) {
563 ConfigParser::CfgFile::nextElement(ConfigParser::TokenType
&type
)
565 const char *pos
= parsePos
;
566 char *token
= TokenParse(pos
, type
);
567 if (!PreviewMode_
|| type
== FunctionParameters
)
569 // else next call will read the same token;
573 ConfigParser::CfgFile::~CfgFile()