]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/HttpReply.cc
Merged from trunk.
[thirdparty/squid.git] / src / HttpReply.cc
index d464d822d3ca10b325e5e92d9a69d28e38920b97..bfbd0b06fe709602d2cb7e6b7efdd5fdd851e3a5 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: HttpReply.cc,v 1.77 2005/09/17 05:50:07 wessels Exp $
+ * $Id: HttpReply.cc,v 1.100 2008/02/08 18:27:59 rousskov Exp $
  *
  * DEBUG: section 58    HTTP Reply (Response)
  * AUTHOR: Alex Rousskov
  */
 
 #include "squid.h"
+#include "SquidTime.h"
 #include "Store.h"
 #include "HttpReply.h"
 #include "HttpHdrContRange.h"
+#include "HttpHdrSc.h"
 #include "ACLChecklist.h"
+#include "HttpRequest.h"
 #include "MemBuf.h"
 
 /* local constants */
 
-/* these entity-headers must be ignored if a bogus server sends them in 304 */
+/* If we receive a 304 from the origin during a cache revalidation, we must
+ * update the headers of the existing entry. Specifically, we need to update all
+ * end-to-end headers and not any hop-by-hop headers (rfc2616 13.5.3).
+ *
+ * This is not the whole story though: since it is possible for a faulty/malicious
+ * origin server to set headers it should not in a 304, we must explicitly ignore
+ * these too. Specifically all entity-headers except those permitted in a 304
+ * (rfc2616 10.3.5) must be ignored.
+ * 
+ * The list of headers we don't update is made up of:
+ *     all hop-by-hop headers
+ *     all entity-headers except Expires and Content-Location
+ */
 static HttpHeaderMask Denied304HeadersMask;
 static http_hdr_type Denied304HeadersArr[] =
     {
+        // hop-by-hop headers
+        HDR_CONNECTION, HDR_KEEP_ALIVE, HDR_PROXY_AUTHENTICATE, HDR_PROXY_AUTHORIZATION,
+        HDR_TE, HDR_TRAILERS, HDR_TRANSFER_ENCODING, HDR_UPGRADE,
+        // entity headers
         HDR_ALLOW, HDR_CONTENT_ENCODING, HDR_CONTENT_LANGUAGE, HDR_CONTENT_LENGTH,
-        HDR_CONTENT_LOCATION, HDR_CONTENT_RANGE, HDR_LAST_MODIFIED, HDR_LINK,
-        HDR_OTHER
+        HDR_CONTENT_MD5, HDR_CONTENT_RANGE, HDR_CONTENT_TYPE, HDR_LAST_MODIFIED
     };
 
-
-/* local routines */
-static void httpReplyClean(HttpReply * rep);
-static void httpReplyDoDestroy(HttpReply * rep);
-static void httpReplyHdrCacheClean(HttpReply * rep);
-static time_t httpReplyHdrExpirationTime(const HttpReply * rep);
-
-
 /* module initialization */
 void
 httpReplyInitModule(void)
 {
     assert(HTTP_STATUS_NONE == 0); // HttpReply::parse() interface assumes that
     httpHeaderMaskInit(&Denied304HeadersMask, 0);
-    httpHeaderCalcMask(&Denied304HeadersMask, (const int *) Denied304HeadersArr, countof(Denied304HeadersArr));
+    httpHeaderCalcMask(&Denied304HeadersMask, Denied304HeadersArr, countof(Denied304HeadersArr));
 }
 
+HttpReply::HttpReply() : HttpMsg(hoReply), date (0), last_modified (0), 
+       expires (0), surrogate_control (NULL), content_range (NULL), keep_alive (0), 
+       protoPrefix("HTTP/"), bodySizeMax(-2)
+{
+    init();
+}
 
-HttpReply *
-httpReplyCreate(void)
+HttpReply::~HttpReply()
 {
-    HttpReply *rep = new HttpReply;
-    debug(58, 7) ("creating rep: %p\n", rep);
-    return rep;
+    if (do_clean)
+        clean();
 }
 
