]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/HttpReply.cc
Merged from trunk.
[thirdparty/squid.git] / src / HttpReply.cc
index 4d286cc0e1e57a37461af9c84f9be8f6d96a1670..bfbd0b06fe709602d2cb7e6b7efdd5fdd851e3a5 100644 (file)
@@ -1,21 +1,21 @@
 
 /*
- * $Id: HttpReply.cc,v 1.37 1999/04/26 21:06:12 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
  *
- * 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 Copyrighted (C) 1998 by
- *  Duane Wessels and the University of California San Diego.  Please
- *  see the COPYRIGHT file for full details.  Squid incorporates
- *  software developed and/or copyrighted by other sources.  Please see
- *  the CREDITS file for full details.
+ *  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
  */
 
 #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[] =
-{
-    HDR_ALLOW, HDR_CONTENT_ENCODING, HDR_CONTENT_LANGUAGE, HDR_CONTENT_LENGTH,
-    HDR_CONTENT_LOCATION, HDR_CONTENT_RANGE, HDR_LAST_MODIFIED, HDR_LINK,
-    HDR_OTHER
-};
-
-/* local routines */
-static void httpReplyInit(HttpReply * rep);
-static void httpReplyClean(HttpReply * rep);
-static void httpReplyDoDestroy(HttpReply * rep);
-static void httpReplyHdrCacheInit(HttpReply * rep);
-static void httpReplyHdrCacheClean(HttpReply * rep);
-static int httpReplyParseStep(HttpReply * rep, const char *parse_start, int atEnd);
-static int httpReplyParseError(HttpReply * rep);
-static int httpReplyIsolateStart(const char **parse_start, const char **blk_start, const char **blk_end);
-
+    {
+        // 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_MD5, HDR_CONTENT_RANGE, HDR_CONTENT_TYPE, HDR_LAST_MODIFIED
+    };
 
 /* module initialization */
 void
-httpReplyInitModule()
+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 *
-httpReplyCreate()
+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 *rep = memAllocate(MEM_HTTP_REPLY);
-    debug(58, 7) ("creating rep: %p\n", rep);
-    httpReplyInit(rep);
-    return rep;
+    init();
 }
 
-static void
-httpReplyInit(HttpReply * rep)
+HttpReply::~HttpReply()
 {
-    assert(rep);
-    rep->hdr_sz = 0;
-    rep->pstate = psReadyToParseStartLine;
-    httpBodyInit(&rep->body);
-    httpHeaderInit(&rep->header, hoReply);
-    httpReplyHdrCacheInit(rep);
-    httpStatusLineInit(&rep->sline);
-}
-
-static void
-httpReplyClean(HttpReply * rep)
-{
-    assert(rep);
-    httpBodyClean(&rep->body);
-    httpReplyHdrCacheClean(rep);
-    httpHeaderClean(&rep->header);
-    httpStatusLineClean(&rep->sline);
+    if (do_clean)
+        clean();
 }
 
 void
-httpReplyDestroy(HttpReply * rep)
+HttpReply::init()
 {
-    assert(rep);
-    debug(58, 7) ("destroying rep: %p\n", rep);
-    httpReplyClean(rep);
-    httpReplyDoDestroy(rep);
+    httpBodyInit(&body);
+    hdrCacheInit();
+    httpStatusLineInit(&sline);
+    pstate = psReadyToParseStartLine;
+    do_clean = true;
 }
 
-void
-httpReplyReset(HttpReply * rep)
+void HttpReply::reset()
 {
-    httpReplyClean(rep);
-    httpReplyInit(rep);
-}
 
-/* 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;
-    /* cannot use Clean() on new reply now! */
-    httpReplyDoDestroy(new_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 = protoPrefix;
+    clean();
+    init();
+    protoPrefix = pfx;
 }
 
-/* parses a 4K buffer that may not be 0-terminated; returns true on success */
-int
-httpReplyParse(HttpReply * rep, const char *buf)
+void
+HttpReply::clean()
 {
-    /*
-     * this extra buffer/copy will be eliminated when headers become meta-data
-     * in store. Currently we have to xstrncpy the buffer becuase store.c may
-     * feed a non 0-terminated buffer to us.
-     */
-    char *headers = memAllocate(MEM_4K_BUF);
-    int success;
-    /* reset current state, because we are not used in incremental fashion */
-    httpReplyReset(rep);
-    /* put a 0-terminator */
-    xstrncpy(headers, buf, 4096);
-    success = httpReplyParseStep(rep, headers, 0);
-    memFree(headers, MEM_4K_BUF);
-    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
-httpReplyPackInto(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);
-    httpBodyPackInto(&rep->body, p);
 }
 
