3 * $Id: HttpReply.cc,v 1.52 2003/01/28 01:29:32 robertc 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.
36 #include "HttpReply.h"
39 #include "HttpHeader.h"
40 #include "HttpHdrContRange.h"
41 #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
,
54 HttpMsgParseState
&operator++ (HttpMsgParseState
&aState
)
56 aState
= (HttpMsgParseState
)(++(int)aState
);
62 static void httpReplyInit(HttpReply
* rep
);
63 static void httpReplyClean(HttpReply
* rep
);
64 static void httpReplyDoDestroy(HttpReply
* rep
);
65 static void httpReplyHdrCacheInit(HttpReply
* rep
);
66 static void httpReplyHdrCacheClean(HttpReply
* rep
);
67 static int httpReplyParseStep(HttpReply
* rep
, const char *parse_start
, int atEnd
);
68 static int httpReplyParseError(HttpReply
* rep
);
69 static int httpReplyIsolateStart(const char **parse_start
, const char **blk_start
, const char **blk_end
);
70 static time_t httpReplyHdrExpirationTime(const HttpReply
* rep
);
73 /* module initialization */
75 httpReplyInitModule(void)
77 httpHeaderMaskInit(&Denied304HeadersMask
, 0);
78 httpHeaderCalcMask(&Denied304HeadersMask
, (const int *) Denied304HeadersArr
, countof(Denied304HeadersArr
));
85 HttpReply
*rep
= (HttpReply
*)memAllocate(MEM_HTTP_REPLY
);
86 debug(58, 7) ("creating rep: %p\n", rep
);
92 httpReplyInit(HttpReply
* rep
)
97 rep
->pstate
= psReadyToParseStartLine
;
98 httpBodyInit(&rep
->body
);
99 httpHeaderInit(&rep
->header
, hoReply
);
100 httpReplyHdrCacheInit(rep
);
101 httpStatusLineInit(&rep
->sline
);
105 httpReplyClean(HttpReply
* rep
)
108 httpBodyClean(&rep
->body
);
109 httpReplyHdrCacheClean(rep
);
110 httpHeaderClean(&rep
->header
);
111 httpStatusLineClean(&rep
->sline
);
115 httpReplyDestroy(HttpReply
* rep
)
118 debug(58, 7) ("destroying rep: %p\n", rep
);
120 httpReplyDoDestroy(rep
);
124 httpReplyReset(HttpReply
* rep
)
130 /* absorb: copy the contents of a new reply to the old one, destroy new one */
132 httpReplyAbsorb(HttpReply
* rep
, HttpReply
* new_rep
)
134 assert(rep
&& new_rep
);
137 new_rep
->header
.entries
.clean();
138 /* cannot use Clean() on new reply now! */
139 httpReplyDoDestroy(new_rep
);
143 * httpReplyParse takes character buffer of HTTP headers (buf),
144 * which may not be NULL-terminated, and fills in an HttpReply
145 * structure (rep). The parameter 'end' specifies the offset to
146 * the end of the reply headers. The caller may know where the
147 * end is, but is unable to NULL-terminate the buffer. This function
148 * returns true on success.
151 httpReplyParse(HttpReply
* rep
, const char *buf
, ssize_t end
)
154 * this extra buffer/copy will be eliminated when headers become
155 * meta-data in store. Currently we have to xstrncpy the buffer
156 * becuase somebody may feed a non NULL-terminated buffer to
159 char *headers
= (char *)memAllocate(MEM_4K_BUF
);
161 size_t s
= XMIN(end
+ 1, 4096);
162 /* reset current state, because we are not used in incremental fashion */
164 /* put a string terminator. s is how many bytes to touch in
165 * 'buf' including the terminating NULL. */
166 xstrncpy(headers
, buf
, s
);
167 success
= httpReplyParseStep(rep
, headers
, 0);
168 memFree(headers
, MEM_4K_BUF
);
173 httpReplyPackHeadersInto(const HttpReply
* rep
, Packer
* p
)
176 httpStatusLinePackInto(&rep
->sline
, p
);
177 httpHeaderPackInto(&rep
->header
, p
);
178 packerAppend(p
, "\r\n", 2);
182 httpReplyPackInto(const HttpReply
* rep
, Packer
* p
)
184 httpReplyPackHeadersInto(rep
, p
);
185 httpBodyPackInto(&rep
->body
, p
);
188 /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
190 httpReplyPack(const HttpReply
* rep
)
197 packerToMemInit(&p
, &mb
);
198 httpReplyPackInto(rep
, &p
);
203 /* swap: create swap-based packer, pack, destroy packer
204 * This eats the reply.
207 httpReplySwapOut(HttpReply
* rep
, StoreEntry
* e
)
211 storeEntryReplaceObject(e
, rep
);
215 httpPackedReply(http_version_t ver
, http_status status
, const char *ctype
,
216 int clen
, time_t lmt
, time_t expires
)
218 HttpReply
*rep
= httpReplyCreate();
220 httpReplySetHeaders(rep
, ver
, status
, ctype
, NULL
, clen
, lmt
, expires
);
221 mb
= httpReplyPack(rep
);
222 httpReplyDestroy(rep
);
227 httpReplyMake304 (const HttpReply
* rep
)
229 static const http_hdr_type ImsEntries
[] =
230 {HDR_DATE
, HDR_CONTENT_TYPE
, HDR_EXPIRES
, HDR_LAST_MODIFIED
, /* eof */ HDR_OTHER
};
237 rv
= httpReplyCreate ();
238 /* rv->content_length; */
239 rv
->date
= rep
->date
;
240 rv
->last_modified
= rep
->last_modified
;
241 rv
->expires
= rep
->expires
;
242 rv
->content_type
= rep
->content_type
;
243 /* rv->cache_control */
244 /* rv->content_range */
246 httpBuildVersion(&ver
, 1, 0);
247 httpStatusLineSet(&rv
->sline
, ver
,
248 HTTP_NOT_MODIFIED
, "");
249 for (t
= 0; ImsEntries
[t
] != HDR_OTHER
; ++t
)
250 if ((e
= httpHeaderFindEntry(&rep
->header
, ImsEntries
[t
])))httpHeaderAddEntry(&rv
->header
, httpHeaderEntryClone(e
));
256 httpPacked304Reply(const HttpReply
* rep
)
258 /* Not as efficient as skipping the header duplication,
259 * but easier to maintain
264 temp
= httpReplyMake304 (rep
);
265 rv
= httpReplyPack(temp
);
266 httpReplyDestroy (temp
);
271 httpReplySetHeaders(HttpReply
* reply
, http_version_t ver
, http_status status
, const char *reason
,
272 const char *ctype
, int clen
, time_t lmt
, time_t expires
)
276 httpStatusLineSet(&reply
->sline
, ver
, status
, reason
);
277 hdr
= &reply
->header
;
278 httpHeaderPutStr(hdr
, HDR_SERVER
, full_appname_string
);
279 httpHeaderPutStr(hdr
, HDR_MIME_VERSION
, "1.0");
280 httpHeaderPutTime(hdr
, HDR_DATE
, squid_curtime
);
282 httpHeaderPutStr(hdr
, HDR_CONTENT_TYPE
, ctype
);
283 reply
->content_type
= ctype
;
285 reply
->content_type
= StringNull
;
287 httpHeaderPutInt(hdr
, HDR_CONTENT_LENGTH
, clen
);
289 httpHeaderPutTime(hdr
, HDR_EXPIRES
, expires
);
290 if (lmt
> 0) /* this used to be lmt != 0 @?@ */
291 httpHeaderPutTime(hdr
, HDR_LAST_MODIFIED
, lmt
);
292 reply
->date
= squid_curtime
;
293 reply
->content_length
= clen
;
294 reply
->expires
= expires
;
295 reply
->last_modified
= lmt
;
299 httpRedirectReply(HttpReply
* reply
, http_status status
, const char *loc
)
304 httpBuildVersion(&ver
, 1, 0);
305 httpStatusLineSet(&reply
->sline
, ver
, status
, httpStatusString(status
));
306 hdr
= &reply
->header
;
307 httpHeaderPutStr(hdr
, HDR_SERVER
, full_appname_string
);
308 httpHeaderPutTime(hdr
, HDR_DATE
, squid_curtime
);
309 httpHeaderPutInt(hdr
, HDR_CONTENT_LENGTH
, 0);
310 httpHeaderPutStr(hdr
, HDR_LOCATION
, loc
);
311 reply
->date
= squid_curtime
;
312 reply
->content_length
= 0;
315 /* compare the validators of two replies.
317 * 0 = they do not match
320 httpReplyValidatorsMatch(HttpReply
const * rep
, HttpReply
const * otherRep
) {
322 assert (rep
&& otherRep
);
323 /* Numbers first - easiest to check */
325 /* TODO: remove -1 bypass */
326 if (rep
->content_length
!= otherRep
->content_length
327 && rep
->content_length
> -1 &&
328 otherRep
->content_length
> -1)
331 one
= httpHeaderGetStrOrList(&rep
->header
, HDR_ETAG
);
332 two
= httpHeaderGetStrOrList(&otherRep
->header
, HDR_ETAG
);
333 if (!one
.buf() || !two
.buf() || strcasecmp (one
.buf(), two
.buf())) {
338 if (rep
->last_modified
!= otherRep
->last_modified
)
341 one
= httpHeaderGetStrOrList(&rep
->header
, HDR_CONTENT_MD5
);
342 two
= httpHeaderGetStrOrList(&otherRep
->header
, HDR_CONTENT_MD5
);
343 if (strcasecmp (one
.buf(), two
.buf())) {
353 httpReplyUpdateOnNotModified(HttpReply
* rep
, HttpReply
const * freshRep
)
355 assert(rep
&& freshRep
);
356 /* Can not update modified headers that don't match! */
357 assert (httpReplyValidatorsMatch(rep
, freshRep
));
359 httpReplyHdrCacheClean(rep
);
360 /* update raw headers */
361 httpHeaderUpdate(&rep
->header
, &freshRep
->header
,
362 (const HttpHeaderMask
*) &Denied304HeadersMask
);
364 httpReplyHdrCacheInit(rep
);
368 /* internal routines */
370 /* internal function used by Destroy and Absorb */
372 httpReplyDoDestroy(HttpReply
* rep
)
374 memFree(rep
, MEM_HTTP_REPLY
);
378 httpReplyHdrExpirationTime(const HttpReply
* rep
)
380 /* The s-maxage and max-age directive takes priority over Expires */
381 if (rep
->cache_control
) {
382 if (rep
->date
>= 0) {
383 if (rep
->cache_control
->s_maxage
>= 0)
384 return rep
->date
+ rep
->cache_control
->s_maxage
;
385 if (rep
->cache_control
->max_age
>= 0)
386 return rep
->date
+ rep
->cache_control
->max_age
;
389 * Conservatively handle the case when we have a max-age
390 * header, but no Date for reference?
392 if (rep
->cache_control
->s_maxage
>= 0)
393 return squid_curtime
;
394 if (rep
->cache_control
->max_age
>= 0)
395 return squid_curtime
;
398 if (Config
.onoff
.vary_ignore_expire
&&
399 httpHeaderHas(&rep
->header
, HDR_VARY
)) {
400 const time_t d
= httpHeaderGetTime(&rep
->header
, HDR_DATE
);
401 const time_t e
= httpHeaderGetTime(&rep
->header
, HDR_EXPIRES
);
405 if (httpHeaderHas(&rep
->header
, HDR_EXPIRES
)) {
406 const time_t e
= httpHeaderGetTime(&rep
->header
, HDR_EXPIRES
);
408 * HTTP/1.0 says that robust implementations should consider
409 * bad or malformed Expires header as equivalent to "expires
412 return e
< 0 ? squid_curtime
: e
;
417 /* sync this routine when you update HttpReply struct */
419 httpReplyHdrCacheInit(HttpReply
* rep
)
421 const HttpHeader
*hdr
= &rep
->header
;
423 rep
->content_length
= httpHeaderGetInt(hdr
, HDR_CONTENT_LENGTH
);
424 rep
->date
= httpHeaderGetTime(hdr
, HDR_DATE
);
425 rep
->last_modified
= httpHeaderGetTime(hdr
, HDR_LAST_MODIFIED
);
426 str
= httpHeaderGetStr(hdr
, HDR_CONTENT_TYPE
);
428 rep
->content_type
.limitInit(str
, strcspn(str
, ";\t "));
430 rep
->content_type
= StringNull
;
431 rep
->cache_control
= httpHeaderGetCc(hdr
);
432 rep
->content_range
= httpHeaderGetContRange(hdr
);
433 rep
->keep_alive
= httpMsgIsPersistent(rep
->sline
.version
, &rep
->header
);
434 /* be sure to set expires after date and cache-control */
435 rep
->expires
= httpReplyHdrExpirationTime(rep
);
438 /* sync this routine when you update HttpReply struct */
440 httpReplyHdrCacheClean(HttpReply
* rep
)
442 rep
->content_type
.clean();
443 if (rep
->cache_control
)
444 httpHdrCcDestroy(rep
->cache_control
);
445 if (rep
->content_range
)
446 httpHdrContRangeDestroy(rep
->content_range
);
450 * parses a 0-terminating buffer into HttpReply.
453 * 0 -- need more data (partial parse)
457 httpReplyParseStep(HttpReply
* rep
, const char *buf
, int atEnd
)
459 const char *parse_start
= buf
;
460 const char *blk_start
, *blk_end
;
461 const char **parse_end_ptr
= &blk_end
;
464 assert(rep
->pstate
< psParsed
);
466 *parse_end_ptr
= parse_start
;
467 if (rep
->pstate
== psReadyToParseStartLine
) {
468 if (!httpReplyIsolateStart(&parse_start
, &blk_start
, &blk_end
))
470 if (!httpStatusLineParse(&rep
->sline
, blk_start
, blk_end
))
471 return httpReplyParseError(rep
);
473 *parse_end_ptr
= parse_start
;
474 rep
->hdr_sz
= *parse_end_ptr
- buf
;
477 if (rep
->pstate
== psReadyToParseHeaders
) {
478 if (!httpMsgIsolateHeaders(&parse_start
, &blk_start
, &blk_end
)) {
480 blk_start
= parse_start
, blk_end
= blk_start
+ strlen(blk_start
);
484 if (!httpHeaderParse(&rep
->header
, blk_start
, blk_end
))
485 return httpReplyParseError(rep
);
487 httpReplyHdrCacheInit(rep
);
489 *parse_end_ptr
= parse_start
;
490 rep
->hdr_sz
= *parse_end_ptr
- buf
;
496 /* handy: resets and returns -1 */
498 httpReplyParseError(HttpReply
* rep
)
503 /* indicate an error */
504 rep
->sline
.status
= HTTP_INVALID_HEADER
;
508 /* find first CRLF */
510 httpReplyIsolateStart(const char **parse_start
, const char **blk_start
, const char **blk_end
)
512 int slen
= strcspn(*parse_start
, "\r\n");
513 if (!(*parse_start
)[slen
]) /* no CRLF found */
516 *blk_start
= *parse_start
;
517 *blk_end
= *blk_start
+ slen
;
518 while (**blk_end
== '\r') /* CR */
520 if (**blk_end
== '\n') /* LF */
523 *parse_start
= *blk_end
;
528 * Returns the body size of a HTTP response
531 httpReplyBodySize(method_t method
, HttpReply
const * reply
)
533 if (METHOD_HEAD
== method
)
535 else if (reply
->sline
.status
== HTTP_OK
)
536 (void) 0; /* common case, continue */
537 else if (reply
->sline
.status
== HTTP_NO_CONTENT
)
539 else if (reply
->sline
.status
== HTTP_NOT_MODIFIED
)
541 else if (reply
->sline
.status
< HTTP_OK
)
543 return reply
->content_length
;
547 * Calculates the maximum size allowed for an HTTP response
550 httpReplyBodyBuildSize(request_t
* request
, HttpReply
* reply
, dlink_list
* bodylist
)
553 ACLChecklist
*checklist
;
554 bs
= (body_size
*) bodylist
->head
;
556 checklist
= aclChecklistCreate(bs
->access_list
, request
, NULL
);
557 checklist
->reply
= reply
;
558 if (1 != aclCheckFast(bs
->access_list
, checklist
)) {
559 /* deny - skip this entry */
560 bs
= (body_size
*) bs
->node
.next
;
562 /* Allow - use this entry */
563 reply
->maxBodySize
= bs
->maxsize
;
565 debug(58, 3) ("httpReplyBodyBuildSize: Setting maxBodySize to %ld\n", (long int) reply
->maxBodySize
);
567 aclChecklistFree(checklist
);