-HttpReply::HttpReply() : HttpMsg(hoReply), date (0), last_modified (0), expires (0), surrogate_control (NULL), content_range (NULL), keep_alive (0), protoPrefix("HTTP/")
+void
+HttpReply::init()
 {
     httpBodyInit(&body);
     hdrCacheInit();
     httpStatusLineInit(&sline);
+    pstate = psReadyToParseStartLine;
+    do_clean = true;
 }
 
 void HttpReply::reset()
 {
-    httpReplyReset(this);
-}
-
-static void
-httpReplyClean(HttpReply * rep)
-{
-    assert(rep);
-    httpBodyClean(&rep->body);
-    httpReplyHdrCacheClean(rep);
-    httpHeaderClean(&rep->header);
-    httpStatusLineClean(&rep->sline);
-}
 
-void
-httpReplyDestroy(HttpReply * rep)
-{
-    assert(rep);
-    debug(58, 7) ("destroying rep: %p\n", rep);
-    httpReplyClean(rep);
-    httpReplyDoDestroy(rep);
-}
-
-void
-httpReplyReset(HttpReply * rep)
-{
     // reset should not reset the protocol; could have made protoPrefix a
     // virtual function instead, but it is not clear whether virtual methods
     // are allowed with MEMPROXY_CLASS() and whether some cbdata void*
     // conversions are not going to kill virtual tables
-    const String pfx = rep->protoPrefix;
-    httpReplyClean(rep);
-    *rep = HttpReply();
-    rep->protoPrefix = pfx;
+    const String pfx = protoPrefix;
+    clean();
+    init();
+    protoPrefix = pfx;
 }
 
-/* absorb: copy the contents of a new reply to the old one, destroy new one */
 void
-httpReplyAbsorb(HttpReply * rep, HttpReply * new_rep)
-{
-    assert(rep && new_rep);
-    httpReplyClean(rep);
-    *rep = *new_rep;
-    new_rep->header.entries.clean();
-    /* cannot use Clean() on new reply now! */
-    new_rep->cache_control = NULL;     // helps with debugging
-    httpReplyDoDestroy(new_rep);
-}
-
-/*
- * httpReplyParse takes character buffer of HTTP headers (buf),
- * which may not be NULL-terminated, and fills in an HttpReply
- * structure (rep).  The parameter 'end' specifies the offset to
- * the end of the reply headers.  The caller may know where the
- * end is, but is unable to NULL-terminate the buffer.  This function
- * returns true on success.
- */
-bool
-httpReplyParse(HttpReply * rep, const char *buf, ssize_t end)
+HttpReply::clean()
 {
-    /*
-     * this extra buffer/copy will be eliminated when headers become
-     * meta-data in store. Currently we have to xstrncpy the buffer
-     * becuase somebody may feed a non NULL-terminated buffer to
-     * us.
-     */
-    MemBuf mb;
-    int success;
-    /* reset current state, because we are not used in incremental fashion */
-    httpReplyReset(rep);
-    /* put a string terminator.  s is how many bytes to touch in
-     * 'buf' including the terminating NULL. */
-    mb.init();
-    mb.append(buf, end);
-    mb.append("\0", 1);
-    success = rep->httpMsgParseStep(mb.buf, 0);
-    mb.clean();
-    return success == 1;
+    // we used to assert that the pipe is NULL, but now the message only 
+    // points to a pipe that is owned and initiated by another object.
+    body_pipe = NULL;
+
+    httpBodyClean(&body);
+    hdrCacheClean();
+    header.clean();
+    httpStatusLineClean(&sline);
+    bodySizeMax = -2; // hack: make calculatedBodySizeMax() false
 }
 
 void
-httpReplyPackHeadersInto(const HttpReply * rep, Packer * p)
+HttpReply::packHeadersInto(Packer * p) const
 {
-    assert(rep);
-    httpStatusLinePackInto(&rep->sline, p);
-    httpHeaderPackInto(&rep->header, p);
+    httpStatusLinePackInto(&sline, p);
+    header.packInto(p);
     packerAppend(p, "\r\n", 2);
 }
 
 void
-httpReplyPackInto(const HttpReply * rep, Packer * p)
+HttpReply::packInto(Packer * p)
 {
-    httpReplyPackHeadersInto(rep, p);
-    httpBodyPackInto(&rep->body, p);
+    packHeadersInto(p);
+    httpBodyPackInto(&body, p);
 }
 
