]>
Commit | Line | Data |
---|---|---|
2ac76861 | 1 | |
cb69b4c7 | 2 | /* |
4a56ee8d | 3 | * $Id: HttpReply.cc,v 1.82 2006/01/23 20:04:24 wessels Exp $ |
cb69b4c7 | 4 | * |
123abbe1 | 5 | * DEBUG: section 58 HTTP Reply (Response) |
cb69b4c7 | 6 | * AUTHOR: Alex Rousskov |
7 | * | |
2b6662ba | 8 | * SQUID Web Proxy Cache http://www.squid-cache.org/ |
e25c139f | 9 | * ---------------------------------------------------------- |
cb69b4c7 | 10 | * |
2b6662ba | 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. | |
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" |
528b2c61 | 37 | #include "Store.h" |
8596962e | 38 | #include "HttpReply.h" |
528b2c61 | 39 | #include "HttpHdrContRange.h" |
4fb35c3c | 40 | #include "ACLChecklist.h" |
0eb49b6d | 41 | #include "MemBuf.h" |
cb69b4c7 | 42 | |
43 | /* local constants */ | |
44 | ||
2246b732 | 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[] = | |
62e76326 | 48 | { |
49 | HDR_ALLOW, HDR_CONTENT_ENCODING, HDR_CONTENT_LANGUAGE, HDR_CONTENT_LENGTH, | |
50 | HDR_CONTENT_LOCATION, HDR_CONTENT_RANGE, HDR_LAST_MODIFIED, HDR_LINK, | |
51 | HDR_OTHER | |
52 | }; | |
2246b732 | 53 | |
2246b732 | 54 | /* module initialization */ |
55 | void | |
9bc73deb | 56 | httpReplyInitModule(void) |
2246b732 | 57 | { |
8596962e | 58 | assert(HTTP_STATUS_NONE == 0); // HttpReply::parse() interface assumes that |
97474590 | 59 | httpHeaderMaskInit(&Denied304HeadersMask, 0); |
8abf232c | 60 | httpHeaderCalcMask(&Denied304HeadersMask, Denied304HeadersArr, countof(Denied304HeadersArr)); |
2246b732 | 61 | } |
62 | ||
8596962e | 63 | HttpReply::HttpReply() : HttpMsg(hoReply), date (0), last_modified (0), expires (0), surrogate_control (NULL), content_range (NULL), keep_alive (0), protoPrefix("HTTP/") |
cb69b4c7 | 64 | { |
06a5ae20 | 65 | init(); |
cb69b4c7 | 66 | } |
67 | ||
06a5ae20 | 68 | HttpReply::~HttpReply() |
cb69b4c7 | 69 | { |
06a5ae20 | 70 | if (do_clean) |
71 | clean(); | |
cb69b4c7 | 72 | } |
73 | ||
74 | void | |
06a5ae20 | 75 | HttpReply::init() |
cb69b4c7 | 76 | { |
06a5ae20 | 77 | httpBodyInit(&body); |
78 | hdrCacheInit(); | |
79 | httpStatusLineInit(&sline); | |
80 | do_clean = true; | |
cb69b4c7 | 81 | } |
82 | ||
06a5ae20 | 83 | void HttpReply::reset() |
cb69b4c7 | 84 | { |
06a5ae20 | 85 | |
8596962e | 86 | // reset should not reset the protocol; could have made protoPrefix a |
87 | // virtual function instead, but it is not clear whether virtual methods | |
88 | // are allowed with MEMPROXY_CLASS() and whether some cbdata void* | |
89 | // conversions are not going to kill virtual tables | |
06a5ae20 | 90 | const String pfx = protoPrefix; |
91 | clean(); | |
92 | init(); | |
93 | protoPrefix = pfx; | |
94 | } | |
95 | ||
96 | void | |
97 | HttpReply::clean() | |
98 | { | |
99 | httpBodyClean(&body); | |
100 | hdrCacheClean(); | |
101 | httpHeaderClean(&header); | |
102 | httpStatusLineClean(&sline); | |
cb69b4c7 | 103 | } |
104 | ||
4a56ee8d | 105 | #if OLD |
63259c34 | 106 | /* absorb: copy the contents of a new reply to the old one, destroy new one */ |
107 | void | |
06a5ae20 | 108 | HttpReply::absorb(HttpReply * new_rep) |
63259c34 | 109 | { |
06a5ae20 | 110 | assert(new_rep); |
111 | clean(); | |
112 | *this = *new_rep; | |
528b2c61 | 113 | new_rep->header.entries.clean(); |
63259c34 | 114 | /* cannot use Clean() on new reply now! */ |
06a5ae20 | 115 | new_rep->do_clean = false; |
07947ad8 | 116 | new_rep->cache_control = NULL; // helps with debugging |
06a5ae20 | 117 | delete new_rep; |
63259c34 | 118 | } |
119 | ||
4a56ee8d | 120 | #endif |
121 | ||
cb69b4c7 | 122 | void |
06a5ae20 | 123 | HttpReply::packHeadersInto(Packer * p) const |
cb69b4c7 | 124 | { |
06a5ae20 | 125 | httpStatusLinePackInto(&sline, p); |
126 | httpHeaderPackInto(&header, p); | |
cb69b4c7 | 127 | packerAppend(p, "\r\n", 2); |
528b2c61 | 128 | } |
129 | ||
130 | void | |
06a5ae20 | 131 | HttpReply::packInto(Packer * p) |
528b2c61 | 132 | { |
06a5ae20 | 133 | packHeadersInto(p); |
134 | httpBodyPackInto(&body, p); | |
cb69b4c7 | 135 | } |
136 | ||
06a5ae20 | 137 | /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */ |
032785bf | 138 | MemBuf * |
06a5ae20 | 139 | HttpReply::pack() |
cb69b4c7 | 140 | { |
032785bf | 141 | MemBuf *mb = new MemBuf; |
cb69b4c7 | 142 | Packer p; |
cb69b4c7 | 143 | |
2fe7eff9 | 144 | mb->init(); |
032785bf | 145 | packerToMemInit(&p, mb); |
06a5ae20 | 146 | packInto(&p); |
cb69b4c7 | 147 | packerClean(&p); |
148 | return mb; | |
149 | } | |
150 | ||
032785bf | 151 | MemBuf * |
450e0c10 | 152 | httpPackedReply(HttpVersion ver, http_status status, const char *ctype, |
62e76326 | 153 | int clen, time_t lmt, time_t expires) |
cb69b4c7 | 154 | { |
06a5ae20 | 155 | HttpReply *rep = new HttpReply; |
156 | rep->setHeaders(ver, status, ctype, NULL, clen, lmt, expires); | |
157 | MemBuf *mb = rep->pack(); | |
158 | delete rep; | |
cb69b4c7 | 159 | return mb; |
160 | } | |
161 | ||
528b2c61 | 162 | HttpReply * |
06a5ae20 | 163 | HttpReply::make304 () const |
cb69b4c7 | 164 | { |
62e76326 | 165 | static const http_hdr_type ImsEntries[] = {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER}; |
166 | ||
06a5ae20 | 167 | HttpReply *rv = new HttpReply; |
728da2ee | 168 | int t; |
de336bbe | 169 | HttpHeaderEntry *e; |
cb69b4c7 | 170 | |
528b2c61 | 171 | /* rv->content_length; */ |
06a5ae20 | 172 | rv->date = date; |
173 | rv->last_modified = last_modified; | |
174 | rv->expires = expires; | |
175 | rv->content_type = content_type; | |
528b2c61 | 176 | /* rv->cache_control */ |
177 | /* rv->content_range */ | |
178 | /* rv->keep_alive */ | |
450e0c10 | 179 | HttpVersion ver(1,0); |
528b2c61 | 180 | httpStatusLineSet(&rv->sline, ver, |
62e76326 | 181 | HTTP_NOT_MODIFIED, ""); |
182 | ||
de336bbe | 183 | for (t = 0; ImsEntries[t] != HDR_OTHER; ++t) |
06a5ae20 | 184 | if ((e = httpHeaderFindEntry(&header, ImsEntries[t]))) |
62e76326 | 185 | httpHeaderAddEntry(&rv->header, httpHeaderEntryClone(e)); |
186 | ||
528b2c61 | 187 | /* rv->body */ |
188 | return rv; | |
189 | } | |
190 | ||
032785bf | 191 | MemBuf * |
06a5ae20 | 192 | HttpReply::packed304Reply() |
528b2c61 | 193 | { |
194 | /* Not as efficient as skipping the header duplication, | |
195 | * but easier to maintain | |
196 | */ | |
06a5ae20 | 197 | HttpReply *temp = make304 (); |
198 | MemBuf *rv = temp->pack(); | |
199 | delete temp; | |
528b2c61 | 200 | return rv; |
cb69b4c7 | 201 | } |
202 | ||
203 | void | |
06a5ae20 | 204 | HttpReply::setHeaders(HttpVersion ver, http_status status, const char *reason, |
205 | const char *ctype, int clen, time_t lmt, time_t expires) | |
cb69b4c7 | 206 | { |
207 | HttpHeader *hdr; | |
06a5ae20 | 208 | httpStatusLineSet(&sline, ver, status, reason); |
209 | hdr = &header; | |
d3caee79 | 210 | httpHeaderPutStr(hdr, HDR_SERVER, visible_appname_string); |
d8b249ef | 211 | httpHeaderPutStr(hdr, HDR_MIME_VERSION, "1.0"); |
212 | httpHeaderPutTime(hdr, HDR_DATE, squid_curtime); | |
62e76326 | 213 | |
d8b249ef | 214 | if (ctype) { |
62e76326 | 215 | httpHeaderPutStr(hdr, HDR_CONTENT_TYPE, ctype); |
06a5ae20 | 216 | content_type = ctype; |
d8b249ef | 217 | } else |
06a5ae20 | 218 | content_type = String(); |
62e76326 | 219 | |
de336bbe | 220 | if (clen >= 0) |
62e76326 | 221 | httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, clen); |
222 | ||
cb69b4c7 | 223 | if (expires >= 0) |
62e76326 | 224 | httpHeaderPutTime(hdr, HDR_EXPIRES, expires); |
225 | ||
2ac76861 | 226 | if (lmt > 0) /* this used to be lmt != 0 @?@ */ |
62e76326 | 227 | httpHeaderPutTime(hdr, HDR_LAST_MODIFIED, lmt); |
228 | ||
06a5ae20 | 229 | date = squid_curtime; |
62e76326 | 230 | |
06a5ae20 | 231 | content_length = clen; |
62e76326 | 232 | |
06a5ae20 | 233 | expires = expires; |
62e76326 | 234 | |
06a5ae20 | 235 | last_modified = lmt; |
cb69b4c7 | 236 | } |
237 | ||
6d38ef86 | 238 | void |
06a5ae20 | 239 | HttpReply::redirect(http_status status, const char *loc) |
6d38ef86 | 240 | { |
241 | HttpHeader *hdr; | |
450e0c10 | 242 | HttpVersion ver(1,0); |
06a5ae20 | 243 | httpStatusLineSet(&sline, ver, status, httpStatusString(status)); |
244 | hdr = &header; | |
6d38ef86 | 245 | httpHeaderPutStr(hdr, HDR_SERVER, full_appname_string); |
246 | httpHeaderPutTime(hdr, HDR_DATE, squid_curtime); | |
247 | httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, 0); | |
248 | httpHeaderPutStr(hdr, HDR_LOCATION, loc); | |
06a5ae20 | 249 | date = squid_curtime; |
250 | content_length = 0; | |
6d38ef86 | 251 | } |
252 | ||
528b2c61 | 253 | /* compare the validators of two replies. |
254 | * 1 = they match | |
255 | * 0 = they do not match | |
256 | */ | |
257 | int | |
06a5ae20 | 258 | HttpReply::validatorsMatch(HttpReply const * otherRep) const |
62e76326 | 259 | { |
528b2c61 | 260 | String one,two; |
06a5ae20 | 261 | assert (otherRep); |
528b2c61 | 262 | /* Numbers first - easiest to check */ |
263 | /* Content-Length */ | |
264 | /* TODO: remove -1 bypass */ | |
62e76326 | 265 | |
06a5ae20 | 266 | if (content_length != otherRep->content_length |
267 | && content_length > -1 && | |
62e76326 | 268 | otherRep->content_length > -1) |
269 | return 0; | |
270 | ||
528b2c61 | 271 | /* ETag */ |
06a5ae20 | 272 | one = httpHeaderGetStrOrList(&header, HDR_ETAG); |
62e76326 | 273 | |
528b2c61 | 274 | two = httpHeaderGetStrOrList(&otherRep->header, HDR_ETAG); |
62e76326 | 275 | |
528b2c61 | 276 | if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) { |
62e76326 | 277 | one.clean(); |
278 | two.clean(); | |
279 | return 0; | |
528b2c61 | 280 | } |
62e76326 | 281 | |
06a5ae20 | 282 | if (last_modified != otherRep->last_modified) |
62e76326 | 283 | return 0; |
284 | ||
528b2c61 | 285 | /* MD5 */ |
06a5ae20 | 286 | one = httpHeaderGetStrOrList(&header, HDR_CONTENT_MD5); |
62e76326 | 287 | |
528b2c61 | 288 | two = httpHeaderGetStrOrList(&otherRep->header, HDR_CONTENT_MD5); |
62e76326 | 289 | |
6bd76974 | 290 | if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) { |
62e76326 | 291 | one.clean(); |
292 | two.clean(); | |
293 | return 0; | |
528b2c61 | 294 | } |
62e76326 | 295 | |
528b2c61 | 296 | return 1; |
297 | } | |
298 | ||
cb69b4c7 | 299 | void |
06a5ae20 | 300 | HttpReply::updateOnNotModified(HttpReply const * freshRep) |
cb69b4c7 | 301 | { |
07947ad8 | 302 | assert(freshRep); |
528b2c61 | 303 | /* Can not update modified headers that don't match! */ |
06a5ae20 | 304 | assert (validatorsMatch(freshRep)); |
d8b249ef | 305 | /* clean cache */ |
06a5ae20 | 306 | hdrCacheClean(); |
d8b249ef | 307 | /* update raw headers */ |
07947ad8 | 308 | httpHeaderUpdate(&header, &freshRep->header, |
62e76326 | 309 | (const HttpHeaderMask *) &Denied304HeadersMask); |
d8b249ef | 310 | /* init cache */ |
07947ad8 | 311 | hdrCacheInit(); |
cb69b4c7 | 312 | } |
313 | ||
d8b249ef | 314 | /* internal routines */ |
cb69b4c7 | 315 | |
06a5ae20 | 316 | time_t |
317 | HttpReply::hdrExpirationTime() | |
d20b1cd0 | 318 | { |
319 | /* The s-maxage and max-age directive takes priority over Expires */ | |
62e76326 | 320 | |
06a5ae20 | 321 | if (cache_control) { |
322 | if (date >= 0) { | |
323 | if (cache_control->s_maxage >= 0) | |
324 | return date + cache_control->s_maxage; | |
62e76326 | 325 | |
06a5ae20 | 326 | if (cache_control->max_age >= 0) |
327 | return date + cache_control->max_age; | |
62e76326 | 328 | } else { |
329 | /* | |
330 | * Conservatively handle the case when we have a max-age | |
331 | * header, but no Date for reference? | |
332 | */ | |
333 | ||
06a5ae20 | 334 | if (cache_control->s_maxage >= 0) |
62e76326 | 335 | return squid_curtime; |
336 | ||
06a5ae20 | 337 | if (cache_control->max_age >= 0) |
62e76326 | 338 | return squid_curtime; |
339 | } | |
d20b1cd0 | 340 | } |
62e76326 | 341 | |
f66a9ef4 | 342 | if (Config.onoff.vary_ignore_expire && |
06a5ae20 | 343 | httpHeaderHas(&header, HDR_VARY)) { |
344 | const time_t d = httpHeaderGetTime(&header, HDR_DATE); | |
345 | const time_t e = httpHeaderGetTime(&header, HDR_EXPIRES); | |
62e76326 | 346 | |
347 | if (d == e) | |
348 | return -1; | |
f66a9ef4 | 349 | } |
62e76326 | 350 | |
06a5ae20 | 351 | if (httpHeaderHas(&header, HDR_EXPIRES)) { |
352 | const time_t e = httpHeaderGetTime(&header, HDR_EXPIRES); | |
62e76326 | 353 | /* |
354 | * HTTP/1.0 says that robust implementations should consider | |
355 | * bad or malformed Expires header as equivalent to "expires | |
356 | * immediately." | |
357 | */ | |
358 | return e < 0 ? squid_curtime : e; | |
d20b1cd0 | 359 | } |
62e76326 | 360 | |
d20b1cd0 | 361 | return -1; |
362 | } | |
363 | ||
d8b249ef | 364 | /* sync this routine when you update HttpReply struct */ |
8596962e | 365 | void |
07947ad8 | 366 | HttpReply::hdrCacheInit() |
cb69b4c7 | 367 | { |
07947ad8 | 368 | HttpMsg::hdrCacheInit(); |
369 | ||
370 | content_length = httpHeaderGetInt(&header, HDR_CONTENT_LENGTH); | |
371 | date = httpHeaderGetTime(&header, HDR_DATE); | |
372 | last_modified = httpHeaderGetTime(&header, HDR_LAST_MODIFIED); | |
373 | surrogate_control = httpHeaderGetSc(&header); | |
374 | content_range = httpHeaderGetContRange(&header); | |
375 | keep_alive = httpMsgIsPersistent(sline.version, &header); | |
376 | const char *str = httpHeaderGetStr(&header, HDR_CONTENT_TYPE); | |
62e76326 | 377 | |
d8b249ef | 378 | if (str) |
07947ad8 | 379 | content_type.limitInit(str, strcspn(str, ";\t ")); |
d8b249ef | 380 | else |
07947ad8 | 381 | content_type = String(); |
62e76326 | 382 | |
d20b1cd0 | 383 | /* be sure to set expires after date and cache-control */ |
06a5ae20 | 384 | expires = hdrExpirationTime(); |
cb69b4c7 | 385 | } |
386 | ||
d8b249ef | 387 | /* sync this routine when you update HttpReply struct */ |
06a5ae20 | 388 | void |
389 | HttpReply::hdrCacheClean() | |
2ac76861 | 390 | { |
06a5ae20 | 391 | content_type.clean(); |
62e76326 | 392 | |
06a5ae20 | 393 | if (cache_control) { |
394 | httpHdrCcDestroy(cache_control); | |
395 | cache_control = NULL; | |
07947ad8 | 396 | } |
62e76326 | 397 | |
06a5ae20 | 398 | if (surrogate_control) { |
399 | httpHdrScDestroy(surrogate_control); | |
400 | surrogate_control = NULL; | |
07947ad8 | 401 | } |
43ae1d95 | 402 | |
06a5ae20 | 403 | if (content_range) { |
404 | httpHdrContRangeDestroy(content_range); | |
405 | content_range = NULL; | |
07947ad8 | 406 | } |
63259c34 | 407 | } |
cb69b4c7 | 408 | |
35282fbf | 409 | /* |
410 | * Returns the body size of a HTTP response | |
411 | */ | |
412 | int | |
06a5ae20 | 413 | HttpReply::bodySize(method_t method) const |
35282fbf | 414 | { |
06a5ae20 | 415 | if (sline.version.major < 1) |
1bda350e | 416 | return -1; |
417 | else if (METHOD_HEAD == method) | |
62e76326 | 418 | return 0; |
06a5ae20 | 419 | else if (sline.status == HTTP_OK) |
62e76326 | 420 | (void) 0; /* common case, continue */ |
06a5ae20 | 421 | else if (sline.status == HTTP_NO_CONTENT) |
62e76326 | 422 | return 0; |
06a5ae20 | 423 | else if (sline.status == HTTP_NOT_MODIFIED) |
62e76326 | 424 | return 0; |
06a5ae20 | 425 | else if (sline.status < HTTP_OK) |
62e76326 | 426 | return 0; |
427 | ||
06a5ae20 | 428 | return content_length; |
35282fbf | 429 | } |
8596962e | 430 | |
431 | bool HttpReply::sanityCheckStartLine(MemBuf *buf, http_status *error) | |
432 | { | |
433 | if (buf->contentSize() >= protoPrefix.size() && protoPrefix.cmp(buf->content(), protoPrefix.size()) != 0) { | |
434 | debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix.buf() << ") in '" << buf->content() << "'"); | |
435 | *error = HTTP_INVALID_HEADER; | |
436 | return false; | |
437 | } | |
438 | ||
439 | return true; | |
440 | } | |
441 | ||
442 | void HttpReply::packFirstLineInto(Packer *p, bool unused) const | |
443 | { | |
444 | httpStatusLinePackInto(&sline, p); | |
445 | } | |
429f7150 | 446 | |
447 | bool HttpReply::parseFirstLine(const char *blk_start, const char *blk_end) | |
448 | { | |
449 | return httpStatusLineParse(&sline, protoPrefix, blk_start, blk_end); | |
450 | } | |
5c09dcb8 | 451 | |
452 | /* | |
453 | * Indicate whether or not we would usually expect an entity-body | |
454 | * along with this response | |
455 | */ | |
456 | bool | |
457 | HttpReply::expectingBody(method_t req_method, ssize_t& theSize) const | |
458 | { | |
459 | bool expectBody = true; | |
460 | ||
461 | if (req_method == METHOD_HEAD) | |
462 | expectBody = false; | |
463 | else if (sline.status == HTTP_NO_CONTENT) | |
464 | expectBody = false; | |
465 | else if (sline.status == HTTP_NOT_MODIFIED) | |
466 | expectBody = false; | |
467 | else if (sline.status < HTTP_OK) | |
468 | expectBody = false; | |
469 | else | |
470 | expectBody = true; | |
471 | ||
472 | if (expectBody) { | |
473 | if (httpHeaderHasListMember(&header, HDR_TRANSFER_ENCODING, "chunked", ',')) | |
474 | theSize = -1; | |
475 | else if (content_length >= 0) | |
476 | theSize = content_length; | |
477 | else | |
478 | theSize = -1; | |
479 | } | |
480 | ||
481 | return expectBody; | |
482 | } |