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