]>
Commit | Line | Data |
---|---|---|
2ac76861 | 1 | |
cb69b4c7 | 2 | /* |
bffee5af | 3 | * $Id: HttpReply.cc,v 1.44 2000/12/05 09:15:57 wessels Exp $ |
cb69b4c7 | 4 | * |
123abbe1 | 5 | * DEBUG: section 58 HTTP Reply (Response) |
cb69b4c7 | 6 | * AUTHOR: Alex Rousskov |
7 | * | |
8 | * SQUID Internet Object Cache http://squid.nlanr.net/Squid/ | |
e25c139f | 9 | * ---------------------------------------------------------- |
cb69b4c7 | 10 | * |
11 | * Squid is the result of efforts by numerous individuals from the | |
12 | * Internet community. Development is led by Duane Wessels of the | |
e25c139f | 13 | * National Laboratory for Applied Network Research and funded by the |
14 | * National Science Foundation. Squid is Copyrighted (C) 1998 by | |
efd900cb | 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. | |
cb69b4c7 | 19 | * |
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. | |
24 | * | |
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. | |
29 | * | |
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 | |
cbdec147 | 32 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. |
e25c139f | 33 | * |
cb69b4c7 | 34 | */ |
35 | ||
cb69b4c7 | 36 | #include "squid.h" |
37 | ||
38 | ||
39 | /* local constants */ | |
40 | ||
2246b732 | 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[] = | |
44 | { | |
45 | HDR_ALLOW, HDR_CONTENT_ENCODING, HDR_CONTENT_LANGUAGE, HDR_CONTENT_LENGTH, | |
46 | HDR_CONTENT_LOCATION, HDR_CONTENT_RANGE, HDR_LAST_MODIFIED, HDR_LINK, | |
47 | HDR_OTHER | |
48 | }; | |
49 | ||
cb69b4c7 | 50 | /* local routines */ |
2246b732 | 51 | static void httpReplyInit(HttpReply * rep); |
52 | static void httpReplyClean(HttpReply * rep); | |
2ac76861 | 53 | static void httpReplyDoDestroy(HttpReply * rep); |
d8b249ef | 54 | static void httpReplyHdrCacheInit(HttpReply * rep); |
55 | static void httpReplyHdrCacheClean(HttpReply * rep); | |
2ac76861 | 56 | static int httpReplyParseStep(HttpReply * rep, const char *parse_start, int atEnd); |
57 | static int httpReplyParseError(HttpReply * rep); | |
cb69b4c7 | 58 | static int httpReplyIsolateStart(const char **parse_start, const char **blk_start, const char **blk_end); |
d20b1cd0 | 59 | static time_t httpReplyHdrExpirationTime(const HttpReply * rep); |
cb69b4c7 | 60 | |
61 | ||
2246b732 | 62 | /* module initialization */ |
63 | void | |
9bc73deb | 64 | httpReplyInitModule(void) |
2246b732 | 65 | { |
97474590 | 66 | httpHeaderMaskInit(&Denied304HeadersMask, 0); |
2246b732 | 67 | httpHeaderCalcMask(&Denied304HeadersMask, (const int *) Denied304HeadersArr, countof(Denied304HeadersArr)); |
68 | } | |
69 | ||
70 | ||
cb69b4c7 | 71 | HttpReply * |
9bc73deb | 72 | httpReplyCreate(void) |
cb69b4c7 | 73 | { |
d8b249ef | 74 | HttpReply *rep = memAllocate(MEM_HTTP_REPLY); |
399e85ea | 75 | debug(58, 7) ("creating rep: %p\n", rep); |
cb69b4c7 | 76 | httpReplyInit(rep); |
77 | return rep; | |
78 | } | |
79 | ||
5446b331 | 80 | static void |
2ac76861 | 81 | httpReplyInit(HttpReply * rep) |
cb69b4c7 | 82 | { |
83 | assert(rep); | |
84 | rep->hdr_sz = 0; | |
85 | rep->pstate = psReadyToParseStartLine; | |
86 | httpBodyInit(&rep->body); | |
2246b732 | 87 | httpHeaderInit(&rep->header, hoReply); |
d8b249ef | 88 | httpReplyHdrCacheInit(rep); |
cb69b4c7 | 89 | httpStatusLineInit(&rep->sline); |
90 | } | |
91 | ||
5446b331 | 92 | static void |
2ac76861 | 93 | httpReplyClean(HttpReply * rep) |
cb69b4c7 | 94 | { |
95 | assert(rep); | |
96 | httpBodyClean(&rep->body); | |
d8b249ef | 97 | httpReplyHdrCacheClean(rep); |
98 | httpHeaderClean(&rep->header); | |
cb69b4c7 | 99 | httpStatusLineClean(&rep->sline); |
100 | } | |
101 | ||
102 | void | |
2ac76861 | 103 | httpReplyDestroy(HttpReply * rep) |
cb69b4c7 | 104 | { |
105 | assert(rep); | |
399e85ea | 106 | debug(58, 7) ("destroying rep: %p\n", rep); |
cb69b4c7 | 107 | httpReplyClean(rep); |
63259c34 | 108 | httpReplyDoDestroy(rep); |
cb69b4c7 | 109 | } |
110 | ||
111 | void | |
2ac76861 | 112 | httpReplyReset(HttpReply * rep) |
cb69b4c7 | 113 | { |
114 | httpReplyClean(rep); | |
115 | httpReplyInit(rep); | |
116 | } | |
117 | ||
63259c34 | 118 | /* absorb: copy the contents of a new reply to the old one, destroy new one */ |
119 | void | |
2ac76861 | 120 | httpReplyAbsorb(HttpReply * rep, HttpReply * new_rep) |
63259c34 | 121 | { |
122 | assert(rep && new_rep); | |
123 | httpReplyClean(rep); | |
124 | *rep = *new_rep; | |
125 | /* cannot use Clean() on new reply now! */ | |
126 | httpReplyDoDestroy(new_rep); | |
127 | } | |
128 | ||
9bc73deb | 129 | /* |
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. | |
136 | */ | |
cb69b4c7 | 137 | int |
9bc73deb | 138 | httpReplyParse(HttpReply * rep, const char *buf, ssize_t end) |
cb69b4c7 | 139 | { |
140 | /* | |
9bc73deb | 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 | |
144 | * us. | |
cb69b4c7 | 145 | */ |
7021844c | 146 | char *headers = memAllocate(MEM_4K_BUF); |
cb69b4c7 | 147 | int success; |
25b0856c | 148 | size_t s = XMIN(end + 1, 4096); |
cb69b4c7 | 149 | /* reset current state, because we are not used in incremental fashion */ |
150 | httpReplyReset(rep); | |
25b0856c | 151 | /* put a string terminator. s is how many bytes to touch in |
152 | * 'buf' including the terminating NULL. */ | |
153 | xstrncpy(headers, buf, s); | |
cb69b4c7 | 154 | success = httpReplyParseStep(rep, headers, 0); |
db1cd23c | 155 | memFree(headers, MEM_4K_BUF); |
cb69b4c7 | 156 | return success == 1; |
157 | } | |
158 | ||
159 | void | |
2ac76861 | 160 | httpReplyPackInto(const HttpReply * rep, Packer * p) |
cb69b4c7 | 161 | { |
162 | assert(rep); | |
163 | httpStatusLinePackInto(&rep->sline, p); | |
d8b249ef | 164 | httpHeaderPackInto(&rep->header, p); |
cb69b4c7 | 165 | packerAppend(p, "\r\n", 2); |
166 | httpBodyPackInto(&rep->body, p); | |
167 | } | |
168 | ||
169 | /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */ | |
170 | MemBuf | |
2ac76861 | 171 | httpReplyPack(const HttpReply * rep) |
cb69b4c7 | 172 | { |
173 | MemBuf mb; | |
174 | Packer p; | |
175 | assert(rep); | |
176 | ||
177 | memBufDefInit(&mb); | |
178 | packerToMemInit(&p, &mb); | |
179 | httpReplyPackInto(rep, &p); | |
180 | packerClean(&p); | |
181 | return mb; | |
182 | } | |
183 | ||
184 | /* swap: create swap-based packer, pack, destroy packer */ | |
185 | void | |
2ac76861 | 186 | httpReplySwapOut(const HttpReply * rep, StoreEntry * e) |
cb69b4c7 | 187 | { |
188 | Packer p; | |
189 | assert(rep && e); | |
190 | ||
191 | packerToStoreInit(&p, e); | |
192 | httpReplyPackInto(rep, &p); | |
193 | packerClean(&p); | |
194 | } | |
195 | ||
196 | MemBuf | |
ccf44862 | 197 | httpPackedReply(http_version_t ver, http_status status, const char *ctype, |
cb69b4c7 | 198 | int clen, time_t lmt, time_t expires) |
199 | { | |
200 | HttpReply *rep = httpReplyCreate(); | |
201 | MemBuf mb; | |
202 | httpReplySetHeaders(rep, ver, status, ctype, NULL, clen, lmt, expires); | |
203 | mb = httpReplyPack(rep); | |
204 | httpReplyDestroy(rep); | |
205 | return mb; | |
206 | } | |
207 | ||
208 | MemBuf | |
2ac76861 | 209 | httpPacked304Reply(const HttpReply * rep) |
cb69b4c7 | 210 | { |
b644367b | 211 | static const http_hdr_type ImsEntries[] = |
6b8e7481 | 212 | {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER}; |
728da2ee | 213 | int t; |
cb69b4c7 | 214 | MemBuf mb; |
de336bbe | 215 | Packer p; |
216 | HttpHeaderEntry *e; | |
cb69b4c7 | 217 | assert(rep); |
218 | ||
cb69b4c7 | 219 | memBufDefInit(&mb); |
de336bbe | 220 | packerToMemInit(&p, &mb); |
cb69b4c7 | 221 | memBufPrintf(&mb, "%s", "HTTP/1.0 304 Not Modified\r\n"); |
de336bbe | 222 | for (t = 0; ImsEntries[t] != HDR_OTHER; ++t) |
d8b249ef | 223 | if ((e = httpHeaderFindEntry(&rep->header, ImsEntries[t]))) |
de336bbe | 224 | httpHeaderEntryPackInto(e, &p); |
cb69b4c7 | 225 | memBufAppend(&mb, "\r\n", 2); |
de336bbe | 226 | packerClean(&p); |
cb69b4c7 | 227 | return mb; |
228 | } | |
229 | ||
230 | void | |
ccf44862 | 231 | httpReplySetHeaders(HttpReply * reply, http_version_t ver, http_status status, const char *reason, |
cb69b4c7 | 232 | const char *ctype, int clen, time_t lmt, time_t expires) |
233 | { | |
234 | HttpHeader *hdr; | |
235 | assert(reply); | |
236 | httpStatusLineSet(&reply->sline, ver, status, reason); | |
d8b249ef | 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); | |
241 | if (ctype) { | |
242 | httpHeaderPutStr(hdr, HDR_CONTENT_TYPE, ctype); | |
243 | stringInit(&reply->content_type, ctype); | |
244 | } else | |
245 | reply->content_type = StringNull; | |
de336bbe | 246 | if (clen >= 0) |
d8b249ef | 247 | httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, clen); |
cb69b4c7 | 248 | if (expires >= 0) |
d8b249ef | 249 | httpHeaderPutTime(hdr, HDR_EXPIRES, expires); |
2ac76861 | 250 | if (lmt > 0) /* this used to be lmt != 0 @?@ */ |
d8b249ef | 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; | |
cb69b4c7 | 256 | } |
257 | ||
6d38ef86 | 258 | void |
259 | httpRedirectReply(HttpReply * reply, http_status status, const char *loc) | |
260 | { | |
261 | HttpHeader *hdr; | |
ccf44862 | 262 | http_version_t ver; |
6d38ef86 | 263 | assert(reply); |
bffee5af | 264 | httpBuildVersion(&ver, 1, 0); |
ccf44862 | 265 | httpStatusLineSet(&reply->sline, ver, status, httpStatusString(status)); |
6d38ef86 | 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; | |
273 | } | |
274 | ||
cb69b4c7 | 275 | void |
2ac76861 | 276 | httpReplyUpdateOnNotModified(HttpReply * rep, HttpReply * freshRep) |
cb69b4c7 | 277 | { |
cb69b4c7 | 278 | assert(rep && freshRep); |
d8b249ef | 279 | /* clean cache */ |
280 | httpReplyHdrCacheClean(rep); | |
281 | /* update raw headers */ | |
c68e9c6b | 282 | httpHeaderUpdate(&rep->header, &freshRep->header, |
283 | (const HttpHeaderMask *) &Denied304HeadersMask); | |
d8b249ef | 284 | /* init cache */ |
285 | httpReplyHdrCacheInit(rep); | |
cb69b4c7 | 286 | } |
287 | ||
cb69b4c7 | 288 | |
d8b249ef | 289 | /* internal routines */ |
cb69b4c7 | 290 | |
d8b249ef | 291 | /* internal function used by Destroy and Absorb */ |
292 | static void | |
293 | httpReplyDoDestroy(HttpReply * rep) | |
cb69b4c7 | 294 | { |
db1cd23c | 295 | memFree(rep, MEM_HTTP_REPLY); |
cb69b4c7 | 296 | } |
297 | ||
d20b1cd0 | 298 | static time_t |
299 | httpReplyHdrExpirationTime(const HttpReply * rep) | |
300 | { | |
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; | |
308 | } else { | |
309 | /* | |
310 | * Conservatively handle the case when we have a max-age | |
311 | * header, but no Date for reference? | |
312 | */ | |
313 | if (rep->cache_control->s_maxage >= 0) | |
314 | return squid_curtime; | |
315 | if (rep->cache_control->max_age >= 0) | |
316 | return squid_curtime; | |
317 | } | |
318 | } | |
319 | if (httpHeaderHas(&rep->header, HDR_EXPIRES)) { | |
320 | const time_t e = httpHeaderGetTime(&rep->header, HDR_EXPIRES); | |
321 | /* | |
322 | * HTTP/1.0 says that robust implementations should consider | |
323 | * bad or malformed Expires header as equivalent to "expires | |
324 | * immediately." | |
325 | */ | |
326 | return e < 0 ? squid_curtime : e; | |
327 | } | |
328 | return -1; | |
329 | } | |
330 | ||
d8b249ef | 331 | /* sync this routine when you update HttpReply struct */ |
332 | static void | |
333 | httpReplyHdrCacheInit(HttpReply * rep) | |
cb69b4c7 | 334 | { |
d8b249ef | 335 | const HttpHeader *hdr = &rep->header; |
336 | const char *str; | |
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); | |
d8b249ef | 340 | str = httpHeaderGetStr(hdr, HDR_CONTENT_TYPE); |
341 | if (str) | |
342 | stringLimitInit(&rep->content_type, str, strcspn(str, ";\t ")); | |
343 | else | |
344 | rep->content_type = StringNull; | |
345 | rep->cache_control = httpHeaderGetCc(hdr); | |
346 | rep->content_range = httpHeaderGetContRange(hdr); | |
99edd1c3 | 347 | rep->keep_alive = httpMsgIsPersistent(rep->sline.version, &rep->header); |
d20b1cd0 | 348 | /* be sure to set expires after date and cache-control */ |
349 | rep->expires = httpReplyHdrExpirationTime(rep); | |
cb69b4c7 | 350 | } |
351 | ||
d8b249ef | 352 | /* sync this routine when you update HttpReply struct */ |
63259c34 | 353 | static void |
d8b249ef | 354 | httpReplyHdrCacheClean(HttpReply * rep) |
2ac76861 | 355 | { |
d8b249ef | 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); | |
63259c34 | 361 | } |
cb69b4c7 | 362 | |
363 | /* | |
364 | * parses a 0-terminating buffer into HttpReply. | |
365 | * Returns: | |
366 | * +1 -- success | |
367 | * 0 -- need more data (partial parse) | |
368 | * -1 -- parse error | |
369 | */ | |
370 | static int | |
2ac76861 | 371 | httpReplyParseStep(HttpReply * rep, const char *buf, int atEnd) |
cb69b4c7 | 372 | { |
373 | const char *parse_start = buf; | |
374 | const char *blk_start, *blk_end; | |
375 | const char **parse_end_ptr = &blk_end; | |
376 | assert(rep); | |
377 | assert(parse_start); | |
378 | assert(rep->pstate < psParsed); | |
379 | ||
380 | *parse_end_ptr = parse_start; | |
381 | if (rep->pstate == psReadyToParseStartLine) { | |
382 | if (!httpReplyIsolateStart(&parse_start, &blk_start, &blk_end)) | |
383 | return 0; | |
384 | if (!httpStatusLineParse(&rep->sline, blk_start, blk_end)) | |
385 | return httpReplyParseError(rep); | |
386 | ||
387 | *parse_end_ptr = parse_start; | |
388 | rep->hdr_sz = *parse_end_ptr - buf; | |
2ac76861 | 389 | rep->pstate++; |
cb69b4c7 | 390 | } |
cb69b4c7 | 391 | if (rep->pstate == psReadyToParseHeaders) { |
99edd1c3 | 392 | if (!httpMsgIsolateHeaders(&parse_start, &blk_start, &blk_end)) { |
cb69b4c7 | 393 | if (atEnd) |
394 | blk_start = parse_start, blk_end = blk_start + strlen(blk_start); | |
395 | else | |
396 | return 0; | |
5999b776 | 397 | } |
d8b249ef | 398 | if (!httpHeaderParse(&rep->header, blk_start, blk_end)) |
cb69b4c7 | 399 | return httpReplyParseError(rep); |
400 | ||
d8b249ef | 401 | httpReplyHdrCacheInit(rep); |
402 | ||
cb69b4c7 | 403 | *parse_end_ptr = parse_start; |
404 | rep->hdr_sz = *parse_end_ptr - buf; | |
405 | rep->pstate++; | |
406 | } | |
cb69b4c7 | 407 | return 1; |
408 | } | |
409 | ||
cb69b4c7 | 410 | /* handy: resets and returns -1 */ |
411 | static int | |
2ac76861 | 412 | httpReplyParseError(HttpReply * rep) |
cb69b4c7 | 413 | { |
414 | assert(rep); | |
415 | /* reset */ | |
416 | httpReplyReset(rep); | |
417 | /* indicate an error */ | |
418 | rep->sline.status = HTTP_INVALID_HEADER; | |
419 | return -1; | |
420 | } | |
421 | ||
422 | /* find first CRLF */ | |
423 | static int | |
424 | httpReplyIsolateStart(const char **parse_start, const char **blk_start, const char **blk_end) | |
425 | { | |
426 | int slen = strcspn(*parse_start, "\r\n"); | |
2ac76861 | 427 | if (!(*parse_start)[slen]) /* no CRLF found */ |
cb69b4c7 | 428 | return 0; |
429 | ||
430 | *blk_start = *parse_start; | |
431 | *blk_end = *blk_start + slen; | |
52639d06 | 432 | while (**blk_end == '\r') /* CR */ |
cb69b4c7 | 433 | (*blk_end)++; |
2ac76861 | 434 | if (**blk_end == '\n') /* LF */ |
cb69b4c7 | 435 | (*blk_end)++; |
436 | ||
437 | *parse_start = *blk_end; | |
438 | return 1; | |
439 | } | |
35282fbf | 440 | |
441 | /* | |
442 | * Returns the body size of a HTTP response | |
443 | */ | |
444 | int | |
445 | httpReplyBodySize(method_t method, HttpReply * reply) | |
446 | { | |
447 | if (METHOD_HEAD == method) | |
448 | return 0; | |
449 | else if (reply->sline.status == HTTP_OK) | |
450 | (void) 0; /* common case, continue */ | |
451 | else if (reply->sline.status == HTTP_NO_CONTENT) | |
452 | return 0; | |
453 | else if (reply->sline.status == HTTP_NOT_MODIFIED) | |
454 | return 0; | |
455 | else if (reply->sline.status < HTTP_OK) | |
456 | return 0; | |
457 | return reply->content_length; | |
458 | } |