]>
Commit | Line | Data |
---|---|---|
2ac76861 | 1 | |
cb69b4c7 | 2 | /* |
35282fbf | 3 | * $Id: HttpReply.cc,v 1.37 1999/04/26 21:06:12 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 | |
15 | * Duane Wessels and the University of California San Diego. Please | |
16 | * see the COPYRIGHT file for full details. Squid incorporates | |
17 | * software developed and/or copyrighted by other sources. Please see | |
18 | * the 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); |
cb69b4c7 | 59 | |
60 | ||
2246b732 | 61 | /* module initialization */ |
62 | void | |
63 | httpReplyInitModule() | |
64 | { | |
97474590 | 65 | httpHeaderMaskInit(&Denied304HeadersMask, 0); |
2246b732 | 66 | httpHeaderCalcMask(&Denied304HeadersMask, (const int *) Denied304HeadersArr, countof(Denied304HeadersArr)); |
67 | } | |
68 | ||
69 | ||
cb69b4c7 | 70 | HttpReply * |
71 | httpReplyCreate() | |
72 | { | |
d8b249ef | 73 | HttpReply *rep = memAllocate(MEM_HTTP_REPLY); |
399e85ea | 74 | debug(58, 7) ("creating rep: %p\n", rep); |
cb69b4c7 | 75 | httpReplyInit(rep); |
76 | return rep; | |
77 | } | |
78 | ||
5446b331 | 79 | static void |
2ac76861 | 80 | httpReplyInit(HttpReply * rep) |
cb69b4c7 | 81 | { |
82 | assert(rep); | |
83 | rep->hdr_sz = 0; | |
84 | rep->pstate = psReadyToParseStartLine; | |
85 | httpBodyInit(&rep->body); | |
2246b732 | 86 | httpHeaderInit(&rep->header, hoReply); |
d8b249ef | 87 | httpReplyHdrCacheInit(rep); |
cb69b4c7 | 88 | httpStatusLineInit(&rep->sline); |
89 | } | |
90 | ||
5446b331 | 91 | static void |
2ac76861 | 92 | httpReplyClean(HttpReply * rep) |
cb69b4c7 | 93 | { |
94 | assert(rep); | |
95 | httpBodyClean(&rep->body); | |
d8b249ef | 96 | httpReplyHdrCacheClean(rep); |
97 | httpHeaderClean(&rep->header); | |
cb69b4c7 | 98 | httpStatusLineClean(&rep->sline); |
99 | } | |
100 | ||
101 | void | |
2ac76861 | 102 | httpReplyDestroy(HttpReply * rep) |
cb69b4c7 | 103 | { |
104 | assert(rep); | |
399e85ea | 105 | debug(58, 7) ("destroying rep: %p\n", rep); |
cb69b4c7 | 106 | httpReplyClean(rep); |
63259c34 | 107 | httpReplyDoDestroy(rep); |
cb69b4c7 | 108 | } |
109 | ||
110 | void | |
2ac76861 | 111 | httpReplyReset(HttpReply * rep) |
cb69b4c7 | 112 | { |
113 | httpReplyClean(rep); | |
114 | httpReplyInit(rep); | |
115 | } | |
116 | ||
63259c34 | 117 | /* absorb: copy the contents of a new reply to the old one, destroy new one */ |
118 | void | |
2ac76861 | 119 | httpReplyAbsorb(HttpReply * rep, HttpReply * new_rep) |
63259c34 | 120 | { |
121 | assert(rep && new_rep); | |
122 | httpReplyClean(rep); | |
123 | *rep = *new_rep; | |
124 | /* cannot use Clean() on new reply now! */ | |
125 | httpReplyDoDestroy(new_rep); | |
126 | } | |
127 | ||
2246b732 | 128 | /* parses a 4K buffer that may not be 0-terminated; returns true on success */ |
cb69b4c7 | 129 | int |
2ac76861 | 130 | httpReplyParse(HttpReply * rep, const char *buf) |
cb69b4c7 | 131 | { |
132 | /* | |
133 | * this extra buffer/copy will be eliminated when headers become meta-data | |
134 | * in store. Currently we have to xstrncpy the buffer becuase store.c may | |
2246b732 | 135 | * feed a non 0-terminated buffer to us. |
cb69b4c7 | 136 | */ |
7021844c | 137 | char *headers = memAllocate(MEM_4K_BUF); |
cb69b4c7 | 138 | int success; |
139 | /* reset current state, because we are not used in incremental fashion */ | |
140 | httpReplyReset(rep); | |
141 | /* put a 0-terminator */ | |
142 | xstrncpy(headers, buf, 4096); | |
143 | success = httpReplyParseStep(rep, headers, 0); | |
db1cd23c | 144 | memFree(headers, MEM_4K_BUF); |
cb69b4c7 | 145 | return success == 1; |
146 | } | |
147 | ||
148 | void | |
2ac76861 | 149 | httpReplyPackInto(const HttpReply * rep, Packer * p) |
cb69b4c7 | 150 | { |
151 | assert(rep); | |
152 | httpStatusLinePackInto(&rep->sline, p); | |
d8b249ef | 153 | httpHeaderPackInto(&rep->header, p); |
cb69b4c7 | 154 | packerAppend(p, "\r\n", 2); |
155 | httpBodyPackInto(&rep->body, p); | |
156 | } | |
157 | ||
158 | /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */ | |
159 | MemBuf | |
2ac76861 | 160 | httpReplyPack(const HttpReply * rep) |
cb69b4c7 | 161 | { |
162 | MemBuf mb; | |
163 | Packer p; | |
164 | assert(rep); | |
165 | ||
166 | memBufDefInit(&mb); | |
167 | packerToMemInit(&p, &mb); | |
168 | httpReplyPackInto(rep, &p); | |
169 | packerClean(&p); | |
170 | return mb; | |
171 | } | |
172 | ||
173 | /* swap: create swap-based packer, pack, destroy packer */ | |
174 | void | |
2ac76861 | 175 | httpReplySwapOut(const HttpReply * rep, StoreEntry * e) |
cb69b4c7 | 176 | { |
177 | Packer p; | |
178 | assert(rep && e); | |
179 | ||
180 | packerToStoreInit(&p, e); | |
181 | httpReplyPackInto(rep, &p); | |
182 | packerClean(&p); | |
183 | } | |
184 | ||
185 | MemBuf | |
186 | httpPackedReply(double ver, http_status status, const char *ctype, | |
187 | int clen, time_t lmt, time_t expires) | |
188 | { | |
189 | HttpReply *rep = httpReplyCreate(); | |
190 | MemBuf mb; | |
191 | httpReplySetHeaders(rep, ver, status, ctype, NULL, clen, lmt, expires); | |
192 | mb = httpReplyPack(rep); | |
193 | httpReplyDestroy(rep); | |
194 | return mb; | |
195 | } | |
196 | ||
197 | MemBuf | |
2ac76861 | 198 | httpPacked304Reply(const HttpReply * rep) |
cb69b4c7 | 199 | { |
b644367b | 200 | static const http_hdr_type ImsEntries[] = |
6b8e7481 | 201 | {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER}; |
728da2ee | 202 | int t; |
cb69b4c7 | 203 | MemBuf mb; |
de336bbe | 204 | Packer p; |
205 | HttpHeaderEntry *e; | |
cb69b4c7 | 206 | assert(rep); |
207 | ||
cb69b4c7 | 208 | memBufDefInit(&mb); |
de336bbe | 209 | packerToMemInit(&p, &mb); |
cb69b4c7 | 210 | memBufPrintf(&mb, "%s", "HTTP/1.0 304 Not Modified\r\n"); |
de336bbe | 211 | for (t = 0; ImsEntries[t] != HDR_OTHER; ++t) |
d8b249ef | 212 | if ((e = httpHeaderFindEntry(&rep->header, ImsEntries[t]))) |
de336bbe | 213 | httpHeaderEntryPackInto(e, &p); |
cb69b4c7 | 214 | memBufAppend(&mb, "\r\n", 2); |
de336bbe | 215 | packerClean(&p); |
cb69b4c7 | 216 | return mb; |
217 | } | |
218 | ||
219 | void | |
2ac76861 | 220 | httpReplySetHeaders(HttpReply * reply, double ver, http_status status, const char *reason, |
cb69b4c7 | 221 | const char *ctype, int clen, time_t lmt, time_t expires) |
222 | { | |
223 | HttpHeader *hdr; | |
224 | assert(reply); | |
225 | httpStatusLineSet(&reply->sline, ver, status, reason); | |
d8b249ef | 226 | hdr = &reply->header; |
227 | httpHeaderPutStr(hdr, HDR_SERVER, full_appname_string); | |
228 | httpHeaderPutStr(hdr, HDR_MIME_VERSION, "1.0"); | |
229 | httpHeaderPutTime(hdr, HDR_DATE, squid_curtime); | |
230 | if (ctype) { | |
231 | httpHeaderPutStr(hdr, HDR_CONTENT_TYPE, ctype); | |
232 | stringInit(&reply->content_type, ctype); | |
233 | } else | |
234 | reply->content_type = StringNull; | |
de336bbe | 235 | if (clen >= 0) |
d8b249ef | 236 | httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, clen); |
cb69b4c7 | 237 | if (expires >= 0) |
d8b249ef | 238 | httpHeaderPutTime(hdr, HDR_EXPIRES, expires); |
2ac76861 | 239 | if (lmt > 0) /* this used to be lmt != 0 @?@ */ |
d8b249ef | 240 | httpHeaderPutTime(hdr, HDR_LAST_MODIFIED, lmt); |
241 | reply->date = squid_curtime; | |
242 | reply->content_length = clen; | |
243 | reply->expires = expires; | |
244 | reply->last_modified = lmt; | |
cb69b4c7 | 245 | } |
246 | ||
6d38ef86 | 247 | void |
248 | httpRedirectReply(HttpReply * reply, http_status status, const char *loc) | |
249 | { | |
250 | HttpHeader *hdr; | |
251 | assert(reply); | |
252 | httpStatusLineSet(&reply->sline, 1.0, status, httpStatusString(status)); | |
253 | hdr = &reply->header; | |
254 | httpHeaderPutStr(hdr, HDR_SERVER, full_appname_string); | |
255 | httpHeaderPutTime(hdr, HDR_DATE, squid_curtime); | |
256 | httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, 0); | |
257 | httpHeaderPutStr(hdr, HDR_LOCATION, loc); | |
258 | reply->date = squid_curtime; | |
259 | reply->content_length = 0; | |
260 | } | |
261 | ||
cb69b4c7 | 262 | void |
2ac76861 | 263 | httpReplyUpdateOnNotModified(HttpReply * rep, HttpReply * freshRep) |
cb69b4c7 | 264 | { |
cb69b4c7 | 265 | assert(rep && freshRep); |
d8b249ef | 266 | /* clean cache */ |
267 | httpReplyHdrCacheClean(rep); | |
268 | /* update raw headers */ | |
c68e9c6b | 269 | httpHeaderUpdate(&rep->header, &freshRep->header, |
270 | (const HttpHeaderMask *) &Denied304HeadersMask); | |
d8b249ef | 271 | /* init cache */ |
272 | httpReplyHdrCacheInit(rep); | |
cb69b4c7 | 273 | } |
274 | ||
cb69b4c7 | 275 | |
d8b249ef | 276 | /* internal routines */ |
cb69b4c7 | 277 | |
d8b249ef | 278 | /* internal function used by Destroy and Absorb */ |
279 | static void | |
280 | httpReplyDoDestroy(HttpReply * rep) | |
cb69b4c7 | 281 | { |
db1cd23c | 282 | memFree(rep, MEM_HTTP_REPLY); |
cb69b4c7 | 283 | } |
284 | ||
d8b249ef | 285 | /* sync this routine when you update HttpReply struct */ |
286 | static void | |
287 | httpReplyHdrCacheInit(HttpReply * rep) | |
cb69b4c7 | 288 | { |
d8b249ef | 289 | const HttpHeader *hdr = &rep->header; |
290 | const char *str; | |
291 | rep->content_length = httpHeaderGetInt(hdr, HDR_CONTENT_LENGTH); | |
292 | rep->date = httpHeaderGetTime(hdr, HDR_DATE); | |
293 | rep->last_modified = httpHeaderGetTime(hdr, HDR_LAST_MODIFIED); | |
294 | rep->expires = httpHeaderGetTime(hdr, HDR_EXPIRES); | |
295 | str = httpHeaderGetStr(hdr, HDR_CONTENT_TYPE); | |
296 | if (str) | |
297 | stringLimitInit(&rep->content_type, str, strcspn(str, ";\t ")); | |
298 | else | |
299 | rep->content_type = StringNull; | |
300 | rep->cache_control = httpHeaderGetCc(hdr); | |
301 | rep->content_range = httpHeaderGetContRange(hdr); | |
99edd1c3 | 302 | rep->keep_alive = httpMsgIsPersistent(rep->sline.version, &rep->header); |
d8b249ef | 303 | /* final adjustments */ |
304 | /* The max-age directive takes priority over Expires, check it first */ | |
305 | if (rep->cache_control && rep->cache_control->max_age >= 0) | |
306 | rep->expires = squid_curtime + rep->cache_control->max_age; | |
307 | else | |
b644367b | 308 | /* |
309 | * The HTTP/1.0 specs says that robust implementations should consider bad | |
310 | * or malformed Expires header as equivalent to "expires immediately." | |
311 | */ | |
d8b249ef | 312 | if (rep->expires < 0 && httpHeaderHas(hdr, HDR_EXPIRES)) |
313 | rep->expires = squid_curtime; | |
cb69b4c7 | 314 | } |
315 | ||
d8b249ef | 316 | /* sync this routine when you update HttpReply struct */ |
63259c34 | 317 | static void |
d8b249ef | 318 | httpReplyHdrCacheClean(HttpReply * rep) |
2ac76861 | 319 | { |
d8b249ef | 320 | stringClean(&rep->content_type); |
321 | if (rep->cache_control) | |
322 | httpHdrCcDestroy(rep->cache_control); | |
323 | if (rep->content_range) | |
324 | httpHdrContRangeDestroy(rep->content_range); | |
63259c34 | 325 | } |
cb69b4c7 | 326 | |
327 | /* | |
328 | * parses a 0-terminating buffer into HttpReply. | |
329 | * Returns: | |
330 | * +1 -- success | |
331 | * 0 -- need more data (partial parse) | |
332 | * -1 -- parse error | |
333 | */ | |
334 | static int | |
2ac76861 | 335 | httpReplyParseStep(HttpReply * rep, const char *buf, int atEnd) |
cb69b4c7 | 336 | { |
337 | const char *parse_start = buf; | |
338 | const char *blk_start, *blk_end; | |
339 | const char **parse_end_ptr = &blk_end; | |
340 | assert(rep); | |
341 | assert(parse_start); | |
342 | assert(rep->pstate < psParsed); | |
343 | ||
344 | *parse_end_ptr = parse_start; | |
345 | if (rep->pstate == psReadyToParseStartLine) { | |
346 | if (!httpReplyIsolateStart(&parse_start, &blk_start, &blk_end)) | |
347 | return 0; | |
348 | if (!httpStatusLineParse(&rep->sline, blk_start, blk_end)) | |
349 | return httpReplyParseError(rep); | |
350 | ||
351 | *parse_end_ptr = parse_start; | |
352 | rep->hdr_sz = *parse_end_ptr - buf; | |
2ac76861 | 353 | rep->pstate++; |
cb69b4c7 | 354 | } |
cb69b4c7 | 355 | if (rep->pstate == psReadyToParseHeaders) { |
99edd1c3 | 356 | if (!httpMsgIsolateHeaders(&parse_start, &blk_start, &blk_end)) { |
cb69b4c7 | 357 | if (atEnd) |
358 | blk_start = parse_start, blk_end = blk_start + strlen(blk_start); | |
359 | else | |
360 | return 0; | |
5999b776 | 361 | } |
d8b249ef | 362 | if (!httpHeaderParse(&rep->header, blk_start, blk_end)) |
cb69b4c7 | 363 | return httpReplyParseError(rep); |
364 | ||
d8b249ef | 365 | httpReplyHdrCacheInit(rep); |
366 | ||
cb69b4c7 | 367 | *parse_end_ptr = parse_start; |
368 | rep->hdr_sz = *parse_end_ptr - buf; | |
369 | rep->pstate++; | |
370 | } | |
cb69b4c7 | 371 | return 1; |
372 | } | |
373 | ||
cb69b4c7 | 374 | /* handy: resets and returns -1 */ |
375 | static int | |
2ac76861 | 376 | httpReplyParseError(HttpReply * rep) |
cb69b4c7 | 377 | { |
378 | assert(rep); | |
379 | /* reset */ | |
380 | httpReplyReset(rep); | |
381 | /* indicate an error */ | |
382 | rep->sline.status = HTTP_INVALID_HEADER; | |
383 | return -1; | |
384 | } | |
385 | ||
386 | /* find first CRLF */ | |
387 | static int | |
388 | httpReplyIsolateStart(const char **parse_start, const char **blk_start, const char **blk_end) | |
389 | { | |
390 | int slen = strcspn(*parse_start, "\r\n"); | |
2ac76861 | 391 | if (!(*parse_start)[slen]) /* no CRLF found */ |
cb69b4c7 | 392 | return 0; |
393 | ||
394 | *blk_start = *parse_start; | |
395 | *blk_end = *blk_start + slen; | |
52639d06 | 396 | while (**blk_end == '\r') /* CR */ |
cb69b4c7 | 397 | (*blk_end)++; |
2ac76861 | 398 | if (**blk_end == '\n') /* LF */ |
cb69b4c7 | 399 | (*blk_end)++; |
400 | ||
401 | *parse_start = *blk_end; | |
402 | return 1; | |
403 | } | |
35282fbf | 404 | |
405 | /* | |
406 | * Returns the body size of a HTTP response | |
407 | */ | |
408 | int | |
409 | httpReplyBodySize(method_t method, HttpReply * reply) | |
410 | { | |
411 | if (METHOD_HEAD == method) | |
412 | return 0; | |
413 | else if (reply->sline.status == HTTP_OK) | |
414 | (void) 0; /* common case, continue */ | |
415 | else if (reply->sline.status == HTTP_NO_CONTENT) | |
416 | return 0; | |
417 | else if (reply->sline.status == HTTP_NOT_MODIFIED) | |
418 | return 0; | |
419 | else if (reply->sline.status < HTTP_OK) | |
420 | return 0; | |
421 | return reply->content_length; | |
422 | } |