]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/HttpHdrRange.cc
Cleanup: zap CVS Id tags
[thirdparty/squid.git] / src / HttpHdrRange.cc
index 87cd399e1a9dcaa5ea5cac0912b2dc01c85a0900..78e59bbd0df6c6635297e7ad2bb87f61c4fc9b9a 100644 (file)
 
 /*
- * $Id: HttpHdrRange.cc,v 1.4 1998/03/11 21:10:57 rousskov Exp $
+ * $Id$
  *
  * DEBUG: section 64    HTTP Range Header
  * AUTHOR: Alex Rousskov
  *
- * SQUID Internet Object Cache  http://squid.nlanr.net/Squid/
- * --------------------------------------------------------
+ * SQUID Web Proxy Cache          http://www.squid-cache.org/
+ * ----------------------------------------------------------
  *
- *  Squid is the result of efforts by numerous individuals from the
- *  Internet community.  Development is led by Duane Wessels of the
- *  National Laboratory for Applied Network Research and funded by
- *  the National Science Foundation.
+ *  Squid is the result of efforts by numerous individuals from
+ *  the Internet community; see the CONTRIBUTORS file for full
+ *  details.   Many organizations have provided support for Squid's
+ *  development; see the SPONSORS file for full details.  Squid is
+ *  Copyrighted (C) 2001 by the Regents of the University of
+ *  California; see the COPYRIGHT file for full details.  Squid
+ *  incorporates software developed and/or copyrighted by other
+ *  sources; see the CREDITS file for full details.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
- *  
+ *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
- *  
+ *
  *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
- *  
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ *
  */
 
 #include "squid.h"
+#include "Store.h"
+#include "HttpHeaderRange.h"
+#include "client_side_request.h"
+#include "HttpReply.h"
 
-#if 0
-    Currently only byte ranges are supported
-
-    Essentially, there are three types of byte ranges:
-
-       1) first-byte-pos "-" last-byte-pos  // range
-       2) first-byte-pos "-"                // trailer
-       3)                "-" suffix-length  // suffix (last length bytes)
-
-
-    When Range field is parsed, we have no clue about the content length of
-    the document. Thus, we simply code an "absent" part using range_spec_unknown
-    constant.
-
-    Note: when response length becomes known, we convert any range spec into
-    type one above. (Canonization process).
-
-#endif
-
+/*
+ *    Currently only byte ranges are supported
+ *
+ *    Essentially, there are three types of byte ranges:
+ *
+ *      1) first-byte-pos "-" last-byte-pos  // range
+ *      2) first-byte-pos "-"                // trailer
+ *      3)                "-" suffix-length  // suffix (last length bytes)
+ *
+ *
+ *    When Range field is parsed, we have no clue about the content
+ *    length of the document. Thus, we simply code an "absent" part
+ *    using HttpHdrRangeSpec::UnknownPosition constant.
+ *
+ *    Note: when response length becomes known, we convert any range
+ *    spec into type one above. (Canonization process).
+ */
 
-/* local constants */
-#define range_spec_unknown ((size_t)-1)
 
 /* local routines */
-#define known_spec(s) ((s) != range_spec_unknown)
-#define size_min(a,b) ((a) <= (b) ? (a) : (b))
-#define size_diff(a,b) ((a) >= (b) ? ((a)-(b)) : 0)
-static HttpHdrRangeSpec *httpHdrRangeSpecDup(const HttpHdrRangeSpec *spec);
-static int httpHdrRangeSpecCanonize(HttpHdrRangeSpec *spec, size_t clen);
-static void httpHdrRangeSpecPackInto(const HttpHdrRangeSpec *spec, Packer *p);
+#define known_spec(s) ((s) > HttpHdrRangeSpec::UnknownPosition)
 
 /* globals */
-static int RangeParsedCount = 0;
+size_t HttpHdrRange::ParsedCount = 0;
+int64_t const HttpHdrRangeSpec::UnknownPosition = -1;
 
 /*
  * Range-Spec
  */
 
-static HttpHdrRangeSpec *
-httpHdrRangeSpecCreate()
+HttpHdrRangeSpec::HttpHdrRangeSpec() : offset(UnknownPosition), length(UnknownPosition) {}
+
+/* parses range-spec and returns new object on success */
+HttpHdrRangeSpec *
+HttpHdrRangeSpec::Create(const char *field, int flen)
 {
-    return memAllocate(MEM_HTTP_HDR_RANGE_SPEC);
+    HttpHdrRangeSpec spec;
+
+    if (!spec.parseInit(field, flen))
+        return NULL;
+
+    return new HttpHdrRangeSpec(spec);
 }
 
