]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/ConfigParser.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / ConfigParser.cc
index 3d3dabf9b29c9f875c439c8cc4983f8cf25f7ef2..43c2314b7933029192eca65a0bbe742ecc1810f2 100644 (file)
-
 /*
- * $Id$
- *
- *
- * SQUID Web Proxy Cache          http://www.squid-cache.org/
- * ----------------------------------------------------------
- *
- *  Squid is the result of efforts by numerous individuals from
- *  the Internet community; see the CONTRIBUTORS file for full
- *  details.   Many organizations have provided support for Squid's
- *  development; see the SPONSORS file for full details.  Squid is
- *  Copyrighted (C) 2001 by the Regents of the University of
- *  California; see the COPYRIGHT file for full details.  Squid
- *  incorporates software developed and/or copyrighted by other
- *  sources; see the CREDITS file for full details.
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
+ * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
  *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
- *
- *
- * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #include "squid.h"
+#include "cache_cf.h"
 #include "ConfigParser.h"
+#include "Debug.h"
+#include "fatal.h"
 #include "globals.h"
 
+bool ConfigParser::RecognizeQuotedValues = true;
+bool ConfigParser::StrictMode = true;
+std::stack<ConfigParser::CfgFile *> ConfigParser::CfgFiles;
+ConfigParser::TokenType ConfigParser::LastTokenType = ConfigParser::SimpleToken;
+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::ParseKvPair_ = false;
+ConfigParser::ParsingStates ConfigParser::KvPairState_ = ConfigParser::atParseKey;
+bool ConfigParser::RecognizeQuotedPair_ = false;
+bool ConfigParser::PreviewMode_ = false;
+
+static const char *SQUID_ERROR_TOKEN = "[invalid token]";
+
 void
 ConfigParser::destruct()
 {
     shutting_down = 1;
-    fatalf("Bungled %s line %d: %s",
-           cfg_filename, config_lineno, config_input_line);
+    if (!CfgFiles.empty()) {
+        std::ostringstream message;
+        CfgFile *f = CfgFiles.top();
+        message << "Bungled " << f->filePath << " line " << f->lineNo <<
+                ": " << f->currentLine << std::endl;
+        CfgFiles.pop();
+        delete f;
+        while (!CfgFiles.empty()) {
+            f = CfgFiles.top();
+            message << " included from " << f->filePath << " line " <<
+                    f->lineNo << ": " << f->currentLine << std::endl;
+            CfgFiles.pop();
+            delete f;
+        }
+        message << " included from " <<  cfg_filename << " line " <<
+                config_lineno << ": " << config_input_line << std::endl;
+        std::string msg = message.str();
+        fatalf("%s", msg.c_str());
+    } else
+        fatalf("Bungled %s line %d: %s",
+               cfg_filename, config_lineno, config_input_line);
+}
+
+void
+ConfigParser::TokenPutBack(const char *tok)
+{
+    assert(tok);
+    Undo_.push(tok);
+}
+
+char *
+ConfigParser::Undo()
+{
+    static char undoToken[CONFIG_LINE_LIMIT];
+    if (!Undo_.empty()) {
+        xstrncpy(undoToken, Undo_.front().c_str(), sizeof(undoToken));
+        undoToken[sizeof(undoToken) - 1] = '\0';
+        if (!PreviewMode_)
+            Undo_.pop();
+        return undoToken;
+    }
+    return NULL;
 }
 
 char *
-ConfigParser::strtokFile(void)
+ConfigParser::strtokFile()
 {
+    if (RecognizeQuotedValues)
+        return ConfigParser::NextToken();
+
     static int fromFile = 0;
     static FILE *wordFile = NULL;
 
-    char *t, *fn;
-    LOCAL_ARRAY(char, buf, CONFIG_LINE_LIMIT);
+    char *t;
+    static char buf[CONFIG_LINE_LIMIT];
+
+    if ((t = ConfigParser::Undo()))
+        return t;
 
     do {
 
         if (!fromFile) {
-            t = (strtok(NULL, w_space));
-
-            if (!t || *t == '#') {
+            ConfigParser::TokenType tokenType;
+            t = ConfigParser::NextElement(tokenType);
+            if (!t) {
                 return NULL;
             } else if (*t == '\"' || *t == '\'') {
                 /* quote found, start reading from file */
