Author: Alex Rousskov <rousskov@measurement-factory.com>, Christos Tsantilas <chtsanti@users.sourceforge.net>
Add request_header_add option and [request|reply]_header_* manglers fixes

This patch:

1) Add request_header_add, a new ACL-driven squid.conf option that allows 
   addition of HTTP request header fields before the request is sent to the
   next HTTP hop (a peer proxy or an origin server):
        request_header_add <field-name> <field-value> acl1 [acl2]
   where:
     * Field-name is a token specifying an HTTP header name.
     * Field-value is either a constant token or a quoted string containing
       %macros.  The following field-value macros are supported: %LOGIN, %SRC,
       %DST, %user_cert_subject, %user_ca, %%
     * One or more Squid ACLs may be specified to restrict header insertion to
       matching requests. The request_header_add option supports fast ACLs only.

2) Fix the [request|reply]_header_[access|replace] configuration parameters to
   support custom headers. Currently the user is able to remove/replace/allow
   ALL custom headers using the "Other" as header name.


This is a Measurement Factory project.

=== modified file 'src/ConfigParser.cc'
--- src/ConfigParser.cc	2012-01-20 18:55:04 +0000
+++ src/ConfigParser.cc	2012-06-27 15:20:49 +0000
@@ -99,79 +99,83 @@
             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 t;
 }
 
 void
-ConfigParser::ParseQuotedString(char **var)
+ConfigParser::ParseQuotedString(char **var, bool *wasQuoted)
 {
     String sVar;
-    ParseQuotedString(&sVar);
+    ParseQuotedString(&sVar, wasQuoted);
     *var = xstrdup(sVar.termedBuf());
 }
 
 void
