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