Author: Christos Tsantilas <chtsanti@users.sourceforge.net>
Add request_header_add option 

This patch:

This patch add request_header_add, a new ACL-driven squid.conf option that 
allow 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.

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-29 17:00: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-29 17:00:49 +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-06-29 16:36:58 +0000
+++ src/HttpHeaderTools.cc	2012-06-29 16:41:34 +0000
@@ -18,47 +18,57 @@
  *
  *  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 "HttpHeaderTools.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);
@@ -600,20 +610,143 @@
     // 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/HttpHeaderTools.h'
--- src/HttpHeaderTools.h	2012-06-29 16:36:58 +0000
+++ src/HttpHeaderTools.h	2012-06-29 17:10:47 +0000
@@ -1,30 +1,36 @@
 #ifndef SQUID_HTTPHEADERTOOLS_H
 #define SQUID_HTTPHEADERTOOLS_H
 
+#if HAVE_LIST
+#include <list>
+#endif
 #if HAVE_MAP
 #include <map>
 #endif
 #if HAVE_STRING
 #include <string>
 #endif
 
+class HeaderWithAcl;
+typedef std::list<HeaderWithAcl> HeaderWithAclList;
+
 class acl_access;
 struct _header_mangler {
     acl_access *access_list;
     char *replacement;
 };
 typedef struct _header_mangler header_mangler;
 
 class StoreEntry;
 
 /// 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);
@@ -38,21 +44,32 @@
     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 ACLList;
+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

=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc	2012-06-29 16:36:58 +0000
+++ src/cache_cf.cc	2012-06-29 16:53:07 +0000
@@ -73,40 +73,44 @@
 #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
 
+#if HAVE_LIST
+#include <list>
+#endif
+
 #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();
 
@@ -160,40 +164,43 @@
 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 free_HeaderManglers(HeaderManglers **pm);
 static void dump_http_header_access(StoreEntry * entry, const char *name, const HeaderManglers *manglers);
 static void parse_http_header_access(HeaderManglers **manglers);
 #define free_http_header_access free_HeaderManglers
 static void dump_http_header_replace(StoreEntry * entry, const char *name, const HeaderManglers *manglers);
 static void parse_http_header_replace(HeaderManglers **manglers);
 #define free_http_header_replace free_HeaderManglers
 #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);
@@ -4297,20 +4304,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-06-29 16:54:08 +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-06-29 16:36:58 +0000
+++ src/cf.data.pre	2012-06-29 17:08:50 +0000
@@ -4407,44 +4407,44 @@
 	more configurable. 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 is
-	removed and request_header_replace is then checked to identify
-	if the removed header has a replacement. If no rules within the
-	set have matching ACLs, the header field is left as is.
+	go through as is. If ACLs of a "deny" rule match, the header field
+	is either removed or replaced, depending on whether there is a
+	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
@@ -4562,40 +4562,91 @@
 DOC_END
 
 NAME: reply_header_replace
 IFDEF: USE_HTTP_VIOLATIONS
 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-29 16:57:43 +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/structs.h'
--- src/structs.h	2012-06-29 16:36:58 +0000
+++ src/structs.h	2012-06-29 16:58:28 +0000
@@ -557,40 +557,42 @@
         } 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
 
     /// 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
 