-ConfigParser::ParseQuotedString(String *var)
+ConfigParser::ParseQuotedString(String *var, bool *wasQuoted)
 {
     // Get all of the remaining string
     char *token = strtok(NULL, "");
     if (token == NULL)
         self_destruct();
 
     if (*token != '"') {
         token = strtok(token, w_space);
         var->reset(token);
+        if (wasQuoted)
+            *wasQuoted = false;
         return;
     }
 
     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 (*s != '"') {
         debugs(3, DBG_CRITICAL, "ParseQuotedString: missing '\"' at the end of quoted string" );
         self_destruct();
     }
     strtok(s-1, "\""); /*Reset the strtok to point after the "  */
     *s = '\0';
 
     var->reset(token+1);
+    if (wasQuoted)
+        *wasQuoted = true;
 }
 
 const char *
 ConfigParser::QuoteString(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('\\');

=== modified file 'src/ConfigParser.h'
--- src/ConfigParser.h	2012-01-20 18:55:04 +0000
+++ src/ConfigParser.h	2012-06-27 15:13:15 +0000
@@ -50,30 +50,35 @@
 
 /**
  * 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);
-    static void ParseQuotedString(char **var);
-    static void ParseQuotedString(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(String &var);
     static void ParseWordList(wordlist **list);
     static char * strtokFile();
 };
 
 extern int parseConfigFile(const char *file_name);
 
 #endif /* SQUID_CONFIGPARSER_H */

=== modified file 'src/HttpHeaderTools.cc'
--- src/HttpHeaderTools.cc	2012-01-20 18:55:04 +0000
+++ src/HttpHeaderTools.cc	2012-06-27 15:20:16 +0000
@@ -17,44 +17,58 @@
  *  sources; see the CREDITS file for full details.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  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-old.h"
 #include "acl/FilledChecklist.h"
+#include "acl/Gadgets.h"
+#include "client_side.h"
+#include "comm/Connection.h"
 #include "compat/strtoll.h"
+#include "fde.h"
 #include "HttpHdrContRange.h"
 #include "HttpHeader.h"
+#include "HttpRequest.h"
 #include "MemBuf.h"
+#if USE_SSL
+#include "ssl/support.h"
+#endif
+#include "Store.h"
+#include <algorithm>
+#if HAVE_STRING
+#include <string>
+#endif
+
 
 static void httpHeaderPutStrvf(HttpHeader * hdr, http_hdr_type id, const char *fmt, va_list vargs);
 
 
 HttpHeaderFieldInfo *
 httpHeaderBuildFieldsInfo(const HttpHeaderFieldAttrs * attrs, int count)
 {
     int i;
     HttpHeaderFieldInfo *table = NULL;
     assert(attrs && count);
 
     /* allocate space */
     table = new HttpHeaderFieldInfo[count];
 
     for (i = 0; i < count; ++i) {
         const http_hdr_type id = attrs[i].id;
         HttpHeaderFieldInfo *info = table + id;
         /* sanity checks */
         assert(id >= 0 && id < count);
         assert(attrs[i].name);
@@ -397,52 +411,58 @@
     /* Make sure it's defined even if empty "" */
     if (!val->defined())
         val->limitInit("", 0);
     return 1;
 }
 
 /**
  * Checks the anonymizer (header_access) configuration.
  *
  * \retval 0    Header is explicitly blocked for removal
  * \retval 1    Header is explicitly allowed
  * \retval 1    Header has been replaced, the current version can be used.
  * \retval 1    Header has no access controls to test
  */
 static int
 httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, int req_or_rep)
 {
     int retval;
 
     /* check with anonymizer tables */
-    header_mangler *hm;
+    HeaderManglers *hms = NULL;
     assert(e);
 
     if (ROR_REQUEST == req_or_rep) {
-        hm = &Config.request_header_access[e->id];
+        hms = Config.request_header_access;
     } else if (ROR_REPLY == req_or_rep) {
-        hm = &Config.reply_header_access[e->id];
+        hms = Config.reply_header_access;
     } else {
         /* error. But let's call it "request". */
-        hm = &Config.request_header_access[e->id];
+        hms = Config.request_header_access;
     }
 
+    /* manglers are not configured for this message kind */
+    if (!hms)
+        return 1;
+
+    const header_mangler *hm = hms->find(*e);
+
     /* mangler or checklist went away. default allow */
     if (!hm || !hm->access_list) {
         return 1;
     }
 
     ACLFilledChecklist checklist(hm->access_list, request, NULL);
 
     if (checklist.fastCheck() == ACCESS_ALLOWED) {
         /* aclCheckFast returns true for allow. */
         retval = 1;
     } else if (NULL == hm->replacement) {
         /* It was denied, and we don't have any replacement */
         retval = 0;
     } else {
         /* It was denied, but we have a replacement. Replace the
          * header on the fly, and return that the new header
          * is allowed.
          */
         e->value = hm->replacement;
         retval = 1;
@@ -450,34 +470,284 @@
 
     return retval;
 }
 
 /** Mangles headers for a list of headers. */
 void
 httpHdrMangleList(HttpHeader * l, HttpRequest * request, int req_or_rep)
 {
     HttpHeaderEntry *e;
     HttpHeaderPos p = HttpHeaderInitPos;
 
     int headers_deleted = 0;
     while ((e = l->getEntry(&p)))
         if (0 == httpHdrMangle(e, request, req_or_rep))
             l->delAt(p, headers_deleted);
 
     if (headers_deleted)
         l->refreshMask();
 }
 
-/**
- * return 1 if manglers are configured.  Used to set a flag
- * for optimization during request forwarding.
- */
-int
-httpReqHdrManglersConfigured()
+static
+void header_mangler_clean(header_mangler &m)
+{
+    aclDestroyAccessList(&m.access_list);
+    safe_free(m.replacement);
+}
+
+static
+void header_mangler_dump_access(StoreEntry * entry, const char *option,
+    const header_mangler &m, const char *name)
+{
+    if (m.access_list != NULL) {
+        storeAppendPrintf(entry, "%s ", option);
+        dump_acl_access(entry, name, m.access_list);
+    }
+}
+
+static
+void header_mangler_dump_replacement(StoreEntry * entry, const char *option,
+    const header_mangler &m, const char *name)
+{
+    if (m.replacement)
+        storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement);
+}
+
+HeaderManglers::HeaderManglers()
+{
+    memset(known, 0, sizeof(known));
+    memset(&all, 0, sizeof(all));
+}
+
+HeaderManglers::~HeaderManglers()
+{
+    for (int i = 0; i < HDR_ENUM_END; i++)
+        header_mangler_clean(known[i]);
+
+    typedef ManglersByName::iterator MBNI;
+    for (MBNI i = custom.begin(); i != custom.end(); ++i)
+        header_mangler_clean(i->second);
+
+    header_mangler_clean(all);
+}
+
+void
+HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const
 {
     for (int i = 0; i < HDR_ENUM_END; i++) {
-        if (NULL != Config.request_header_access[i].access_list)
-            return 1;
+        header_mangler_dump_access(entry, name, known[i],
+                                   httpHeaderNameById(i));
     }
 
-    return 0;
+    typedef ManglersByName::const_iterator MBNCI;
+    for (MBNCI i = custom.begin(); i != custom.end(); ++i)
+        header_mangler_dump_access(entry, name, i->second, i->first.c_str());
+
+    header_mangler_dump_access(entry, name, all, "All");
+}
+
+void
+HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const
+{
+    for (int i = 0; i < HDR_ENUM_END; i++) {
+        header_mangler_dump_replacement(entry, name, known[i],
+                                        httpHeaderNameById(i));
+    }
+
+    typedef ManglersByName::const_iterator MBNCI;
+    for (MBNCI i = custom.begin(); i != custom.end(); ++i) {
+        header_mangler_dump_replacement(entry, name, i->second,
+                                        i->first.c_str());
+    }
+
+    header_mangler_dump_replacement(entry, name, all, "All");
+}
+
+header_mangler *
+HeaderManglers::track(const char *name)
+{
+    int id = httpHeaderIdByNameDef(name, strlen(name));
+
+    if (id == HDR_BAD_HDR) { // special keyword or a custom header
+        if (strcmp(name, "All") == 0)
+            id = HDR_ENUM_END;
+        else if (strcmp(name, "Other") == 0)
+            id = HDR_OTHER;
+    }
+
+    header_mangler *m = NULL;
+    if (id == HDR_ENUM_END) {
+        m = &all;
+    } else
+    if (id == HDR_BAD_HDR) {
+        m = &custom[name];
+    } else {
+        m = &known[id]; // including HDR_OTHER
+    }
+
+    assert(m);
+    return m;
+}
+
+void
+HeaderManglers::setReplacement(const char *name, const char *value)
+{
+    // for backword compatibility, we allow replacements to be configured
+    // for headers w/o access rules, but such replacements are ignored
+    header_mangler *m = track(name);
+
+    safe_free(m->replacement); // overwrite old value if any
+    m->replacement = xstrdup(value);
+}
+
+const header_mangler *
+HeaderManglers::find(const HttpHeaderEntry &e) const
+{
+    // a known header with a configured ACL list
+    if (e.id != HDR_OTHER && 0 <= e.id && e.id < HDR_ENUM_END &&
+        known[e.id].access_list)
+        return &known[e.id];
+
+    // a custom header
+    if (e.id == HDR_OTHER) {
+        // does it have an ACL list configured?
+        // Optimize: use a name type that we do not need to convert to here
+        const ManglersByName::const_iterator i = custom.find(e.name.termedBuf());
+        if (i != custom.end())
+            return &i->second;
+    }
+
+    // Next-to-last resort: "Other" rules match any custom header
+    if (e.id == HDR_OTHER && known[HDR_OTHER].access_list)
+        return &known[HDR_OTHER];
+
+    // Last resort: "All" rules match any header
+    if (all.access_list)
+        return &all;
+
+    return NULL;
+}
+
+#if USE_AUTH
+static
+const char *
+httpHdrFmt_login(HttpRequest *request)
+{
+    if (request->auth_user_request != NULL)
+        return request->auth_user_request->username();
+
+    return NULL;
+}
+#endif
+
+static
+const char *
+httpHdrFmt_src(HttpRequest *request)
+{
+    static char buf[256];
+    /*Maybe we want to use indirect_client in some cases:
+      return request->indirect_client_addr.NtoA(buf, sizeof(buf));
+      Do we need a new configuration parameter: *uses_indirect_client
+    */
+    return request->client_addr.NtoA(buf, sizeof(buf));
+}
+
+static
+const char *
+httpHdrFmt_dst(HttpRequest *request)
+{
+    return request->GetHost();
+}
+
+#if USE_SSL
+static
+const char *
+httpHdrFmt_user_cert_subject(HttpRequest *request)
+{
+    ConnStateData *conn = request->clientConnectionManager.get();
+    if (conn && conn->clientConnection != NULL) {
+        SSL *ssl = fd_table[conn->clientConnection->fd].ssl;
+        return sslGetUserAttribute(ssl, "DN");
+    }
+    return NULL;
+}
+
+static
+const char *
+httpHdrFmt_user_ca(HttpRequest *request)
+{
+    const ConnStateData *conn = request->clientConnectionManager.get();
+    if (conn && conn->clientConnection != NULL) {
+        SSL *ssl = fd_table[conn->clientConnection->fd].ssl;
+        return sslGetCAAttribute(ssl, "DN");
+    }
+    return NULL;
+}
+#endif
+
+/// 
+static
+const char *
+httpHdrFormat(const std::string &hdrValueWithMacros, HttpRequest *request)
+{
+    /// header value formatting macro details
+    struct FmtMacro {
+        const std::string code; ///< macro name
+        const char *(*fn)(HttpRequest *); ///< computes macro substitution
+    };
+    static const FmtMacro fmtMacros[] =  {
+#if USE_AUTH
+        {"LOGIN", httpHdrFmt_login},
+#endif
+        {"SRC", httpHdrFmt_src},
+        {"DST", httpHdrFmt_dst},
+#if USE_SSL
+        {"user_cert_subject", httpHdrFmt_user_cert_subject}, 
+        {"user_ca", httpHdrFmt_user_ca}, 
+#endif
+        {"", NULL}
+    };
+
+    static std::string buf;
+    buf.clear();
+    const char *s = hdrValueWithMacros.c_str();
+    while (const char *macroName = strchr(s, '%')) {
+        buf.append(s, macroName - s); // left of macro
+        ++macroName; // skip '%'
+        int macroNameLen = 0;
+        char const *macroValue = NULL;
+        for (int i = 0; fmtMacros[i].fn; ++i) {
+            const FmtMacro &fmt = fmtMacros[i];
+            macroNameLen = fmt.code.length();
+            if (fmt.code.compare(0, macroNameLen, macroName, macroNameLen) == 0) {
+                macroValue = fmt.fn(request);
+                break;
+            }
+            macroNameLen = 0; // positive value would indicate a match
+        }
+        if (macroNameLen) { // we found a known macro
+            buf.append(macroValue ? macroValue : "");
+        } else if (*macroName == '%') { // a "%%" escape sequence
+            buf.append("%");
+            macroNameLen = 1;
+        } else {
+            buf.append("%"); // unknown macro -- a misconfiguration
+        }
+        s = macroName + macroNameLen; // skip processed macro
+    }
+    buf.append(s); // leftovers if any
+    return buf.c_str();
+}
+
+void
+httpHdrAdd(HttpHeader *heads, HttpRequest *request, HeaderWithAclList &headersAdd)
+{
+    ACLFilledChecklist checklist(NULL, request, NULL);
+    for (HeaderWithAclList::const_iterator hwa = headersAdd.begin(); hwa != headersAdd.end(); ++hwa) {
+        if (!hwa->aclList || checklist.fastCheck(hwa->aclList) == ACCESS_ALLOWED) {
+            const char *fieldValue = hwa->quoted ? httpHdrFormat(hwa->fieldValue, request) : hwa->fieldValue.c_str();
+            HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, hwa->fieldName.c_str(), 
+                                                     fieldValue);
+            heads->addEntry(e);
+        }
+    }
 }

