/*
- * $Id: HttpReply.cc,v 1.74 2005/09/15 19:22:30 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)
+HttpReply::clean()
{
- 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)
-{
- /*
- * 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. */
- memBufDefInit(&mb);
- memBufAppend(&mb, buf, end);
- memBufAppend(&mb, "\0", 1);
- success = rep->httpMsgParseStep(mb.buf, 0);
- memBufClean(&mb);
- 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);
- memBufDefInit(mb);
+ 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 */
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.
* 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();
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();
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
{
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 "));
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)
{
httpStatusLinePackInto(&sline, p);
}
+
+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;
+}