Author: Alex Rousskov , Christos Tsantilas Add request_header_add option and [request|reply]_header_* manglers fixes This patch: 1) Add request_header_add, a new ACL-driven squid.conf option that allows addition of HTTP request header fields before the request is sent to the next HTTP hop (a peer proxy or an origin server): request_header_add acl1 [acl2] where: * Field-name is a token specifying an HTTP header name. * Field-value is either a constant token or a quoted string containing %macros. The following field-value macros are supported: %LOGIN, %SRC, %DST, %user_cert_subject, %user_ca, %% * One or more Squid ACLs may be specified to restrict header insertion to matching requests. The request_header_add option supports fast ACLs only. 2) Fix the [request|reply]_header_[access|replace] configuration parameters to support custom headers. Currently the user is able to remove/replace/allow ALL custom headers using the "Other" as header name. This is a Measurement Factory project. === modified file 'src/ConfigParser.cc' --- src/ConfigParser.cc 2012-01-20 18:55:04 +0000 +++ src/ConfigParser.cc 2012-06-27 15:20:49 +0000 @@ -99,79 +99,83 @@ t += strspn(buf, w_space); t2 = t + strcspn(t, w_space); t3 = t2 + strspn(t2, w_space); while (*t3 && *t3 != '#') { t2 = t3 + strcspn(t3, w_space); t3 = t2 + strspn(t2, w_space); } *t2 = '\0'; } /* skip comments */ /* skip blank lines */ } while ( *t == '#' || !*t ); return t; } void -ConfigParser::ParseQuotedString(char **var) +ConfigParser::ParseQuotedString(char **var, bool *wasQuoted) { String sVar; - ParseQuotedString(&sVar); + ParseQuotedString(&sVar, wasQuoted); *var = xstrdup(sVar.termedBuf()); } void -ConfigParser::ParseQuotedString(String *var) +ConfigParser::ParseQuotedString(String *var, bool *wasQuoted) { // Get all of the remaining string char *token = strtok(NULL, ""); if (token == NULL) self_destruct(); if (*token != '"') { token = strtok(token, w_space); var->reset(token); + if (wasQuoted) + *wasQuoted = false; return; } char *s = token + 1; /* scan until the end of the quoted string, unescaping " and \ */ while (*s && *s != '"') { if (*s == '\\') { const char * next = s+1; // may point to 0 memmove(s, next, strlen(next) + 1); } s++; } if (*s != '"') { debugs(3, DBG_CRITICAL, "ParseQuotedString: missing '\"' at the end of quoted string" ); self_destruct(); } strtok(s-1, "\""); /*Reset the strtok to point after the " */ *s = '\0'; var->reset(token+1); + if (wasQuoted) + *wasQuoted = true; } const char * ConfigParser::QuoteString(String &var) { static String quotedStr; const char *s = var.termedBuf(); bool needQuote = false; for (const char *l = s; !needQuote && *l != '\0'; l++ ) needQuote = !isalnum(*l); if (!needQuote) return s; quotedStr.clean(); quotedStr.append('"'); for (; *s != '\0'; s++) { if (*s == '"' || *s == '\\') quotedStr.append('\\'); === modified file 'src/ConfigParser.h' --- src/ConfigParser.h 2012-01-20 18:55:04 +0000 +++ src/ConfigParser.h 2012-06-27 15:13:15 +0000 @@ -50,30 +50,35 @@ /** * A configuration file Parser. Instances of this class track * parsing state and perform tokenisation. Syntax is currently * taken care of outside this class. * * One reason for this class is to allow testing of configuration * using modules without linking cache_cf.o in - because that drags * in all of squid by reference. Instead the tokeniser only is * brought in. */ class ConfigParser { public: void destruct(); static void ParseUShort(unsigned short *var); static void ParseBool(bool *var); static void ParseString(char **var); static void ParseString(String *var); - static void ParseQuotedString(char **var); - static void ParseQuotedString(String *var); + /// Parse an unquoted token (no spaces) or a "quoted string" that + /// may include spaces. In some contexts, quotes strings may also + /// include macros. Quoted strings may escape any character with + /// a backslash (\), which is currently only useful for inner + /// quotes. TODO: support quoted strings anywhere a token is accepted. + static void ParseQuotedString(char **var, bool *wasQuoted = NULL); + static void ParseQuotedString(String *var, bool *wasQuoted = NULL); static const char *QuoteString(String &var); static void ParseWordList(wordlist **list); static char * strtokFile(); }; extern int parseConfigFile(const char *file_name); #endif /* SQUID_CONFIGPARSER_H */ === modified file 'src/HttpHeaderTools.cc' --- src/HttpHeaderTools.cc 2012-01-20 18:55:04 +0000 +++ src/HttpHeaderTools.cc 2012-06-27 15:20:16 +0000 @@ -17,44 +17,58 @@ * sources; see the CREDITS file for full details. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #include "squid-old.h" #include "acl/FilledChecklist.h" +#include "acl/Gadgets.h" +#include "client_side.h" +#include "comm/Connection.h" #include "compat/strtoll.h" +#include "fde.h" #include "HttpHdrContRange.h" #include "HttpHeader.h" +#include "HttpRequest.h" #include "MemBuf.h" +#if USE_SSL +#include "ssl/support.h" +#endif +#include "Store.h" +#include +#if HAVE_STRING +#include +#endif + static void httpHeaderPutStrvf(HttpHeader * hdr, http_hdr_type id, const char *fmt, va_list vargs); HttpHeaderFieldInfo * httpHeaderBuildFieldsInfo(const HttpHeaderFieldAttrs * attrs, int count) { int i; HttpHeaderFieldInfo *table = NULL; assert(attrs && count); /* allocate space */ table = new HttpHeaderFieldInfo[count]; for (i = 0; i < count; ++i) { const http_hdr_type id = attrs[i].id; HttpHeaderFieldInfo *info = table + id; /* sanity checks */ assert(id >= 0 && id < count); assert(attrs[i].name); @@ -397,52 +411,58 @@ /* Make sure it's defined even if empty "" */ if (!val->defined()) val->limitInit("", 0); return 1; } /** * Checks the anonymizer (header_access) configuration. * * \retval 0 Header is explicitly blocked for removal * \retval 1 Header is explicitly allowed * \retval 1 Header has been replaced, the current version can be used. * \retval 1 Header has no access controls to test */ static int httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, int req_or_rep) { int retval; /* check with anonymizer tables */ - header_mangler *hm; + HeaderManglers *hms = NULL; assert(e); if (ROR_REQUEST == req_or_rep) { - hm = &Config.request_header_access[e->id]; + hms = Config.request_header_access; } else if (ROR_REPLY == req_or_rep) { - hm = &Config.reply_header_access[e->id]; + hms = Config.reply_header_access; } else { /* error. But let's call it "request". */ - hm = &Config.request_header_access[e->id]; + hms = Config.request_header_access; } + /* manglers are not configured for this message kind */ + if (!hms) + return 1; + + const header_mangler *hm = hms->find(*e); + /* mangler or checklist went away. default allow */ if (!hm || !hm->access_list) { return 1; } ACLFilledChecklist checklist(hm->access_list, request, NULL); if (checklist.fastCheck() == ACCESS_ALLOWED) { /* aclCheckFast returns true for allow. */ retval = 1; } else if (NULL == hm->replacement) { /* It was denied, and we don't have any replacement */ retval = 0; } else { /* It was denied, but we have a replacement. Replace the * header on the fly, and return that the new header * is allowed. */ e->value = hm->replacement; retval = 1; @@ -450,34 +470,284 @@ return retval; } /** Mangles headers for a list of headers. */ void httpHdrMangleList(HttpHeader * l, HttpRequest * request, int req_or_rep) { HttpHeaderEntry *e; HttpHeaderPos p = HttpHeaderInitPos; int headers_deleted = 0; while ((e = l->getEntry(&p))) if (0 == httpHdrMangle(e, request, req_or_rep)) l->delAt(p, headers_deleted); if (headers_deleted) l->refreshMask(); } -/** - * return 1 if manglers are configured. Used to set a flag - * for optimization during request forwarding. - */ -int -httpReqHdrManglersConfigured() +static +void header_mangler_clean(header_mangler &m) +{ + aclDestroyAccessList(&m.access_list); + safe_free(m.replacement); +} + +static +void header_mangler_dump_access(StoreEntry * entry, const char *option, + const header_mangler &m, const char *name) +{ + if (m.access_list != NULL) { + storeAppendPrintf(entry, "%s ", option); + dump_acl_access(entry, name, m.access_list); + } +} + +static +void header_mangler_dump_replacement(StoreEntry * entry, const char *option, + const header_mangler &m, const char *name) +{ + if (m.replacement) + storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement); +} + +HeaderManglers::HeaderManglers() +{ + memset(known, 0, sizeof(known)); + memset(&all, 0, sizeof(all)); +} + +HeaderManglers::~HeaderManglers() +{ + for (int i = 0; i < HDR_ENUM_END; i++) + header_mangler_clean(known[i]); + + typedef ManglersByName::iterator MBNI; + for (MBNI i = custom.begin(); i != custom.end(); ++i) + header_mangler_clean(i->second); + + header_mangler_clean(all); +} + +void +HeaderManglers::dumpAccess(StoreEntry * entry, const char *name) const { for (int i = 0; i < HDR_ENUM_END; i++) { - if (NULL != Config.request_header_access[i].access_list) - return 1; + header_mangler_dump_access(entry, name, known[i], + httpHeaderNameById(i)); } - return 0; + typedef ManglersByName::const_iterator MBNCI; + for (MBNCI i = custom.begin(); i != custom.end(); ++i) + header_mangler_dump_access(entry, name, i->second, i->first.c_str()); + + header_mangler_dump_access(entry, name, all, "All"); +} + +void +HeaderManglers::dumpReplacement(StoreEntry * entry, const char *name) const +{ + for (int i = 0; i < HDR_ENUM_END; i++) { + header_mangler_dump_replacement(entry, name, known[i], + httpHeaderNameById(i)); + } + + typedef ManglersByName::const_iterator MBNCI; + for (MBNCI i = custom.begin(); i != custom.end(); ++i) { + header_mangler_dump_replacement(entry, name, i->second, + i->first.c_str()); + } + + header_mangler_dump_replacement(entry, name, all, "All"); +} + +header_mangler * +HeaderManglers::track(const char *name) +{ + int id = httpHeaderIdByNameDef(name, strlen(name)); + + if (id == HDR_BAD_HDR) { // special keyword or a custom header + if (strcmp(name, "All") == 0) + id = HDR_ENUM_END; + else if (strcmp(name, "Other") == 0) + id = HDR_OTHER; + } + + header_mangler *m = NULL; + if (id == HDR_ENUM_END) { + m = &all; + } else + if (id == HDR_BAD_HDR) { + m = &custom[name]; + } else { + m = &known[id]; // including HDR_OTHER + } + + assert(m); + return m; +} + +void +HeaderManglers::setReplacement(const char *name, const char *value) +{ + // for backword compatibility, we allow replacements to be configured + // for headers w/o access rules, but such replacements are ignored + header_mangler *m = track(name); + + safe_free(m->replacement); // overwrite old value if any + m->replacement = xstrdup(value); +} + +const header_mangler * +HeaderManglers::find(const HttpHeaderEntry &e) const +{ + // a known header with a configured ACL list + if (e.id != HDR_OTHER && 0 <= e.id && e.id < HDR_ENUM_END && + known[e.id].access_list) + return &known[e.id]; + + // a custom header + if (e.id == HDR_OTHER) { + // does it have an ACL list configured? + // Optimize: use a name type that we do not need to convert to here + const ManglersByName::const_iterator i = custom.find(e.name.termedBuf()); + if (i != custom.end()) + return &i->second; + } + + // Next-to-last resort: "Other" rules match any custom header + if (e.id == HDR_OTHER && known[HDR_OTHER].access_list) + return &known[HDR_OTHER]; + + // Last resort: "All" rules match any header + if (all.access_list) + return &all; + + return NULL; +} + +#if USE_AUTH +static +const char * +httpHdrFmt_login(HttpRequest *request) +{ + if (request->auth_user_request != NULL) + return request->auth_user_request->username(); + + return NULL; +} +#endif + +static +const char * +httpHdrFmt_src(HttpRequest *request) +{ + static char buf[256]; + /*Maybe we want to use indirect_client in some cases: + return request->indirect_client_addr.NtoA(buf, sizeof(buf)); + Do we need a new configuration parameter: *uses_indirect_client + */ + return request->client_addr.NtoA(buf, sizeof(buf)); +} + +static +const char * +httpHdrFmt_dst(HttpRequest *request) +{ + return request->GetHost(); +} + +#if USE_SSL +static +const char * +httpHdrFmt_user_cert_subject(HttpRequest *request) +{ + ConnStateData *conn = request->clientConnectionManager.get(); + if (conn && conn->clientConnection != NULL) { + SSL *ssl = fd_table[conn->clientConnection->fd].ssl; + return sslGetUserAttribute(ssl, "DN"); + } + return NULL; +} + +static +const char * +httpHdrFmt_user_ca(HttpRequest *request) +{ + const ConnStateData *conn = request->clientConnectionManager.get(); + if (conn && conn->clientConnection != NULL) { + SSL *ssl = fd_table[conn->clientConnection->fd].ssl; + return sslGetCAAttribute(ssl, "DN"); + } + return NULL; +} +#endif + +/// +static +const char * +httpHdrFormat(const std::string &hdrValueWithMacros, HttpRequest *request) +{ + /// header value formatting macro details + struct FmtMacro { + const std::string code; ///< macro name + const char *(*fn)(HttpRequest *); ///< computes macro substitution + }; + static const FmtMacro fmtMacros[] = { +#if USE_AUTH + {"LOGIN", httpHdrFmt_login}, +#endif + {"SRC", httpHdrFmt_src}, + {"DST", httpHdrFmt_dst}, +#if USE_SSL + {"user_cert_subject", httpHdrFmt_user_cert_subject}, + {"user_ca", httpHdrFmt_user_ca}, +#endif + {"", NULL} + }; + + static std::string buf; + buf.clear(); + const char *s = hdrValueWithMacros.c_str(); + while (const char *macroName = strchr(s, '%')) { + buf.append(s, macroName - s); // left of macro + ++macroName; // skip '%' + int macroNameLen = 0; + char const *macroValue = NULL; + for (int i = 0; fmtMacros[i].fn; ++i) { + const FmtMacro &fmt = fmtMacros[i]; + macroNameLen = fmt.code.length(); + if (fmt.code.compare(0, macroNameLen, macroName, macroNameLen) == 0) { + macroValue = fmt.fn(request); + break; + } + macroNameLen = 0; // positive value would indicate a match + } + if (macroNameLen) { // we found a known macro + buf.append(macroValue ? macroValue : ""); + } else if (*macroName == '%') { // a "%%" escape sequence + buf.append("%"); + macroNameLen = 1; + } else { + buf.append("%"); // unknown macro -- a misconfiguration + } + s = macroName + macroNameLen; // skip processed macro + } + buf.append(s); // leftovers if any + return buf.c_str(); +} + +void +httpHdrAdd(HttpHeader *heads, HttpRequest *request, HeaderWithAclList &headersAdd) +{ + ACLFilledChecklist checklist(NULL, request, NULL); + for (HeaderWithAclList::const_iterator hwa = headersAdd.begin(); hwa != headersAdd.end(); ++hwa) { + if (!hwa->aclList || checklist.fastCheck(hwa->aclList) == ACCESS_ALLOWED) { + const char *fieldValue = hwa->quoted ? httpHdrFormat(hwa->fieldValue, request) : hwa->fieldValue.c_str(); + HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, hwa->fieldName.c_str(), + fieldValue); + heads->addEntry(e); + } + } } === modified file 'src/Makefile.am' --- src/Makefile.am 2012-05-30 10:25:42 +0000 +++ src/Makefile.am 2012-06-10 15:44:04 +0000 @@ -1038,106 +1038,115 @@ ##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) #tests_testX_LDFLAGS = $(LIBADD_DL) #tests_testX_LDADD=\ # $(SQUID_CPPUNIT_LIBS) \ # $(SQUID_CPPUNIT_LA) \ # $(COMPAT_LIB) \ #tests_testX_DEPENDENCIES= $(SQUID_CPPUNIT_LA) # - add other component .(h|cc) files needed to link and run tests tests_testHttpReply_SOURCES=\ + anyp/ProtocolType.cc \ cbdata.cc \ cbdata.h \ ETag.cc \ HttpBody.h \ HttpBody.cc \ HttpHdrCc.h \ HttpHdrCc.cc \ HttpHdrCc.cci \ HttpHdrContRange.cc \ HttpHdrContRange.h \ HttpHdrRange.cc \ HttpHdrSc.cc \ HttpHdrSc.h \ HttpHdrScTarget.cc \ HttpHdrScTarget.h \ HttpHeader.cc \ HttpHeader.h \ HttpHeaderMask.h \ HttpHeaderTools.cc \ HttpControlMsg.h \ HttpMsg.cc \ HttpMsg.h \ HttpReply.cc \ HttpReply.h \ HttpStatusCode.h \ HttpStatusLine.cc \ HttpStatusLine.h \ mem.cc \ MemBuf.cc \ MemBuf.h \ mime_header.cc \ Packer.cc \ Packer.h \ SquidString.h \ SquidTime.h \ String.cc \ + tests/stub_access_log.cc \ tests/stub_cache_cf.cc \ tests/stub_cache_manager.cc \ tests/stub_debug.cc \ + tests/stub_errorpage.cc \ tests/stub_HelperChildConfig.cc \ StatCounters.h \ StatCounters.cc \ StatHist.h \ tests/stub_StatHist.cc \ tests/stub_store.cc \ tests/stub_store_stats.cc \ tests/stub_tools.cc \ + tests/stub_HttpRequest.cc \ tests/testHttpReply.cc \ tests/testHttpReply.h \ tests/testMain.cc \ time.cc \ + url.cc \ + URLScheme.cc \ wordlist.cc nodist_tests_testHttpReply_SOURCES=\ $(TESTSOURCES) tests_testHttpReply_LDFLAGS = $(LIBADD_DL) tests_testHttpReply_LDADD=\ + acl/libacls.la \ acl/libapi.la \ acl/libstate.la \ $(AUTH_LIBS) \ ip/libip.la \ base/libbase.la \ + $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ $(SQUID_CPPUNIT_LIBS) \ $(SQUID_CPPUNIT_LA) \ + $(SSLLIB) \ $(COMPAT_LIB) \ $(XTRA_LIBS) tests_testHttpReply_DEPENDENCIES= $(SQUID_CPPUNIT_LA) tests_testACLMaxUserIP_SOURCES= \ cbdata.cc \ ClientInfo.h \ ConfigOption.cc \ ConfigParser.cc \ DiskIO/ReadRequest.cc \ DiskIO/WriteRequest.cc \ ETag.cc \ event.cc \ FileMap.h \ filemap.cc \ HelperChildConfig.h \ HelperChildConfig.cc \ HttpHeader.cc \ HttpHeaderTools.cc \ @@ -1165,76 +1174,78 @@ StoreIOState.cc \ StoreMeta.cc \ StoreMetaMD5.cc \ StoreMetaSTD.cc \ StoreMetaSTDLFS.cc \ StoreMetaUnpacker.cc \ StoreMetaURL.cc \ StoreMetaVary.cc \ StoreSwapLogData.cc \ store_key_md5.cc \ swap_log_op.cc \ swap_log_op.h \ SwapDir.cc \ SwapDir.h \ tests/stub_access_log.cc \ tests/stub_cache_cf.cc \ tests/stub_comm.cc \ tests/stub_debug.cc \ tests/stub_DelayId.cc \ tests/stub_DiskIOModule.cc \ + tests/stub_errorpage.cc \ tests/stub_fd.cc \ tests/stub_HttpRequest.cc \ tests/stub_MemObject.cc \ tests/stub_MemStore.cc \ tests/stub_mime.cc \ tests/stub_store.cc \ tests/stub_store_rebuild.cc \ tests/stub_store_stats.cc \ tests/stub_store_swapout.cc \ tests/stub_tools.cc \ tests/stub_cache_manager.cc \ tests/testACLMaxUserIP.cc \ tests/testACLMaxUserIP.h \ tests/testMain.cc \ time.cc \ url.cc \ URL.h \ URLScheme.cc \ URLScheme.h \ mem.cc \ MemBuf.cc \ wordlist.cc nodist_tests_testACLMaxUserIP_SOURCES= \ $(TESTSOURCES) tests_testACLMaxUserIP_LDADD= \ $(AUTH_ACL_LIBS) \ ident/libident.la \ acl/libacls.la \ eui/libeui.la \ acl/libstate.la \ $(AUTH_LIBS) \ acl/libapi.la \ anyp/libanyp.la \ base/libbase.la \ libsquid.la \ ip/libip.la \ + $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ $(DISK_OS_LIBS) \ $(REGEXLIB) \ $(SQUID_CPPUNIT_LIBS) \ $(SSLLIB) \ $(COMPAT_LIB) \ $(XTRA_LIBS) tests_testACLMaxUserIP_LDFLAGS = $(LIBADD_DL) tests_testACLMaxUserIP_DEPENDENCIES = \ $(SQUID_CPPUNIT_LA) ## a demonstration test that does nothing but shows the salient points ## involved in writing tests. tests_testBoilerplate_SOURCES = \ tests/testBoilerplate.cc \ tests/testMain.cc \ tests/testBoilerplate.h \ time.cc @@ -2482,40 +2493,41 @@ $(TESTSOURCES) \ SquidMath.cc \ SquidMath.h \ swap_log_op.cc tests_testStore_LDADD= \ $(AUTH_ACL_LIBS) \ ident/libident.la \ acl/libacls.la \ eui/libeui.la \ acl/libstate.la \ $(AUTH_LIBS) \ acl/libapi.la \ base/libbase.la \ libsquid.la \ ip/libip.la \ fs/libfs.la \ mgr/libmgr.la \ ipc/libipc.la \ anyp/libanyp.la \ + $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ $(REGEXLIB) \ $(SQUID_CPPUNIT_LIBS) \ $(SSLLIB) \ CommCalls.o \ DnsLookupDetails.o \ $(COMPAT_LIB) \ $(XTRA_LIBS) tests_testStore_LDFLAGS = $(LIBADD_DL) tests_testStore_DEPENDENCIES = \ $(SQUID_CPPUNIT_LA) ## string needs mem.cc. ## mem.cc needs ClientInfo.h ## libsquid pulls in SquidConfig and children. stub them. tests_testString_SOURCES = \ ClientInfo.h \ mem.cc \ === modified file 'src/cache_cf.cc' --- src/cache_cf.cc 2012-06-22 03:49:38 +0000 +++ src/cache_cf.cc 2012-06-27 15:19:10 +0000 @@ -73,40 +73,42 @@ #include "mgr/Registration.h" #include "Parsing.h" #include "rfc1738.h" #if SQUID_SNMP #include "snmp.h" #endif #include "Store.h" #include "StoreFileSystem.h" #include "SwapDir.h" #include "wordlist.h" #include "ipc/Kids.h" #if HAVE_GLOB_H #include #endif #if HAVE_LIMITS_H #include #endif +#include + #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(); @@ -152,47 +154,50 @@ static uint64_t parseTimeUnits(const char *unit, bool allowMsec); static void parseTimeLine(time_msec_t * tptr, const char *units, bool allowMsec); static void parse_u_short(unsigned short * var); static void parse_string(char **); static void default_all(void); static void defaults_if_none(void); static int parse_line(char *); static void parse_obsolete(const char *); static void parseBytesLine(size_t * bptr, const char *units); #if USE_SSL static void parseBytesOptionValue(size_t * bptr, const char *units, char const * value); #endif #if !USE_DNSHELPER static void parseBytesLineSigned(ssize_t * bptr, const char *units); #endif static size_t parseBytesUnits(const char *unit); static void free_all(void); void requirePathnameExists(const char *name, const char *path); static OBJH dump_config; #if USE_HTTP_VIOLATIONS -static void dump_http_header_access(StoreEntry * entry, const char *name, header_mangler header[]); -static void parse_http_header_access(header_mangler header[]); -static void free_http_header_access(header_mangler header[]); -static void dump_http_header_replace(StoreEntry * entry, const char *name, header_mangler header[]); -static void parse_http_header_replace(header_mangler * header); -static void free_http_header_replace(header_mangler * header); -#endif +static void dump_http_header_access(StoreEntry * entry, const char *name, const HeaderManglers *manglers); +static void parse_http_header_access(HeaderManglers **manglers); +static void free_http_header_access(HeaderManglers **manglers); +static void dump_http_header_replace(StoreEntry * entry, const char *name, const HeaderManglers *manglers); +static void parse_http_header_replace(HeaderManglers **manglers); +static void free_http_header_replace(HeaderManglers **manglers); +#endif +static void dump_HeaderWithAclList(StoreEntry * entry, const char *name, HeaderWithAclList *headers); +static void parse_HeaderWithAclList(HeaderWithAclList **header); +static void free_HeaderWithAclList(HeaderWithAclList **header); static void parse_denyinfo(acl_deny_info_list ** var); static void dump_denyinfo(StoreEntry * entry, const char *name, acl_deny_info_list * var); static void free_denyinfo(acl_deny_info_list ** var); #if USE_WCCPv2 static void parse_IpAddress_list(Ip::Address_list **); static void dump_IpAddress_list(StoreEntry *, const char *, const Ip::Address_list *); static void free_IpAddress_list(Ip::Address_list **); #if CURRENTLY_UNUSED static int check_null_IpAddress_list(const Ip::Address_list *); #endif /* CURRENTLY_UNUSED */ #endif /* USE_WCCPv2 */ static void parsePortCfg(AnyP::PortCfg **, const char *protocol); #define parse_PortCfg(l) parsePortCfg((l), token) static void dump_PortCfg(StoreEntry *, const char *, const AnyP::PortCfg *); static void free_PortCfg(AnyP::PortCfg **); static void parse_b_size_t(size_t * var); static void parse_b_int64_t(int64_t * var); @@ -793,41 +798,42 @@ debugs(22, 1, "WARNING: use of 'ignore-auth' in 'refresh_pattern' violates HTTP"); break; } } #endif #if !USE_HTTP_VIOLATIONS Config.onoff.via = 1; #else if (!Config.onoff.via) debugs(22, 1, "WARNING: HTTP requires the use of Via"); #endif // we enable runtime PURGE checks if there is at least one PURGE method ACL // TODO: replace with a dedicated "purge" ACL option? Config2.onoff.enable_purge = (ACLMethodData::ThePurgeCount > 0); - Config2.onoff.mangle_request_headers = httpReqHdrManglersConfigured(); + Config2.onoff.mangle_request_headers = + Config.request_header_access != NULL; if (geteuid() == 0) { if (NULL != Config.effectiveUser) { struct passwd *pwd = getpwnam(Config.effectiveUser); if (NULL == pwd) { /* * Andres Kroonmaa : * Some getpwnam() implementations (Solaris?) require * an available FD < 256 for opening a FILE* to the * passwd file. * DW: * This should be safe at startup, but might still fail * during reconfigure. */ fatalf("getpwnam failed to find userid for effective user '%s'", Config.effectiveUser); return; } @@ -1658,168 +1664,108 @@ parse_client_delay_pool_count(ClientDelayConfig * cfg) { cfg->parsePoolCount(); } static void parse_client_delay_pool_rates(ClientDelayConfig * cfg) { cfg->parsePoolRates(); } static void parse_client_delay_pool_access(ClientDelayConfig * cfg) { cfg->parsePoolAccess(LegacyParser); } #endif #if USE_HTTP_VIOLATIONS static void -dump_http_header_access(StoreEntry * entry, const char *name, header_mangler header[]) +dump_http_header_access(StoreEntry * entry, const char *name, const HeaderManglers *manglers) { - int i; - - for (i = 0; i < HDR_ENUM_END; i++) { - if (header[i].access_list != NULL) { - storeAppendPrintf(entry, "%s ", name); - dump_acl_access(entry, httpHeaderNameById(i), - header[i].access_list); - } - } + if (manglers) + manglers->dumpAccess(entry, name); } static void -parse_http_header_access(header_mangler header[]) +parse_http_header_access(HeaderManglers **pm) { - int id, i; char *t = NULL; if ((t = strtok(NULL, w_space)) == NULL) { debugs(3, 0, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line); debugs(3, 0, "parse_http_header_access: missing header name."); return; } - /* Now lookup index of header. */ - id = httpHeaderIdByNameDef(t, strlen(t)); - - if (strcmp(t, "All") == 0) - id = HDR_ENUM_END; - else if (strcmp(t, "Other") == 0) - id = HDR_OTHER; - else if (id == -1) { - debugs(3, 0, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line); - debugs(3, 0, "parse_http_header_access: unknown header name '" << t << "'"); - return; - } - - if (id != HDR_ENUM_END) { - parse_acl_access(&header[id].access_list); - } else { - char *next_string = t + strlen(t) - 1; - *next_string = 'A'; - *(next_string + 1) = ' '; - - for (i = 0; i < HDR_ENUM_END; i++) { - char *new_string = xstrdup(next_string); - strtok(new_string, w_space); - parse_acl_access(&header[i].access_list); - safe_free(new_string); - } - } + if (!*pm) + *pm = new HeaderManglers; + HeaderManglers *manglers = *pm; + header_mangler *mangler = manglers->track(t); + assert(mangler); + parse_acl_access(&mangler->access_list); } static void -free_http_header_access(header_mangler header[]) +free_http_header_access(HeaderManglers **pm) { - int i; - - for (i = 0; i < HDR_ENUM_END; i++) { - free_acl_access(&header[i].access_list); + // we delete the entire http_header_* mangler configuration at once + if (const HeaderManglers *manglers = *pm) { + delete manglers; + *pm = NULL; } } static void -dump_http_header_replace(StoreEntry * entry, const char *name, header_mangler - header[]) +dump_http_header_replace(StoreEntry * entry, const char *name, const HeaderManglers *manglers) { - int i; - - for (i = 0; i < HDR_ENUM_END; i++) { - if (NULL == header[i].replacement) - continue; - - storeAppendPrintf(entry, "%s %s %s\n", name, httpHeaderNameById(i), - header[i].replacement); - } + if (manglers) + manglers->dumpReplacement(entry, name); } static void -parse_http_header_replace(header_mangler header[]) +parse_http_header_replace(HeaderManglers **pm) { - int id, i; char *t = NULL; if ((t = strtok(NULL, w_space)) == NULL) { debugs(3, 0, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line); debugs(3, 0, "parse_http_header_replace: missing header name."); return; } - /* Now lookup index of header. */ - id = httpHeaderIdByNameDef(t, strlen(t)); - - if (strcmp(t, "All") == 0) - id = HDR_ENUM_END; - else if (strcmp(t, "Other") == 0) - id = HDR_OTHER; - else if (id == -1) { - debugs(3, 0, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line); - debugs(3, 0, "parse_http_header_replace: unknown header name " << t << "."); - - return; - } - - if (id != HDR_ENUM_END) { - if (header[id].replacement != NULL) - safe_free(header[id].replacement); - - header[id].replacement = xstrdup(t + strlen(t) + 1); - } else { - for (i = 0; i < HDR_ENUM_END; i++) { - if (header[i].replacement != NULL) - safe_free(header[i].replacement); + const char *value = t + strlen(t) + 1; - header[i].replacement = xstrdup(t + strlen(t) + 1); - } - } + if (!*pm) + *pm = new HeaderManglers; + HeaderManglers *manglers = *pm; + manglers->setReplacement(t, value); } static void -free_http_header_replace(header_mangler header[]) +free_http_header_replace(HeaderManglers **pm) { - int i; - - for (i = 0; i < HDR_ENUM_END; i++) { - if (header[i].replacement != NULL) - safe_free(header[i].replacement); + // we delete the entire http_header_* mangler configuration at once + if (const HeaderManglers *manglers = *pm) { + delete manglers; + *pm = NULL; } } #endif static void dump_cachedir(StoreEntry * entry, const char *name, SquidConfig::_cacheSwap swap) { SwapDir *s; int i; assert (entry); for (i = 0; i < swap.n_configured; i++) { s = dynamic_cast(swap.swapDirs[i].getRaw()); if (!s) continue; storeAppendPrintf(entry, "%s %s %s", name, s->type(), s->path); s->dump(*entry); storeAppendPrintf(entry, "\n"); } } @@ -4366,20 +4312,72 @@ cfg->oldest_service_failure = (m * d); } static void dump_icap_service_failure_limit(StoreEntry *entry, const char *name, const Adaptation::Icap::Config &cfg) { storeAppendPrintf(entry, "%s %d", name, cfg.service_failure_limit); if (cfg.oldest_service_failure > 0) { storeAppendPrintf(entry, " in %d seconds", (int)cfg.oldest_service_failure); } storeAppendPrintf(entry, "\n"); } static void free_icap_service_failure_limit(Adaptation::Icap::Config *cfg) { cfg->oldest_service_failure = 0; cfg->service_failure_limit = 0; } #endif + +static void dump_HeaderWithAclList(StoreEntry * entry, const char *name, HeaderWithAclList *headers) +{ + if (!headers) + return; + + for (HeaderWithAclList::iterator hwa = headers->begin(); hwa != headers->end(); ++hwa) { + storeAppendPrintf(entry, "%s ", hwa->fieldName.c_str()); + storeAppendPrintf(entry, "%s ", hwa->fieldValue.c_str()); + if (hwa->aclList) + dump_acl_list(entry, hwa->aclList); + storeAppendPrintf(entry, "\n"); + } +} + +static void parse_HeaderWithAclList(HeaderWithAclList **headers) +{ + char *fn; + if (!*headers) { + *headers = new HeaderWithAclList; + } + if ((fn = strtok(NULL, w_space)) == NULL) { + self_destruct(); + return; + } + HeaderWithAcl hwa; + hwa.fieldName = fn; + hwa.fieldId = httpHeaderIdByNameDef(fn, strlen(fn)); + if (hwa.fieldId == HDR_BAD_HDR) + hwa.fieldId = HDR_OTHER; + + String buf; + bool wasQuoted; + ConfigParser::ParseQuotedString(&buf, &wasQuoted); + hwa.fieldValue = buf.termedBuf(); + hwa.quoted = wasQuoted; + aclParseAclList(LegacyParser, &hwa.aclList); + (*headers)->push_back(hwa); +} + +static void free_HeaderWithAclList(HeaderWithAclList **header) +{ + if (!(*header)) + return; + + for (HeaderWithAclList::iterator hwa = (*header)->begin(); hwa != (*header)->end(); ++hwa) { + if (hwa->aclList) + aclDestroyAclList(&hwa->aclList); + } + delete *header; + *header = NULL; +} === modified file 'src/cf.data.depend' --- src/cf.data.depend 2012-04-25 05:29:20 +0000 +++ src/cf.data.depend 2012-05-18 13:26:54 +0000 @@ -14,40 +14,41 @@ cachedir cache_replacement_policy cachemgrpasswd ConfigAclTos CpuAffinityMap debug delay_pool_access acl delay_class delay_pool_class delay_pools delay_pool_count delay_pool_rates delay_class client_delay_pool_access acl client_delay_pool_count client_delay_pool_rates denyinfo acl eol externalAclHelper auth_param HelperChildConfig hostdomain cache_peer hostdomaintype cache_peer http_header_access acl http_header_replace +HeaderWithAclList acl adaptation_access_type adaptation_service_set adaptation_service_chain acl icap_service icap_class adaptation_service_set_type icap_service ecap_service adaptation_service_chain_type icap_service ecap_service adaptation_meta_type acl icap_access_type icap_class acl icap_class_type icap_service icap_service_type icap_service_failure_limit ecap_service_type int kb_int64_t kb_size_t logformat YesNoNone memcachemode obsolete onoff peer peer_access cache_peer acl PortCfg === modified file 'src/cf.data.pre' --- src/cf.data.pre 2012-05-06 01:29:22 +0000 +++ src/cf.data.pre 2012-05-22 14:42:19 +0000 @@ -4375,62 +4375,76 @@ DOC_END NAME: request_entities TYPE: onoff LOC: Config.onoff.request_entities DEFAULT: off DOC_START Squid defaults to deny GET and HEAD requests with request entities, as the meaning of such requests are undefined in the HTTP standard even if not explicitly forbidden. Set this directive to on if you have clients which insists on sending request entities in GET or HEAD requests. But be warned that there is server software (both proxies and web servers) which can fail to properly process this kind of request which may make you vulnerable to cache pollution attacks if enabled. DOC_END NAME: request_header_access IFDEF: USE_HTTP_VIOLATIONS -TYPE: http_header_access[] +TYPE: http_header_access LOC: Config.request_header_access DEFAULT: none DOC_START Usage: request_header_access header_name allow|deny [!]aclname ... WARNING: Doing this VIOLATES the HTTP standard. Enabling this feature could make you liable for problems which it causes. This option replaces the old 'anonymize_headers' and the older 'http_anonymizer' option with something that is much - more configurable. This new method creates a list of ACLs - for each header, allowing you very fine-tuned header - mangling. - - This option only applies to request headers, i.e., from the - client to the server. - - You can only specify known headers for the header name. - Other headers are reclassified as 'Other'. You can also - refer to all the headers with 'All'. + more configurable. A a list of ACLs for each header name allows + removal of specific header fields under specific conditions. + + This option only applies to outgoing HTTP request headers (i.e., + headers sent by Squid to the next HTTP hop such as a cache peer + or an origin server). The option has no effect during cache hit + detection. The equivalent adaptation vectoring point in ICAP + terminology is post-cache REQMOD. + + The option is applied to individual outgoing request header + fields. For each request header field F, Squid uses the first + qualifying sets of request_header_access rules: + + 1. Rules with header_name equal to F's name. + 2. Rules with header_name 'Other', provided F's name is not + on the hard-coded list of commonly used HTTP header names. + 3. Rules with header_name 'All'. + + Within that qualifying rule set, rule ACLs are checked as usual. + If ACLs of an "allow" rule match, the header field is allowed to + go through as is. If ACLs of a "deny" rule match, the header + field is removed (unless its value is replaced by the + corresponding request_header_replace option). If no rules within + the set have matching ACLs, the header field is left as is. For example, to achieve the same behavior as the old 'http_anonymizer standard' option, you should use: request_header_access From deny all request_header_access Referer deny all request_header_access Server deny all request_header_access User-Agent deny all request_header_access WWW-Authenticate deny all request_header_access Link deny all Or, to reproduce the old 'http_anonymizer paranoid' feature you should use: request_header_access Allow allow all request_header_access Authorization allow all request_header_access WWW-Authenticate allow all request_header_access Proxy-Authorization allow all request_header_access Proxy-Authenticate allow all request_header_access Cache-Control allow all @@ -4447,65 +4461,56 @@ request_header_access Accept allow all request_header_access Accept-Charset allow all request_header_access Accept-Encoding allow all request_header_access Accept-Language allow all request_header_access Content-Language allow all request_header_access Mime-Version allow all request_header_access Retry-After allow all request_header_access Title allow all request_header_access Connection allow all request_header_access All deny all although many of those are HTTP reply headers, and so should be controlled with the reply_header_access directive. By default, all headers are allowed (no anonymizing is performed). DOC_END NAME: reply_header_access IFDEF: USE_HTTP_VIOLATIONS -TYPE: http_header_access[] +TYPE: http_header_access LOC: Config.reply_header_access DEFAULT: none DOC_START Usage: reply_header_access header_name allow|deny [!]aclname ... WARNING: Doing this VIOLATES the HTTP standard. Enabling this feature could make you liable for problems which it causes. This option only applies to reply headers, i.e., from the server to the client. This is the same as request_header_access, but in the other - direction. - - This option replaces the old 'anonymize_headers' and the - older 'http_anonymizer' option with something that is much - more configurable. This new method creates a list of ACLs - for each header, allowing you very fine-tuned header - mangling. - - You can only specify known headers for the header name. - Other headers are reclassified as 'Other'. You can also - refer to all the headers with 'All'. + direction. Please see request_header_access for detailed + documentation. For example, to achieve the same behavior as the old 'http_anonymizer standard' option, you should use: reply_header_access From deny all reply_header_access Referer deny all reply_header_access Server deny all reply_header_access User-Agent deny all reply_header_access WWW-Authenticate deny all reply_header_access Link deny all Or, to reproduce the old 'http_anonymizer paranoid' feature you should use: reply_header_access Allow allow all reply_header_access Authorization allow all reply_header_access WWW-Authenticate allow all reply_header_access Proxy-Authorization allow all reply_header_access Proxy-Authenticate allow all reply_header_access Cache-Control allow all @@ -4522,75 +4527,126 @@ reply_header_access Accept allow all reply_header_access Accept-Charset allow all reply_header_access Accept-Encoding allow all reply_header_access Accept-Language allow all reply_header_access Content-Language allow all reply_header_access Mime-Version allow all reply_header_access Retry-After allow all reply_header_access Title allow all reply_header_access Connection allow all reply_header_access All deny all although the HTTP request headers won't be usefully controlled by this directive -- see request_header_access for details. By default, all headers are allowed (no anonymizing is performed). DOC_END NAME: request_header_replace header_replace IFDEF: USE_HTTP_VIOLATIONS -TYPE: http_header_replace[] +TYPE: http_header_replace LOC: Config.request_header_access DEFAULT: none DOC_START Usage: request_header_replace header_name message Example: request_header_replace User-Agent Nutscrape/1.0 (CP/M; 8-bit) This option allows you to change the contents of headers denied with request_header_access above, by replacing them with some fixed string. This replaces the old fake_user_agent option. This only applies to request headers, not reply headers. By default, headers are removed if denied. DOC_END NAME: reply_header_replace IFDEF: USE_HTTP_VIOLATIONS -TYPE: http_header_replace[] +TYPE: http_header_replace LOC: Config.reply_header_access DEFAULT: none DOC_START Usage: reply_header_replace header_name message Example: reply_header_replace Server Foo/1.0 This option allows you to change the contents of headers denied with reply_header_access above, by replacing them with some fixed string. This only applies to reply headers, not request headers. By default, headers are removed if denied. DOC_END +NAME: request_header_add +TYPE: HeaderWithAclList +LOC: Config.request_header_add +DEFAULT: none +DOC_START + Usage: request_header_add field-name field-value acl1 [acl2] ... + Example: request_header_add X-Client-CA "CA=%user_ca" all + + This option adds header fields to outgoing HTTP requests (i.e., + request headers sent by Squid to the next HTTP hop such as a + cache peer or an origin server). The option has no effect during + cache hit detection. The equivalent adaptation vectoring point + in ICAP terminology is post-cache REQMOD. + + Field-name is a token specifying an HTTP header name. If a + standard HTTP header name is used, Squid does not check whether + the new header conflicts with any existing headers or violates + HTTP rules. If the request to be modified already contains a + field with the same name, the old field is preserved but the + header field values are not merged. + + Field-value is either a token or a quoted string. If quoted + string format is used, then the surrounding quotes are removed + while escape sequences and %macros are processed. The following + field-value macros are supported: + + %LOGIN: Authenticated user login name or an empty string if + no authentication used. + + %SRC: Client IP address + + %DST: Requested host + + %user_cert_subject: The subject line of the received client + certificate or an empty string if Squid received no client + certificate or received an invalid/malformed certificate. + + %user_ca: The name of the SSL certificate issuer of the + received client certificate or an empty string if Squid + received no client certificate or received an invalid or + malformed certificate. + + %%: A single percent symbol (%). + + One or more Squid ACLs may be specified to restrict header + injection to matching requests. As always in squid.conf, all + ACLs in an option ACL list must be satisfied for the insertion + to happen. The request_header_add option supports fast ACLs + only. +DOC_END + NAME: relaxed_header_parser COMMENT: on|off|warn TYPE: tristate LOC: Config.onoff.relaxed_header_parser DEFAULT: on DOC_START In the default "on" setting Squid accepts certain forms of non-compliant HTTP messages where it is unambiguous what the sending application intended even if the message is not correctly formatted. The messages is then normalized to the correct form when forwarded by Squid. If set to "warn" then a warning will be emitted in cache.log each time such HTTP error is encountered. If set to "off" then such HTTP errors will cause the request or response to be rejected. DOC_END COMMENT_START === modified file 'src/http.cc' --- src/http.cc 2012-06-16 15:03:46 +0000 +++ src/http.cc 2012-06-27 14:45:11 +0000 @@ -71,40 +71,42 @@ #include "StatCounters.h" #include "Store.h" #define SQUID_ENTER_THROWING_CODE() try { #define SQUID_EXIT_THROWING_CODE(status) \ status = true; \ } \ catch (const std::exception &e) { \ debugs (11, 1, "Exception error:" << e.what()); \ status = false; \ } CBDATA_CLASS_INIT(HttpStateData); static const char *const crlf = "\r\n"; static void httpMaybeRemovePublic(StoreEntry *, http_status); static void copyOneHeaderFromClientsideRequestToUpstreamRequest(const HttpHeaderEntry *e, const String strConnection, const HttpRequest * request, HttpHeader * hdr_out, const int we_do_ranges, const http_state_flags); +//Declared in HttpHeaderTools.cc +void httpHdrAdd(HttpHeader *heads, HttpRequest *request, HeaderWithAclList &headers_add); HttpStateData::HttpStateData(FwdState *theFwdState) : AsyncJob("HttpStateData"), ServerStateData(theFwdState), lastChunk(0), header_bytes_read(0), reply_bytes_read(0), body_bytes_truncated(0), httpChunkDecoder(NULL) { debugs(11,5,HERE << "HttpStateData " << this << " created"); ignoreCacheControl = false; surrogateNoStore = false; serverConnection = fwd->serverConnection(); readBuf = new MemBuf; readBuf->init(16*1024, 256*1024); // reset peer response time stats for %hier.peer_http_request_sent.tv_sec = 0; request->hier.peer_http_request_sent.tv_usec = 0; if (fwd->serverConnection() != NULL) _peer = cbdataReference(fwd->serverConnection()->getPeer()); /* might be NULL */ if (_peer) { @@ -1768,40 +1770,43 @@ if (flags.keepalive) { hdr_out->putStr(HDR_CONNECTION, "keep-alive"); } /* append Front-End-Https */ if (flags.front_end_https) { if (flags.front_end_https == 1 || request->protocol == AnyP::PROTO_HTTPS) hdr_out->putStr(HDR_FRONT_END_HTTPS, "On"); } if (flags.chunked_request) { // Do not just copy the original value so that if the client-side // starts decode other encodings, this code may remain valid. hdr_out->putStr(HDR_TRANSFER_ENCODING, "chunked"); } /* Now mangle the headers. */ if (Config2.onoff.mangle_request_headers) httpHdrMangleList(hdr_out, request, ROR_REQUEST); + if (Config.request_header_add && !Config.request_header_add->empty()) + httpHdrAdd(hdr_out, request, *Config.request_header_add); + strConnection.clean(); } /** * Decides whether a particular header may be cloned from the received Clients request * to our outgoing fetch request. */ void copyOneHeaderFromClientsideRequestToUpstreamRequest(const HttpHeaderEntry *e, const String strConnection, const HttpRequest * request, HttpHeader * hdr_out, const int we_do_ranges, const http_state_flags flags) { debugs(11, 5, "httpBuildRequestHeader: " << e->name << ": " << e->value ); switch (e->id) { /** \par RFC 2616 sect 13.5.1 - Hop-by-Hop headers which Squid should not pass on. */ case HDR_PROXY_AUTHORIZATION: /** \par Proxy-Authorization: * Only pass on proxy authentication to peers for which * authentication forwarding is explicitly enabled === modified file 'src/protos.h' --- src/protos.h 2012-04-25 05:29:20 +0000 +++ src/protos.h 2012-05-18 13:26:54 +0000 @@ -217,41 +217,40 @@ SQUIDCEXTERN const char *httpHeaderNameById(int id); SQUIDCEXTERN int httpHeaderHasConnDir(const HttpHeader * hdr, const char *directive); SQUIDCEXTERN void strListAdd(String * str, const char *item, char del); SQUIDCEXTERN int strListIsMember(const String * str, const char *item, char del); SQUIDCEXTERN int strListIsSubstr(const String * list, const char *s, char del); SQUIDCEXTERN int strListGetItem(const String * str, char del, const char **item, int *ilen, const char **pos); SQUIDCEXTERN const char *getStringPrefix(const char *str, const char *end); SQUIDCEXTERN int httpHeaderParseInt(const char *start, int *val); SQUIDCEXTERN int httpHeaderParseOffset(const char *start, int64_t * off); SQUIDCEXTERN void httpHeaderPutStrf(HttpHeader * hdr, http_hdr_type id, const char *fmt,...) PRINTF_FORMAT_ARG3; /* Http Header */ SQUIDCEXTERN void httpHeaderInitModule(void); SQUIDCEXTERN void httpHeaderCleanModule(void); /* store report about current header usage and other stats */ void httpHeaderStoreReport(StoreEntry * e); SQUIDCEXTERN void httpHdrMangleList(HttpHeader *, HttpRequest *, int req_or_rep); -SQUIDCEXTERN int httpReqHdrManglersConfigured(); #if SQUID_SNMP SQUIDCEXTERN PF snmpHandleUdp; SQUIDCEXTERN void snmpInit(void); SQUIDCEXTERN void snmpOpenPorts(void); SQUIDCEXTERN void snmpClosePorts(void); SQUIDCEXTERN const char * snmpDebugOid(oid * Name, snint Len, MemBuf &outbuf); SQUIDCEXTERN void addr2oid(Ip::Address &addr, oid *Dest); SQUIDCEXTERN void oid2addr(oid *Dest, Ip::Address &addr, u_int code); SQUIDCEXTERN Ip::Address *client_entry(Ip::Address *current); extern variable_list *snmp_basicFn(variable_list *, snint *); extern variable_list *snmp_confFn(variable_list *, snint *); extern variable_list *snmp_sysFn(variable_list *, snint *); extern variable_list *snmp_prfSysFn(variable_list *, snint *); extern variable_list *snmp_prfProtoFn(variable_list *, snint *); extern variable_list *snmp_prfPeerFn(variable_list *, snint *); extern variable_list *snmp_netIpFn(variable_list *, snint *); extern variable_list *snmp_netFqdnFn(variable_list *, snint *); === modified file 'src/structs.h' --- src/structs.h 2012-04-26 01:04:17 +0000 +++ src/structs.h 2012-05-25 20:13:57 +0000 @@ -26,63 +26,109 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #ifndef SQUID_STRUCTS_H #define SQUID_STRUCTS_H #include "RefCount.h" #include "cbdata.h" #include "dlink.h" #include "err_type.h" /* needed for the global config */ #include "HttpHeader.h" /* for ICP_END */ #include "icp_opcode.h" #if USE_SSL #include #endif +#include +#if HAVE_STRING +#include +#endif #define PEER_MULTICAST_SIBLINGS 1 struct acl_name_list { char name[ACL_NAME_SZ]; acl_name_list *next; }; struct acl_deny_info_list { err_type err_page_id; char *err_page_name; acl_name_list *acl_list; acl_deny_info_list *next; }; class acl_access; struct _header_mangler { acl_access *access_list; char *replacement; }; +#include +#include + +/// A collection of header_mangler objects for a given message kind. +class HeaderManglers { +public: + HeaderManglers(); + ~HeaderManglers(); + + /// returns a header mangler for field e or nil if none was specified + const header_mangler *find(const HttpHeaderEntry &e) const; + + /// returns a mangler for the named header (known or custom) + header_mangler *track(const char *name); + + /// updates mangler for the named header with a replacement value + void setReplacement(const char *name, const char *replacementValue); + + /// report the *_header_access part of the configuration + void dumpAccess(StoreEntry *entry, const char *optionName) const; + /// report the *_header_replace part of the configuration + void dumpReplacement(StoreEntry *entry, const char *optionName) const; + +private: + /// a name:mangler map; optimize: use unordered map or some such + typedef std::map ManglersByName; + + /// one mangler for each known header + header_mangler known[HDR_ENUM_END]; + + /// one mangler for each custom header + ManglersByName custom; + + /// configured if some mangling ACL applies to all header names + header_mangler all; + +private: + /* not implemented */ + HeaderManglers(const HeaderManglers &); + HeaderManglers &operator =(const HeaderManglers &); +}; + class ACLChecklist; #if SQUID_SNMP struct _snmp_request_t { u_char *buf; u_char *outbuf; int len; int sock; long reqid; int outlen; Ip::Address from; struct snmp_pdu *PDU; ACLChecklist *acl_checklist; u_char *community; struct snmp_session session; }; @@ -133,40 +179,42 @@ #include "ClientDelayConfig.h" #endif #if USE_ICMP #include "icmp/IcmpConfig.h" #endif #include "HelperChildConfig.h" /* forward decl for SquidConfig, see RemovalPolicy.h */ class CpuAffinityMap; class RemovalPolicySettings; class external_acl; class Store; namespace AnyP { struct PortCfg; } class SwapDir; +class HeaderWithAcl; +typedef std::list HeaderWithAclList; /// Used for boolean enabled/disabled options with complex default logic. /// Allows Squid to compute the right default after configuration. /// Checks that not-yet-defined option values are not used. class YesNoNone { // TODO: generalize to non-boolean option types public: YesNoNone(): option(0) {} /// returns true iff enabled; asserts if the option has not been configured operator void *() const; // TODO: use a fancy/safer version of the operator /// enables or disables the option; void configure(bool beSet); /// whether the option was enabled or disabled, by user or Squid bool configured() const { return option != 0; } private: @@ -560,44 +608,46 @@ struct { struct { int average; int min_poll; } dns, udp, tcp; } comm_incoming; int max_open_disk_fds; int uri_whitespace; acl_size_t *rangeOffsetLimit; #if MULTICAST_MISS_STREAM struct { Ip::Address addr; int ttl; unsigned short port; char *encode_key; } mcast_miss; #endif - /* one access list per header type we know of */ - header_mangler request_header_access[HDR_ENUM_END]; - /* one access list per header type we know of */ - header_mangler reply_header_access[HDR_ENUM_END]; + /// request_header_access and request_header_replace + HeaderManglers *request_header_access; + /// reply_header_access and reply_header_replace + HeaderManglers *reply_header_access; + ///request_header_add access list + HeaderWithAclList *request_header_add; char *coredump_dir; char *chroot_dir; #if USE_CACHE_DIGESTS struct { int bits_per_entry; time_t rebuild_period; time_t rewrite_period; size_t swapout_chunk_size; int rebuild_chunk_percentage; } digest; #endif #if USE_SSL struct { int unclean_shutdown; char *ssl_engine; } SSL; #endif @@ -1091,21 +1141,31 @@ int cancelcount; /* # SWAP_LOG_DEL objects purged */ int invalid; /* # bad lines */ int badflags; /* # bad e->flags */ int bad_log_op; int zero_object_sz; }; class Logfile; #include "format/Format.h" #include "log/Formats.h" struct _customlog { char *filename; ACLList *aclList; Format::Format *logFormat; Logfile *logfile; customlog *next; Log::Format::log_type type; }; +class HeaderWithAcl { +public: +HeaderWithAcl() : fieldId (HDR_BAD_HDR), quoted(false), aclList(NULL) {} + http_hdr_type fieldId; + std::string fieldName; + std::string fieldValue; + bool quoted; + ACLList *aclList; +}; + #endif /* SQUID_STRUCTS_H */