#include "fatal.h"
#include "globals.h"
-int ConfigParser::RecognizeQuotedValues = true;
+bool ConfigParser::RecognizeQuotedValues = true;
+bool ConfigParser::StrictMode = true;
std::stack<ConfigParser::CfgFile *> ConfigParser::CfgFiles;
ConfigParser::TokenType ConfigParser::LastTokenType = ConfigParser::SimpleToken;
-char *ConfigParser::LastToken = NULL;
-char *ConfigParser::CfgLine = NULL;
-char *ConfigParser::CfgPos = NULL;
+const char *ConfigParser::CfgLine = NULL;
+const char *ConfigParser::CfgPos = NULL;
+std::queue<char *> ConfigParser::CfgLineTokens_;
std::queue<std::string> ConfigParser::Undo_;
bool ConfigParser::AllowMacros_ = false;
+bool ConfigParser::ParseQuotedOrToEol_ = false;
+bool ConfigParser::PreviewMode_ = false;
+
+static const char *SQUID_ERROR_TOKEN = "[invalid token]";
void
ConfigParser::destruct()
cfg_filename, config_lineno, config_input_line);
}
-void
-ConfigParser::TokenUndo()
-{
- assert(LastToken);
- Undo_.push(LastToken);
-}
-
void
ConfigParser::TokenPutBack(const char *tok)
{
if (!Undo_.empty()) {
strncpy(undoToken, Undo_.front().c_str(), sizeof(undoToken));
undoToken[sizeof(undoToken) - 1] = '\0';
- Undo_.pop();
+ if (!PreviewMode_)
+ Undo_.pop();
return undoToken;
}
return NULL;
char *t;
LOCAL_ARRAY(char, buf, CONFIG_LINE_LIMIT);
- if ((LastToken = ConfigParser::Undo()))
- return LastToken;
+ if ((t = ConfigParser::Undo()))
+ return t;
do {
if (!fromFile) {
ConfigParser::TokenType tokenType;
- t = ConfigParser::NextElement(tokenType, true);
+ t = ConfigParser::NextElement(tokenType);
if (!t) {
return NULL;
- } else if (tokenType == ConfigParser::QuotedToken) {
+ } else if (*t == '\"' || *t == '\'') {
/* quote found, start reading from file */
debugs(3, 8,"Quoted token found : " << t);
+ char *fn = ++t;
- if ((wordFile = fopen(t, "r")) == NULL) {
+ while (*t && *t != '\"' && *t != '\'')
+ ++t;
+
+ *t = '\0';
+
+ if ((wordFile = fopen(fn, "r")) == NULL) {
debugs(3, DBG_CRITICAL, "Can not open file " << t << " for reading");
return NULL;
}
fromFile = 1;
} else {
- return LastToken = t;
+ return t;
}
}
/* skip blank lines */
} while ( *t == '#' || !*t );
- return LastToken = t;
+ return t;
}
char *
-ConfigParser::UnQuote(char *token, char **end)
+ConfigParser::UnQuote(const char *token, const char **next)
{
+ const char *errorStr = NULL;
+ const char *errorPos = NULL;
char quoteChar = *token;
assert(quoteChar == '"' || quoteChar == '\'');
- char *s = token + 1;
- /* scan until the end of the quoted string, unescaping " and \ */
- while (*s && *s != quoteChar) {
- if (*s == '\\' && isalnum(*( s + 1))) {
- debugs(3, DBG_CRITICAL, "Unsupported escape sequence: " << s);
- self_destruct();
+ LOCAL_ARRAY(char, UnQuoted, CONFIG_LINE_LIMIT);
+ const char *s = token + 1;
+ char *d = UnQuoted;
+ /* scan until the end of the quoted string, handling escape sequences*/
+ while (*s && *s != quoteChar && !errorStr && (d - UnQuoted) < sizeof(UnQuoted)) {
+ if (*s == '\\') {
+ s++;
+ switch (*s) {
+ case 'r':
+ *d = '\r';
+ break;
+ case 'n':
+ *d = '\n';
+ break;
+ case 't':
+ *d = '\t';
+ break;
+ default:
+ if (isalnum(*s)) {
+ errorStr = "Unsupported escape sequence";
+ errorPos = s;
+ }
+ *d = *s;
+ break;
+ }
+#if 0
} else if (*s == '$' && quoteChar == '"') {
- debugs(3, DBG_CRITICAL, "Unsupported cfg macro: " << s);
- self_destruct();
+ errorStr = "Unsupported cfg macro";
+ errorPos = s;
+#endif
} else if (*s == '%' && quoteChar == '"' && (!AllowMacros_ )) {
- debugs(3, DBG_CRITICAL, "Macros are not supported here: " << s);
- self_destruct();
- } else if (*s == '\\') {
- const char * next = s+1; // may point to 0
- memmove(s, next, strlen(next) + 1);
- }
+ errorStr = "Macros are not supported here";
+ errorPos = s;
+ } else
+ *d = *s;
++s;
+ ++d;
}
- if (*s != quoteChar) {
- debugs(3, DBG_CRITICAL, "missing '" << quoteChar << "' at the end of quoted string: " << (s-1));
- self_destruct();
+ if (*s != quoteChar && !errorStr) {
+ errorStr = "missing quote char at the end of quoted string";
+ errorPos = s - 1;
}
- *end = s;
- return (token+1);
+ // The end of token
+ *d = '\0';
+
+ // We are expecting a separator after quoted string, space or one of "()#"
+ if (*(s + 1) != '\0' && !strchr(w_space "()#", *(s + 1)) && !errorStr) {
+ errorStr = "Expecting space after the end of quoted token";
+ errorPos = token;
+ }
+
+ if (errorStr) {
+ if (PreviewMode_)
+ strncpy(UnQuoted, SQUID_ERROR_TOKEN, sizeof(UnQuoted));
+ else {
+ debugs(3, DBG_CRITICAL, errorStr << ": " << errorPos);
+ self_destruct();
+ }
+ }
+
+ if (next)
+ *next = s + 1;
+ return UnQuoted;
}
void
{
CfgLine = line;
CfgPos = line;
+ while (!CfgLineTokens_.empty()) {
+ char *token = CfgLineTokens_.front();
+ CfgLineTokens_.pop();
+ free(token);
+ }
}
char *
-ConfigParser::TokenParse(char * &nextToken, ConfigParser::TokenType &type, bool legacy)
+ConfigParser::TokenParse(const char * &nextToken, ConfigParser::TokenType &type)
{
if (!nextToken || *nextToken == '\0')
return NULL;
type = ConfigParser::SimpleToken;
nextToken += strspn(nextToken, w_space);
- if (*nextToken == '"' || *nextToken == '\'') {
+
+ if (*nextToken == '#')
+ return NULL;
+
+ if (ConfigParser::RecognizeQuotedValues && (*nextToken == '"' || *nextToken == '\'')) {
type = ConfigParser::QuotedToken;
- char *token = UnQuote(nextToken, &nextToken);
- *nextToken = '\0';
- ++nextToken;
+ char *token = xstrdup(UnQuote(nextToken, &nextToken));
+ CfgLineTokens_.push(token);
return token;
}
- char *token = nextToken;
- if (char *t = strchr(nextToken, '#'))
- *t = '\0';
+ const char *tokenStart = nextToken;
const char *sep;
- if (legacy)
+ if (ConfigParser::ParseQuotedOrToEol_)
+ sep = "\n";
+ else if (!ConfigParser::RecognizeQuotedValues || *nextToken == '(')
sep = w_space;
else
sep = w_space "(";
nextToken += strcspn(nextToken, sep);
- if (!legacy && *nextToken == '(')
- type = ConfigParser::FunctionNameToken;
- else
+ if (ConfigParser::RecognizeQuotedValues && *nextToken == '(') {
+ if (strncmp(tokenStart, "parameters", nextToken - tokenStart) == 0)
+ type = ConfigParser::FunctionParameters;
+ else {
+ if (PreviewMode_) {
+ char *err = xstrdup(SQUID_ERROR_TOKEN);
+ CfgLineTokens_.push(err);
+ return err;
+ } else {
+ debugs(3, DBG_CRITICAL, "Unknown cfg function: " << tokenStart);
+ self_destruct();
+ }
+ }
+ } else
type = ConfigParser::SimpleToken;
- if (*nextToken != '\0') {
- *nextToken = '\0';
- ++nextToken;
+ char *token = NULL;
+ if (nextToken - tokenStart) {
+ if (ConfigParser::StrictMode && type == ConfigParser::SimpleToken) {
+ bool tokenIsNumber = true;
+ for (const char *s = tokenStart; s != nextToken; ++s) {
+ const bool isValidChar = isalnum(*s) || strchr(".,()-=_/:", *s) ||
+ (tokenIsNumber && *s == '%' && (s + 1 == nextToken));
+
+ if (!isdigit(*s))
+ tokenIsNumber = false;
+
+ if (!isValidChar) {
+ if (PreviewMode_) {
+ char *err = xstrdup(SQUID_ERROR_TOKEN);
+ CfgLineTokens_.push(err);
+ return err;
+ } else {
+ debugs(3, DBG_CRITICAL, "Not alphanumeric character '"<< *s << "' in unquoted token " << tokenStart);
+ self_destruct();
+ }
+ }
+ }
+ }
+ token = xstrndup(tokenStart, nextToken - tokenStart + 1);
+ CfgLineTokens_.push(token);
}
- if (*token == '\0')
- return NULL;
+ if (*nextToken != '\0' && *nextToken != '#') {
+ ++nextToken;
+ }
return token;
}
char *
-ConfigParser::NextElement(ConfigParser::TokenType &type, bool legacy)
+ConfigParser::NextElement(ConfigParser::TokenType &type)
{
- char *token = TokenParse(CfgPos, type, legacy);
+ const char *pos = CfgPos;
+ char *token = TokenParse(pos, type);
+ // If not in preview mode the next call of this method should start
+ // parsing after the end of current token.
+ // For function "parameters(...)" we need always to update current parsing
+ // position to allow parser read the arguments of "parameters(..)"
+ if (!PreviewMode_ || type == FunctionParameters)
+ CfgPos = pos;
+ // else next call will read the same token
return token;
}
char *
ConfigParser::NextToken()
{
- if ((LastToken = ConfigParser::Undo()))
- return LastToken;
-
char *token = NULL;
+ if ((token = ConfigParser::Undo())) {
+ debugs(3, 6, "TOKEN (undone): " << token);
+ return token;
+ }
+
do {
while (token == NULL && !CfgFiles.empty()) {
ConfigParser::CfgFile *wordfile = CfgFiles.top();
if (!token) {
assert(!wordfile->isOpen());
CfgFiles.pop();
+ debugs(3, 4, "CfgFiles.pop " << wordfile->filePath);
delete wordfile;
}
}
if (!token)
token = NextElement(LastTokenType);
- if (token && LastTokenType == ConfigParser::FunctionNameToken && strcmp("parameters", token) == 0) {
+ if (token && LastTokenType == ConfigParser::FunctionParameters) {
+ //Disable temporary preview mode, we need to parse function parameters
+ const bool savePreview = ConfigParser::PreviewMode_;
+ ConfigParser::PreviewMode_ = false;
+
char *path = NextToken();
if (LastTokenType != ConfigParser::QuotedToken) {
debugs(3, DBG_CRITICAL, "Quoted filename missing: " << token);
// The next token in current cfg file line must be a ")"
char *end = NextToken();
+ ConfigParser::PreviewMode_ = savePreview;
if (LastTokenType != ConfigParser::SimpleToken || strcmp(end, ")") != 0) {
debugs(3, DBG_CRITICAL, "missing ')' after " << token << "(\"" << path << "\"");
self_destruct();
}
CfgFiles.push(wordfile);
token = NULL;
- } else if (token && LastTokenType == ConfigParser::FunctionNameToken) {
- debugs(3, DBG_CRITICAL, "Unknown cfg function: " << token);
- self_destruct();
- return NULL;
}
} while (token == NULL && !CfgFiles.empty());
- return (LastToken = token);
+ return token;
+}
+
+char *
+ConfigParser::PeekAtToken()
+{
+ PreviewMode_ = true;
+ char *token = NextToken();
+ PreviewMode_ = false;
+ return token;
}
char *
ConfigParser::NextQuotedOrToEol()
{
- char *token;
+ ParseQuotedOrToEol_ = true;
+ char *token = NextToken();
+ ParseQuotedOrToEol_ = false;
+
+ // Assume end of current config line
+ // Close all open configuration files for this config line
+ while (!CfgFiles.empty()) {
+ ConfigParser::CfgFile *wordfile = CfgFiles.top();
+ CfgFiles.pop();
+ delete wordfile;
+ }
- if ((token = CfgPos) == NULL) {
- debugs(3, DBG_CRITICAL, "token is missing");
+ return token;
+}
+
+char *
+ConfigParser::RegexStrtokFile()
+{
+ if (ConfigParser::RecognizeQuotedValues) {
+ debugs(3, DBG_CRITICAL, "Can not read regex expresion while configuration_includes_quoted_values is enabled");
self_destruct();
- return NULL;
}
- token += strspn(token, w_space);
-
- if (*token == '\"' || *token == '\'') {
- //TODO: eat the spaces at the end and check if it is untill the end of file.
- char *end;
- token = UnQuote(token, &end);
- *end = '\0';
- CfgPos = end + 1;
- LastTokenType = ConfigParser::QuotedToken;
- } else
- LastTokenType = ConfigParser::SimpleToken;
+ char * token = strtokFile();
+ return token;
+}
- CfgPos = NULL;
- return (LastToken = token);
+char *
+ConfigParser::RegexPattern()
+{
+ if (ConfigParser::RecognizeQuotedValues) {
+ debugs(3, DBG_CRITICAL, "Can not read regex expresion while configuration_includes_quoted_values is enabled");
+ self_destruct();
+ }
+
+ char * token = NextToken();
+ return token;
+}
+
+char *
+ConfigParser::NextQuotedToken()
+{
+ const bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues;
+ ConfigParser::RecognizeQuotedValues = true;
+ char *token = NextToken();
+ ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues;
+ return token;
}
const char *
ConfigParser::CfgFile::startParse(char *path)
{
assert(wordFile == NULL);
+ debugs(3, 3, "Parsing from " << path);
if ((wordFile = fopen(path, "r")) == NULL) {
debugs(3, DBG_CRITICAL, "file :" << path << " not found");
return false;
char *
ConfigParser::CfgFile::nextElement(ConfigParser::TokenType &type)
{
- return TokenParse(parsePos, type);
+ const char *pos = parsePos;
+ char *token = TokenParse(pos, type);
+ if (!PreviewMode_ || type == FunctionParameters)
+ parsePos = pos;
+ // else next call will read the same token;
+ return token;
}
ConfigParser::CfgFile::~CfgFile()
* Parsed tokens type: simple tokens, quoted tokens or function
* like parameters.
*/
- enum TokenType {SimpleToken, QuotedToken, FunctionNameToken};
+ enum TokenType {SimpleToken, QuotedToken, FunctionParameters};
void destruct();
static void ParseUShort(unsigned short *var);
*/
static char *NextToken();
+ /**
+ * Backward compatibility wrapper for ConfigParser::RegexPattern method.
+ * If the configuration_includes_quoted_values configuration parameter is
+ * set to 'off' this interprets the quoted tokens as filenames.
+ */
+ static char *RegexStrtokFile();
+
+ /**
+ * Parse the next token as a regex patern. The regex patterns are non quoted
+ * tokens.
+ */
+ static char *RegexPattern();
+
+ /**
+ * Parse the next token with support for quoted values enabled even if
+ * the configuration_includes_quoted_values is set to off
+ */
+ static char *NextQuotedToken();
+
/// \return true if the last parsed token was quoted
static bool LastTokenWasQuoted() {return (LastTokenType == ConfigParser::QuotedToken);}
static char *NextQuotedOrToEol();
/**
- * Undo last NextToken call. The next call to NextToken() method will return
- * again the last parsed element.
- * Can not be called repeatedly to undo multiple NextToken calls. In this case
- * the behaviour is undefined.
+ * Preview the next token. The next NextToken() and strtokFile() call
+ * will return the same token.
+ * On parse error (eg invalid characters in token) will return an
+ * error message as token.
*/
- static void TokenUndo();
+ static char *PeekAtToken();
/**
* The next NextToken call will return the token as next element
static void DisableMacros() {AllowMacros_ = false;}
/// configuration_includes_quoted_values in squid.conf
- static int RecognizeQuotedValues;
+ static bool RecognizeQuotedValues;
+
+ /**
+ * Strict syntax mode. Does not allow not alphanumeric characters in unquoted tokens.
+ * Controled by the configuration_includes_quoted_values in squid.conf but remains
+ * false when the the legacy ConfigParser::NextQuotedToken() call forces
+ * RecognizeQuotedValues to be temporary true.
+ */
+ static bool StrictMode;
protected:
/**
char *nextElement(TokenType &type);
FILE *wordFile; ///< Pointer to the file.
char parseBuffer[CONFIG_LINE_LIMIT]; ///< Temporary buffer to store data to parse
- char *parsePos; ///< The next element position in parseBuffer string
+ const char *parsePos; ///< The next element position in parseBuffer string
public:
std::string filePath; ///< The file path
std::string currentLine; ///< The current line to parse
int lineNo; ///< Current line number
};
- /**
- * Return the last TokenUndo() or TokenPutBack() queued element, or NULL
- * if none exist
- */
+ /// Return the last TokenPutBack() queued element or NULL if none exist
static char *Undo();
/**
* Unquotes the token, which must be quoted.
- * \param end if it is not NULL, it is set to the end of token.
+ * \param next if it is not NULL, it is set after the end of token.
*/
- static char *UnQuote(char *token, char **end = NULL);
+ static char *UnQuote(const char *token, const char **next = NULL);
/**
* Does the real tokens parsing job: Ignore comments, unquote an
* \return the next token, or NULL if there are no available tokens in the nextToken string.
* \param nextToken updated to point to the pos after parsed token.
* \param type The token type
- * \param legacy If it is true function-like parameters are not allowed
*/
- static char *TokenParse(char * &nextToken, TokenType &type, bool legacy = false);
+ static char *TokenParse(const char * &nextToken, TokenType &type);
/// Wrapper method for TokenParse.
- static char *NextElement(TokenType &type, bool legacy = false);
+ static char *NextElement(TokenType &type);
static std::stack<CfgFile *> CfgFiles; ///< The stack of open cfg files
static TokenType LastTokenType; ///< The type of last parsed element
- static char *LastToken; ///< Points to the last parsed token
- static char *CfgLine; ///< The current line to parse
- static char *CfgPos; ///< Pointer to the next element in cfgLine string
- static std::queue<std::string> Undo_; ///< The list with TokenUndo() or TokenPutBack() queued elements
+ static const char *CfgLine; ///< The current line to parse
+ static const char *CfgPos; ///< Pointer to the next element in cfgLine string
+ static std::queue<char *> CfgLineTokens_; ///< Store the list of tokens for current configuration line
+ static std::queue<std::string> Undo_; ///< The list with TokenPutBack() queued elements
static bool AllowMacros_;
+ static bool ParseQuotedOrToEol_; ///< The next tokens will be handled as quoted or to_eol token
+ static bool PreviewMode_; ///< The next token will not poped from cfg files, will just previewd.
};
int parseConfigFile(const char *file_name);
static int parseOneConfigFile(const char *file_name, unsigned int depth);
+static void parse_configuration_includes_quoted_values(bool *recognizeQuotedValues);
+static void dump_configuration_includes_quoted_values(StoreEntry *const entry, const char *const name, bool recognizeQuotedValues);
+static void free_configuration_includes_quoted_values(bool *recognizeQuotedValues);
+
/*
* LegacyParser is a parser for legacy code that uses the global
* approach. This is static so that it is only exposed to cache_cf.
return;
}
- const char *value = t + strlen(t) + 1;
+ const char *value = ConfigParser::NextQuotedOrToEol();
if (!*pm)
*pm = new HeaderManglers;
void
parse_pipelinePrefetch(int *var)
{
- char *token = ConfigParser::strtokFile();
+ char *token = ConfigParser::PeekAtToken();
if (token == NULL)
self_destruct();
if (!strcmp(token, "on")) {
debugs(0, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: 'pipeline_prefetch on' is deprecated. Please update to use 1 (or a higher number).");
*var = 1;
+ //pop the token
+ (void)ConfigParser::NextToken();
} else if (!strcmp(token, "off")) {
debugs(0, DBG_PARSE_NOTE(2), "WARNING: 'pipeline_prefetch off' is deprecated. Please update to use '0'.");
*var = 0;
- } else {
- ConfigParser::TokenUndo();
+ //pop the token
+ (void)ConfigParser::NextToken();
+ } else
parse_int(var);
- }
}
#define free_pipelinePrefetch free_int
int errcode;
int flags = REG_EXTENDED | REG_NOSUB;
- if ((token = ConfigParser::NextToken()) != NULL) {
+ if ((token = ConfigParser::RegexPattern()) != NULL) {
if (strcmp(token, "-i") == 0) {
flags |= REG_ICASE;
- token = ConfigParser::NextToken();
+ token = ConfigParser::RegexPattern();
} else if (strcmp(token, "+i") == 0) {
flags &= ~REG_ICASE;
- token = ConfigParser::NextToken();
+ token = ConfigParser::RegexPattern();
}
}
parse_wordlist(wordlist ** list)
{
char *token;
- while ((token = ConfigParser::NextToken()))
+ while ((token = ConfigParser::NextQuotedToken()))
wordlistAdd(list, token);
}
cl->filename = xstrdup(filename);
cl->type = Log::Format::CLF_UNKNOWN;
- const char *token = ConfigParser::strtokFile();
+ const char *token = ConfigParser::PeekAtToken();
if (!token) { // style #1
// no options to deal with
} else if (!strchr(token, '=')) { // style #3
- // if logformat name is not recognized,
- // put back the token; it must be an ACL name
- if (!setLogformat(cl, token, false))
- ConfigParser::TokenUndo();
+ // if logformat name is recognized,
+ // pop the previewed token; Else it must be an ACL name
+ if (setLogformat(cl, token, false))
+ (void)ConfigParser::NextToken();
} else { // style #4
do {
if (strncasecmp(token, "on-error=", 9) == 0) {
} else if (strncasecmp(token, "logformat=", 10) == 0) {
setLogformat(cl, token+10, true);
} else if (!strchr(token, '=')) {
- // put back the token; it must be an ACL name
- ConfigParser::TokenUndo();
+ // Do not pop the token; it must be an ACL name
break; // done with name=value options, now to ACLs
} else {
debugs(3, DBG_CRITICAL, "Unknown access_log option " << token);
self_destruct();
}
- } while ((token = ConfigParser::strtokFile()) != NULL);
+ // Pop the token, it was a valid "name=value" option
+ (void)ConfigParser::NextToken();
+ // Get next with preview ConfigParser::NextToken call.
+ } while ((token = ConfigParser::PeekAtToken()) != NULL);
}
// set format if it has not been specified explicitly
Format::Format *nlf = new ::Format::Format("hdrWithAcl");
ConfigParser::EnableMacros();
- String buf = ConfigParser::NextToken();
+ String buf = ConfigParser::NextQuotedToken();
ConfigParser::DisableMacros();
hwa.fieldValue = buf.termedBuf();
hwa.quoted = ConfigParser::LastTokenWasQuoted();
{
notes->clean();
}
+
+static void
+parse_configuration_includes_quoted_values(bool *recognizeQuotedValues)
+{
+ int val = 0;
+ parse_onoff(&val);
+
+ // If quoted values is set to on then enable new strict mode parsing
+ if (val) {
+ ConfigParser::RecognizeQuotedValues = true;
+ ConfigParser::StrictMode = true;
+ } else {
+ ConfigParser::RecognizeQuotedValues = false;
+ ConfigParser::StrictMode = false;
+ }
+}
+
+static void
+dump_configuration_includes_quoted_values(StoreEntry *const entry, const char *const name, bool recognizeQuotedValues)
+{
+ int val = ConfigParser::RecognizeQuotedValues ? 1 : 0;
+ dump_onoff(entry, name, val);
+}
+
+static void
+free_configuration_includes_quoted_values(bool *recognizeQuotedValues)
+{
+ ConfigParser::RecognizeQuotedValues = false;
+ ConfigParser::StrictMode = false;
+}