Quoted values in squid.conf

This patch :
  - adds support for quoted values in the entire squid.conf
  - warn about or prohibit values that can no longer be interpreted as 
    either quoted strings or simple tokens
  - support file:"path/to/file.name" syntax to load external configuration
    files
  - support macros in "double quoted" values
  - support 'single quoted' values that do not expand macros
  - replaces the strtok() calls with calls to the new ConfigParser::NextToken()
  - modify strtokFile to use new ConfigParser::NextToken()
  - Add the new configuration_includes_quoted_values configuration option, to
    control the squid parser behaviour. If set to on Squid will recognize each
    "quoted string" after a configuration directive as a single parameter

This is a Measurement Factory project

=== modified file 'src/ConfigParser.cc'
--- src/ConfigParser.cc	2013-04-16 15:08:46 +0000
+++ src/ConfigParser.cc	2013-05-23 18:19:41 +0000
@@ -21,192 +21,363 @@
  *  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>
  */
 
 #include "squid.h"
 #include "cache_cf.h"
 #include "ConfigParser.h"
 #include "Debug.h"
 #include "fatal.h"
 #include "globals.h"
 
-char *ConfigParser::lastToken = NULL;
-std::queue<std::string> ConfigParser::undo;
+int ConfigParser::RecognizeQuotedValues = true;
+std::stack<ConfigParser::CfgFile *> ConfigParser::CfgFiles;
+bool ConfigParser::Quoted = false;
+char *ConfigParser::LastToken = NULL;
+char *ConfigParser::CfgLine = NULL;
+char *ConfigParser::CfgPos = NULL;
+std::queue<std::string> ConfigParser::Undo_;
+MacroUser *ConfigParser::MacroUser_ = NULL;
 
 void
 ConfigParser::destruct()
 {
     shutting_down = 1;
     fatalf("Bungled %s line %d: %s",
            cfg_filename, config_lineno, config_input_line);
 }
 
 void
-ConfigParser::strtokFileUndo()
+ConfigParser::TokenUndo()
 {
-    assert(lastToken);
-    undo.push(lastToken);
+    assert(LastToken);
+    Undo_.push(LastToken);
 }
 
 void
-ConfigParser::strtokFilePutBack(const char *tok)
+ConfigParser::TokenPutBack(const char *tok)
 {
     assert(tok);
-    undo.push(tok);
+    Undo_.push(tok);
 }
 
 char *
-ConfigParser::strtokFile(void)
+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();
+        return undoToken;
+    }
+    return NULL;
+}
+
+char *
+ConfigParser::strtokFile()
+{
+    // XXX: add file:name support to the quoted string-aware parser
+    if (RecognizeQuotedValues)
+        return ConfigParser::NextToken();
+
     static int fromFile = 0;
     static FILE *wordFile = NULL;
-    LOCAL_ARRAY(char, undoToken, CONFIG_LINE_LIMIT);
 
-    char *t, *fn;
+    char *t;
     LOCAL_ARRAY(char, buf, CONFIG_LINE_LIMIT);
 
-    if (!undo.empty()) {
-        strncpy(undoToken, undo.front().c_str(), sizeof(undoToken));
-        undoToken[sizeof(undoToken) - 1] = '\0';
-        undo.pop();
-        return undoToken;
-    }
+    if ((LastToken = ConfigParser::Undo()))
+        return LastToken;
 
-    lastToken = NULL;
     do {
 
         if (!fromFile) {
-            t = (strtok(NULL, w_space));
-
-            if (!t || *t == '#') {
+            bool wasQuoted;
+            t = ConfigParser::NextElement(&wasQuoted);
+            debugs(28, DBG_CRITICAL,"Token scanned for quote : " << t);
+            if (!t) {
                 return NULL;
-            } else if (*t == '\"' || *t == '\'') {
+            } else if (wasQuoted) {
                 /* quote found, start reading from file */
-                fn = ++t;
-
-                while (*t && *t != '\"' && *t != '\'')
-                    ++t;
+                debugs(28, DBG_CRITICAL,"Quote found : " << t);
 
-                *t = '\0';
-
-                if ((wordFile = fopen(fn, "r")) == NULL) {
-                    debugs(28, DBG_CRITICAL, "strtokFile: " << fn << " not found");
-                    return (NULL);
+                if ((wordFile = fopen(t, "r")) == NULL) {
+                    debugs(28, DBG_CRITICAL, "strtokFile: " << t << " not found");
+                    return false;
                 }
 
 #if _SQUID_WINDOWS_
                 setmode(fileno(wordFile), O_TEXT);
 #endif
 
                 fromFile = 1;
             } else {
-                return lastToken = t;
+                return LastToken = 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 LastToken = t;
 }
 
-void
-ConfigParser::ParseQuotedString(char **var, bool *wasQuoted)
+char *
+ConfigParser::UnQuote(char *token, char **end)
 {
-    String sVar;
-    ParseQuotedString(&sVar, wasQuoted);
-    *var = xstrdup(sVar.termedBuf());
+    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();
+        } else if (*s == '$') {
+            debugs(3, DBG_CRITICAL, "Unsupported cfg macro: " << s);
+            self_destruct();
+        } else if (*s == '%' && quoteChar == '"' && 
+                   (!MacroUser_  || !MacroUser_->supportedMacro(s))) {
+            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);
+        }
+        ++s;
+    }
+
+    if (*s != quoteChar) {
+        debugs(3, DBG_CRITICAL, "missing '" << quoteChar << "' at the end of quoted string: " << (s-1));
+        self_destruct();
+    }
+    *end = s;
+    return (token+1);
 }
 
 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;
+}
 
-    if (*token != '"') {
-        token = strtok(token, w_space);
-        var->reset(token);
+char *
+ConfigParser::TokenParse(char * &nextToken, bool *wasQuoted)
+{
+    if (!nextToken || *nextToken == '\0')
+        return NULL;
+    nextToken += strspn(nextToken, w_space);
+    if (*nextToken == '"' || *nextToken == '\'') {
         if (wasQuoted)
-            *wasQuoted = false;
-        return;
-    } else if (wasQuoted)
-        *wasQuoted = true;
+            *wasQuoted = true;
+        char *token = UnQuote(nextToken, &nextToken);
+        *nextToken = '\0';
+        ++nextToken;
+        return token;
+    }
 
-    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);
-        }
-        ++s;
+    if (wasQuoted)
+        *wasQuoted = false;
+
+    char *token = nextToken;
+    if (char *t = strchr(nextToken, '#'))
+        *t = '\0';
+    nextToken += strcspn(nextToken, w_space);
+    if (*nextToken != '\0') {
+        *nextToken = '\0';
+        ++nextToken;
     }
 
-    if (*s != '"') {
-        debugs(3, DBG_CRITICAL, "ParseQuotedString: missing '\"' at the end of quoted string" );
+    if (*token == '\0')
+        return NULL;
+
+    return token;
+}
+
+char *
+ConfigParser::NextElement(bool *wasQuoted)
+{
+    char *token = TokenParse(CfgPos, wasQuoted);
+    return token;
+}
+
+char *
+ConfigParser::NextToken()
+{
+    if ((LastToken = ConfigParser::Undo()))
+        return LastToken;
+
+    char *token = NULL;
+    do {
+        while (token == NULL && !CfgFiles.empty()) {
+            ConfigParser::CfgFile *wordfile = CfgFiles.top();
+            token = wordfile->parse(&Quoted);
+            if (!token) {
+                assert(!wordfile->isOpen());
+                CfgFiles.pop();
+                delete wordfile;
+            }
+        }
+
+        if (!token)
+            token = NextElement(&Quoted);
+
+        if (token && strncmp("file:", token, 5) == 0 && !Quoted) {
+            char *path = token + 5;
+            if (*path != '"') {
+                debugs(3, DBG_CRITICAL, "Quoted filename missing: " << token);
+                self_destruct();
+                return NULL;
+            }
+            char *end; 
+            path = UnQuote(path, &end);
+            *end = '\0';
+            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;
+        }
+    } while(token == NULL && !CfgFiles.empty());
+
+    return (LastToken = token);
+}
+
+char *
+ConfigParser::NextQuotedOrToEol()
+{
+    char *token;
+
+    if ((token = CfgPos) == NULL) {
         self_destruct();
+        return NULL;
     }
-    strtok(s-1, "\""); /*Reset the strtok to point after the "  */
-    *s = '\0';
+    token += strspn(token, w_space);
 
-    var->reset(token+1);
+    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;
+    }
+
+    CfgPos = NULL;
+    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);
+    if ((wordFile = fopen(path, "r")) == NULL) {
+        debugs(3, DBG_CRITICAL, "strtokFile: " << path << " not found");
+        return false;
+    }
+
+#if _SQUID_WINDOWS_
+    setmode(fileno(wordFile), O_TEXT);
+#endif
+
+    return getFileLine();
+}
+
+bool
+ConfigParser::CfgFile::getFileLine()
+{
+    // Else get the next line
+    if (fgets(parseLine, CONFIG_LINE_LIMIT, wordFile) == NULL) {
+        /* stop reading from file */
+        fclose(wordFile);
+        wordFile = NULL;
+        parseLine[0] = '\0';
+        return false;
+    }
+    parsePos = parseLine;
+    return true;
+}
+
+char *
+ConfigParser::CfgFile::parse(bool *wasQuoted)
+{
+    if (!wordFile)
+        return NULL;
+
+    if (!*parseLine)
+        return NULL;
+
+    char *token;
+    while(!(token = nextElement(wasQuoted))) {
+        if (!getFileLine())
+            return NULL;
+    }
+    return token;
+}
+
+char *
+ConfigParser::CfgFile::nextElement(bool *wasQuoted)
+{
+    return TokenParse(parsePos, wasQuoted);
+}

=== modified file 'src/ConfigParser.h'
--- src/ConfigParser.h	2013-04-16 15:08:46 +0000
+++ src/ConfigParser.h	2013-05-23 19:24:10 +0000
@@ -19,74 +19,180 @@
  *  (at your option) any later version.
  *
  *  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>
  */
 
 #ifndef SQUID_CONFIGPARSER_H
 #define SQUID_CONFIGPARSER_H
 
 #include "SquidString.h"
 #include <queue>
+#include <stack>
 #if HAVE_STRING
 #include <string>
 #endif
 
 class wordlist;
 /**
  * Limit to how long any given config line may be.
  * This affects squid.conf and all included files.
  *
  * Behaviour when setting larger than 2KB is unknown.
  * The config parser read mechanism can cope, but the other systems
  * receiving the data from its buffers on such lines may not.
  */
 #define CONFIG_LINE_LIMIT	2048
 
+/// API for checking %macros found while parsing configuration options.
+class MacroUser
+{
+public:
+    /**
+     * Check if the %macro at the position pointed by token
+     * supported.
+     * Returns true if supported 
+     */
+    virtual bool supportedMacro(const char *token) = 0;
+    virtual ~MacroUser() {}
+};
+
 /**
  * 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:
     void destruct();
     static void ParseUShort(unsigned short *var);
     static void ParseBool(bool *var);
-    static void ParseString(char **var);
-    static void ParseString(String *var);
-    /// Parse an unquoted token (no spaces) or a "quoted string" that
-    /// may include spaces. In some contexts, quotes strings may also
-    /// include macros. Quoted strings may escape any character with
-    /// a backslash (\), which is currently only useful for inner
-    /// quotes. TODO: support quoted strings anywhere a token is accepted.
-    static void ParseQuotedString(char **var, bool *wasQuoted = NULL);
-    static void ParseQuotedString(String *var, bool *wasQuoted = NULL);
     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 then understands the quoted tokens as filenames.
+     */
     static char * strtokFile();
-    static void strtokFileUndo();
-    static void strtokFilePutBack(const char *);
-private:
-    static char *lastToken;
-    static std::queue<std::string> undo;
+
+    /**
+     * 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();
+
+    /// Return true if the last parsed token was quoted
+    static bool LastTokenWasQuoted() {return Quoted;}
+
+    /**
+     * Returns the next quoted string or the raw string data untill 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 of this function will return
+     * again the last parsed element.
+     */
+    static void TokenUndo();
+
+    /**
+     * The next NextToken call will return the token as next element
+     */
+    static void TokenPutBack(const char *token);
+
+    /// Set the configuration file line to parse.
+    static void SetCfgLine(char *line);
+
+    /// Set the macro user to user. If no macro user is set
+    /// the %macros inside quoted strings are not allowed.
+    static void SetMacroUser(MacroUser *user) {MacroUser_ = user;}
+
+    /// configuration_includes_quoted_values in squid.conf
+    static int RecognizeQuotedValues;
+protected:
+    /**
+     * Class used to store required information for the current
+     * configuration file.
+     */
+    class CfgFile{
+    public:
+        CfgFile(): wordFile(NULL), parsePos(NULL) { parseLine[0] = '\0';}
+        /// 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:
+         * read next line from file if required, and return the body of next
+         * element or NULL if there are not any new tokens in file.
+         */
+        char *parse(bool *wasQuoted);
+    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(bool *wasQuoted);
+        FILE *wordFile; ///< Pointer to the file.
+        char parseLine[CONFIG_LINE_LIMIT]; ///< The current line to parse
+        char *parsePos; ///< The next element position in parseLine string
+    };
+
+    /// Return the last undoed element, or NULL if none exist
+    static char *Undo();
+
+    /**
+     * Unquotes the token, which must be quoted. If the end is not NULL, 
+     * it is set to the end of token.
+     */
+    static char *UnQuote(char *token, char **end = NULL);
+
+    /**
+     * Does the real tokens parsing job: Ignore comments, unquote an 
+     * element if required.
+     * Returns NULL if there are no available tokens in nextToken string
+     * or the next token.
+     * The nextToken updated to point to the pos after parsed token.
+     */
+    static char *TokenParse(char * &nextToken, bool *wasQuoted);
+    static char *NextElement(bool *wasQuoted);
+    static std::stack<CfgFile *> CfgFiles; ///< The stack of open cfg files
+    static bool Quoted; ///< True if the last parsed element was quoted
+    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 undoed elements
+    static MacroUser *MacroUser_;
 };
 
 int parseConfigFile(const char *file_name);
 
 #endif /* SQUID_CONFIGPARSER_H */

