]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Bug 2821: Ignore Content-Range in non-206 responses (#77)
authorEduard Bagdasaryan <dictiano@gmail.com>
Thu, 16 Nov 2017 00:05:21 +0000 (03:05 +0300)
committerAmos Jeffries <yadij@users.noreply.github.com>
Thu, 30 Nov 2017 08:14:04 +0000 (21:14 +1300)
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.

src/HttpHdrRange.cc
src/HttpHeaderRange.h
src/HttpReply.cc
src/HttpReply.h
src/clients/Client.cc
src/http/Stream.cc
src/tests/stub_HttpReply.cc

index baf9656e59303eeabb7c90785cc5a6431a9acd9f..5abd799646e38ec2fce2b96b645007211755ea66 100644 (file)
@@ -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;
 
@@ -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);
index 85e2b93a66e7f09856ad28b26ab066095f947b42..f2f1ab68aa6905266dedcf30d2e6953dd3f3a7d0 100644 (file)
@@ -78,7 +78,7 @@ public:
     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:
index 1043096a6bc9d2e53897393cd200a462f8664ec3..bebe75971658f057b03e52d9ed840a1a79ceb392 100644 (file)
 #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();
 }
@@ -304,7 +311,8 @@ HttpReply::hdrCacheInit()
     date = header.getTime(Http::HdrType::DATE);
     last_modified = header.getTime(Http::HdrType::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(Http::HdrType::CONTENT_TYPE);
 
@@ -317,6 +325,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()
index 36418cf1a74fb5c43d0f02bf2c8cc97b67a188ed..492aaa4adbfd663cfb0bd07eb7795ee5fb56a51a 100644 (file)
@@ -52,7 +52,8 @@ public:
 
     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;
 
@@ -142,6 +143,8 @@ private:
 
     mutable int64_t bodySizeMax; /**< cached result of calcMaxBodySize */
 
+    HttpHdrContRange *content_range; ///< parsed Content-Range; nil for non-206 responses!
+
 protected:
     virtual void packFirstLineInto(Packable * p, bool) const { sline.packInto(p); }
 
index ab32dad78f2dd0969ef6782929730d85fe83fdd4..546c270065d0189577caf936852c75bb456d56b8 100644 (file)
@@ -524,9 +524,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
index 859eb51fb690242968e782250d1042128011514f..a311c4bdf1ab1e17e3db534006b8a3738d72462f 100644 (file)
@@ -163,12 +163,12 @@ Http::Stream::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;
@@ -221,14 +221,14 @@ Http::Stream::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, "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?
 
@@ -399,13 +399,20 @@ Http::Stream::buildRangeHeader(HttpReply *rep)
     assert(request->range);
     /* check if we still want to do ranges */
     int64_t roffLimit = request->getRangeOffsetLimit();
+    auto 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(Http::HdrType::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(Http::HdrType::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)
@@ -440,8 +447,9 @@ Http::Stream::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 != nullptr ?
-                                 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;
@@ -452,19 +460,18 @@ Http::Stream::buildRangeHeader(HttpReply *rep)
         /* append appropriate header(s) */
         if (spec_count == 1) {
             if (!replyMatchRequest) {
-                hdr->delById(Http::HdrType::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(Http::HdrType::CONTENT_RANGE)) {
+                if (!contentRange) {
                     /* No content range, so this was a full object we are
                      * sending parts of.
                      */
index 202e964df4490ff08167c914fbfa78fed51e37ec..e761514cb0be53ebc2bbba0bb489678f514e6662 100644 (file)
@@ -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
@@ -30,4 +30,5 @@ HttpReply::HttpReply() : HttpMsg(hoReply), date (0), last_modified (0),
     bool HttpReply::inheritProperties(const HttpMsg *aMsg) STUB_RETVAL(false)
     bool HttpReply::updateOnNotModified(HttpReply const*) STUB_RETVAL(false)
     int64_t HttpReply::bodySize(const HttpRequestMethod&) const STUB_RETVAL(0)
+    const HttpHdrContRange *HttpReply::contentRange() const STUB_RETVAL(nullptr)