Skip to content

Commit

Permalink
Bug 2821: Ignore Content-Range in non-206 responses (squid-cache#250)
Browse files Browse the repository at this point in the history
Squid used to honor Content-Range header in HTTP 200 OK (and possibly
other non-206) responses, truncating (and possibly enlarging) some
response bodies. RFC 7233 declares Content-Range meaningless for
standard HTTP status codes other than 206 and 416. Squid now relays
meaningless Content-Range as is, without using its value.

Why not just strip a meaningless Content-Range header? Squid does not
really know whether it is the status code or the header that is "wrong".
Let the client figure it out while the server remains responsible.

Also ignore Content-Range in 416 (Range Not Satisfiable) responses
because that header does not apply to the response body.

Also fixed body corruption of (unlikely) multipart 206 responses to
single-part Range requests. Valid multipart responses carry no
Content-Range (in the primary header), which confused Squid.
  • Loading branch information
eduard-bagdasaryan authored and yadij committed Jul 11, 2018
1 parent 79b2379 commit e11ad24
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 28 deletions.
6 changes: 3 additions & 3 deletions src/HttpHdrRange.cc
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,8 @@ HttpHdrRange::canonize(HttpReply *rep)
{
assert(rep);

if (rep->content_range)
clen = rep->content_range->elength;
if (rep->contentRange())
clen = rep->contentRange()->elength;
else
clen = rep->content_length;

Expand Down Expand Up @@ -527,7 +527,7 @@ HttpHdrRange::offsetLimitExceeded(const int64_t limit) const
}

bool
HttpHdrRange::contains(HttpHdrRangeSpec& r) const
HttpHdrRange::contains(const HttpHdrRangeSpec& r) const
{
assert(r.length >= 0);
HttpHdrRangeSpec::HttpRange rrange(r.offset, r.offset + r.length);
Expand Down
2 changes: 1 addition & 1 deletion src/HttpHeaderRange.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class HttpHdrRange
int64_t firstOffset() const;
int64_t lowestOffset(int64_t) const;
bool offsetLimitExceeded(const int64_t limit) const;
bool contains(HttpHdrRangeSpec& r) const;
bool contains(const HttpHdrRangeSpec& r) const;
std::vector<HttpHdrRangeSpec *> specs;

private:
Expand Down
23 changes: 19 additions & 4 deletions src/HttpReply.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,16 @@
#include "Store.h"
#include "StrList.h"

HttpReply::HttpReply() : HttpMsg(hoReply), date (0), last_modified (0),
expires (0), surrogate_control (NULL), content_range (NULL), keep_alive (0),
protoPrefix("HTTP/"), bodySizeMax(-2)
HttpReply::HttpReply():
HttpMsg(hoReply),
date(0),
last_modified(0),
expires(0),
surrogate_control(nullptr),
keep_alive(0),
protoPrefix("HTTP/"),
bodySizeMax(-2),
content_range(nullptr)
{
init();
}
Expand Down Expand Up @@ -317,7 +324,8 @@ HttpReply::hdrCacheInit()
date = header.getTime(HDR_DATE);
last_modified = header.getTime(HDR_LAST_MODIFIED);
surrogate_control = header.getSc();
content_range = header.getContRange();
content_range = (sline.status() == Http::scPartialContent) ?
header.getContRange() : nullptr;
keep_alive = persistent() ? 1 : 0;
const char *str = header.getStr(HDR_CONTENT_TYPE);

Expand All @@ -330,6 +338,13 @@ HttpReply::hdrCacheInit()
expires = hdrExpirationTime();
}

const HttpHdrContRange *
HttpReply::contentRange() const
{
assert(!content_range || sline.status() == Http::scPartialContent);
return content_range;
}

/* sync this routine when you update HttpReply struct */
void
HttpReply::hdrCacheClean()
Expand Down
5 changes: 4 additions & 1 deletion src/HttpReply.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ class HttpReply: public HttpMsg

HttpHdrSc *surrogate_control;

HttpHdrContRange *content_range;
/// \returns parsed Content-Range for a 206 response and nil for others
const HttpHdrContRange *contentRange() const;

short int keep_alive;

Expand Down Expand Up @@ -142,6 +143,8 @@ class HttpReply: public HttpMsg

mutable int64_t bodySizeMax; /**< cached result of calcMaxBodySize */

HttpHdrContRange *content_range; ///< parsed Content-Range; nil for non-206 responses!

protected:
virtual void packFirstLineInto(Packer * p, bool) const { sline.packInto(p); }

Expand Down
35 changes: 21 additions & 14 deletions src/client_side.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1285,13 +1285,20 @@ ClientSocketContext::buildRangeHeader(HttpReply * rep)
/* check if we still want to do ranges */

int64_t roffLimit = request->getRangeOffsetLimit();
const HttpHdrContRange *contentRange = rep ? rep->contentRange() : nullptr;

if (!rep)
range_err = "no [parse-able] reply";
else if ((rep->sline.status() != Http::scOkay) && (rep->sline.status() != Http::scPartialContent))
range_err = "wrong status code";
else if (hdr->has(HDR_CONTENT_RANGE))
range_err = "origin server does ranges";
else if (rep->sline.status() == Http::scPartialContent)
range_err = "too complex response"; // probably contains what the client needs
else if (rep->sline.status() != Http::scOkay)
range_err = "wrong status code";
else if (hdr->has(HDR_CONTENT_RANGE)) {
Must(!contentRange); // this is a 200, not 206 response
range_err = "meaningless response"; // the status code or the header is wrong
}
else if (rep->content_length < 0)
range_err = "unknown length";
else if (rep->content_length != http->memObject()->getReply()->content_length)
Expand Down Expand Up @@ -1323,8 +1330,9 @@ ClientSocketContext::buildRangeHeader(HttpReply * rep)
// web server responded with a valid, but unexpected range.
// will (try-to) forward as-is.
//TODO: we should cope with multirange request/responses
bool replyMatchRequest = rep->content_range != NULL ?
request->range->contains(rep->content_range->spec) :
// TODO: review, since rep->content_range is always nil here.
bool replyMatchRequest = contentRange != nullptr ?
request->range->contains(contentRange->spec) :
true;
const int spec_count = http->request->range->specs.size();
int64_t actual_clen = -1;
Expand All @@ -1336,19 +1344,18 @@ ClientSocketContext::buildRangeHeader(HttpReply * rep)

if (spec_count == 1) {
if (!replyMatchRequest) {
hdr->delById(HDR_CONTENT_RANGE);
hdr->putContRange(rep->content_range);
hdr->putContRange(contentRange);
actual_clen = rep->content_length;
//http->range_iter.pos = rep->content_range->spec.begin();
(*http->range_iter.pos)->offset = rep->content_range->spec.offset;
(*http->range_iter.pos)->length = rep->content_range->spec.length;
(*http->range_iter.pos)->offset = contentRange->spec.offset;
(*http->range_iter.pos)->length = contentRange->spec.length;

} else {
HttpHdrRange::iterator pos = http->request->range->begin();
assert(*pos);
/* append Content-Range */

if (!hdr->has(HDR_CONTENT_RANGE)) {
if (!contentRange) {
/* No content range, so this was a full object we are
* sending parts of.
*/
Expand Down Expand Up @@ -1719,12 +1726,12 @@ ClientSocketContext::getNextRangeOffset() const
return start;
}

} else if (reply && reply->content_range) {
} else if (reply && reply->contentRange()) {
/* request does not have ranges, but reply does */
/** \todo FIXME: should use range_iter_pos on reply, as soon as reply->content_range
* becomes HttpHdrRange rather than HttpHdrRangeSpec.
*/
return http->out.offset + reply->content_range->spec.offset;
return http->out.offset + reply->contentRange()->spec.offset;
}

return http->out.offset;
Expand Down Expand Up @@ -1768,14 +1775,14 @@ ClientSocketContext::socketState()
// we got everything we wanted from the store
return STREAM_COMPLETE;
}
} else if (reply && reply->content_range) {
} else if (reply && reply->contentRange()) {
/* reply has content-range, but Squid is not managing ranges */
const int64_t &bytesSent = http->out.offset;
const int64_t &bytesExpected = reply->content_range->spec.length;
const int64_t &bytesExpected = reply->contentRange()->spec.length;

debugs(33, 7, HERE << "body bytes sent vs. expected: " <<
bytesSent << " ? " << bytesExpected << " (+" <<
reply->content_range->spec.offset << ")");
reply->contentRange()->spec.offset << ")");

// did we get at least what we expected, based on range specs?

Expand Down
5 changes: 2 additions & 3 deletions src/clients/Client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -523,9 +523,8 @@ Client::haveParsedReplyHeaders()
maybePurgeOthers();

// adaptation may overwrite old offset computed using the virgin response
const bool partial = theFinalReply->content_range &&
theFinalReply->sline.status() == Http::scPartialContent;
currentOffset = partial ? theFinalReply->content_range->spec.offset : 0;
const bool partial = theFinalReply->contentRange();
currentOffset = partial ? theFinalReply->contentRange()->spec.offset : 0;
}

