3 * $Id: HttpReply.cc,v 1.43 2000/11/13 12:25:11 adrian Exp $
5 * DEBUG: section 58 HTTP Reply (Response)
6 * AUTHOR: Alex Rousskov
8 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from the
12 * Internet community. Development is led by Duane Wessels of the
13 * National Laboratory for Applied Network Research and funded by the
14 * National Science Foundation. Squid is Copyrighted (C) 1998 by
15 * the Regents of the University of California. Please see the
16 * COPYRIGHT file for full details. Squid incorporates software
17 * developed and/or copyrighted by other sources. Please see the
18 * 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.
41 /* these entity-headers must be ignored if a bogus server sends them in 304 */
42 static HttpHeaderMask Denied304HeadersMask
;
43 static http_hdr_type Denied304HeadersArr
[] =
45 HDR_ALLOW
, HDR_CONTENT_ENCODING
, HDR_CONTENT_LANGUAGE
, HDR_CONTENT_LENGTH
,
46 HDR_CONTENT_LOCATION
, HDR_CONTENT_RANGE
, HDR_LAST_MODIFIED
, HDR_LINK
,
51 static void httpReplyInit(HttpReply
* rep
);
52 static void httpReplyClean(HttpReply
* rep
);
53 static void httpReplyDoDestroy(HttpReply
* rep
);
54 static void httpReplyHdrCacheInit(HttpReply
* rep
);
55 static void httpReplyHdrCacheClean(HttpReply
* rep
);
56 static int httpReplyParseStep(HttpReply
* rep
, const char *parse_start
, int atEnd
);
57 static int httpReplyParseError(HttpReply
* rep
);
58 static int httpReplyIsolateStart(const char **parse_start
, const char **blk_start
, const char **blk_end
);
59 static time_t httpReplyHdrExpirationTime(const HttpReply
* rep
);
62 /* module initialization */
64 httpReplyInitModule(void)
66 httpHeaderMaskInit(&Denied304HeadersMask
, 0);
67 httpHeaderCalcMask(&Denied304HeadersMask
, (const int *) Denied304HeadersArr
, countof(Denied304HeadersArr
));
74 HttpReply
*rep
= memAllocate(MEM_HTTP_REPLY
);
75 debug(58, 7) ("creating rep: %p\n", rep
);
81 httpReplyInit(HttpReply
* rep
)
85 rep
->pstate
= psReadyToParseStartLine
;
86 httpBodyInit(&rep
->body
);
87 httpHeaderInit(&rep
->header
, hoReply
);
88 httpReplyHdrCacheInit(rep
);
89 httpStatusLineInit(&rep
->sline
);
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
)
118 /* absorb: copy the contents of a new reply to the old one, destroy new one */
120 httpReplyAbsorb(HttpReply
* rep
, HttpReply
* new_rep
)
122 assert(rep
&& new_rep
);
125 /* cannot use Clean() on new reply now! */
126 httpReplyDoDestroy(new_rep
);
130 * httpReplyParse takes character buffer of HTTP headers (buf),
131 * which may not be NULL-terminated, and fills in an HttpReply
132 * structure (rep). The parameter 'end' specifies the offset to
133 * the end of the reply headers. The caller may know where the
134 * end is, but is unable to NULL-terminate the buffer. This function
135 * returns true on success.
138 httpReplyParse(HttpReply
* rep
, const char *buf
, ssize_t end
)
141 * this extra buffer/copy will be eliminated when headers become
142 * meta-data in store. Currently we have to xstrncpy the buffer
143 * becuase somebody may feed a non NULL-terminated buffer to
146 char *headers
= memAllocate(MEM_4K_BUF
);
148 size_t s
= XMIN(end
+ 1, 4096);
149 /* reset current state, because we are not used in incremental fashion */
151 /* put a string terminator. s is how many bytes to touch in
152 * 'buf' including the terminating NULL. */
153 xstrncpy(headers
, buf
, s
);
154 success
= httpReplyParseStep(rep
, headers
, 0);
155 memFree(headers
, MEM_4K_BUF
);
160 httpReplyPackInto(const HttpReply
* rep
, Packer
* p
)
163 httpStatusLinePackInto(&rep
->sline
, p
);
164 httpHeaderPackInto(&rep
->header
, p
);
165 packerAppend(p
, "\r\n", 2);
166 httpBodyPackInto(&rep
->body
, p
);
169 /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
171 httpReplyPack(const HttpReply
* rep
)
178 packerToMemInit(&p
, &mb
);
179 httpReplyPackInto(rep
, &p
);
184 /* swap: create swap-based packer, pack, destroy packer */
186 httpReplySwapOut(const HttpReply
* rep
, StoreEntry
* e
)
191 packerToStoreInit(&p
, e
);
192 httpReplyPackInto(rep
, &p
);
197 httpPackedReply(http_version_t ver
, http_status status
, const char *ctype
,
198 int clen
, time_t lmt
, time_t expires
)
200 HttpReply
*rep
= httpReplyCreate();
202 httpReplySetHeaders(rep
, ver
, status
, ctype
, NULL
, clen
, lmt
, expires
);
203 mb
= httpReplyPack(rep
);
204 httpReplyDestroy(rep
);
209 httpPacked304Reply(const HttpReply
* rep
)
211 static const http_hdr_type ImsEntries
[] =
212 {HDR_DATE
, HDR_CONTENT_TYPE
, HDR_EXPIRES
, HDR_LAST_MODIFIED
, /* eof */ HDR_OTHER
};
220 packerToMemInit(&p
, &mb
);
221 memBufPrintf(&mb
, "%s", "HTTP/1.0 304 Not Modified\r\n");
222 for (t
= 0; ImsEntries
[t
] != HDR_OTHER
; ++t
)
223 if ((e
= httpHeaderFindEntry(&rep
->header
, ImsEntries
[t
])))
224 httpHeaderEntryPackInto(e
, &p
);
225 memBufAppend(&mb
, "\r\n", 2);
231 httpReplySetHeaders(HttpReply
* reply
, http_version_t ver
, http_status status
, const char *reason
,
232 const char *ctype
, int clen
, time_t lmt
, time_t expires
)
236 httpStatusLineSet(&reply
->sline
, ver
, status
, reason
);
237 hdr
= &reply
->header
;
238 httpHeaderPutStr(hdr
, HDR_SERVER
, full_appname_string
);
239 httpHeaderPutStr(hdr
, HDR_MIME_VERSION
, "1.0");
240 httpHeaderPutTime(hdr
, HDR_DATE
, squid_curtime
);
242 httpHeaderPutStr(hdr
, HDR_CONTENT_TYPE
, ctype
);
243 stringInit(&reply
->content_type
, ctype
);
245 reply
->content_type
= StringNull
;
247 httpHeaderPutInt(hdr
, HDR_CONTENT_LENGTH
, clen
);
249 httpHeaderPutTime(hdr
, HDR_EXPIRES
, expires
);
250 if (lmt
> 0) /* this used to be lmt != 0 @?@ */
251 httpHeaderPutTime(hdr
, HDR_LAST_MODIFIED
, lmt
);
252 reply
->date
= squid_curtime
;
253 reply
->content_length
= clen
;
254 reply
->expires
= expires
;
255 reply
->last_modified
= lmt
;
259 httpRedirectReply(HttpReply
* reply
, http_status status
, const char *loc
)
264 httpBuildVersion(&ver
,1,0);
265 httpStatusLineSet(&reply
->sline
, ver
, status
, httpStatusString(status
));
266 hdr
= &reply
->header
;
267 httpHeaderPutStr(hdr
, HDR_SERVER
, full_appname_string
);
268 httpHeaderPutTime(hdr
, HDR_DATE
, squid_curtime
);
269 httpHeaderPutInt(hdr
, HDR_CONTENT_LENGTH
, 0);
270 httpHeaderPutStr(hdr
, HDR_LOCATION
, loc
);
271 reply
->date
= squid_curtime
;
272 reply
->content_length
= 0;
276 httpReplyUpdateOnNotModified(HttpReply
* rep
, HttpReply
* freshRep
)
278 assert(rep
&& freshRep
);
280 httpReplyHdrCacheClean(rep
);
281 /* update raw headers */
282 httpHeaderUpdate(&rep
->header
, &freshRep
->header
,
283 (const HttpHeaderMask
*) &Denied304HeadersMask
);
285 httpReplyHdrCacheInit(rep
);
289 /* internal routines */
291 /* internal function used by Destroy and Absorb */
293 httpReplyDoDestroy(HttpReply
* rep
)
295 memFree(rep
, MEM_HTTP_REPLY
);
299 httpReplyHdrExpirationTime(const HttpReply
* rep
)
301 /* The s-maxage and max-age directive takes priority over Expires */
302 if (rep
->cache_control
) {
303 if (rep
->date
>= 0) {
304 if (rep
->cache_control
->s_maxage
>= 0)
305 return rep
->date
+ rep
->cache_control
->s_maxage
;
306 if (rep
->cache_control
->max_age
>= 0)
307 return rep
->date
+ rep
->cache_control
->max_age
;
310 * Conservatively handle the case when we have a max-age
311 * header, but no Date for reference?
313 if (rep
->cache_control
->s_maxage
>= 0)
314 return squid_curtime
;
315 if (rep
->cache_control
->max_age
>= 0)
316 return squid_curtime
;
319 if (httpHeaderHas(&rep
->header
, HDR_EXPIRES
)) {
320 const time_t e
= httpHeaderGetTime(&rep
->header
, HDR_EXPIRES
);
322 * HTTP/1.0 says that robust implementations should consider
323 * bad or malformed Expires header as equivalent to "expires
326 return e
< 0 ? squid_curtime
: e
;
331 /* sync this routine when you update HttpReply struct */
333 httpReplyHdrCacheInit(HttpReply
* rep
)
335 const HttpHeader
*hdr
= &rep
->header
;
337 rep
->content_length
= httpHeaderGetInt(hdr
, HDR_CONTENT_LENGTH
);
338 rep
->date
= httpHeaderGetTime(hdr
, HDR_DATE
);
339 rep
->last_modified
= httpHeaderGetTime(hdr
, HDR_LAST_MODIFIED
);
340 str
= httpHeaderGetStr(hdr
, HDR_CONTENT_TYPE
);
342 stringLimitInit(&rep
->content_type
, str
, strcspn(str
, ";\t "));
344 rep
->content_type
= StringNull
;
345 rep
->cache_control
= httpHeaderGetCc(hdr
);
346 rep
->content_range
= httpHeaderGetContRange(hdr
);
347 rep
->keep_alive
= httpMsgIsPersistent(rep
->sline
.version
, &rep
->header
);
348 /* be sure to set expires after date and cache-control */
349 rep
->expires
= httpReplyHdrExpirationTime(rep
);
352 /* sync this routine when you update HttpReply struct */
354 httpReplyHdrCacheClean(HttpReply
* rep
)
356 stringClean(&rep
->content_type
);
357 if (rep
->cache_control
)
358 httpHdrCcDestroy(rep
->cache_control
);
359 if (rep
->content_range
)
360 httpHdrContRangeDestroy(rep
->content_range
);
364 * parses a 0-terminating buffer into HttpReply.
367 * 0 -- need more data (partial parse)
371 httpReplyParseStep(HttpReply
* rep
, const char *buf
, int atEnd
)
373 const char *parse_start
= buf
;
374 const char *blk_start
, *blk_end
;
375 const char **parse_end_ptr
= &blk_end
;
378 assert(rep
->pstate
< psParsed
);
380 *parse_end_ptr
= parse_start
;
381 if (rep
->pstate
== psReadyToParseStartLine
) {
382 if (!httpReplyIsolateStart(&parse_start
, &blk_start
, &blk_end
))
384 if (!httpStatusLineParse(&rep
->sline
, blk_start
, blk_end
))
385 return httpReplyParseError(rep
);
387 *parse_end_ptr
= parse_start
;
388 rep
->hdr_sz
= *parse_end_ptr
- buf
;
391 if (rep
->pstate
== psReadyToParseHeaders
) {
392 if (!httpMsgIsolateHeaders(&parse_start
, &blk_start
, &blk_end
)) {
394 blk_start
= parse_start
, blk_end
= blk_start
+ strlen(blk_start
);
398 if (!httpHeaderParse(&rep
->header
, blk_start
, blk_end
))
399 return httpReplyParseError(rep
);
401 httpReplyHdrCacheInit(rep
);
403 *parse_end_ptr
= parse_start
;
404 rep
->hdr_sz
= *parse_end_ptr
- buf
;
410 /* handy: resets and returns -1 */
412 httpReplyParseError(HttpReply
* rep
)
417 /* indicate an error */
418 rep
->sline
.status
= HTTP_INVALID_HEADER
;
422 /* find first CRLF */
424 httpReplyIsolateStart(const char **parse_start
, const char **blk_start
, const char **blk_end
)
426 int slen
= strcspn(*parse_start
, "\r\n");
427 if (!(*parse_start
)[slen
]) /* no CRLF found */
430 *blk_start
= *parse_start
;
431 *blk_end
= *blk_start
+ slen
;
432 while (**blk_end
== '\r') /* CR */
434 if (**blk_end
== '\n') /* LF */
437 *parse_start
= *blk_end
;
442 * Returns the body size of a HTTP response
445 httpReplyBodySize(method_t method
, HttpReply
* reply
)
447 if (METHOD_HEAD
== method
)
449 else if (reply
->sline
.status
== HTTP_OK
)
450 (void) 0; /* common case, continue */
451 else if (reply
->sline
.status
== HTTP_NO_CONTENT
)
453 else if (reply
->sline
.status
== HTTP_NOT_MODIFIED
)
455 else if (reply
->sline
.status
< HTTP_OK
)
457 return reply
->content_length
;