]>
Commit | Line | Data |
---|---|---|
cb69b4c7 | 1 | /* |
bbc27441 | 2 | * Copyright (C) 1996-2014 The Squid Software Foundation and contributors |
e25c139f | 3 | * |
bbc27441 AJ |
4 | * Squid software is distributed under GPLv2+ license and includes |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
cb69b4c7 | 7 | */ |
8 | ||
bbc27441 AJ |
9 | /* DEBUG: section 58 HTTP Reply (Response) */ |
10 | ||
582c2af2 | 11 | #include "squid.h" |
1328cfb7 | 12 | #include "acl/AclSizeLimit.h" |
582c2af2 FC |
13 | #include "acl/FilledChecklist.h" |
14 | #include "globals.h" | |
0521f8be | 15 | #include "HttpBody.h" |
7ebe76de | 16 | #include "HttpHdrCc.h" |
582c2af2 | 17 | #include "HttpHdrContRange.h" |
25b6a907 | 18 | #include "HttpHdrSc.h" |
582c2af2 | 19 | #include "HttpReply.h" |
0667cbfb | 20 | #include "HttpRequest.h" |
0eb49b6d | 21 | #include "MemBuf.h" |
4d5904f7 | 22 | #include "SquidConfig.h" |
582c2af2 FC |
23 | #include "SquidTime.h" |
24 | #include "Store.h" | |
28204b3b | 25 | #include "StrList.h" |
cb69b4c7 | 26 | |
27 | /* local constants */ | |
28 | ||
1d7ab0f4 | 29 | /* If we receive a 304 from the origin during a cache revalidation, we must |
30 | * update the headers of the existing entry. Specifically, we need to update all | |
31 | * end-to-end headers and not any hop-by-hop headers (rfc2616 13.5.3). | |
32 | * | |
33 | * This is not the whole story though: since it is possible for a faulty/malicious | |
34 | * origin server to set headers it should not in a 304, we must explicitly ignore | |
35 | * these too. Specifically all entity-headers except those permitted in a 304 | |
36 | * (rfc2616 10.3.5) must be ignored. | |
26ac0430 | 37 | * |
1d7ab0f4 | 38 | * The list of headers we don't update is made up of: |
39 | * all hop-by-hop headers | |
40 | * all entity-headers except Expires and Content-Location | |
41 | */ | |
2246b732 | 42 | static HttpHeaderMask Denied304HeadersMask; |
26ac0430 AJ |
43 | static http_hdr_type Denied304HeadersArr[] = { |
44 | // hop-by-hop headers | |
45 | HDR_CONNECTION, HDR_KEEP_ALIVE, HDR_PROXY_AUTHENTICATE, HDR_PROXY_AUTHORIZATION, | |
a1651bad | 46 | HDR_TE, HDR_TRAILER, HDR_TRANSFER_ENCODING, HDR_UPGRADE, |
26ac0430 AJ |
47 | // entity headers |
48 | HDR_ALLOW, HDR_CONTENT_ENCODING, HDR_CONTENT_LANGUAGE, HDR_CONTENT_LENGTH, | |
49 | HDR_CONTENT_MD5, HDR_CONTENT_RANGE, HDR_CONTENT_TYPE, HDR_LAST_MODIFIED | |
50 | }; | |
2246b732 | 51 | |
2246b732 | 52 | /* module initialization */ |
53 | void | |
9bc73deb | 54 | httpReplyInitModule(void) |
2246b732 | 55 | { |
955394ce | 56 | assert(Http::scNone == 0); // HttpReply::parse() interface assumes that |
97474590 | 57 | httpHeaderMaskInit(&Denied304HeadersMask, 0); |
8abf232c | 58 | httpHeaderCalcMask(&Denied304HeadersMask, Denied304HeadersArr, countof(Denied304HeadersArr)); |
2246b732 | 59 | } |
60 | ||
26ac0430 | 61 | HttpReply::HttpReply() : HttpMsg(hoReply), date (0), last_modified (0), |
f53969cc SM |
62 | expires (0), surrogate_control (NULL), content_range (NULL), keep_alive (0), |
63 | protoPrefix("HTTP/"), bodySizeMax(-2) | |
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 | hdrCacheInit(); |
9b769c67 | 78 | sline.init(); |
c99de607 | 79 | pstate = psReadyToParseStartLine; |
06a5ae20 | 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 | |
30abd221 | 90 | const String pfx = protoPrefix; |
06a5ae20 | 91 | clean(); |
92 | init(); | |
93 | protoPrefix = pfx; | |
94 | } | |
95 | ||
96 | void | |
97 | HttpReply::clean() | |
98 | { | |
26ac0430 | 99 | // we used to assert that the pipe is NULL, but now the message only |
5f8252d2 | 100 | // points to a pipe that is owned and initiated by another object. |
101 | body_pipe = NULL; | |
102 | ||
0521f8be | 103 | body.clear(); |
06a5ae20 | 104 | hdrCacheClean(); |
519e0948 | 105 | header.clean(); |
9b769c67 | 106 | sline.clean(); |
0667cbfb | 107 | bodySizeMax = -2; // hack: make calculatedBodySizeMax() false |
cb69b4c7 | 108 | } |
109 | ||
cb69b4c7 | 110 | void |
06a5ae20 | 111 | HttpReply::packHeadersInto(Packer * p) const |
cb69b4c7 | 112 | { |
9b769c67 | 113 | sline.packInto(p); |
a9925b40 | 114 | header.packInto(p); |
cb69b4c7 | 115 | packerAppend(p, "\r\n", 2); |
528b2c61 | 116 | } |
117 | ||
118 | void | |
06a5ae20 | 119 | HttpReply::packInto(Packer * p) |
528b2c61 | 120 | { |
06a5ae20 | 121 | packHeadersInto(p); |
0521f8be | 122 | body.packInto(p); |
cb69b4c7 | 123 | } |
124 | ||
06a5ae20 | 125 | /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */ |
032785bf | 126 | MemBuf * |
06a5ae20 | 127 | HttpReply::pack() |
cb69b4c7 | 128 | { |
032785bf | 129 | MemBuf *mb = new MemBuf; |
cb69b4c7 | 130 | Packer p; |
cb69b4c7 | 131 | |
2fe7eff9 | 132 | mb->init(); |
032785bf | 133 | packerToMemInit(&p, mb); |
06a5ae20 | 134 | packInto(&p); |
cb69b4c7 | 135 | packerClean(&p); |
136 | return mb; | |
137 | } | |
138 | ||
528b2c61 | 139 | HttpReply * |
11992b6f | 140 | HttpReply::make304() const |
cb69b4c7 | 141 | { |
62e76326 | 142 | static const http_hdr_type ImsEntries[] = {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER}; |
143 | ||
06a5ae20 | 144 | HttpReply *rv = new HttpReply; |
728da2ee | 145 | int t; |
de336bbe | 146 | HttpHeaderEntry *e; |
cb69b4c7 | 147 | |
528b2c61 | 148 | /* rv->content_length; */ |
06a5ae20 | 149 | rv->date = date; |
150 | rv->last_modified = last_modified; | |
151 | rv->expires = expires; | |
152 | rv->content_type = content_type; | |
528b2c61 | 153 | /* rv->cache_control */ |
154 | /* rv->content_range */ | |
155 | /* rv->keep_alive */ | |
2592bc70 | 156 | rv->sline.set(Http::ProtocolVersion(), Http::scNotModified, NULL); |
62e76326 | 157 | |
de336bbe | 158 | for (t = 0; ImsEntries[t] != HDR_OTHER; ++t) |
a9925b40 | 159 | if ((e = header.findEntry(ImsEntries[t]))) |
eede25e7 | 160 | rv->header.addEntry(e->clone()); |
62e76326 | 161 | |
528b2c61 | 162 | /* rv->body */ |
163 | return rv; | |
164 | } | |
165 | ||
032785bf | 166 | MemBuf * |
06a5ae20 | 167 | HttpReply::packed304Reply() |
528b2c61 | 168 | { |
169 | /* Not as efficient as skipping the header duplication, | |
170 | * but easier to maintain | |
171 | */ | |
871c031f | 172 | HttpReply *temp = make304(); |
06a5ae20 | 173 | MemBuf *rv = temp->pack(); |
174 | delete temp; | |
528b2c61 | 175 | return rv; |
cb69b4c7 | 176 | } |
177 | ||
178 | void | |
955394ce | 179 | HttpReply::setHeaders(Http::StatusCode status, const char *reason, |
350e2aec | 180 | const char *ctype, int64_t clen, time_t lmt, time_t expiresTime) |
cb69b4c7 | 181 | { |
182 | HttpHeader *hdr; | |
2592bc70 | 183 | sline.set(Http::ProtocolVersion(), status, reason); |
06a5ae20 | 184 | hdr = &header; |
a9925b40 | 185 | hdr->putStr(HDR_SERVER, visible_appname_string); |
186 | hdr->putStr(HDR_MIME_VERSION, "1.0"); | |
187 | hdr->putTime(HDR_DATE, squid_curtime); | |
62e76326 | 188 | |
d8b249ef | 189 | if (ctype) { |
a9925b40 | 190 | hdr->putStr(HDR_CONTENT_TYPE, ctype); |
06a5ae20 | 191 | content_type = ctype; |
d8b249ef | 192 | } else |
30abd221 | 193 | content_type = String(); |
62e76326 | 194 | |
de336bbe | 195 | if (clen >= 0) |
47f6e231 | 196 | hdr->putInt64(HDR_CONTENT_LENGTH, clen); |
62e76326 | 197 | |
350e2aec FC |
198 | if (expiresTime >= 0) |
199 | hdr->putTime(HDR_EXPIRES, expiresTime); | |
62e76326 | 200 | |
f53969cc | 201 | if (lmt > 0) /* this used to be lmt != 0 @?@ */ |
a9925b40 | 202 | hdr->putTime(HDR_LAST_MODIFIED, lmt); |
62e76326 | 203 | |
06a5ae20 | 204 | date = squid_curtime; |
62e76326 | 205 | |
06a5ae20 | 206 | content_length = clen; |
62e76326 | 207 | |
350e2aec | 208 | expires = expiresTime; |
62e76326 | 209 | |
06a5ae20 | 210 | last_modified = lmt; |
cb69b4c7 | 211 | } |
212 | ||
6d38ef86 | 213 | void |
955394ce | 214 | HttpReply::redirect(Http::StatusCode status, const char *loc) |
6d38ef86 | 215 | { |
216 | HttpHeader *hdr; | |
2592bc70 | 217 | sline.set(Http::ProtocolVersion(), status, NULL); |
06a5ae20 | 218 | hdr = &header; |
7dbca7a4 | 219 | hdr->putStr(HDR_SERVER, APP_FULLNAME); |
a9925b40 | 220 | hdr->putTime(HDR_DATE, squid_curtime); |
47f6e231 | 221 | hdr->putInt64(HDR_CONTENT_LENGTH, 0); |
a9925b40 | 222 | hdr->putStr(HDR_LOCATION, loc); |
06a5ae20 | 223 | date = squid_curtime; |
224 | content_length = 0; | |
6d38ef86 | 225 | } |
226 | ||
528b2c61 | 227 | /* compare the validators of two replies. |
228 | * 1 = they match | |
229 | * 0 = they do not match | |
230 | */ | |
231 | int | |
06a5ae20 | 232 | HttpReply::validatorsMatch(HttpReply const * otherRep) const |
62e76326 | 233 | { |
30abd221 | 234 | String one,two; |
06a5ae20 | 235 | assert (otherRep); |
528b2c61 | 236 | /* Numbers first - easiest to check */ |
237 | /* Content-Length */ | |
238 | /* TODO: remove -1 bypass */ | |
62e76326 | 239 | |
06a5ae20 | 240 | if (content_length != otherRep->content_length |
241 | && content_length > -1 && | |
62e76326 | 242 | otherRep->content_length > -1) |
243 | return 0; | |
244 | ||
528b2c61 | 245 | /* ETag */ |
a9925b40 | 246 | one = header.getStrOrList(HDR_ETAG); |
62e76326 | 247 | |
a9925b40 | 248 | two = otherRep->header.getStrOrList(HDR_ETAG); |
62e76326 | 249 | |
a1377698 | 250 | if (one.size()==0 || two.size()==0 || one.caseCmp(two)!=0 ) { |
30abd221 | 251 | one.clean(); |
252 | two.clean(); | |
62e76326 | 253 | return 0; |
528b2c61 | 254 | } |
62e76326 | 255 | |
06a5ae20 | 256 | if (last_modified != otherRep->last_modified) |
62e76326 | 257 | return 0; |
258 | ||
528b2c61 | 259 | /* MD5 */ |
a9925b40 | 260 | one = header.getStrOrList(HDR_CONTENT_MD5); |
62e76326 | 261 | |
a9925b40 | 262 | two = otherRep->header.getStrOrList(HDR_CONTENT_MD5); |
62e76326 | 263 | |
a1377698 | 264 | if (one.size()==0 || two.size()==0 || one.caseCmp(two)!=0 ) { |
30abd221 | 265 | one.clean(); |
266 | two.clean(); | |
62e76326 | 267 | return 0; |
528b2c61 | 268 | } |
62e76326 | 269 | |
528b2c61 | 270 | return 1; |
271 | } | |
272 | ||
cb69b4c7 | 273 | void |
06a5ae20 | 274 | HttpReply::updateOnNotModified(HttpReply const * freshRep) |
cb69b4c7 | 275 | { |
07947ad8 | 276 | assert(freshRep); |
1d7ab0f4 | 277 | |
d8b249ef | 278 | /* clean cache */ |
06a5ae20 | 279 | hdrCacheClean(); |
d8b249ef | 280 | /* update raw headers */ |
a9925b40 | 281 | header.update(&freshRep->header, |
282 | (const HttpHeaderMask *) &Denied304HeadersMask); | |
1d7ab0f4 | 283 | |
394499bd | 284 | header.compact(); |
d8b249ef | 285 | /* init cache */ |
07947ad8 | 286 | hdrCacheInit(); |
cb69b4c7 | 287 | } |
288 | ||
d8b249ef | 289 | /* internal routines */ |
cb69b4c7 | 290 | |
06a5ae20 | 291 | time_t |
292 | HttpReply::hdrExpirationTime() | |
d20b1cd0 | 293 | { |
294 | /* The s-maxage and max-age directive takes priority over Expires */ | |
62e76326 | 295 | |
06a5ae20 | 296 | if (cache_control) { |
297 | if (date >= 0) { | |
d74ad83f | 298 | if (cache_control->hasSMaxAge()) |
cf7c2e94 | 299 | return date + cache_control->sMaxAge(); |
62e76326 | 300 | |
d74ad83f | 301 | if (cache_control->hasMaxAge()) |
cf7c2e94 | 302 | return date + cache_control->maxAge(); |
62e76326 | 303 | } else { |
304 | /* | |
305 | * Conservatively handle the case when we have a max-age | |
306 | * header, but no Date for reference? | |
307 | */ | |
308 | ||
d74ad83f | 309 | if (cache_control->hasSMaxAge()) |
62e76326 | 310 | return squid_curtime; |
311 | ||
d74ad83f | 312 | if (cache_control->hasMaxAge()) |
62e76326 | 313 | return squid_curtime; |
314 | } | |
d20b1cd0 | 315 | } |
62e76326 | 316 | |
f66a9ef4 | 317 | if (Config.onoff.vary_ignore_expire && |
a9925b40 | 318 | header.has(HDR_VARY)) { |
319 | const time_t d = header.getTime(HDR_DATE); | |
320 | const time_t e = header.getTime(HDR_EXPIRES); | |
62e76326 | 321 | |
322 | if (d == e) | |
323 | return -1; | |
f66a9ef4 | 324 | } |
62e76326 | 325 | |
a9925b40 | 326 | if (header.has(HDR_EXPIRES)) { |
327 | const time_t e = header.getTime(HDR_EXPIRES); | |
62e76326 | 328 | /* |
329 | * HTTP/1.0 says that robust implementations should consider | |
330 | * bad or malformed Expires header as equivalent to "expires | |
331 | * immediately." | |
332 | */ | |
333 | return e < 0 ? squid_curtime : e; | |
d20b1cd0 | 334 | } |
62e76326 | 335 | |
d20b1cd0 | 336 | return -1; |
337 | } | |
338 | ||
d8b249ef | 339 | /* sync this routine when you update HttpReply struct */ |
8596962e | 340 | void |
07947ad8 | 341 | HttpReply::hdrCacheInit() |
cb69b4c7 | 342 | { |
07947ad8 | 343 | HttpMsg::hdrCacheInit(); |
344 | ||
4a1acc56 | 345 | http_ver = sline.version; |
47f6e231 | 346 | content_length = header.getInt64(HDR_CONTENT_LENGTH); |
a9925b40 | 347 | date = header.getTime(HDR_DATE); |
348 | last_modified = header.getTime(HDR_LAST_MODIFIED); | |
349 | surrogate_control = header.getSc(); | |
350 | content_range = header.getContRange(); | |
4a1acc56 | 351 | keep_alive = persistent() ? 1 : 0; |
a9925b40 | 352 | const char *str = header.getStr(HDR_CONTENT_TYPE); |
62e76326 | 353 | |
d8b249ef | 354 | if (str) |
07947ad8 | 355 | content_type.limitInit(str, strcspn(str, ";\t ")); |
d8b249ef | 356 | else |
30abd221 | 357 | content_type = String(); |
62e76326 | 358 | |
d20b1cd0 | 359 | /* be sure to set expires after date and cache-control */ |
06a5ae20 | 360 | expires = hdrExpirationTime(); |
cb69b4c7 | 361 | } |
362 | ||
d8b249ef | 363 | /* sync this routine when you update HttpReply struct */ |
06a5ae20 | 364 | void |
365 | HttpReply::hdrCacheClean() | |
2ac76861 | 366 | { |
30abd221 | 367 | content_type.clean(); |
62e76326 | 368 | |
06a5ae20 | 369 | if (cache_control) { |
3d7782c1 | 370 | delete cache_control; |
06a5ae20 | 371 | cache_control = NULL; |
07947ad8 | 372 | } |
62e76326 | 373 | |
06a5ae20 | 374 | if (surrogate_control) { |
45a58345 | 375 | delete surrogate_control; |
06a5ae20 | 376 | surrogate_control = NULL; |
07947ad8 | 377 | } |
43ae1d95 | 378 | |
06a5ae20 | 379 | if (content_range) { |
380 | httpHdrContRangeDestroy(content_range); | |
381 | content_range = NULL; | |
07947ad8 | 382 | } |
63259c34 | 383 | } |
cb69b4c7 | 384 | |
35282fbf | 385 | /* |
386 | * Returns the body size of a HTTP response | |
387 | */ | |
47f6e231 | 388 | int64_t |
60745f24 | 389 | HttpReply::bodySize(const HttpRequestMethod& method) const |
35282fbf | 390 | { |
06a5ae20 | 391 | if (sline.version.major < 1) |
1bda350e | 392 | return -1; |
c2a7cefd | 393 | else if (method.id() == Http::METHOD_HEAD) |
62e76326 | 394 | return 0; |
9b769c67 | 395 | else if (sline.status() == Http::scOkay) |
f53969cc | 396 | (void) 0; /* common case, continue */ |
9b769c67 | 397 | else if (sline.status() == Http::scNoContent) |
62e76326 | 398 | return 0; |
9b769c67 | 399 | else if (sline.status() == Http::scNotModified) |
62e76326 | 400 | return 0; |
9b769c67 | 401 | else if (sline.status() < Http::scOkay) |
62e76326 | 402 | return 0; |
403 | ||
06a5ae20 | 404 | return content_length; |
35282fbf | 405 | } |
8596962e | 406 | |
96ee497f AJ |
407 | /** |
408 | * Checks the first line of an HTTP Reply is valid. | |
409 | * currently only checks "HTTP/" exists. | |
410 | * | |
411 | * NP: not all error cases are detected yet. Some are left for detection later in parse. | |
412 | */ | |
413 | bool | |
955394ce | 414 | HttpReply::sanityCheckStartLine(MemBuf *buf, const size_t hdr_len, Http::StatusCode *error) |
8596962e | 415 | { |
96ee497f AJ |
416 | // hack warning: using psize instead of size here due to type mismatches with MemBuf. |
417 | ||
418 | // content is long enough to possibly hold a reply | |
419 | // 4 being magic size of a 3-digit number plus space delimiter | |
420 | if ( buf->contentSize() < (protoPrefix.psize() + 4) ) { | |
0246f6b8 AJ |
421 | if (hdr_len > 0) { |
422 | debugs(58, 3, HERE << "Too small reply header (" << hdr_len << " bytes)"); | |
955394ce | 423 | *error = Http::scInvalidHeader; |
0246f6b8 | 424 | } |
96ee497f AJ |
425 | return false; |
426 | } | |
427 | ||
e77d7ef0 | 428 | int pos; |
96ee497f | 429 | // catch missing or mismatched protocol identifier |
e77d7ef0 AJ |
430 | // allow special-case for ICY protocol (non-HTTP identifier) in response to faked HTTP request. |
431 | if (strncmp(buf->content(), "ICY", 3) == 0) { | |
432 | protoPrefix = "ICY"; | |
433 | pos = protoPrefix.psize(); | |
dd20bfd3 | 434 | } else { |
8596962e | 435 | |
e77d7ef0 AJ |
436 | if (protoPrefix.cmp(buf->content(), protoPrefix.size()) != 0) { |
437 | debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix << ") in '" << buf->content() << "'"); | |
955394ce | 438 | *error = Http::scInvalidHeader; |
e77d7ef0 AJ |
439 | return false; |
440 | } | |
96ee497f | 441 | |
e77d7ef0 AJ |
442 | // catch missing or negative status value (negative '-' is not a digit) |
443 | pos = protoPrefix.psize(); | |
dd20bfd3 | 444 | |
e77d7ef0 AJ |
445 | // skip arbitrary number of digits and a dot in the verion portion |
446 | while ( pos <= buf->contentSize() && (*(buf->content()+pos) == '.' || xisdigit(*(buf->content()+pos)) ) ) ++pos; | |
96ee497f | 447 | |
e77d7ef0 AJ |
448 | // catch missing version info |
449 | if (pos == protoPrefix.psize()) { | |
450 | debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol version numbers (ie. " << protoPrefix << "/1.0) in '" << buf->content() << "'"); | |
955394ce | 451 | *error = Http::scInvalidHeader; |
e77d7ef0 AJ |
452 | return false; |
453 | } | |
96ee497f AJ |
454 | } |
455 | ||
456 | // skip arbitrary number of spaces... | |
457 | while (pos <= buf->contentSize() && (char)*(buf->content()+pos) == ' ') ++pos; | |
458 | ||
6934ca51 | 459 | if (pos < buf->contentSize() && !xisdigit(*(buf->content()+pos))) { |
96ee497f | 460 | debugs(58, 3, "HttpReply::sanityCheckStartLine: missing or invalid status number in '" << buf->content() << "'"); |
955394ce | 461 | *error = Http::scInvalidHeader; |
96ee497f AJ |
462 | return false; |
463 | } | |
464 | ||
8596962e | 465 | return true; |
466 | } | |
467 | ||
9b769c67 AJ |
468 | bool |
469 | HttpReply::parseFirstLine(const char *blk_start, const char *blk_end) | |
429f7150 | 470 | { |
9b769c67 | 471 | return sline.parse(protoPrefix, blk_start, blk_end); |
429f7150 | 472 | } |
5c09dcb8 | 473 | |
fb525683 | 474 | /* handy: resets and returns -1 */ |
475 | int | |
476 | HttpReply::httpMsgParseError() | |
477 | { | |
478 | int result(HttpMsg::httpMsgParseError()); | |
479 | /* indicate an error in the status line */ | |
2592bc70 | 480 | sline.set(Http::ProtocolVersion(), Http::scInvalidHeader); |
fb525683 | 481 | return result; |
482 | } | |
483 | ||
5c09dcb8 | 484 | /* |
485 | * Indicate whether or not we would usually expect an entity-body | |
486 | * along with this response | |
487 | */ | |
488 | bool | |
60745f24 | 489 | HttpReply::expectingBody(const HttpRequestMethod& req_method, int64_t& theSize) const |
5c09dcb8 | 490 | { |
491 | bool expectBody = true; | |
492 | ||
c2a7cefd | 493 | if (req_method == Http::METHOD_HEAD) |
5c09dcb8 | 494 | expectBody = false; |
9b769c67 | 495 | else if (sline.status() == Http::scNoContent) |
5c09dcb8 | 496 | expectBody = false; |
9b769c67 | 497 | else if (sline.status() == Http::scNotModified) |
5c09dcb8 | 498 | expectBody = false; |
9b769c67 | 499 | else if (sline.status() < Http::scOkay) |
5c09dcb8 | 500 | expectBody = false; |
501 | else | |
502 | expectBody = true; | |
503 | ||
504 | if (expectBody) { | |
c3d0ba0c | 505 | if (header.chunked()) |
5c09dcb8 | 506 | theSize = -1; |
507 | else if (content_length >= 0) | |
508 | theSize = content_length; | |
509 | else | |
510 | theSize = -1; | |
511 | } | |
512 | ||
513 | return expectBody; | |
514 | } | |
0667cbfb | 515 | |
516 | bool | |
517 | HttpReply::receivedBodyTooLarge(HttpRequest& request, int64_t receivedSize) | |
518 | { | |
519 | calcMaxBodySize(request); | |
520 | debugs(58, 3, HERE << receivedSize << " >? " << bodySizeMax); | |
521 | return bodySizeMax >= 0 && receivedSize > bodySizeMax; | |
522 | } | |
523 | ||
524 | bool | |
525 | HttpReply::expectedBodyTooLarge(HttpRequest& request) | |
526 | { | |
527 | calcMaxBodySize(request); | |
528 | debugs(58, 7, HERE << "bodySizeMax=" << bodySizeMax); | |
529 | ||
530 | if (bodySizeMax < 0) // no body size limit | |
531 | return false; | |
532 | ||
533 | int64_t expectedSize = -1; | |
534 | if (!expectingBody(request.method, expectedSize)) | |
535 | return false; | |
26ac0430 | 536 | |
0667cbfb | 537 | debugs(58, 6, HERE << expectedSize << " >? " << bodySizeMax); |
538 | ||
539 | if (expectedSize < 0) // expecting body of an unknown length | |
540 | return false; | |
541 | ||
542 | return expectedSize > bodySizeMax; | |
543 | } | |
544 | ||
545 | void | |
b248c2a3 | 546 | HttpReply::calcMaxBodySize(HttpRequest& request) const |
0667cbfb | 547 | { |
548 | // hack: -2 is used as "we have not calculated max body size yet" state | |
549 | if (bodySizeMax != -2) // already tried | |
550 | return; | |
551 | bodySizeMax = -1; | |
552 | ||
4194f58d AJ |
553 | // short-circuit ACL testing if there are none configured |
554 | if (!Config.ReplyBodySize) | |
555 | return; | |
556 | ||
c0941a6a | 557 | ACLFilledChecklist ch(NULL, &request, NULL); |
b248c2a3 AJ |
558 | // XXX: cont-cast becomes irrelevant when checklist is HttpReply::Pointer |
559 | ch.reply = const_cast<HttpReply *>(this); | |
560 | HTTPMSGLOCK(ch.reply); | |
1328cfb7 | 561 | for (AclSizeLimit *l = Config.ReplyBodySize; l; l = l -> next) { |
b50e327b | 562 | /* if there is no ACL list or if the ACLs listed match use this size value */ |
2efeb0b7 | 563 | if (!l->aclList || ch.fastCheck(l->aclList) == ACCESS_ALLOWED) { |
0667cbfb | 564 | debugs(58, 4, HERE << "bodySizeMax=" << bodySizeMax); |
565 | bodySizeMax = l->size; // may be -1 | |
566 | break; | |
567 | } | |
568 | } | |
569 | } | |
da33c835 | 570 | |
fa0e6114 | 571 | // XXX: check that this is sufficient for eCAP cloning |
da33c835 HN |
572 | HttpReply * |
573 | HttpReply::clone() const | |
574 | { | |
575 | HttpReply *rep = new HttpReply(); | |
66363092 | 576 | rep->sline = sline; // used in hdrCacheInit() call below |
da33c835 HN |
577 | rep->header.append(&header); |
578 | rep->hdrCacheInit(); | |
579 | rep->hdr_sz = hdr_sz; | |
f230832f HN |
580 | rep->http_ver = http_ver; |
581 | rep->pstate = pstate; | |
fa0e6114 AR |
582 | rep->body_pipe = body_pipe; |
583 | ||
66363092 | 584 | // keep_alive is handled in hdrCacheInit() |
da33c835 HN |
585 | return rep; |
586 | } | |
d67acb4e | 587 | |
d67acb4e AJ |
588 | bool HttpReply::inheritProperties(const HttpMsg *aMsg) |
589 | { | |
590 | const HttpReply *aRep = dynamic_cast<const HttpReply*>(aMsg); | |
26ac0430 AJ |
591 | if (!aRep) |
592 | return false; | |
d67acb4e AJ |
593 | keep_alive = aRep->keep_alive; |
594 | return true; | |
595 | } | |
c679653d AR |
596 | |
597 | void HttpReply::removeStaleWarnings() | |
598 | { | |
599 | String warning; | |
600 | if (header.getList(HDR_WARNING, &warning)) { | |
601 | const String newWarning = removeStaleWarningValues(warning); | |
602 | if (warning.size() && warning.size() == newWarning.size()) | |
603 | return; // some warnings are there and none changed | |
604 | header.delById(HDR_WARNING); | |
605 | if (newWarning.size()) { // some warnings left | |
606 | HttpHeaderEntry *const e = | |
607 | new HttpHeaderEntry(HDR_WARNING, NULL, newWarning.termedBuf()); | |
608 | header.addEntry(e); | |
609 | } | |
610 | } | |
611 | } | |
612 | ||
613 | /** | |
614 | * Remove warning-values with warn-date different from Date value from | |
615 | * a single header entry. Returns a string with all valid warning-values. | |
616 | */ | |
617 | String HttpReply::removeStaleWarningValues(const String &value) | |
618 | { | |
619 | String newValue; | |
620 | const char *item = 0; | |
621 | int len = 0; | |
622 | const char *pos = 0; | |
623 | while (strListGetItem(&value, ',', &item, &len, &pos)) { | |
624 | bool keep = true; | |
625 | // Does warning-value have warn-date (which contains quoted date)? | |
626 | // We scan backwards, looking for two quoted strings. | |
627 | // warning-value = warn-code SP warn-agent SP warn-text [SP warn-date] | |
628 | const char *p = item + len - 1; | |
629 | ||
630 | while (p >= item && xisspace(*p)) --p; // skip whitespace | |
631 | ||
632 | // warning-value MUST end with quote | |
633 | if (p >= item && *p == '"') { | |
634 | const char *const warnDateEnd = p; | |
635 | --p; | |
636 | while (p >= item && *p != '"') --p; // find the next quote | |
637 | ||
638 | const char *warnDateBeg = p + 1; | |
639 | --p; | |
640 | while (p >= item && xisspace(*p)) --p; // skip whitespace | |
641 | ||
642 | if (p >= item && *p == '"' && warnDateBeg - p > 2) { | |
643 | // found warn-text | |
644 | String warnDate; | |
645 | warnDate.append(warnDateBeg, warnDateEnd - warnDateBeg); | |
646 | const time_t time = parse_rfc1123(warnDate.termedBuf()); | |
647 | keep = (time > 0 && time == date); // keep valid and matching date | |
648 | } | |
649 | } | |
650 | ||
651 | if (keep) { | |
652 | if (newValue.size()) | |
653 | newValue.append(", "); | |
654 | newValue.append(item, len); | |
655 | } | |
656 | } | |
657 | ||
658 | return newValue; | |
659 | } | |
f53969cc | 660 |