-/* create memBuf, create mem-based packer,  pack, destroy packer, return MemBuf */
+/* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
 MemBuf *
-httpReplyPack(const HttpReply * rep)
+HttpReply::pack()
 {
     MemBuf *mb = new MemBuf;
     Packer p;
-    assert(rep);
 
     mb->init();
     packerToMemInit(&p, mb);
-    httpReplyPackInto(rep, &p);
+    packInto(&p);
     packerClean(&p);
     return mb;
 }
 
-/* swap: create swap-based packer, pack, destroy packer
- * This eats the reply.
- */
-void
-httpReplySwapOut(HttpReply * rep, StoreEntry * e)
-{
-    assert(rep && e);
-
-    storeEntryReplaceObject(e, rep);
-}
-
 MemBuf *
 httpPackedReply(HttpVersion ver, http_status status, const char *ctype,
-                int clen, time_t lmt, time_t expires)
+                int64_t clen, time_t lmt, time_t expires)
 {
-    HttpReply *rep = httpReplyCreate();
-    httpReplySetHeaders(rep, ver, status, ctype, NULL, clen, lmt, expires);
-    MemBuf *mb = httpReplyPack(rep);
-    httpReplyDestroy(rep);
+    HttpReply *rep = new HttpReply;
+    rep->setHeaders(ver, status, ctype, NULL, clen, lmt, expires);
+    MemBuf *mb = rep->pack();
+    delete rep;
     return mb;
 }
 
 HttpReply *
-httpReplyMake304 (const HttpReply * rep)
+HttpReply::make304 () const
 {
     static const http_hdr_type ImsEntries[] = {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER};
 
-    HttpReply *rv;
+    HttpReply *rv = new HttpReply;
     int t;
     HttpHeaderEntry *e;
-    assert(rep);
 
-    rv = httpReplyCreate ();
     /* rv->content_length; */
-    rv->date = rep->date;
-    rv->last_modified = rep->last_modified;
-    rv->expires = rep->expires;
-    rv->content_type = rep->content_type;
+    rv->date = date;
+    rv->last_modified = last_modified;
+    rv->expires = expires;
+    rv->content_type = content_type;
     /* rv->cache_control */
     /* rv->content_range */
     /* rv->keep_alive */
@@ -242,77 +190,73 @@ httpReplyMake304 (const HttpReply * rep)
                       HTTP_NOT_MODIFIED, "");
 
     for (t = 0; ImsEntries[t] != HDR_OTHER; ++t)
-        if ((e = httpHeaderFindEntry(&rep->header, ImsEntries[t])))
-            httpHeaderAddEntry(&rv->header, httpHeaderEntryClone(e));
+        if ((e = header.findEntry(ImsEntries[t])))
+            rv->header.addEntry(e->clone());
 
     /* rv->body */
     return rv;
 }
 
 MemBuf *
-httpPacked304Reply(const HttpReply * rep)
+HttpReply::packed304Reply()
 {
     /* Not as efficient as skipping the header duplication,
      * but easier to maintain
      */
-    HttpReply *temp;
-    assert (rep);
-    temp = httpReplyMake304 (rep);
-    MemBuf *rv = httpReplyPack(temp);
-    httpReplyDestroy (temp);
+    HttpReply *temp = make304 ();
+    MemBuf *rv = temp->pack();
+    delete temp;
     return rv;
 }
 
 void
-httpReplySetHeaders(HttpReply * reply, HttpVersion ver, http_status status, const char *reason,
-                    const char *ctype, int clen, time_t lmt, time_t expires)
+HttpReply::setHeaders(HttpVersion ver, http_status status, const char *reason,
+                      const char *ctype, int64_t clen, time_t lmt, time_t expires)
 {
     HttpHeader *hdr;
-    assert(reply);
-    httpStatusLineSet(&reply->sline, ver, status, reason);
-    hdr = &reply->header;
-    httpHeaderPutStr(hdr, HDR_SERVER, visible_appname_string);
-    httpHeaderPutStr(hdr, HDR_MIME_VERSION, "1.0");
-    httpHeaderPutTime(hdr, HDR_DATE, squid_curtime);
+    httpStatusLineSet(&sline, ver, status, reason);
+    hdr = &header;
+    hdr->putStr(HDR_SERVER, visible_appname_string);
+    hdr->putStr(HDR_MIME_VERSION, "1.0");
+    hdr->putTime(HDR_DATE, squid_curtime);
 
     if (ctype) {
-        httpHeaderPutStr(hdr, HDR_CONTENT_TYPE, ctype);
-        reply->content_type = ctype;
+        hdr->putStr(HDR_CONTENT_TYPE, ctype);
+        content_type = ctype;
     } else
-        reply->content_type = String();
+        content_type = String();
 
     if (clen >= 0)
-        httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, clen);
+        hdr->putInt64(HDR_CONTENT_LENGTH, clen);
 
     if (expires >= 0)
