This option allows Squid administrator to add custom ICAP request headers or eCAP options to Squid ICAP requests or eCAP transactions. Use it to pass custom authentication tokens and other transaction-state related meta information to an ICAP/eCAP service.

The addition of a meta header is ACL-driven:
        adaptation_meta name value [!]aclname ...

Processing for a given header name stops after the first ACL list match. Thus, it is impossible to add two headers with the same name. If no ACL lists match for a given header name, no such header is added. For example:

        # do not debug transactions except for those that need debugging
        adaptation_meta X-Debug 1 needs_debugging

        # log all transactions except for those that must remain secret
        adaptation_meta X-Log 1 !keep_secret

        # mark transactions from users in the "G 1" group
        adaptation_meta X-Authenticated-Groups "G 1" authed_as_G1

The "value" parameter may be a regular squid.conf token or a "double quoted string". Within the quoted string, use backslash (\) to escape any character, which is currently only useful for escaping backslashes and double quotes. For example,
    "this string has one backslash (\\) and two \"quotes\""


This is a Measurement Factory project
adaptation_meta option

This option allows Squid administrator to add custom ICAP request
headers or eCAP options to Squid ICAP requests or eCAP transactions.
Use it to pass custom authentication tokens and other
transaction-state related meta information to an ICAP/eCAP service.

The addition of a meta header is ACL-driven:
        adaptation_meta name value [!]aclname ...

Processing for a given header name stops after the first ACL list match.
Thus, it is impossible to add two headers with the same name. If no ACL
lists match for a given header name, no such header is added. For example:

        # do not debug transactions except for those that need debugging
        adaptation_meta X-Debug 1 needs_debugging

        # log all transactions except for those that must remain secret
        adaptation_meta X-Log 1 !keep_secret

        # mark transactions from users in the "G 1" group
        adaptation_meta X-Authenticated-Groups "G 1" authed_as_G1

The "value" parameter may be a regular squid.conf token or a "double
quoted string". Within the quoted string, use backslash (\) to escape
any character, which is currently only useful for escaping backslashes
and double quotes. For example,
    "this string has one backslash (\\) and two \"quotes\""

This is a Measurement Factory project

=== modified file 'src/ConfigParser.cc'
--- src/ConfigParser.cc	2011-01-28 07:58:53 +0000
+++ src/ConfigParser.cc	2011-10-20 20:53:01 +0000
@@ -97,20 +97,86 @@
             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 t;
 }
+
+void
+ConfigParser::ParseQuotedString(char **var)
+{
+    String sVar;
+    ParseQuotedString(&sVar);
+    *var = xstrdup(sVar.termedBuf());
+}
+
+void
+ConfigParser::ParseQuotedString(String *var)
+{
+    // 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);
+        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, 0, "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);
+}
+
+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('\\');
+        quotedStr.append(*s);
+    }
+    quotedStr.append('"');
+    return quotedStr.termedBuf();
+}

=== modified file 'src/ConfigParser.h'
--- src/ConfigParser.h	2011-08-26 16:50:49 +0000
+++ src/ConfigParser.h	2011-10-17 18:19:50 +0000
@@ -50,27 +50,30 @@
 
 /**
  * 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);
+    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/Makefile.am'
--- src/Makefile.am	2011-10-06 16:41:46 +0000
+++ src/Makefile.am	2011-10-20 08:27:31 +0000
@@ -984,40 +984,41 @@
 # globals.cc is needed by test_tools.cc.
 # Neither of these should be disted from here.
 TESTSOURCES= \
 	tests/STUB.h \
 	test_tools.cc \
 	globals.cc
 
 check_PROGRAMS+=\
 	tests/testBoilerplate \
 	tests/testCacheManager \
 	tests/testDiskIO \
 	tests/testEvent \
 	tests/testEventLoop \
 	tests/test_http_range \
 	tests/testHttpParser \
 	tests/testHttpReply \
 	tests/testHttpRequest \
 	tests/testStore \
 	tests/testString \
 	tests/testURL \
+	tests/testConfigParser \
 	$(STORE_TESTS)
 
 ## NP: required to run the above list. check_PROGRAMS only builds the binaries...
 TESTS += $(check_PROGRAMS)
 
 ### Template for new Unit Test Program
 ## - add tests/testX to check_PROGRAMS above.
 ## - copy template below and substitue X for class name
 ## - add other component .(h|cc) files needed to link and run tests
 ##
 ##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)
@@ -3074,32 +3075,62 @@
 	$(REGEXLIB) \
 	$(REPL_OBJS) \
 	$(ADAPTATION_LIBS) \
 	$(ESI_LIBS) \
 	$(SSL_LIBS) \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(COMPAT_LIB) \
 	$(SQUID_CPPUNIT_LIBS) \
 	$(SQUID_CPPUNIT_LA) \
 	$(SSLLIB) \
 	$(KRB5LIBS) \
 	$(COMPAT_LIB) \
 	$(XTRA_LIBS)
 tests_testURL_LDFLAGS = $(LIBADD_DL)
 tests_testURL_DEPENDENCIES = \
 	$(REPL_OBJS) \
 	$(SQUID_CPPUNIT_LA)
 
+tests_testConfigParser_SOURCES = \
+	ClientInfo.h \
+	mem.cc \
+	MemBuf.cc \
+	String.cc \
+	ConfigParser.cc \
+	tests/testMain.cc \
+	tests/testConfigParser.cc \
+	tests/testConfigParser.h \
+	tests/stub_cache_cf.cc \
+	tests/stub_cache_manager.cc \
+	tests/stub_debug.cc \
+	tests/stub_HelperChildConfig.cc \
+	time.cc \
+	wordlist.cc
+nodist_tests_testConfigParser_SOURCES = \
+	$(TESTSOURCES)
+tests_testConfigParser_LDADD = \
+	base/libbase.la \
+	libsquid.la \
+	ip/libip.la \
+	$(top_builddir)/lib/libmiscutil.la \
+	$(REGEXLIB) \
+	$(SQUID_CPPUNIT_LIBS) \
+	$(SSLLIB) \
+	$(COMPAT_LIB) \
+	$(XTRA_LIBS)
+tests_testConfigParser_LDFLAGS = $(LIBADD_DL)
+tests_testConfigParser_DEPENDENCIES = \
+	$(SQUID_CPPUNIT_LA)
 
 TESTS += testHeaders
 
 ## Special Universal .h dependency test script
 ## aborts if error encountered
 testHeaders: $(srcdir)/*.h $(srcdir)/DiskIO/*.h $(srcdir)/DiskIO/*/*.h
 	$(SHELL) $(top_srcdir)/test-suite/testheaders.sh "$(CXXCOMPILE)" $^ || exit 1
 ## src/repl/ has no .h files and its own makefile.
 
 CLEANFILES += testHeaders
 .PHONY: testHeaders
 

