]> git.ipfire.org Git - thirdparty/squid.git/blame - src/HttpReply.cc
Bug #874: rough port of the http_reply_header_size functionality
[thirdparty/squid.git] / src / HttpReply.cc
CommitLineData
2ac76861 1
cb69b4c7 2/*
4eb368f9 3 * $Id: HttpReply.cc,v 1.70 2004/12/21 17:52:53 robertc 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
528b2c61 36#include "HttpReply.h"
cb69b4c7 37#include "squid.h"
528b2c61 38#include "Store.h"
39#include "HttpHeader.h"
40#include "HttpHdrContRange.h"
4fb35c3c 41#include "ACLChecklist.h"
cb69b4c7 42
43/* local constants */
44
2246b732 45/* these entity-headers must be ignored if a bogus server sends them in 304 */
46static HttpHeaderMask Denied304HeadersMask;
47static 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
e6ccf245 54HttpMsgParseState &operator++ (HttpMsgParseState &aState)
55{
1f1ae50a 56 int tmp = (int)aState;
57 aState = (HttpMsgParseState)(++tmp);
e6ccf245 58 return aState;
59}
60
61
cb69b4c7 62/* local routines */
2246b732 63static void httpReplyClean(HttpReply * rep);
2ac76861 64static void httpReplyDoDestroy(HttpReply * rep);
d8b249ef 65static void httpReplyHdrCacheInit(HttpReply * rep);
66static void httpReplyHdrCacheClean(HttpReply * rep);
2ac76861 67static int httpReplyParseStep(HttpReply * rep, const char *parse_start, int atEnd);
68static int httpReplyParseError(HttpReply * rep);
cb69b4c7 69static int httpReplyIsolateStart(const char **parse_start, const char **blk_start, const char **blk_end);
d20b1cd0 70static time_t httpReplyHdrExpirationTime(const HttpReply * rep);
cb69b4c7 71
72
2246b732 73/* module initialization */
74void
9bc73deb 75httpReplyInitModule(void)
2246b732 76{
97474590 77 httpHeaderMaskInit(&Denied304HeadersMask, 0);
2246b732 78 httpHeaderCalcMask(&Denied304HeadersMask, (const int *) Denied304HeadersArr, countof(Denied304HeadersArr));
79}
80
81
cb69b4c7 82HttpReply *
9bc73deb 83httpReplyCreate(void)
cb69b4c7 84{
0353e724 85 HttpReply *rep = new HttpReply;
399e85ea 86 debug(58, 7) ("creating rep: %p\n", rep);
cb69b4c7 87 return rep;
88}
89
75faaa7a 90HttpReply::HttpReply() : hdr_sz (0), content_length (0), date (0), last_modified (0), expires (0), cache_control (NULL), surrogate_control (NULL), content_range (NULL), keep_alive (0), pstate(psReadyToParseStartLine), header (hoReply)
cb69b4c7 91{
75faaa7a 92 assert(this);
93 httpBodyInit(&body);
94 httpReplyHdrCacheInit(this);
95 httpStatusLineInit(&sline);
96
cb69b4c7 97}
98
5446b331 99static void
2ac76861 100httpReplyClean(HttpReply * rep)
cb69b4c7 101{
102 assert(rep);
103 httpBodyClean(&rep->body);
d8b249ef 104 httpReplyHdrCacheClean(rep);
105 httpHeaderClean(&rep->header);
cb69b4c7 106 httpStatusLineClean(&rep->sline);
107}
108
109void
2ac76861 110httpReplyDestroy(HttpReply * rep)
cb69b4c7 111{
112 assert(rep);
399e85ea 113 debug(58, 7) ("destroying rep: %p\n", rep);
cb69b4c7 114 httpReplyClean(rep);
63259c34 115 httpReplyDoDestroy(rep);
cb69b4c7 116}
117
118void
2ac76861 119httpReplyReset(HttpReply * rep)
cb69b4c7 120{
121 httpReplyClean(rep);
75faaa7a 122 *rep = HttpReply();
cb69b4c7 123}
124
63259c34 125/* absorb: copy the contents of a new reply to the old one, destroy new one */
126void
2ac76861 127httpReplyAbsorb(HttpReply * rep, HttpReply * new_rep)
63259c34 128{
129 assert(rep && new_rep);
130 httpReplyClean(rep);
131 *rep = *new_rep;
528b2c61 132 new_rep->header.entries.clean();
63259c34 133 /* cannot use Clean() on new reply now! */
134 httpReplyDoDestroy(new_rep);
135}
136
9bc73deb 137/*
138 * httpReplyParse takes character buffer of HTTP headers (buf),
139 * which may not be NULL-terminated, and fills in an HttpReply
140 * structure (rep). The parameter 'end' specifies the offset to
141 * the end of the reply headers. The caller may know where the
142 * end is, but is unable to NULL-terminate the buffer. This function
143 * returns true on success.
144 */
cb69b4c7 145int
9bc73deb 146httpReplyParse(HttpReply * rep, const char *buf, ssize_t end)
cb69b4c7 147{
148 /*
9bc73deb 149 * this extra buffer/copy will be eliminated when headers become
150 * meta-data in store. Currently we have to xstrncpy the buffer
151 * becuase somebody may feed a non NULL-terminated buffer to
152 * us.
cb69b4c7 153 */
4eb368f9 154 MemBuf mb = MemBufNull;
cb69b4c7 155 int success;
156 /* reset current state, because we are not used in incremental fashion */
157 httpReplyReset(rep);
25b0856c 158 /* put a string terminator. s is how many bytes to touch in
159 * 'buf' including the terminating NULL. */
4eb368f9 160 memBufDefInit(&mb);
161 memBufAppend(&mb, buf, end);
162 memBufAppend(&mb, "\0", 1);
163 success = httpReplyParseStep(rep, mb.buf, 0);
164 memBufClean(&mb);
cb69b4c7 165 return success == 1;
166}
167
168void
528b2c61 169httpReplyPackHeadersInto(const HttpReply * rep, Packer * p)
cb69b4c7 170{
171 assert(rep);
172 httpStatusLinePackInto(&rep->sline, p);
d8b249ef 173 httpHeaderPackInto(&rep->header, p);
cb69b4c7 174 packerAppend(p, "\r\n", 2);
528b2c61 175}
176
177void
178httpReplyPackInto(const HttpReply * rep, Packer * p)
179{
180 httpReplyPackHeadersInto(rep, p);
cb69b4c7 181 httpBodyPackInto(&rep->body, p);
182}
183
184/* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
185MemBuf
2ac76861 186httpReplyPack(const HttpReply * rep)
cb69b4c7 187{
188 MemBuf mb;
189 Packer p;
190 assert(rep);
191
192 memBufDefInit(&mb);
193 packerToMemInit(&p, &mb);
194 httpReplyPackInto(rep, &p);
195 packerClean(&p);
196 return mb;
197}
198
528b2c61 199/* swap: create swap-based packer, pack, destroy packer
200 * This eats the reply.
201 */
cb69b4c7 202void
528b2c61 203httpReplySwapOut(HttpReply * rep, StoreEntry * e)
cb69b4c7 204{
cb69b4c7 205 assert(rep && e);
206
528b2c61 207 storeEntryReplaceObject(e, rep);
cb69b4c7 208}
209
210MemBuf
450e0c10 211httpPackedReply(HttpVersion ver, http_status status, const char *ctype,
62e76326 212 int clen, time_t lmt, time_t expires)
cb69b4c7 213{
214 HttpReply *rep = httpReplyCreate();
215 MemBuf mb;
216 httpReplySetHeaders(rep, ver, status, ctype, NULL, clen, lmt, expires);
217 mb = httpReplyPack(rep);
218 httpReplyDestroy(rep);
219 return mb;
220}
221
528b2c61 222HttpReply *
223httpReplyMake304 (const HttpReply * rep)
cb69b4c7 224{
62e76326 225 static const http_hdr_type ImsEntries[] = {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER};
226
528b2c61 227 HttpReply *rv;
728da2ee 228 int t;
de336bbe 229 HttpHeaderEntry *e;
cb69b4c7 230 assert(rep);
231
528b2c61 232 rv = httpReplyCreate ();
233 /* rv->content_length; */
234 rv->date = rep->date;
235 rv->last_modified = rep->last_modified;
236 rv->expires = rep->expires;
237 rv->content_type = rep->content_type;
238 /* rv->cache_control */
239 /* rv->content_range */
240 /* rv->keep_alive */
450e0c10 241 HttpVersion ver(1,0);
528b2c61 242 httpStatusLineSet(&rv->sline, ver,
62e76326 243 HTTP_NOT_MODIFIED, "");
244
de336bbe 245 for (t = 0; ImsEntries[t] != HDR_OTHER; ++t)
62e76326 246 if ((e = httpHeaderFindEntry(&rep->header, ImsEntries[t])))
247 httpHeaderAddEntry(&rv->header, httpHeaderEntryClone(e));
248
528b2c61 249 /* rv->body */
250 return rv;
251}
252
253MemBuf
254httpPacked304Reply(const HttpReply * rep)
255{
256 /* Not as efficient as skipping the header duplication,
257 * but easier to maintain
258 */
259 HttpReply *temp;
260 MemBuf rv;
261 assert (rep);
262 temp = httpReplyMake304 (rep);
263 rv = httpReplyPack(temp);
264 httpReplyDestroy (temp);
265 return rv;
cb69b4c7 266}
267
268void
450e0c10 269httpReplySetHeaders(HttpReply * reply, HttpVersion ver, http_status status, const char *reason,
62e76326 270 const char *ctype, int clen, time_t lmt, time_t expires)
cb69b4c7 271{
272 HttpHeader *hdr;
273 assert(reply);
274 httpStatusLineSet(&reply->sline, ver, status, reason);
d8b249ef 275 hdr = &reply->header;
276 httpHeaderPutStr(hdr, HDR_SERVER, full_appname_string);
277 httpHeaderPutStr(hdr, HDR_MIME_VERSION, "1.0");
278 httpHeaderPutTime(hdr, HDR_DATE, squid_curtime);
62e76326 279
d8b249ef 280 if (ctype) {
62e76326 281 httpHeaderPutStr(hdr, HDR_CONTENT_TYPE, ctype);
282 reply->content_type = ctype;
d8b249ef 283 } else
650c4b88 284 reply->content_type = String();
62e76326 285
de336bbe 286 if (clen >= 0)
62e76326 287 httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, clen);
288
cb69b4c7 289 if (expires >= 0)
62e76326 290 httpHeaderPutTime(hdr, HDR_EXPIRES, expires);
291
2ac76861 292 if (lmt > 0) /* this used to be lmt != 0 @?@ */
62e76326 293 httpHeaderPutTime(hdr, HDR_LAST_MODIFIED, lmt);
294
d8b249ef 295 reply->date = squid_curtime;
62e76326 296
d8b249ef 297 reply->content_length = clen;
62e76326 298
d8b249ef 299 reply->expires = expires;
62e76326 300
d8b249ef 301 reply->last_modified = lmt;
cb69b4c7 302}
303
6d38ef86 304void
305httpRedirectReply(HttpReply * reply, http_status status, const char *loc)
306{
307 HttpHeader *hdr;
308 assert(reply);
450e0c10 309 HttpVersion ver(1,0);
ccf44862 310 httpStatusLineSet(&reply->sline, ver, status, httpStatusString(status));
6d38ef86 311 hdr = &reply->header;
312 httpHeaderPutStr(hdr, HDR_SERVER, full_appname_string);
313 httpHeaderPutTime(hdr, HDR_DATE, squid_curtime);
314 httpHeaderPutInt(hdr, HDR_CONTENT_LENGTH, 0);
315 httpHeaderPutStr(hdr, HDR_LOCATION, loc);
316 reply->date = squid_curtime;
317 reply->content_length = 0;
318}
319
528b2c61 320/* compare the validators of two replies.
321 * 1 = they match
322 * 0 = they do not match
323 */
324int
62e76326 325httpReplyValidatorsMatch(HttpReply const * rep, HttpReply const * otherRep)
326{
528b2c61 327 String one,two;
328 assert (rep && otherRep);
329 /* Numbers first - easiest to check */
330 /* Content-Length */
331 /* TODO: remove -1 bypass */
62e76326 332
528b2c61 333 if (rep->content_length != otherRep->content_length
62e76326 334 && rep->content_length > -1 &&
335 otherRep->content_length > -1)
336 return 0;
337
528b2c61 338 /* ETag */
339 one = httpHeaderGetStrOrList(&rep->header, HDR_ETAG);
62e76326 340
528b2c61 341 two = httpHeaderGetStrOrList(&otherRep->header, HDR_ETAG);
62e76326 342
528b2c61 343 if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
62e76326 344 one.clean();
345 two.clean();
346 return 0;
528b2c61 347 }
62e76326 348
528b2c61 349 if (rep->last_modified != otherRep->last_modified)
62e76326 350 return 0;
351
528b2c61 352 /* MD5 */
353 one = httpHeaderGetStrOrList(&rep->header, HDR_CONTENT_MD5);
62e76326 354
528b2c61 355 two = httpHeaderGetStrOrList(&otherRep->header, HDR_CONTENT_MD5);
62e76326 356
6bd76974 357 if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
62e76326 358 one.clean();
359 two.clean();
360 return 0;
528b2c61 361 }
62e76326 362
528b2c61 363 return 1;
364}
365
366
cb69b4c7 367void
528b2c61 368httpReplyUpdateOnNotModified(HttpReply * rep, HttpReply const * freshRep)
cb69b4c7 369{
cb69b4c7 370 assert(rep && freshRep);
528b2c61 371 /* Can not update modified headers that don't match! */
372 assert (httpReplyValidatorsMatch(rep, freshRep));
d8b249ef 373 /* clean cache */
374 httpReplyHdrCacheClean(rep);
375 /* update raw headers */
c68e9c6b 376 httpHeaderUpdate(&rep->header, &freshRep->header,
62e76326 377 (const HttpHeaderMask *) &Denied304HeadersMask);
d8b249ef 378 /* init cache */
379 httpReplyHdrCacheInit(rep);
cb69b4c7 380}
381
cb69b4c7 382
d8b249ef 383/* internal routines */
cb69b4c7 384
d8b249ef 385/* internal function used by Destroy and Absorb */
386static void
387httpReplyDoDestroy(HttpReply * rep)
cb69b4c7 388{
0353e724 389 delete rep;
cb69b4c7 390}
391
d20b1cd0 392static time_t
393httpReplyHdrExpirationTime(const HttpReply * rep)
394{
395 /* The s-maxage and max-age directive takes priority over Expires */
62e76326 396
d20b1cd0 397 if (rep->cache_control) {
62e76326 398 if (rep->date >= 0) {
399 if (rep->cache_control->s_maxage >= 0)
400 return rep->date + rep->cache_control->s_maxage;
401
402 if (rep->cache_control->max_age >= 0)
403 return rep->date + rep->cache_control->max_age;
404 } else {
405 /*
406 * Conservatively handle the case when we have a max-age
407 * header, but no Date for reference?
408 */
409
410 if (rep->cache_control->s_maxage >= 0)
411 return squid_curtime;
412
413 if (rep->cache_control->max_age >= 0)
414 return squid_curtime;
415 }
d20b1cd0 416 }
62e76326 417
f66a9ef4 418 if (Config.onoff.vary_ignore_expire &&
62e76326 419 httpHeaderHas(&rep->header, HDR_VARY)) {
420 const time_t d = httpHeaderGetTime(&rep->header, HDR_DATE);
421 const time_t e = httpHeaderGetTime(&rep->header, HDR_EXPIRES);
422
423 if (d == e)
424 return -1;
f66a9ef4 425 }
62e76326 426
d20b1cd0 427 if (httpHeaderHas(&rep->header, HDR_EXPIRES)) {
62e76326 428 const time_t e = httpHeaderGetTime(&rep->header, HDR_EXPIRES);
429 /*
430 * HTTP/1.0 says that robust implementations should consider
431 * bad or malformed Expires header as equivalent to "expires
432 * immediately."
433 */
434 return e < 0 ? squid_curtime : e;
d20b1cd0 435 }
62e76326 436
d20b1cd0 437 return -1;
438}
439
d8b249ef 440/* sync this routine when you update HttpReply struct */
441static void
442httpReplyHdrCacheInit(HttpReply * rep)
cb69b4c7 443{
d8b249ef 444 const HttpHeader *hdr = &rep->header;
445 const char *str;
446 rep->content_length = httpHeaderGetInt(hdr, HDR_CONTENT_LENGTH);
447 rep->date = httpHeaderGetTime(hdr, HDR_DATE);
448 rep->last_modified = httpHeaderGetTime(hdr, HDR_LAST_MODIFIED);
d8b249ef 449 str = httpHeaderGetStr(hdr, HDR_CONTENT_TYPE);
62e76326 450
d8b249ef 451 if (str)
62e76326 452 rep->content_type.limitInit(str, strcspn(str, ";\t "));
d8b249ef 453 else
650c4b88 454 rep->content_type = String();
62e76326 455
d8b249ef 456 rep->cache_control = httpHeaderGetCc(hdr);
62e76326 457
43ae1d95 458 rep->surrogate_control = httpHeaderGetSc(hdr);
459
d8b249ef 460 rep->content_range = httpHeaderGetContRange(hdr);
62e76326 461
99edd1c3 462 rep->keep_alive = httpMsgIsPersistent(rep->sline.version, &rep->header);
62e76326 463
d20b1cd0 464 /* be sure to set expires after date and cache-control */
465 rep->expires = httpReplyHdrExpirationTime(rep);
cb69b4c7 466}
467
d8b249ef 468/* sync this routine when you update HttpReply struct */
63259c34 469static void
d8b249ef 470httpReplyHdrCacheClean(HttpReply * rep)
2ac76861 471{
528b2c61 472 rep->content_type.clean();
62e76326 473
d8b249ef 474 if (rep->cache_control)
62e76326 475 httpHdrCcDestroy(rep->cache_control);
476
43ae1d95 477 if (rep->surrogate_control)
478 httpHdrScDestroy(rep->surrogate_control);
479
d8b249ef 480 if (rep->content_range)
62e76326 481 httpHdrContRangeDestroy(rep->content_range);
63259c34 482}
cb69b4c7 483
484/*
485 * parses a 0-terminating buffer into HttpReply.
486 * Returns:
528b2c61 487 * 1 -- success
cb69b4c7 488 * 0 -- need more data (partial parse)
489 * -1 -- parse error
490 */
491static int
2ac76861 492httpReplyParseStep(HttpReply * rep, const char *buf, int atEnd)
cb69b4c7 493{
494 const char *parse_start = buf;
495 const char *blk_start, *blk_end;
496 const char **parse_end_ptr = &blk_end;
497 assert(rep);
498 assert(parse_start);
499 assert(rep->pstate < psParsed);
500
501 *parse_end_ptr = parse_start;
62e76326 502
cb69b4c7 503 if (rep->pstate == psReadyToParseStartLine) {
62e76326 504 if (!httpReplyIsolateStart(&parse_start, &blk_start, &blk_end))
505 return 0;
506
507 if (!httpStatusLineParse(&rep->sline, blk_start, blk_end))
508 return httpReplyParseError(rep);
509
510 *parse_end_ptr = parse_start;
511
512 rep->hdr_sz = *parse_end_ptr - buf;
513
514 ++rep->pstate;
cb69b4c7 515 }
62e76326 516
cb69b4c7 517 if (rep->pstate == psReadyToParseHeaders) {
62e76326 518 if (!httpMsgIsolateHeaders(&parse_start, &blk_start, &blk_end)) {
519 if (atEnd)
520 blk_start = parse_start, blk_end = blk_start + strlen(blk_start);
521 else
522 return 0;
523 }
524
525 if (!httpHeaderParse(&rep->header, blk_start, blk_end))
526 return httpReplyParseError(rep);
527
528 httpReplyHdrCacheInit(rep);
529
530 *parse_end_ptr = parse_start;
531
532 rep->hdr_sz = *parse_end_ptr - buf;
533
534 ++rep->pstate;
cb69b4c7 535 }
62e76326 536
cb69b4c7 537 return 1;
538}
539
cb69b4c7 540/* handy: resets and returns -1 */
541static int
2ac76861 542httpReplyParseError(HttpReply * rep)
cb69b4c7 543{
544 assert(rep);
545 /* reset */
546 httpReplyReset(rep);
547 /* indicate an error */
548 rep->sline.status = HTTP_INVALID_HEADER;
549 return -1;
550}
551
552/* find first CRLF */
553static int
554httpReplyIsolateStart(const char **parse_start, const char **blk_start, const char **blk_end)
555{
556 int slen = strcspn(*parse_start, "\r\n");
62e76326 557
2ac76861 558 if (!(*parse_start)[slen]) /* no CRLF found */
62e76326 559 return 0;
cb69b4c7 560
561 *blk_start = *parse_start;
62e76326 562
cb69b4c7 563 *blk_end = *blk_start + slen;
62e76326 564
52639d06 565 while (**blk_end == '\r') /* CR */
62e76326 566 (*blk_end)++;
567
2ac76861 568 if (**blk_end == '\n') /* LF */
62e76326 569 (*blk_end)++;
cb69b4c7 570
571 *parse_start = *blk_end;
62e76326 572
cb69b4c7 573 return 1;
574}
35282fbf 575
576/*
577 * Returns the body size of a HTTP response
578 */
579int
528b2c61 580httpReplyBodySize(method_t method, HttpReply const * reply)
35282fbf 581{
1bda350e 582 if (reply->sline.version.major < 1)
583 return -1;
584 else if (METHOD_HEAD == method)
62e76326 585 return 0;
35282fbf 586 else if (reply->sline.status == HTTP_OK)
62e76326 587 (void) 0; /* common case, continue */
35282fbf 588 else if (reply->sline.status == HTTP_NO_CONTENT)
62e76326 589 return 0;
35282fbf 590 else if (reply->sline.status == HTTP_NOT_MODIFIED)
62e76326 591 return 0;
35282fbf 592 else if (reply->sline.status < HTTP_OK)
62e76326 593 return 0;
594
35282fbf 595 return reply->content_length;
596}