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, DBG_CRITICAL, "ParseQuotedString: missing '\"' at the end of quoted string" );
+ self_destruct();
+ }
+ strtok(s-1, "\""); /*Reset the strtok to point after the " */
+ *s = '\0';
+
+ var->reset(token+1);
+}
+
+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();
+}
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();
};
tests/testStore \
tests/testString \
tests/testURL \
+ tests/testConfigParser \
$(STORE_TESTS)
## NP: required to run the above list. check_PROGRAMS only builds the binaries...
$(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
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 */
#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;
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*
DetachServices();
serviceConfigs.clean();
+
+ FreeMetaHeader();
}
void
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) {
+ fatalf("%s:%d: meta name \"%s\" is a reserved ICAP header name",
+ cfg_filename, config_lineno, name.termedBuf());
+ }
+ }
+
+ 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()
{
#define SQUID_ADAPTATION__CONFIG_H
#include "event.h"
+#include "acl/Gadgets.h"
#include "base/AsyncCall.h"
#include "adaptation/forward.h"
#include "adaptation/Elements.h"
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);
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;
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
if (const libecap::Area value = masterxSharedValue(name))
visitor.visit(name, value);
}
+
+ visitEachMetaHeader(visitor);
// TODO: metaServerIp, metaAuthenticatedUser, and metaAuthenticatedGroups
}
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()
{
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
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
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
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 */
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
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