Compliance: remove Content-Length header if Transfer-Encoding is present.
If after HTTP header parsing we have both "Transfer-Encoding: chunked"
and Content-Length headers, remove the Content-Length entry. The
adjusted behavior follows httpbis recommendations (ticket #95, part 2).
The old client-side code forwarded the original Content-Length header
which did not match the [dechunked] response, resulting in a malformed
response.
HttpHeader::chunked() method added to check if HTTP headers contain
chunked Transfer-Encoding header. Use this method in code that checks
for chunked encoding.
Co-Advisor test cases: test_case/rfc2616/chunked-1p0-badClen-toClt
test_case/rfc2616/chunked-1p1-badClen-toClt
Compliance: remove Content-Length header if Transfer-Encoding is present.
If after HTTP header parsing we have both "Transfer-Encoding: chunked"
and Content-Length headers, remove the Content-Length entry. The
adjusted behavior follows httpbis recommendations (ticket #95,
part 2).
The old client-side code forwarded the original Content-Length header which
did not match the [dechunked] response, resulting in a malformed response.
HttpHeader::chunked() method added to check if HTTP headers contain
chunked Transfer-Encoding header. Use this method in code that checks
for chunked encoding.
Co-Advisor test cases: test_case/rfc2616/chunked-1p0-badClen-toClt
test_case/rfc2616/chunked-1p1-badClen-toClt
=== modified file 'src/HttpHeader.cc'
--- src/HttpHeader.cc 2010-05-31 19:51:06 +0000
+++ src/HttpHeader.cc 2010-08-18 16:29:10 +0000
@@ -630,40 +630,45 @@ HttpHeader::parse(const char *header_sta
delete e;
goto reset;
}
}
}
if (e->id == HDR_OTHER && stringHasWhitespace(e->name.termedBuf())) {
debugs(55, Config.onoff.relaxed_header_parser <= 0 ? 1 : 2,
"WARNING: found whitespace in HTTP header name {" <<
getStringPrefix(field_start, field_end) << "}");
if (!Config.onoff.relaxed_header_parser) {
delete e;
goto reset;
}
}
addEntry(e);
}
+ if (chunked()) {
+ // RFC 2616 section 4.4: ignore Content-Length with Transfer-Encoding
+ delById(HDR_CONTENT_LENGTH);
+ }
+
PROF_stop(HttpHeaderParse);
return 1; /* even if no fields where found, it is a valid header */
reset:
PROF_stop(HttpHeaderParse);
return reset();
}
/* packs all the entries using supplied packer */
void
HttpHeader::packInto(Packer * p) 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);
/* Pack in the "special" entries */
=== modified file 'src/HttpHeader.h'
--- src/HttpHeader.h 2010-03-05 07:10:40 +0000
+++ src/HttpHeader.h 2010-08-18 20:44:09 +0000
@@ -238,48 +238,56 @@ public:
void putCc(const HttpHdrCc * cc);
void putContRange(const HttpHdrContRange * cr);
void putRange(const HttpHdrRange * range);
void putSc(HttpHdrSc *sc);
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;
HttpHdrCc *getCc() const;
HttpHdrRange *getRange() const;
HttpHdrSc *getSc() const;
HttpHdrContRange *getContRange() const;
const char *getAuth(http_hdr_type id, const char *auth_scheme) const;
ETag getETag(http_hdr_type id) const;
TimeOrTag getTimeOrTag(http_hdr_type id) const;
int hasListMember(http_hdr_type id, const char *member, const char separator) const;
int hasByNameListMember(const char *name, const char *member, const char separator) const;
void removeHopByHopEntries();
+ inline bool chunked() const; ///< whether message uses chunked Transfer-Encoding
/* protected, do not use these, use interface functions instead */
Vector<HttpHeaderEntry *> entries; /**< parsed fields in raw format */
HttpHeaderMask mask; /**< bit set <=> entry present */
http_hdr_owner_type owner; /**< request or reply */
int len; /**< length when packed, not counting terminating null-byte */
protected:
/** \deprecated Public access replaced by removeHopByHopEntries() */
void removeConnectionHeaderEntries();
private:
HttpHeaderEntry *findLastEntry(http_hdr_type id) const;
/// Made it non-copyable. Our destructor is a bit nasty...
HttpHeader(const HttpHeader &);
//assignment is used by the reset method, can't block it..
//const HttpHeader operator=(const HttpHeader &);
};
+inline bool
+HttpHeader::chunked() const
+{
+ return has(HDR_TRANSFER_ENCODING) &&
+ hasListMember(HDR_TRANSFER_ENCODING, "chunked", ',');
+}
+
extern int httpHeaderParseQuotedString (const char *start, String *val);
SQUIDCEXTERN int httpHeaderHasByNameListMember(const HttpHeader * hdr, const char *name, const char *member, const char separator);
SQUIDCEXTERN void httpHeaderUpdate(HttpHeader * old, const HttpHeader * fresh, const HttpHeaderMask * denied_mask);
int httpMsgIsPersistent(HttpVersion const &http_ver, const HttpHeader * hdr);
SQUIDCEXTERN void httpHeaderCalcMask(HttpHeaderMask * mask, http_hdr_type http_hdr_type_enums[], size_t count);
#endif /* SQUID_HTTPHEADER_H */
=== modified file 'src/HttpReply.cc'
--- src/HttpReply.cc 2010-07-13 16:43:00 +0000
+++ src/HttpReply.cc 2010-08-18 01:02:52 +0000
@@ -523,41 +523,41 @@ HttpReply::httpMsgParseError()
* Indicate whether or not we would usually expect an entity-body
* along with this response
*/
bool
HttpReply::expectingBody(const HttpRequestMethod& req_method, int64_t& theSize) const
{
bool expectBody = true;
if (req_method == METHOD_HEAD)
expectBody = false;
else if (sline.status == HTTP_NO_CONTENT)
expectBody = false;
else if (sline.status == HTTP_NOT_MODIFIED)
expectBody = false;
else if (sline.status < HTTP_OK)
expectBody = false;
else
expectBody = true;
if (expectBody) {
- if (header.hasListMember(HDR_TRANSFER_ENCODING, "chunked", ','))
+ if (header.chunked())
theSize = -1;
else if (content_length >= 0)
theSize = content_length;
else
theSize = -1;
}
return expectBody;
}
bool
HttpReply::receivedBodyTooLarge(HttpRequest& request, int64_t receivedSize)
{
calcMaxBodySize(request);
debugs(58, 3, HERE << receivedSize << " >? " << bodySizeMax);
return bodySizeMax >= 0 && receivedSize > bodySizeMax;
}
bool
HttpReply::expectedBodyTooLarge(HttpRequest& request)
=== modified file 'src/HttpRequest.cc'
--- src/HttpRequest.cc 2010-07-13 16:43:00 +0000
+++ src/HttpRequest.cc 2010-08-18 01:02:23 +0000
@@ -481,49 +481,49 @@ void HttpRequest::packFirstLineInto(Pack
}
/*
* Indicate whether or not we would usually expect an entity-body
* along with this request
*/
bool
HttpRequest::expectingBody(const HttpRequestMethod& unused, int64_t& theSize) const
{
bool expectBody = false;
/*
* GET and HEAD don't usually have bodies, but we should be prepared
* to accept one if the request_entities directive is set
*/
if (method == METHOD_GET || method == METHOD_HEAD)
expectBody = Config.onoff.request_entities ? true : false;
else if (method == METHOD_PUT || method == METHOD_POST)
expectBody = true;
- else if (header.hasListMember(HDR_TRANSFER_ENCODING, "chunked", ','))
+ else if (header.chunked())
expectBody = true;
else if (content_length >= 0)
expectBody = true;
else
expectBody = false;
if (expectBody) {
- if (header.hasListMember(HDR_TRANSFER_ENCODING, "chunked", ','))
+ if (header.chunked())
theSize = -1;
else if (content_length >= 0)
theSize = content_length;
else
theSize = -1;
}
return expectBody;
}
/*
* Create a Request from a URL and METHOD.
*
* If the METHOD is CONNECT, then a host:port pair is looked for instead of a URL.
* If the request cannot be created cleanly, NULL is returned
*/
HttpRequest *
HttpRequest::CreateFromUrlAndMethod(char * url, const HttpRequestMethod& method)
{
return urlParse(method, url, NULL);
=== modified file 'src/client_side.cc'
--- src/client_side.cc 2010-08-07 14:22:54 +0000
+++ src/client_side.cc 2010-08-18 01:07:10 +0000
@@ -1950,42 +1950,41 @@ prepareTransparentURL(ConnStateData * co
} else {
/* Put the local socket IP address as the hostname. */
int url_sz = strlen(url) + 32 + Config.appendDomainLen;
http->uri = (char *)xcalloc(url_sz, 1);
snprintf(http->uri, url_sz, "%s://%s:%d%s",
http->getConn()->port->protocol,
http->getConn()->me.NtoA(ntoabuf,MAX_IPSTRLEN),
http->getConn()->me.GetPort(), url);
debugs(33, 5, "TRANSPARENT REWRITE: '" << http->uri << "'");
}
}
// Temporary hack helper: determine whether the request is chunked, expensive
static bool
isChunkedRequest(const HttpParser *hp)
{
HttpRequest request;
if (!request.parseHeader(HttpParserHdrBuf(hp), HttpParserHdrSz(hp)))
return false;
- return request.header.has(HDR_TRANSFER_ENCODING) &&
- request.header.hasListMember(HDR_TRANSFER_ENCODING, "chunked", ',');
+ return request.header.chunked();
}
/**
* parseHttpRequest()
*
* Returns
* NULL on incomplete requests
* a ClientSocketContext structure on success or failure.
* Sets result->flags.parsed_ok to 0 if failed to parse the request.
* Sets result->flags.parsed_ok to 1 if we have a good request.
*/
static ClientSocketContext *
parseHttpRequest(ConnStateData *conn, HttpParser *hp, HttpRequestMethod * method_p, HttpVersion *http_ver)
{
char *req_hdr = NULL;
char *end;
size_t req_sz;
ClientHttpRequest *http;
ClientSocketContext *result;
=== modified file 'src/http.cc'
--- src/http.cc 2010-08-13 07:53:08 +0000
+++ src/http.cc 2010-08-18 01:01:41 +0000
@@ -709,41 +709,41 @@ HttpStateData::processReplyHeader()
if (newrep->sline.protocol == PROTO_HTTP && newrep->sline.status >= 100 && newrep->sline.status < 200) {
#if WHEN_HTTP11_EXPECT_HANDLED
/* When HTTP/1.1 check if the client is expecting a 1xx reply and maybe pass it on */
if (orig_request->header.has(HDR_EXPECT)) {
// TODO: pass to the client anyway?
}
#endif
delete newrep;
debugs(11, 2, HERE << "1xx headers consume " << header_bytes_read << " bytes header.");
header_bytes_read = 0;
if (reply_bytes_read > 0)
debugs(11, 2, HERE << "1xx headers consume " << reply_bytes_read << " bytes reply.");
reply_bytes_read = 0;
ctx_exit(ctx);
processReplyHeader();
return;
}
flags.chunked = 0;
- if (newrep->sline.protocol == PROTO_HTTP && newrep->header.hasListMember(HDR_TRANSFER_ENCODING, "chunked", ',')) {
+ if (newrep->sline.protocol == PROTO_HTTP && newrep->header.chunked()) {
flags.chunked = 1;
httpChunkDecoder = new ChunkedCodingParser;
}
if (!peerSupportsConnectionPinning())
orig_request->flags.connection_auth_disabled = 1;
HttpReply *vrep = setVirginReply(newrep);
flags.headers_parsed = 1;
keepaliveAccounting(vrep);
checkDateSkew(vrep);
processSurrogateControl (vrep);
/** \todo IF the reply is a 1.0 reply, AND it has a Connection: Header
* Parse the header and remove all referenced headers
*/