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