-
/*
- * $Id$
- *
* DEBUG: section 66 HTTP Header Tools
* AUTHOR: Alex Rousskov
*
*/
#include "squid.h"
-#include "HttpHeader.h"
-#include "HttpHdrContRange.h"
#include "acl/FilledChecklist.h"
+#include "acl/Gadgets.h"
+#include "client_side.h"
+#include "client_side_request.h"
+#include "comm/Connection.h"
+#include "compat/strtoll.h"
+#include "ConfigParser.h"
+#include "fde.h"
+#include "globals.h"
+#include "HttpHdrContRange.h"
+#include "HttpHeader.h"
+#include "HttpHeaderFieldInfo.h"
+#include "HttpHeaderTools.h"
+#include "HttpRequest.h"
#include "MemBuf.h"
+#include "SquidConfig.h"
+#include "Store.h"
+#include "StrList.h"
-static void httpHeaderPutStrvf(HttpHeader * hdr, http_hdr_type id, const char *fmt, va_list vargs);
+#if USE_OPENSSL
+#include "ssl/support.h"
+#endif
+#include <algorithm>
+#include <string>
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+
+static void httpHeaderPutStrvf(HttpHeader * hdr, http_hdr_type id, const char *fmt, va_list vargs);
HttpHeaderFieldInfo *
httpHeaderBuildFieldsInfo(const HttpHeaderFieldAttrs * attrs, int count)
mb.clean();
}
-
/** wrapper arrounf PutContRange */
void
httpHeaderAddContRange(HttpHeader * hdr, HttpHdrRangeSpec spec, int64_t ent_len)
httpHdrContRangeDestroy(cr);
}
-
/**
* return true if a given directive is found in at least one of
* the "connection" header-fields note: if HDR_PROXY_CONNECTION is
httpHeaderHasConnDir(const HttpHeader * hdr, const char *directive)
{
String list;
- http_hdr_type ht;
int res;
/* what type of header do we have? */
+#if USE_HTTP_VIOLATIONS
if (hdr->has(HDR_PROXY_CONNECTION))
- ht = HDR_PROXY_CONNECTION;
- else if (hdr->has(HDR_CONNECTION))
- ht = HDR_CONNECTION;
+ list = hdr->getList(HDR_PROXY_CONNECTION);
else
- return 0;
-
- list = hdr->getList(ht);
+#endif
+ if (hdr->has(HDR_CONNECTION))
+ list = hdr->getList(HDR_CONNECTION);
+ else
+ return 0;
res = strListIsMember(&list, directive, ',');
return res;
}
-/** returns true iff "m" is a member of the list */
-int
-strListIsMember(const String * list, const char *m, char del)
-{
- const char *pos = NULL;
- const char *item;
- int ilen = 0;
- int mlen;
- assert(list && m);
- mlen = strlen(m);
-
- while (strListGetItem(list, del, &item, &ilen, &pos)) {
- if (mlen == ilen && !strncasecmp(item, m, ilen))
- return 1;
- }
-
- return 0;
-}
-
-/** returns true iff "s" is a substring of a member of the list */
-int
-strListIsSubstr(const String * list, const char *s, char del)
-{
- assert(list && del);
- return (list->find(s) != String::npos);
-
- /** \note
- * Note: the original code with a loop is broken because it uses strstr()
- * instead of strnstr(). If 's' contains a 'del', strListIsSubstr() may
- * return true when it should not. If 's' does not contain a 'del', the
- * implementaion is equavalent to strstr()! Thus, we replace the loop with
- * strstr() above until strnstr() is available.
- */
-}
-
-/** appends an item to the list */
-void
-strListAdd(String * str, const char *item, char del)
-{
- assert(str && item);
-
- if (str->size()) {
- char buf[3];
- buf[0] = del;
- buf[1] = ' ';
- buf[2] = '\0';
- str->append(buf, 2);
- }
-
- str->append(item, strlen(item));
-}
-
-/**
- * iterates through a 0-terminated string of items separated by 'del's.
- * white space around 'del' is considered to be a part of 'del'
- * like strtok, but preserves the source, and can iterate several strings at once
- *
- * returns true if next item is found.
- * init pos with NULL to start iteration.
- */
-int
-strListGetItem(const String * str, char del, const char **item, int *ilen, const char **pos)
-{
- size_t len;
- static char delim[3][8] = {
- "\"?,",
- "\"\\",
- " ?,\t\r\n"
- };
- int quoted = 0;
- assert(str && item && pos);
-
- delim[0][1] = del;
- delim[2][1] = del;
-
- if (!*pos) {
- *pos = str->termedBuf();
-
- if (!*pos)
- return 0;
- }
-
- /* skip leading ws and delimiters */
- *pos += strspn(*pos, delim[2]);
-
- *item = *pos; /* remember item's start */
-
- /* find next delimiter */
- do {
- *pos += strcspn(*pos, delim[quoted]);
-
- if (**pos == del)
- break;
-
- if (**pos == '"') {
- quoted = !quoted;
- *pos += 1;
- }
-
- if (quoted && **pos == '\\') {
- *pos += 1;
-
- if (**pos)
- *pos += 1;
- }
- } while (**pos);
-
- len = *pos - *item; /* *pos points to del or '\0' */
-
- /* rtrim */
- while (len > 0 && xisspace((*item)[len - 1]))
- len--;
-
- if (ilen)
- *ilen = len;
-
- return len > 0;
-}
-
/** handy to printf prefixes of potentially very long buffers */
const char *
getStringPrefix(const char *str, const char *end)
return 1;
}
-
/**
* Parses a quoted-string field (RFC 2616 section 2.2), complains if
* something went wrong, returns non-zero on success.
- * start should point at the first ".
- * RC TODO: This is too looose. We should honour the BNF and exclude CTL's
+ * Un-escapes quoted-pair characters found within the string.
+ * start should point at the first double-quote.
*/
int
-httpHeaderParseQuotedString (const char *start, String *val)
+httpHeaderParseQuotedString(const char *start, const int len, String *val)
{
const char *end, *pos;
val->clean();
- assert (*start == '"');
+ if (*start != '"') {
+ debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
+ return 0;
+ }
pos = start + 1;
- while (1) {
- if (!(end = index (pos,'"'))) {
- debugs(66, 2, "failed to parse a quoted-string header field near '" << start << "'");
- return 0;
+ while (*pos != '"' && len > (pos-start)) {
+
+ if (*pos =='\r') {
+ ++pos;
+ if ((pos-start) > len || *pos != '\n') {
+ debugs(66, 2, HERE << "failed to parse a quoted-string header field with '\\r' octet " << (start-pos)
+ << " bytes into '" << start << "'");
+ val->clean();
+ return 0;
+ }
+ }
+
+ if (*pos == '\n') {
+ ++pos;
+ if ( (pos-start) > len || (*pos != ' ' && *pos != '\t')) {
+ debugs(66, 2, HERE << "failed to parse multiline quoted-string header field '" << start << "'");
+ val->clean();
+ return 0;
+ }
+ // TODO: replace the entire LWS with a space
+ val->append(" ");
+ ++pos;
+ debugs(66, 2, HERE << "len < pos-start => " << len << " < " << (pos-start));
+ continue;
}
- /* check for quoted-chars */
- if (*(end - 1) != '\\') {
- /* done */
- val->append(start + 1, end-start-1);
- return 1;
+ bool quoted = (*pos == '\\');
+ if (quoted) {
+ ++pos;
+ if (!*pos || (pos-start) > len) {
+ debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
+ val->clean();
+ return 0;
+ }
+ }
+ end = pos;
+ while (end < (start+len) && *end != '\\' && *end != '\"' && (unsigned char)*end > 0x1F && *end != 0x7F)
+ ++end;
+ if (((unsigned char)*end <= 0x1F && *end != '\r' && *end != '\n') || *end == 0x7F) {
+ debugs(66, 2, HERE << "failed to parse a quoted-string header field with CTL octet " << (start-pos)
+ << " bytes into '" << start << "'");
+ val->clean();
+ return 0;
}
+ val->append(pos, end-pos);
+ pos = end;
+ }
- /* try for the end again */
- pos = end + 1;
+ if (*pos != '\"') {
+ debugs(66, 2, HERE << "failed to parse a quoted-string header field which did not end with \" ");
+ val->clean();
+ return 0;
}
+ /* Make sure it's defined even if empty "" */
+ if (!val->termedBuf())
+ val->limitInit("", 0);
+ return 1;
+}
+
+// TODO: Optimize using SBuf
+String
+httpHeaderQuoteString(const char *raw)
+{
+ assert(raw);
+
+ // HTTPbis says Senders SHOULD NOT escape octets in quoted-strings that
+ // do not require escaping (i.e., except DQUOTE and the backslash octet).
+ bool needInnerQuote = false;
+ for (const char *s = raw; !needInnerQuote && *s; ++s)
+ needInnerQuote = *s == '"' || *s == '\\';
+
+ static String quotedStr;
+ quotedStr.clean();
+ quotedStr.append('"');
+
+ if (needInnerQuote) {
+ for (const char *s = raw; *s; ++s) {
+ if (*s == '"' || *s == '\\')
+ quotedStr.append('\\');
+ quotedStr.append(*s);
+ }
+ } else {
+ quotedStr.append(raw);
+ }
+
+ quotedStr.append('"');
+ return quotedStr;
}
/**
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 headerMangler *hm = hms->find(*e);
+
/* mangler or checklist went away. default allow */
- if(!hm || !hm->access_list) {
+ if (!hm || !hm->access_list) {
return 1;
}
ACLFilledChecklist checklist(hm->access_list, request, NULL);
- if (checklist.fastCheck()) {
+ if (checklist.fastCheck() == ACCESS_ALLOWED) {
/* aclCheckFast returns true for allow. */
retval = 1;
} else if (NULL == hm->replacement) {
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(headerMangler &m)
+{
+ aclDestroyAccessList(&m.access_list);
+ safe_free(m.replacement);
+}
+
+static
+void header_mangler_dump_access(StoreEntry * entry, const char *option,
+ const headerMangler &m, const char *name)
{
- for (int i = 0; i < HDR_ENUM_END; i++) {
- if (NULL != Config.request_header_access[i].access_list)
- return 1;
+ 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 headerMangler &m, const char *name)
+{
+ if (m.replacement)
+ storeAppendPrintf(entry, "%s %s %s\n", option, name, m.replacement);
+}
- return 0;
+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) {
+ header_mangler_dump_access(entry, name, known[i],
+ httpHeaderNameById(i));
+ }
+
+ 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");
+}
+
+headerMangler *
+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;
+ }
+
+ headerMangler *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
+ headerMangler *m = track(name);
+
+ safe_free(m->replacement); // overwrite old value if any
+ m->replacement = xstrdup(value);
+}
+
+const headerMangler *
+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;
+}
+
+void
+httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, 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 = NULL;
+ MemBuf mb;
+ if (hwa->quoted) {
+ if (al != NULL) {
+ mb.init();
+ hwa->valueFormat->assemble(mb, al, 0);
+ fieldValue = mb.content();
+ }
+ } else {
+ fieldValue = hwa->fieldValue.c_str();
+ }
+
+ if (!fieldValue || fieldValue[0] == '\0')
+ fieldValue = "-";
+
+ HttpHeaderEntry *e = new HttpHeaderEntry(hwa->fieldId, hwa->fieldName.c_str(),
+ fieldValue);
+ heads->addEntry(e);
+ }
+ }
}