=== modified file 'src/Makefile.am'
--- src/Makefile.am	2012-05-30 10:25:42 +0000
+++ src/Makefile.am	2012-06-10 15:44:04 +0000
@@ -1038,106 +1038,115 @@
 ##NP: (TESTSOURCES) defines stub debugs() and new/delete for testing
 ##
 #tests_testX_SOURCES=\
 #	tests/testX.h \
 #	tests/testX.cc \
 #	tests/testMain.cc \
 #	X.h \
 #	X.cc
 #nodist_tests_testX_SOURCES=\
 #	$(TESTSOURCES)
 #tests_testX_LDFLAGS = $(LIBADD_DL)
 #tests_testX_LDADD=\
 #	$(SQUID_CPPUNIT_LIBS) \
 #	$(SQUID_CPPUNIT_LA) \
 #	$(COMPAT_LIB) \
 #tests_testX_DEPENDENCIES= $(SQUID_CPPUNIT_LA)
 
 
 # - add other component .(h|cc) files needed to link and run tests
 tests_testHttpReply_SOURCES=\
+	anyp/ProtocolType.cc \
 	cbdata.cc \
 	cbdata.h \
 	ETag.cc \
 	HttpBody.h \
 	HttpBody.cc \
 	HttpHdrCc.h \
 	HttpHdrCc.cc \
 	HttpHdrCc.cci \
 	HttpHdrContRange.cc \
 	HttpHdrContRange.h \
 	HttpHdrRange.cc \
 	HttpHdrSc.cc \
 	HttpHdrSc.h \
 	HttpHdrScTarget.cc \
 	HttpHdrScTarget.h \
 	HttpHeader.cc \
 	HttpHeader.h \
 	HttpHeaderMask.h \
 	HttpHeaderTools.cc \
 	HttpControlMsg.h \
 	HttpMsg.cc \
 	HttpMsg.h \
 	HttpReply.cc \
 	HttpReply.h \
 	HttpStatusCode.h \
 	HttpStatusLine.cc \
 	HttpStatusLine.h \
 	mem.cc \
 	MemBuf.cc \
 	MemBuf.h \
 	mime_header.cc \
 	Packer.cc \
 	Packer.h \
 	SquidString.h \
 	SquidTime.h \
 	String.cc \
+	tests/stub_access_log.cc \
 	tests/stub_cache_cf.cc \
 	tests/stub_cache_manager.cc \
 	tests/stub_debug.cc \
+	tests/stub_errorpage.cc \
 	tests/stub_HelperChildConfig.cc \
 	StatCounters.h \
 	StatCounters.cc \
 	StatHist.h \
 	tests/stub_StatHist.cc \
 	tests/stub_store.cc \
 	tests/stub_store_stats.cc \
 	tests/stub_tools.cc \
+	tests/stub_HttpRequest.cc \
 	tests/testHttpReply.cc \
 	tests/testHttpReply.h \
 	tests/testMain.cc \
 	time.cc \
+	url.cc \
+	URLScheme.cc \
 	wordlist.cc
 nodist_tests_testHttpReply_SOURCES=\
 	$(TESTSOURCES)
 tests_testHttpReply_LDFLAGS = $(LIBADD_DL)
 tests_testHttpReply_LDADD=\
+	acl/libacls.la \
 	acl/libapi.la \
 	acl/libstate.la \
 	$(AUTH_LIBS) \
 	ip/libip.la \
 	base/libbase.la \
+	$(SSL_LIBS) \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(SQUID_CPPUNIT_LIBS) \
 	$(SQUID_CPPUNIT_LA) \
+	$(SSLLIB) \
 	$(COMPAT_LIB) \
 	$(XTRA_LIBS)
 tests_testHttpReply_DEPENDENCIES= $(SQUID_CPPUNIT_LA)
 
 
 tests_testACLMaxUserIP_SOURCES= \
 	cbdata.cc \
 	ClientInfo.h \
 	ConfigOption.cc \
 	ConfigParser.cc \
 	DiskIO/ReadRequest.cc \
 	DiskIO/WriteRequest.cc \
 	ETag.cc \
 	event.cc \
 	FileMap.h \
 	filemap.cc \
 	HelperChildConfig.h \
 	HelperChildConfig.cc \
 	HttpHeader.cc \
 	HttpHeaderTools.cc \
@@ -1165,76 +1174,78 @@
 	StoreIOState.cc \
 	StoreMeta.cc \
 	StoreMetaMD5.cc \
 	StoreMetaSTD.cc \
 	StoreMetaSTDLFS.cc \
 	StoreMetaUnpacker.cc \
 	StoreMetaURL.cc \
 	StoreMetaVary.cc \
 	StoreSwapLogData.cc \
 	store_key_md5.cc \
 	swap_log_op.cc \
 	swap_log_op.h \
 	SwapDir.cc \
 	SwapDir.h \
 	tests/stub_access_log.cc \
 	tests/stub_cache_cf.cc \
 	tests/stub_comm.cc \
 	tests/stub_debug.cc \
 	tests/stub_DelayId.cc \
 	tests/stub_DiskIOModule.cc \
+	tests/stub_errorpage.cc \
 	tests/stub_fd.cc \
 	tests/stub_HttpRequest.cc \
 	tests/stub_MemObject.cc \
 	tests/stub_MemStore.cc \
 	tests/stub_mime.cc \
 	tests/stub_store.cc \
 	tests/stub_store_rebuild.cc \
 	tests/stub_store_stats.cc \
 	tests/stub_store_swapout.cc \
 	tests/stub_tools.cc \
 	tests/stub_cache_manager.cc \
 	tests/testACLMaxUserIP.cc \
 	tests/testACLMaxUserIP.h \
 	tests/testMain.cc \
 	time.cc \
 	url.cc \
 	URL.h \
 	URLScheme.cc \
 	URLScheme.h \
 	mem.cc \
 	MemBuf.cc \
 	wordlist.cc
 nodist_tests_testACLMaxUserIP_SOURCES= \
 	$(TESTSOURCES)
 tests_testACLMaxUserIP_LDADD= \
 	$(AUTH_ACL_LIBS) \
 	ident/libident.la \
 	acl/libacls.la \
 	eui/libeui.la \
 	acl/libstate.la \
 	$(AUTH_LIBS) \
 	acl/libapi.la \
 	anyp/libanyp.la \
 	base/libbase.la \
 	libsquid.la \
 	ip/libip.la \
+	$(SSL_LIBS) \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(DISK_OS_LIBS) \
 	$(REGEXLIB) \
 	$(SQUID_CPPUNIT_LIBS) \
 	$(SSLLIB) \
 	$(COMPAT_LIB) \
 	$(XTRA_LIBS)
 tests_testACLMaxUserIP_LDFLAGS = $(LIBADD_DL)
 tests_testACLMaxUserIP_DEPENDENCIES = \
 	$(SQUID_CPPUNIT_LA)
 
 ## a demonstration test that does nothing but shows the salient points
 ## involved in writing tests.
 tests_testBoilerplate_SOURCES = \
 	tests/testBoilerplate.cc \
 	tests/testMain.cc \
 	tests/testBoilerplate.h \
 	time.cc