-                fn = ++t;
+                debugs(3, 8,"Quoted token found : " << t);
+                char *fn = ++t;
 
                 while (*t && *t != '\"' && *t != '\'')
                     ++t;
@@ -71,8 +111,8 @@ ConfigParser::strtokFile(void)
                 *t = '\0';
 
                 if ((wordFile = fopen(fn, "r")) == NULL) {
-                    debugs(28, DBG_CRITICAL, "strtokFile: " << fn << " not found");
-                    return (NULL);
+                    debugs(3, DBG_CRITICAL, "ERROR: Can not open file " << fn << " for reading");
+                    return NULL;
                 }
 
 #if _SQUID_WINDOWS_
@@ -86,7 +126,7 @@ ConfigParser::strtokFile(void)
         }
 
         /* fromFile */
-        if (fgets(buf, CONFIG_LINE_LIMIT, wordFile) == NULL) {
+        if (fgets(buf, sizeof(buf), wordFile) == NULL) {
             /* stop reading from file */
             fclose(wordFile);
             wordFile = NULL;
@@ -115,53 +155,357 @@ ConfigParser::strtokFile(void)
     return t;
 }
 
-void
-ConfigParser::ParseQuotedString(char **var, bool *wasQuoted)
+char *
+ConfigParser::UnQuote(const char *token, const char **next)
 {
-    String sVar;
-    ParseQuotedString(&sVar, wasQuoted);
-    *var = xstrdup(sVar.termedBuf());
+    const char *errorStr = NULL;
+    const char *errorPos = NULL;
+    char quoteChar = *token;
+    assert(quoteChar == '"' || quoteChar == '\'');
+    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 && (size_t)(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 == '"') {
+            errorStr = "Unsupported cfg macro";
+            errorPos = s;
+#endif
+#if 0
+        } else if (*s == '%' && quoteChar == '"' && (!AllowMacros_ )) {
+            errorStr = "Macros are not supported here";
+            errorPos = s;
+#endif
+        } else
+            *d = *s;
+        ++s;
+        ++d;
+    }
+
+    if (*s != quoteChar && !errorStr) {
+        errorStr = "missing quote char at the end of quoted string";
+        errorPos = s - 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_)
+            xstrncpy(UnQuoted, SQUID_ERROR_TOKEN, sizeof(UnQuoted));
+        else {
+            debugs(3, DBG_CRITICAL, "FATAL: " << errorStr << ": " << errorPos);
+            self_destruct();
+        }
+    }
+
+    if (next)
+        *next = s + 1;
+    return UnQuoted;
 }
 
 void
-ConfigParser::ParseQuotedString(String *var, bool *wasQuoted)
+ConfigParser::SetCfgLine(char *line)
 {
-    // Get all of the remaining string
-    char *token = strtok(NULL, "");
-    if (token == NULL)
-        self_destruct();
+    CfgLine = line;
+    CfgPos = line;
+    while (!CfgLineTokens_.empty()) {
+        char *token = CfgLineTokens_.front();
+        CfgLineTokens_.pop();
+        free(token);
+    }
+}
 
