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 Pointer; + /// Stores a value for the meta header. + class Value: public RefCountable { + public: + typedef RefCount 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 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 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 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(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(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(theCauseRep ? + theCauseRep->raw().header : theVirginRep.raw().header); + Must(request); + HttpReply *reply = dynamic_cast(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(theCauseRep ? + theCauseRep->raw().header : theVirginRep.raw().header); + Must(request); + HttpReply *reply = dynamic_cast(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 (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 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(virgin.header); + Must(r); + + HttpReply *reply = dynamic_cast(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 #endif #if HAVE_LIMITS_H #include #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 + +/* + * 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