@@ -2482,40 +2493,41 @@
 	$(TESTSOURCES) \
 	SquidMath.cc \
 	SquidMath.h \
 	swap_log_op.cc
 
 tests_testStore_LDADD= \
 	$(AUTH_ACL_LIBS) \
 	ident/libident.la \
 	acl/libacls.la \
 	eui/libeui.la \
 	acl/libstate.la \
 	$(AUTH_LIBS) \
 	acl/libapi.la \
 	base/libbase.la \
 	libsquid.la \
 	ip/libip.la \
 	fs/libfs.la \
 	mgr/libmgr.la \
 	ipc/libipc.la \
 	anyp/libanyp.la \
+	$(SSL_LIBS) \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(REGEXLIB) \
 	$(SQUID_CPPUNIT_LIBS) \
 	$(SSLLIB) \
 	CommCalls.o \
 	DnsLookupDetails.o \
 	$(COMPAT_LIB) \
 	$(XTRA_LIBS)
 tests_testStore_LDFLAGS = $(LIBADD_DL)
 tests_testStore_DEPENDENCIES = \
 	$(SQUID_CPPUNIT_LA)
 
 ## string needs mem.cc.
 ## mem.cc needs ClientInfo.h
 ## libsquid pulls in SquidConfig and children. stub them.
 tests_testString_SOURCES = \
 	ClientInfo.h \
 	mem.cc \

=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc	2012-06-22 03:49:38 +0000
+++ src/cache_cf.cc	2012-06-27 15:19:10 +0000
@@ -73,40 +73,42 @@
 #include "mgr/Registration.h"
 #include "Parsing.h"
 #include "rfc1738.h"
 #if SQUID_SNMP
 #include "snmp.h"
 #endif
 #include "Store.h"
 #include "StoreFileSystem.h"
 #include "SwapDir.h"
 #include "wordlist.h"
 #include "ipc/Kids.h"
 
 #if HAVE_GLOB_H
 #include <glob.h>
 #endif
 
 #if HAVE_LIMITS_H
 #include <limits>
 #endif
 
+#include <list>
+
 #if USE_SSL
 #include "ssl/gadgets.h"
 #endif
 
 #if USE_ADAPTATION
 static void parse_adaptation_service_set_type();
 static void parse_adaptation_service_chain_type();
 static void parse_adaptation_access_type();
 static void parse_adaptation_meta_type(Adaptation::Config::MetaHeaders *);
 static void dump_adaptation_meta_type(StoreEntry *, const char *, Adaptation::Config::MetaHeaders &);
 static void free_adaptation_meta_type(Adaptation::Config::MetaHeaders *);
 #endif
 
 #if ICAP_CLIENT
 static void parse_icap_service_type(Adaptation::Icap::Config *);
 static void dump_icap_service_type(StoreEntry *, const char *, const Adaptation::Icap::Config &);
 static void free_icap_service_type(Adaptation::Icap::Config *);
 static void parse_icap_class_type();
 static void parse_icap_access_type();
 
@@ -152,47 +154,50 @@
 static uint64_t parseTimeUnits(const char *unit,  bool allowMsec);
 static void parseTimeLine(time_msec_t * tptr, const char *units, bool allowMsec);
 static void parse_u_short(unsigned short * var);
 static void parse_string(char **);
 static void default_all(void);
 static void defaults_if_none(void);
 static int parse_line(char *);
 static void parse_obsolete(const char *);
 static void parseBytesLine(size_t * bptr, const char *units);
 #if USE_SSL
 static void parseBytesOptionValue(size_t * bptr, const char *units, char const * value);
 #endif
 #if !USE_DNSHELPER
 static void parseBytesLineSigned(ssize_t * bptr, const char *units);
 #endif
 static size_t parseBytesUnits(const char *unit);
 static void free_all(void);
 void requirePathnameExists(const char *name, const char *path);
 static OBJH dump_config;
 #if USE_HTTP_VIOLATIONS
-static void dump_http_header_access(StoreEntry * entry, const char *name, header_mangler header[]);
-static void parse_http_header_access(header_mangler header[]);
-static void free_http_header_access(header_mangler header[]);
-static void dump_http_header_replace(StoreEntry * entry, const char *name, header_mangler header[]);
-static void parse_http_header_replace(header_mangler * header);
-static void free_http_header_replace(header_mangler * header);
-#endif
+static void dump_http_header_access(StoreEntry * entry, const char *name, const HeaderManglers *manglers);
+static void parse_http_header_access(HeaderManglers **manglers);
+static void free_http_header_access(HeaderManglers **manglers);
+static void dump_http_header_replace(StoreEntry * entry, const char *name, const HeaderManglers *manglers);
+static void parse_http_header_replace(HeaderManglers **manglers);
+static void free_http_header_replace(HeaderManglers **manglers);
+#endif
+static void dump_HeaderWithAclList(StoreEntry * entry, const char *name, HeaderWithAclList *headers);
+static void parse_HeaderWithAclList(HeaderWithAclList **header);
+static void free_HeaderWithAclList(HeaderWithAclList **header);
 static void parse_denyinfo(acl_deny_info_list ** var);
 static void dump_denyinfo(StoreEntry * entry, const char *name, acl_deny_info_list * var);
 static void free_denyinfo(acl_deny_info_list ** var);
 
 #if USE_WCCPv2
 static void parse_IpAddress_list(Ip::Address_list **);
 static void dump_IpAddress_list(StoreEntry *, const char *, const Ip::Address_list *);
 static void free_IpAddress_list(Ip::Address_list **);
 #if CURRENTLY_UNUSED
 static int check_null_IpAddress_list(const Ip::Address_list *);
 #endif /* CURRENTLY_UNUSED */
 #endif /* USE_WCCPv2 */
 
 static void parsePortCfg(AnyP::PortCfg **, const char *protocol);
 #define parse_PortCfg(l) parsePortCfg((l), token)
 static void dump_PortCfg(StoreEntry *, const char *, const AnyP::PortCfg *);
 static void free_PortCfg(AnyP::PortCfg **);
 
 static void parse_b_size_t(size_t * var);
 static void parse_b_int64_t(int64_t * var);
@@ -793,41 +798,42 @@
             debugs(22, 1, "WARNING: use of 'ignore-auth' in 'refresh_pattern' violates HTTP");
 
             break;
         }
 
     }
 #endif
 #if !USE_HTTP_VIOLATIONS
     Config.onoff.via = 1;
 #else
 
     if (!Config.onoff.via)
         debugs(22, 1, "WARNING: HTTP requires the use of Via");
 
 #endif
 
     // we enable runtime PURGE checks if there is at least one PURGE method ACL
     // TODO: replace with a dedicated "purge" ACL option?
     Config2.onoff.enable_purge = (ACLMethodData::ThePurgeCount > 0);
 