-/* create memBuf, create mem-based packer,  pack, destroy packer, return MemBuf */
-MemBuf
-httpReplyPack(const HttpReply * rep)
+void
+HttpReply::packInto(Packer * p)
 {
-    MemBuf mb;
-    Packer p;
-    assert(rep);
-
-    memBufDefInit(&mb);
-    packerToMemInit(&p, &mb);
-    httpReplyPackInto(rep, &p);
-    packerClean(&p);
-    return mb;
+    packHeadersInto(p);
+    httpBodyPackInto(&body, p);
 }
 
-/* swap: create swap-based packer, pack, destroy packer */
-void
-httpReplySwapOut(const HttpReply * rep, StoreEntry * e)
+/* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
+MemBuf *
+HttpReply::pack()
 {
+    MemBuf *mb = new MemBuf;
     Packer p;
-    assert(rep && e);
 
-    packerToStoreInit(&p, e);
-    httpReplyPackInto(rep, &p);
+    mb->init();
+    packerToMemInit(&p, mb);
+    packInto(&p);
     packerClean(&p);
+    return mb;
 }
 
-MemBuf
-httpPackedReply(double ver, http_status status, const char *ctype,
-    int clen, time_t lmt, time_t expires)
+MemBuf *
+httpPackedReply(HttpVersion ver, http_status status, const char *ctype,
+                int64_t clen, time_t lmt, time_t expires)
 {
-    HttpReply *rep = httpReplyCreate();
-    MemBuf mb;
-    httpReplySetHeaders(rep, ver, status, ctype, NULL, clen, lmt, expires);
-    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;
 }
 
-MemBuf
-httpPacked304Reply(const HttpReply * rep)
+HttpReply *
+HttpReply::make304 () const
 {
-    static const http_hdr_type ImsEntries[] =
-    {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER};
+    static const http_hdr_type ImsEntries[] = {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER};
+
+    HttpReply *rv = new HttpReply;
     int t;
-    MemBuf mb;
-    Packer p;
     HttpHeaderEntry *e;
-    assert(rep);
 
-    memBufDefInit(&mb);
-    packerToMemInit(&p, &mb);
-    memBufPrintf(&mb, "%s", "HTTP/1.0 304 Not Modified\r\n");
+    /* rv->content_length; */
+    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 */
+    HttpVersion ver(1,0);
+    httpStatusLineSet(&rv->sline, ver,
+                      HTTP_NOT_MODIFIED, "");
+
     for (t = 0; ImsEntries[t] != HDR_OTHER; ++t)
-       if ((e = httpHeaderFindEntry(&rep->header, ImsEntries[t])))
-           httpHeaderEntryPackInto(e, &p);
-    memBufAppend(&mb, "\r\n", 2);
-    packerClean(&p);
-    return mb;
+        if ((e = header.findEntry(ImsEntries[t])))
+            rv->header.addEntry(e->clone());
+
+    /* rv->body */
+    return rv;
+}
+
+MemBuf *
+HttpReply::packed304Reply()
+{
+    /* Not as efficient as skipping the header duplication,
+     * but easier to maintain
+     */
+    HttpReply *temp = make304 ();
+    MemBuf *rv = temp->pack();
+    delete temp;
+    return rv;
 }
 
 void
-httpReplySetHeaders(HttpReply * reply, double 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, full_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);
-       stringInit(&reply->content_type, ctype);
+        hdr->putStr(HDR_CONTENT_TYPE, ctype);
+        content_type = ctype;
     } else
-       reply->content_type = StringNull;
+        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);
-    reply->date = squid_curtime;
-    reply->content_length = clen;
-    reply->expires = expires;
-    reply->last_modified = lmt;
+        hdr->putTime(HDR_LAST_MODIFIED, lmt);
+
+    date = squid_curtime;
+
+    content_length = clen;
+
+    expires = expires;
+
+    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);
-    httpStatusLineSet(&reply->sline, 1.0, 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;
+    HttpVersion ver(1,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.
+ * 1 = they match
+ * 0 = they do not match
+ */
+int
+HttpReply::validatorsMatch(HttpReply const * otherRep) const
+{
+    String one,two;
+    assert (otherRep);
+    /* Numbers first - easiest to check */
+    /* Content-Length */
+    /* TODO: remove -1 bypass */
+
+    if (content_length != otherRep->content_length
+            && content_length > -1 &&
+            otherRep->content_length > -1)
+        return 0;
+
+    /* ETag */
+    one = header.getStrOrList(HDR_ETAG);
+
+    two = otherRep->header.getStrOrList(HDR_ETAG);
+
+    if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
+        one.clean();
+        two.clean();
+        return 0;
+    }
+
+    if (last_modified != otherRep->last_modified)
+        return 0;
+
+    /* MD5 */
+    one = header.getStrOrList(HDR_CONTENT_MD5);
+
+    two = otherRep->header.getStrOrList(HDR_CONTENT_MD5);
+
+    if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
+        one.clean();
+        two.clean();
+        return 0;
+    }
+
+    return 1;
 }
 
 void