-    if (*token != '"') {
-        token = strtok(token, w_space);
-        var->reset(token);
-        if (wasQuoted)
-            *wasQuoted = false;
-        return;
-    } else if (wasQuoted)
-        *wasQuoted = true;
-
-    char  *s = token + 1;
-    /* scan until the end of the quoted string, unescaping " and \  */
-    while (*s && *s != '"') {
-        if (*s == '\\') {
-            const char * next = s+1; // may point to 0
-            memmove(s, next, strlen(next) + 1);
+char *
+ConfigParser::TokenParse(const char * &nextToken, ConfigParser::TokenType &type)
+{
+    if (!nextToken || *nextToken == '\0')
+        return NULL;
+    type = ConfigParser::SimpleToken;
+    nextToken += strspn(nextToken, w_space);
+
+    if (*nextToken == '#')
+        return NULL;
+
+    if (ConfigParser::RecognizeQuotedValues && (*nextToken == '"' || *nextToken == '\'')) {
+        type = ConfigParser::QuotedToken;
+        char *token = xstrdup(UnQuote(nextToken, &nextToken));
+        CfgLineTokens_.push(token);
+        return token;
+    }
+
+    const char *tokenStart = nextToken;
+    const char *sep;
+    if (ConfigParser::ParseKvPair_) {
+        if (ConfigParser::KvPairState_ == ConfigParser::atParseKey)
+            sep = "=";
+        else
+            sep = w_space;
+    } else if (ConfigParser::ParseQuotedOrToEol_)
+        sep = "\n";
+    else if (ConfigParser::RecognizeQuotedPair_)
+        sep = w_space "\\";
+    else if (!ConfigParser::RecognizeQuotedValues || *nextToken == '(')
+        sep = w_space;
+    else
+        sep = w_space "(";
+    nextToken += strcspn(nextToken, sep);
+
+    while (ConfigParser::RecognizeQuotedPair_ && *nextToken == '\\') {
+        // NP: do not permit \0 terminator to be escaped.
+        if (*(nextToken+1) && *(nextToken+1) != '\r' && *(nextToken+1) != '\n') {
+            nextToken += 2; // skip the quoted-pair (\-escaped) character
+            nextToken += strcspn(nextToken, sep);
+        } else {
+            debugs(3, DBG_CRITICAL, "FATAL: Unescaped '\' character in regex pattern: " << tokenStart);
+            self_destruct();
         }
-        ++s;
     }
 
-    if (*s != '"') {
-        debugs(3, DBG_CRITICAL, "ParseQuotedString: missing '\"' at the end of quoted string" );
+    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, "FATAL: Unknown cfg function: " << tokenStart);
+                self_destruct();
+            }
+        }
+    } else
+        type = ConfigParser::SimpleToken;
+
+    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, "FATAL: Not alphanumeric character '"<< *s << "' in unquoted token " << tokenStart);
+                        self_destruct();
+                    }
+                }
+            }
+        }
+        token = xstrndup(tokenStart, nextToken - tokenStart + 1);
+        CfgLineTokens_.push(token);
+    }
+
+    if (*nextToken != '\0' && *nextToken != '#') {
+        ++nextToken;
+    }
+
+    return token;
+}
+
+char *
+ConfigParser::NextElement(ConfigParser::TokenType &type)
+{
+    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()
+{
+    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();
+            token = wordfile->parse(LastTokenType);
+            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::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, "FATAL: Quoted filename missing: " << token);
+                self_destruct();
+                return NULL;
+            }
+
+            // 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, "FATAL: missing ')' after " << token << "(\"" << path << "\"");
+                self_destruct();
+                return NULL;
+            }
+
+            if (CfgFiles.size() > 16) {
+                debugs(3, DBG_CRITICAL, "FATAL: can't open %s for reading parameters: includes are nested too deeply (>16)!\n" << path);
+                self_destruct();
+                return NULL;
+            }
+
+            ConfigParser::CfgFile *wordfile = new ConfigParser::CfgFile();
+            if (!path || !wordfile->startParse(path)) {
+                debugs(3, DBG_CRITICAL, "FATAL: Error opening config file: " << token);
+                delete wordfile;
+                self_destruct();
+                return NULL;
+            }
+            CfgFiles.push(wordfile);
+            token = NULL;
+        }
+    } while (token == NULL && !CfgFiles.empty());
+
+    return token;
+}
+
+char *
+ConfigParser::PeekAtToken()
+{
+    PreviewMode_ = true;
+    char *token = NextToken();
+    PreviewMode_ = false;
+    return token;
+}
+
+char *
+ConfigParser::NextQuotedOrToEol()
+{
+    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;
+    }
+
+    return token;
+}
+
+bool
+ConfigParser::NextKvPair(char * &key, char * &value)
+{
+    key = value = NULL;
+    ParseKvPair_ = true;
+    KvPairState_ = ConfigParser::atParseKey;
+    if ((key = NextToken()) != NULL) {
+        KvPairState_ = ConfigParser::atParseValue;
+        value = NextQuotedToken();
+    }
+    ParseKvPair_ = false;
+
+    if (!key)
+        return false;
+    if (!value) {
+        debugs(3, DBG_CRITICAL, "Error while parsing key=value token. Value missing after: " << key);
+        return false;
+    }
+
+    return true;
+}
+
+char *
+ConfigParser::RegexStrtokFile()
+{
+    if (ConfigParser::RecognizeQuotedValues) {
+        debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
+        self_destruct();
+    }
+    ConfigParser::RecognizeQuotedPair_ = true;
+    char * token = strtokFile();
+    ConfigParser::RecognizeQuotedPair_ = false;
+    return token;
+}
+
+char *
+ConfigParser::RegexPattern()
+{
+    if (ConfigParser::RecognizeQuotedValues) {
+        debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
         self_destruct();
     }
-    strtok(s-1, "\""); /*Reset the strtok to point after the "  */
-    *s = '\0';
+    ConfigParser::RecognizeQuotedPair_ = true;
+    char * token = NextToken();
+    ConfigParser::RecognizeQuotedPair_ = false;
+    return token;
+}
 