/// whether to prevent caching of an otherwise cachable response
Expand Down
5 changes: 3 additions & 2 deletions src/tests/stub_HttpReply.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
#include "tests/STUB.h"

HttpReply::HttpReply() : HttpMsg(hoReply), date (0), last_modified (0),
expires (0), surrogate_control (NULL), content_range (NULL), keep_alive (0),
protoPrefix("HTTP/"), do_clean(false), bodySizeMax(-2)
expires(0), surrogate_control(nullptr), keep_alive(0),
protoPrefix("HTTP/"), do_clean(false), bodySizeMax(-2), content_range(nullptr)
STUB_NOP
HttpReply::~HttpReply() STUB
void HttpReply::setHeaders(Http::StatusCode status, const char *reason, const char *ctype, int64_t clen, time_t lmt, time_t expires_) STUB
Expand All @@ -29,4 +29,5 @@ HttpReply::HttpReply() : HttpMsg(hoReply), date (0), last_modified (0),
HttpReply * HttpReply::clone() const STUB_RETVAL(NULL)
bool HttpReply::inheritProperties(const HttpMsg *aMsg) STUB_RETVAL(false)
int64_t HttpReply::bodySize(const HttpRequestMethod&) const STUB_RETVAL(0)
const HttpHdrContRange *HttpReply::contentRange() const STUB_RETVAL(nullptr)

0 comments on commit e11ad24

Please sign in to comment.