-    Config2.onoff.mangle_request_headers = httpReqHdrManglersConfigured();
+    Config2.onoff.mangle_request_headers =
+        Config.request_header_access != NULL;
 
     if (geteuid() == 0) {
         if (NULL != Config.effectiveUser) {
 
             struct passwd *pwd = getpwnam(Config.effectiveUser);
 
             if (NULL == pwd) {
                 /*
                  * Andres Kroonmaa <andre@online.ee>:
                  * Some getpwnam() implementations (Solaris?) require
                  * an available FD < 256 for opening a FILE* to the
                  * passwd file.
                  * DW:
                  * This should be safe at startup, but might still fail
                  * during reconfigure.
                  */
                 fatalf("getpwnam failed to find userid for effective user '%s'",
                        Config.effectiveUser);
                 return;
             }
@@ -1658,168 +1664,108 @@
 parse_client_delay_pool_count(ClientDelayConfig * cfg)
 {
     cfg->parsePoolCount();
 }
 
 static void
 parse_client_delay_pool_rates(ClientDelayConfig * cfg)
 {
     cfg->parsePoolRates();
 }
 
 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, header_mangler header[])
+dump_http_header_access(StoreEntry * entry, const char *name, const HeaderManglers *manglers)
 {
-    int i;
-
-    for (i = 0; i < HDR_ENUM_END; i++) {
-        if (header[i].access_list != NULL) {
-            storeAppendPrintf(entry, "%s ", name);
-            dump_acl_access(entry, httpHeaderNameById(i),
-                            header[i].access_list);
-        }
-    }
+    if (manglers)
+        manglers->dumpAccess(entry, name);
 }
 
 static void
-parse_http_header_access(header_mangler header[])
+parse_http_header_access(HeaderManglers **pm)
 {
-    int id, i;
     char *t = NULL;
 
     if ((t = strtok(NULL, w_space)) == NULL) {
         debugs(3, 0, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line);
         debugs(3, 0, "parse_http_header_access: missing header name.");
         return;
     }
 
-    /* Now lookup index of header. */
-    id = httpHeaderIdByNameDef(t, strlen(t));
-
-    if (strcmp(t, "All") == 0)
-        id = HDR_ENUM_END;
-    else if (strcmp(t, "Other") == 0)
-        id = HDR_OTHER;
-    else if (id == -1) {
-        debugs(3, 0, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line);
-        debugs(3, 0, "parse_http_header_access: unknown header name '" << t << "'");
-        return;
-    }
-
-    if (id != HDR_ENUM_END) {
-        parse_acl_access(&header[id].access_list);
-    } else {
-        char *next_string = t + strlen(t) - 1;
-        *next_string = 'A';
-        *(next_string + 1) = ' ';
-
-        for (i = 0; i < HDR_ENUM_END; i++) {
-            char *new_string = xstrdup(next_string);
-            strtok(new_string, w_space);
-            parse_acl_access(&header[i].access_list);
-            safe_free(new_string);
-        }
-    }
+    if (!*pm)
+        *pm = new HeaderManglers;
+    HeaderManglers *manglers = *pm;
+    header_mangler *mangler = manglers->track(t);
+    assert(mangler);
+    parse_acl_access(&mangler->access_list);
 }
 
 static void
-free_http_header_access(header_mangler header[])
+free_http_header_access(HeaderManglers **pm)
 {
-    int i;
-
-    for (i = 0; i < HDR_ENUM_END; i++) {
-        free_acl_access(&header[i].access_list);
+    // 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, header_mangler
-                         header[])
+dump_http_header_replace(StoreEntry * entry, const char *name, const HeaderManglers *manglers)
 {
-    int i;
-
-    for (i = 0; i < HDR_ENUM_END; i++) {
-        if (NULL == header[i].replacement)
-            continue;
-
-        storeAppendPrintf(entry, "%s %s %s\n", name, httpHeaderNameById(i),
-                          header[i].replacement);
-    }
+    if (manglers)
+        manglers->dumpReplacement(entry, name);
 }
 
 static void
-parse_http_header_replace(header_mangler header[])
+parse_http_header_replace(HeaderManglers **pm)
 {
-    int id, i;
     char *t = NULL;
 
     if ((t = strtok(NULL, w_space)) == NULL) {
         debugs(3, 0, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line);
         debugs(3, 0, "parse_http_header_replace: missing header name.");
         return;
     }
 
-    /* Now lookup index of header. */
-    id = httpHeaderIdByNameDef(t, strlen(t));
-
-    if (strcmp(t, "All") == 0)
-        id = HDR_ENUM_END;
-    else if (strcmp(t, "Other") == 0)
-        id = HDR_OTHER;
-    else if (id == -1) {
-        debugs(3, 0, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line);
-        debugs(3, 0, "parse_http_header_replace: unknown header name " << t << ".");
-
-        return;
-    }
-
-    if (id != HDR_ENUM_END) {
-        if (header[id].replacement != NULL)
-            safe_free(header[id].replacement);
-
-        header[id].replacement = xstrdup(t + strlen(t) + 1);
-    } else {
-        for (i = 0; i < HDR_ENUM_END; i++) {
-            if (header[i].replacement != NULL)
-                safe_free(header[i].replacement);
+    const char *value = t + strlen(t) + 1;
 
-            header[i].replacement = xstrdup(t + strlen(t) + 1);
-        }
-    }
+    if (!*pm)
+        *pm = new HeaderManglers;
+    HeaderManglers *manglers = *pm;
+    manglers->setReplacement(t, value);
 }
 
 static void
-free_http_header_replace(header_mangler header[])
+free_http_header_replace(HeaderManglers **pm)
 {
-    int i;
-
-    for (i = 0; i < HDR_ENUM_END; i++) {
-        if (header[i].replacement != NULL)
-            safe_free(header[i].replacement);
+    // we delete the entire http_header_* mangler configuration at once
+    if (const HeaderManglers *manglers = *pm) {
+        delete manglers;
+        *pm = NULL;
     }
 }
 
 #endif
 
 static void
 dump_cachedir(StoreEntry * entry, const char *name, SquidConfig::_cacheSwap swap)
 {
     SwapDir *s;
     int i;
     assert (entry);
 
     for (i = 0; i < swap.n_configured; i++) {
         s = dynamic_cast<SwapDir *>(swap.swapDirs[i].getRaw());
         if (!s) continue;
         storeAppendPrintf(entry, "%s %s %s", name, s->type(), s->path);
         s->dump(*entry);
         storeAppendPrintf(entry, "\n");
     }
 }
@@ -4366,20 +4312,72 @@
 
     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
+
+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) {
+        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);
+    hwa.fieldValue = buf.termedBuf();
+    hwa.quoted = wasQuoted;
+    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);
+    }
+    delete *header;
+    *header = NULL;
+}

=== modified file 'src/cf.data.depend'
--- src/cf.data.depend	2012-04-25 05:29:20 +0000
+++ src/cf.data.depend	2012-05-18 13:26:54 +0000
@@ -14,40 +14,41 @@
 cachedir		cache_replacement_policy
 cachemgrpasswd
 ConfigAclTos
 CpuAffinityMap
 debug
 delay_pool_access	acl	delay_class
 delay_pool_class	delay_pools
 delay_pool_count
 delay_pool_rates	delay_class
 client_delay_pool_access	acl
 client_delay_pool_count
 client_delay_pool_rates
 denyinfo		acl
 eol
 externalAclHelper	auth_param
 HelperChildConfig
 hostdomain		cache_peer
 hostdomaintype		cache_peer
 http_header_access	acl
 http_header_replace
+HeaderWithAclList	acl
 adaptation_access_type	adaptation_service_set adaptation_service_chain acl icap_service icap_class
 adaptation_service_set_type	icap_service ecap_service
 adaptation_service_chain_type	icap_service ecap_service
 adaptation_meta_type	acl
 icap_access_type	icap_class acl
 icap_class_type		icap_service
 icap_service_type
 icap_service_failure_limit
 ecap_service_type
 int
 kb_int64_t
 kb_size_t
 logformat
 YesNoNone
 memcachemode
 obsolete
 onoff
 peer
 peer_access		cache_peer acl
 PortCfg

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre	2012-05-06 01:29:22 +0000
+++ src/cf.data.pre	2012-05-22 14:42:19 +0000
@@ -4375,62 +4375,76 @@
 DOC_END
 
 NAME: request_entities
 TYPE: onoff
 LOC: Config.onoff.request_entities
 DEFAULT: off
 DOC_START
 	Squid defaults to deny GET and HEAD requests with request entities,
 	as the meaning of such requests are undefined in the HTTP standard
 	even if not explicitly forbidden.
 
 	Set this directive to on if you have clients which insists
 	on sending request entities in GET or HEAD requests. But be warned
 	that there is server software (both proxies and web servers) which
 	can fail to properly process this kind of request which may make you
 	vulnerable to cache pollution attacks if enabled.
 DOC_END
 
 NAME: request_header_access
 IFDEF: USE_HTTP_VIOLATIONS
