3 * $Id: HttpReply.cc,v 1.77 2005/09/17 05:50:07 wessels Exp $
5 * DEBUG: section 58 HTTP Reply (Response)
6 * AUTHOR: Alex Rousskov
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
38 #include "HttpReply.h"
39 #include "HttpHdrContRange.h"
40 #include "ACLChecklist.h"
45 /* these entity-headers must be ignored if a bogus server sends them in 304 */
46 static HttpHeaderMask Denied304HeadersMask
;
47 static http_hdr_type Denied304HeadersArr
[] =
49 HDR_ALLOW
, HDR_CONTENT_ENCODING
, HDR_CONTENT_LANGUAGE
, HDR_CONTENT_LENGTH
,
50 HDR_CONTENT_LOCATION
, HDR_CONTENT_RANGE
, HDR_LAST_MODIFIED
, HDR_LINK
,
56 static void httpReplyClean(HttpReply
* rep
);
57 static void httpReplyDoDestroy(HttpReply
* rep
);
58 static void httpReplyHdrCacheClean(HttpReply
* rep
);
59 static time_t httpReplyHdrExpirationTime(const HttpReply
* rep
);
62 /* module initialization */
64 httpReplyInitModule(void)
66 assert(HTTP_STATUS_NONE
== 0); // HttpReply::parse() interface assumes that
67 httpHeaderMaskInit(&Denied304HeadersMask
, 0);
68 httpHeaderCalcMask(&Denied304HeadersMask
, (const int *) Denied304HeadersArr
, countof(Denied304HeadersArr
));
75 HttpReply
*rep
= new HttpReply
;
76 debug(58, 7) ("creating rep: %p\n", rep
);
80 HttpReply::HttpReply() : HttpMsg(hoReply
), date (0), last_modified (0), expires (0), surrogate_control (NULL
), content_range (NULL
), keep_alive (0), protoPrefix("HTTP/")
84 httpStatusLineInit(&sline
);
87 void HttpReply::reset()
93 httpReplyClean(HttpReply
* rep
)
96 httpBodyClean(&rep
->body
);
97 httpReplyHdrCacheClean(rep
);
98 httpHeaderClean(&rep
->header
);
99 httpStatusLineClean(&rep
->sline
);
103 httpReplyDestroy(HttpReply
* rep
)
106 debug(58, 7) ("destroying rep: %p\n", rep
);
108 httpReplyDoDestroy(rep
);
112 httpReplyReset(HttpReply
* rep
)
114 // reset should not reset the protocol; could have made protoPrefix a
115 // virtual function instead, but it is not clear whether virtual methods
116 // are allowed with MEMPROXY_CLASS() and whether some cbdata void*
117 // conversions are not going to kill virtual tables
118 const String pfx
= rep
->protoPrefix
;
121 rep
->protoPrefix
= pfx
;
124 /* absorb: copy the contents of a new reply to the old one, destroy new one */
126 httpReplyAbsorb(HttpReply
* rep
, HttpReply
* new_rep
)
128 assert(rep
&& new_rep
);
131 new_rep
->header
.entries
.clean();
132 /* cannot use Clean() on new reply now! */
133 new_rep
->cache_control
= NULL
; // helps with debugging
134 httpReplyDoDestroy(new_rep
);
138 * httpReplyParse takes character buffer of HTTP headers (buf),
139 * which may not be NULL-terminated, and fills in an HttpReply
140 * structure (rep). The parameter 'end' specifies the offset to
141 * the end of the reply headers. The caller may know where the
142 * end is, but is unable to NULL-terminate the buffer. This function
143 * returns true on success.
146 httpReplyParse(HttpReply
* rep
, const char *buf
, ssize_t end
)
149 * this extra buffer/copy will be eliminated when headers become
150 * meta-data in store. Currently we have to xstrncpy the buffer
151 * becuase somebody may feed a non NULL-terminated buffer to
156 /* reset current state, because we are not used in incremental fashion */
158 /* put a string terminator. s is how many bytes to touch in
159 * 'buf' including the terminating NULL. */
163 success
= rep
->httpMsgParseStep(mb
.buf
, 0);
169 httpReplyPackHeadersInto(const HttpReply
* rep
, Packer
* p
)
172 httpStatusLinePackInto(&rep
->sline
, p
);
173 httpHeaderPackInto(&rep
->header
, p
);
174 packerAppend(p
, "\r\n", 2);
178 httpReplyPackInto(const HttpReply
* rep
, Packer
* p
)
180 httpReplyPackHeadersInto(rep
, p
);
181 httpBodyPackInto(&rep
->body
, p
);
184 /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
186 httpReplyPack(const HttpReply
* rep
)
188 MemBuf
*mb
= new MemBuf
;
193 packerToMemInit(&p
, mb
);
194 httpReplyPackInto(rep
, &p
);
199 /* swap: create swap-based packer, pack, destroy packer
200 * This eats the reply.
203 httpReplySwapOut(HttpReply
* rep
, StoreEntry
* e
)
207 storeEntryReplaceObject(e
, rep
);
211 httpPackedReply(HttpVersion ver
, http_status status
, const char *ctype
,
212 int clen
, time_t lmt
, time_t expires
)
214 HttpReply
*rep
= httpReplyCreate();
215 httpReplySetHeaders(rep
, ver
, status
, ctype
, NULL
, clen
, lmt
, expires
);
216 MemBuf
*mb
= httpReplyPack(rep
);
217 httpReplyDestroy(rep
);
222 httpReplyMake304 (const HttpReply
* rep
)
224 static const http_hdr_type ImsEntries
[] = {HDR_DATE
, HDR_CONTENT_TYPE
, HDR_EXPIRES
, HDR_LAST_MODIFIED
, /* eof */ HDR_OTHER
};
231 rv
= httpReplyCreate ();
232 /* rv->content_length; */
233 rv
->date
= rep
->date
;
234 rv
->last_modified
= rep
->last_modified
;
235 rv
->expires
= rep
->expires
;
236 rv
->content_type
= rep
->content_type
;
237 /* rv->cache_control */
238 /* rv->content_range */
240 HttpVersion
ver(1,0);
241 httpStatusLineSet(&rv
->sline
, ver
,
242 HTTP_NOT_MODIFIED
, "");
244 for (t
= 0; ImsEntries
[t
] != HDR_OTHER
; ++t
)
245 if ((e
= httpHeaderFindEntry(&rep
->header
, ImsEntries
[t
])))
246 httpHeaderAddEntry(&rv
->header
, httpHeaderEntryClone(e
));
253 httpPacked304Reply(const HttpReply
* rep
)
255 /* Not as efficient as skipping the header duplication,
256 * but easier to maintain
260 temp
= httpReplyMake304 (rep
);
261 MemBuf
*rv
= httpReplyPack(temp
);
262 httpReplyDestroy (temp
);
267 httpReplySetHeaders(HttpReply
* reply
, HttpVersion ver
, http_status status
, const char *reason
,
268 const char *ctype
, int clen
, time_t lmt
, time_t expires
)
272 httpStatusLineSet(&reply
->sline
, ver
, status
, reason
);
273 hdr
= &reply
->header
;
274 httpHeaderPutStr(hdr
, HDR_SERVER
, visible_appname_string
);
275 httpHeaderPutStr(hdr
, HDR_MIME_VERSION
, "1.0");
276 httpHeaderPutTime(hdr
, HDR_DATE
, squid_curtime
);
279 httpHeaderPutStr(hdr
, HDR_CONTENT_TYPE
, ctype
);
280 reply
->content_type
= ctype
;
282 reply
->content_type
= String();
285 httpHeaderPutInt(hdr
, HDR_CONTENT_LENGTH
, clen
);
288 httpHeaderPutTime(hdr
, HDR_EXPIRES
, expires
);
290 if (lmt
> 0) /* this used to be lmt != 0 @?@ */
291 httpHeaderPutTime(hdr
, HDR_LAST_MODIFIED
, lmt
);
293 reply
->date
= squid_curtime
;
295 reply
->content_length
= clen
;
297 reply
->expires
= expires
;
299 reply
->last_modified
= lmt
;
303 httpRedirectReply(HttpReply
* reply
, http_status status
, const char *loc
)
307 HttpVersion
ver(1,0);
308 httpStatusLineSet(&reply
->sline
, ver
, status
, httpStatusString(status
));
309 hdr
= &reply
->header
;
310 httpHeaderPutStr(hdr
, HDR_SERVER
, full_appname_string
);
311 httpHeaderPutTime(hdr
, HDR_DATE
, squid_curtime
);
312 httpHeaderPutInt(hdr
, HDR_CONTENT_LENGTH
, 0);
313 httpHeaderPutStr(hdr
, HDR_LOCATION
, loc
);
314 reply
->date
= squid_curtime
;
315 reply
->content_length
= 0;
318 /* compare the validators of two replies.
320 * 0 = they do not match
323 httpReplyValidatorsMatch(HttpReply
const * rep
, HttpReply
const * otherRep
)
326 assert (rep
&& otherRep
);
327 /* Numbers first - easiest to check */
329 /* TODO: remove -1 bypass */
331 if (rep
->content_length
!= otherRep
->content_length
332 && rep
->content_length
> -1 &&
333 otherRep
->content_length
> -1)
337 one
= httpHeaderGetStrOrList(&rep
->header
, HDR_ETAG
);
339 two
= httpHeaderGetStrOrList(&otherRep
->header
, HDR_ETAG
);
341 if (!one
.buf() || !two
.buf() || strcasecmp (one
.buf(), two
.buf())) {
347 if (rep
->last_modified
!= otherRep
->last_modified
)
351 one
= httpHeaderGetStrOrList(&rep
->header
, HDR_CONTENT_MD5
);
353 two
= httpHeaderGetStrOrList(&otherRep
->header
, HDR_CONTENT_MD5
);
355 if (!one
.buf() || !two
.buf() || strcasecmp (one
.buf(), two
.buf())) {
366 HttpReply::httpReplyUpdateOnNotModified(HttpReply
const * freshRep
)
369 /* Can not update modified headers that don't match! */
370 assert (httpReplyValidatorsMatch(this, freshRep
));
372 httpReplyHdrCacheClean(this);
373 /* update raw headers */
374 httpHeaderUpdate(&header
, &freshRep
->header
,
375 (const HttpHeaderMask
*) &Denied304HeadersMask
);
381 /* internal routines */
383 /* internal function used by Destroy and Absorb */
385 httpReplyDoDestroy(HttpReply
* rep
)
391 httpReplyHdrExpirationTime(const HttpReply
* rep
)
393 /* The s-maxage and max-age directive takes priority over Expires */
395 if (rep
->cache_control
) {
396 if (rep
->date
>= 0) {
397 if (rep
->cache_control
->s_maxage
>= 0)
398 return rep
->date
+ rep
->cache_control
->s_maxage
;
400 if (rep
->cache_control
->max_age
>= 0)
401 return rep
->date
+ rep
->cache_control
->max_age
;
404 * Conservatively handle the case when we have a max-age
405 * header, but no Date for reference?
408 if (rep
->cache_control
->s_maxage
>= 0)
409 return squid_curtime
;
411 if (rep
->cache_control
->max_age
>= 0)
412 return squid_curtime
;
416 if (Config
.onoff
.vary_ignore_expire
&&
417 httpHeaderHas(&rep
->header
, HDR_VARY
)) {
418 const time_t d
= httpHeaderGetTime(&rep
->header
, HDR_DATE
);
419 const time_t e
= httpHeaderGetTime(&rep
->header
, HDR_EXPIRES
);
425 if (httpHeaderHas(&rep
->header
, HDR_EXPIRES
)) {
426 const time_t e
= httpHeaderGetTime(&rep
->header
, HDR_EXPIRES
);
428 * HTTP/1.0 says that robust implementations should consider
429 * bad or malformed Expires header as equivalent to "expires
432 return e
< 0 ? squid_curtime
: e
;
438 /* sync this routine when you update HttpReply struct */
440 HttpReply::hdrCacheInit()
442 HttpMsg::hdrCacheInit();
444 content_length
= httpHeaderGetInt(&header
, HDR_CONTENT_LENGTH
);
445 date
= httpHeaderGetTime(&header
, HDR_DATE
);
446 last_modified
= httpHeaderGetTime(&header
, HDR_LAST_MODIFIED
);
447 surrogate_control
= httpHeaderGetSc(&header
);
448 content_range
= httpHeaderGetContRange(&header
);
449 keep_alive
= httpMsgIsPersistent(sline
.version
, &header
);
450 const char *str
= httpHeaderGetStr(&header
, HDR_CONTENT_TYPE
);
453 content_type
.limitInit(str
, strcspn(str
, ";\t "));
455 content_type
= String();
457 /* be sure to set expires after date and cache-control */
458 expires
= httpReplyHdrExpirationTime(this);
461 /* sync this routine when you update HttpReply struct */
463 httpReplyHdrCacheClean(HttpReply
* rep
)
465 rep
->content_type
.clean();
467 if (rep
->cache_control
) {
468 httpHdrCcDestroy(rep
->cache_control
);
469 rep
->cache_control
= NULL
;
472 if (rep
->surrogate_control
) {
473 httpHdrScDestroy(rep
->surrogate_control
);
474 rep
->surrogate_control
= NULL
;
477 if (rep
->content_range
) {
478 httpHdrContRangeDestroy(rep
->content_range
);
479 rep
->content_range
= NULL
;
484 * Returns the body size of a HTTP response
487 httpReplyBodySize(method_t method
, HttpReply
const * reply
)
489 if (reply
->sline
.version
.major
< 1)
491 else if (METHOD_HEAD
== method
)
493 else if (reply
->sline
.status
== HTTP_OK
)
494 (void) 0; /* common case, continue */
495 else if (reply
->sline
.status
== HTTP_NO_CONTENT
)
497 else if (reply
->sline
.status
== HTTP_NOT_MODIFIED
)
499 else if (reply
->sline
.status
< HTTP_OK
)
502 return reply
->content_length
;
505 bool HttpReply::sanityCheckStartLine(MemBuf
*buf
, http_status
*error
)
507 if (buf
->contentSize() >= protoPrefix
.size() && protoPrefix
.cmp(buf
->content(), protoPrefix
.size()) != 0) {
508 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix
.buf() << ") in '" << buf
->content() << "'");
509 *error
= HTTP_INVALID_HEADER
;
516 void HttpReply::packFirstLineInto(Packer
*p
, bool unused
) const
518 httpStatusLinePackInto(&sline
, p
);
521 bool HttpReply::parseFirstLine(const char *blk_start
, const char *blk_end
)
523 return httpStatusLineParse(&sline
, protoPrefix
, blk_start
, blk_end
);