-        httpHeaderPutTime(hdr, HDR_EXPIRES, expires);
+        hdr->putTime(HDR_EXPIRES, expires);
 
     if (lmt > 0)               /* this used to be lmt != 0 @?@ */
-        httpHeaderPutTime(hdr, HDR_LAST_MODIFIED, lmt);
+        hdr->putTime(HDR_LAST_MODIFIED, lmt);
 
-    reply->date = squid_curtime;
+    date = squid_curtime;
 
-    reply->content_length = clen;
+    content_length = clen;
 
-    reply->expires = expires;
+    expires = expires;
 
-    reply->last_modified = lmt;
+    last_modified = lmt;
 }
 
 void
-httpRedirectReply(HttpReply * reply, http_status status, const char *loc)
+HttpReply::redirect(http_status status, const char *loc)
 {
     HttpHeader *hdr;
-    assert(reply);
     HttpVersion ver(1,0);
-    httpStatusLineSet(&reply->sline, ver, status, httpStatusString(status));
-    hdr = &reply->header;
-    httpHeaderPutStr(hdr, HDR_SERVER, full_appname_string);
-    httpHeaderPutTime(hdr, HDR_DATE, squid_curtime);
-    httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, 0);
-    httpHeaderPutStr(hdr, HDR_LOCATION, loc);
-    reply->date = squid_curtime;
-    reply->content_length = 0;
+    httpStatusLineSet(&sline, ver, status, httpStatusString(status));
+    hdr = &header;
+    hdr->putStr(HDR_SERVER, APP_FULLNAME);
+    hdr->putTime(HDR_DATE, squid_curtime);
+    hdr->putInt64(HDR_CONTENT_LENGTH, 0);
+    hdr->putStr(HDR_LOCATION, loc);
+    date = squid_curtime;
+    content_length = 0;
 }
 
 /* compare the validators of two replies.
@@ -320,23 +264,23 @@ httpRedirectReply(HttpReply * reply, http_status status, const char *loc)
  * 0 = they do not match
  */
 int
-httpReplyValidatorsMatch(HttpReply const * rep, HttpReply const * otherRep)
+HttpReply::validatorsMatch(HttpReply const * otherRep) const
 {
     String one,two;
-    assert (rep && otherRep);
+    assert (otherRep);
     /* Numbers first - easiest to check */
     /* Content-Length */
     /* TODO: remove -1 bypass */
 
-    if (rep->content_length != otherRep->content_length
-            && rep->content_length > -1 &&
+    if (content_length != otherRep->content_length
+            && content_length > -1 &&
             otherRep->content_length > -1)
         return 0;
 
     /* ETag */
-    one = httpHeaderGetStrOrList(&rep->header, HDR_ETAG);
+    one = header.getStrOrList(HDR_ETAG);
 
-    two = httpHeaderGetStrOrList(&otherRep->header, HDR_ETAG);
+    two = otherRep->header.getStrOrList(HDR_ETAG);
 
     if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
         one.clean();
@@ -344,13 +288,13 @@ httpReplyValidatorsMatch(HttpReply const * rep, HttpReply const * otherRep)
         return 0;
     }
 
-    if (rep->last_modified != otherRep->last_modified)
+    if (last_modified != otherRep->last_modified)
         return 0;
 
     /* MD5 */
-    one = httpHeaderGetStrOrList(&rep->header, HDR_CONTENT_MD5);
+    one = header.getStrOrList(HDR_CONTENT_MD5);
 
-    two = httpHeaderGetStrOrList(&otherRep->header, HDR_CONTENT_MD5);
+    two = otherRep->header.getStrOrList(HDR_CONTENT_MD5);
 
     if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
         one.clean();
@@ -361,69 +305,61 @@ httpReplyValidatorsMatch(HttpReply const * rep, HttpReply const * otherRep)
     return 1;
 }
 
-
 void
