2 * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
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.
11 #include "ConfigParser.h"
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;
31 static const char *SQUID_ERROR_TOKEN
= "[invalid token]";
34 ConfigParser::destruct()
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
;
44 while (!CfgFiles
.empty()) {
46 message
<< " included from " << f
->filePath
<< " line " <<
47 f
->lineNo
<< ": " << f
->currentLine
<< std::endl
;
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());
56 fatalf("Bungled %s line %d: %s",
57 cfg_filename
, config_lineno
, config_input_line
);
61 ConfigParser::TokenPutBack(const char *tok
)
70 LOCAL_ARRAY(char, undoToken
, CONFIG_LINE_LIMIT
);
72 strncpy(undoToken
, Undo_
.front().c_str(), sizeof(undoToken
));
73 undoToken
[sizeof(undoToken
) - 1] = '\0';
82 ConfigParser::strtokFile()
84 if (RecognizeQuotedValues
)
85 return ConfigParser::NextToken();
87 static int fromFile
= 0;
88 static FILE *wordFile
= NULL
;
91 LOCAL_ARRAY(char, buf
, CONFIG_LINE_LIMIT
);
93 if ((t
= ConfigParser::Undo()))
99 ConfigParser::TokenType tokenType
;
100 t
= ConfigParser::NextElement(tokenType
);
103 } else if (*t
== '\"' || *t
== '\'') {
104 /* quote found, start reading from file */
105 debugs(3, 8,"Quoted token found : " << t
);
108 while (*t
&& *t
!= '\"' && *t
!= '\'')
113 if ((wordFile
= fopen(fn
, "r")) == NULL
) {
114 debugs(3, DBG_CRITICAL
, "ERROR: Can not open file " << fn
<< " for reading");
119 setmode(fileno(wordFile
), O_TEXT
);
129 if (fgets(buf
, CONFIG_LINE_LIMIT
, wordFile
) == NULL
) {
130 /* stop reading from file */
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
);
143 while (*t3
&& *t3
!= '#') {
144 t2
= t3
+ strcspn(t3
, w_space
);
145 t3
= t2
+ strspn(t2
, w_space
);
152 /* skip blank lines */
153 } while ( *t
== '#' || !*t
);
159 ConfigParser::UnQuote(const char *token
, const char **next
)
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;
168 /* scan until the end of the quoted string, handling escape sequences*/
169 while (*s
&& *s
!= quoteChar
&& !errorStr
&& (size_t)(d
- UnQuoted
) < sizeof(UnQuoted
)) {
184 errorStr
= "Unsupported escape sequence";
191 } else if (*s
== '$' && quoteChar
== '"') {
192 errorStr
= "Unsupported cfg macro";
196 } else if (*s
== '%' && quoteChar
== '"' && (!AllowMacros_
)) {
197 errorStr
= "Macros are not supported here";
206 if (*s
!= quoteChar
&& !errorStr
) {
207 errorStr
= "missing quote char at the end of quoted string";
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";
221 strncpy(UnQuoted
, SQUID_ERROR_TOKEN
, sizeof(UnQuoted
));
223 debugs(3, DBG_CRITICAL
, "FATAL: " << errorStr
<< ": " << errorPos
);
234 ConfigParser::SetCfgLine(char *line
)
238 while (!CfgLineTokens_
.empty()) {
239 char *token
= CfgLineTokens_
.front();
240 CfgLineTokens_
.pop();
246 ConfigParser::TokenParse(const char * &nextToken
, ConfigParser::TokenType
&type
)
248 if (!nextToken
|| *nextToken
== '\0')
250 type
= ConfigParser::SimpleToken
;
251 nextToken
+= strspn(nextToken
, w_space
);
253 if (*nextToken
== '#')
256 if (ConfigParser::RecognizeQuotedValues
&& (*nextToken
== '"' || *nextToken
== '\'')) {
257 type
= ConfigParser::QuotedToken
;
258 char *token
= xstrdup(UnQuote(nextToken
, &nextToken
));
259 CfgLineTokens_
.push(token
);
263 const char *tokenStart
= nextToken
;
265 if (ConfigParser::ParseKvPair_
) {
266 if (ConfigParser::KvPairState_
== ConfigParser::atParseKey
)
270 } else if (ConfigParser::ParseQuotedOrToEol_
)
272 else if (ConfigParser::RecognizeQuotedPair_
)
274 else if (!ConfigParser::RecognizeQuotedValues
|| *nextToken
== '(')
278 nextToken
+= strcspn(nextToken
, sep
);
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
);
286 debugs(3, DBG_CRITICAL
, "FATAL: Unescaped '\' character in regex pattern: " << tokenStart
);
291 if (ConfigParser::RecognizeQuotedValues
&& *nextToken
== '(') {
292 if (strncmp(tokenStart
, "parameters", nextToken
- tokenStart
) == 0)
293 type
= ConfigParser::FunctionParameters
;
296 char *err
= xstrdup(SQUID_ERROR_TOKEN
);
297 CfgLineTokens_
.push(err
);
300 debugs(3, DBG_CRITICAL
, "FATAL: Unknown cfg function: " << tokenStart
);
305 type
= ConfigParser::SimpleToken
;
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
));
316 tokenIsNumber
= false;
320 char *err
= xstrdup(SQUID_ERROR_TOKEN
);
321 CfgLineTokens_
.push(err
);
324 debugs(3, DBG_CRITICAL
, "FATAL: Not alphanumeric character '"<< *s
<< "' in unquoted token " << tokenStart
);
330 token
= xstrndup(tokenStart
, nextToken
- tokenStart
+ 1);
331 CfgLineTokens_
.push(token
);
334 if (*nextToken
!= '\0' && *nextToken
!= '#') {
342 ConfigParser::NextElement(ConfigParser::TokenType
&type
)
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
)
352 // else next call will read the same token
357 ConfigParser::NextToken()
360 if ((token
= ConfigParser::Undo())) {
361 debugs(3, 6, "TOKEN (undone): " << token
);
366 while (token
== NULL
&& !CfgFiles
.empty()) {
367 ConfigParser::CfgFile
*wordfile
= CfgFiles
.top();
368 token
= wordfile
->parse(LastTokenType
);
370 assert(!wordfile
->isOpen());
372 debugs(3, 4, "CfgFiles.pop " << wordfile
->filePath
);
378 token
= NextElement(LastTokenType
);
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;
385 char *path
= NextToken();
386 if (LastTokenType
!= ConfigParser::QuotedToken
) {
387 debugs(3, DBG_CRITICAL
, "FATAL: Quoted filename missing: " << token
);
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
<< "\"");
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
);
407 ConfigParser::CfgFile
*wordfile
= new ConfigParser::CfgFile();
408 if (!path
|| !wordfile
->startParse(path
)) {
409 debugs(3, DBG_CRITICAL
, "FATAL: Error opening config file: " << token
);
414 CfgFiles
.push(wordfile
);
417 } while (token
== NULL
&& !CfgFiles
.empty());
423 ConfigParser::PeekAtToken()
426 char *token
= NextToken();
427 PreviewMode_
= false;
432 ConfigParser::NextQuotedOrToEol()
434 ParseQuotedOrToEol_
= true;
435 char *token
= NextToken();
436 ParseQuotedOrToEol_
= false;
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();
450 ConfigParser::NextKvPair(char * &key
, char * &value
)
454 KvPairState_
= ConfigParser::atParseKey
;
455 if ((key
= NextToken()) != NULL
) {
456 KvPairState_
= ConfigParser::atParseValue
;
457 value
= NextQuotedToken();
459 ParseKvPair_
= false;
464 debugs(3, DBG_CRITICAL
, "Error while parsing key=value token. Value missing after: " << key
);
472 ConfigParser::RegexStrtokFile()
474 if (ConfigParser::RecognizeQuotedValues
) {
475 debugs(3, DBG_CRITICAL
, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
478 ConfigParser::RecognizeQuotedPair_
= true;
479 char * token
= strtokFile();
480 ConfigParser::RecognizeQuotedPair_
= false;
485 ConfigParser::RegexPattern()
487 if (ConfigParser::RecognizeQuotedValues
) {
488 debugs(3, DBG_CRITICAL
, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
491 ConfigParser::RecognizeQuotedPair_
= true;
492 char * token
= NextToken();
493 ConfigParser::RecognizeQuotedPair_
= false;
498 ConfigParser::NextQuotedToken()
500 const bool saveRecognizeQuotedValues
= ConfigParser::RecognizeQuotedValues
;
501 ConfigParser::RecognizeQuotedValues
= true;
502 char *token
= NextToken();
503 ConfigParser::RecognizeQuotedValues
= saveRecognizeQuotedValues
;
508 ConfigParser::QuoteString(const String
&var
)
510 static String quotedStr
;
511 const char *s
= var
.termedBuf();
512 bool needQuote
= false;
514 for (const char *l
= s
; !needQuote
&& *l
!= '\0'; ++l
)
515 needQuote
= !isalnum(*l
);
521 quotedStr
.append('"');
522 for (; *s
!= '\0'; ++s
) {
523 if (*s
== '"' || *s
== '\\')
524 quotedStr
.append('\\');
525 quotedStr
.append(*s
);
527 quotedStr
.append('"');
528 return quotedStr
.termedBuf();
532 ConfigParser::CfgFile::startParse(char *path
)
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");
542 setmode(fileno(wordFile
), O_TEXT
);
546 return getFileLine();
550 ConfigParser::CfgFile::getFileLine()
552 // Else get the next line
553 if (fgets(parseBuffer
, CONFIG_LINE_LIMIT
, wordFile
) == NULL
) {
554 /* stop reading from file */
557 parseBuffer
[0] = '\0';
560 parsePos
= parseBuffer
;
561 currentLine
= parseBuffer
;
567 ConfigParser::CfgFile::parse(ConfigParser::TokenType
&type
)
576 while (!(token
= nextElement(type
))) {
584 ConfigParser::CfgFile::nextElement(ConfigParser::TokenType
&type
)
586 const char *pos
= parsePos
;
587 char *token
= TokenParse(pos
, type
);
588 if (!PreviewMode_
|| type
== FunctionParameters
)
590 // else next call will read the same token;
594 ConfigParser::CfgFile::~CfgFile()