-TYPE: http_header_access[]
+TYPE: http_header_access
 LOC: Config.request_header_access
 DEFAULT: none
 DOC_START
 	Usage: request_header_access header_name allow|deny [!]aclname ...
 
 	WARNING: Doing this VIOLATES the HTTP standard.  Enabling
 	this feature could make you liable for problems which it
 	causes.
 
 	This option replaces the old 'anonymize_headers' and the
 	older 'http_anonymizer' option with something that is much
-	more configurable. This new method creates a list of ACLs
-	for each header, allowing you very fine-tuned header
-	mangling.
-
-	This option only applies to request headers, i.e., from the
-	client to the server.
-
-	You can only specify known headers for the header name.
-	Other headers are reclassified as 'Other'. You can also
-	refer to all the headers with 'All'.
+	more configurable. A a list of ACLs for each header name allows
+	removal of specific header fields under specific conditions.
+
+	This option only applies to outgoing HTTP request headers (i.e.,
+	headers sent by Squid to the next HTTP hop such as a cache peer
+	or an origin server). The option has no effect during cache hit
+	detection. The equivalent adaptation vectoring point in ICAP
+	terminology is post-cache REQMOD.
+
+	The option is applied to individual outgoing request header
+	fields. For each request header field F, Squid uses the first
+	qualifying sets of request_header_access rules:
+
+	    1. Rules with header_name equal to F's name.
+	    2. Rules with header_name 'Other', provided F's name is not
+	       on the hard-coded list of commonly used HTTP header names.
+	    3. Rules with header_name 'All'.
+
+	Within that qualifying rule set, rule ACLs are checked as usual.
+	If ACLs of an "allow" rule match, the header field is allowed to
+	go through as is. If ACLs of a "deny" rule match, the header
+	field is removed (unless its value is replaced by the
+	corresponding request_header_replace option). If no rules within
+	the set have matching ACLs, the header field is left as is.
 
 	For example, to achieve the same behavior as the old
 	'http_anonymizer standard' option, you should use:
 
 		request_header_access From deny all
 		request_header_access Referer deny all
 		request_header_access Server deny all
 		request_header_access User-Agent deny all
 		request_header_access WWW-Authenticate deny all
 		request_header_access Link deny all
 
 	Or, to reproduce the old 'http_anonymizer paranoid' feature
 	you should use:
 
 		request_header_access Allow allow all
 		request_header_access Authorization allow all
 		request_header_access WWW-Authenticate allow all
 		request_header_access Proxy-Authorization allow all
 		request_header_access Proxy-Authenticate allow all
 		request_header_access Cache-Control allow all
@@ -4447,65 +4461,56 @@
 		request_header_access Accept allow all
 		request_header_access Accept-Charset allow all
 		request_header_access Accept-Encoding allow all
 		request_header_access Accept-Language allow all
 		request_header_access Content-Language allow all
 		request_header_access Mime-Version allow all
 		request_header_access Retry-After allow all
 		request_header_access Title allow all
 		request_header_access Connection allow all
 		request_header_access All deny all
 
 	although many of those are HTTP reply headers, and so should be
 	controlled with the reply_header_access directive.
 
 	By default, all headers are allowed (no anonymizing is
 	performed).
 DOC_END
 
 NAME: reply_header_access
 IFDEF: USE_HTTP_VIOLATIONS
-TYPE: http_header_access[]
+TYPE: http_header_access
 LOC: Config.reply_header_access
 DEFAULT: none
 DOC_START
 	Usage: reply_header_access header_name allow|deny [!]aclname ...
 
 	WARNING: Doing this VIOLATES the HTTP standard.  Enabling
 	this feature could make you liable for problems which it
 	causes.
 
 	This option only applies to reply headers, i.e., from the
 	server to the client.
 
 	This is the same as request_header_access, but in the other
-	direction.
-
-	This option replaces the old 'anonymize_headers' and the
-	older 'http_anonymizer' option with something that is much
-	more configurable. This new method creates a list of ACLs
-	for each header, allowing you very fine-tuned header
-	mangling.
-
-	You can only specify known headers for the header name.
-	Other headers are reclassified as 'Other'. You can also
-	refer to all the headers with 'All'.
+	direction. Please see request_header_access for detailed
+	documentation.
 
 	For example, to achieve the same behavior as the old
 	'http_anonymizer standard' option, you should use:
 
 		reply_header_access From deny all
 		reply_header_access Referer deny all
 		reply_header_access Server deny all
 		reply_header_access User-Agent deny all
 		reply_header_access WWW-Authenticate deny all
 		reply_header_access Link deny all
 
 	Or, to reproduce the old 'http_anonymizer paranoid' feature
 	you should use:
 
 		reply_header_access Allow allow all
 		reply_header_access Authorization allow all
 		reply_header_access WWW-Authenticate allow all
 		reply_header_access Proxy-Authorization allow all
 		reply_header_access Proxy-Authenticate allow all
 		reply_header_access Cache-Control allow all
@@ -4522,75 +4527,126 @@
 		reply_header_access Accept allow all
 		reply_header_access Accept-Charset allow all
 		reply_header_access Accept-Encoding allow all
 		reply_header_access Accept-Language allow all
 		reply_header_access Content-Language allow all
 		reply_header_access Mime-Version allow all
 		reply_header_access Retry-After allow all
 		reply_header_access Title allow all
 		reply_header_access Connection allow all
 		reply_header_access All deny all
 
 	although the HTTP request headers won't be usefully controlled
 	by this directive -- see request_header_access for details.
 
 	By default, all headers are allowed (no anonymizing is
 	performed).
 DOC_END
 
 NAME: request_header_replace header_replace
 IFDEF: USE_HTTP_VIOLATIONS
-TYPE: http_header_replace[]
+TYPE: http_header_replace
 LOC: Config.request_header_access
 DEFAULT: none
 DOC_START
 	Usage:   request_header_replace header_name message
 	Example: request_header_replace User-Agent Nutscrape/1.0 (CP/M; 8-bit)
 
 	This option allows you to change the contents of headers
 	denied with request_header_access above, by replacing them
 	with some fixed string. This replaces the old fake_user_agent
 	option.
 
 	This only applies to request headers, not reply headers.
 
 	By default, headers are removed if denied.
 DOC_END
 
 NAME: reply_header_replace
 IFDEF: USE_HTTP_VIOLATIONS
-TYPE: http_header_replace[]
+TYPE: http_header_replace
 LOC: Config.reply_header_access
 DEFAULT: none
 DOC_START
         Usage:   reply_header_replace header_name message
         Example: reply_header_replace Server Foo/1.0
 
         This option allows you to change the contents of headers
         denied with reply_header_access above, by replacing them
         with some fixed string.
 
         This only applies to reply headers, not request headers.
 
         By default, headers are removed if denied.
 DOC_END
 
