Make it possible to match empty header field values using req_header. Warning: Some req_header and rep_header ACLs that were [accidentally] not matching empty headers (e.g., "^$" or ".*") will now start matching them. A new HttpHeader::getByNameIfPresent() method is added to be able to detect presence of empty header fields while ACLHTTPHeaderData::match() is adjusted to convert undefined String values into empty c-strings ("") for ACLRegexData::match() to work. Prior to these changes, when trying to match an empty header value with a regex like "^$", ACLHTTPHeaderData::match() would return false because: * HttpHeader::getStrOrList() and getByName() return an undefined String. * String::termedBuf() returns NULL for undefined Strings; and * ACLRegexData::match() always fails on NULL c-strings. === modified file 'src/HttpHeader.cc' --- src/HttpHeader.cc 2012-03-03 23:19:03 +0000 +++ src/HttpHeader.cc 2012-11-21 04:53:29 +0000 @@ -983,67 +983,80 @@ return s; } /* return a string or list of entries with the same id separated by ',' and ws */ String HttpHeader::getStrOrList(http_hdr_type id) const { HttpHeaderEntry *e; if (CBIT_TEST(ListHeadersMask, id)) return getList(id); if ((e = findEntry(id))) return e->value; return String(); } /* - * 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; HttpHeaderEntry *e; assert(name); /* 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; } /* * Returns a the value of the specified list member, if any. */ String HttpHeader::getByNameListMember(const char *name, const char *member, const char separator) const { String header; const char *pos = NULL; const char *item; int ilen; int mlen = strlen(member); assert(name); header = getByName(name); String result; === modified file 'src/HttpHeader.h' --- src/HttpHeader.h 2012-01-27 12:33:17 +0000 +++ src/HttpHeader.h 2012-11-21 04:53:29 +0000 @@ -230,40 +230,42 @@ /* Interface functions */ void clean(); void append(const HttpHeader * src); void update (HttpHeader const *fresh, HttpHeaderMask const *denied_mask); void compact(); int reset(); int parse(const char *header_start, const char *header_end); void packInto(Packer * p) const; HttpHeaderEntry *getEntry(HttpHeaderPos * pos) const; HttpHeaderEntry *findEntry(http_hdr_type id) const; int delByName(const char *name); int delById(http_hdr_type id); void delAt(HttpHeaderPos pos, int &headers_deleted); void refreshMask(); void addEntry(HttpHeaderEntry * e); void insertEntry(HttpHeaderEntry * e); String getList(http_hdr_type id) const; bool getList(http_hdr_type id, String *s) const; String getStrOrList(http_hdr_type id) const; String getByName(const char *name) const; + /// sets value and returns true iff a [possibly empty] named field is there + bool getByNameIfPresent(const char *name, String &value) const; String getByNameListMember(const char *name, const char *member, const char separator) const; String getListMember(http_hdr_type id, const char *member, const char separator) const; int has(http_hdr_type id) const; void putInt(http_hdr_type id, int number); void putInt64(http_hdr_type id, int64_t number); void putTime(http_hdr_type id, time_t htime); void insertTime(http_hdr_type id, time_t htime); void putStr(http_hdr_type id, const char *str); void putAuth(const char *auth_scheme, const char *realm); void putCc(const HttpHdrCc * cc); void putContRange(const HttpHdrContRange * cr); void putRange(const HttpHdrRange * range); void putSc(HttpHdrSc *sc); void putWarning(const int code, const char *const text); ///< add a Warning header void putExt(const char *name, const char *value); int getInt(http_hdr_type id) const; int64_t getInt64(http_hdr_type id) const; time_t getTime(http_hdr_type id) const; const char *getStr(http_hdr_type id) const; const char *getLastStr(http_hdr_type id) const; === modified file 'src/acl/HttpHeaderData.cc' --- src/acl/HttpHeaderData.cc 2012-02-05 06:09:46 +0000 +++ src/acl/HttpHeaderData.cc 2012-11-21 04:55:56 +0000 @@ -47,43 +47,57 @@ * * TODO: This can be generalised by making the type of the regex_rule into a * template parameter - so that we can use different rules types in future. */ ACLHTTPHeaderData::ACLHTTPHeaderData() : hdrId(HDR_BAD_HDR), regex_rule(new ACLRegexData) {} ACLHTTPHeaderData::~ACLHTTPHeaderData() { delete regex_rule; } bool ACLHTTPHeaderData::match(HttpHeader* hdr) { if (hdr == NULL) return false; debugs(28, 3, "aclHeaderData::match: checking '" << hdrName << "'"); - String value = hdrId != HDR_BAD_HDR ? hdr->getStrOrList(hdrId) : hdr->getByName(hdrName.termedBuf()); - - return regex_rule->match(value.termedBuf()); + String value; + if (hdrId != HDR_BAD_HDR) { + if (!hdr->has(hdrId)) + return false; + value = hdr->getStrOrList(hdrId); + } else { + if (!hdr->getByNameIfPresent(hdrName.termedBuf(), value)) + return false; + } + + // By now, we know the header is present, but: + // HttpHeader::get*() return an undefined String for empty header values; + // String::termedBuf() returns NULL for undefined Strings; and + // ACLRegexData::match() always fails on NULL strings. + // This makes it possible to detect an empty header value using regex: + const char *cvalue = value.defined() ? value.termedBuf() : ""; + return regex_rule->match(cvalue); } wordlist * ACLHTTPHeaderData::dump() { wordlist *W = NULL; wordlistAdd(&W, hdrName.termedBuf()); wordlist * regex_dump = regex_rule->dump(); wordlistAddWl(&W, regex_dump); wordlistDestroy(®ex_dump); return W; } void ACLHTTPHeaderData::parse() { char* t = strtokFile(); assert (t != NULL); hdrName = t; hdrId = httpHeaderIdByNameDef(hdrName.rawBuf(), hdrName.size());