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";
217 } else if (*s
== '%' && quoteChar
== '"' && (!AllowMacros_
)) {
218 errorStr
= "Macros are not supported here";
226 if (*s
!= quoteChar
&& !errorStr
) {
227 errorStr
= "missing quote char at the end of quoted string";
233 // We are expecting a separator after quoted string, space or one of "()#"
234 if (*(s
+ 1) != '\0' && !strchr(w_space
"()#", *(s
+ 1)) && !errorStr
) {
235 errorStr
= "Expecting space after the end of quoted token";
241 strncpy(UnQuoted
, SQUID_ERROR_TOKEN
, sizeof(UnQuoted
));
243 debugs(3, DBG_CRITICAL
, errorStr
<< ": " << errorPos
);
254 ConfigParser::SetCfgLine(char *line
)
258 while (!CfgLineTokens_
.empty()) {
259 char *token
= CfgLineTokens_
.front();
260 CfgLineTokens_
.pop();
266 ConfigParser::TokenParse(const char * &nextToken
, ConfigParser::TokenType
&type
)
268 if (!nextToken
|| *nextToken
== '\0')
270 type
= ConfigParser::SimpleToken
;
271 nextToken
+= strspn(nextToken
, w_space
);
273 if (*nextToken
== '#')
276 if (ConfigParser::RecognizeQuotedValues
&& (*nextToken
== '"' || *nextToken
== '\'')) {
277 type
= ConfigParser::QuotedToken
;
278 char *token
= xstrdup(UnQuote(nextToken
, &nextToken
));
279 CfgLineTokens_
.push(token
);
283 const char *tokenStart
= nextToken
;
285 if (ConfigParser::ParseQuotedOrToEol_
)
287 else if (!ConfigParser::RecognizeQuotedValues
|| *nextToken
== '(')
291 nextToken
+= strcspn(nextToken
, sep
);
293 if (ConfigParser::RecognizeQuotedValues
&& *nextToken
== '(') {
294 if (strncmp(tokenStart
, "parameters", nextToken
- tokenStart
) == 0)
295 type
= ConfigParser::FunctionParameters
;
298 char *err
= xstrdup(SQUID_ERROR_TOKEN
);
299 CfgLineTokens_
.push(err
);
302 debugs(3, DBG_CRITICAL
, "Unknown cfg function: " << tokenStart
);
307 type
= ConfigParser::SimpleToken
;
310 if (nextToken
- tokenStart
) {
311 if (ConfigParser::StrictMode
&& type
== ConfigParser::SimpleToken
) {
312 bool tokenIsNumber
= true;
313 for (const char *s
= tokenStart
; s
!= nextToken
; ++s
) {
314 const bool isValidChar
= isalnum(*s
) || strchr(".,()-=_/:", *s
) ||
315 (tokenIsNumber
&& *s
== '%' && (s
+ 1 == nextToken
));
318 tokenIsNumber
= false;
322 char *err
= xstrdup(SQUID_ERROR_TOKEN
);
323 CfgLineTokens_
.push(err
);
326 debugs(3, DBG_CRITICAL
, "Not alphanumeric character '"<< *s
<< "' in unquoted token " << tokenStart
);
332 token
= xstrndup(tokenStart
, nextToken
- tokenStart
+ 1);
333 CfgLineTokens_
.push(token
);
336 if (*nextToken
!= '\0' && *nextToken
!= '#') {
344 ConfigParser::NextElement(ConfigParser::TokenType
&type
)
346 const char *pos
= CfgPos
;
347 char *token
= TokenParse(pos
, type
);
348 // If not in preview mode the next call of this method should start
349 // parsing after the end of current token.
350 // For function "parameters(...)" we need always to update current parsing
351 // position to allow parser read the arguments of "parameters(..)"
352 if (!PreviewMode_
|| type
== FunctionParameters
)
354 // else next call will read the same token
359 ConfigParser::NextToken()
362 if ((token
= ConfigParser::Undo())) {
363 debugs(3, 6, "TOKEN (undone): " << token
);
368 while (token
== NULL
&& !CfgFiles
.empty()) {
369 ConfigParser::CfgFile
*wordfile
= CfgFiles
.top();
370 token
= wordfile
->parse(LastTokenType
);
372 assert(!wordfile
->isOpen());
374 debugs(3, 4, "CfgFiles.pop " << wordfile
->filePath
);
380 token
= NextElement(LastTokenType
);
382 if (token
&& LastTokenType
== ConfigParser::FunctionParameters
) {
383 //Disable temporary preview mode, we need to parse function parameters
384 const bool savePreview
= ConfigParser::PreviewMode_
;
385 ConfigParser::PreviewMode_
= false;
387 char *path
= NextToken();
388 if (LastTokenType
!= ConfigParser::QuotedToken
) {
389 debugs(3, DBG_CRITICAL
, "Quoted filename missing: " << token
);
394 // The next token in current cfg file line must be a ")"
395 char *end
= NextToken();
396 ConfigParser::PreviewMode_
= savePreview
;
397 if (LastTokenType
!= ConfigParser::SimpleToken
|| strcmp(end
, ")") != 0) {
398 debugs(3, DBG_CRITICAL
, "missing ')' after " << token
<< "(\"" << path
<< "\"");
403 if (CfgFiles
.size() > 16) {
404 debugs(3, DBG_CRITICAL
, "WARNING: can't open %s for reading parameters: includes are nested too deeply (>16)!\n" << path
);
409 ConfigParser::CfgFile
*wordfile
= new ConfigParser::CfgFile();
410 if (!path
|| !wordfile
->startParse(path
)) {
411 debugs(3, DBG_CRITICAL
, "Error opening config file: " << token
);
416 CfgFiles
.push(wordfile
);
419 } while (token
== NULL
&& !CfgFiles
.empty());
425 ConfigParser::PeekAtToken()
428 char *token
= NextToken();
429 PreviewMode_
= false;
434 ConfigParser::NextQuotedOrToEol()
436 ParseQuotedOrToEol_
= true;
437 char *token
= NextToken();
438 ParseQuotedOrToEol_
= false;
440 // Assume end of current config line
441 // Close all open configuration files for this config line
442 while (!CfgFiles
.empty()) {
443 ConfigParser::CfgFile
*wordfile
= CfgFiles
.top();
452 ConfigParser::RegexStrtokFile()
454 if (ConfigParser::RecognizeQuotedValues
) {
455 debugs(3, DBG_CRITICAL
, "Can not read regex expresion while configuration_includes_quoted_values is enabled");
458 char * token
= strtokFile();
463 ConfigParser::RegexPattern()
465 if (ConfigParser::RecognizeQuotedValues
) {
466 debugs(3, DBG_CRITICAL
, "Can not read regex expresion while configuration_includes_quoted_values is enabled");
470 char * token
= NextToken();
475 ConfigParser::NextQuotedToken()
477 const bool saveRecognizeQuotedValues
= ConfigParser::RecognizeQuotedValues
;
478 ConfigParser::RecognizeQuotedValues
= true;
479 char *token
= NextToken();
480 ConfigParser::RecognizeQuotedValues
= saveRecognizeQuotedValues
;
485 ConfigParser::QuoteString(const String
&var
)
487 static String quotedStr
;
488 const char *s
= var
.termedBuf();
489 bool needQuote
= false;
491 for (const char *l
= s
; !needQuote
&& *l
!= '\0'; ++l
)
492 needQuote
= !isalnum(*l
);
498 quotedStr
.append('"');
499 for (; *s
!= '\0'; ++s
) {
500 if (*s
== '"' || *s
== '\\')
501 quotedStr
.append('\\');
502 quotedStr
.append(*s
);
504 quotedStr
.append('"');
505 return quotedStr
.termedBuf();
509 ConfigParser::CfgFile::startParse(char *path
)
511 assert(wordFile
== NULL
);
512 debugs(3, 3, "Parsing from " << path
);
513 if ((wordFile
= fopen(path
, "r")) == NULL
) {
514 debugs(3, DBG_CRITICAL
, "file :" << path
<< " not found");
519 setmode(fileno(wordFile
), O_TEXT
);
523 return getFileLine();
527 ConfigParser::CfgFile::getFileLine()
529 // Else get the next line
530 if (fgets(parseBuffer
, CONFIG_LINE_LIMIT
, wordFile
) == NULL
) {
531 /* stop reading from file */
534 parseBuffer
[0] = '\0';
537 parsePos
= parseBuffer
;
538 currentLine
= parseBuffer
;
544 ConfigParser::CfgFile::parse(ConfigParser::TokenType
&type
)
553 while (!(token
= nextElement(type
))) {
561 ConfigParser::CfgFile::nextElement(ConfigParser::TokenType
&type
)
563 const char *pos
= parsePos
;
564 char *token
= TokenParse(pos
, type
);
565 if (!PreviewMode_
|| type
== FunctionParameters
)
567 // else next call will read the same token;
571 ConfigParser::CfgFile::~CfgFile()