+NAME: request_header_add
+TYPE: HeaderWithAclList
+LOC: Config.request_header_add
+DEFAULT: none
+DOC_START
+	Usage:   request_header_add field-name field-value acl1 [acl2] ...
+	Example: request_header_add X-Client-CA "CA=%user_ca" all
+
+	This option adds header fields to outgoing HTTP requests (i.e.,
+	request headers sent by Squid to the next HTTP hop such as a
+	cache peer or an origin server). The option has no effect during
+	cache hit detection. The equivalent adaptation vectoring point
+	in ICAP terminology is post-cache REQMOD.
+
+	Field-name is a token specifying an HTTP header name. If a
+	standard HTTP header name is used, Squid does not check whether
+	the new header conflicts with any existing headers or violates
+	HTTP rules. If the request to be modified already contains a
+	field with the same name, the old field is preserved but the
+	header field values are not merged.
+
+	Field-value is either a token or a quoted string. If quoted
+	string format is used, then the surrounding quotes are removed
+	while escape sequences and %macros are processed. The following
+	field-value macros are supported:
+
+	    %LOGIN: Authenticated user login name or an empty string if
+	    no authentication used.
+
+	    %SRC: Client IP address
+
+	    %DST: Requested host
+
+	    %user_cert_subject: The subject line of the received client
+	    certificate or an empty string if Squid received no client
+	    certificate or received an invalid/malformed certificate.
+
+	    %user_ca: The name of the SSL certificate issuer of the
+	    received client certificate or an empty string if Squid
+	    received no client certificate or received an invalid or
+	    malformed certificate.
+
+	    %%: A single percent symbol (%).
+
+	One or more Squid ACLs may be specified to restrict header
+	injection to matching requests. As always in squid.conf, all
+	ACLs in an option ACL list must be satisfied for the insertion
+	to happen. The request_header_add option supports fast ACLs
+	only.
+DOC_END
+
 NAME: relaxed_header_parser
 COMMENT: on|off|warn
 TYPE: tristate
 LOC: Config.onoff.relaxed_header_parser
 DEFAULT: on
 DOC_START
 	In the default "on" setting Squid accepts certain forms
 	of non-compliant HTTP messages where it is unambiguous
 	what the sending application intended even if the message
 	is not correctly formatted. The messages is then normalized
 	to the correct form when forwarded by Squid.
 
 	If set to "warn" then a warning will be emitted in cache.log
 	each time such HTTP error is encountered.
 
 	If set to "off" then such HTTP errors will cause the request
 	or response to be rejected.
 DOC_END
 
 COMMENT_START

=== modified file 'src/http.cc'
--- src/http.cc	2012-06-16 15:03:46 +0000
+++ src/http.cc	2012-06-27 14:45:11 +0000
@@ -71,40 +71,42 @@
 #include "StatCounters.h"
 #include "Store.h"
 
 
 #define SQUID_ENTER_THROWING_CODE() try {
 #define SQUID_EXIT_THROWING_CODE(status) \
   	status = true; \
     } \
     catch (const std::exception &e) { \
 	debugs (11, 1, "Exception error:" << e.what()); \
 	status = false; \
     }
 
 CBDATA_CLASS_INIT(HttpStateData);
 
 static const char *const crlf = "\r\n";
 
 static void httpMaybeRemovePublic(StoreEntry *, http_status);
 static void copyOneHeaderFromClientsideRequestToUpstreamRequest(const HttpHeaderEntry *e, const String strConnection, const HttpRequest * request,
         HttpHeader * hdr_out, const int we_do_ranges, const http_state_flags);