=== modified file 'src/acl/Checklist.cc'
--- src/acl/Checklist.cc	2011-07-16 15:21:48 +0000
+++ src/acl/Checklist.cc	2011-10-17 17:37:47 +0000
@@ -349,40 +349,41 @@
 
     debugs(28, 5, "aclCheckFast: list: " << accessList);
     const acl_access *acl = cbdataReference(accessList);
     while (acl != NULL && cbdataReferenceValid(acl)) {
         currentAnswer(acl->allow);
         matchAclList(acl->aclList, true);
         if (finished()) {
             PROF_stop(aclCheckFast);
             cbdataReferenceDone(acl);
             return currentAnswer();
         }
 
         /*
          * Reference the next access entry
          */
         const acl_access *A = acl;
         acl = cbdataReference(acl->next);
         cbdataReferenceDone(A);
     }
 
+    currentAnswer(ACCESS_DUNNO);
     debugs(28, 5, "aclCheckFast: no matches, returning: " << currentAnswer());
     PROF_stop(aclCheckFast);
 
     return currentAnswer();
 }
 
 
 bool
 ACLChecklist::checking() const
 {
     return checking_;
 }
 
 void
 ACLChecklist::checking (bool const newValue)
 {
     checking_ = newValue;
 }
 
 bool

=== modified file 'src/acl/Gadgets.h'
--- src/acl/Gadgets.h	2010-11-21 04:40:05 +0000
+++ src/acl/Gadgets.h	2011-10-20 16:54:18 +0000
@@ -19,22 +19,24 @@
 /// \ingroup ACLAPI
 extern void aclDestroyAclList(ACLList **);
 /// \ingroup ACLAPI
 extern void aclParseAccessLine(ConfigParser &parser, acl_access **);
 /// \ingroup ACLAPI
 extern void aclParseAclList(ConfigParser &parser, ACLList **);
 /// \ingroup ACLAPI
 extern int aclIsProxyAuth(const char *name);
 /// \ingroup ACLAPI
 extern err_type aclGetDenyInfoPage(acl_deny_info_list ** head, const char *name, int redirect_allowed);
 /// \ingroup ACLAPI
 extern void aclParseDenyInfoLine(acl_deny_info_list **);
 /// \ingroup ACLAPI
 extern void aclDestroyDenyInfoList(acl_deny_info_list **);
 /// \ingroup ACLAPI
 extern wordlist *aclDumpGeneric(const ACL *);
 /// \ingroup ACLAPI
 extern void aclCacheMatchFlush(dlink_list * cache);
 /// \ingroup ACLAPI
 extern void dump_acl_access(StoreEntry * entry, const char *name, acl_access * head);
+/// \ingroup ACLAPI
+extern void dump_acl_list(StoreEntry * entry, ACLList * head);
 
 #endif /* SQUID_ACL_GADGETS_H */

