Hi all, I am attaching a new patch which try to handle the reported problems.
Just adding a new method which do special parsing for tokens like the regex has many effects, for example the ConfigParser::TokenUndo mechanism can not work. This patch: 1) By default disables quoted tokens ("configuration_includes_quoted_values off") 2) If configuration_includes_quoted_values is off the quoted tokens parsed using the ConfigParser::NextToken include the quotes, to keep compatibility with older releases. 3) For the cases where quoted strings are required (wordlists, Notes parsing, Headers with acl), the new ConfigParser::NextQuotedToken method added. The old wordlists parser allowed escaping any character, this patch will return an error if you try to escape alphanumeric characters. The \r \n and \t have the C semantics. 4) Add the ConfigParser::RegexPattern() to get the next regex token 5) Add the ConfigParser::RegexStrtokFile() to get the next regex token which is compatible with the old strtokFile 6) Removes the ConfigParser::TokenUndo method. The new method ConfigParser::NextTokenPreview() which can be used to preview the next token is added. This method if the next token is invalid (eg unquoted with special characters) return as token the "SQUID_ERROR_TOKEN" (we do not want to call self_destruct while previewing next element). 7) In this patch I kept the ConfigParser::TokenPutBack method which is used in only one place (acl regex). However this method should removed in the future, with the ConfigParser::Undo_ member and the ConfigParser::Undo() method Notes ====== 1) The current trunk parser read a line, and the tokens stored in this line and the line modified while parsed. This patch consider the line we are parsing as const and stores parsed tokens to ConfigParser::CfgLineTokens_ std::queue: - we may need to parse again the line (NextTokenPreview/NextToken) so we do not want to modify it - The current line tokens must stored somewhere to support the following: char *name = ConfigParser::NextToken(); char *value = ConfigParser::NextToken(); The ConfigParser::CfgLineTokens_ emptied every time new config line is read. 2) A set of new flags defined under ConfigParser class to define the type of parsing: ParseRegex_ (next token is regex) ParseQuotedOrToEOL_ (next token is quoted or to-EOL), PreviewMode_ (just do preview do not pop token) This method is not the best, but it is not so bad.... 3) The goal of new parser was to have a small and simple parser, but now looks very complex. But it very is difficult to keep compatibility with a simple parser. Probably we will need to re-implement it after the old configuration file style support removed from squid. On 07/31/2013 07:59 PM, Alex Rousskov wrote: > Christos, Amos, > > Thank you for working on this! I hope we can avoid repeating the > same set of mistakes thrice by carefully considering the big picture and > upgrade path. Here is my understanding of how we want things to work, > based on the review of the reported bugs and this thread so far: > > > 1. configuration_includes_quoted_values defaults to off. The setting > scope extends from the place where the directive is used to either the > end of configuration or to the next use, whichever comes first. OK this patch make configuration_includes_quoted_values defaults to off > > > 2. When configuration_includes_quoted_values is on, new "strict syntax" > rules are enforced: > > 2a. "quoted values" and function()s are supported. %Macros are supported > inside quoted values and only inside them. Unknown functions and macros > terminate Squid. Code uses NextToken() to get tokens by default. A small > subset of hand-picked directives may use NextQuotedOrToEol() or other > special methods instead of NextToken() to accommodate more legacy cases. > Logformat is one such example. I think this patch cover this requirement. > > 2b. A % sign can be \-escaped to block macro expansion in quoted strings > where needed. A few "standard" escape sequences are supported such as > \n. Unknown escape sequences terminate Squid. Any not alphanumeric character can be escaped in this patch. Also the \n\t\r are supported. We can add/remove more if required. > > 2c. By default, token delimiter is whitespace. Bare (i.e., unquoted) > tokens containing any character other than alphanumeric, underscore, > period, '(', ')', '=', and '-' terminate Squid. For example, foo{bar}, > foo@bar, and foo"bar" terminate Squid in strict syntax mode. OK. In this patch allow the following ".,()-=_%/:" We can easily add remove characters from this list. If we remove from this list the ':' then we should require quotes to define http, icap or ecap urls. The % used to define percent values and the '/' to define filenames. > > 3. When configuration_includes_quoted_values is off, old "legacy syntax" > is supported, to the extent possible: > > 3a. "quoted values", functions(), and %macros are not recognized or > treated specially compared to Squid v3.3. Code uses NextToken() to get > tokens by default. A small subset of hand-picked directives may use > NextQuotedOrToEol() instead of NextToken() but that method does _not_ > treat quoted strings specially in legacy mode. Logformat is one such > example. OK on this. I just add the NextQuotedToken to support quoted tokens in "legacy syntax" mode. We need it in wordlist tokens, Notes and Header with acls.... > > 3b. I am not sure about "file.cfg" include syntax in legacy mode. I > think it would be OK _not_ to support it (because fixing configurations > to use new parameters() syntax should not be very difficult in most > cases), but I may be wrong. If it is easy to continue to support > "file.cfg" include style in NextToken() working in legacy mode, then we > should do it. This patch supports "file.cfg" in legacy mode. > > > 4. Exceptions: A small set of directives that already supported quoted > strings correctly before the introduction of > configuration_includes_quoted_values must continue to support them. > These directives should call NextQuotedOrLegacy() method. The method > temporary forces configuration_includes_quoted_values to ON if and only > if the next token starts with a quote. I used the NextQuotedToken for this ... > > This may create a few upgrade problems when, for example, somebody is > using unsupported %macros inside quoted strings with these options, but > the support for quoted strings in those headers was added relatively > recently so there should not be many such cases, and it is not practical > to treat these options as a complex third class. They have to obey all > strict syntax rules when they use quoted strings (and also when > configuration_includes_quoted_values is on, of course). > > > 5. Future changes: > > 5a. We revisit handling of 'single quoted strings' later, after the > above is done. They are useful to disable macros and standard escape > sequences in strings, but they are not a "must have" for now, and we > need to decide how to treat \-escapes inside them. We will probably > allow escaping of two characters only: \ and '. > > 5b. We revisit handling of REs, after the above is done. We will > probably add a proper dedicated /quoting mechanism/ for them. For now, > most REs require configuration_includes_quoted_values set to off. > > 5c. I recommend combining 5a and 5b implementation to support a simple > generic quoting mechanism with an admin-selectable quoting character. > That is almost a must for REs but also help with general quoting of > complex expressions. See "Quote and Quote-like Operators" in Perl > (perlop man page) for ideas (but we only need a few lines from that > table). Note how 2c facilitates this future change by immediately > prohibiting unquoted tokens that may become quoted strings later. For > example q{foobar} is prohibited if configuration_includes_quoted_values > is on. > > > Anything I missed or misrepresented? > > > Thank you, > > Alex. >
=== modified file 'src/ConfigParser.cc' --- src/ConfigParser.cc 2013-07-22 01:26:09 +0000 +++ src/ConfigParser.cc 2013-08-07 07:12:25 +0000 @@ -21,377 +21,495 @@ * 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 <robe...@squid-cache.org> */ #include "squid.h" #include "cache_cf.h" #include "ConfigParser.h" #include "Debug.h" #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::ParseRegex_ = false; +bool ConfigParser::ParseQuotedOrToEOL_ = false; +bool ConfigParser::PreviewMode_ = false; + +static const char *SQUID_ERROR_TOKEN = "SQUID_ERROR_TOKEN"; void ConfigParser::destruct() { shutting_down = 1; if (!CfgFiles.empty()) { std::ostringstream message; CfgFile *f = CfgFiles.top(); - message << "Bungled " << f->filePath << " line " << f->lineNo << + message << "Bungled (#1)" << 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", + fatalf("Bungled (#2) %s line %d: %s", cfg_filename, config_lineno, config_input_line); } void -ConfigParser::TokenUndo() -{ - assert(LastToken); - Undo_.push(LastToken); -} - -void ConfigParser::TokenPutBack(const char *tok) { assert(tok); Undo_.push(tok); } char * ConfigParser::Undo() { LOCAL_ARRAY(char, undoToken, CONFIG_LINE_LIMIT); 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 * ConfigParser::strtokFile() { if (RecognizeQuotedValues) return ConfigParser::NextToken(); static int fromFile = 0; static FILE *wordFile = 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) { - /* quote found, start reading from file */ + } 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; } #if _SQUID_WINDOWS_ setmode(fileno(wordFile), O_TEXT); #endif fromFile = 1; } else { - return LastToken = t; + return t; } } /* fromFile */ if (fgets(buf, CONFIG_LINE_LIMIT, wordFile) == NULL) { /* stop reading from file */ fclose(wordFile); wordFile = NULL; fromFile = 0; return NULL; } else { char *t2, *t3; t = buf; /* skip leading and trailing white space */ t += strspn(buf, w_space); t2 = t + strcspn(t, w_space); t3 = t2 + strspn(t2, w_space); while (*t3 && *t3 != '#') { t2 = t3 + strcspn(t3, w_space); t3 = t2 + strspn(t2, w_space); } *t2 = '\0'; } /* skip comments */ /* 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; + LOCAL_ARRAY(char, UnQuoted, CONFIG_LINE_LIMIT); + const char *s = token + 1; + char *d = UnQuoted; /* 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(); + 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; + } } else if (*s == '$' && quoteChar == '"') { - debugs(3, DBG_CRITICAL, "Unsupported cfg macro: " << s); - self_destruct(); + errorStr = "Unsupported cfg macro"; + errorPos = s; } 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 && !errorStr) { + errorStr = "missing quote char at the end of quoted string"; + errorPos = s - 1; } + // The end of token + *d = '\0'; - if (*s != quoteChar) { - debugs(3, DBG_CRITICAL, "missing '" << quoteChar << "' at the end of quoted string: " << (s-1)); - self_destruct(); + // 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(); + } } - *end = s; - return (token+1); + + if (next) + *next = s + 1; + return UnQuoted; } void ConfigParser::SetCfgLine(char *line) { 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::ParseRegex_ && 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 || ConfigParser::ParseRegex_ || *nextToken == '(') sep = w_space; else sep = w_space "("; nextToken += strcspn(nextToken, sep); - if (!legacy && *nextToken == '(') - type = ConfigParser::FunctionNameToken; - else - type = ConfigParser::SimpleToken; + 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::ParseRegex_ ? ConfigParser::RegexPatternToken : ConfigParser::SimpleToken); - if (*nextToken != '\0') { - *nextToken = '\0'; - ++nextToken; + char *token = NULL; + if (nextToken - tokenStart) { + if (ConfigParser::StrictMode && type == ConfigParser::SimpleToken) { + for (const char *s = tokenStart; s != nextToken; ++s) { + if (!isalnum(*s) && !strchr(".,()-=_%/:", *s)) { + 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 (!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(); 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::FunctionNameToken && strcmp("parameters", token) == 0) { + if (token && LastTokenType == ConfigParser::FunctionParameters) { + //Disable temporary preview mode, we need to parse function parameters + bool savePreview = ConfigParser::PreviewMode_; + ConfigParser::PreviewMode_ = false; + char *path = NextToken(); if (LastTokenType != ConfigParser::QuotedToken) { debugs(3, DBG_CRITICAL, "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, "missing ')' after " << token << "(\"" << path << "\""); self_destruct(); return NULL; } if (CfgFiles.size() > 16) { debugs(3, DBG_CRITICAL, "WARNING: 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, "Error opening config file: " << token); delete wordfile; self_destruct(); return NULL; } 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::NextQuotedOrToEol() +ConfigParser::NextTokenPreview() { - char *token; + PreviewMode_ = true; + char *token = NextToken(); + PreviewMode_ = false; + return token; +} - if ((token = CfgPos) == NULL) { - debugs(3, DBG_CRITICAL, "token is missing"); - self_destruct(); - return NULL; +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; } - 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; + return token; +} + +char * +ConfigParser::RegexStrtokFile() +{ + ParseRegex_ = true; + char * token = strtokFile(); + ParseRegex_ = false; + return token; +} - CfgPos = NULL; - return (LastToken = token); +char * +ConfigParser::RegexPattern() +{ + ParseRegex_ = true; + char * token = NextToken(); + ParseRegex_ = false; + return token; +} + +char * +ConfigParser::NextQuotedToken() +{ + bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues; + ConfigParser::RecognizeQuotedValues = true; + char *token = NextToken(); + ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues; + return token; } const char * ConfigParser::QuoteString(const String &var) { static String quotedStr; const char *s = var.termedBuf(); bool needQuote = false; for (const char *l = s; !needQuote && *l != '\0'; ++l ) needQuote = !isalnum(*l); if (!needQuote) return s; quotedStr.clean(); quotedStr.append('"'); for (; *s != '\0'; ++s) { if (*s == '"' || *s == '\\') quotedStr.append('\\'); quotedStr.append(*s); } 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, "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); @@ -408,28 +526,33 @@ 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) { - 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() { if (wordFile) fclose(wordFile); } === modified file 'src/ConfigParser.h' --- src/ConfigParser.h 2013-07-21 19:24:35 +0000 +++ src/ConfigParser.h 2013-08-07 06:39:13 +0000 @@ -53,158 +53,182 @@ #define CONFIG_LINE_LIMIT 2048 /** * A configuration file Parser. Instances of this class track * parsing state and perform tokenisation. Syntax is currently * taken care of outside this class. * * One reason for this class is to allow testing of configuration * using modules without linking cache_cf.o in - because that drags * in all of squid by reference. Instead the tokeniser only is * brought in. */ class ConfigParser { public: /** * Parsed tokens type: simple tokens, quoted tokens or function * like parameters. */ - enum TokenType {SimpleToken, QuotedToken, FunctionNameToken}; + enum TokenType {SimpleToken, QuotedToken, RegexPatternToken, FunctionParameters}; void destruct(); static void ParseUShort(unsigned short *var); static void ParseBool(bool *var); static const char *QuoteString(const String &var); static void ParseWordList(wordlist **list); /** * Backward compatibility wrapper for the ConfigParser::NextToken method. * If the configuration_includes_quoted_values configuration parameter is * set to 'off' this interprets the quoted tokens as filenames. */ static char * strtokFile(); /** * Returns the body of the next element. The element is either a token or * a quoted string with optional escape sequences and/or macros. The body * of a quoted string element does not include quotes or escape sequences. * Future code will want to see Elements and not just their bodies. */ 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);} /** * \return the next quoted string or the raw string data until the end of line. * This method allows %macros in unquoted strings to keep compatibility * for the logformat option. */ 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 *NextTokenPreview(); /** * The next NextToken call will return the token as next element * It can be used repeatedly to add more than one tokens in a FIFO list. */ static void TokenPutBack(const char *token); /// Set the configuration file line to parse. static void SetCfgLine(char *line); /// Allow %macros inside quoted strings static void EnableMacros() {AllowMacros_ = true;} /// Do not allow %macros inside quoted strings 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 + static bool StrictMode; protected: /** * Class used to store required information for the current * configuration file. */ class CfgFile { public: CfgFile(): wordFile(NULL), parsePos(NULL), lineNo(0) { parseBuffer[0] = '\0';} ~CfgFile(); /// True if the configuration file is open bool isOpen() {return wordFile != NULL;} /** * Open the file given by 'path' and initializes the CfgFile object * to start parsing */ bool startParse(char *path); /** * Do the next parsing step: * reads the next line from file if required. * \return the body of next element or a NULL pointer if there are no more token elements in the file. * \param type will be filled with the ConfigParse::TokenType for any element found, or left unchanged if NULL is returned. */ char *parse(TokenType &type); private: bool getFileLine(); ///< Read the next line from the file /** * Return the body of the next element. If the wasQuoted is given * set to true if the element was quoted. */ 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 + * 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 * element if required. * \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 ParseRegex_; ///< The next tokens will be handled as regex patterns + 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); #endif /* SQUID_CONFIGPARSER_H */ === modified file 'src/Notes.cc' --- src/Notes.cc 2013-07-21 19:24:35 +0000 +++ src/Notes.cc 2013-08-05 20:05:41 +0000 @@ -76,41 +76,41 @@ } Note::Pointer Notes::add(const String ¬eKey) { typedef Notes::NotesList::iterator AMLI; for (AMLI i = notes.begin(); i != notes.end(); ++i) { if ((*i)->key == noteKey) return (*i); } Note::Pointer note = new Note(noteKey); notes.push_back(note); return note; } Note::Pointer Notes::parse(ConfigParser &parser) { String key = ConfigParser::NextToken(); - String value = ConfigParser::NextToken(); + String value = ConfigParser::NextQuotedToken(); Note::Pointer note = add(key); Note::Value::Pointer noteValue = note->addValue(value); String label(key); label.append('='); label.append(value); aclParseAclList(parser, ¬eValue->aclList, label.termedBuf()); if (blacklisted) { for (int i = 0; blacklisted[i] != NULL; ++i) { if (note->key.caseCmp(blacklisted[i]) == 0) { fatalf("%s:%d: meta key \"%s\" is a reserved %s name", cfg_filename, config_lineno, note->key.termedBuf(), descr ? descr : ""); } } } return note; } === modified file 'src/acl/Acl.cc' --- src/acl/Acl.cc 2013-07-21 19:24:35 +0000 +++ src/acl/Acl.cc 2013-08-06 19:24:10 +0000 @@ -38,62 +38,59 @@ #include "dlink.h" #include "globals.h" #include "profiler/Profiler.h" #include "SquidConfig.h" const ACLFlag ACLFlags::NoFlags[1] = {ACL_F_END}; const char *AclMatchedName = NULL; bool ACLFlags::supported(const ACLFlag f) const { if (f == ACL_F_REGEX_CASE) return true; return (supported_.find(f) != std::string::npos); } void ACLFlags::parseFlags() { char *nextToken; - while ((nextToken = ConfigParser::strtokFile()) != NULL && nextToken[0] == '-') { - + while ((nextToken = ConfigParser::NextTokenPreview()) != NULL && nextToken[0] == '-') { + (void)ConfigParser::NextToken(); //Get token fron cfg line //if token is the "--" break flag if (strcmp(nextToken, "--") == 0) break; for (const char *flg = nextToken+1; *flg!='\0'; flg++ ) { if (supported(*flg)) { makeSet(*flg); } else { debugs(28, 0, HERE << "Flag '" << *flg << "' not supported"); self_destruct(); } } } /*Regex code needs to parse -i file*/ if ( isSet(ACL_F_REGEX_CASE)) ConfigParser::TokenPutBack("-i"); - - if (nextToken != NULL && strcmp(nextToken, "--") != 0 ) - ConfigParser::TokenUndo(); } const char * ACLFlags::flagsStr() const { static char buf[64]; if (flags_ == 0) return ""; char *s = buf; *s++ = '-'; for (ACLFlag f = 'A'; f <= 'z'; f++) { // ACL_F_REGEX_CASE (-i) flag handled by ACLRegexData class, ignore if (isSet(f) && f != ACL_F_REGEX_CASE) *s++ = f; } *s = '\0'; return buf; } === modified file 'src/acl/RegexData.cc' --- src/acl/RegexData.cc 2012-09-06 11:56:46 +0000 +++ src/acl/RegexData.cc 2013-08-05 09:51:23 +0000 @@ -305,41 +305,41 @@ flags &= ~REG_ICASE; } else { newTail = compileRE( Tail, wl->key , flags ); if (newTail == NULL) debugs(28, DBG_CRITICAL, "ERROR: Skipping regular expression. Compile failed: '" << wl->key << "'"); else Tail = newTail; } wl = wl->next; } } static void aclParseRegexList(RegexList **curlist) { char *t; wordlist *wl = NULL; debugs(28, 2, HERE << "aclParseRegexList: new Regex line or file"); - while ((t = ConfigParser::strtokFile()) != NULL) { + while ((t = ConfigParser::RegexStrtokFile()) != NULL) { const char *clean = removeUnnecessaryWildcards(t); if (strlen(clean) > BUFSIZ-1) { debugs(28, DBG_CRITICAL, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line); debugs(28, DBG_CRITICAL, "ERROR: Skipping regular expression. Larger than " << BUFSIZ-1 << " characters: '" << clean << "'"); } else { debugs(28, 3, "aclParseRegexList: buffering RE '" << clean << "'"); wordlistAdd(&wl, clean); } } if (!compileOptimisedREs(curlist, wl)) { debugs(28, DBG_IMPORTANT, "WARNING: optimisation of regular expressions failed; using fallback method without optimisation"); compileUnoptimisedREs(curlist, wl); } wordlistDestroy(&wl); } void ACLRegexData::parse() === modified file 'src/cache_cf.cc' --- src/cache_cf.cc 2013-07-30 00:44:04 +0000 +++ src/cache_cf.cc 2013-08-06 19:29:07 +0000 @@ -243,40 +243,44 @@ static void free_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign); static void parse_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt); static void dump_sslproxy_cert_adapt(StoreEntry *entry, const char *name, sslproxy_cert_adapt *cert_adapt); static void free_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt); static void parse_sslproxy_ssl_bump(acl_access **ssl_bump); static void dump_sslproxy_ssl_bump(StoreEntry *entry, const char *name, acl_access *ssl_bump); static void free_sslproxy_ssl_bump(acl_access **ssl_bump); #endif /* USE_SSL */ static void parse_b_size_t(size_t * var); static void parse_b_int64_t(int64_t * var); static bool parseNamedIntList(const char *data, const String &name, Vector<int> &list); static void parse_CpuAffinityMap(CpuAffinityMap **const cpuAffinityMap); static void dump_CpuAffinityMap(StoreEntry *const entry, const char *const name, const CpuAffinityMap *const cpuAffinityMap); static void free_CpuAffinityMap(CpuAffinityMap **const cpuAffinityMap); 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. * Other modules needing access to a ConfigParser should have it * provided to them in their parserFOO methods. */ static ConfigParser LegacyParser = ConfigParser(); void self_destruct(void) { LegacyParser.destruct(); } static void update_maxobjsize(void) { int64_t ms = -1; // determine the maximum size object that can be stored to disk @@ -1777,41 +1781,41 @@ } static void dump_http_header_replace(StoreEntry * entry, const char *name, const HeaderManglers *manglers) { if (manglers) manglers->dumpReplacement(entry, name); } static void parse_http_header_replace(HeaderManglers **pm) { char *t = NULL; if ((t = ConfigParser::NextToken()) == NULL) { debugs(3, DBG_CRITICAL, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line); debugs(3, DBG_CRITICAL, "parse_http_header_replace: missing header name."); return; } - const char *value = t + strlen(t) + 1; + const char *value = ConfigParser::NextQuotedOrToEol(); if (!*pm) *pm = new HeaderManglers; HeaderManglers *manglers = *pm; manglers->setReplacement(t, value); } #endif static void dump_cachedir(StoreEntry * entry, const char *name, SquidConfig::_cacheSwap swap) { SwapDir *s; int i; assert (entry); for (i = 0; i < swap.n_configured; ++i) { s = dynamic_cast<SwapDir *>(swap.swapDirs[i].getRaw()); if (!s) continue; storeAppendPrintf(entry, "%s %s %s", name, s->type(), s->path); @@ -2682,55 +2686,57 @@ debugs(0, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: 'enable' is deprecated. Please update to use value 'on'."); *var = 1; } else if (!strcmp(token, "warn")) { *var = -1; } else if (!strcmp(token, "off")) { *var = 0; } else if (!strcmp(token, "disable")) { debugs(0, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: 'disable' is deprecated. Please update to use value 'off'."); *var = 0; } else { debugs(0, DBG_PARSE_NOTE(DBG_IMPORTANT), "ERROR: Invalid option: Tristate options can only be 'on', 'off', or 'warn'."); self_destruct(); } } #define free_tristate free_int void parse_pipelinePrefetch(int *var) { - char *token = ConfigParser::strtokFile(); + char *token = ConfigParser::NextTokenPreview(); 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 #define dump_pipelinePrefetch dump_int static void dump_refreshpattern(StoreEntry * entry, const char *name, RefreshPattern * head) { while (head != NULL) { storeAppendPrintf(entry, "%s%s %s %d %d%% %d", name, head->flags.icase ? " -i" : null_string, head->pattern, (int) head->min / 60, (int) (100.0 * head->pct + 0.5), (int) head->max / 60); if (head->max_stale >= 0) storeAppendPrintf(entry, " max-stale=%d", head->max_stale); @@ -2787,48 +2793,48 @@ int max_stale = -1; #if USE_HTTP_VIOLATIONS int override_expire = 0; int override_lastmod = 0; int reload_into_ims = 0; int ignore_reload = 0; int ignore_no_store = 0; int ignore_must_revalidate = 0; int ignore_private = 0; int ignore_auth = 0; #endif int i; RefreshPattern *t; regex_t comp; 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(); } } if (token == NULL) { debugs(3, DBG_CRITICAL, "FATAL: refresh_pattern missing the regex pattern parameter"); self_destruct(); return; } pattern = xstrdup(token); i = GetInteger(); /* token: min */ /* catch negative and insanely huge values close to 32-bit wrap */ if (i < 0) { debugs(3, DBG_IMPORTANT, "WARNING: refresh_pattern minimum age negative. Cropped back to zero."); i = 0; } if (i > 60*24*365) { @@ -3228,41 +3234,41 @@ static void dump_wordlist(StoreEntry * entry, const char *name, wordlist * list) { while (list != NULL) { storeAppendPrintf(entry, "%s %s\n", name, list->key); list = list->next; } } void ConfigParser::ParseWordList(wordlist ** list) { parse_wordlist(list); } void parse_wordlist(wordlist ** list) { char *token; - while ((token = ConfigParser::NextToken())) + while ((token = ConfigParser::NextQuotedToken())) wordlistAdd(list, token); } #if 0 /* now unused */ static int check_null_wordlist(wordlist * w) { return w == NULL; } #endif static int check_null_acl_access(acl_access * a) { return a == NULL; } #define free_wordlist wordlistDestroy #define free_uri_whitespace free_int @@ -4059,73 +4065,75 @@ /* determine configuration style */ const char *filename = ConfigParser::NextToken(); if (!filename) { self_destruct(); return; } if (strcmp(filename, "none") == 0) { cl->type = Log::Format::CLF_NONE; aclParseAclList(LegacyParser, &cl->aclList, filename); while (*logs) logs = &(*logs)->next; *logs = cl; return; } cl->filename = xstrdup(filename); cl->type = Log::Format::CLF_UNKNOWN; - const char *token = ConfigParser::strtokFile(); + const char *token = ConfigParser::NextTokenPreview(); 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) { if (strncasecmp(token+9, "die", 3) == 0) { cl->fatal = true; } else if (strncasecmp(token+9, "drop", 4) == 0) { cl->fatal = false; } else { debugs(3, DBG_CRITICAL, "Unknown value for on-error '" << token << "' expected 'drop' or 'die'"); self_destruct(); } } else if (strncasecmp(token, "buffer-size=", 12) == 0) { parseBytesOptionValue(&cl->bufferSize, B_BYTES_STR, token+12); } 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::NextTokenPreview()) != NULL); } // set format if it has not been specified explicitly if (cl->type == Log::Format::CLF_UNKNOWN) setLogformat(cl, "squid", true); aclParseAclList(LegacyParser, &cl->aclList, cl->filename); while (*logs) logs = &(*logs)->next; *logs = cl; } /// sets CustomLog::type and, if needed, CustomLog::lf /// returns false iff there is no named log format static bool setLogformat(CustomLog *cl, const char *logdef_name, const bool dieWhenMissing) { assert(cl); @@ -4743,41 +4751,41 @@ } static void parse_HeaderWithAclList(HeaderWithAclList **headers) { char *fn; if (!*headers) { *headers = new HeaderWithAclList; } if ((fn = ConfigParser::NextToken()) == NULL) { self_destruct(); return; } HeaderWithAcl hwa; hwa.fieldName = fn; hwa.fieldId = httpHeaderIdByNameDef(fn, strlen(fn)); if (hwa.fieldId == HDR_BAD_HDR) hwa.fieldId = HDR_OTHER; 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(); if (hwa.quoted) { if (!nlf->parse(hwa.fieldValue.c_str())) { self_destruct(); return; } hwa.valueFormat = nlf; } else delete nlf; aclParseAclList(LegacyParser, &hwa.aclList, (hwa.fieldName + ':' + hwa.fieldValue).c_str()); (*headers)->push_back(hwa); } static void free_HeaderWithAclList(HeaderWithAclList **header) { if (!(*header)) return; @@ -4792,20 +4800,50 @@ } delete *header; *header = NULL; } static void parse_note(Notes *notes) { assert(notes); notes->parse(LegacyParser); } static void dump_note(StoreEntry *entry, const char *name, Notes ¬es) { notes.dump(entry, name); } static void free_note(Notes *notes) { 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; +} === modified file 'src/cf.data.depend' --- src/cf.data.depend 2013-05-26 01:57:47 +0000 +++ src/cf.data.depend 2013-08-06 06:47:45 +0000 @@ -1,36 +1,37 @@ # type dependencies access_log acl logformat acl external_acl_type auth_param acl_access acl acl_address acl acl_b_size_t acl acl_tos acl acl_nfmark acl address authparam b_int64_t b_size_t b_ssize_t cachedir cache_replacement_policy cachemgrpasswd ConfigAclTos +configuration_includes_quoted_values CpuAffinityMap debug delay_pool_access acl delay_class delay_pool_class delay_pools delay_pool_count delay_pool_rates delay_class client_delay_pool_access acl client_delay_pool_count client_delay_pool_rates denyinfo acl eol externalAclHelper auth_param HelperChildConfig hostdomain cache_peer hostdomaintype cache_peer http_header_access acl http_header_replace HeaderWithAclList acl adaptation_access_type adaptation_service_set adaptation_service_chain acl icap_service icap_class adaptation_service_set_type icap_service ecap_service === modified file 'src/cf.data.pre' --- src/cf.data.pre 2013-07-27 13:37:29 +0000 +++ src/cf.data.pre 2013-08-06 06:54:23 +0000 @@ -8452,42 +8452,42 @@ DOC_START The size, low-, and high-water marks for the IP cache. DOC_END NAME: fqdncache_size COMMENT: (number of entries) TYPE: int DEFAULT: 1024 LOC: Config.fqdncache.size DOC_START Maximum number of FQDN cache entries. DOC_END COMMENT_START MISCELLANEOUS ----------------------------------------------------------------------------- COMMENT_END NAME: configuration_includes_quoted_values COMMENT: on|off -TYPE: onoff -DEFAULT: on +TYPE: configuration_includes_quoted_values +DEFAULT: off LOC: ConfigParser::RecognizeQuotedValues DOC_START If set, Squid will recognize each "quoted string" after a configuration directive as a single parameter. The quotes are stripped before the parameter value is interpreted or used. See "Values with spaces, quotes, and other special characters" section for more details. DOC_END NAME: memory_pools COMMENT: on|off TYPE: onoff DEFAULT: on LOC: Config.onoff.mem_pools DOC_START If set, Squid will keep pools of allocated (but unused) memory available for future use. If memory is a premium on your system and you believe your malloc library outperforms Squid routines, disable this. DOC_END