-/* parses range-spec and returns new object on success */
-static HttpHdrRangeSpec *
-httpHdrRangeSpecParseCreate(const char *field, int flen)
+bool
+HttpHdrRangeSpec::parseInit(const char *field, int flen)
 {
-    HttpHdrRangeSpec spec = { range_spec_unknown, range_spec_unknown };
     const char *p;
+
     if (flen < 2)
-       return NULL;
+        return false;
+
     /* is it a suffix-byte-range-spec ? */
     if (*field == '-') {
-       if (!httpHeaderParseSize(field+1, &spec.length))
-           return NULL;
+        if (!httpHeaderParseOffset(field + 1, &length))
+            return false;
     } else
-    /* must have a '-' somewhere in _this_ field */
-    if (!((p = strchr(field, '-')) || (p-field >= flen))) {
-       debug(64, 2) ("ignoring invalid (missing '-') range-spec near: '%s'\n", field);
-       return NULL;
-    } else {
-       if (!httpHeaderParseSize(field, &spec.offset))
-           return NULL;
-       p++;
-       /* do we have last-pos ? */
-       if (p - field < flen) {
-           size_t last_pos;
-           if (!httpHeaderParseSize(p, &last_pos))
-               return NULL;
-           spec.length = size_diff(last_pos+1, spec.offset);
-       }
-    }
+        /* must have a '-' somewhere in _this_ field */
+        if (!((p = strchr(field, '-')) || (p - field >= flen))) {
+            debugs(64, 2, "ignoring invalid (missing '-') range-spec near: '" << field << "'");
+            return false;
+        } else {
+            if (!httpHeaderParseOffset(field, &offset))
+                return false;
+
+            p++;
+
+            /* do we have last-pos ? */
+            if (p - field < flen) {
+                int64_t last_pos;
+
+                if (!httpHeaderParseOffset(p, &last_pos))
+                    return false;
+
+                HttpHdrRangeSpec::HttpRange aSpec (offset, last_pos + 1);
+
+                length = aSpec.size();
+            }
+        }
+
     /* we managed to parse, check if the result makes sence */
-    if (known_spec(spec.length) && !spec.length) {
-       debug(64, 2) ("ignoring invalid (zero length) range-spec near: '%s'\n", field);
-       return NULL;
+    if (length == 0) {
+        debugs(64, 2, "ignoring invalid (zero length) range-spec near: '" << field << "'");
+        return false;
     }
-    return httpHdrRangeSpecDup(&spec);
+
+    return true;
 }
 
-static void
-httpHdrRangeSpecDestroy(HttpHdrRangeSpec *spec)
+void
+HttpHdrRangeSpec::packInto(Packer * packer) const
 {
-    memFree(MEM_HTTP_HDR_RANGE_SPEC, spec);
+    if (!known_spec(offset))   /* suffix */
+        packerPrintf(packer, "-%"PRId64,  length);
+    else if (!known_spec(length))              /* trailer */
+        packerPrintf(packer, "%"PRId64"-", offset);
+    else                       /* range */
+        packerPrintf(packer, "%"PRId64"-%"PRId64,
+                     offset, offset + length - 1);
 }
 
-
-static HttpHdrRangeSpec *
-httpHdrRangeSpecDup(const HttpHdrRangeSpec *spec)
+void
+HttpHdrRangeSpec::outputInfo( char const *note) const
 {
-    HttpHdrRangeSpec *dup = httpHdrRangeSpecCreate();
-    dup->offset = spec->offset;
-    dup->length = spec->length;
-    return dup;
+    debugs(64, 5, "HttpHdrRangeSpec::canonize: " << note << ": [" <<
+           offset << ", " << offset + length <<
+           ") len: " << length);
 }
 
-static void
-httpHdrRangeSpecPackInto(const HttpHdrRangeSpec *spec, Packer *p)
+/* fills "absent" positions in range specification based on response body size
+ * returns true if the range is still valid
+ * range is valid if its intersection with [0,length-1] is not empty
+ */
+int
+HttpHdrRangeSpec::canonize(int64_t clen)
 {
-    if (!known_spec(spec->offset)) /* suffix */
-       packerPrintf(p, "-%d", spec->length);
-    else
-    if (!known_spec(spec->length)) /* trailer */
-       packerPrintf(p, "%d-", spec->offset);
-    else                           /* range */
-       packerPrintf(p, "%d-%d", 
-           spec->offset, spec->offset+spec->length-1);
+    outputInfo ("have");
+    HttpRange object(0, clen);
+
+    if (!known_spec(offset)) { /* suffix */
+        assert(known_spec(length));
+        offset = object.intersection(HttpRange (clen - length, clen)).start;
+    } else if (!known_spec(length)) {  /* trailer */
+        assert(known_spec(offset));
+        HttpRange newRange = object.intersection(HttpRange (offset, clen));
+        length = newRange.size();
+    }
+    /* we have a "range" now, adjust length if needed */
+    assert(known_spec(length));
+
+    assert(known_spec(offset));
+
+    HttpRange newRange = object.intersection (HttpRange (offset, offset + length));
+
+    length = newRange.size();
+
+    outputInfo ("done");
+
+    return length > 0;
 }
 
-/* fills "absent" positions in range specification based on response body size 
-   returns true if the range is still valid
-   range is valid if its intersection with [0,length-1] is not empty
-*/
-static int
-httpHdrRangeSpecCanonize(HttpHdrRangeSpec *spec, size_t clen)
+/* merges recepient with donor if possible; returns true on success
+ * both specs must be canonized prior to merger, of course */
+bool
+HttpHdrRangeSpec::mergeWith(const HttpHdrRangeSpec * donor)
 {
-    if (!known_spec(spec->offset)) /* suffix */
-       spec->offset = size_diff(clen, spec->length);
-    else
-    if (!known_spec(spec->length)) /* trailer */
-       spec->length = size_diff(clen, spec->offset);
-    /* we have a "range" now, adjust length if needed */
-    assert(known_spec(spec->length));
-    assert(known_spec(spec->offset));
-    spec->length = size_min(size_diff(clen, spec->offset), spec->length);
-    /* check range validity */
-    return spec->length > 0;
+    bool merged (false);
+#if MERGING_BREAKS_NOTHING
+    /* Note: this code works, but some clients may not like its effects */
+    int64_t rhs = offset + length;             /* no -1 ! */
+    const int64_t donor_rhs = donor->offset + donor->length;   /* no -1 ! */
+    assert(known_spec(offset));
+    assert(known_spec(donor->offset));
+    assert(length > 0);
+    assert(donor->length > 0);
+    /* do we have a left hand side overlap? */
+
+    if (donor->offset < offset && offset <= donor_rhs) {
+        offset = donor->offset;        /* decrease left offset */
+        merged = 1;
+    }
+
+    /* do we have a right hand side overlap? */
+    if (donor->offset <= rhs && rhs < donor_rhs) {
+        rhs = donor_rhs;       /* increase right offset */
+        merged = 1;
+    }
+
+    /* adjust length if offsets have been changed */
+    if (merged) {
+        assert(rhs > offset);
+        length = rhs - offset;
+    } else {
+        /* does recepient contain donor? */
+        merged =
+            offset <= donor->offset && donor->offset < rhs;
+    }
+
+#endif
+    return merged;
 }
 
 /*
  * Range
  */
 
-HttpHdrRange *
-httpHdrRangeCreate()
-{
-    HttpHdrRange *r = memAllocate(MEM_HTTP_HDR_RANGE);
-    stackInit(&r->specs);
-    return r;
-}
+HttpHdrRange::HttpHdrRange () : clen (HttpHdrRangeSpec::UnknownPosition)
+{}
 
 HttpHdrRange *
-httpHdrRangeParseCreate(const char *str)
+HttpHdrRange::ParseCreate(const String * range_spec)
 {
-    HttpHdrRange *r = httpHdrRangeCreate();
-    if (!httpHdrRangeParseInit(r, str)) {
-       httpHdrRangeDestroy(r);
-       r = NULL;
+    HttpHdrRange *r = new HttpHdrRange;
+
+    if (!r->parseInit(range_spec)) {
+        delete r;
+        r = NULL;
     }
+
     return r;
 }
 
 /* returns true if ranges are valid; inits HttpHdrRange */
-int
-httpHdrRangeParseInit(HttpHdrRange *range, const char *str)
+bool
+HttpHdrRange::parseInit(const String * range_spec)
 {
     const char *item;
     const char *pos = NULL;
     int ilen;
-    assert(range && str);
-    RangeParsedCount++;
-    debug(64, 8) ("parsing range field: '%s'\n", str);
+    int count = 0;
+    assert(this && range_spec);
+    ++ParsedCount;
+    debugs(64, 8, "parsing range field: '" << range_spec->buf() << "'");
     /* check range type */
-    if (strncasecmp(str, "bytes=", 6))
-       return 0;
-    str += 6;
+
+    if (range_spec->caseCmp("bytes=", 6))
+        return 0;
+
+    /* skip "bytes="; hack! */
+    pos = range_spec->buf() + 6;
+
     /* iterate through comma separated list */
-    while (strListGetItem(str, ',', &item, &ilen, &pos)) {
-       HttpHdrRangeSpec *spec = httpHdrRangeSpecParseCreate(item, ilen);
-       /*
-        * HTTP/1.1 draft says we must ignore the whole header field if one spec
-        * is invalid. However, RFC 2068 just says that we must ignore that spec.
-        */
-       if (spec)
-           stackPush(&range->specs, spec);
+    while (strListGetItem(range_spec, ',', &item, &ilen, &pos)) {
+        HttpHdrRangeSpec *spec = HttpHdrRangeSpec::Create(item, ilen);
+        /*
+         * HTTP/1.1 draft says we must ignore the whole header field if one spec
+         * is invalid. However, RFC 2068 just says that we must ignore that spec.
+         */
+
+        if (spec)
+            specs.push_back(spec);
+
+        ++count;
     }
-    debug(68, 8) ("parsed range range count: %d\n", range->specs.count);
-    return range->specs.count;
+
+    debugs(64, 8, "parsed range range count: " << count << ", kept " <<
+           specs.size());
+    return specs.count != 0;
 }
 
-void
-httpHdrRangeDestroy(HttpHdrRange *range)
+HttpHdrRange::~HttpHdrRange()
 {
-    assert(range);
-    while (range->specs.count)
-       httpHdrRangeSpecDestroy(stackPop(&range->specs));
-    stackClean(&range->specs);
-    memFree(MEM_HTTP_HDR_RANGE, range);
+    while (specs.size())
+        delete specs.pop_back();
 }
 
-HttpHdrRange *
-httpHdrRangeDup(const HttpHdrRange * range)
+HttpHdrRange::HttpHdrRange(HttpHdrRange const &old) : specs()
 {
-    HttpHdrRange *dup;
-    int i;
-    assert(range);
-    dup = httpHdrRangeCreate();
-    stackPrePush(&dup->specs, range->specs.count);
-    for (i = 0; i < range->specs.count; i++)
-       stackPush(&dup->specs, httpHdrRangeSpecDup(range->specs.items[i]));
-    assert(range->specs.count == dup->specs.count);
-    return dup;
+    specs.reserve(old.specs.size());
+
+    for (const_iterator i = old.begin(); i != old.end(); ++i)
+        specs.push_back(new HttpHdrRangeSpec ( **i));
+
+    assert(old.specs.size() == specs.size());
+}
+
+HttpHdrRange::iterator
+HttpHdrRange::begin()
+{
+    return specs.begin();
+}
+
+HttpHdrRange::iterator
+HttpHdrRange::end()
+{
+    return specs.end();
+}
+
+HttpHdrRange::const_iterator
+HttpHdrRange::begin() const
+{
+    return specs.begin();
+}
+
+HttpHdrRange::const_iterator
+HttpHdrRange::end() const
+{
+    return specs.end();
 }
 
 void
-httpHdrRangePackInto(const HttpHdrRange * range, Packer * p)
+HttpHdrRange::packInto(Packer * packer) const
 {
-    HttpHdrRangePos pos = HttpHdrRangeInitPos;
-    HttpHdrRangeSpec spec;
-    assert(range);
-    while (httpHdrRangeGetSpec(range, &spec, &pos)) {
-       if (pos != HttpHdrRangeInitPos)
-           packerAppend(p, ",", 1);
-       httpHdrRangeSpecPackInto(&spec, p);
+    const_iterator pos = begin();
+    assert(this);
+
+    while (pos != end()) {
+        if (pos != begin())
+            packerAppend(packer, ",", 1);
+
+        (*pos)->packInto(packer);
+
+        ++pos;
     }
 }
 
 void
-httpHdrRangeJoinWith(HttpHdrRange * range, const HttpHdrRange * new_range)
+HttpHdrRange::merge (Vector<HttpHdrRangeSpec *> &basis)
 {
-    HttpHdrRangePos pos = HttpHdrRangeInitPos;
-    HttpHdrRangeSpec spec;
-    assert(range && new_range);
-    stackPrePush(&range->specs, new_range->specs.count);
-    while (httpHdrRangeGetSpec(new_range, &spec, &pos))
-       stackPush(&range->specs, httpHdrRangeSpecDup(&spec));
+    /* reset old array */
+    specs.clean();
+    /* merge specs:
+     * take one spec from "goods" and merge it with specs from
+     * "specs" (if any) until there is no overlap */
+    iterator i = basis.begin();
+
+    while (i != basis.end()) {
+        if (specs.size() && (*i)->mergeWith(specs.back())) {
+            /* merged with current so get rid of the prev one */
+            delete specs.pop_back();
+            continue;  /* re-iterate */
+        }
+
+        specs.push_back (*i);
+        ++i;                   /* progress */
+    }
+
+    debugs(64, 3, "HttpHdrRange::merge: had " << basis.size() <<
+           " specs, merged " << basis.size() - specs.size() << " specs");
 }
 
+
+void
+HttpHdrRange::getCanonizedSpecs (Vector<HttpHdrRangeSpec *> &copy)
+{
+    /* canonize each entry and destroy bad ones if any */
+
+    for (iterator pos (begin()); pos != end(); ++pos) {
+        if ((*pos)->canonize(clen))
+            copy.push_back (*pos);
+        else
+            delete (*pos);
+    }
+
+    debugs(64, 3, "HttpHdrRange::getCanonizedSpecs: found " <<
+           specs.size() - copy.size() << " bad specs");
+}
+
+#include "HttpHdrContRange.h"
+
 /*
  * canonizes all range specs within a set preserving the order
- * returns true if the set is valid after canonization; 
- * the set is valid if 
- *   - all range specs are valid and 
+ * returns true if the set is valid after canonization;
+ * the set is valid if
+ *   - all range specs are valid and
  *   - there is at least one range spec
  */
 int
-httpHdrRangeCanonize(HttpHdrRange *range, size_t clen)
+HttpHdrRange::canonize(HttpReply *rep)
 {
-    int i;
-    assert(range);
-    for (i = 0; i < range->specs.count; i++)
-        if (!httpHdrRangeSpecCanonize(range->specs.items[i], clen))
-           return 0;
-    return range->specs.count;
+    assert(this && rep);
+
+    if (rep->content_range)
+        clen = rep->content_range->elength;
+    else
+        clen = rep->content_length;
+
+    return canonize (clen);
 }
 
-/* searches for next range, returns true if found */
 int
-httpHdrRangeGetSpec(const HttpHdrRange *range, HttpHdrRangeSpec *spec, int *pos)
+HttpHdrRange::canonize (int64_t newClen)
 {
-    assert(range && spec);
-    assert(pos && *pos >= -1 && *pos < range->specs.count);
-    (*pos)++;
-    if (*pos < range->specs.count) {
-       *spec = *(HttpHdrRangeSpec*)range->specs.items[*pos];
-       return 1;
+    clen = newClen;
+    debugs(64, 3, "HttpHdrRange::canonize: started with " << specs.count <<
+           " specs, clen: " << clen);
+    Vector<HttpHdrRangeSpec*> goods;
+    getCanonizedSpecs(goods);
+    merge (goods);
+    debugs(64, 3, "HttpHdrRange::canonize: finished with " << specs.count <<
+           " specs");
+    return specs.count > 0;
+}
+
+/* hack: returns true if range specs are too "complex" for Squid to handle */
+/* requires that specs are "canonized" first! */
+bool
+HttpHdrRange::isComplex() const
+{
+    int64_t offset = 0;
+    assert(this);
+    /* check that all rangers are in "strong" order */
+    const_iterator pos (begin());
+
+    while (pos != end()) {
+        /* Ensure typecasts is safe */
+        assert ((*pos)->offset >= 0);
+
+        if ((*pos)->offset < offset)
+            return 1;
+
+        offset = (*pos)->offset + (*pos)->length;
+
+        ++pos;
     }
-    spec->offset = spec->length = 0;
+
     return 0;
 }
+
+/*
+ * hack: returns true if range specs may be too "complex" when "canonized".
+ * see also: HttpHdrRange::isComplex.
+ */
+bool
+HttpHdrRange::willBeComplex() const
+{
+    assert(this);
+    /* check that all rangers are in "strong" order, */
+    /* as far as we can tell without the content length */
+    int64_t offset = 0;
+
+    for (const_iterator pos (begin()); pos != end(); ++pos) {
+        if (!known_spec((*pos)->offset))       /* ignore unknowns */
+            continue;
+
+        /* Ensure typecasts is safe */
+        assert ((*pos)->offset >= 0);
+
+        if ((*pos)->offset < offset)
+            return true;
+
+        offset = (*pos)->offset;
+
+        if (known_spec((*pos)->length))        /* avoid  unknowns */
+            offset += (*pos)->length;
+    }
+
+    return false;
+}
+
+/*
+ * Returns lowest known offset in range spec(s),
+ * or HttpHdrRangeSpec::UnknownPosition
+ * this is used for size limiting
+ */
+int64_t
+HttpHdrRange::firstOffset() const
+{
+    int64_t offset = HttpHdrRangeSpec::UnknownPosition;
+    assert(this);
+    const_iterator pos = begin();
+
+    while (pos != end()) {
+        if ((*pos)->offset < offset || !known_spec(offset))
+            offset = (*pos)->offset;
+
+        ++pos;
+    }
+
+    return offset;
+}
+
+/*
+ * Returns lowest offset in range spec(s), 0 if unknown.
+ * This is used for finding out where we need to start if all
+ * ranges are combined into one, for example FTP REST.
+ * Use 0 for size if unknown
+ */
+int64_t
+HttpHdrRange::lowestOffset(int64_t size) const
+{
+    int64_t offset = HttpHdrRangeSpec::UnknownPosition;
+    const_iterator pos = begin();
+    assert(this);
+
+    while (pos != end()) {
+        int64_t current = (*pos)->offset;
+
+        if (!known_spec(current)) {
+            if ((*pos)->length > size || !known_spec((*pos)->length))
+                return 0;      /* Unknown. Assume start of file */
+
+            current = size - (*pos)->length;
+        }
+
+        if (current < offset || !known_spec(offset))
+            offset = current;
+
+        ++pos;
+    }
+
+    return known_spec(offset) ? offset : 0;
+}
+
+/*
+ * Return true if the first range offset is larger than the configured
+ * limit.
+ * Note that exceeding the limit (returning true) results in only
+ * grabbing the needed range elements from the origin.
+ */
+bool
+HttpHdrRange::offsetLimitExceeded() const
+{
+    if (NULL == this)
+        /* not a range request */
+        return false;
+
+    if (Config.rangeOffsetLimit == 0)
+        /* disabled */
+        return true;
+
+    if (-1 == Config.rangeOffsetLimit)
+        /* forced */
+        return false;
+
+    if (firstOffset() == -1)
+        /* tail request */
+        return true;
+
+    if (Config.rangeOffsetLimit >= firstOffset())
+        /* below the limit */
+        return false;
+
+    return true;
+}
+
+bool
+HttpHdrRange::contains(HttpHdrRangeSpec& r) const
+{
+    assert(r.length >= 0);
+    HttpHdrRangeSpec::HttpRange rrange(r.offset, r.offset + r.length);
+
+    for (const_iterator i = begin(); i != end(); ++i) {
+        HttpHdrRangeSpec::HttpRange irange((*i)->offset, (*i)->offset + (*i)->length);
+        HttpHdrRangeSpec::HttpRange intersection = rrange.intersection(irange);
+
+        if (intersection.start == irange.start && intersection.size() == irange.size())
+            return true;
+    }
+
+    return false;
+}
+
+const HttpHdrRangeSpec *
+HttpHdrRangeIter::currentSpec() const
+{
+    if (pos.incrementable())
+        return *pos;
+
+    return NULL;
+}
+
+void
+HttpHdrRangeIter::updateSpec()
+{
+    assert (debt_size == 0);
+    assert (valid);
+
+    if (pos.incrementable()) {
+        debt(currentSpec()->length);
+    }
+}
+
+int64_t
+HttpHdrRangeIter::debt() const
+{
+    debugs(64, 3, "HttpHdrRangeIter::debt: debt is " << debt_size);
+    return debt_size;
+}
+
+void HttpHdrRangeIter::debt(int64_t newDebt)
+{
+    debugs(64, 3, "HttpHdrRangeIter::debt: was " << debt_size << " now " << newDebt);
+    debt_size = newDebt;
+}