]> git.ipfire.org Git - thirdparty/squid.git/blob - src/HttpReply.cc
Merged from trunk.
[thirdparty/squid.git] / src / HttpReply.cc
1
2 /*
3 * $Id: HttpReply.cc,v 1.100 2008/02/08 18:27:59 rousskov Exp $
4 *
5 * DEBUG: section 58 HTTP Reply (Response)
6 * AUTHOR: Alex Rousskov
7 *
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
10 *
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.
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
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
33 *
34 */
35
36 #include "squid.h"
37 #include "SquidTime.h"
38 #include "Store.h"
39 #include "HttpReply.h"
40 #include "HttpHdrContRange.h"
41 #include "HttpHdrSc.h"
42 #include "ACLChecklist.h"
43 #include "HttpRequest.h"
44 #include "MemBuf.h"
45
46 /* local constants */
47
48 /* If we receive a 304 from the origin during a cache revalidation, we must
49 * update the headers of the existing entry. Specifically, we need to update all
50 * end-to-end headers and not any hop-by-hop headers (rfc2616 13.5.3).
51 *
52 * This is not the whole story though: since it is possible for a faulty/malicious
53 * origin server to set headers it should not in a 304, we must explicitly ignore
54 * these too. Specifically all entity-headers except those permitted in a 304
55 * (rfc2616 10.3.5) must be ignored.
56 *
57 * The list of headers we don't update is made up of:
58 * all hop-by-hop headers
59 * all entity-headers except Expires and Content-Location
60 */
61 static HttpHeaderMask Denied304HeadersMask;
62 static http_hdr_type Denied304HeadersArr[] =
63 {
64 // hop-by-hop headers
65 HDR_CONNECTION, HDR_KEEP_ALIVE, HDR_PROXY_AUTHENTICATE, HDR_PROXY_AUTHORIZATION,
66 HDR_TE, HDR_TRAILERS, HDR_TRANSFER_ENCODING, HDR_UPGRADE,
67 // entity headers
68 HDR_ALLOW, HDR_CONTENT_ENCODING, HDR_CONTENT_LANGUAGE, HDR_CONTENT_LENGTH,
69 HDR_CONTENT_MD5, HDR_CONTENT_RANGE, HDR_CONTENT_TYPE, HDR_LAST_MODIFIED
70 };
71
72 /* module initialization */
73 void
74 httpReplyInitModule(void)
75 {
76 assert(HTTP_STATUS_NONE == 0); // HttpReply::parse() interface assumes that
77 httpHeaderMaskInit(&Denied304HeadersMask, 0);
78 httpHeaderCalcMask(&Denied304HeadersMask, Denied304HeadersArr, countof(Denied304HeadersArr));
79 }
80
81 HttpReply::HttpReply() : HttpMsg(hoReply), date (0), last_modified (0),
82 expires (0), surrogate_control (NULL), content_range (NULL), keep_alive (0),
83 protoPrefix("HTTP/"), bodySizeMax(-2)
84 {
85 init();
86 }
87
88 HttpReply::~HttpReply()
89 {
90 if (do_clean)
91 clean();
92 }
93
94 void
95 HttpReply::init()
96 {
97 httpBodyInit(&body);
98 hdrCacheInit();
99 httpStatusLineInit(&sline);
100 pstate = psReadyToParseStartLine;
101 do_clean = true;
102 }
103
104 void HttpReply::reset()
105 {
106
107 // reset should not reset the protocol; could have made protoPrefix a
108 // virtual function instead, but it is not clear whether virtual methods
109 // are allowed with MEMPROXY_CLASS() and whether some cbdata void*
110 // conversions are not going to kill virtual tables
111 const String pfx = protoPrefix;
112 clean();
113 init();
114 protoPrefix = pfx;
115 }
116
117 void
118 HttpReply::clean()
119 {
120 // we used to assert that the pipe is NULL, but now the message only
121 // points to a pipe that is owned and initiated by another object.
122 body_pipe = NULL;
123
124 httpBodyClean(&body);
125 hdrCacheClean();
126 header.clean();
127 httpStatusLineClean(&sline);
128 bodySizeMax = -2; // hack: make calculatedBodySizeMax() false
129 }
130
131 void
132 HttpReply::packHeadersInto(Packer * p) const
133 {
134 httpStatusLinePackInto(&sline, p);
135 header.packInto(p);
136 packerAppend(p, "\r\n", 2);
137 }
138
139 void
140 HttpReply::packInto(Packer * p)
141 {
142 packHeadersInto(p);
143 httpBodyPackInto(&body, p);
144 }
145
146 /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
147 MemBuf *
148 HttpReply::pack()
149 {
150 MemBuf *mb = new MemBuf;
151 Packer p;
152
153 mb->init();
154 packerToMemInit(&p, mb);
155 packInto(&p);
156 packerClean(&p);
157 return mb;
158 }
159
160 MemBuf *
161 httpPackedReply(HttpVersion ver, http_status status, const char *ctype,
162 int64_t clen, time_t lmt, time_t expires)
163 {
164 HttpReply *rep = new HttpReply;
165 rep->setHeaders(ver, status, ctype, NULL, clen, lmt, expires);
166 MemBuf *mb = rep->pack();
167 delete rep;
168 return mb;
169 }
170
171 HttpReply *
172 HttpReply::make304 () const
173 {
174 static const http_hdr_type ImsEntries[] = {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER};
175
176 HttpReply *rv = new HttpReply;
177 int t;
178 HttpHeaderEntry *e;
179
180 /* rv->content_length; */
181 rv->date = date;
182 rv->last_modified = last_modified;
183 rv->expires = expires;
184 rv->content_type = content_type;
185 /* rv->cache_control */
186 /* rv->content_range */
187 /* rv->keep_alive */
188 HttpVersion ver(1,0);
189 httpStatusLineSet(&rv->sline, ver,
190 HTTP_NOT_MODIFIED, "");
191
192 for (t = 0; ImsEntries[t] != HDR_OTHER; ++t)
193 if ((e = header.findEntry(ImsEntries[t])))
194 rv->header.addEntry(e->clone());
195
196 /* rv->body */
197 return rv;
198 }
199
200 MemBuf *
201 HttpReply::packed304Reply()
202 {
203 /* Not as efficient as skipping the header duplication,
204 * but easier to maintain
205 */
206 HttpReply *temp = make304 ();
207 MemBuf *rv = temp->pack();
208 delete temp;
209 return rv;
210 }
211
212 void
213 HttpReply::setHeaders(HttpVersion ver, http_status status, const char *reason,
214 const char *ctype, int64_t clen, time_t lmt, time_t expires)
215 {
216 HttpHeader *hdr;
217 httpStatusLineSet(&sline, ver, status, reason);
218 hdr = &header;
219 hdr->putStr(HDR_SERVER, visible_appname_string);
220 hdr->putStr(HDR_MIME_VERSION, "1.0");
221 hdr->putTime(HDR_DATE, squid_curtime);
222
223 if (ctype) {
224 hdr->putStr(HDR_CONTENT_TYPE, ctype);
225 content_type = ctype;
226 } else
227 content_type = String();
228
229 if (clen >= 0)
230 hdr->putInt64(HDR_CONTENT_LENGTH, clen);
231
232 if (expires >= 0)
233 hdr->putTime(HDR_EXPIRES, expires);
234
235 if (lmt > 0) /* this used to be lmt != 0 @?@ */
236 hdr->putTime(HDR_LAST_MODIFIED, lmt);
237
238 date = squid_curtime;
239
240 content_length = clen;
241
242 expires = expires;
243
244 last_modified = lmt;
245 }
246
247 void
248 HttpReply::redirect(http_status status, const char *loc)
249 {
250 HttpHeader *hdr;
251 HttpVersion ver(1,0);
252 httpStatusLineSet(&sline, ver, status, httpStatusString(status));
253 hdr = &header;
254 hdr->putStr(HDR_SERVER, APP_FULLNAME);
255 hdr->putTime(HDR_DATE, squid_curtime);
256 hdr->putInt64(HDR_CONTENT_LENGTH, 0);
257 hdr->putStr(HDR_LOCATION, loc);
258 date = squid_curtime;
259 content_length = 0;
260 }
261
262 /* compare the validators of two replies.
263 * 1 = they match
264 * 0 = they do not match
265 */
266 int
267 HttpReply::validatorsMatch(HttpReply const * otherRep) const
268 {
269 String one,two;
270 assert (otherRep);
271 /* Numbers first - easiest to check */
272 /* Content-Length */
273 /* TODO: remove -1 bypass */
274
275 if (content_length != otherRep->content_length
276 && content_length > -1 &&
277 otherRep->content_length > -1)
278 return 0;
279
280 /* ETag */
281 one = header.getStrOrList(HDR_ETAG);
282
283 two = otherRep->header.getStrOrList(HDR_ETAG);
284
285 if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
286 one.clean();
287 two.clean();
288 return 0;
289 }
290
291 if (last_modified != otherRep->last_modified)
292 return 0;
293
294 /* MD5 */
295 one = header.getStrOrList(HDR_CONTENT_MD5);
296
297 two = otherRep->header.getStrOrList(HDR_CONTENT_MD5);
298
299 if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
300 one.clean();
301 two.clean();
302 return 0;
303 }
304
305 return 1;
306 }
307
308 void
309 HttpReply::updateOnNotModified(HttpReply const * freshRep)
310 {
311 assert(freshRep);
312
313 /* clean cache */
314 hdrCacheClean();
315 /* update raw headers */
316 header.update(&freshRep->header,
317 (const HttpHeaderMask *) &Denied304HeadersMask);
318
319 header.compact();
320 /* init cache */
321 hdrCacheInit();
322 }
323
324 /* internal routines */
325
326 time_t
327 HttpReply::hdrExpirationTime()
328 {
329 /* The s-maxage and max-age directive takes priority over Expires */
330
331 if (cache_control) {
332 if (date >= 0) {
333 if (cache_control->s_maxage >= 0)
334 return date + cache_control->s_maxage;
335
336 if (cache_control->max_age >= 0)
337 return date + cache_control->max_age;
338 } else {
339 /*
340 * Conservatively handle the case when we have a max-age
341 * header, but no Date for reference?
342 */
343
344 if (cache_control->s_maxage >= 0)
345 return squid_curtime;
346
347 if (cache_control->max_age >= 0)
348 return squid_curtime;
349 }
350 }
351
352 if (Config.onoff.vary_ignore_expire &&
353 header.has(HDR_VARY)) {
354 const time_t d = header.getTime(HDR_DATE);
355 const time_t e = header.getTime(HDR_EXPIRES);
356
357 if (d == e)
358 return -1;
359 }
360
361 if (header.has(HDR_EXPIRES)) {
362 const time_t e = header.getTime(HDR_EXPIRES);
363 /*
364 * HTTP/1.0 says that robust implementations should consider
365 * bad or malformed Expires header as equivalent to "expires
366 * immediately."
367 */
368 return e < 0 ? squid_curtime : e;
369 }
370
371 return -1;
372 }
373
374 /* sync this routine when you update HttpReply struct */
375 void
376 HttpReply::hdrCacheInit()
377 {
378 HttpMsg::hdrCacheInit();
379
380 content_length = header.getInt64(HDR_CONTENT_LENGTH);
381 date = header.getTime(HDR_DATE);
382 last_modified = header.getTime(HDR_LAST_MODIFIED);
383 surrogate_control = header.getSc();
384 content_range = header.getContRange();
385 keep_alive = httpMsgIsPersistent(sline.version, &header);
386 const char *str = header.getStr(HDR_CONTENT_TYPE);
387
388 if (str)
389 content_type.limitInit(str, strcspn(str, ";\t "));
390 else
391 content_type = String();
392
393 /* be sure to set expires after date and cache-control */
394 expires = hdrExpirationTime();
395 }
396
397 /* sync this routine when you update HttpReply struct */
398 void
399 HttpReply::hdrCacheClean()
400 {
401 content_type.clean();
402
403 if (cache_control) {
404 httpHdrCcDestroy(cache_control);
405 cache_control = NULL;
406 }
407
408 if (surrogate_control) {
409 httpHdrScDestroy(surrogate_control);
410 surrogate_control = NULL;
411 }
412
413 if (content_range) {
414 httpHdrContRangeDestroy(content_range);
415 content_range = NULL;
416 }
417 }
418
419 /*
420 * Returns the body size of a HTTP response
421 */
422 int64_t
423 HttpReply::bodySize(const HttpRequestMethod& method) const
424 {
425 if (sline.version.major < 1)
426 return -1;
427 else if (method.id() == METHOD_HEAD)
428 return 0;
429 else if (sline.status == HTTP_OK)
430 (void) 0; /* common case, continue */
431 else if (sline.status == HTTP_NO_CONTENT)
432 return 0;
433 else if (sline.status == HTTP_NOT_MODIFIED)
434 return 0;
435 else if (sline.status < HTTP_OK)
436 return 0;
437
438 return content_length;
439 }
440
441 bool HttpReply::sanityCheckStartLine(MemBuf *buf, http_status *error)
442 {
443 if (buf->contentSize() >= protoPrefix.size() && protoPrefix.cmp(buf->content(), protoPrefix.size()) != 0) {
444 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix.buf() << ") in '" << buf->content() << "'");
445 *error = HTTP_INVALID_HEADER;
446 return false;
447 }
448
449 return true;
450 }
451
452 void HttpReply::packFirstLineInto(Packer *p, bool unused) const
453 {
454 httpStatusLinePackInto(&sline, p);
455 }
456
457 bool HttpReply::parseFirstLine(const char *blk_start, const char *blk_end)
458 {
459 return httpStatusLineParse(&sline, protoPrefix, blk_start, blk_end);
460 }
461
462 /* handy: resets and returns -1 */
463 int
464 HttpReply::httpMsgParseError()
465 {
466 int result(HttpMsg::httpMsgParseError());
467 /* indicate an error in the status line */
468 sline.status = HTTP_INVALID_HEADER;
469 return result;
470 }
471
472 /*
473 * Indicate whether or not we would usually expect an entity-body
474 * along with this response
475 */
476 bool
477 HttpReply::expectingBody(const HttpRequestMethod& req_method, int64_t& theSize) const
478 {
479 bool expectBody = true;
480
481 if (req_method == METHOD_HEAD)
482 expectBody = false;
483 else if (sline.status == HTTP_NO_CONTENT)
484 expectBody = false;
485 else if (sline.status == HTTP_NOT_MODIFIED)
486 expectBody = false;
487 else if (sline.status < HTTP_OK)
488 expectBody = false;
489 else
490 expectBody = true;
491
492 if (expectBody) {
493 if (header.hasListMember(HDR_TRANSFER_ENCODING, "chunked", ','))
494 theSize = -1;
495 else if (content_length >= 0)
496 theSize = content_length;
497 else
498 theSize = -1;
499 }
500
501 return expectBody;
502 }
503
504 bool
505 HttpReply::receivedBodyTooLarge(HttpRequest& request, int64_t receivedSize)
506 {
507 calcMaxBodySize(request);
508 debugs(58, 3, HERE << receivedSize << " >? " << bodySizeMax);
509 return bodySizeMax >= 0 && receivedSize > bodySizeMax;
510 }
511
512 bool
513 HttpReply::expectedBodyTooLarge(HttpRequest& request)
514 {
515 calcMaxBodySize(request);
516 debugs(58, 7, HERE << "bodySizeMax=" << bodySizeMax);
517
518 if (bodySizeMax < 0) // no body size limit
519 return false;
520
521 int64_t expectedSize = -1;
522 if (!expectingBody(request.method, expectedSize))
523 return false;
524
525 debugs(58, 6, HERE << expectedSize << " >? " << bodySizeMax);
526
527 if (expectedSize < 0) // expecting body of an unknown length
528 return false;
529
530 return expectedSize > bodySizeMax;
531 }
532
533 void
534 HttpReply::calcMaxBodySize(HttpRequest& request)
535 {
536 // hack: -2 is used as "we have not calculated max body size yet" state
537 if (bodySizeMax != -2) // already tried
538 return;
539 bodySizeMax = -1;
540
541 ACLChecklist ch;
542 ch.src_addr = request.client_addr;
543 ch.my_addr = request.my_addr;
544 ch.reply = HTTPMSGLOCK(this); // XXX: this lock makes method non-const
545 ch.request = HTTPMSGLOCK(&request);
546 for (acl_size_t *l = Config.ReplyBodySize; l; l = l -> next) {
547 if (ch.matchAclListFast(l->aclList)) {
548 debugs(58, 4, HERE << "bodySizeMax=" << bodySizeMax);
549 bodySizeMax = l->size; // may be -1
550 break;
551 }
552 }
553 }
554
555 // XXX: check that this is sufficient for eCAP cloning
556 HttpReply *
557 HttpReply::clone() const
558 {
559 HttpReply *rep = new HttpReply();
560 rep->header.append(&header);
561 rep->hdrCacheInit();
562 rep->hdr_sz = hdr_sz;
563 rep->http_ver = http_ver;
564 rep->pstate = pstate;
565 rep->body_pipe = body_pipe;
566
567 rep->protocol = protocol;
568 rep->sline = sline;
569 return rep;
570 }