-
/*
- * $Id$
- *
* DEBUG: section 55 HTTP Header
* AUTHOR: Alex Rousskov
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
- *
*/
#include "squid.h"
#include "base64.h"
+#include "globals.h"
+#include "HttpHdrCc.h"
#include "HttpHdrContRange.h"
#include "HttpHdrSc.h"
#include "HttpHeader.h"
+#include "HttpHeaderFieldInfo.h"
+#include "HttpHeaderStat.h"
+#include "HttpHeaderTools.h"
#include "MemBuf.h"
#include "mgr/Registration.h"
+#include "profiler/Profiler.h"
#include "rfc1123.h"
+#include "SquidConfig.h"
+#include "SquidString.h"
+#include "StatHist.h"
#include "Store.h"
+#include "StrList.h"
+#include "TimeOrTag.h"
/*
* On naming conventions:
* An entry is a (field_id, field_name, field_value) triplet.
*/
-
/*
* local constants and vars
*/
{"Cookie2", HDR_COOKIE2, ftStr},
{"Date", HDR_DATE, ftDate_1123},
{"ETag", HDR_ETAG, ftETag},
- {"Expires", HDR_EXPIRES, ftDate_1123},
{"Expect", HDR_EXPECT, ftStr},
+ {"Expires", HDR_EXPIRES, ftDate_1123},
{"From", HDR_FROM, ftStr},
{"Host", HDR_HOST, ftStr},
+ {"HTTP2-Settings", HDR_HTTP2_SETTINGS, ftStr}, /* for now */
{"If-Match", HDR_IF_MATCH, ftStr}, /* for now */
{"If-Modified-Since", HDR_IF_MODIFIED_SINCE, ftDate_1123},
{"If-None-Match", HDR_IF_NONE_MATCH, ftStr}, /* for now */
{"If-Range", HDR_IF_RANGE, ftDate_1123_or_ETag},
{"Keep-Alive", HDR_KEEP_ALIVE, ftStr},
+ {"Key", HDR_KEY, ftStr},
{"Last-Modified", HDR_LAST_MODIFIED, ftDate_1123},
{"Link", HDR_LINK, ftStr},
{"Location", HDR_LOCATION, ftStr},
{"Max-Forwards", HDR_MAX_FORWARDS, ftInt64},
{"Mime-Version", HDR_MIME_VERSION, ftStr}, /* for now */
+ {"Negotiate", HDR_NEGOTIATE, ftStr},
+ {"Origin", HDR_ORIGIN, ftStr},
{"Pragma", HDR_PRAGMA, ftStr},
{"Proxy-Authenticate", HDR_PROXY_AUTHENTICATE, ftStr},
{"Proxy-Authentication-Info", HDR_PROXY_AUTHENTICATION_INFO, ftStr},
{"X-Forwarded-For", HDR_X_FORWARDED_FOR, ftStr},
{"X-Request-URI", HDR_X_REQUEST_URI, ftStr},
{"X-Squid-Error", HDR_X_SQUID_ERROR, ftStr},
- {"Negotiate", HDR_NEGOTIATE, ftStr},
#if X_ACCELERATOR_VARY
{"X-Accelerator-Vary", HDR_X_ACCELERATOR_VARY, ftStr},
#endif
return aHeader;
}
-
/*
* headers with field values defined as #(values) in HTTP/1.1
* Headers that are currently not recognized, are commented out.
HDR_CONNECTION,
HDR_EXPECT,
HDR_IF_MATCH, HDR_IF_NONE_MATCH,
+ HDR_KEY,
HDR_LINK, HDR_PRAGMA,
HDR_PROXY_CONNECTION,
HDR_PROXY_SUPPORT,
static http_hdr_type ReplyHeadersArr[] = {
HDR_ACCEPT, HDR_ACCEPT_CHARSET, HDR_ACCEPT_ENCODING, HDR_ACCEPT_LANGUAGE,
HDR_ACCEPT_RANGES, HDR_AGE,
+ HDR_KEY,
HDR_LOCATION, HDR_MAX_FORWARDS,
HDR_MIME_VERSION, HDR_PUBLIC, HDR_RETRY_AFTER, HDR_SERVER, HDR_SET_COOKIE, HDR_SET_COOKIE2,
+ HDR_ORIGIN,
HDR_VARY,
HDR_WARNING, HDR_PROXY_CONNECTION, HDR_X_CACHE,
HDR_X_CACHE_LOOKUP,
static HttpHeaderMask RequestHeadersMask; /* set run-time using RequestHeaders */
static http_hdr_type RequestHeadersArr[] = {
HDR_AUTHORIZATION, HDR_FROM, HDR_HOST,
+ HDR_HTTP2_SETTINGS,
HDR_IF_MATCH, HDR_IF_MODIFIED_SINCE, HDR_IF_NONE_MATCH,
- HDR_IF_RANGE, HDR_MAX_FORWARDS, HDR_PROXY_CONNECTION,
+ HDR_IF_RANGE, HDR_MAX_FORWARDS,
+ HDR_ORIGIN,
+ HDR_PROXY_CONNECTION,
HDR_PROXY_AUTHORIZATION, HDR_RANGE, HDR_REFERER, HDR_REQUEST_RANGE,
HDR_USER_AGENT, HDR_X_FORWARDED_FOR, HDR_SURROGATE_CAPABILITY
};
static HttpHeaderMask HopByHopHeadersMask;
static http_hdr_type HopByHopHeadersArr[] = {
- HDR_CONNECTION, HDR_KEEP_ALIVE, /*HDR_PROXY_AUTHENTICATE,*/ HDR_PROXY_AUTHORIZATION,
+ HDR_CONNECTION, HDR_HTTP2_SETTINGS, HDR_KEEP_ALIVE, /*HDR_PROXY_AUTHENTICATE,*/ HDR_PROXY_AUTHORIZATION,
HDR_TE, HDR_TRAILER, HDR_TRANSFER_ENCODING, HDR_UPGRADE, HDR_PROXY_CONNECTION
};
static int HeaderEntryParsedCount = 0;
/*
- * local routines
+ * forward declarations and local routines
*/
+class StoreEntry;
#define assert_eid(id) assert((id) >= 0 && (id) < HDR_ENUM_END)
static void httpHeaderNoteParsedEntry(http_hdr_type id, String const &value, int error);
static void httpHeaderStatInit(HttpHeaderStat * hs, const char *label);
static void httpHeaderStatDump(const HttpHeaderStat * hs, StoreEntry * e);
+/** store report about current header usage and other stats */
+static void httpHeaderStoreReport(StoreEntry * e);
+
/*
* Module initialization routines
*/
/* init header stats */
assert(HttpHeaderStatCount == hoReply + 1);
- for (i = 0; i < HttpHeaderStatCount; i++)
+ for (i = 0; i < HttpHeaderStatCount; ++i)
httpHeaderStatInit(HttpHeaderStats + i, HttpHeaderStats[i].label);
HttpHeaderStats[hoRequest].owner_mask = &RequestHeadersMask;
assert(label);
memset(hs, 0, sizeof(HttpHeaderStat));
hs->label = label;
- statHistEnumInit(&hs->hdrUCountDistr, 32); /* not a real enum */
- statHistEnumInit(&hs->fieldTypeDistr, HDR_ENUM_END);
- statHistEnumInit(&hs->ccTypeDistr, CC_ENUM_END);
- statHistEnumInit(&hs->scTypeDistr, SC_ENUM_END);
+ hs->hdrUCountDistr.enumInit(32); /* not a real enum */
+ hs->fieldTypeDistr.enumInit(HDR_ENUM_END);
+ hs->ccTypeDistr.enumInit(CC_ENUM_END);
+ hs->scTypeDistr.enumInit(SC_ENUM_END);
}
/*
PROF_start(HttpHeaderClean);
- /*
- * An unfortunate bug. The entries array is initialized
- * such that count is set to zero. httpHeaderClean() seems to
- * be called both when 'hdr' is created, and destroyed. Thus,
- * we accumulate a large number of zero counts for 'hdr' before
- * it is ever used. Can't think of a good way to fix it, except
- * adding a state variable that indicates whether or not 'hdr'
- * has been used. As a hack, just never count zero-sized header
- * arrays.
- */
-
if (owner <= hoReply) {
+ /*
+ * An unfortunate bug. The entries array is initialized
+ * such that count is set to zero. httpHeaderClean() seems to
+ * be called both when 'hdr' is created, and destroyed. Thus,
+ * we accumulate a large number of zero counts for 'hdr' before
+ * it is ever used. Can't think of a good way to fix it, except
+ * adding a state variable that indicates whether or not 'hdr'
+ * has been used. As a hack, just never count zero-sized header
+ * arrays.
+ */
if (0 != entries.count)
- statHistCount(&HttpHeaderStats[owner].hdrUCountDistr, entries.count);
+ HttpHeaderStats[owner].hdrUCountDistr.count(entries.count);
- HttpHeaderStats[owner].destroyedCount++;
+ ++ HttpHeaderStats[owner].destroyedCount;
HttpHeaderStats[owner].busyDestroyedCount += entries.count > 0;
+ } // if (owner <= hoReply)
- while ((e = getEntry(&pos))) {
- /* tmp hack to try to avoid coredumps */
+ while ((e = getEntry(&pos))) {
+ /* tmp hack to try to avoid coredumps */
- if (e->id < 0 || e->id >= HDR_ENUM_END) {
- debugs(55, 0, "HttpHeader::clean BUG: entry[" << pos << "] is invalid (" << e->id << "). Ignored.");
- } else {
- statHistCount(&HttpHeaderStats[owner].fieldTypeDistr, e->id);
- /* yes, this deletion leaves us in an inconsistent state */
- delete e;
- }
+ if (e->id < 0 || e->id >= HDR_ENUM_END) {
+ debugs(55, DBG_CRITICAL, "HttpHeader::clean BUG: entry[" << pos << "] is invalid (" << e->id << "). Ignored.");
+ } else {
+ if (owner <= hoReply)
+ HttpHeaderStats[owner].fieldTypeDistr.count(e->id);
+ /* yes, this deletion leaves us in an inconsistent state */
+ delete e;
}
- } // if (owner <= hoReply)
+ }
entries.clean();
httpHeaderMaskInit(&mask, 0);
len = 0;
assert(header_start && header_end);
debugs(55, 7, "parsing hdr: (" << this << ")" << std::endl << getStringPrefix(header_start, header_end));
- HttpHeaderStats[owner].parsedCount++;
+ ++ HttpHeaderStats[owner].parsedCount;
char *nulpos;
if ((nulpos = (char*)memchr(header_start, '\0', header_end - header_start))) {
- debugs(55, 1, "WARNING: HTTP header contains NULL characters {" <<
+ debugs(55, DBG_IMPORTANT, "WARNING: HTTP header contains NULL characters {" <<
getStringPrefix(header_start, nulpos) << "}\nNULL\n{" << getStringPrefix(nulpos+1, header_end));
goto reset;
}
field_end = field_ptr;
- field_ptr++; /* Move to next line */
+ ++field_ptr; /* Move to next line */
if (field_end > this_line && field_end[-1] == '\r') {
- field_end--; /* Ignore CR LF */
+ --field_end; /* Ignore CR LF */
if (owner == hoRequest && field_end > this_line) {
bool cr_only = true;
cr_only = false;
}
if (cr_only) {
- debugs(55, 1, "WARNING: Rejecting HTTP request with a CR+ "
+ debugs(55, DBG_IMPORTANT, "WARNING: Rejecting HTTP request with a CR+ "
"header field to prevent request smuggling attacks: {" <<
getStringPrefix(header_start, header_end) << "}");
goto reset;
/* Barf on stray CR characters */
if (memchr(this_line, '\r', field_end - this_line)) {
- debugs(55, 1, "WARNING: suspicious CR characters in HTTP header {" <<
+ debugs(55, DBG_IMPORTANT, "WARNING: suspicious CR characters in HTTP header {" <<
getStringPrefix(field_start, field_end) << "}");
if (Config.onoff.relaxed_header_parser) {
char *p = (char *) this_line; /* XXX Warning! This destroys original header content and violates specifications somewhat */
- while ((p = (char *)memchr(p, '\r', field_end - p)) != NULL)
- *p++ = ' ';
+ while ((p = (char *)memchr(p, '\r', field_end - p)) != NULL) {
+ *p = ' ';
+ ++p;
+ }
} else
goto reset;
}
if (this_line + 1 == field_end && this_line > field_start) {
- debugs(55, 1, "WARNING: Blank continuation line in HTTP header {" <<
+ debugs(55, DBG_IMPORTANT, "WARNING: Blank continuation line in HTTP header {" <<
getStringPrefix(header_start, header_end) << "}");
goto reset;
}
if (field_start == field_end) {
if (field_ptr < header_end) {
- debugs(55, 1, "WARNING: unparseable HTTP header field near {" <<
+ debugs(55, DBG_IMPORTANT, "WARNING: unparseable HTTP header field near {" <<
getStringPrefix(field_start, header_end) << "}");
goto reset;
}
}
if ((e = HttpHeaderEntry::parse(field_start, field_end)) == NULL) {
- debugs(55, 1, "WARNING: unparseable HTTP header field {" <<
+ debugs(55, DBG_IMPORTANT, "WARNING: unparseable HTTP header field {" <<
getStringPrefix(field_start, field_end) << "}");
debugs(55, Config.onoff.relaxed_header_parser <= 0 ? 1 : 2,
" in {" << getStringPrefix(header_start, header_end) << "}");
}
if (!httpHeaderParseOffset(e->value.termedBuf(), &l1)) {
- debugs(55, 1, "WARNING: Unparseable content-length '" << e->value << "'");
+ debugs(55, DBG_IMPORTANT, "WARNING: Unparseable content-length '" << e->value << "'");
delete e;
continue;
} else if (!httpHeaderParseOffset(e2->value.termedBuf(), &l2)) {
- debugs(55, 1, "WARNING: Unparseable content-length '" << e2->value << "'");
+ debugs(55, DBG_IMPORTANT, "WARNING: Unparseable content-length '" << e2->value << "'");
delById(e2->id);
} else if (l1 > l2) {
delById(e2->id);
/* packs all the entries using supplied packer */
void
-HttpHeader::packInto(Packer * p) const
+HttpHeader::packInto(Packer * p, bool mask_sensitive_info) const
{
HttpHeaderPos pos = HttpHeaderInitPos;
const HttpHeaderEntry *e;
assert(p);
debugs(55, 7, "packing hdr: (" << this << ")");
/* pack all entries one by one */
- while ((e = getEntry(&pos)))
- e->packInto(p);
-
+ while ((e = getEntry(&pos))) {
+ if (!mask_sensitive_info) {
+ e->packInto(p);
+ continue;
+ }
+ switch (e->id) {
+ case HDR_AUTHORIZATION:
+ case HDR_PROXY_AUTHORIZATION:
+ packerAppend(p, e->name.rawBuf(), e->name.size());
+ packerAppend(p, ": ** NOT DISPLAYED **\r\n", 23);
+ break;
+ default:
+ e->packInto(p);
+ break;
+ }
+ }
/* Pack in the "special" entries */
/* Cache-Control */
assert(pos);
assert(*pos >= HttpHeaderInitPos && *pos < (ssize_t)entries.count);
- for ((*pos)++; *pos < (ssize_t)entries.count; (*pos)++) {
+ for (++(*pos); *pos < (ssize_t)entries.count; ++(*pos)) {
if (entries.items[*pos])
return (HttpHeaderEntry*)entries.items[*pos];
}
assert_eid(e->id);
assert(e->name.size());
- debugs(55, 9, this << " adding entry: " << e->id << " at " <<
- entries.count);
+ debugs(55, 7, HERE << this << " adding entry: " << e->id << " at " << entries.count);
if (CBIT_TEST(mask, e->id))
- Headers[e->id].stat.repCount++;
+ ++ Headers[e->id].stat.repCount;
else
CBIT_SET(mask, e->id);
assert(e);
assert_eid(e->id);
- debugs(55, 7, this << " adding entry: " << e->id << " at " <<
- entries.count);
+ debugs(55, 7, HERE << this << " adding entry: " << e->id << " at " << entries.count);
if (CBIT_TEST(mask, e->id))
- Headers[e->id].stat.repCount++;
+ ++ Headers[e->id].stat.repCount;
else
CBIT_SET(mask, e->id);
}
/*
- * Returns the value of the specified header.
+ * Returns the value of the specified header and/or an undefined String.
*/
String
HttpHeader::getByName(const char *name) const
+{
+ String result;
+ // ignore presence: return undefined string if an empty header is present
+ (void)getByNameIfPresent(name, result);
+ return result;
+}
+
+bool
+HttpHeader::getByNameIfPresent(const char *name, String &result) const
{
http_hdr_type id;
HttpHeaderPos pos = HttpHeaderInitPos;
/* First try the quick path */
id = httpHeaderIdByNameDef(name, strlen(name));
- if (id != -1)
- return getStrOrList(id);
-
- String result;
+ if (id != -1) {
+ if (!has(id))
+ return false;
+ result = getStrOrList(id);
+ return true;
+ }
/* Sorry, an unknown header name. Do linear search */
+ bool found = false;
while ((e = getEntry(&pos))) {
if (e->id == HDR_OTHER && e->name.caseCmp(name) == 0) {
+ found = true;
strListAdd(&result, e->value.termedBuf(), ',');
}
}
- return result;
+ return found;
}
/*
/* pack into mb */
mb.init();
packerToMemInit(&p, &mb);
- httpHdrCcPackInto(cc, &p);
+ cc->packInto(&p);
/* put */
addEntry(new HttpHeaderEntry(HDR_CACHE_CONTROL, NULL, mb.buf));
/* cleanup */
/* pack into mb */
mb.init();
packerToMemInit(&p, &mb);
- httpHdrScPackInto(sc, &p);
+ sc->packInto(&p);
/* put */
addEntry(new HttpHeaderEntry(HDR_SURROGATE_CONTROL, NULL, mb.buf));
/* cleanup */
HttpHdrCc *
HttpHeader::getCc() const
{
- HttpHdrCc *cc;
- String s;
-
if (!CBIT_TEST(mask, HDR_CACHE_CONTROL))
return NULL;
PROF_start(HttpHeader_getCc);
+ String s;
getList(HDR_CACHE_CONTROL, &s);
- cc = httpHdrCcParseCreate(&s);
+ HttpHdrCc *cc=new HttpHdrCc();
- HttpHeaderStats[owner].ccParsedCount++;
+ if (!cc->parse(s)) {
+ delete cc;
+ cc = NULL;
+ }
+
+ ++ HttpHeaderStats[owner].ccParsedCount;
if (cc)
httpHdrCcUpdateStats(cc, &HttpHeaderStats[owner].ccTypeDistr);
(void) getList(HDR_SURROGATE_CONTROL, &s);
- HttpHdrSc *sc = httpHdrScParseCreate(&s);
+ HttpHdrSc *sc = httpHdrScParseCreate(s);
- HttpHeaderStats[owner].ccParsedCount++;
+ ++ HttpHeaderStats[owner].ccParsedCount;
if (sc)
- httpHdrScUpdateStats(sc, &HttpHeaderStats[owner].scTypeDistr);
+ sc->updateStats(&HttpHeaderStats[owner].scTypeDistr);
httpHeaderNoteParsedEntry(HDR_SURROGATE_CONTROL, s, !sc);
return NULL;
/* skip white space */
- field += xcountws(field);
+ for (; field && xisspace(*field); ++field);
if (!*field) /* no authorization cookie */
return NULL;
value = aValue;
- Headers[id].stat.aliveCount++;
+ ++ Headers[id].stat.aliveCount;
debugs(55, 9, "created HttpHeaderEntry " << this << ": '" << name << " : " << value );
}
assert(Headers[id].stat.aliveCount);
- Headers[id].stat.aliveCount--;
+ -- Headers[id].stat.aliveCount;
id = HDR_BAD_HDR;
}
const char *value_start = field_start + name_len + 1; /* skip ':' */
/* note: value_end == field_end */
- HeaderEntryParsedCount++;
+ ++ HeaderEntryParsedCount;
/* do we have a valid field name within this field? */
if (name_len > 65534) {
/* String must be LESS THAN 64K and it adds a terminating NULL */
- debugs(55, 1, "WARNING: ignoring header name of " << name_len << " bytes");
+ debugs(55, DBG_IMPORTANT, "WARNING: ignoring header name of " << name_len << " bytes");
return NULL;
}
"NOTICE: Whitespace after header name in '" << getStringPrefix(field_start, field_end) << "'");
while (name_len > 0 && xisspace(field_start[name_len - 1]))
- name_len--;
+ --name_len;
if (!name_len)
return NULL;
/* trim field value */
while (value_start < field_end && xisspace(*value_start))
- value_start++;
+ ++value_start;
while (value_start < field_end && xisspace(field_end[-1]))
- field_end--;
+ --field_end;
if (field_end - value_start > 65534) {
/* String must be LESS THAN 64K and it adds a terminating NULL */
- debugs(55, 1, "WARNING: ignoring '" << name << "' header of " << (field_end - value_start) << " bytes");
+ debugs(55, DBG_IMPORTANT, "WARNING: ignoring '" << name << "' header of " << (field_end - value_start) << " bytes");
if (id == HDR_OTHER)
name.clean();
/* set field value */
value.limitInit(value_start, field_end - value_start);
- Headers[id].stat.seenCount++;
+ ++ Headers[id].stat.seenCount;
debugs(55, 9, "parsed HttpHeaderEntry: '" << name << ": " << value << "'");
static void
httpHeaderNoteParsedEntry(http_hdr_type id, String const &context, int error)
{
- Headers[id].stat.parsCount++;
+ ++ Headers[id].stat.parsCount;
if (error) {
- Headers[id].stat.errCount++;
+ ++ Headers[id].stat.errCount;
debugs(55, 2, "cannot parse hdr field: '" << Headers[id].name << ": " << context << "'");
}
}
xpercent(count, dump_stat->destroyedCount));
}
-
static void
httpHeaderStatDump(const HttpHeaderStat * hs, StoreEntry * e)
{
storeAppendPrintf(e, "\nField type distribution\n");
storeAppendPrintf(e, "%2s\t %-20s\t %5s\t %6s\n",
"id", "name", "count", "#/header");
- statHistDump(&hs->fieldTypeDistr, e, httpHeaderFieldStatDumper);
+ hs->fieldTypeDistr.dump(e, httpHeaderFieldStatDumper);
storeAppendPrintf(e, "\nCache-control directives distribution\n");
storeAppendPrintf(e, "%2s\t %-20s\t %5s\t %6s\n",
"id", "name", "count", "#/cc_field");
- statHistDump(&hs->ccTypeDistr, e, httpHdrCcStatDumper);
+ hs->ccTypeDistr.dump(e, httpHdrCcStatDumper);
storeAppendPrintf(e, "\nSurrogate-control directives distribution\n");
storeAppendPrintf(e, "%2s\t %-20s\t %5s\t %6s\n",
"id", "name", "count", "#/sc_field");
- statHistDump(&hs->scTypeDistr, e, httpHdrScStatDumper);
+ hs->scTypeDistr.dump(e, httpHdrScStatDumper);
storeAppendPrintf(e, "\nNumber of fields per header distribution\n");
storeAppendPrintf(e, "%2s\t %-5s\t %5s\t %6s\n",
"id", "#flds", "count", "%total");
- statHistDump(&hs->hdrUCountDistr, e, httpHeaderFldsPerHdrDumper);
+ hs->hdrUCountDistr.dump(e, httpHeaderFldsPerHdrDumper);
+ storeAppendPrintf(e, "\n");
dump_stat = NULL;
}
HttpHeaderStats[0].busyDestroyedCount =
HttpHeaderStats[hoRequest].busyDestroyedCount + HttpHeaderStats[hoReply].busyDestroyedCount;
- for (i = 1; i < HttpHeaderStatCount; i++) {
+ for (i = 1; i < HttpHeaderStatCount; ++i) {
httpHeaderStatDump(HttpHeaderStats + i, e);
- storeAppendPrintf(e, "%s\n", "<br>");
}
/* field stats for all messages */