]> git.ipfire.org Git - thirdparty/squid.git/blob - src/HttpReply.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / HttpReply.cc
1
2 /*
3 * $Id$
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 "acl/FilledChecklist.h"
38 #include "globals.h"
39 #include "HttpBody.h"
40 #include "HttpHdrCc.h"
41 #include "HttpHdrContRange.h"
42 #include "HttpHdrSc.h"
43 #include "HttpReply.h"
44 #include "HttpRequest.h"
45 #include "MemBuf.h"
46 #include "protos.h"
47 #include "SquidTime.h"
48 #include "Store.h"
49
50 /* local constants */
51
52 /* If we receive a 304 from the origin during a cache revalidation, we must
53 * update the headers of the existing entry. Specifically, we need to update all
54 * end-to-end headers and not any hop-by-hop headers (rfc2616 13.5.3).
55 *
56 * This is not the whole story though: since it is possible for a faulty/malicious
57 * origin server to set headers it should not in a 304, we must explicitly ignore
58 * these too. Specifically all entity-headers except those permitted in a 304
59 * (rfc2616 10.3.5) must be ignored.
60 *
61 * The list of headers we don't update is made up of:
62 * all hop-by-hop headers
63 * all entity-headers except Expires and Content-Location
64 */
65 static HttpHeaderMask Denied304HeadersMask;
66 static http_hdr_type Denied304HeadersArr[] = {
67 // hop-by-hop headers
68 HDR_CONNECTION, HDR_KEEP_ALIVE, HDR_PROXY_AUTHENTICATE, HDR_PROXY_AUTHORIZATION,
69 HDR_TE, HDR_TRAILER, HDR_TRANSFER_ENCODING, HDR_UPGRADE,
70 // entity headers
71 HDR_ALLOW, HDR_CONTENT_ENCODING, HDR_CONTENT_LANGUAGE, HDR_CONTENT_LENGTH,
72 HDR_CONTENT_MD5, HDR_CONTENT_RANGE, HDR_CONTENT_TYPE, HDR_LAST_MODIFIED
73 };
74
75 /* module initialization */
76 void
77 httpReplyInitModule(void)
78 {
79 assert(HTTP_STATUS_NONE == 0); // HttpReply::parse() interface assumes that
80 httpHeaderMaskInit(&Denied304HeadersMask, 0);
81 httpHeaderCalcMask(&Denied304HeadersMask, Denied304HeadersArr, countof(Denied304HeadersArr));
82 }
83
84 HttpReply::HttpReply() : HttpMsg(hoReply), date (0), last_modified (0),
85 expires (0), surrogate_control (NULL), content_range (NULL), keep_alive (0),
86 protoPrefix("HTTP/"), bodySizeMax(-2)
87 {
88 init();
89 }
90
91 HttpReply::~HttpReply()
92 {
93 if (do_clean)
94 clean();
95 }
96
97 void
98 HttpReply::init()
99 {
100 hdrCacheInit();
101 httpStatusLineInit(&sline);
102 pstate = psReadyToParseStartLine;
103 do_clean = true;
104 }
105
106 void HttpReply::reset()
107 {
108
109 // reset should not reset the protocol; could have made protoPrefix a
110 // virtual function instead, but it is not clear whether virtual methods
111 // are allowed with MEMPROXY_CLASS() and whether some cbdata void*
112 // conversions are not going to kill virtual tables
113 const String pfx = protoPrefix;
114 clean();
115 init();
116 protoPrefix = pfx;
117 }
118
119 void
120 HttpReply::clean()
121 {
122 // we used to assert that the pipe is NULL, but now the message only
123 // points to a pipe that is owned and initiated by another object.
124 body_pipe = NULL;
125
126 body.clear();
127 hdrCacheClean();
128 header.clean();
129 httpStatusLineClean(&sline);
130 bodySizeMax = -2; // hack: make calculatedBodySizeMax() false
131 }
132
133 void
134 HttpReply::packHeadersInto(Packer * p) const
135 {
136 httpStatusLinePackInto(&sline, p);
137 header.packInto(p);
138 packerAppend(p, "\r\n", 2);
139 }
140
141 void
142 HttpReply::packInto(Packer * p)
143 {
144 packHeadersInto(p);
145 body.packInto(p);
146 }
147
148 /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
149 MemBuf *
150 HttpReply::pack()
151 {
152 MemBuf *mb = new MemBuf;
153 Packer p;
154
155 mb->init();
156 packerToMemInit(&p, mb);
157 packInto(&p);
158 packerClean(&p);
159 return mb;
160 }
161
162 #if DEAD_CODE
163 MemBuf *
164 httpPackedReply(http_status status, const char *ctype, int64_t clen, time_t lmt, time_t expires)
165 {
166 HttpReply *rep = new HttpReply;
167 rep->setHeaders(status, ctype, NULL, clen, lmt, expires);
168 MemBuf *mb = rep->pack();
169 delete rep;
170 return mb;
171 }
172 #endif
173
174 HttpReply *
175 HttpReply::make304() const
176 {
177 static const http_hdr_type ImsEntries[] = {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER};
178
179 HttpReply *rv = new HttpReply;
180 int t;
181 HttpHeaderEntry *e;
182
183 /* rv->content_length; */
184 rv->date = date;
185 rv->last_modified = last_modified;
186 rv->expires = expires;
187 rv->content_type = content_type;
188 /* rv->cache_control */
189 /* rv->content_range */
190 /* rv->keep_alive */
191 HttpVersion ver(1,1);
192 httpStatusLineSet(&rv->sline, ver, HTTP_NOT_MODIFIED, NULL);
193
194 for (t = 0; ImsEntries[t] != HDR_OTHER; ++t)
195 if ((e = header.findEntry(ImsEntries[t])))
196 rv->header.addEntry(e->clone());
197
198 /* rv->body */
199 return rv;
200 }
201
202 MemBuf *
203 HttpReply::packed304Reply()
204 {
205 /* Not as efficient as skipping the header duplication,
206 * but easier to maintain
207 */
208 HttpReply *temp = make304();
209 MemBuf *rv = temp->pack();
210 delete temp;
211 return rv;
212 }
213
214 void
215 HttpReply::setHeaders(http_status status, const char *reason,
216 const char *ctype, int64_t clen, time_t lmt, time_t expiresTime)
217 {
218 HttpHeader *hdr;
219 HttpVersion ver(1,1);
220 httpStatusLineSet(&sline, ver, status, reason);
221 hdr = &header;
222 hdr->putStr(HDR_SERVER, visible_appname_string);
223 hdr->putStr(HDR_MIME_VERSION, "1.0");
224 hdr->putTime(HDR_DATE, squid_curtime);
225
226 if (ctype) {
227 hdr->putStr(HDR_CONTENT_TYPE, ctype);
228 content_type = ctype;
229 } else
230 content_type = String();
231
232 if (clen >= 0)
233 hdr->putInt64(HDR_CONTENT_LENGTH, clen);
234
235 if (expiresTime >= 0)
236 hdr->putTime(HDR_EXPIRES, expiresTime);
237
238 if (lmt > 0) /* this used to be lmt != 0 @?@ */
239 hdr->putTime(HDR_LAST_MODIFIED, lmt);
240
241 date = squid_curtime;
242
243 content_length = clen;
244
245 expires = expiresTime;
246
247 last_modified = lmt;
248 }
249
250 void
251 HttpReply::redirect(http_status status, const char *loc)
252 {
253 HttpHeader *hdr;
254 HttpVersion ver(1,1);
255 httpStatusLineSet(&sline, ver, status, httpStatusString(status));
256 hdr = &header;
257 hdr->putStr(HDR_SERVER, APP_FULLNAME);
258 hdr->putTime(HDR_DATE, squid_curtime);
259 hdr->putInt64(HDR_CONTENT_LENGTH, 0);
260 hdr->putStr(HDR_LOCATION, loc);
261 date = squid_curtime;
262 content_length = 0;
263 }
264
265 /* compare the validators of two replies.
266 * 1 = they match
267 * 0 = they do not match
268 */
269 int
270 HttpReply::validatorsMatch(HttpReply const * otherRep) const
271 {
272 String one,two;
273 assert (otherRep);
274 /* Numbers first - easiest to check */
275 /* Content-Length */
276 /* TODO: remove -1 bypass */
277
278 if (content_length != otherRep->content_length
279 && content_length > -1 &&
280 otherRep->content_length > -1)
281 return 0;
282
283 /* ETag */
284 one = header.getStrOrList(HDR_ETAG);
285
286 two = otherRep->header.getStrOrList(HDR_ETAG);
287
288 if (one.undefined() || two.undefined() || one.caseCmp(two)!=0 ) {
289 one.clean();
290 two.clean();
291 return 0;
292 }
293
294 if (last_modified != otherRep->last_modified)
295 return 0;
296
297 /* MD5 */
298 one = header.getStrOrList(HDR_CONTENT_MD5);
299
300 two = otherRep->header.getStrOrList(HDR_CONTENT_MD5);
301
302 if (one.undefined() || two.undefined() || one.caseCmp(two) != 0 ) {
303 one.clean();
304 two.clean();
305 return 0;
306 }
307
308 return 1;
309 }
310
311 void
312 HttpReply::updateOnNotModified(HttpReply const * freshRep)
313 {
314 assert(freshRep);
315
316 /* clean cache */
317 hdrCacheClean();
318 /* update raw headers */
319 header.update(&freshRep->header,
320 (const HttpHeaderMask *) &Denied304HeadersMask);
321
322 header.compact();
323 /* init cache */
324 hdrCacheInit();
325 }
326
327 /* internal routines */
328
329 time_t
330 HttpReply::hdrExpirationTime()
331 {
332 /* The s-maxage and max-age directive takes priority over Expires */
333
334 if (cache_control) {
335 if (date >= 0) {
336 if (cache_control->hasSMaxAge())
337 return date + cache_control->sMaxAge();
338
339 if (cache_control->hasMaxAge())
340 return date + cache_control->maxAge();
341 } else {
342 /*
343 * Conservatively handle the case when we have a max-age
344 * header, but no Date for reference?
345 */
346
347 if (cache_control->hasSMaxAge())
348 return squid_curtime;
349
350 if (cache_control->hasMaxAge())
351 return squid_curtime;
352 }
353 }
354
355 if (Config.onoff.vary_ignore_expire &&
356 header.has(HDR_VARY)) {
357 const time_t d = header.getTime(HDR_DATE);
358 const time_t e = header.getTime(HDR_EXPIRES);
359
360 if (d == e)
361 return -1;
362 }
363
364 if (header.has(HDR_EXPIRES)) {
365 const time_t e = header.getTime(HDR_EXPIRES);
366 /*
367 * HTTP/1.0 says that robust implementations should consider
368 * bad or malformed Expires header as equivalent to "expires
369 * immediately."
370 */
371 return e < 0 ? squid_curtime : e;
372 }
373
374 return -1;
375 }
376
377 /* sync this routine when you update HttpReply struct */
378 void
379 HttpReply::hdrCacheInit()
380 {
381 HttpMsg::hdrCacheInit();
382
383 http_ver = sline.version;
384 content_length = header.getInt64(HDR_CONTENT_LENGTH);
385 date = header.getTime(HDR_DATE);
386 last_modified = header.getTime(HDR_LAST_MODIFIED);
387 surrogate_control = header.getSc();
388 content_range = header.getContRange();
389 keep_alive = persistent() ? 1 : 0;
390 const char *str = header.getStr(HDR_CONTENT_TYPE);
391
392 if (str)
393 content_type.limitInit(str, strcspn(str, ";\t "));
394 else
395 content_type = String();
396
397 /* be sure to set expires after date and cache-control */
398 expires = hdrExpirationTime();
399 }
400
401 /* sync this routine when you update HttpReply struct */
402 void
403 HttpReply::hdrCacheClean()
404 {
405 content_type.clean();
406
407 if (cache_control) {
408 delete cache_control;
409 cache_control = NULL;
410 }
411
412 if (surrogate_control) {
413 delete surrogate_control;
414 surrogate_control = NULL;
415 }
416
417 if (content_range) {
418 httpHdrContRangeDestroy(content_range);
419 content_range = NULL;
420 }
421 }
422
423 /*
424 * Returns the body size of a HTTP response
425 */
426 int64_t
427 HttpReply::bodySize(const HttpRequestMethod& method) const
428 {
429 if (sline.version.major < 1)
430 return -1;
431 else if (method.id() == METHOD_HEAD)
432 return 0;
433 else if (sline.status == HTTP_OK)
434 (void) 0; /* common case, continue */
435 else if (sline.status == HTTP_NO_CONTENT)
436 return 0;
437 else if (sline.status == HTTP_NOT_MODIFIED)
438 return 0;
439 else if (sline.status < HTTP_OK)
440 return 0;
441
442 return content_length;
443 }
444
445 /**
446 * Checks the first line of an HTTP Reply is valid.
447 * currently only checks "HTTP/" exists.
448 *
449 * NP: not all error cases are detected yet. Some are left for detection later in parse.
450 */
451 bool
452 HttpReply::sanityCheckStartLine(MemBuf *buf, const size_t hdr_len, http_status *error)
453 {
454 // hack warning: using psize instead of size here due to type mismatches with MemBuf.
455
456 // content is long enough to possibly hold a reply
457 // 4 being magic size of a 3-digit number plus space delimiter
458 if ( buf->contentSize() < (protoPrefix.psize() + 4) ) {
459 if (hdr_len > 0) {
460 debugs(58, 3, HERE << "Too small reply header (" << hdr_len << " bytes)");
461 *error = HTTP_INVALID_HEADER;
462 }
463 return false;
464 }
465
466 int pos;
467 // catch missing or mismatched protocol identifier
468 // allow special-case for ICY protocol (non-HTTP identifier) in response to faked HTTP request.
469 if (strncmp(buf->content(), "ICY", 3) == 0) {
470 protoPrefix = "ICY";
471 pos = protoPrefix.psize();
472 } else {
473
474 if (protoPrefix.cmp(buf->content(), protoPrefix.size()) != 0) {
475 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix << ") in '" << buf->content() << "'");
476 *error = HTTP_INVALID_HEADER;
477 return false;
478 }
479
480 // catch missing or negative status value (negative '-' is not a digit)
481 pos = protoPrefix.psize();
482
483 // skip arbitrary number of digits and a dot in the verion portion
484 while ( pos <= buf->contentSize() && (*(buf->content()+pos) == '.' || xisdigit(*(buf->content()+pos)) ) ) ++pos;
485
486 // catch missing version info
487 if (pos == protoPrefix.psize()) {
488 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol version numbers (ie. " << protoPrefix << "/1.0) in '" << buf->content() << "'");
489 *error = HTTP_INVALID_HEADER;
490 return false;
491 }
492 }
493
494 // skip arbitrary number of spaces...
495 while (pos <= buf->contentSize() && (char)*(buf->content()+pos) == ' ') ++pos;
496
497 if (pos < buf->contentSize() && !xisdigit(*(buf->content()+pos))) {
498 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing or invalid status number in '" << buf->content() << "'");
499 *error = HTTP_INVALID_HEADER;
500 return false;
501 }
502
503 return true;
504 }
505
506 void HttpReply::packFirstLineInto(Packer *p, bool unused) const
507 {
508 httpStatusLinePackInto(&sline, p);
509 }
510
511 bool HttpReply::parseFirstLine(const char *blk_start, const char *blk_end)
512 {
513 return httpStatusLineParse(&sline, protoPrefix, blk_start, blk_end);
514 }
515
516 /* handy: resets and returns -1 */
517 int
518 HttpReply::httpMsgParseError()
519 {
520 int result(HttpMsg::httpMsgParseError());
521 /* indicate an error in the status line */
522 sline.status = HTTP_INVALID_HEADER;
523 return result;
524 }
525
526 /*
527 * Indicate whether or not we would usually expect an entity-body
528 * along with this response
529 */
530 bool
531 HttpReply::expectingBody(const HttpRequestMethod& req_method, int64_t& theSize) const
532 {
533 bool expectBody = true;
534
535 if (req_method == METHOD_HEAD)
536 expectBody = false;
537 else if (sline.status == HTTP_NO_CONTENT)
538 expectBody = false;
539 else if (sline.status == HTTP_NOT_MODIFIED)
540 expectBody = false;
541 else if (sline.status < HTTP_OK)
542 expectBody = false;
543 else
544 expectBody = true;
545
546 if (expectBody) {
547 if (header.chunked())
548 theSize = -1;
549 else if (content_length >= 0)
550 theSize = content_length;
551 else
552 theSize = -1;
553 }
554
555 return expectBody;
556 }
557
558 bool
559 HttpReply::receivedBodyTooLarge(HttpRequest& request, int64_t receivedSize)
560 {
561 calcMaxBodySize(request);
562 debugs(58, 3, HERE << receivedSize << " >? " << bodySizeMax);
563 return bodySizeMax >= 0 && receivedSize > bodySizeMax;
564 }
565
566 bool
567 HttpReply::expectedBodyTooLarge(HttpRequest& request)
568 {
569 calcMaxBodySize(request);
570 debugs(58, 7, HERE << "bodySizeMax=" << bodySizeMax);
571
572 if (bodySizeMax < 0) // no body size limit
573 return false;
574
575 int64_t expectedSize = -1;
576 if (!expectingBody(request.method, expectedSize))
577 return false;
578
579 debugs(58, 6, HERE << expectedSize << " >? " << bodySizeMax);
580
581 if (expectedSize < 0) // expecting body of an unknown length
582 return false;
583
584 return expectedSize > bodySizeMax;
585 }
586
587 void
588 HttpReply::calcMaxBodySize(HttpRequest& request)
589 {
590 // hack: -2 is used as "we have not calculated max body size yet" state
591 if (bodySizeMax != -2) // already tried
592 return;
593 bodySizeMax = -1;
594
595 // short-circuit ACL testing if there are none configured
596 if (!Config.ReplyBodySize)
597 return;
598
599 ACLFilledChecklist ch(NULL, &request, NULL);
600 ch.reply = HTTPMSGLOCK(this); // XXX: this lock makes method non-const
601 for (acl_size_t *l = Config.ReplyBodySize; l; l = l -> next) {
602 /* if there is no ACL list or if the ACLs listed match use this size value */
603 if (!l->aclList || ch.fastCheck(l->aclList) == ACCESS_ALLOWED) {
604 debugs(58, 4, HERE << "bodySizeMax=" << bodySizeMax);
605 bodySizeMax = l->size; // may be -1
606 break;
607 }
608 }
609 }
610
611 // XXX: check that this is sufficient for eCAP cloning
612 HttpReply *
613 HttpReply::clone() const
614 {
615 HttpReply *rep = new HttpReply();
616 rep->sline = sline; // used in hdrCacheInit() call below
617 rep->header.append(&header);
618 rep->hdrCacheInit();
619 rep->hdr_sz = hdr_sz;
620 rep->http_ver = http_ver;
621 rep->pstate = pstate;
622 rep->body_pipe = body_pipe;
623
624 rep->protocol = protocol;
625 // keep_alive is handled in hdrCacheInit()
626 return rep;
627 }
628
629 bool HttpReply::inheritProperties(const HttpMsg *aMsg)
630 {
631 const HttpReply *aRep = dynamic_cast<const HttpReply*>(aMsg);
632 if (!aRep)
633 return false;
634 keep_alive = aRep->keep_alive;
635 return true;
636 }
637
638 void HttpReply::removeStaleWarnings()
639 {
640 String warning;
641 if (header.getList(HDR_WARNING, &warning)) {
642 const String newWarning = removeStaleWarningValues(warning);
643 if (warning.size() && warning.size() == newWarning.size())
644 return; // some warnings are there and none changed
645 header.delById(HDR_WARNING);
646 if (newWarning.size()) { // some warnings left
647 HttpHeaderEntry *const e =
648 new HttpHeaderEntry(HDR_WARNING, NULL, newWarning.termedBuf());
649 header.addEntry(e);
650 }
651 }
652 }
653
654 /**
655 * Remove warning-values with warn-date different from Date value from
656 * a single header entry. Returns a string with all valid warning-values.
657 */
658 String HttpReply::removeStaleWarningValues(const String &value)
659 {
660 String newValue;
661 const char *item = 0;
662 int len = 0;
663 const char *pos = 0;
664 while (strListGetItem(&value, ',', &item, &len, &pos)) {
665 bool keep = true;
666 // Does warning-value have warn-date (which contains quoted date)?
667 // We scan backwards, looking for two quoted strings.
668 // warning-value = warn-code SP warn-agent SP warn-text [SP warn-date]
669 const char *p = item + len - 1;
670
671 while (p >= item && xisspace(*p)) --p; // skip whitespace
672
673 // warning-value MUST end with quote
674 if (p >= item && *p == '"') {
675 const char *const warnDateEnd = p;
676 --p;
677 while (p >= item && *p != '"') --p; // find the next quote
678
679 const char *warnDateBeg = p + 1;
680 --p;
681 while (p >= item && xisspace(*p)) --p; // skip whitespace
682
683 if (p >= item && *p == '"' && warnDateBeg - p > 2) {
684 // found warn-text
685 String warnDate;
686 warnDate.append(warnDateBeg, warnDateEnd - warnDateBeg);
687 const time_t time = parse_rfc1123(warnDate.termedBuf());
688 keep = (time > 0 && time == date); // keep valid and matching date
689 }
690 }
691
692 if (keep) {
693 if (newValue.size())
694 newValue.append(", ");
695 newValue.append(item, len);
696 }
697 }
698
699 return newValue;
700 }