=== modified file 'src/adaptation/Config.cc'
--- src/adaptation/Config.cc	2011-08-04 00:13:34 +0000
+++ src/adaptation/Config.cc	2011-10-20 16:56:58 +0000
@@ -20,53 +20,103 @@
  *  (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 "structs.h"
 
 #include "ConfigParser.h"
 #include "acl/Gadgets.h"
 #include "Store.h"
 #include "Array.h"    // really Vector
+#include "acl/FilledChecklist.h"
 #include "adaptation/Config.h"
 #include "adaptation/Service.h"
 #include "adaptation/AccessRule.h"
 #include "adaptation/ServiceGroups.h"
 #include "adaptation/History.h"
+#include "HttpRequest.h"
 
 
 bool Adaptation::Config::Enabled = false;
 char *Adaptation::Config::masterx_shared_name = NULL;
 int Adaptation::Config::service_iteration_limit = 16;
 int Adaptation::Config::send_client_ip = false;
 int Adaptation::Config::send_username = false;
 int Adaptation::Config::use_indirect_client = true;
+Adaptation::Config::MetaHeaders Adaptation::Config::metaHeaders;
+
+
+Adaptation::Config::MetaHeader::Value::~Value()
+{
+    aclDestroyAclList(&aclList);
+}
+
+Adaptation::Config::MetaHeader::Value::Pointer 
+Adaptation::Config::MetaHeader::addValue(const String &value)
+{
+    Value::Pointer v = new Value(value);
+    values.push_back(v);
+    return v;
+}
+
+const char *
+Adaptation::Config::MetaHeader::match(HttpRequest *request, HttpReply *reply)
+{
+
+    typedef Values::iterator VLI;
+    ACLFilledChecklist ch(NULL, request, NULL);
+    if (reply)
+        ch.reply = HTTPMSGLOCK(reply);
+
+    for (VLI i = values.begin(); i != values.end(); ++i ) {
+        const int ret= ch.fastCheck((*i)->aclList); 
+        debugs(93, 5, HERE << "Check for header name: " << name << ": " << (*i)->value
+               <<", HttpRequest: " << request << " HttpReply: " << reply << " matched: " << ret);
+        if (ret == ACCESS_ALLOWED)
+            return (*i)->value.termedBuf();
+    }
+    return NULL;
+}
+
+Adaptation::Config::MetaHeader::Pointer 
+Adaptation::Config::addMetaHeader(const String &headerName)
+{
+    typedef MetaHeaders::iterator AMLI;
+    for(AMLI i = metaHeaders.begin(); i != metaHeaders.end(); ++i) {
+        if ((*i)->name == headerName)
+            return (*i);
+    }
+
+    MetaHeader::Pointer meta = new MetaHeader(headerName);
+    metaHeaders.push_back(meta);
+    return meta;
+}
 
 
 Adaptation::ServiceConfig*
 Adaptation::Config::newServiceConfig() const
 {
     return new ServiceConfig();
 }
 
 void
 Adaptation::Config::removeService(const String& service)
 {
     removeRule(service);
     const Groups& groups = AllGroups();
     for (unsigned int i = 0; i < groups.size(); ) {
         const ServiceGroupPointer group = groups[i];
         const ServiceGroup::Store& services = group->services;
         typedef ServiceGroup::Store::const_iterator SGSI;
         for (SGSI it = services.begin(); it != services.end(); ++it) {
             if (*it == service) {
                 group->removedServices.push_back(service);
@@ -118,40 +168,42 @@
 void
 Adaptation::Config::parseService()
 {
     ServiceConfigPointer cfg = newServiceConfig();
     if (!cfg->parse()) {
         fatalf("%s:%d: malformed adaptation service configuration",
                cfg_filename, config_lineno);
     }
     serviceConfigs.push_back(cfg);
 }
 
 void
 Adaptation::Config::freeService()
 {
     FreeAccess();
     FreeServiceGroups();
 
     DetachServices();
 
     serviceConfigs.clean();
+
+    FreeMetaHeader();
 }
 
 void
 Adaptation::Config::dumpService(StoreEntry *entry, const char *name) const
 {
     typedef Services::iterator SCI;
     for (SCI i = AllServices().begin(); i != AllServices().end(); ++i) {
         const ServiceConfig &cfg = (*i)->cfg();
         storeAppendPrintf(entry, "%s " SQUIDSTRINGPH "_%s %s %d " SQUIDSTRINGPH "\n",
                           name,
                           SQUIDSTRINGPRINT(cfg.key),
                           cfg.methodStr(), cfg.vectPointStr(), cfg.bypass,
                           SQUIDSTRINGPRINT(cfg.uri));
     }
 }
 
 bool
 Adaptation::Config::finalize()
 {
     if (!onoff) {
@@ -192,40 +244,98 @@
 {
     typedef typename Collection::iterator CI;
     for (CI i = collection.begin(); i != collection.end(); ++i)
         (*i)->finalize();
 
     debugs(93,2, HERE << "Initialized " << collection.size() << ' ' << label);
 }
 
 void
 Adaptation::Config::Finalize(bool enabled)
 {
     Enabled = enabled;
     debugs(93,1, "Adaptation support is " << (Enabled ? "on" : "off."));
 
     FinalizeEach(AllServices(), "message adaptation services");
     FinalizeEach(AllGroups(), "message adaptation service groups");
     FinalizeEach(AllRules(), "message adaptation access rules");
 }
 
 void
+Adaptation::Config::ParseMetaHeader(ConfigParser &parser)
+{
+    String name, value;
+    const char *warnFor[] = {
+        "Methods",
+        "Service",
+        "ISTag",
+        "Encapsulated",
+        "Opt-body-type",
+        "Max-Connections",
+        "Options-TTL",
+        "Date",
+        "Service-ID",
+        "Allow",
+        "Preview",
+        "Transfer-Preview",
+        "Transfer-Ignore",
+        "Transfer-Complete",
+        NULL
+    };
+    ConfigParser::ParseString(&name);
+    ConfigParser::ParseQuotedString(&value);
+
+    // TODO: Find a way to move this check to ICAP
+    for (int i = 0; warnFor[i] != NULL; i++) {
+        if (name.caseCmp(warnFor[i]) == 0) {
+            debugs(93, DBG_CRITICAL, "WARNING: meta name \"" << name <<"\" is a reserved ICAP header name");
+            break;
+        }
+    }
+
+    MetaHeader::Pointer meta = addMetaHeader(name);
+    MetaHeader::Value::Pointer headValue = meta->addValue(value);
+    aclParseAclList(parser, &headValue->aclList);
+}
+
+void
+Adaptation::Config::DumpMetaHeader(StoreEntry *entry, const char *name)
+{
+    typedef MetaHeaders::iterator AMLI;
+    for(AMLI m = metaHeaders.begin(); m != metaHeaders.end(); ++m) {
+        typedef MetaHeader::Values::iterator VLI;
+        for (VLI v =(*m)->values.begin(); v != (*m)->values.end(); ++v ) {
+            storeAppendPrintf(entry, "%s " SQUIDSTRINGPH " %s", 
+                              name, SQUIDSTRINGPRINT((*m)->name), ConfigParser::QuoteString((*v)->value));
+            dump_acl_list(entry, (*v)->aclList);
+            storeAppendPrintf(entry, "\n");
+         }
+    }        
+}
+
+void 
+Adaptation::Config::FreeMetaHeader()
+{
+    metaHeaders.clean();
+}
+
+void
 Adaptation::Config::ParseServiceSet()
 {
     Adaptation::Config::ParseServiceGroup(new ServiceSet);
 }
 
 void
 Adaptation::Config::ParseServiceChain()
 {
     Adaptation::Config::ParseServiceGroup(new ServiceChain);
 }
 
 void
 Adaptation::Config::ParseServiceGroup(ServiceGroupPointer g)
 {
     assert(g != NULL);
     g->parse();
     AllGroups().push_back(g);
 }
 
 void

=== modified file 'src/adaptation/Config.h'
--- src/adaptation/Config.h	2011-08-03 08:30:00 +0000
+++ src/adaptation/Config.h	2011-10-20 16:58:55 +0000
@@ -1,65 +1,115 @@
 #ifndef SQUID_ADAPTATION__CONFIG_H
 #define SQUID_ADAPTATION__CONFIG_H
 
 #include "event.h"
+#include "acl/Gadgets.h"
 #include "base/AsyncCall.h"
 #include "adaptation/forward.h"
 #include "adaptation/Elements.h"
 
 class acl_access;
 class ConfigParser;
 
 namespace Adaptation
 {
 
 class Config
 {
 public:
     static void Finalize(bool enable);
 
     static void ParseServiceSet(void);
     static void ParseServiceChain(void);
+    static void ParseMetaHeader(ConfigParser &parser);
+    static void FreeMetaHeader();
+    static void DumpMetaHeader(StoreEntry *, const char *);
 
     static void ParseAccess(ConfigParser &parser);
     static void FreeAccess(void);
     static void DumpAccess(StoreEntry *, const char *);
 
     friend class AccessCheck;
 
 public:
     static bool Enabled; // true if at least one adaptation mechanism is
 
     // these are global squid.conf options, documented elsewhere
     static char *masterx_shared_name; // global TODO: do we need TheConfig?
     static int service_iteration_limit;
     static int send_client_ip;
     static int send_username;
     static int use_indirect_client;
 
     // Options below are accessed via Icap::TheConfig or Ecap::TheConfig
     // TODO: move ICAP-specific options to Icap::Config and add TheConfig
     int onoff;
     int service_failure_limit;
     time_t oldest_service_failure;
     int service_revival_delay;
 
+    /**
+     * Used to store meta headers. The meta headers are custom
+     * ICAP request headers or ECAP options used to pass custom 
+     * transaction-state related meta information to a service.
+     */
+    class MetaHeader: public RefCountable {
+    public:
+        typedef RefCount<MetaHeader> Pointer;
+        /// Stores a value for the meta header.
+        class Value: public RefCountable {
+        public:
+            typedef RefCount<Value> Pointer;
+            String value; ///< a header value
+            ACLList *aclList; ///< The access list used to determine if this value is valid for a request
+            explicit Value(const String &aVal) : value(aVal), aclList(NULL) {}
+            ~Value();
+        };
+        typedef Vector<Value::Pointer> Values;
+
+        explicit MetaHeader(const String &aName): name(aName) {}
+        
+        /**
+         * Adds a value to the meta header and returns a  pointer to the
+         * related Value object.
+         */
+        Value::Pointer addValue(const String &value);
+
+       /**
+        * Walks through the  possible values list of the  meta and selects 
+        * the first value which matches the given HttpRequest and HttpReply
+        * or NULL if none matches.
+        */
+        const char *match(HttpRequest *request, HttpReply *reply);
+        String name; ///< The meta header name
+        Values values; ///< The possible values list for the meta header
+    };
+    typedef Vector<MetaHeader::Pointer> MetaHeaders; 
+    static MetaHeaders metaHeaders; ///< The list of configured meta headers
+
+    /**
+     * Adds a header to the meta headers list and returns a pointer to the
+     * related metaHeaders object. If the header name already exists in list,
+     * returns a pointer to the existing object.
+     */
+    static MetaHeader::Pointer addMetaHeader(const String &header);
+
     typedef Vector<ServiceConfigPointer> ServiceConfigs;
     ServiceConfigs serviceConfigs;
 
     Config();
     virtual ~Config();
 
     void parseService(void);
     void freeService(void);
     void dumpService(StoreEntry *, const char *) const;
     ServicePointer findService(const String&);
 
     /**
      * Creates and starts the adaptation services. In the case the adaptation
      * mechanism is disabled then removes any reference to the services from
      * access rules and service groups, and returns false.
      * \return true if the services are ready and running, false otherwise
      */
     virtual bool finalize();
 
 protected:

=== modified file 'src/adaptation/ecap/XactionRep.cc'
--- src/adaptation/ecap/XactionRep.cc	2011-10-10 03:27:47 +0000
+++ src/adaptation/ecap/XactionRep.cc	2011-10-19 18:11:47 +0000
@@ -64,60 +64,64 @@
 {
     Must(!theMaster);
     Must(x != NULL);
     theMaster = x;
 }
 
 Adaptation::Service &
 Adaptation::Ecap::XactionRep::service()
 {
     Must(theService != NULL);
     return *theService;
 }
 
 const libecap::Area
 Adaptation::Ecap::XactionRep::option(const libecap::Name &name) const
 {
     if (name == libecap::metaClientIp)
         return clientIpValue();
     if (name == libecap::metaUserName)
         return usernameValue();
-    if (name == Adaptation::Config::masterx_shared_name)
+    if (Adaptation::Config::masterx_shared_name && name == Adaptation::Config::masterx_shared_name)
         return masterxSharedValue(name);
 
     // TODO: metaServerIp, metaAuthenticatedUser, and metaAuthenticatedGroups
-    return libecap::Area();
+
+    // If the name is unknown, metaValue returns an emtpy area
+    return metaValue(name);
 }
 
 void
 Adaptation::Ecap::XactionRep::visitEachOption(libecap::NamedValueVisitor &visitor) const
 {
     if (const libecap::Area value = clientIpValue())
         visitor.visit(libecap::metaClientIp, value);
     if (const libecap::Area value = usernameValue())
         visitor.visit(libecap::metaUserName, value);
 
     if (Adaptation::Config::masterx_shared_name) {
         const libecap::Name name(Adaptation::Config::masterx_shared_name);
         if (const libecap::Area value = masterxSharedValue(name))
             visitor.visit(name, value);
     }
+    
+    visitEachMetaHeader(visitor);
 
     // TODO: metaServerIp, metaAuthenticatedUser, and metaAuthenticatedGroups
 }
 
 const libecap::Area
 Adaptation::Ecap::XactionRep::clientIpValue() const
 {
     const HttpRequest *request = dynamic_cast<const HttpRequest*>(theCauseRep ?
                                  theCauseRep->raw().header : theVirginRep.raw().header);
     Must(request);
     // TODO: move this logic into HttpRequest::clientIp(bool) and
     // HttpRequest::clientIpString(bool) and reuse everywhere
     if (TheConfig.send_client_ip && request) {
         Ip::Address client_addr;
 #if FOLLOW_X_FORWARDED_FOR
         if (TheConfig.use_indirect_client) {
             client_addr = request->indirect_client_addr;
         } else
 #endif
             client_addr = request->client_addr;
@@ -145,40 +149,82 @@
     return libecap::Area();
 }
 
 const libecap::Area
 Adaptation::Ecap::XactionRep::masterxSharedValue(const libecap::Name &name) const
 {
     const HttpRequest *request = dynamic_cast<const HttpRequest*>(theCauseRep ?
                                  theCauseRep->raw().header : theVirginRep.raw().header);
     Must(request);
     if (name.known()) { // must check to avoid empty names matching unset cfg
         Adaptation::History::Pointer ah = request->adaptHistory(false);
         if (ah != NULL) {
             String name, value;
             if (ah->getXxRecord(name, value))
                 return libecap::Area::FromTempBuffer(value.rawBuf(), value.size());
         }
     }
     return libecap::Area();
 }
 
+const libecap::Area
+Adaptation::Ecap::XactionRep::metaValue(const libecap::Name &name) const
+{
+    HttpRequest *request = dynamic_cast<HttpRequest*>(theCauseRep ?
+                                 theCauseRep->raw().header : theVirginRep.raw().header);
+    Must(request);
+    HttpReply *reply = dynamic_cast<HttpReply*>(theVirginRep.raw().header);
+
+    if (name.known()) { // must check to avoid empty names matching unset cfg
+        typedef Adaptation::Config::MetaHeaders::iterator ACAMLI;
+        for(ACAMLI i = Adaptation::Config::metaHeaders.begin(); i != Adaptation::Config::metaHeaders.end(); ++i) {
+            if (name == (*i)->name.termedBuf()) {
+                if (const char *value = (*i)->match(request, reply))
+                    return libecap::Area::FromTempString(value);
+                else
+                    return libecap::Area();
+            }
+        }
+    }
+
+    return libecap::Area();
+}
+
+void 
+Adaptation::Ecap::XactionRep::visitEachMetaHeader(libecap::NamedValueVisitor &visitor) const
+{
+    HttpRequest *request = dynamic_cast<HttpRequest*>(theCauseRep ?
+                                                      theCauseRep->raw().header : theVirginRep.raw().header);
+    Must(request);
+    HttpReply *reply = dynamic_cast<HttpReply*>(theVirginRep.raw().header);
+    
+    typedef Adaptation::Config::MetaHeaders::iterator ACAMLI;
+    for(ACAMLI i = Adaptation::Config::metaHeaders.begin(); i != Adaptation::Config::metaHeaders.end(); ++i) {
+        const char *v;
+        if (v = (*i)->match(request, reply)) {
+            const libecap::Name name((*i)->name.termedBuf());
+            const libecap::Area value = libecap::Area::FromTempString(v);
+            visitor.visit(name, value);
+        }
+    }
+}
+
 void
 Adaptation::Ecap::XactionRep::start()
 {
     Must(theMaster);
 
     if (!theVirginRep.raw().body_pipe)
         makingVb = opNever; // there is nothing to deliver
 
     const HttpRequest *request = dynamic_cast<const HttpRequest*> (theCauseRep ?
                                  theCauseRep->raw().header : theVirginRep.raw().header);
     Must(request);
     Adaptation::History::Pointer ah = request->adaptLogHistory();
     if (ah != NULL) {
         // retrying=false because ecap never retries transactions
         adaptHistoryId = ah->recordXactStart(service().cfg().key, current_time, false);
     }
 
     theMaster->start();
 }
 

=== modified file 'src/adaptation/ecap/XactionRep.h'
--- src/adaptation/ecap/XactionRep.h	2011-03-11 22:22:13 +0000
+++ src/adaptation/ecap/XactionRep.h	2011-10-19 18:02:51 +0000
@@ -76,40 +76,44 @@
     virtual const char *status() const;
 
 protected:
     Service &service();
 
     Adaptation::Message &answer();
 
     void sinkVb(const char *reason);
     void preserveVb(const char *reason);
     void forgetVb(const char *reason);
 
     void moveAbContent();
 
     void updateHistory(HttpMsg *adapted);
     void terminateMaster();
     void scheduleStop(const char *reason);
 
     const libecap::Area clientIpValue() const;
     const libecap::Area usernameValue() const;
     const libecap::Area masterxSharedValue(const libecap::Name &name) const;
+    /// Return the adaptation meta header value for the given header "name"
+    const libecap::Area metaValue(const libecap::Name &name) const;
+    /// Return the adaptation meta headers and their values
+    void visitEachMetaHeader(libecap::NamedValueVisitor &visitor) const;
 
 private:
     AdapterXaction theMaster; // the actual adaptation xaction we represent
     Adaptation::ServicePointer theService; ///< xaction's adaptation service
 
     MessageRep theVirginRep;
     MessageRep *theCauseRep;
 
     typedef libecap::shared_ptr<libecap::Message> MessagePtr;
     MessagePtr theAnswerRep;
 
     typedef enum { opUndecided, opOn, opComplete, opNever } OperationState;
     OperationState makingVb; //< delivering virgin body from pipe to adapter
     OperationState proxyingAb; // delivering adapted body from adapter to core
     int adaptHistoryId;        ///< adaptation history slot reservation
     bool vbProductionFinished; // whether there can be no more vb bytes
     bool abProductionFinished; // whether adapter has finished producing ab
     bool abProductionAtEnd;    // whether adapter produced a complete ab
 
     CBDATA_CLASS2(XactionRep);

=== modified file 'src/adaptation/icap/ModXact.cc'
--- src/adaptation/icap/ModXact.cc	2011-06-04 12:48:45 +0000
+++ src/adaptation/icap/ModXact.cc	2011-10-19 17:58:41 +0000
@@ -1397,40 +1397,53 @@
             finishNullOrEmptyBodyPreview(httpBuf);
     }
 
     makeAllowHeader(buf);
 
     if (TheConfig.send_client_ip && request) {
         Ip::Address client_addr;
 #if FOLLOW_X_FORWARDED_FOR
         if (TheConfig.use_indirect_client) {
             client_addr = request->indirect_client_addr;
         } else
 #endif
             client_addr = request->client_addr;
         if (!client_addr.IsAnyAddr() && !client_addr.IsNoAddr())
             buf.Printf("X-Client-IP: %s\r\n", client_addr.NtoA(ntoabuf,MAX_IPSTRLEN));
     }
 
     if (TheConfig.send_username && request)
         makeUsernameHeader(request, buf);
 
+    // Adaptation::Config::metaHeaders
+    typedef Adaptation::Config::MetaHeaders::iterator ACAMLI;
+    for(ACAMLI i = Adaptation::Config::metaHeaders.begin(); i != Adaptation::Config::metaHeaders.end(); ++i) {
+        HttpRequest *r = virgin.cause ?
+            virgin.cause : dynamic_cast<HttpRequest*>(virgin.header);
+        Must(r);
+
+        HttpReply *reply = dynamic_cast<HttpReply*>(virgin.header);
+
+        if (const char *value = (*i)->match(r, reply))
+            buf.Printf("%s: %s\r\n", (*i)->name.termedBuf(), value);
+    }
+
     // fprintf(stderr, "%s\n", buf.content());
 
     buf.append(ICAP::crlf, 2); // terminate ICAP header
 
     // fill icapRequest for logging
     Must(icapRequest->parseCharBuf(buf.content(), buf.contentSize()));
 
     // start ICAP request body with encapsulated HTTP headers
     buf.append(httpBuf.content(), httpBuf.contentSize());
 
     httpBuf.clean();
 }
 
 // decides which Allow values to write and updates the request buffer
 void Adaptation::Icap::ModXact::makeAllowHeader(MemBuf &buf)
 {
     const bool allow204in = preview.enabled(); // TODO: add shouldAllow204in()
     const bool allow204out = state.allowedPostview204 = shouldAllow204();
     const bool allow206in = state.allowedPreview206 = shouldAllow206in();
     const bool allow206out = state.allowedPostview206 = shouldAllow206out();

=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc	2011-10-04 17:28:03 +0000
+++ src/cache_cf.cc	2011-10-19 18:44:25 +0000
@@ -81,40 +81,43 @@
 #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 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();
 
 static void parse_icap_service_failure_limit(Adaptation::Icap::Config *);
 static void dump_icap_service_failure_limit(StoreEntry *, const char *, const Adaptation::Icap::Config &);
 static void free_icap_service_failure_limit(Adaptation::Icap::Config *);
 #endif
 
 #if USE_ECAP
 static void parse_ecap_service_type(Adaptation::Ecap::Config *);
 static void dump_ecap_service_type(StoreEntry *, const char *, const Adaptation::Ecap::Config &);
 static void free_ecap_service_type(Adaptation::Ecap::Config *);
 #endif
 
@@ -4356,40 +4359,57 @@
 #if USE_ADAPTATION
 
 static void
 parse_adaptation_service_set_type()
 {
     Adaptation::Config::ParseServiceSet();
 }
 
 static void
 parse_adaptation_service_chain_type()
 {
     Adaptation::Config::ParseServiceChain();
 }
 
 static void
 parse_adaptation_access_type()
 {
     Adaptation::Config::ParseAccess(LegacyParser);
 }
 
+static void
+parse_adaptation_meta_type(Adaptation::Config::MetaHeaders *)
+{
+    Adaptation::Config::ParseMetaHeader(LegacyParser);
+}
+
+static void
+dump_adaptation_meta_type(StoreEntry *entry, const char *name, Adaptation::Config::MetaHeaders &)
+{
+    Adaptation::Config::DumpMetaHeader(entry, name);
+}
+
+static void
+free_adaptation_meta_type(Adaptation::Config::MetaHeaders *)
+{
+    // Nothing to do, it is released inside Adaptation::Config::freeService()
+}
 #endif /* USE_ADAPTATION */
 
 
 #if ICAP_CLIENT
 
 static void
 parse_icap_service_type(Adaptation::Icap::Config * cfg)
 {
     cfg->parseService();
 }
 
 static void
 free_icap_service_type(Adaptation::Icap::Config * cfg)
 {
     cfg->freeService();
 }
 
 static void
 dump_icap_service_type(StoreEntry * entry, const char *name, const Adaptation::Icap::Config &cfg)
 {

=== modified file 'src/cf.data.depend'
--- src/cf.data.depend	2011-08-10 15:54:51 +0000
+++ src/cf.data.depend	2011-10-15 17:21:51 +0000
@@ -19,40 +19,41 @@
 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
 http_port_list
 https_port_list
 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
 QosConfig
 refreshpattern
 removalpolicy
 size_t
 IpAddress_list

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre	2011-10-09 05:44:22 +0000
+++ src/cf.data.pre	2011-10-20 21:08:08 +0000
@@ -6967,40 +6967,75 @@
 
 	An ICAP REQMOD or RESPMOD transaction may set an entry in the 
 	shared table by returning an ICAP header field with a name 
 	specified in adaptation_masterx_shared_names.
 
 	An eCAP REQMOD or RESPMOD transaction may set an entry in the
 	shared table by implementing the libecap::visitEachOption() API
 	to provide an option with a name specified in
 	adaptation_masterx_shared_names.
 
 	Squid will store and forward the set entry to subsequent adaptation
 	transactions within the same master transaction scope.
 
 	Only one shared entry name is supported at this time.
 
 Example:
 # share authentication information among ICAP services
 adaptation_masterx_shared_names X-Subscriber-ID
 DOC_END
 
+NAME: adaptation_meta
+TYPE: adaptation_meta_type
+IFDEF: USE_ADAPTATION
+LOC: Adaptation::Config::metaHeaders
+DEFAULT: none
+DOC_START
+	This option allows Squid administrator to add custom ICAP request
+	headers or eCAP options to Squid ICAP requests or eCAP transactions.
+	Use it to pass custom authentication tokens and other
+	transaction-state related meta information to an ICAP/eCAP service.
+	
+	The addition of a meta header is ACL-driven:
+		adaptation_meta name value [!]aclname ...
+	
+	Processing for a given header name stops after the first ACL list match.
+	Thus, it is impossible to add two headers with the same name. If no ACL
+	lists match for a given header name, no such header is added. For 
+	example:
+	
+		# do not debug transactions except for those that need debugging
+		adaptation_meta X-Debug 1 needs_debugging
+	
+		# log all transactions except for those that must remain secret
+		adaptation_meta X-Log 1 !keep_secret
+	
+		# mark transactions from users in the "G 1" group
+		adaptation_meta X-Authenticated-Groups "G 1" authed_as_G1
+	
+	The "value" parameter may be a regular squid.conf token or a "double
+	quoted string". Within the quoted string, use backslash (\) to escape
+	any character, which is currently only useful for escaping backslashes
+	and double quotes. For example,
+	    "this string has one backslash (\\) and two \"quotes\""
+DOC_END
+
 NAME: icap_retry
 TYPE: acl_access
 IFDEF: ICAP_CLIENT
 LOC: Adaptation::Icap::TheConfig.repeat
 DEFAULT_IF_NONE: deny all
 DOC_START
 	This ACL determines which retriable ICAP transactions are
 	retried. Transactions that received a complete ICAP response
 	and did not have to consume or produce HTTP bodies to receive
 	that response are usually retriable.
 
 	icap_retry allow|deny [!]aclname ...
 
 	Squid automatically retries some ICAP I/O timeouts and errors
 	due to persistent connection race conditions.
 
 	See also: icap_retry_limit
 DOC_END
 
 NAME: icap_retry_limit

=== added file 'src/tests/testConfigParser.cc'
--- src/tests/testConfigParser.cc	1970-01-01 00:00:00 +0000
+++ src/tests/testConfigParser.cc	2011-10-20 20:51:06 +0000
@@ -0,0 +1,85 @@
+#define SQUID_UNIT_TEST 1
+#include "config.h"
+
+#include "testConfigParser.h"
+#include "SquidString.h"
+#include "Mem.h"
+#include "event.h"
+#include "ConfigParser.h"
+
+CPPUNIT_TEST_SUITE_REGISTRATION( testConfigParser);
+
+/* let this test link sanely */
+void
+eventAdd(const char *name, EVH * func, void *arg, double when, int, bool cbdata)
+{}
+
+void testConfigParser::setUp()
+{
+}
+
+bool testConfigParser::doParseQuotedTest(const char *s, const char *expectInterp)
+{
+    char cfgline[2048];
+    char cfgparam[2048];
+    snprintf(cfgline, 2048, "Config %s", s);
+
+    // Points to the start of quoted string
+    const char *tmp = strchr(cfgline, ' ');
+
+    if (tmp == NULL) {
+        fprintf(stderr, "Invalid config line: %s\n", s);
+        return false;
+    }
+    // Keep the initial value on cfgparam. The ConfigParser  methods will write on cfgline
+    strcpy(cfgparam, tmp+1);
+
+    // Initialize parser to point to the start of quoted string
+    strtok(cfgline, w_space);
+    String unEscaped;
+    ConfigParser::ParseQuotedString(&unEscaped);
+    
+    const bool interpOk = (unEscaped.cmp(expectInterp) == 0);
+    if (!interpOk) {
+        printf("%25s: %s\n%25s: %s\n%25s: %s\n",
+               "Raw configuration", cfgparam,
+               "Expected interpretation", expectInterp,
+               "Actual interpretation", unEscaped.termedBuf());
+    }
+
+    const char *quoted = ConfigParser::QuoteString(unEscaped);
+    bool quotedOk = (strcmp(cfgparam, quoted)==0);
+    if (!quotedOk) {
+        printf("%25s: %s\n%25s: %s\n%25s: %s\n",
+               "Raw configuration", cfgparam,
+               "Parsed and quoted", quoted,
+               "parsed value was", unEscaped.termedBuf());
+    }
+
+    return quotedOk && interpOk ;
+}
+
+void testConfigParser::testParseQuoted()
+{
+    // SingleToken
+    CPPUNIT_ASSERT(doParseQuotedTest("SingleToken", "SingleToken"));
+
+    // This is a quoted "string" by me
+    CPPUNIT_ASSERT(doParseQuotedTest("\"This is a quoted \\\"string\\\" by me\"",
+                                     "This is a quoted \"string\" by me"));
+
+    // escape sequence test: \\"\"\\"
+    CPPUNIT_ASSERT(doParseQuotedTest("\"escape sequence test: \\\\\\\\\\\"\\\\\\\"\\\\\\\\\\\"\"", 
+                                     "escape sequence test: \\\\\"\\\"\\\\\""));
+    
+    // \beginning and end test"
+    CPPUNIT_ASSERT(doParseQuotedTest("\"\\\\beginning and end test\\\"\"",
+                                     "\\beginning and end test\""));
+
+    // "
+    CPPUNIT_ASSERT(doParseQuotedTest("\"\\\"\"", "\""));
+
+    /* \ */
+    CPPUNIT_ASSERT(doParseQuotedTest("\"\\\\\"", "\\"));
+}
+

=== added file 'src/tests/testConfigParser.h'
--- src/tests/testConfigParser.h	1970-01-01 00:00:00 +0000
+++ src/tests/testConfigParser.h	2011-10-20 17:07:26 +0000
@@ -0,0 +1,24 @@
+#ifndef SQUID_SRC_TEST_CONFIG_PARSER_H
+#define SQUID_SRC_TEST_CONFIG_PARSER_H
+
+#include <cppunit/extensions/HelperMacros.h>
+
+/*
+ * test the ConfigParser framework
+ */
+
+class testConfigParser : public CPPUNIT_NS::TestFixture
+{
+    CPPUNIT_TEST_SUITE( testConfigParser );
+    CPPUNIT_TEST( testParseQuoted );
+    CPPUNIT_TEST_SUITE_END();
+
+public:
+    void setUp();
+
+protected:
+    bool doParseQuotedTest(const char *, const char *);
+    void testParseQuoted();
+};
+
+#endif

Reply via email to