=== modified file 'src/HelperChildConfig.cc'
--- src/HelperChildConfig.cc	2013-01-25 01:26:21 +0000
+++ src/HelperChildConfig.cc	2013-02-21 14:11:34 +0000
@@ -1,22 +1,23 @@
 #include "squid.h"
 #include "cache_cf.h"
+#include "ConfigParser.h"
 #include "Debug.h"
 #include "HelperChildConfig.h"
 #include "globals.h"
 #include "Parsing.h"
 
 #include <string.h>
 
 HelperChildConfig::HelperChildConfig(const unsigned int m):
         n_max(m),
         n_startup(0),
         n_idle(1),
         concurrency(0),
         n_running(0),
         n_active(0)
 {}
 
 HelperChildConfig &
 HelperChildConfig::updateLimits(const HelperChildConfig &rhs)
 {
     // Copy the limits only.
@@ -27,55 +28,55 @@
     concurrency = rhs.concurrency;
     return *this;
 }
 
 int
 HelperChildConfig::needNew() const
 {
     /* during the startup and reconfigure use our special amount... */
     if (starting_up || reconfiguring) return n_startup;
 
     /* keep a minimum of n_idle helpers free... */
     if ( (n_active + n_idle) < n_max) return n_idle;
 
     /* dont ever start more than n_max processes. */
     return (n_max - n_active);
 }
 
 void
 HelperChildConfig::parseConfig()
 {
-    char const *token = strtok(NULL, w_space);
+    char const *token = ConfigParser::NextToken();
 
     if (!token)
         self_destruct();
 
     /* starts with a bare number for the max... back-compatible */
     n_max = xatoui(token);
 
     if (n_max < 1) {
         debugs(0, DBG_CRITICAL, "ERROR: The maximum number of processes cannot be less than 1.");
         self_destruct();
     }
 
     /* Parse extension options */
-    for (; (token = strtok(NULL, w_space)) ;) {
+    for (; (token = ConfigParser::NextToken()) ;) {
         if (strncmp(token, "startup=", 8) == 0) {
             n_startup = xatoui(token + 8);
         } else if (strncmp(token, "idle=", 5) == 0) {
             n_idle = xatoui(token + 5);
             if (n_idle < 1) {
                 debugs(0, DBG_CRITICAL, "WARNING OVERIDE: Using idle=0 for helpers causes request failures. Overiding to use idle=1 instead.");
                 n_idle = 1;
             }
         } else if (strncmp(token, "concurrency=", 12) == 0) {
             concurrency = xatoui(token + 12);
         } else {
             debugs(0, DBG_PARSE_NOTE(DBG_IMPORTANT), "ERROR: Undefined option: " << token << ".");
             self_destruct();
         }
     }
 
     /* simple sanity. */
 
     if (n_startup > n_max) {
         debugs(0, DBG_CRITICAL, "WARNING OVERIDE: Capping startup=" << n_startup << " to the defined maximum (" << n_max <<")");

=== modified file 'src/Notes.cc'
--- src/Notes.cc	2013-04-30 00:13:26 +0000
+++ src/Notes.cc	2013-05-17 16:21:21 +0000
@@ -74,43 +74,42 @@
     return NULL;
 }
 
 Note::Pointer
 Notes::add(const String &noteKey)
 {
     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, value;
-    ConfigParser::ParseString(&key);
-    ConfigParser::ParseQuotedString(&value);
+    String key = ConfigParser::NextToken();
+    String value = ConfigParser::NextToken();
     Note::Pointer note = add(key);
     Note::Value::Pointer noteValue = note->addValue(value);
     aclParseAclList(parser, &noteValue->aclList);
 
     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;
 }
 
 void
 Notes::dump(StoreEntry *entry, const char *key)
 {

=== modified file 'src/Parsing.cc'
--- src/Parsing.cc	2013-05-04 13:14:23 +0000
+++ src/Parsing.cc	2013-05-17 16:21:21 +0000
@@ -16,40 +16,41 @@
  *
  *  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.
  *
  *  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.
  *
  */
 
 #include "squid.h"
 #include "cache_cf.h"
 #include "compat/strtoll.h"
+#include "ConfigParser.h"
 #include "Parsing.h"
 #include "globals.h"
 #include "Debug.h"
 
 /*
  * These functions is the same as atoi/l/f, except that they check for errors
  */
 
 double
 xatof(const char *token)
 {
     char *end = NULL;
     double ret = strtod(token, &end);
 
     if (ret == 0 && end == token) {
         debugs(0, DBG_PARSE_NOTE(DBG_IMPORTANT), "ERROR: No digits were found in the input value '" << token << "'.");
         self_destruct();
     }
 
     if (*end) {
@@ -129,56 +130,56 @@
 xatos(const char *token)
 {
     long port = xatol(token);
 
     if (port < 0) {
         debugs(0, DBG_PARSE_NOTE(DBG_IMPORTANT), "ERROR: The value '" << token << "' cannot be less than 0.");
         self_destruct();
     }
 
     if (port & ~0xFFFF) {
         debugs(0, DBG_PARSE_NOTE(DBG_IMPORTANT), "ERROR: The value '" << token << "' is larger than the type 'short'.");
         self_destruct();
     }
 
     return port;
 }
 
 int64_t
 GetInteger64(void)
 {
-    char *token = strtok(NULL, w_space);
+    char *token = ConfigParser::NextToken();
 
     if (token == NULL)
         self_destruct();
 
     return xatoll(token, 10);
 }
 
 /*
  * This function is different from others (e.g., GetInteger64, GetShort)
  * because it supports octal and hexadecimal numbers
  */
 int
 GetInteger(void)
 {
-    char *token = strtok(NULL, w_space);
+    char *token = ConfigParser::NextToken();
     int i;
 
     if (token == NULL)
         self_destruct();
 
     // The conversion must honor 0 and 0x prefixes, which are important for things like umask
     int64_t ret = xatoll(token, 0);
 
     i = (int) ret;
     if (ret != static_cast<int64_t>(i)) {
         debugs(0, DBG_PARSE_NOTE(DBG_IMPORTANT), "ERROR: The value '" << token << "' is larger than the type 'int'.");
         self_destruct();
     }
 
     return i;
 }
 
 /*
  * This function is similar as GetInteger() but the token might contain
  * the percentage symbol (%) and we check whether the value is in the range
@@ -198,41 +199,41 @@
 
     //if there is a % in the end of the digits, we remove it and go on.
     char* end = &token[strlen(token)-1];
     if (*end == '%') {
         *end = '\0';
     }
 
     p = xatoi(token);
 
     if (p < 0 || p > 100) {
         debugs(0, DBG_PARSE_NOTE(DBG_IMPORTANT), "ERROR: The value '" << token << "' is out of range. A percentage should be within [0, 100].");
         self_destruct();
     }
 
     return p;
 }
 
 unsigned short
 GetShort(void)
 {
-    char *token = strtok(NULL, w_space);
+    char *token = ConfigParser::NextToken();
 
     if (token == NULL)
         self_destruct();
 
     return xatos(token);
 }
 
 bool
 StringToInt(const char *s, int &result, const char **p, int base)
 {
     if (s) {
         char *ptr = 0;
         const int h = (int) strtol(s, &ptr, base);
 
         if (ptr != s && ptr) {
             result = h;
 
             if (p)
                 *p = ptr;
 

=== modified file 'src/SwapDir.cc'
--- src/SwapDir.cc	2013-02-09 00:44:07 +0000
+++ src/SwapDir.cc	2013-02-21 14:11:34 +0000
@@ -17,40 +17,41 @@
  *  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.
  *
  *  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.
  *
  */
 
 #include "squid.h"
 #include "cache_cf.h"
 #include "compat/strtoll.h"
 #include "ConfigOption.h"
+#include "ConfigParser.h"
 #include "globals.h"
 #include "Parsing.h"
 #include "SquidConfig.h"
 #include "StoreFileSystem.h"
 #include "SwapDir.h"
 #include "tools.h"
 
 SwapDir::SwapDir(char const *aType): theType(aType),
         max_size(0), min_objsize(0), max_objsize (-1),
         path(NULL), index(-1), disker(-1),
         repl(NULL), removals(0), scanned(0),
         cleanLog(NULL)
 {
     fs.blksize = 1024;
 }
 
 SwapDir::~SwapDir()
 {
     // TODO: should we delete repl?
     xfree(path);
@@ -259,41 +260,41 @@
 /* NOT performance critical. Really. Don't bother optimising for speed
  * - RBC 20030718
  */
 ConfigOption *
 SwapDir::getOptionTree() const
 {
     ConfigOptionVector *result = new ConfigOptionVector;
     result->options.push_back(new ConfigOptionAdapter<SwapDir>(*const_cast<SwapDir *>(this), &SwapDir::optionReadOnlyParse, &SwapDir::optionReadOnlyDump));
     result->options.push_back(new ConfigOptionAdapter<SwapDir>(*const_cast<SwapDir *>(this), &SwapDir::optionObjectSizeParse, &SwapDir::optionObjectSizeDump));
     return result;
 }
 
 void
 SwapDir::parseOptions(int isaReconfig)
 {
     const bool old_read_only = flags.read_only;
     char *name, *value;
 
     ConfigOption *newOption = getOptionTree();
 
-    while ((name = strtok(NULL, w_space)) != NULL) {
+    while ((name = ConfigParser::NextToken()) != NULL) {
         value = strchr(name, '=');
 
         if (value) {
             *value = '\0';	/* cut on = */
             ++value;
         }
 
         debugs(3,2, "SwapDir::parseOptions: parsing store option '" << name << "'='" << (value ? value : "") << "'");
 
         if (newOption)
             if (!newOption->parse(name, value, isaReconfig))
                 self_destruct();
     }
 
     delete newOption;
 
     /*
      * Handle notifications about reconfigured single-options with no value
      * where the removal of the option cannot be easily detected in the
      * parsing...

=== modified file 'src/acl/Acl.cc'
--- src/acl/Acl.cc	2013-05-05 08:38:06 +0000
+++ src/acl/Acl.cc	2013-05-22 16:39:42 +0000
@@ -56,44 +56,44 @@
 {
     char *nextToken;
     while ((nextToken = ConfigParser::strtokFile()) != NULL && nextToken[0] == '-') {
 
         //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::strtokFilePutBack("-i");
+        ConfigParser::TokenPutBack("-i");
 
     if (nextToken != NULL && strcmp(nextToken, "--") != 0 )
-        ConfigParser::strtokFileUndo();
+        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;
 }
 
@@ -142,58 +142,58 @@
 {
     *name = 0;
 }
 
 bool ACL::valid () const
 {
     return true;
 }
 
 void
 ACL::ParseAclLine(ConfigParser &parser, ACL ** head)
 {
     /* we're already using strtok() to grok the line */
     char *t = NULL;
     ACL *A = NULL;
     LOCAL_ARRAY(char, aclname, ACL_NAME_SZ);
     int new_acl = 0;
 
     /* snarf the ACL name */
 
-    if ((t = strtok(NULL, w_space)) == NULL) {
+    if ((t = ConfigParser::NextToken()) == NULL) {
         debugs(28, DBG_CRITICAL, "aclParseAclLine: missing ACL name.");
         parser.destruct();
         return;
     }
 
     if (strlen(t) >= ACL_NAME_SZ) {
         debugs(28, DBG_CRITICAL, "aclParseAclLine: aclParseAclLine: ACL name '" << t <<
                "' too long, max " << ACL_NAME_SZ - 1 << " characters supported");
         parser.destruct();
         return;
     }
 
     xstrncpy(aclname, t, ACL_NAME_SZ);
     /* snarf the ACL type */
     const char *theType;
 
-    if ((theType = strtok(NULL, w_space)) == NULL) {
+    if ((theType = ConfigParser::NextToken()) == NULL) {
         debugs(28, DBG_CRITICAL, "aclParseAclLine: missing ACL type.");
         parser.destruct();
         return;
     }
 
     // Is this ACL going to work?
     if (strcmp(theType, "myip") == 0) {
         AnyP::PortCfg *p = Config.Sockaddr.http;
         while (p) {
             // Bug 3239: not reliable when there is interception traffic coming
             if (p->flags.natIntercept)
                 debugs(28, DBG_CRITICAL, "WARNING: 'myip' ACL is not reliable for interception proxies. Please use 'myportname' instead.");
             p = p->next;
         }
         debugs(28, DBG_IMPORTANT, "UPGRADE: ACL 'myip' type is has been renamed to 'localip' and matches the IP the client connected to.");
         theType = "localip";
     } else if (strcmp(theType, "myport") == 0) {
         AnyP::PortCfg *p = Config.Sockaddr.http;
         while (p) {
             // Bug 3239: not reliable when there is interception traffic coming

=== modified file 'src/acl/Gadgets.cc'
--- src/acl/Gadgets.cc	2013-05-11 20:59:44 +0000
+++ src/acl/Gadgets.cc	2013-05-17 18:21:03 +0000
@@ -102,84 +102,84 @@
  *    get the info for redirecting "access denied" to info pages
  *      TODO (probably ;-)
  *      currently there is no optimization for
  *      - more than one deny_info line with the same url
  *      - a check, whether the given acl really is defined
  *      - a check, whether an acl is added more than once for the same url
  */
 
 void
 aclParseDenyInfoLine(AclDenyInfoList ** head)
 {
     char *t = NULL;
     AclDenyInfoList *A = NULL;
     AclDenyInfoList *B = NULL;
     AclDenyInfoList **T = NULL;
     AclNameList *L = NULL;
     AclNameList **Tail = NULL;
 
     /* first expect a page name */
 
-    if ((t = strtok(NULL, w_space)) == NULL) {
+    if ((t = ConfigParser::NextToken()) == NULL) {
         debugs(28, DBG_CRITICAL, "aclParseDenyInfoLine: " << cfg_filename << " line " << config_lineno << ": " << config_input_line);
         debugs(28, DBG_CRITICAL, "aclParseDenyInfoLine: missing 'error page' parameter.");
         return;
     }
 
     A = (AclDenyInfoList *)memAllocate(MEM_ACL_DENY_INFO_LIST);
     A->err_page_id = errorReservePageId(t);
     A->err_page_name = xstrdup(t);
     A->next = (AclDenyInfoList *) NULL;
     /* next expect a list of ACL names */
     Tail = &A->acl_list;
 
-    while ((t = strtok(NULL, w_space))) {
+    while ((t = ConfigParser::NextToken())) {
         L = (AclNameList *)memAllocate(MEM_ACL_NAME_LIST);
         xstrncpy(L->name, t, ACL_NAME_SZ);
         *Tail = L;
         Tail = &L->next;
     }
 
     if (A->acl_list == NULL) {
         debugs(28, DBG_CRITICAL, "aclParseDenyInfoLine: " << cfg_filename << " line " << config_lineno << ": " << config_input_line);
         debugs(28, DBG_CRITICAL, "aclParseDenyInfoLine: deny_info line contains no ACL's, skipping");
         memFree(A, MEM_ACL_DENY_INFO_LIST);
         return;
     }
 
     for (B = *head, T = head; B; T = &B->next, B = B->next)
 
         ;	/* find the tail */
     *T = A;
 }
 
 void
 aclParseAccessLine(ConfigParser &parser, acl_access ** head)
 {
     char *t = NULL;
     acl_access *A = NULL;
     acl_access *B = NULL;
     acl_access **T = NULL;
 
     /* first expect either 'allow' or 'deny' */
 
-    if ((t = strtok(NULL, w_space)) == NULL) {
+    if ((t = ConfigParser::NextToken()) == NULL) {
         debugs(28, DBG_CRITICAL, "aclParseAccessLine: " << cfg_filename << " line " << config_lineno << ": " << config_input_line);
         debugs(28, DBG_CRITICAL, "aclParseAccessLine: missing 'allow' or 'deny'.");
         return;
     }
 
     A = new acl_access;
 
     if (!strcmp(t, "allow"))
         A->allow = ACCESS_ALLOWED;
     else if (!strcmp(t, "deny"))
         A->allow = ACCESS_DENIED;
     else {
         debugs(28, DBG_CRITICAL, "aclParseAccessLine: " << cfg_filename << " line " << config_lineno << ": " << config_input_line);
         debugs(28, DBG_CRITICAL, "aclParseAccessLine: expecting 'allow' or 'deny', got '" << t << "'.");
         delete A;
         return;
     }
 
     aclParseAclList(parser, &A->aclList);
 

=== modified file 'src/adaptation/Config.cc'
--- src/adaptation/Config.cc	2013-05-04 11:50:26 +0000
+++ src/adaptation/Config.cc	2013-05-17 16:21:21 +0000
@@ -247,42 +247,41 @@
 void
 Adaptation::Config::FreeServiceGroups()
 {
     while (!AllGroups().empty()) {
         // groups are refcounted so we do not explicitly delete them
         AllGroups().pop_back();
     }
 }
 
 void
 Adaptation::Config::DumpServiceGroups(StoreEntry *entry, const char *name)
 {
     typedef Groups::iterator GI;
     for (GI i = AllGroups().begin(); i != AllGroups().end(); ++i)
         storeAppendPrintf(entry, "%s " SQUIDSTRINGPH "\n", name, SQUIDSTRINGPRINT((*i)->id));
 }
 
 void
 Adaptation::Config::ParseAccess(ConfigParser &parser)
 {
-    String groupId;
-    ConfigParser::ParseString(&groupId);
+    String groupId = ConfigParser::NextToken();
     AccessRule *r;
     if (!(r=FindRuleByGroupId(groupId))) {
         r = new AccessRule(groupId);
         AllRules().push_back(r);
     }
     r->parse(parser);
 }
 
 void
 Adaptation::Config::FreeAccess()
 {
     while (!AllRules().empty()) {
         delete AllRules().back();
         AllRules().pop_back();
     }
 }
 
 void
 Adaptation::Config::DumpAccess(StoreEntry *entry, const char *name)
 {

=== modified file 'src/adaptation/ServiceConfig.cc'
--- src/adaptation/ServiceConfig.cc	2013-03-03 07:10:22 +0000
+++ src/adaptation/ServiceConfig.cc	2013-05-17 16:21:21 +0000
@@ -44,56 +44,54 @@
 Adaptation::ServiceConfig::parseVectPoint(const char *service_configConfig) const
 {
     const char *t = service_configConfig;
     const char *q = strchr(t, '_');
 
     if (q)
         t = q + 1;
 
     if (!strcmp(t, "precache"))
         return Adaptation::pointPreCache;
 
     if (!strcmp(t, "postcache"))
         return Adaptation::pointPostCache;
 
     return Adaptation::pointNone;
 }
 
 bool
 Adaptation::ServiceConfig::parse()
 {
-    String method_point;
-
-    ConfigParser::ParseString(&key);
-    ConfigParser::ParseString(&method_point);
+    key = ConfigParser::NextToken();
+    String method_point = ConfigParser::NextToken();
     method = parseMethod(method_point.termedBuf());
     point = parseVectPoint(method_point.termedBuf());
 
     // reset optional parameters in case we are reconfiguring
     bypass = routing = false;
 
     // handle optional service name=value parameters
     bool grokkedUri = false;
     bool onOverloadSet = false;
     std::set<std::string> options;
 
-    while (char *option = strtok(NULL, w_space)) {
+    while (char *option = ConfigParser::NextToken()) {
         const char *name = option;
         const char *value = "";
         if (strcmp(option, "0") == 0) { // backward compatibility
             name = "bypass";
             value = "off";
             debugs(3, DBG_PARSE_NOTE(DBG_IMPORTANT), "UPGRADE: Please use 'bypass=off' option to disable service bypass");
         }  else if (strcmp(option, "1") == 0) { // backward compatibility
             name = "bypass";
             value = "on";
             debugs(3, DBG_PARSE_NOTE(DBG_IMPORTANT), "UPGRADE: Please use 'bypass=on' option to enable service bypass");
         } else {
             char *eq = strstr(option, "=");
             const char *sffx = strstr(option, "://");
             if (!eq || (sffx && sffx < eq)) { //no "=" or has the form "icap://host?arg=val"
                 name = "uri";
                 value = option;
             }  else { // a normal name=value option
                 *eq = '\0'; // terminate option name
                 value = eq + 1; // skip '='
             }

=== modified file 'src/adaptation/ServiceGroups.cc'
--- src/adaptation/ServiceGroups.cc	2012-08-31 16:57:39 +0000
+++ src/adaptation/ServiceGroups.cc	2013-02-05 16:36:07 +0000
@@ -6,41 +6,41 @@
 #include "adaptation/ServiceFilter.h"
 #include "adaptation/ServiceGroups.h"
 #include "ConfigParser.h"
 #include "Debug.h"
 #include "StrList.h"
 #include "wordlist.h"
 
 Adaptation::ServiceGroup::ServiceGroup(const String &aKind, bool allSame):
         kind(aKind), method(methodNone), point(pointNone),
         allServicesSame(allSame)
 {
 }
 
 Adaptation::ServiceGroup::~ServiceGroup()
 {
 }
 
 void
 Adaptation::ServiceGroup::parse()
 {
-    ConfigParser::ParseString(&id);
+    id = ConfigParser::NextToken();
 
     wordlist *names = NULL;
     ConfigParser::ParseWordList(&names);
     for (wordlist *i = names; i; i = i->next)
         services.push_back(i->key);
     wordlistDestroy(&names);
 }
 
 // Note: configuration code aside, this method is called by DynamicServiceChain
 void
 Adaptation::ServiceGroup::finalize()
 {
     // 1) warn if services have different methods or vectoring point
     // 2) warn if all-same services have different bypass status
     // 3) warn if there are seemingly identical services in the group
     // TODO: optimize by remembering ServicePointers rather than IDs
     if (!removedServices.empty()) {
         String s;
         for (Store::iterator it = removedServices.begin(); it != removedServices.end(); ++it) {
             s.append(*it);

=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc	2013-05-14 18:36:45 +0000
+++ src/cache_cf.cc	2013-05-23 18:12:58 +0000
@@ -998,50 +998,50 @@
         int cval;
         parse_int(&cval);
         debugs(3, DBG_CRITICAL, "WARNING: url_rewrite_concurrency upgrade overriding url_rewrite_children settings.");
         Config.redirectChildren.concurrency = cval;
     }
 }
 
 /* Parse a time specification from the config file.  Store the
  * result in 'tptr', after converting it to 'units' */
 static void
 parseTimeLine(time_msec_t * tptr, const char *units,  bool allowMsec)
 {
     char *token;
     double d;
     time_msec_t m;
     time_msec_t u;
 
     if ((u = parseTimeUnits(units, allowMsec)) == 0)
         self_destruct();
 
-    if ((token = strtok(NULL, w_space)) == NULL)
+    if ((token = ConfigParser::NextToken()) == NULL)
         self_destruct();
 
     d = xatof(token);
 
     m = u;			/* default to 'units' if none specified */
 
     if (0 == d)
         (void) 0;
-    else if ((token = strtok(NULL, w_space)) == NULL)
+    else if ((token = ConfigParser::NextToken()) == NULL)
         debugs(3, DBG_CRITICAL, "WARNING: No units on '" <<
                config_input_line << "', assuming " <<
                d << " " << units  );
     else if ((m = parseTimeUnits(token, allowMsec)) == 0)
         self_destruct();
 
     *tptr = static_cast<time_msec_t>(m * d);
 
     if (static_cast<double>(*tptr) * 2 != m * d * 2) {
         debugs(3, DBG_CRITICAL, "ERROR: Invalid value '" <<
                d << " " << token << ": integer overflow (time_msec_t).");
         self_destruct();
     }
 }
 
 static uint64_t
 parseTimeUnits(const char *unit, bool allowMsec)
 {
     if (allowMsec && !strncasecmp(unit, T_MILLISECOND_STR, strlen(T_MILLISECOND_STR)))
         return 1;
@@ -1074,152 +1074,152 @@
         return static_cast<uint64_t>(86400 * 1000 * 365.2522 * 10);
 
     debugs(3, DBG_IMPORTANT, "parseTimeUnits: unknown time unit '" << unit << "'");
 
     return 0;
 }
 
 static void
 parseBytesLine64(int64_t * bptr, const char *units)
 {
     char *token;
     double d;
     int64_t m;
     int64_t u;
 
     if ((u = parseBytesUnits(units)) == 0) {
         self_destruct();
         return;
     }
 
-    if ((token = strtok(NULL, w_space)) == NULL) {
+    if ((token = ConfigParser::NextToken()) == NULL) {
         self_destruct();
         return;
     }
 
     if (strcmp(token, "none") == 0 || strcmp(token, "-1") == 0) {
         *bptr = -1;
         return;
     }
 
     d = xatof(token);
 
     m = u;			/* default to 'units' if none specified */
 
     if (0.0 == d)
         (void) 0;
-    else if ((token = strtok(NULL, w_space)) == NULL)
+    else if ((token = ConfigParser::NextToken()) == NULL)
         debugs(3, DBG_CRITICAL, "WARNING: No units on '" <<
                config_input_line << "', assuming " <<
                d << " " <<  units  );
     else if ((m = parseBytesUnits(token)) == 0) {
         self_destruct();
         return;
     }
 
     *bptr = static_cast<int64_t>(m * d / u);
 
     if (static_cast<double>(*bptr) * 2 != (m * d / u) * 2) {
         debugs(3, DBG_CRITICAL, "ERROR: Invalid value '" <<
                d << " " << token << ": integer overflow (int64_t).");
         self_destruct();
     }
 }
 
 static void
 parseBytesLine(size_t * bptr, const char *units)
 {
     char *token;
     double d;
     int m;
     int u;
 
     if ((u = parseBytesUnits(units)) == 0) {
         self_destruct();
         return;
     }
 
-    if ((token = strtok(NULL, w_space)) == NULL) {
+    if ((token = ConfigParser::NextToken()) == NULL) {
         self_destruct();
         return;
     }
 
     if (strcmp(token, "none") == 0 || strcmp(token, "-1") == 0) {
         *bptr = static_cast<size_t>(-1);
         return;
     }
 
     d = xatof(token);
 
     m = u;			/* default to 'units' if none specified */
 
     if (0.0 == d)
         (void) 0;
-    else if ((token = strtok(NULL, w_space)) == NULL)
+    else if ((token = ConfigParser::NextToken()) == NULL)
         debugs(3, DBG_CRITICAL, "WARNING: No units on '" <<
                config_input_line << "', assuming " <<
                d << " " <<  units  );
     else if ((m = parseBytesUnits(token)) == 0) {
         self_destruct();
         return;
     }
 
     *bptr = static_cast<size_t>(m * d / u);
 
     if (static_cast<double>(*bptr) * 2 != (m * d / u) * 2) {
         debugs(3, DBG_CRITICAL, "ERROR: Invalid value '" <<
                d << " " << token << ": integer overflow (size_t).");
         self_destruct();
     }
 }
 
 #if !USE_DNSHELPER
 static void
 parseBytesLineSigned(ssize_t * bptr, const char *units)
 {
     char *token;
     double d;
     int m;
     int u;
 
     if ((u = parseBytesUnits(units)) == 0) {
         self_destruct();
         return;
     }
 
-    if ((token = strtok(NULL, w_space)) == NULL) {
+    if ((token = ConfigParser::NextToken()) == NULL) {
         self_destruct();
         return;
     }
 
     if (strcmp(token, "none") == 0 || token[0] == '-' /* -N */) {
         *bptr = -1;
         return;
     }
 
     d = xatof(token);
 
     m = u;			/* default to 'units' if none specified */
 
     if (0.0 == d)
         (void) 0;
-    else if ((token = strtok(NULL, w_space)) == NULL)
+    else if ((token = ConfigParser::NextToken()) == NULL)
         debugs(3, DBG_CRITICAL, "WARNING: No units on '" <<
                config_input_line << "', assuming " <<
                d << " " <<  units  );
     else if ((m = parseBytesUnits(token)) == 0) {
         self_destruct();
         return;
     }
 
     *bptr = static_cast<ssize_t>(m * d / u);
 
     if (static_cast<double>(*bptr) * 2 != (m * d / u) * 2) {
         debugs(3, DBG_CRITICAL, "ERROR: Invalid value '" <<
                d << " " << token << ": integer overflow (ssize_t).");
         self_destruct();
     }
 }
 #endif
 
 /**
  * Parse bytes from a string.
@@ -1281,40 +1281,41 @@
  * Max
  *****************************************************************************/
 
 static void
 dump_acl(StoreEntry * entry, const char *name, ACL * ae)
 {
     wordlist *w;
     wordlist *v;
 
     while (ae != NULL) {
         debugs(3, 3, "dump_acl: " << name << " " << ae->name);
         storeAppendPrintf(entry, "%s %s %s %s ",
                           name,
                           ae->name,
                           ae->typeString(),
                           ae->flags.flagsStr());
         v = w = ae->dump();
 
         while (v != NULL) {
             debugs(3, 3, "dump_acl: " << name << " " << ae->name << " " << v->key);
+            // XXX: use something like ConfigParser::QuoteString() here
             storeAppendPrintf(entry, "%s ", v->key);
             v = v->next;
         }
 
         storeAppendPrintf(entry, "\n");
         wordlistDestroy(&w);
         ae = ae->next;
     }
 }
 
 static void
 parse_acl(ACL ** ae)
 {
     ACL::ParseAclLine(LegacyParser, ae);
 }
 
 static void
 free_acl(ACL ** ae)
 {
     aclDestroyAcls(ae);
@@ -1351,41 +1352,41 @@
 {
     aclParseAccessLine(LegacyParser, head);
 }
 
 static void
 free_acl_access(acl_access ** head)
 {
     aclDestroyAccessList(head);
 }
 
 static void
 dump_address(StoreEntry * entry, const char *name, Ip::Address &addr)
 {
     char buf[MAX_IPSTRLEN];
     storeAppendPrintf(entry, "%s %s\n", name, addr.NtoA(buf,MAX_IPSTRLEN) );
 }
 
 static void
 parse_address(Ip::Address *addr)
 {
-    char *token = strtok(NULL, w_space);
+    char *token = ConfigParser::NextToken();
 
     if (!token) {
         self_destruct();
         return;
     }
 
     if (!strcmp(token,"any_addr"))
         addr->SetAnyAddr();
     else if ( (!strcmp(token,"no_addr")) || (!strcmp(token,"full_mask")) )
         addr->SetNoAddr();
     else if ( (*addr = token) ) // try parse numeric/IPA
         (void) 0;
     else
         addr->GetHostByName(token); // dont use ipcache
 }
 
 static void
 free_address(Ip::Address *addr)
 {
     addr->SetEmpty();
@@ -1459,41 +1460,41 @@
 
         dump_acl_list(entry, l->aclList);
 
         storeAppendPrintf(entry, "\n");
     }
 }
 
 static void
 freed_acl_tos(void *data)
 {
     acl_tos *l = static_cast<acl_tos *>(data);
     aclDestroyAclList(&l->aclList);
 }
 
 static void
 parse_acl_tos(acl_tos ** head)
 {
     acl_tos *l;
     acl_tos **tail = head;	/* sane name below */
     unsigned int tos;           /* Initially uint for strtoui. Casted to tos_t before return */
-    char *token = strtok(NULL, w_space);
+    char *token = ConfigParser::NextToken();
 
     if (!token) {
         self_destruct();
         return;
     }
 
     if (!xstrtoui(token, NULL, &tos, 0, std::numeric_limits<tos_t>::max())) {
         self_destruct();
         return;
     }
 
     CBDATA_INIT_TYPE_FREECB(acl_tos, freed_acl_tos);
 
     l = cbdataAlloc(acl_tos);
 
     l->tos = (tos_t)tos;
 
     aclParseAclList(LegacyParser, &l->aclList);
 
     while (*tail)
@@ -1530,41 +1531,41 @@
 
         dump_acl_list(entry, l->aclList);
 
         storeAppendPrintf(entry, "\n");
     }
 }
 
 static void
 freed_acl_nfmark(void *data)
 {
     acl_nfmark *l = static_cast<acl_nfmark *>(data);
     aclDestroyAclList(&l->aclList);
 }
 
 static void
 parse_acl_nfmark(acl_nfmark ** head)
 {
     acl_nfmark *l;
     acl_nfmark **tail = head;	/* sane name below */
     nfmark_t mark;
-    char *token = strtok(NULL, w_space);
+    char *token = ConfigParser::NextToken();
 
     if (!token) {
         self_destruct();
         return;
     }
 
     if (!xstrtoui(token, NULL, &mark, 0, std::numeric_limits<nfmark_t>::max())) {
         self_destruct();
         return;
     }
 
     CBDATA_INIT_TYPE_FREECB(acl_nfmark, freed_acl_nfmark);
 
     l = cbdataAlloc(acl_nfmark);
 
     l->nfmark = mark;
 
     aclParseAclList(LegacyParser, &l->aclList);
 
     while (*tail)
@@ -1732,77 +1733,77 @@
 static void
 parse_client_delay_pool_access(ClientDelayConfig * cfg)
 {
     cfg->parsePoolAccess(LegacyParser);
 }
 #endif
 
 #if USE_HTTP_VIOLATIONS
 static void
 dump_http_header_access(StoreEntry * entry, const char *name, const HeaderManglers *manglers)
 {
     if (manglers)
         manglers->dumpAccess(entry, name);
 }
 
 static void
 parse_http_header_access(HeaderManglers **pm)
 {
     char *t = NULL;
 
-    if ((t = strtok(NULL, w_space)) == 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_access: missing header name.");
         return;
     }
 
     if (!*pm)
         *pm = new HeaderManglers;
     HeaderManglers *manglers = *pm;
     headerMangler *mangler = manglers->track(t);
     assert(mangler);
     parse_acl_access(&mangler->access_list);
 }
 
 static void
 free_HeaderManglers(HeaderManglers **pm)
 {
     // we delete the entire http_header_* mangler configuration at once
     if (const HeaderManglers *manglers = *pm) {
         delete manglers;
         *pm = NULL;
     }
 }
 
 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 = strtok(NULL, w_space)) == 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;
 
     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;
@@ -1813,44 +1814,44 @@
         if (!s) continue;
         storeAppendPrintf(entry, "%s %s %s", name, s->type(), s->path);
         s->dump(*entry);
         storeAppendPrintf(entry, "\n");
     }
 }
 
 static int
 check_null_string(char *s)
 {
     return s == NULL;
 }
 
 #if USE_AUTH
 static void
 parse_authparam(Auth::ConfigVector * config)
 {
     char *type_str;
     char *param_str;
 
-    if ((type_str = strtok(NULL, w_space)) == NULL)
+    if ((type_str = ConfigParser::NextToken()) == NULL)
         self_destruct();
 
-    if ((param_str = strtok(NULL, w_space)) == NULL)
+    if ((param_str = ConfigParser::NextToken()) == NULL)
         self_destruct();
 
     /* find a configuration for the scheme in the currently parsed configs... */
     Auth::Config *schemeCfg = Auth::Config::Find(type_str);
 
     if (schemeCfg == NULL) {
         /* Create a configuration based on the scheme info */
         Auth::Scheme::Pointer theScheme = Auth::Scheme::Find(type_str);
 
         if (theScheme == NULL) {
             debugs(3, DBG_CRITICAL, "Parsing Config File: Unknown authentication scheme '" << type_str << "'.");
             self_destruct();
         }
 
         config->push_back(theScheme->createConfig());
         schemeCfg = Auth::Config::Find(type_str);
         if (schemeCfg == NULL) {
             debugs(3, DBG_CRITICAL, "Parsing Config File: Corruption configuring authentication scheme '" << type_str << "'.");
             self_destruct();
         }
@@ -1887,44 +1888,44 @@
 /* TODO: just return the object, the # is irrelevant */
 static int
 find_fstype(char *type)
 {
     for (size_t i = 0; i < StoreFileSystem::FileSystems().size(); ++i)
         if (strcasecmp(type, StoreFileSystem::FileSystems().items[i]->type()) == 0)
             return (int)i;
 
     return (-1);
 }
 
 static void
 parse_cachedir(SquidConfig::_cacheSwap * swap)
 {
     char *type_str;
     char *path_str;
     RefCount<SwapDir> sd;
     int i;
     int fs;
 
-    if ((type_str = strtok(NULL, w_space)) == NULL)
+    if ((type_str = ConfigParser::NextToken()) == NULL)
         self_destruct();
 
-    if ((path_str = strtok(NULL, w_space)) == NULL)
+    if ((path_str = ConfigParser::NextToken()) == NULL)
         self_destruct();
 
     fs = find_fstype(type_str);
 
     if (fs < 0) {
         debugs(3, DBG_PARSE_NOTE(DBG_IMPORTANT), "ERROR: This proxy does not support the '" << type_str << "' cache type. Ignoring.");
         return;
     }
 
     /* reconfigure existing dir */
 
     for (i = 0; i < swap->n_configured; ++i) {
         assert (swap->swapDirs[i].getRaw());
 
         if ((strcasecmp(path_str, dynamic_cast<SwapDir *>(swap->swapDirs[i].getRaw())->path)) == 0) {
             /* this is specific to on-fs Stores. The right
              * way to handle this is probably to have a mapping
              * from paths to stores, and have on-fs stores
              * register with that, and lookip in that in their
              * own setup logic. RBC 20041225. TODO.
@@ -2045,41 +2046,41 @@
 isUnsignedNumeric(const char *str, size_t len)
 {
     if (len < 1) return false;
 
     for (; len >0 && *str; ++str, --len) {
         if (! isdigit(*str))
             return false;
     }
     return true;
 }
 
 /**
  \param proto	'tcp' or 'udp' for protocol
  \returns       Port the named service is supposed to be listening on.
  */
 static unsigned short
 GetService(const char *proto)
 {
     struct servent *port = NULL;
     /** Parses a port number or service name from the squid.conf */
-    char *token = strtok(NULL, w_space);
+    char *token = ConfigParser::NextToken();
     if (token == NULL) {
         self_destruct();
         return 0; /* NEVER REACHED */
     }
     /** Returns either the service port number from /etc/services */
     if ( !isUnsignedNumeric(token, strlen(token)) )
         port = getservbyname(token, proto);
     if (port != NULL) {
         return ntohs((unsigned short)port->s_port);
     }
     /** Or a numeric translation of the config text. */
     return xatos(token);
 }
 
 /**
  \returns       Port the named TCP service is supposed to be listening on.
  \copydoc GetService(const char *proto)
  */
 inline unsigned short
 GetTcpService(void)
@@ -2093,66 +2094,66 @@
  */
 inline unsigned short
 GetUdpService(void)
 {
     return GetService("udp");
 }
 
 static void
 parse_peer(CachePeer ** head)
 {
     char *token = NULL;
     CachePeer *p;
     CBDATA_INIT_TYPE_FREECB(CachePeer, peerDestroy);
     p = cbdataAlloc(CachePeer);
     p->http_port = CACHE_HTTP_PORT;
     p->icp.port = CACHE_ICP_PORT;
     p->weight = 1;
     p->basetime = 0;
     p->stats.logged_state = PEER_ALIVE;
 
-    if ((token = strtok(NULL, w_space)) == NULL)
+    if ((token = ConfigParser::NextToken()) == NULL)
         self_destruct();
 
     p->host = xstrdup(token);
 
     p->name = xstrdup(token);
 
-    if ((token = strtok(NULL, w_space)) == NULL)
+    if ((token = ConfigParser::NextToken()) == NULL)
         self_destruct();
 
     p->type = parseNeighborType(token);
 
     if (p->type == PEER_MULTICAST) {
         p->options.no_digest = true;
         p->options.no_netdb_exchange = true;
     }
 
     p->http_port = GetTcpService();
 
     if (!p->http_port)
         self_destruct();
 
     p->icp.port = GetUdpService();
     p->connection_auth = 2;    /* auto */
 
-    while ((token = strtok(NULL, w_space))) {
+    while ((token = ConfigParser::NextToken())) {
         if (!strcmp(token, "proxy-only")) {
             p->options.proxy_only = true;
         } else if (!strcmp(token, "no-query")) {
             p->options.no_query = true;
         } else if (!strcmp(token, "background-ping")) {
             p->options.background_ping = true;
         } else if (!strcmp(token, "no-digest")) {
             p->options.no_digest = true;
         } else if (!strcmp(token, "no-tproxy")) {
             p->options.no_tproxy = true;
         } else if (!strcmp(token, "multicast-responder")) {
             p->options.mcast_responder = true;
 #if PEER_MULTICAST_SIBLINGS
         } else if (!strcmp(token, "multicast-siblings")) {
             p->options.mcast_siblings = true;
 #endif
         } else if (!strncmp(token, "weight=", 7)) {
             p->weight = xatoi(token + 7);
         } else if (!strncmp(token, "basetime=", 9)) {
             p->basetime = xatoi(token + 9);
@@ -2502,99 +2503,99 @@
         for (l = a->acl_list; l; l = l_next) {
             l_next = l->next;
             memFree(l, MEM_ACL_NAME_LIST);
             l = NULL;
         }
 
         a_next = a->next;
         memFree(a, MEM_ACL_DENY_INFO_LIST);
         a = NULL;
     }
 
     *list = NULL;
 }
 
 static void
 parse_peer_access(void)
 {
     char *host = NULL;
     CachePeer *p;
 
-    if (!(host = strtok(NULL, w_space)))
+    if (!(host = ConfigParser::NextToken()))
         self_destruct();
 
     if ((p = peerFindByName(host)) == NULL) {
         debugs(15, DBG_CRITICAL, "" << cfg_filename << ", line " << config_lineno << ": No cache_peer '" << host << "'");
         return;
     }
 
     aclParseAccessLine(LegacyParser, &p->access);
 }
 
 static void
 parse_hostdomain(void)
 {
     char *host = NULL;
     char *domain = NULL;
 
-    if (!(host = strtok(NULL, w_space)))
+    if (!(host = ConfigParser::NextToken()))
         self_destruct();
 
-    while ((domain = strtok(NULL, list_sep))) {
+    while ((domain = ConfigParser::NextToken())) {
         CachePeerDomainList *l = NULL;
         CachePeerDomainList **L = NULL;
         CachePeer *p;
 
         if ((p = peerFindByName(host)) == NULL) {
             debugs(15, DBG_CRITICAL, "" << cfg_filename << ", line " << config_lineno << ": No cache_peer '" << host << "'");
             continue;
         }
 
         l = static_cast<CachePeerDomainList *>(xcalloc(1, sizeof(CachePeerDomainList)));
         l->do_ping = true;
 
         if (*domain == '!') {	/* check for !.edu */
             l->do_ping = false;
             ++domain;
         }
 
         l->domain = xstrdup(domain);
 
         for (L = &(p->peer_domain); *L; L = &((*L)->next));
         *L = l;
     }
 }
 
 static void
 parse_hostdomaintype(void)
 {
     char *host = NULL;
     char *type = NULL;
     char *domain = NULL;
 
-    if (!(host = strtok(NULL, w_space)))
+    if (!(host = ConfigParser::NextToken()))
         self_destruct();
 
-    if (!(type = strtok(NULL, w_space)))
+    if (!(type = ConfigParser::NextToken()))
         self_destruct();
 
-    while ((domain = strtok(NULL, list_sep))) {
+    while ((domain = ConfigParser::NextToken())) {
         NeighborTypeDomainList *l = NULL;
         NeighborTypeDomainList **L = NULL;
         CachePeer *p;
 
         if ((p = peerFindByName(host)) == NULL) {
             debugs(15, DBG_CRITICAL, "" << cfg_filename << ", line " << config_lineno << ": No cache_peer '" << host << "'");
             return;
         }
 
         l = static_cast<NeighborTypeDomainList *>(xcalloc(1, sizeof(NeighborTypeDomainList)));
         l->type = parseNeighborType(type);
         l->domain = xstrdup(domain);
 
         for (L = &(p->typelist); *L; L = &((*L)->next));
         *L = l;
     }
 }
 
 static void
 dump_int(StoreEntry * entry, const char *name, int var)
@@ -2608,82 +2609,82 @@
     int i;
     i = GetInteger();
     *var = i;
 }
 
 static void
 free_int(int *var)
 {
     *var = 0;
 }
 
 static void
 dump_onoff(StoreEntry * entry, const char *name, int var)
 {
     storeAppendPrintf(entry, "%s %s\n", name, var ? "on" : "off");
 }
 
 void
 parse_onoff(int *var)
 {
-    char *token = strtok(NULL, w_space);
+    char *token = ConfigParser::NextToken();
 
     if (token == NULL)
         self_destruct();
 
     if (!strcmp(token, "on")) {
         *var = 1;
     } else if (!strcmp(token, "enable")) {
         debugs(0, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: 'enable' is deprecated. Please update to use 'on'.");
         *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 'off'.");
         *var = 0;
     } else {
         debugs(0, DBG_PARSE_NOTE(DBG_IMPORTANT), "ERROR: Invalid option: Boolean options can only be 'on' or 'off'.");
         self_destruct();
     }
 }
 
 #define free_onoff free_int
 
 static void
 dump_tristate(StoreEntry * entry, const char *name, int var)
 {
     const char *state;
 
     if (var > 0)
         state = "on";
     else if (var < 0)
         state = "warn";
     else
         state = "off";
 
     storeAppendPrintf(entry, "%s %s\n", name, state);
 }
 
 static void
 parse_tristate(int *var)
 {
-    char *token = strtok(NULL, w_space);
+    char *token = ConfigParser::NextToken();
 
     if (token == NULL)
         self_destruct();
 
     if (!strcmp(token, "on")) {
         *var = 1;
     } else if (!strcmp(token, "enable")) {
         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();
     }
@@ -2759,51 +2760,51 @@
     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 = strtok(NULL, w_space)) == NULL) {
+    if ((token = ConfigParser::NextToken()) == NULL) {
         self_destruct();
         return;
     }
 
     if (strcmp(token, "-i") == 0) {
         flags |= REG_ICASE;
-        token = strtok(NULL, w_space);
+        token = ConfigParser::NextToken();
     } else if (strcmp(token, "+i") == 0) {
         flags &= ~REG_ICASE;
-        token = strtok(NULL, w_space);
+        token = ConfigParser::NextToken();
     }
 
     if (token == NULL) {
         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) {
         debugs(3, DBG_IMPORTANT, "WARNING: refresh_pattern minimum age too high. Cropped back to 1 year.");
         i = 60*24*365;
     }
@@ -2812,41 +2813,41 @@
 
     i = GetPercentage();	/* token: pct */
 
     pct = (double) i / 100.0;
 
     i = GetInteger();		/* token: max */
 
     /* catch negative and insanely huge values close to 32-bit wrap */
     if (i < 0) {
         debugs(3, DBG_IMPORTANT, "WARNING: refresh_pattern maximum age negative. Cropped back to zero.");
         i = 0;
     }
     if (i > 60*24*365) {
         debugs(3, DBG_IMPORTANT, "WARNING: refresh_pattern maximum age too high. Cropped back to 1 year.");
         i = 60*24*365;
     }
 
     max = (time_t) (i * 60);	/* convert minutes to seconds */
 
     /* Options */
-    while ((token = strtok(NULL, w_space)) != NULL) {
+    while ((token = ConfigParser::NextToken()) != NULL) {
         if (!strcmp(token, "refresh-ims")) {
             refresh_ims = 1;
         } else if (!strcmp(token, "store-stale")) {
             store_stale = 1;
         } else if (!strncmp(token, "max-stale=", 10)) {
             max_stale = xatoi(token + 10);
 #if USE_HTTP_VIOLATIONS
 
         } else if (!strcmp(token, "override-expire"))
             override_expire = 1;
         else if (!strcmp(token, "override-lastmod"))
             override_lastmod = 1;
         else if (!strcmp(token, "ignore-no-store"))
             ignore_no_store = 1;
         else if (!strcmp(token, "ignore-must-revalidate"))
             ignore_must_revalidate = 1;
         else if (!strcmp(token, "ignore-private"))
             ignore_private = 1;
         else if (!strcmp(token, "ignore-auth"))
             ignore_auth = 1;
@@ -2943,81 +2944,64 @@
         regfree(&t->compiled_pattern);
         safe_free(t);
     }
 
 #if USE_HTTP_VIOLATIONS
     refresh_nocache_hack = 0;
 
 #endif
 }
 
 static void
 dump_string(StoreEntry * entry, const char *name, char *var)
 {
     if (var != NULL)
         storeAppendPrintf(entry, "%s %s\n", name, var);
 }
 
 static void
 parse_string(char **var)
 {
-    char *token = strtok(NULL, w_space);
+    char *token = ConfigParser::NextToken();
     safe_free(*var);
 
     if (token == NULL)
         self_destruct();
 
     *var = xstrdup(token);
 }
 
-void
-ConfigParser::ParseString(char **var)
-{
-    parse_string(var);
-}
-
-void
-ConfigParser::ParseString(String *var)
-{
-    char *token = strtok(NULL, w_space);
-
-    if (token == NULL)
-        self_destruct();
-
-    var->reset(token);
-}
-
 static void
 free_string(char **var)
 {
     safe_free(*var);
 }
 
 void
 parse_eol(char *volatile *var)
 {
     if (!var) {
         self_destruct();
         return;
     }
 
-    unsigned char *token = (unsigned char *) strtok(NULL, null_string);
+    unsigned char *token = (unsigned char *) ConfigParser::NextQuotedOrToEol();
     safe_free(*var);
 
     if (!token) {
         self_destruct();
         return;
     }
 
     while (*token && xisspace(*token))
         ++token;
 
     if (!*token) {
         self_destruct();
         return;
     }
 
     *var = xstrdup((char *) token);
 }
 
 #define dump_eol dump_string
 #define free_eol free_string
@@ -3217,68 +3201,66 @@
 
 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;
-    char *t = strtok(NULL, "");
-
-    while ((token = strwordtok(NULL, &t)))
+    while ((token = ConfigParser::NextToken()))
         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
 
 static void
 parse_uri_whitespace(int *var)
 {
-    char *token = strtok(NULL, w_space);
+    char *token = ConfigParser::NextToken();
 
     if (token == NULL)
         self_destruct();
 
     if (!strcmp(token, "strip"))
         *var = URI_WHITESPACE_STRIP;
     else if (!strcmp(token, "deny"))
         *var = URI_WHITESPACE_DENY;
     else if (!strcmp(token, "allow"))
         *var = URI_WHITESPACE_ALLOW;
     else if (!strcmp(token, "encode"))
         *var = URI_WHITESPACE_ENCODE;
     else if (!strcmp(token, "chop"))
         *var = URI_WHITESPACE_CHOP;
     else {
         debugs(0, DBG_PARSE_NOTE(2), "ERROR: Invalid option '" << token << "': 'uri_whitespace' accepts 'strip', 'deny', 'allow', 'encode', and 'chop'.");
         self_destruct();
     }
 }
 
@@ -3357,41 +3339,41 @@
     parse_onoff(&value);
     option->configure(value > 0);
 }
 
 static void
 dump_YesNoNone(StoreEntry * entry, const char *name, YesNoNone &option)
 {
     if (option.configured())
         dump_onoff(entry, name, option ? 1 : 0);
 }
 
 static void
 free_memcachemode(SquidConfig * config)
 {
     return;
 }
 
 static void
 parse_memcachemode(SquidConfig * config)
 {
-    char *token = strtok(NULL, w_space);
+    char *token = ConfigParser::NextToken();
     if (!token)
         self_destruct();
 
     if (strcmp(token, "always") == 0) {
         Config.onoff.memory_cache_first = 1;
         Config.onoff.memory_cache_disk = 1;
     } else if (strcmp(token, "disk") == 0) {
         Config.onoff.memory_cache_first = 0;
         Config.onoff.memory_cache_disk = 1;
     } else if (strncmp(token, "net", 3) == 0) {
         Config.onoff.memory_cache_first = 1;
         Config.onoff.memory_cache_disk = 0;
     } else if (strcmp(token, "never") == 0) {
         Config.onoff.memory_cache_first = 0;
         Config.onoff.memory_cache_disk = 0;
     } else {
         debugs(0, DBG_PARSE_NOTE(2), "ERROR: Invalid option '" << token << "': 'memory_cache_mode' accepts 'always', 'disk', 'network', and 'never'.");
         self_destruct();
     }
 }
@@ -3427,41 +3409,41 @@
 
     if (!strcmp(s, "sibling"))
         return PEER_SIBLING;
 
     if (!strcmp(s, "multicast"))
         return PEER_MULTICAST;
 
     debugs(15, DBG_CRITICAL, "WARNING: Unknown neighbor type: " << s);
 
     return PEER_SIBLING;
 }
 
 #if USE_WCCPv2
 static void
 parse_IpAddress_list(Ip::Address_list ** head)
 {
     char *token;
     Ip::Address_list *s;
     Ip::Address ipa;
 
-    while ((token = strtok(NULL, w_space))) {
+    while ((token = ConfigParser::NextToken())) {
         if (GetHostWithPort(token, &ipa)) {
 
             while (*head)
                 head = &(*head)->next;
 
             s = static_cast<Ip::Address_list *>(xcalloc(1, sizeof(*s)));
             s->s = ipa;
 
             *head = s;
         } else
             self_destruct();
     }
 }
 
 static void
 dump_IpAddress_list(StoreEntry * e, const char *n, const Ip::Address_list * s)
 {
     char ntoabuf[MAX_IPSTRLEN];
 
     while (s) {
@@ -3776,52 +3758,52 @@
     assert(s->next == NULL);
     s->next = cbdataReference(Config.Sockaddr.http);
     cbdataReferenceDone(Config.Sockaddr.http);
     Config.Sockaddr.http = cbdataReference(s);
 }
 
 static void
 parsePortCfg(AnyP::PortCfg ** head, const char *optionName)
 {
     const char *protocol = NULL;
     if (strcmp(optionName, "http_port") == 0 ||
             strcmp(optionName, "ascii_port") == 0)
         protocol = "http";
     else if (strcmp(optionName, "https_port") == 0)
         protocol = "https";
     if (!protocol) {
         self_destruct();
         return;
     }
 
-    char *token = strtok(NULL, w_space);
+    char *token = ConfigParser::NextToken();
 
     if (!token) {
         self_destruct();
         return;
     }
 
     AnyP::PortCfg *s = new AnyP::PortCfg(protocol);
     parsePortSpecification(s, token);
 
     /* parse options ... */
-    while ((token = strtok(NULL, w_space))) {
+    while ((token = ConfigParser::NextToken())) {
         parse_port_option(s, token);
     }
 
 #if USE_SSL
     if (strcmp(protocol, "https") == 0) {
         /* ssl-bump on https_port configuration requires either tproxy or intercept, and vice versa */
         const bool hijacked = s->flags.isIntercepted();
         if (s->flags.tunnelSslBumping && !hijacked) {
             debugs(3, DBG_CRITICAL, "FATAL: ssl-bump on https_port requires tproxy/intercept which is missing.");
             self_destruct();
         }
         if (hijacked && !s->flags.tunnelSslBumping) {
             debugs(3, DBG_CRITICAL, "FATAL: tproxy/intercept on https_port requires ssl-bump which is missing.");
             self_destruct();
         }
     }
 #endif
 
     if (Ip::EnableIpv6&IPV6_SPECIAL_SPLITSTACK && s->s.IsAnyAddr()) {
         // clone the port options from *s to *(s->next)
@@ -4035,85 +4017,85 @@
  * If no explicit logformat name is given, the first ACL name, if any,
  * should not be an existing logformat name or it will be treated as such.
  * access_log module:place [logformat_name] [acl ...]
  *
  * #4: Configurable logging module with name=value options such as logformat=x:
  * The first ACL name may not contain '='.
  * access_log module:place [option ...] [acl ...]
  *
  */
 static void
 parse_access_log(CustomLog ** logs)
 {
     CustomLog *cl = (CustomLog *)xcalloc(1, sizeof(*cl));
 
     // default buffer size and fatal settings
     cl->bufferSize = 8*MAX_URL;
     cl->fatal = true;
 
     /* determine configuration style */
 
-    const char *filename = strtok(NULL, w_space);
+    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);
         while (*logs)
             logs = &(*logs)->next;
         *logs = cl;
         return;
     }
 
     cl->filename = xstrdup(filename);
     cl->type = Log::Format::CLF_UNKNOWN;
 
     const char *token = ConfigParser::strtokFile();
     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::strtokFileUndo();
+            ConfigParser::TokenUndo();
     } 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::strtokFileUndo();
+                ConfigParser::TokenUndo();
                 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);
     }
 
     // set format if it has not been specified explicitly
     if (cl->type == Log::Format::CLF_UNKNOWN)
         setLogformat(cl, "squid", true);
 
     aclParseAclList(LegacyParser, &cl->aclList);
 
     while (*logs)
         logs = &(*logs)->next;
 
     *logs = cl;
 }
 
@@ -4272,42 +4254,42 @@
                 if (*data == '\0' || *data != ',')
                     break;
             }
         }
     }
     return data && *data == '\0';
 }
 
 static void
 parse_CpuAffinityMap(CpuAffinityMap **const cpuAffinityMap)
 {
 #if !HAVE_CPU_AFFINITY
     debugs(3, DBG_CRITICAL, "FATAL: Squid built with no CPU affinity " <<
            "support, do not set 'cpu_affinity_map'");
     self_destruct();
 #endif /* HAVE_CPU_AFFINITY */
 
     if (!*cpuAffinityMap)
         *cpuAffinityMap = new CpuAffinityMap;
 
-    const char *const pToken = strtok(NULL, w_space);
-    const char *const cToken = strtok(NULL, w_space);
+    const char *const pToken = ConfigParser::NextToken();
+    const char *const cToken = ConfigParser::NextToken();
     Vector<int> processes, cores;
     if (!parseNamedIntList(pToken, "process_numbers", processes)) {
         debugs(3, DBG_CRITICAL, "FATAL: bad 'process_numbers' parameter " <<
                "in 'cpu_affinity_map'");
         self_destruct();
     } else if (!parseNamedIntList(cToken, "cores", cores)) {
         debugs(3, DBG_CRITICAL, "FATAL: bad 'cores' parameter in " <<
                "'cpu_affinity_map'");
         self_destruct();
     } else if (!(*cpuAffinityMap)->add(processes, cores)) {
         debugs(3, DBG_CRITICAL, "FATAL: bad 'cpu_affinity_map'; " <<
                "process_numbers and cores lists differ in length or " <<
                "contain numbers <= 0");
         self_destruct();
     }
 }
 
 static void
 dump_CpuAffinityMap(StoreEntry *const entry, const char *const name, const CpuAffinityMap *const cpuAffinityMap)
 {
@@ -4405,89 +4387,89 @@
 {
     cfg->freeService();
 }
 
 static void
 dump_ecap_service_type(StoreEntry * entry, const char *name, const Adaptation::Ecap::Config &cfg)
 {
     cfg.dumpService(entry, name);
 }
 
 #endif /* USE_ECAP */
 
 #if ICAP_CLIENT
 static void parse_icap_service_failure_limit(Adaptation::Icap::Config *cfg)
 {
     char *token;
     time_t d;
     time_t m;
     cfg->service_failure_limit = GetInteger();
 
-    if ((token = strtok(NULL, w_space)) == NULL)
+    if ((token = ConfigParser::NextToken()) == NULL)
         return;
 
     if (strcmp(token,"in") != 0) {
         debugs(3, DBG_CRITICAL, "expecting 'in' on'"  << config_input_line << "'");
         self_destruct();
     }
 
-    if ((token = strtok(NULL, w_space)) == NULL) {
+    if ((token = ConfigParser::NextToken()) == NULL) {
         self_destruct();
     }
 
     d = static_cast<time_t> (xatoi(token));
 
     m = static_cast<time_t> (1);
 
     if (0 == d)
         (void) 0;
-    else if ((token = strtok(NULL, w_space)) == NULL) {
+    else if ((token = ConfigParser::NextToken()) == NULL) {
         debugs(3, DBG_CRITICAL, "No time-units on '" << config_input_line << "'");
         self_destruct();
     } else if ((m = parseTimeUnits(token, false)) == 0)
         self_destruct();
 
     cfg->oldest_service_failure = (m * d);
 }
 
 static void dump_icap_service_failure_limit(StoreEntry *entry, const char *name, const Adaptation::Icap::Config &cfg)
 {
     storeAppendPrintf(entry, "%s %d", name, cfg.service_failure_limit);
     if (cfg.oldest_service_failure > 0) {
         storeAppendPrintf(entry, " in %d seconds", (int)cfg.oldest_service_failure);
     }
     storeAppendPrintf(entry, "\n");
 }
 
 static void free_icap_service_failure_limit(Adaptation::Icap::Config *cfg)
 {
     cfg->oldest_service_failure = 0;
     cfg->service_failure_limit = 0;
 }
 #endif
 
 #if USE_SSL
 static void parse_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt)
 {
     char *al;
     sslproxy_cert_adapt *ca = (sslproxy_cert_adapt *) xcalloc(1, sizeof(sslproxy_cert_adapt));
-    if ((al = strtok(NULL, w_space)) == NULL) {
+    if ((al = ConfigParser::NextToken()) == NULL) {
         self_destruct();
         return;
     }
 
     const char *param;
     if ( char *s = strchr(al, '{')) {
         *s = '\0'; // terminate the al string
         ++s;
         param = s;
         s = strchr(s, '}');
         if (!s) {
             self_destruct();
             return;
         }
         *s = '\0';
     } else
         param = NULL;
 
     if (strcmp(al, Ssl::CertAdaptAlgorithmStr[Ssl::algSetValidAfter]) == 0) {
         ca->alg = Ssl::algSetValidAfter;
@@ -4531,41 +4513,41 @@
 }
 
 static void free_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt)
 {
     while (*cert_adapt) {
         sslproxy_cert_adapt *ca = *cert_adapt;
         *cert_adapt = ca->next;
         safe_free(ca->param);
 
         if (ca->aclList)
             aclDestroyAclList(&ca->aclList);
 
         safe_free(ca);
     }
 }
 
 static void parse_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign)
 {
     char *al;
     sslproxy_cert_sign *cs = (sslproxy_cert_sign *) xcalloc(1, sizeof(sslproxy_cert_sign));
-    if ((al = strtok(NULL, w_space)) == NULL) {
+    if ((al = ConfigParser::NextToken()) == NULL) {
         self_destruct();
         return;
     }
 
     if (strcmp(al, Ssl::CertSignAlgorithmStr[Ssl::algSignTrusted]) == 0)
         cs->alg = Ssl::algSignTrusted;
     else if (strcmp(al, Ssl::CertSignAlgorithmStr[Ssl::algSignUntrusted]) == 0)
         cs->alg = Ssl::algSignUntrusted;
     else if (strcmp(al, Ssl::CertSignAlgorithmStr[Ssl::algSignSelf]) == 0)
         cs->alg = Ssl::algSignSelf;
     else {
         debugs(3, DBG_CRITICAL, "FATAL: sslproxy_cert_sign: unknown cert signing algorithm: " << al);
         self_destruct();
         return;
     }
 
     aclParseAclList(LegacyParser, &cs->aclList);
 
     while (*cert_sign)
         cert_sign = &(*cert_sign)->next;
@@ -4623,41 +4605,41 @@
         } else {
             strcpy(buf, "ssl_bump allow all");
             debugs(3, DBG_CRITICAL, "SECURITY NOTICE: auto-converting deprecated implicit "
                    "\"ssl_bump allow all\" to \"ssl_bump client-first all\" which is usually "
                    "inferior to the newer server-first bumping mode. New ssl_bump"
                    " configurations must not use implicit rules. Update your ssl_bump rules.");
         }
         parse_line(buf);
     }
 }
 
 static void parse_sslproxy_ssl_bump(acl_access **ssl_bump)
 {
     typedef const char *BumpCfgStyle;
     BumpCfgStyle bcsNone = NULL;
     BumpCfgStyle bcsNew = "new client/server-first/none";
     BumpCfgStyle bcsOld = "deprecated allow/deny";
     static BumpCfgStyle bumpCfgStyleLast = bcsNone;
     BumpCfgStyle bumpCfgStyleNow = bcsNone;
     char *bm;
-    if ((bm = strtok(NULL, w_space)) == NULL) {
+    if ((bm = ConfigParser::NextToken()) == NULL) {
         self_destruct();
         return;
     }
 
     // if this is the first rule proccessed
     if (*ssl_bump == NULL) {
         bumpCfgStyleLast = bcsNone;
         sslBumpCfgRr::lastDeprecatedRule = Ssl::bumpEnd;
     }
 
     acl_access *A = new acl_access;
     A->allow = allow_t(ACCESS_ALLOWED);
 
     if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpClientFirst]) == 0) {
         A->allow.kind = Ssl::bumpClientFirst;
         bumpCfgStyleNow = bcsNew;
     } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpServerFirst]) == 0) {
         A->allow.kind = Ssl::bumpServerFirst;
         bumpCfgStyleNow = bcsNew;
     } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpNone]) == 0) {
@@ -4722,63 +4704,64 @@
 static void dump_HeaderWithAclList(StoreEntry * entry, const char *name, HeaderWithAclList *headers)
 {
     if (!headers)
         return;
 
     for (HeaderWithAclList::iterator hwa = headers->begin(); hwa != headers->end(); ++hwa) {
         storeAppendPrintf(entry, "%s ", hwa->fieldName.c_str());
         storeAppendPrintf(entry, "%s ", hwa->fieldValue.c_str());
         if (hwa->aclList)
             dump_acl_list(entry, hwa->aclList);
         storeAppendPrintf(entry, "\n");
     }
 }
 
 static void parse_HeaderWithAclList(HeaderWithAclList **headers)
 {
     char *fn;
     if (!*headers) {
         *headers = new HeaderWithAclList;
     }
-    if ((fn = strtok(NULL, w_space)) == NULL) {
+    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;
 
-    String buf;
-    bool wasQuoted;
-    ConfigParser::ParseQuotedString(&buf, &wasQuoted);
+    Format::Format *nlf =  new ::Format::Format("hdrWithAcl");
+    ConfigParser::SetMacroUser(nlf);
+    String buf = ConfigParser::NextToken();
+    ConfigParser::SetMacroUser(NULL);
     hwa.fieldValue = buf.termedBuf();
-    hwa.quoted = wasQuoted;
+    hwa.quoted = ConfigParser::LastTokenWasQuoted();
     if (hwa.quoted) {
-        Format::Format *nlf =  new ::Format::Format("hdrWithAcl");
         if (!nlf->parse(hwa.fieldValue.c_str())) {
             self_destruct();
             return;
         }
         hwa.valueFormat = nlf;
-    }
+    } else
+        delete nlf;
     aclParseAclList(LegacyParser, &hwa.aclList);
     (*headers)->push_back(hwa);
 }
 
 static void free_HeaderWithAclList(HeaderWithAclList **header)
 {
     if (!(*header))
         return;
 
     for (HeaderWithAclList::iterator hwa = (*header)->begin(); hwa != (*header)->end(); ++hwa) {
         if (hwa->aclList)
             aclDestroyAclList(&hwa->aclList);
 
         if (hwa->valueFormat) {
             delete hwa->valueFormat;
             hwa->valueFormat = NULL;
         }
     }
     delete *header;
     *header = NULL;

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre	2013-05-14 17:53:18 +0000
+++ src/cf.data.pre	2013-05-23 17:29:59 +0000
@@ -48,40 +48,59 @@
 	while in other cases it refers to the value of the option
 	- the comments for that keyword indicate if this is the case.
 
 COMMENT_END
 
 COMMENT_START
   Configuration options can be included using the "include" directive.
   Include takes a list of files to include. Quoting and wildcards are
   supported.
 
   For example,
 
   include /path/to/included/file/squid.acl.config
 
   Includes can be nested up to a hard-coded depth of 16 levels.
   This arbitrary restriction is to prevent recursive include references
   from causing Squid entering an infinite loop whilst trying to load
   configuration files.
 
 
+  Values with spaces, quotes, and other special characters
+
+	Squid supports directive parameters with spaces, quotes, and other
+	special characters. Surround such parameters with "double quotes". Use
+	the configuration_includes_quoted_values directive to enable or
+	disable that support.
+
+	Squid supports reading configuration option parameters from external
+	files using the syntax:
+		file:"/path/filename"
+	For example:
+		http_port file:"/etc/squid/http_port_options.conf"	
+
+	TODO: Document escaping and macro substitution rules inside quoted
+	strings.
+
+	Someone can use single-quoted strings to prevent macro substitution.
+
+
   Conditional configuration
 
 	If-statements can be used to make configuration directives
 	depend on conditions:
 
 	    if <CONDITION>
 	        ... regular configuration directives ...
 	    [else
 	        ... regular configuration directives ...]
 	    endif
 
 	The else part is optional. The keywords "if", "else", and "endif"
 	must be typed on their own lines, as if they were regular
 	configuration directives.
 
 	NOTE: An else-if condition is not supported.
 
 	These individual conditions types are supported:
 
 	    true
@@ -8389,40 +8408,53 @@
 DEFAULT: 95
 LOC: Config.ipcache.high
 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
+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
 
 NAME: memory_pools_limit
 COMMENT: (bytes)
 TYPE: b_int64_t
 DEFAULT: 5 MB
 LOC: Config.MemPools.limit
 DOC_START
 	Used only with memory_pools on:
 	memory_pools_limit 50 MB

=== modified file 'src/cf_gen.cc'
--- src/cf_gen.cc	2013-04-23 12:35:44 +0000
+++ src/cf_gen.cc	2013-05-17 16:21:21 +0000
@@ -662,41 +662,42 @@
         return;
 
     // Once for the current directive name
     genParseAlias(name, fout);
 
     // All accepted aliases
     for (EntryAliasList::const_iterator a = alias.begin(); a != alias.end(); ++a) {
         genParseAlias(*a, fout);
     }
 }
 
 static void
 gen_parse(const EntryList &head, std::ostream &fout)
 {
     fout <<
     "static int\n"
     "parse_line(char *buff)\n"
     "{\n"
     "\tchar\t*token;\n"
     "\tif ((token = strtok(buff, w_space)) == NULL) \n"
-    "\t\treturn 1;\t/* ignore empty lines */\n";
+    "\t\treturn 1;\t/* ignore empty lines */\n"
+    "\tConfigParser::SetCfgLine(strtok(NULL, \"\"));\n";
 
     for (EntryList::const_iterator e = head.begin(); e != head.end(); ++e)
         e->genParse(fout);
 
     fout << "\treturn 0; /* failure */\n"
     "}\n\n";
 
 }
 
 static void
 gen_dump(const EntryList &head, std::ostream &fout)
 {
     fout <<
     "static void" << std::endl <<
     "dump_config(StoreEntry *entry)" << std::endl <<
     "{" << std::endl <<
     "    debugs(5, 4, HERE);" << std::endl;
 
     for (EntryList::const_iterator e = head.begin(); e != head.end(); ++e) {
 

=== modified file 'src/external_acl.cc'
--- src/external_acl.cc	2013-05-14 17:53:18 +0000
+++ src/external_acl.cc	2013-05-23 19:00:34 +0000
@@ -27,40 +27,41 @@
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  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.
  *
  */
 
 #include "squid.h"
 #include "acl/Acl.h"
 #include "acl/FilledChecklist.h"
 #include "cache_cf.h"
 #include "client_side.h"
 #include "comm/Connection.h"
+#include "ConfigParser.h"
 #include "ExternalACL.h"
 #include "ExternalACLEntry.h"
 #include "fde.h"
 #include "helper.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "ip/tools.h"
 #include "MemBuf.h"
 #include "mgr/Registration.h"
 #include "rfc1738.h"
 #include "SquidConfig.h"
 #include "SquidString.h"
 #include "SquidTime.h"
 #include "Store.h"
 #include "tools.h"
 #include "URL.h"
 #include "URLScheme.h"
 #include "wordlist.h"
 #if USE_SSL
@@ -189,40 +190,50 @@
         EXT_ACL_USER_CERT_RAW,
         EXT_ACL_USER_CERTCHAIN_RAW,
 #endif
 #if USE_AUTH
         EXT_ACL_EXT_USER,
 #endif
         EXT_ACL_EXT_LOG,
         EXT_ACL_TAG,
         EXT_ACL_ACLNAME,
         EXT_ACL_ACLDATA,
         EXT_ACL_PERCENT,
         EXT_ACL_END
     } type;
     external_acl_format *next;
     char *header;
     char *member;
     char separator;
     http_hdr_type header_id;
 };
 
+// Temporary MacroUser class to support quoted %macros
+// It should be merged to external_acl or external_acl_format
+// when these structs converted to real c++ classes
+class ExtAclMacroUser: public MacroUser {
+public:
+    /* MacroUser API */
+    virtual bool supportedMacro(const char *token);
+    virtual ~ExtAclMacroUser() {};
+};
+
 /* FIXME: These are not really cbdata, but it is an easy way
  * to get them pooled, refcounted, accounted and freed properly...
  */
 CBDATA_TYPE(external_acl);
 CBDATA_TYPE(external_acl_format);
 
 static void
 free_external_acl_format(void *data)
 {
     external_acl_format *p = static_cast<external_acl_format *>(data);
     safe_free(p->header);
 }
 
 static void
 free_external_acl(void *data)
 {
     external_acl *p = static_cast<external_acl *>(data);
     safe_free(p->name);
 
     while (p->format) {
@@ -313,48 +324,51 @@
 {
     external_acl *a;
     char *token;
     external_acl_format **p;
 
     CBDATA_INIT_TYPE_FREECB(external_acl, free_external_acl);
     CBDATA_INIT_TYPE_FREECB(external_acl_format, free_external_acl_format);
 
     a = cbdataAlloc(external_acl);
 
     /* set defaults */
     a->ttl = DEFAULT_EXTERNAL_ACL_TTL;
     a->negative_ttl = -1;
     a->cache_size = 256*1024;
     a->children.n_max = DEFAULT_EXTERNAL_ACL_CHILDREN;
     a->children.n_startup = a->children.n_max;
     a->children.n_idle = 1;
     a->local_addr.SetLocalhost();
     a->quote = external_acl::QUOTE_METHOD_URL;
 
-    token = strtok(NULL, w_space);
+    token = ConfigParser::NextToken();
 
     if (!token)
         self_destruct();
 
     a->name = xstrdup(token);
 
-    token = strtok(NULL, w_space);
+    // Allow supported %macros inside quoted tokens
+    ExtAclMacroUser extAclMacroUser;
+    ConfigParser::SetMacroUser(&extAclMacroUser);
+    token = ConfigParser::NextToken();
 
     /* Parse options */
     while (token) {
         if (strncmp(token, "ttl=", 4) == 0) {
             a->ttl = atoi(token + 4);
         } else if (strncmp(token, "negative_ttl=", 13) == 0) {
             a->negative_ttl = atoi(token + 13);
         } else if (strncmp(token, "children=", 9) == 0) {
             a->children.n_max = atoi(token + 9);
             debugs(0, DBG_CRITICAL, "WARNING: external_acl_type option children=N has been deprecated in favor of children-max=N and children-startup=N");
         } else if (strncmp(token, "children-max=", 13) == 0) {
             a->children.n_max = atoi(token + 13);
         } else if (strncmp(token, "children-startup=", 17) == 0) {
             a->children.n_startup = atoi(token + 17);
         } else if (strncmp(token, "children-idle=", 14) == 0) {
             a->children.n_idle = atoi(token + 14);
         } else if (strncmp(token, "concurrency=", 12) == 0) {
             a->children.concurrency = atoi(token + 12);
         } else if (strncmp(token, "cache=", 6) == 0) {
             a->cache_size = atoi(token + 6);
@@ -369,42 +383,43 @@
             debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option quote=url is deprecated. Remove this from your config.");
             a->quote = external_acl::QUOTE_METHOD_URL;
         } else if (strcmp(token, "quote=shell") == 0) {
             debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option quote=shell is deprecated. Use protocol=2.5 if still needed.");
             a->quote = external_acl::QUOTE_METHOD_SHELL;
 
             /* INET6: allow admin to configure some helpers explicitly to
                       bind to IPv4/v6 localhost port. */
         } else if (strcmp(token, "ipv4") == 0) {
             if ( !a->local_addr.SetIPv4() ) {
                 debugs(3, DBG_CRITICAL, "WARNING: Error converting " << a->local_addr << " to IPv4 in " << a->name );
             }
         } else if (strcmp(token, "ipv6") == 0) {
             if (!Ip::EnableIpv6)
                 debugs(3, DBG_CRITICAL, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a->name );
             // else nothing to do.
         } else {
             break;
         }
 
-        token = strtok(NULL, w_space);
+        token = ConfigParser::NextToken();
     }
+    ConfigParser::SetMacroUser(NULL);
 
     /* check that child startup value is sane. */
     if (a->children.n_startup > a->children.n_max)
         a->children.n_startup = a->children.n_max;
 
     /* check that child idle value is sane. */
     if (a->children.n_idle > a->children.n_max)
         a->children.n_idle = a->children.n_max;
     if (a->children.n_idle < 1)
         a->children.n_idle = 1;
 
     if (a->negative_ttl == -1)
         a->negative_ttl = a->ttl;
 
     /* Parse format */
     p = &a->format;
 
     while (token) {
         external_acl_format *format;
 
@@ -480,41 +495,41 @@
         else if (strcmp(token, "%EXT_USER") == 0)
             format->type = _external_acl_format::EXT_ACL_EXT_USER;
 #endif
         else if (strcmp(token, "%EXT_LOG") == 0)
             format->type = _external_acl_format::EXT_ACL_EXT_LOG;
         else if (strcmp(token, "%TAG") == 0)
             format->type = _external_acl_format::EXT_ACL_TAG;
         else if (strcmp(token, "%ACL") == 0)
             format->type = _external_acl_format::EXT_ACL_ACLNAME;
         else if (strcmp(token, "%DATA") == 0)
             format->type = _external_acl_format::EXT_ACL_ACLDATA;
         else if (strcmp(token, "%%") == 0)
             format->type = _external_acl_format::EXT_ACL_PERCENT;
         else {
             debugs(0, DBG_CRITICAL, "ERROR: Unknown Format token " << token);
             self_destruct();
         }
 
         *p = format;
         p = &format->next;
-        token = strtok(NULL, w_space);
+        token = ConfigParser::NextToken();
     }
 
     /* There must be at least one format token */
     if (!a->format)
         self_destruct();
 
     /* helper */
     if (!token)
         self_destruct();
 
     wordlistAdd(&a->cmdline, token);
 
     /* arguments */
     parse_wordlist(&a->cmdline);
 
     while (*list)
         list = &(*list)->next;
 
     *list = a;
 }
@@ -664,40 +679,58 @@
 }
 
 void
 external_acl::add(ExternalACLEntry *anEntry)
 {
     trimCache();
     assert (anEntry->def == NULL);
     anEntry->def = this;
     hash_join(cache, anEntry);
     dlinkAdd(anEntry, &anEntry->lru, &lru_list);
     ++cache_entries;
 }
 
 void
 external_acl::trimCache()
 {
     if (cache_size && cache_entries >= cache_size)
         external_acl_cache_delete(this, static_cast<external_acl_entry *>(lru_list.tail->data));
 }
 
+bool
+ExtAclMacroUser::supportedMacro(const char *token)
+{
+    // Not really a test required here because the 
+    return true;
+
+#if 0
+    //We may want to check if %macros supported by external_acl API. eg:
+    static const char *supportedMacros[] = { "%SRC", "%DST", NULL};
+    for (int i = 0; supportedMacros[i] != NULL; i++) {
+        if (strncmp(token, supportedMacros[i], strlen(supportedMacros[i])) == 0)
+            return true;
+    }
+    return false;
+#endif      
+
+}
+
 /******************************************************************
  * external acl type
  */
 
 struct _external_acl_data {
     external_acl *def;
     const char *name;
     wordlist *arguments;
 };
 
 CBDATA_TYPE(external_acl_data);
 static void
 free_external_acl_data(void *data)
 {
     external_acl_data *p = static_cast<external_acl_data *>(data);
     safe_free(p->name);
     wordlistDestroy(&p->arguments);
     cbdataReferenceDone(p->def);
 }
 

=== modified file 'src/format/Config.cc'
--- src/format/Config.cc	2012-08-31 16:57:39 +0000
+++ src/format/Config.cc	2013-01-23 16:02:32 +0000
@@ -1,37 +1,38 @@
 #include "squid.h"
+#include "ConfigParser.h"
 #include "cache_cf.h"
 #include "Debug.h"
 #include "format/Config.h"
 #include <list>
 
 Format::FmtConfig Format::TheConfig;
 
 void
 Format::FmtConfig::parseFormats()
 {
     char *name, *def;
 
-    if ((name = strtok(NULL, w_space)) == NULL)
+    if ((name = ConfigParser::NextToken()) == NULL)
         self_destruct();
 
-    if ((def = strtok(NULL, "\r\n")) == NULL) {
+    if ((def = ConfigParser::NextQuotedOrToEol()) == NULL) {
         self_destruct();
         return;
     }
 
     debugs(3, 2, "Custom Format for '" << name << "' is '" << def << "'");
 
     Format *nlf = new Format(name);
 
     if (!nlf->parse(def)) {
         self_destruct();
         return;
     }
 
     // add to global config list
     nlf->next = formats;
     formats = nlf;
 }
 
 void
 Format::FmtConfig::registerTokens(const String &nsName, TokenTableEntry const *tokenArray)

=== modified file 'src/format/Format.cc'
--- src/format/Format.cc	2013-05-13 03:57:03 +0000
+++ src/format/Format.cc	2013-05-23 18:27:09 +0000
@@ -29,40 +29,49 @@
     name = xstrdup(n);
 }
 
 Format::Format::~Format()
 {
     // erase the list without consuming stack space
     while (next) {
         // unlink the next entry for deletion
         Format *temp = next;
         next = temp->next;
         temp->next = NULL;
         delete temp;
     }
 
     // remove locals
     xfree(name);
     delete format;
 }
 
 bool
+Format::Format::supportedMacro(const char *def)
+{
+    Token tk;
+    enum Quoting quote = LOG_QUOTE_NONE;
+    // Token::parse will call self_destruct if the macro is not supported
+    return tk.parse(def, &quote) != 0;
+}
+
+bool
 Format::Format::parse(const char *def)
 {
     const char *cur, *eos;
     Token *new_lt, *last_lt;
     enum Quoting quote = LOG_QUOTE_NONE;
 
     debugs(46, 2, HERE << "got definition '" << def << "'");
 
     if (format) {
         debugs(46, DBG_IMPORTANT, "WARNING: existing format for '" << name << " " << def << "'");
         return false;
     }
 
     /* very inefficent parser, but who cares, this needs to be simple */
     /* First off, let's tokenize, we'll optimize in a second pass.
      * A token can either be a %-prefixed sequence (usually a dynamic
      * token but it can be an escaped sequence), or a string. */
     cur = def;
     eos = def + strlen(def);
     format = new_lt = last_lt = new Token;

=== modified file 'src/format/Format.h'
--- src/format/Format.h	2013-01-02 23:40:49 +0000
+++ src/format/Format.h	2013-05-23 17:50:13 +0000
@@ -1,52 +1,56 @@
 #ifndef _SQUID_FORMAT_FORMAT_H
 #define _SQUID_FORMAT_FORMAT_H
 
 #include "base/RefCount.h"
+#include "ConfigParser.h"
 /*
  * Squid configuration allows users to define custom formats in
  * several components.
  * - logging
  * - external ACL input
  * - deny page URL
  *
  * These enumerations and classes define the API for parsing of
  * format directives to define these patterns. Along with output
  * functionality to produce formatted buffers.
  */
 
 class AccessLogEntry;
 typedef RefCount<AccessLogEntry> AccessLogEntryPointer;
 class MemBuf;
 class StoreEntry;
 
 namespace Format
 {
 
 class Token;
 
 // XXX: inherit from linked list
-class Format
+class Format : public MacroUser
 {
 public:
     Format(const char *name);
-    ~Format();
+    virtual ~Format();
+
+    /* MacroUser API */
+    virtual bool supportedMacro(const char *token);
 
     /* very inefficent parser, but who cares, this needs to be simple */
     /* First off, let's tokenize, we'll optimize in a second pass.
      * A token can either be a %-prefixed sequence (usually a dynamic
      * token but it can be an escaped sequence), or a string. */
     bool parse(const char *def);
 
     /// assemble the state information into a formatted line.
     void assemble(MemBuf &mb, const AccessLogEntryPointer &al, int logSequenceNumber) const;
 
     /// dump this whole list of formats into the provided StoreEntry
     void dump(StoreEntry * entry, const char *directiveName);
 
     char *name;
     Token *format;
     Format *next;
 };
 
 } // namespace Format
 

=== modified file 'src/ip/QosConfig.cc'
--- src/ip/QosConfig.cc	2012-12-31 01:13:06 +0000
+++ src/ip/QosConfig.cc	2013-01-18 16:22:48 +0000
@@ -192,41 +192,41 @@
         tosToServer(NULL), tosToClient(NULL), nfmarkToServer(NULL),
         nfmarkToClient(NULL)
 {
 }
 
 void
 Ip::Qos::Config::parseConfigLine()
 {
     /* parse options ... */
     char *token;
     /* These are set as appropriate and then used to check whether the initial loop has been done */
     bool mark = false;
     bool tos = false;
     /* Assume preserve is true. We don't set at initialisation as this affects isHitTosActive().
        We have to do this now, as we may never match the 'tos' parameter below */
 #if !USE_QOS_TOS
     debugs(3, DBG_CRITICAL, "ERROR: Invalid option 'qos_flows'. QOS features not enabled in this build");
     self_destruct();
 #endif
 
-    while ( (token = strtok(NULL, w_space)) ) {
+    while ( (token = ConfigParser::NextToken()) ) {
 
         // Work out TOS or mark. Default to TOS for backwards compatibility
         if (!(mark || tos)) {
             if (strncmp(token, "mark",4) == 0) {
 #if SO_MARK && USE_LIBCAP
                 mark = true;
                 // Assume preserve is true. We don't set at initialisation as this affects isHitNfmarkActive()
 #if USE_LIBNETFILTERCONNTRACK
                 preserveMissMark = true;
 # else // USE_LIBNETFILTERCONNTRACK
                 preserveMissMark = false;
                 debugs(3, DBG_IMPORTANT, "WARNING: Squid not compiled with Netfilter conntrack library. "
                        << "Netfilter mark preservation not available.");
 #endif // USE_LIBNETFILTERCONNTRACK
 #elif SO_MARK // SO_MARK && USE_LIBCAP
                 debugs(3, DBG_CRITICAL, "ERROR: Invalid parameter 'mark' in qos_flows option. "
                        << "Linux Netfilter marking not available without LIBCAP support.");
                 self_destruct();
 #else // SO_MARK && USE_LIBCAP
                 debugs(3, DBG_CRITICAL, "ERROR: Invalid parameter 'mark' in qos_flows option. "

=== modified file 'src/log/Config.cc'
--- src/log/Config.cc	2012-08-31 16:57:39 +0000
+++ src/log/Config.cc	2013-05-23 18:09:52 +0000
@@ -1,33 +1,36 @@
 #include "squid.h"
 #include "cache_cf.h"
+#include "ConfigParser.h"
 #include "Debug.h"
 #include "log/Config.h"
 
 Log::LogConfig Log::TheConfig;
 
 void
 Log::LogConfig::parseFormats()
 {
     char *name, *def;
 
-    if ((name = strtok(NULL, w_space)) == NULL)
+    if ((name = ConfigParser::NextToken()) == NULL)
         self_destruct();
 
-    if ((def = strtok(NULL, "\r\n")) == NULL) {
+    ::Format::Format *nlf = new ::Format::Format(name);
+
+    ConfigParser::SetMacroUser(nlf);
+    if ((def = ConfigParser::NextQuotedOrToEol()) == NULL) {
         self_destruct();
         return;
     }
+    ConfigParser::SetMacroUser(NULL);
 
     debugs(3, 2, "Log Format for '" << name << "' is '" << def << "'");
 
-    ::Format::Format *nlf = new ::Format::Format(name);
-
     if (!nlf->parse(def)) {
         self_destruct();
         return;
     }
 
     // add to global config list
     nlf->next = logformats;
     logformats = nlf;
 }

=== modified file 'src/tests/stub_cache_cf.cc'
--- src/tests/stub_cache_cf.cc	2012-10-26 19:42:31 +0000
+++ src/tests/stub_cache_cf.cc	2013-01-14 10:48:38 +0000
@@ -32,23 +32,24 @@
 
 #include "squid.h"
 #include "acl/Acl.h"
 #include "ConfigParser.h"
 #include "wordlist.h"
 #include "YesNoNone.h"
 
 #define STUB_API "cache_cf.cc"
 #include "tests/STUB.h"
 
 void self_destruct(void) STUB
 void parse_int(int *var) STUB
 void parse_onoff(int *var) STUB
 void parse_eol(char *volatile *var) STUB
 void parse_wordlist(wordlist ** list) STUB
 void requirePathnameExists(const char *name, const char *path) STUB_NOP
 void parse_time_t(time_t * var) STUB
 char * strtokFile(void) STUB_RETVAL(NULL)
 void ConfigParser::ParseUShort(unsigned short *var) STUB
 void ConfigParser::ParseString(String*) STUB
+char * ConfigParser::NextToken() STUB
 void dump_acl_access(StoreEntry * entry, const char *name, acl_access * head) STUB
 void dump_acl_list(StoreEntry*, ACLList*) STUB
 YesNoNone::operator void*() const { STUB_NOP; return NULL; }

=== modified file 'src/wccp2.cc'
--- src/wccp2.cc	2013-01-25 01:26:21 +0000
+++ src/wccp2.cc	2013-02-21 14:11:34 +0000
@@ -22,40 +22,41 @@
  *  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.
  *
  */
 
 #include "squid.h"
 
 #if USE_WCCPv2
 
 #include "cache_cf.h"
 #include "comm.h"
 #include "comm/Connection.h"
 #include "comm/Loops.h"
 #include "compat/strsep.h"
+#include "ConfigParser.h"
 #include "event.h"
 #include "ip/Address.h"
 #include "md5.h"
 #include "Parsing.h"
 #include "Store.h"
 #include "SwapDir.h"
 
 #if HAVE_NETDB_H
 #include <netdb.h>
 #endif
 
 #define WCCP_PORT 2048
 #define WCCP_RESPONSE_SIZE 12448
 #define WCCP_BUCKETS 256
 
 static int theWccp2Connection = -1;
 static int wccp2_connected = 0;
 
 static PF wccp2HandleUdp;
 static EVH wccp2HereIam;
@@ -1996,41 +1997,41 @@
 
         service_list_ptr = service_list_ptr->next;
     }
 }
 
 /*
  * Configuration option parsing code
  */
 
 /**
  * Parse wccp2_return_method and wccp2_forwarding_method options
  * they can be '1' aka 'gre' or  '2' aka 'l2'
  * repesenting the integer numeric of the same.
  */
 void
 parse_wccp2_method(int *method)
 {
     char *t;
 
     /* Snarf the method */
-    if ((t = strtok(NULL, w_space)) == NULL) {
+    if ((t = ConfigParser::NextToken()) == NULL) {
         debugs(80, DBG_CRITICAL, "wccp2_*_method: missing setting.");
         self_destruct();
     }
 
     /* update configuration if its valid */
     if (strcmp(t, "gre") == 0 || strcmp(t, "1") == 0) {
         *method = WCCP2_METHOD_GRE;
     } else if (strcmp(t, "l2") == 0 || strcmp(t, "2") == 0) {
         *method = WCCP2_METHOD_L2;
     } else {
         debugs(80, DBG_CRITICAL, "wccp2_*_method: unknown setting, got " << t );
         self_destruct();
     }
 }
 
 void
 dump_wccp2_method(StoreEntry * e, const char *label, int v)
 {
     switch (v) {
     case WCCP2_METHOD_GRE:
@@ -2043,41 +2044,41 @@
         debugs(80, DBG_CRITICAL, "FATAL: WCCPv2 configured method (" << v << ") is not valid.");
         self_destruct();
     }
 }
 
 void
 free_wccp2_method(int *v)
 { }
 
 /**
  * Parse wccp2_assignment_method option
  * they can be '1' aka 'hash' or  '2' aka 'mask'
  * repesenting the integer numeric of the same.
  */
 void
 parse_wccp2_amethod(int *method)
 {
     char *t;
 
     /* Snarf the method */
-    if ((t = strtok(NULL, w_space)) == NULL) {
+    if ((t = ConfigParser::NextToken()) == NULL) {
         debugs(80, DBG_CRITICAL, "wccp2_assignment_method: missing setting.");
         self_destruct();
     }
 
     /* update configuration if its valid */
     if (strcmp(t, "hash") == 0 || strcmp(t, "1") == 0) {
         *method = WCCP2_ASSIGNMENT_METHOD_HASH;
     } else if (strcmp(t, "mask") == 0 || strcmp(t, "2") == 0) {
         *method = WCCP2_ASSIGNMENT_METHOD_MASK;
     } else {
         debugs(80, DBG_CRITICAL, "wccp2_assignment_method: unknown setting, got " << t );
         self_destruct();
     }
 }
 
 void
 dump_wccp2_amethod(StoreEntry * e, const char *label, int v)
 {
     switch (v) {
     case WCCP2_ASSIGNMENT_METHOD_HASH:
@@ -2099,66 +2100,66 @@
 /*
  * Format:
  *
  * wccp2_service {standard|dynamic} {id} (password=password)
  */
 void
 parse_wccp2_service(void *v)
 {
     char *t;
     int service = 0;
     int service_id = 0;
     int security_type = WCCP2_NO_SECURITY;
     char wccp_password[WCCP2_PASSWORD_LEN + 1];
 
     if (wccp2_connected == 1) {
         debugs(80, DBG_IMPORTANT, "WCCPv2: Somehow reparsing the configuration without having shut down WCCP! Try reloading squid again.");
         return;
     }
 
     /* Snarf the type */
-    if ((t = strtok(NULL, w_space)) == NULL) {
+    if ((t = ConfigParser::NextToken()) == NULL) {
         debugs(80, DBG_CRITICAL, "wccp2ParseServiceInfo: missing service info type (standard|dynamic)");
         self_destruct();
     }
 
     if (strcmp(t, "standard") == 0) {
         service = WCCP2_SERVICE_STANDARD;
     } else if (strcmp(t, "dynamic") == 0) {
         service = WCCP2_SERVICE_DYNAMIC;
     } else {
         debugs(80, DBG_CRITICAL, "wccp2ParseServiceInfo: bad service info type (expected standard|dynamic, got " << t << ")");
         self_destruct();
     }
 
     /* Snarf the ID */
     service_id = GetInteger();
 
     if (service_id < 0 || service_id > 255) {
         debugs(80, DBG_CRITICAL, "ERROR: invalid WCCP service id " << service_id << " (must be between 0 .. 255)");
         self_destruct();
     }
 
     memset(wccp_password, 0, sizeof(wccp_password));
     /* Handle password, if any */
 
-    if ((t = strtok(NULL, w_space)) != NULL) {
+    if ((t = ConfigParser::NextToken()) != NULL) {
         if (strncmp(t, "password=", 9) == 0) {
             security_type = WCCP2_MD5_SECURITY;
             strncpy(wccp_password, t + 9, WCCP2_PASSWORD_LEN);
         }
     }
 
     /* Create a placeholder service record */
     wccp2_add_service_list(service, service_id, 0, 0, 0, empty_portlist, security_type, wccp_password);
 }
 
 void
 dump_wccp2_service(StoreEntry * e, const char *label, void *v)
 {
 
     struct wccp2_service_list_t *srv;
     srv = wccp2_service_list_head;
 
     while (srv != NULL) {
         debugs(80, 3, "dump_wccp2_service: id " << srv->info.service_id << ", type " << srv->info.service);
         storeAppendPrintf(e, "%s %s %d", label,
@@ -2300,41 +2301,41 @@
     }
 
     debugs(80, 5, "parse_wccp2_service_info: called");
     memset(portlist, 0, sizeof(portlist));
     /* First argument: id */
     service_id = GetInteger();
 
     if (service_id < 0 || service_id > 255) {
         debugs(80, DBG_CRITICAL, "ERROR: invalid WCCP service id " << service_id << " (must be between 0 .. 255)");
         self_destruct();
     }
 
     /* Next: find the (hopefully!) existing service */
     srv = wccp2_get_service_by_id(WCCP2_SERVICE_DYNAMIC, service_id);
 
     if (srv == NULL) {
         fatalf("parse_wccp2_service_info: unknown dynamic service id %d: you need to define it using wccp2_service (and make sure you wish to configure it as a dynamic service.)\n", service_id);
     }
 
     /* Next: loop until we don't have any more tokens */
-    while ((t = strtok(NULL, w_space)) != NULL) {
+    while ((t = ConfigParser::NextToken()) != NULL) {
         if (strncmp(t, "flags=", 6) == 0) {
             /* XXX eww, string pointer math */
             flags = parse_wccp2_service_flags(t + 6);
         } else if (strncmp(t, "ports=", 6) == 0) {
             parse_wccp2_service_ports(t + 6, portlist);
             flags |= WCCP2_SERVICE_PORTS_DEFINED;
         } else if (strncmp(t, "protocol=tcp", 12) == 0) {
             protocol = IPPROTO_TCP;
         } else if (strncmp(t, "protocol=udp", 12) == 0) {
             protocol = IPPROTO_UDP;
         } else if (strncmp(t, "protocol=", 9) == 0) {
             fatalf("parse_wccp2_service_info: id %d: unknown protocol (%s) - must be tcp or udp!\n", service_id, t);
         } else if (strncmp(t, "priority=", 9) == 0) {
             priority = strtol(t + 9, &end, 0);
 
             if (priority < 0 || priority > 255) {
                 fatalf("parse_wccp2_service_info: id %d: %s out of range (0..255)!\n", service_id, t);
             }
         } else {
             fatalf("parse_wccp2_service_info: id %d: unknown option '%s'\n", service_id, t);


