]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/HttpHeaderTools.cc
Merged from trunk (r13356).
[thirdparty/squid.git] / src / HttpHeaderTools.cc
index 875a98d62f02f711b40da2e907ad662d6b7dfe39..ce24eed4266760f8767e75e0b3e19d567eeb6459 100644 (file)
@@ -1,7 +1,4 @@
-
 /*
- * $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)
@@ -123,7 +143,6 @@ httpHeaderPutStrvf(HttpHeader * hdr, http_hdr_type id, const char *fmt, va_list
     mb.clean();
 }
 
-
 /** wrapper arrounf PutContRange */
 void
 httpHeaderAddContRange(HttpHeader * hdr, HttpHdrRangeSpec spec, int64_t ent_len)
@@ -135,7 +154,6 @@ 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
@@ -145,18 +163,18 @@ int
 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, ',');
 
@@ -165,124 +183,6 @@ httpHeaderHasConnDir(const HttpHeader * hdr, const char *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;
-    /* ',' is always enabled as field delimiter as this is required for
-     * processing merged header values properly, even if Cookie normally
-     * uses ';' as delimiter.
-     */
-    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 whitespace and delimiters */
-    *pos += strspn(*pos, delim[2]);
-
-    *item = *pos;              /* remember item's start */
-
-    /* find next delimiter */
-    do {
-        *pos += strcspn(*pos, delim[quoted]);
-        if (**pos == '"') {
-            quoted = !quoted;
-            *pos += 1;
-        } else if (quoted && **pos == '\\') {
-            *pos += 1;
-            if (**pos)
-                *pos += 1;
-        } else {
-            break;             /* Delimiter found, marking the end of this value */
-        }
-    } 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)
@@ -323,43 +223,112 @@ httpHeaderParseOffset(const char *start, int64_t * value)
     return 1;
 }
 
-
 /**
  * Parses a quoted-string field (RFC 2616 section 2.2), complains if
  * something went wrong, returns non-zero on success.
+ * Un-escapes quoted-pair characters found within the string.
  * start should point at the first double-quote.
- * RC TODO: This is too looose. We should honour the BNF and exclude CTL's
  */
 int
-httpHeaderParseQuotedString(const char *start, String *val)
+httpHeaderParseQuotedString(const char *start, const int len, String *val)
 {
     const char *end, *pos;
     val->clean();
     if (*start != '"') {
-       debugs(66, 2, "failed to parse a quoted-string header field near '" << start << "'");
-       return 0;
+        debugs(66, 2, HERE << "failed to parse a quoted-string header field near '" << start << "'");
+        return 0;
     }
     pos = start + 1;
 
-    while (*pos != '"') {
-       if (*pos == '\\') {
-           pos++;
-       }
-       if (!*pos) {
-           debugs(66, 2, "failed to parse a quoted-string header field near '" << start << "'");
-           val->clean();
-           return 0;
-       }
-       end = pos + strcspn(pos, "\"\\");
-       val->append(pos, end-pos);
-       pos = end;
+    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;
+        }
+
+        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;
+    }
+
+    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->defined())
-       val->limitInit("", 0);
+    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;
+}
+
 /**
  * Checks the anonymizer (header_access) configuration.
  *
@@ -374,18 +343,24 @@ 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 headerMangler *hm = hms->find(*e);
+
     /* mangler or checklist went away. default allow */
     if (!hm || !hm->access_list) {
         return 1;
@@ -393,7 +368,7 @@ httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, int req_or_rep)
 
     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) {
@@ -427,17 +402,170 @@ httpHdrMangleList(HttpHeader * l, HttpRequest * request, int req_or_rep)
         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)
+{
+    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);
+}
+
+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)
 {
-    for (int i = 0; i < HDR_ENUM_END; i++) {
-        if (NULL != Config.request_header_access[i].access_list)
-            return 1;
+    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
     }
 
-    return 0;
+    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);
+        }
+    }
 }