+//Declared in HttpHeaderTools.cc
+void httpHdrAdd(HttpHeader *heads, HttpRequest *request, HeaderWithAclList &headers_add);
 
 HttpStateData::HttpStateData(FwdState *theFwdState) : AsyncJob("HttpStateData"), ServerStateData(theFwdState),
         lastChunk(0), header_bytes_read(0), reply_bytes_read(0),
         body_bytes_truncated(0), httpChunkDecoder(NULL)
 {
     debugs(11,5,HERE << "HttpStateData " << this << " created");
     ignoreCacheControl = false;
     surrogateNoStore = false;
     serverConnection = fwd->serverConnection();
     readBuf = new MemBuf;
     readBuf->init(16*1024, 256*1024);
 
     // reset peer response time stats for %<pt
     request->hier.peer_http_request_sent.tv_sec = 0;
     request->hier.peer_http_request_sent.tv_usec = 0;
 
     if (fwd->serverConnection() != NULL)
         _peer = cbdataReference(fwd->serverConnection()->getPeer());         /* might be NULL */
 
     if (_peer) {
@@ -1768,40 +1770,43 @@
     if (flags.keepalive) {
         hdr_out->putStr(HDR_CONNECTION, "keep-alive");
     }
 
     /* append Front-End-Https */
     if (flags.front_end_https) {
         if (flags.front_end_https == 1 || request->protocol == AnyP::PROTO_HTTPS)
             hdr_out->putStr(HDR_FRONT_END_HTTPS, "On");
     }
 
     if (flags.chunked_request) {
         // Do not just copy the original value so that if the client-side
         // starts decode other encodings, this code may remain valid.
         hdr_out->putStr(HDR_TRANSFER_ENCODING, "chunked");
     }
 
     /* Now mangle the headers. */
     if (Config2.onoff.mangle_request_headers)
         httpHdrMangleList(hdr_out, request, ROR_REQUEST);
 
+    if (Config.request_header_add && !Config.request_header_add->empty())
+        httpHdrAdd(hdr_out, request, *Config.request_header_add);
+
     strConnection.clean();
 }
 
 /**
  * Decides whether a particular header may be cloned from the received Clients request
  * to our outgoing fetch request.
  */
 void
 copyOneHeaderFromClientsideRequestToUpstreamRequest(const HttpHeaderEntry *e, const String strConnection, const HttpRequest * request, HttpHeader * hdr_out, const int we_do_ranges, const http_state_flags flags)
 {
     debugs(11, 5, "httpBuildRequestHeader: " << e->name << ": " << e->value );
 
     switch (e->id) {
 
         /** \par RFC 2616 sect 13.5.1 - Hop-by-Hop headers which Squid should not pass on. */
 
     case HDR_PROXY_AUTHORIZATION:
         /** \par Proxy-Authorization:
          * Only pass on proxy authentication to peers for which
          * authentication forwarding is explicitly enabled

=== modified file 'src/protos.h'
--- src/protos.h	2012-04-25 05:29:20 +0000
+++ src/protos.h	2012-05-18 13:26:54 +0000
@@ -217,41 +217,40 @@
 SQUIDCEXTERN const char *httpHeaderNameById(int id);
 SQUIDCEXTERN int httpHeaderHasConnDir(const HttpHeader * hdr, const char *directive);
 SQUIDCEXTERN void strListAdd(String * str, const char *item, char del);
 SQUIDCEXTERN int strListIsMember(const String * str, const char *item, char del);
 SQUIDCEXTERN int strListIsSubstr(const String * list, const char *s, char del);
 SQUIDCEXTERN int strListGetItem(const String * str, char del, const char **item, int *ilen, const char **pos);
 SQUIDCEXTERN const char *getStringPrefix(const char *str, const char *end);
 SQUIDCEXTERN int httpHeaderParseInt(const char *start, int *val);
 SQUIDCEXTERN int httpHeaderParseOffset(const char *start, int64_t * off);
 SQUIDCEXTERN void
 httpHeaderPutStrf(HttpHeader * hdr, http_hdr_type id, const char *fmt,...) PRINTF_FORMAT_ARG3;
 
 
 /* Http Header */
 SQUIDCEXTERN void httpHeaderInitModule(void);
 SQUIDCEXTERN void httpHeaderCleanModule(void);
 
 /* store report about current header usage and other stats */
 void httpHeaderStoreReport(StoreEntry * e);
 SQUIDCEXTERN void httpHdrMangleList(HttpHeader *, HttpRequest *, int req_or_rep);
-SQUIDCEXTERN int httpReqHdrManglersConfigured();
 
 #if SQUID_SNMP
 SQUIDCEXTERN PF snmpHandleUdp;
 SQUIDCEXTERN void snmpInit(void);
 SQUIDCEXTERN void snmpOpenPorts(void);
 SQUIDCEXTERN void snmpClosePorts(void);
 SQUIDCEXTERN const char * snmpDebugOid(oid * Name, snint Len, MemBuf &outbuf);
 
 SQUIDCEXTERN void addr2oid(Ip::Address &addr, oid *Dest);
 SQUIDCEXTERN void oid2addr(oid *Dest, Ip::Address &addr, u_int code);
 
 SQUIDCEXTERN Ip::Address *client_entry(Ip::Address *current);
 extern variable_list *snmp_basicFn(variable_list *, snint *);
 extern variable_list *snmp_confFn(variable_list *, snint *);
 extern variable_list *snmp_sysFn(variable_list *, snint *);
 extern variable_list *snmp_prfSysFn(variable_list *, snint *);
 extern variable_list *snmp_prfProtoFn(variable_list *, snint *);
 extern variable_list *snmp_prfPeerFn(variable_list *, snint *);
 extern variable_list *snmp_netIpFn(variable_list *, snint *);
 extern variable_list *snmp_netFqdnFn(variable_list *, snint *);

=== modified file 'src/structs.h'
--- src/structs.h	2012-04-26 01:04:17 +0000
+++ src/structs.h	2012-05-25 20:13:57 +0000
@@ -26,63 +26,109 @@
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  */
 #ifndef SQUID_STRUCTS_H
 #define SQUID_STRUCTS_H
 
 #include "RefCount.h"
 #include "cbdata.h"
 #include "dlink.h"
 #include "err_type.h"
 
 /* needed for the global config */
 #include "HttpHeader.h"
 
 /* for ICP_END */
 #include "icp_opcode.h"
 
 #if USE_SSL
 #include <openssl/ssl.h>
 #endif
+#include <list>
+#if HAVE_STRING
+#include <string>
+#endif
 
 #define PEER_MULTICAST_SIBLINGS 1
 
 struct acl_name_list {
     char name[ACL_NAME_SZ];
     acl_name_list *next;
 };
 
 struct acl_deny_info_list {
     err_type err_page_id;
     char *err_page_name;
     acl_name_list *acl_list;
     acl_deny_info_list *next;
 };
 
 
 class acl_access;
 
 struct _header_mangler {
     acl_access *access_list;
     char *replacement;
 };
 
+#include <map>
+#include <string>
+
+/// A collection of header_mangler objects for a given message kind.
+class HeaderManglers {
+public:
+    HeaderManglers();
+    ~HeaderManglers();
+
+    /// returns a header mangler for field e or nil if none was specified
+    const header_mangler *find(const HttpHeaderEntry &e) const;
+
+    /// returns a mangler for the named header (known or custom)
+    header_mangler *track(const char *name);
+
+    /// updates mangler for the named header with a replacement value
+    void setReplacement(const char *name, const char *replacementValue);
+
+    /// report the *_header_access part of the configuration
+    void dumpAccess(StoreEntry *entry, const char *optionName) const;
+    /// report the *_header_replace part of the configuration
+    void dumpReplacement(StoreEntry *entry, const char *optionName) const;
+
+private:
+    /// a name:mangler map; optimize: use unordered map or some such
+    typedef std::map<std::string, header_mangler> ManglersByName;
+
+    /// one mangler for each known header
+    header_mangler known[HDR_ENUM_END];
+
+    /// one mangler for each custom header
+    ManglersByName custom;
+
+    /// configured if some mangling ACL applies to all header names
+    header_mangler all;
+
+private:
+    /* not implemented */
+    HeaderManglers(const HeaderManglers &);
+    HeaderManglers &operator =(const HeaderManglers &);
+};
+
 class ACLChecklist;
 
 #if SQUID_SNMP
 
 struct _snmp_request_t {
     u_char *buf;
     u_char *outbuf;
     int len;
     int sock;
     long reqid;
     int outlen;
 
     Ip::Address from;
 
     struct snmp_pdu *PDU;
     ACLChecklist *acl_checklist;
     u_char *community;
 
     struct snmp_session session;
 };
@@ -133,40 +179,42 @@
 #include "ClientDelayConfig.h"
 #endif
 
 #if USE_ICMP
 #include "icmp/IcmpConfig.h"
 #endif
 
 #include "HelperChildConfig.h"
 
 /* forward decl for SquidConfig, see RemovalPolicy.h */
 
 class CpuAffinityMap;
 class RemovalPolicySettings;
 class external_acl;
 class Store;
 namespace AnyP
 {
 struct PortCfg;
 }
 class SwapDir;
+class HeaderWithAcl;
+typedef std::list<HeaderWithAcl> HeaderWithAclList;
 
 /// Used for boolean enabled/disabled options with complex default logic.
 /// Allows Squid to compute the right default after configuration.
 /// Checks that not-yet-defined option values are not used.
 class YesNoNone
 {
 // TODO: generalize to non-boolean option types
 public:
     YesNoNone(): option(0) {}
 
     /// returns true iff enabled; asserts if the option has not been configured
     operator void *() const; // TODO: use a fancy/safer version of the operator
 
     /// enables or disables the option;
     void configure(bool beSet);
 
     /// whether the option was enabled or disabled, by user or Squid
     bool configured() const { return option != 0; }
 
 private:
@@ -560,44 +608,46 @@
     struct {
         struct {
             int average;
             int min_poll;
         } dns, udp, tcp;
     } comm_incoming;
     int max_open_disk_fds;
     int uri_whitespace;
     acl_size_t *rangeOffsetLimit;
 #if MULTICAST_MISS_STREAM
 
     struct {
 
         Ip::Address addr;
         int ttl;
         unsigned short port;
         char *encode_key;
     } mcast_miss;
 #endif
 
-    /* one access list per header type we know of */
-    header_mangler request_header_access[HDR_ENUM_END];
-    /* one access list per header type we know of */
-    header_mangler reply_header_access[HDR_ENUM_END];
+    /// request_header_access and request_header_replace
+    HeaderManglers *request_header_access;
+    /// reply_header_access and reply_header_replace
+    HeaderManglers *reply_header_access;
+    ///request_header_add access list
+    HeaderWithAclList *request_header_add;
     char *coredump_dir;
     char *chroot_dir;
 #if USE_CACHE_DIGESTS
 
     struct {
         int bits_per_entry;
         time_t rebuild_period;
         time_t rewrite_period;
         size_t swapout_chunk_size;
         int rebuild_chunk_percentage;
     } digest;
 #endif
 #if USE_SSL
 
     struct {
         int unclean_shutdown;
         char *ssl_engine;
     } SSL;
 #endif
 
@@ -1091,21 +1141,31 @@
     int cancelcount;		/* # SWAP_LOG_DEL objects purged */
     int invalid;		/* # bad lines */
     int badflags;		/* # bad e->flags */
     int bad_log_op;
     int zero_object_sz;
 };
 
 class Logfile;
 
 #include "format/Format.h"
 #include "log/Formats.h"
 struct _customlog {
     char *filename;
     ACLList *aclList;
     Format::Format *logFormat;
     Logfile *logfile;
     customlog *next;
     Log::Format::log_type type;
 };
 
+class HeaderWithAcl {
+public:
+HeaderWithAcl() : fieldId (HDR_BAD_HDR), quoted(false), aclList(NULL) {}
+    http_hdr_type fieldId;
+    std::string fieldName;
+    std::string fieldValue;
+    bool quoted;
+    ACLList *aclList;
+};
+
 #endif /* SQUID_STRUCTS_H */


