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