-    var->reset(token+1);
+char *
+ConfigParser::NextQuotedToken()
+{
+    const bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues;
+    ConfigParser::RecognizeQuotedValues = true;
+    char *token = NextToken();
+    ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues;
+    return token;
 }
 
 const char *
-ConfigParser::QuoteString(String &var)
+ConfigParser::QuoteString(const String &var)
 {
     static String quotedStr;
     const char *s = var.termedBuf();
@@ -183,3 +527,73 @@ ConfigParser::QuoteString(String &var)
     quotedStr.append('"');
     return quotedStr.termedBuf();
 }
+
+bool
+ConfigParser::CfgFile::startParse(char *path)
+{
+    assert(wordFile == NULL);
+    debugs(3, 3, "Parsing from " << path);
+    if ((wordFile = fopen(path, "r")) == NULL) {
+        debugs(3, DBG_CRITICAL, "WARNING: file :" << path << " not found");
+        return false;
+    }
+
+#if _SQUID_WINDOWS_
+    setmode(fileno(wordFile), O_TEXT);
+#endif
+
+    filePath = path;
+    return getFileLine();
+}
+
+bool
+ConfigParser::CfgFile::getFileLine()
+{
+    // Else get the next line
+    if (fgets(parseBuffer, CONFIG_LINE_LIMIT, wordFile) == NULL) {
+        /* stop reading from file */
+        fclose(wordFile);
+        wordFile = NULL;
+        parseBuffer[0] = '\0';
+        return false;
+    }
+    parsePos = parseBuffer;
+    currentLine = parseBuffer;
+    lineNo++;
+    return true;
+}
+
+char *
+ConfigParser::CfgFile::parse(ConfigParser::TokenType &type)
+{
+    if (!wordFile)
+        return NULL;
+
+    if (!*parseBuffer)
+        return NULL;
+
+    char *token;
+    while (!(token = nextElement(type))) {
+        if (!getFileLine())
+            return NULL;
+    }
+    return token;
+}
+
+char *
+ConfigParser::CfgFile::nextElement(ConfigParser::TokenType &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()
+{
+    if (wordFile)
+        fclose(wordFile);
+}
+