-HttpReply::httpReplyUpdateOnNotModified(HttpReply const * freshRep)
+HttpReply::updateOnNotModified(HttpReply const * freshRep)
 {
     assert(freshRep);
-    /* Can not update modified headers that don't match! */
-    assert (httpReplyValidatorsMatch(this, freshRep));
+
     /* clean cache */
-    httpReplyHdrCacheClean(this);
+    hdrCacheClean();
     /* update raw headers */
-    httpHeaderUpdate(&header, &freshRep->header,
-                     (const HttpHeaderMask *) &Denied304HeadersMask);
+    header.update(&freshRep->header,
+                  (const HttpHeaderMask *) &Denied304HeadersMask);
+
+    header.compact();
     /* init cache */
     hdrCacheInit();
 }
 
-
 /* internal routines */
 
-/* internal function used by Destroy and Absorb */
-static void
-httpReplyDoDestroy(HttpReply * rep)
-{
-    delete rep;
-}
-
-static time_t
-httpReplyHdrExpirationTime(const HttpReply * rep)
+time_t
+HttpReply::hdrExpirationTime()
 {
     /* The s-maxage and max-age directive takes priority over Expires */
 
-    if (rep->cache_control) {
-        if (rep->date >= 0) {
-            if (rep->cache_control->s_maxage >= 0)
-                return rep->date + rep->cache_control->s_maxage;
+    if (cache_control) {
+        if (date >= 0) {
+            if (cache_control->s_maxage >= 0)
+                return date + cache_control->s_maxage;
 
-            if (rep->cache_control->max_age >= 0)
-                return rep->date + rep->cache_control->max_age;
+            if (cache_control->max_age >= 0)
+                return date + cache_control->max_age;
         } else {
             /*
              * Conservatively handle the case when we have a max-age
              * header, but no Date for reference?
              */
 
-            if (rep->cache_control->s_maxage >= 0)
+            if (cache_control->s_maxage >= 0)
                 return squid_curtime;
 
-            if (rep->cache_control->max_age >= 0)
+            if (cache_control->max_age >= 0)
                 return squid_curtime;
         }
     }
 
     if (Config.onoff.vary_ignore_expire &&
-            httpHeaderHas(&rep->header, HDR_VARY)) {
-        const time_t d = httpHeaderGetTime(&rep->header, HDR_DATE);
-        const time_t e = httpHeaderGetTime(&rep->header, HDR_EXPIRES);
+            header.has(HDR_VARY)) {
+        const time_t d = header.getTime(HDR_DATE);
+        const time_t e = header.getTime(HDR_EXPIRES);
 
         if (d == e)
             return -1;
     }
 
-    if (httpHeaderHas(&rep->header, HDR_EXPIRES)) {
-        const time_t e = httpHeaderGetTime(&rep->header, HDR_EXPIRES);
+    if (header.has(HDR_EXPIRES)) {
+        const time_t e = header.getTime(HDR_EXPIRES);
         /*
          * HTTP/1.0 says that robust implementations should consider
          * bad or malformed Expires header as equivalent to "expires
@@ -441,13 +377,13 @@ HttpReply::hdrCacheInit()
 {
     HttpMsg::hdrCacheInit();
 
-    content_length = httpHeaderGetInt(&header, HDR_CONTENT_LENGTH);
-    date = httpHeaderGetTime(&header, HDR_DATE);
-    last_modified = httpHeaderGetTime(&header, HDR_LAST_MODIFIED);
-    surrogate_control = httpHeaderGetSc(&header);
-    content_range = httpHeaderGetContRange(&header);
+    content_length = header.getInt64(HDR_CONTENT_LENGTH);
+    date = header.getTime(HDR_DATE);
+    last_modified = header.getTime(HDR_LAST_MODIFIED);
+    surrogate_control = header.getSc();
+    content_range = header.getContRange();
     keep_alive = httpMsgIsPersistent(sline.version, &header);
-    const char *str = httpHeaderGetStr(&header, HDR_CONTENT_TYPE);
+    const char *str = header.getStr(HDR_CONTENT_TYPE);
 
     if (str)
         content_type.limitInit(str, strcspn(str, ";\t "));
@@ -455,51 +391,51 @@ HttpReply::hdrCacheInit()
         content_type = String();
 
     /* be sure to set expires after date and cache-control */
-    expires = httpReplyHdrExpirationTime(this);
+    expires = hdrExpirationTime();
 }
 
 /* sync this routine when you update HttpReply struct */
-static void
-httpReplyHdrCacheClean(HttpReply * rep)
+void
+HttpReply::hdrCacheClean()
 {
-    rep->content_type.clean();
+    content_type.clean();
 
-    if (rep->cache_control) {
-        httpHdrCcDestroy(rep->cache_control);
-        rep->cache_control = NULL;
+    if (cache_control) {
+        httpHdrCcDestroy(cache_control);
+        cache_control = NULL;
     }
 
-    if (rep->surrogate_control) {
-        httpHdrScDestroy(rep->surrogate_control);
-        rep->surrogate_control = NULL;
+    if (surrogate_control) {
+        httpHdrScDestroy(surrogate_control);
+        surrogate_control = NULL;
     }
 
-    if (rep->content_range) {
-        httpHdrContRangeDestroy(rep->content_range);
-        rep->content_range = NULL;
+    if (content_range) {
+        httpHdrContRangeDestroy(content_range);
+        content_range = NULL;
     }
 }
 
 /*
  * Returns the body size of a HTTP response
  */
-int
-httpReplyBodySize(method_t method, HttpReply const * reply)
+int64_t
+HttpReply::bodySize(const HttpRequestMethod& method) const
 {
-    if (reply->sline.version.major < 1)
+    if (sline.version.major < 1)
         return -1;
-    else if (METHOD_HEAD == method)
+    else if (method.id() == METHOD_HEAD)
         return 0;
-    else if (reply->sline.status == HTTP_OK)
+    else if (sline.status == HTTP_OK)
         (void) 0;              /* common case, continue */
-    else if (reply->sline.status == HTTP_NO_CONTENT)
+    else if (sline.status == HTTP_NO_CONTENT)
         return 0;
-    else if (reply->sline.status == HTTP_NOT_MODIFIED)
+    else if (sline.status == HTTP_NOT_MODIFIED)
         return 0;
-    else if (reply->sline.status < HTTP_OK)
+    else if (sline.status < HTTP_OK)
         return 0;
 
-    return reply->content_length;
+    return content_length;
 }
 
 bool HttpReply::sanityCheckStartLine(MemBuf *buf, http_status *error)
@@ -522,3 +458,113 @@ bool HttpReply::parseFirstLine(const char *blk_start, const char *blk_end)
 {
     return httpStatusLineParse(&sline, protoPrefix, blk_start, blk_end);
 }
+
+/* handy: resets and returns -1 */
+int
+HttpReply::httpMsgParseError()
+{
+    int result(HttpMsg::httpMsgParseError());
+    /* indicate an error in the status line */
+    sline.status = HTTP_INVALID_HEADER;
+    return result;
+}
+
+/*
+ * 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", ','))
+            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)
+{
+    calcMaxBodySize(request);
+    debugs(58, 7, HERE << "bodySizeMax=" << bodySizeMax);
+
+    if (bodySizeMax < 0) // no body size limit
+        return false;
+
+    int64_t expectedSize = -1;
+    if (!expectingBody(request.method, expectedSize))
+        return false;
+    
+    debugs(58, 6, HERE << expectedSize << " >? " << bodySizeMax);
+
+    if (expectedSize < 0) // expecting body of an unknown length
+        return false;
+
+    return expectedSize > bodySizeMax;
+}
+
+void
+HttpReply::calcMaxBodySize(HttpRequest& request)
+{
+    // hack: -2 is used as "we have not calculated max body size yet" state
+    if (bodySizeMax != -2) // already tried
+        return;
+    bodySizeMax = -1;
+
+    ACLChecklist ch;
+    ch.src_addr = request.client_addr;
+    ch.my_addr = request.my_addr;
+    ch.reply = HTTPMSGLOCK(this); // XXX: this lock makes method non-const
+    ch.request = HTTPMSGLOCK(&request);
+    for (acl_size_t *l = Config.ReplyBodySize; l; l = l -> next) {
+        if (ch.matchAclListFast(l->aclList)) {
+            debugs(58, 4, HERE << "bodySizeMax=" << bodySizeMax);
+            bodySizeMax = l->size; // may be -1
+            break;
+        }
+    }
+}
+
+// XXX: check that this is sufficient for eCAP cloning
+HttpReply *
+HttpReply::clone() const
+{
+    HttpReply *rep = new HttpReply();
+    rep->header.append(&header);
+    rep->hdrCacheInit();
+    rep->hdr_sz = hdr_sz;
+    rep->http_ver = http_ver;
+    rep->pstate = pstate;
+    rep->body_pipe = body_pipe;
+
+    rep->protocol = protocol;
+    rep->sline = sline;
+    return rep;
+}