-httpReplyUpdateOnNotModified(HttpReply * rep, HttpReply * freshRep)
+HttpReply::updateOnNotModified(HttpReply const * freshRep)
 {
-    assert(rep && freshRep);
+    assert(freshRep);
+
     /* clean cache */
-    httpReplyHdrCacheClean(rep);
+    hdrCacheClean();
     /* update raw headers */
-    httpHeaderUpdate(&rep->header, &freshRep->header,
-       (const HttpHeaderMask *) &Denied304HeadersMask);
+    header.update(&freshRep->header,
+                  (const HttpHeaderMask *) &Denied304HeadersMask);
+
+    header.compact();
     /* init cache */
-    httpReplyHdrCacheInit(rep);
+    hdrCacheInit();
 }
 
-
 /* internal routines */
 
-/* internal function used by Destroy and Absorb */
-static void
-httpReplyDoDestroy(HttpReply * rep)
+time_t
+HttpReply::hdrExpirationTime()
 {
-    memFree(rep, MEM_HTTP_REPLY);
+    /* The s-maxage and max-age directive takes priority over Expires */
+
+    if (cache_control) {
+        if (date >= 0) {
+            if (cache_control->s_maxage >= 0)
+                return date + cache_control->s_maxage;
+
+            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 (cache_control->s_maxage >= 0)
+                return squid_curtime;
+
+            if (cache_control->max_age >= 0)
+                return squid_curtime;
+        }
+    }
+
+    if (Config.onoff.vary_ignore_expire &&
+            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 (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
+         * immediately."
+         */
+        return e < 0 ? squid_curtime : e;
+    }
+
+    return -1;
 }
 
 /* sync this routine when you update HttpReply struct */
-static void
-httpReplyHdrCacheInit(HttpReply * rep)
+void
+HttpReply::hdrCacheInit()
 {
-    const HttpHeader *hdr = &rep->header;
-    const char *str;
-    rep->content_length = httpHeaderGetInt(hdr, HDR_CONTENT_LENGTH);
-    rep->date = httpHeaderGetTime(hdr, HDR_DATE);
-    rep->last_modified = httpHeaderGetTime(hdr, HDR_LAST_MODIFIED);
-    rep->expires = httpHeaderGetTime(hdr, HDR_EXPIRES);
-    str = httpHeaderGetStr(hdr, HDR_CONTENT_TYPE);
+    HttpMsg::hdrCacheInit();
+
+    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 = header.getStr(HDR_CONTENT_TYPE);
+
     if (str)
-       stringLimitInit(&rep->content_type, str, strcspn(str, ";\t "));
-    else
-       rep->content_type = StringNull;
-    rep->cache_control = httpHeaderGetCc(hdr);
-    rep->content_range = httpHeaderGetContRange(hdr);
-    rep->keep_alive = httpMsgIsPersistent(rep->sline.version, &rep->header);
-    /* final adjustments */
-    /* The max-age directive takes priority over Expires, check it first */
-    if (rep->cache_control && rep->cache_control->max_age >= 0)
-       rep->expires = squid_curtime + rep->cache_control->max_age;
+        content_type.limitInit(str, strcspn(str, ";\t "));
     else
-       /*
-        * The HTTP/1.0 specs says that robust implementations should consider bad
-        * or malformed Expires header as equivalent to "expires immediately."
-        */
-    if (rep->expires < 0 && httpHeaderHas(hdr, HDR_EXPIRES))
-       rep->expires = squid_curtime;
+        content_type = String();
+
+    /* be sure to set expires after date and cache-control */
+    expires = hdrExpirationTime();
 }
 
 /* sync this routine when you update HttpReply struct */
-static void
-httpReplyHdrCacheClean(HttpReply * rep)
+void
+HttpReply::hdrCacheClean()
 {
-    stringClean(&rep->content_type);
-    if (rep->cache_control)
-       httpHdrCcDestroy(rep->cache_control);
-    if (rep->content_range)
-       httpHdrContRangeDestroy(rep->content_range);
+    content_type.clean();
+
+    if (cache_control) {
+        httpHdrCcDestroy(cache_control);
+        cache_control = NULL;
+    }
+
+    if (surrogate_control) {
+        httpHdrScDestroy(surrogate_control);
+        surrogate_control = NULL;
+    }
+
+    if (content_range) {
+        httpHdrContRangeDestroy(content_range);
+        content_range = NULL;
+    }
 }
 
 /*
- * parses a 0-terminating buffer into HttpReply. 
- * Returns:
- *      +1 -- success 
- *       0 -- need more data (partial parse)
- *      -1 -- parse error
+ * Returns the body size of a HTTP response
  */
-static int
-httpReplyParseStep(HttpReply * rep, const char *buf, int atEnd)
+int64_t
+HttpReply::bodySize(const HttpRequestMethod& method) const
 {
-    const char *parse_start = buf;
-    const char *blk_start, *blk_end;
-    const char **parse_end_ptr = &blk_end;
-    assert(rep);
-    assert(parse_start);
-    assert(rep->pstate < psParsed);
-
-    *parse_end_ptr = parse_start;
-    if (rep->pstate == psReadyToParseStartLine) {
-       if (!httpReplyIsolateStart(&parse_start, &blk_start, &blk_end))
-           return 0;
-       if (!httpStatusLineParse(&rep->sline, blk_start, blk_end))
-           return httpReplyParseError(rep);
-
-       *parse_end_ptr = parse_start;
-       rep->hdr_sz = *parse_end_ptr - buf;
-       rep->pstate++;
-    }
-    if (rep->pstate == psReadyToParseHeaders) {
-       if (!httpMsgIsolateHeaders(&parse_start, &blk_start, &blk_end)) {
-           if (atEnd)
-               blk_start = parse_start, blk_end = blk_start + strlen(blk_start);
-           else
-               return 0;
-       }
-       if (!httpHeaderParse(&rep->header, blk_start, blk_end))
-           return httpReplyParseError(rep);
-
-       httpReplyHdrCacheInit(rep);
-
-       *parse_end_ptr = parse_start;
-       rep->hdr_sz = *parse_end_ptr - buf;
-       rep->pstate++;
+    if (sline.version.major < 1)
+        return -1;
+    else if (method.id() == METHOD_HEAD)
+        return 0;
+    else if (sline.status == HTTP_OK)
+        (void) 0;              /* common case, continue */
+    else if (sline.status == HTTP_NO_CONTENT)
+        return 0;
+    else if (sline.status == HTTP_NOT_MODIFIED)
+        return 0;
+    else if (sline.status < HTTP_OK)
+        return 0;
+
+    return content_length;
+}
+
+bool HttpReply::sanityCheckStartLine(MemBuf *buf, http_status *error)
+{
+    if (buf->contentSize() >= protoPrefix.size() && protoPrefix.cmp(buf->content(), protoPrefix.size()) != 0) {
+        debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix.buf() << ") in '" << buf->content() << "'");
+        *error = HTTP_INVALID_HEADER;
+        return false;
     }
-    return 1;
+
+    return true;
 }
 
-/* handy: resets and returns -1 */
-static int
-httpReplyParseError(HttpReply * rep)
+void HttpReply::packFirstLineInto(Packer *p, bool unused) const
 {
-    assert(rep);
-    /* reset */
-    httpReplyReset(rep);
-    /* indicate an error */
-    rep->sline.status = HTTP_INVALID_HEADER;
-    return -1;
+    httpStatusLinePackInto(&sline, p);
 }
 
-/* find first CRLF */
-static int
-httpReplyIsolateStart(const char **parse_start, const char **blk_start, const char **blk_end)
+bool HttpReply::parseFirstLine(const char *blk_start, const char *blk_end)
 {
-    int slen = strcspn(*parse_start, "\r\n");
-    if (!(*parse_start)[slen]) /* no CRLF found */
-       return 0;
-
-    *blk_start = *parse_start;
-    *blk_end = *blk_start + slen;
-    while (**blk_end == '\r')  /* CR */
-       (*blk_end)++;
-    if (**blk_end == '\n')     /* LF */
-       (*blk_end)++;
-
-    *parse_start = *blk_end;
-    return 1;
+    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;
 }
 
 /*
- * Returns the body size of a HTTP response
+ * Indicate whether or not we would usually expect an entity-body
+ * along with this response
  */
-int
-httpReplyBodySize(method_t method, HttpReply * reply)
+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)
 {
-    if (METHOD_HEAD == method)
-       return 0;
-    else if (reply->sline.status == HTTP_OK)
-       (void) 0;               /* common case, continue */
-    else if (reply->sline.status == HTTP_NO_CONTENT)
-       return 0;
-    else if (reply->sline.status == HTTP_NOT_MODIFIED)
-       return 0;
-    else if (reply->sline.status < HTTP_OK)
-       return 0;
-    return reply->content_length;
+    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;
 }