3 * $Id: HttpReply.cc,v 1.82 2006/01/23 20:04:24 wessels Exp $
5 * DEBUG: section 58 HTTP Reply (Response)
6 * AUTHOR: Alex Rousskov
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
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.
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.
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.
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.
38 #include "HttpReply.h"
39 #include "HttpHdrContRange.h"
40 #include "ACLChecklist.h"
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
[] =
49 HDR_ALLOW
, HDR_CONTENT_ENCODING
, HDR_CONTENT_LANGUAGE
, HDR_CONTENT_LENGTH
,
50 HDR_CONTENT_LOCATION
, HDR_CONTENT_RANGE
, HDR_LAST_MODIFIED
, HDR_LINK
,
54 /* module initialization */
56 httpReplyInitModule(void)
58 assert(HTTP_STATUS_NONE
== 0); // HttpReply::parse() interface assumes that
59 httpHeaderMaskInit(&Denied304HeadersMask
, 0);
60 httpHeaderCalcMask(&Denied304HeadersMask
, Denied304HeadersArr
, countof(Denied304HeadersArr
));
63 HttpReply::HttpReply() : HttpMsg(hoReply
), date (0), last_modified (0), expires (0), surrogate_control (NULL
), content_range (NULL
), keep_alive (0), protoPrefix("HTTP/")
68 HttpReply::~HttpReply()
79 httpStatusLineInit(&sline
);
83 void HttpReply::reset()
86 // reset should not reset the protocol; could have made protoPrefix a
87 // virtual function instead, but it is not clear whether virtual methods
88 // are allowed with MEMPROXY_CLASS() and whether some cbdata void*
89 // conversions are not going to kill virtual tables
90 const String pfx
= protoPrefix
;
101 httpHeaderClean(&header
);
102 httpStatusLineClean(&sline
);
106 /* absorb: copy the contents of a new reply to the old one, destroy new one */
108 HttpReply::absorb(HttpReply
* new_rep
)
113 new_rep
->header
.entries
.clean();
114 /* cannot use Clean() on new reply now! */
115 new_rep
->do_clean
= false;
116 new_rep
->cache_control
= NULL
; // helps with debugging
123 HttpReply::packHeadersInto(Packer
* p
) const
125 httpStatusLinePackInto(&sline
, p
);
126 httpHeaderPackInto(&header
, p
);
127 packerAppend(p
, "\r\n", 2);
131 HttpReply::packInto(Packer
* p
)
134 httpBodyPackInto(&body
, p
);
137 /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
141 MemBuf
*mb
= new MemBuf
;
145 packerToMemInit(&p
, mb
);
152 httpPackedReply(HttpVersion ver
, http_status status
, const char *ctype
,
153 int clen
, time_t lmt
, time_t expires
)
155 HttpReply
*rep
= new HttpReply
;
156 rep
->setHeaders(ver
, status
, ctype
, NULL
, clen
, lmt
, expires
);
157 MemBuf
*mb
= rep
->pack();
163 HttpReply::make304 () const
165 static const http_hdr_type ImsEntries
[] = {HDR_DATE
, HDR_CONTENT_TYPE
, HDR_EXPIRES
, HDR_LAST_MODIFIED
, /* eof */ HDR_OTHER
};
167 HttpReply
*rv
= new HttpReply
;
171 /* rv->content_length; */
173 rv
->last_modified
= last_modified
;
174 rv
->expires
= expires
;
175 rv
->content_type
= content_type
;
176 /* rv->cache_control */
177 /* rv->content_range */
179 HttpVersion
ver(1,0);
180 httpStatusLineSet(&rv
->sline
, ver
,
181 HTTP_NOT_MODIFIED
, "");
183 for (t
= 0; ImsEntries
[t
] != HDR_OTHER
; ++t
)
184 if ((e
= httpHeaderFindEntry(&header
, ImsEntries
[t
])))
185 httpHeaderAddEntry(&rv
->header
, httpHeaderEntryClone(e
));
192 HttpReply::packed304Reply()
194 /* Not as efficient as skipping the header duplication,
195 * but easier to maintain
197 HttpReply
*temp
= make304 ();
198 MemBuf
*rv
= temp
->pack();
204 HttpReply::setHeaders(HttpVersion ver
, http_status status
, const char *reason
,
205 const char *ctype
, int clen
, time_t lmt
, time_t expires
)
208 httpStatusLineSet(&sline
, ver
, status
, reason
);
210 httpHeaderPutStr(hdr
, HDR_SERVER
, visible_appname_string
);
211 httpHeaderPutStr(hdr
, HDR_MIME_VERSION
, "1.0");
212 httpHeaderPutTime(hdr
, HDR_DATE
, squid_curtime
);
215 httpHeaderPutStr(hdr
, HDR_CONTENT_TYPE
, ctype
);
216 content_type
= ctype
;
218 content_type
= String();
221 httpHeaderPutInt(hdr
, HDR_CONTENT_LENGTH
, clen
);
224 httpHeaderPutTime(hdr
, HDR_EXPIRES
, expires
);
226 if (lmt
> 0) /* this used to be lmt != 0 @?@ */
227 httpHeaderPutTime(hdr
, HDR_LAST_MODIFIED
, lmt
);
229 date
= squid_curtime
;
231 content_length
= clen
;
239 HttpReply::redirect(http_status status
, const char *loc
)
242 HttpVersion
ver(1,0);
243 httpStatusLineSet(&sline
, ver
, status
, httpStatusString(status
));
245 httpHeaderPutStr(hdr
, HDR_SERVER
, full_appname_string
);
246 httpHeaderPutTime(hdr
, HDR_DATE
, squid_curtime
);
247 httpHeaderPutInt(hdr
, HDR_CONTENT_LENGTH
, 0);
248 httpHeaderPutStr(hdr
, HDR_LOCATION
, loc
);
249 date
= squid_curtime
;
253 /* compare the validators of two replies.
255 * 0 = they do not match
258 HttpReply::validatorsMatch(HttpReply
const * otherRep
) const
262 /* Numbers first - easiest to check */
264 /* TODO: remove -1 bypass */
266 if (content_length
!= otherRep
->content_length
267 && content_length
> -1 &&
268 otherRep
->content_length
> -1)
272 one
= httpHeaderGetStrOrList(&header
, HDR_ETAG
);
274 two
= httpHeaderGetStrOrList(&otherRep
->header
, HDR_ETAG
);
276 if (!one
.buf() || !two
.buf() || strcasecmp (one
.buf(), two
.buf())) {
282 if (last_modified
!= otherRep
->last_modified
)
286 one
= httpHeaderGetStrOrList(&header
, HDR_CONTENT_MD5
);
288 two
= httpHeaderGetStrOrList(&otherRep
->header
, HDR_CONTENT_MD5
);
290 if (!one
.buf() || !two
.buf() || strcasecmp (one
.buf(), two
.buf())) {
300 HttpReply::updateOnNotModified(HttpReply
const * freshRep
)
303 /* Can not update modified headers that don't match! */
304 assert (validatorsMatch(freshRep
));
307 /* update raw headers */
308 httpHeaderUpdate(&header
, &freshRep
->header
,
309 (const HttpHeaderMask
*) &Denied304HeadersMask
);
314 /* internal routines */
317 HttpReply::hdrExpirationTime()
319 /* The s-maxage and max-age directive takes priority over Expires */
323 if (cache_control
->s_maxage
>= 0)
324 return date
+ cache_control
->s_maxage
;
326 if (cache_control
->max_age
>= 0)
327 return date
+ cache_control
->max_age
;
330 * Conservatively handle the case when we have a max-age
331 * header, but no Date for reference?
334 if (cache_control
->s_maxage
>= 0)
335 return squid_curtime
;
337 if (cache_control
->max_age
>= 0)
338 return squid_curtime
;
342 if (Config
.onoff
.vary_ignore_expire
&&
343 httpHeaderHas(&header
, HDR_VARY
)) {
344 const time_t d
= httpHeaderGetTime(&header
, HDR_DATE
);
345 const time_t e
= httpHeaderGetTime(&header
, HDR_EXPIRES
);
351 if (httpHeaderHas(&header
, HDR_EXPIRES
)) {
352 const time_t e
= httpHeaderGetTime(&header
, HDR_EXPIRES
);
354 * HTTP/1.0 says that robust implementations should consider
355 * bad or malformed Expires header as equivalent to "expires
358 return e
< 0 ? squid_curtime
: e
;
364 /* sync this routine when you update HttpReply struct */
366 HttpReply::hdrCacheInit()
368 HttpMsg::hdrCacheInit();
370 content_length
= httpHeaderGetInt(&header
, HDR_CONTENT_LENGTH
);
371 date
= httpHeaderGetTime(&header
, HDR_DATE
);
372 last_modified
= httpHeaderGetTime(&header
, HDR_LAST_MODIFIED
);
373 surrogate_control
= httpHeaderGetSc(&header
);
374 content_range
= httpHeaderGetContRange(&header
);
375 keep_alive
= httpMsgIsPersistent(sline
.version
, &header
);
376 const char *str
= httpHeaderGetStr(&header
, HDR_CONTENT_TYPE
);
379 content_type
.limitInit(str
, strcspn(str
, ";\t "));
381 content_type
= String();
383 /* be sure to set expires after date and cache-control */
384 expires
= hdrExpirationTime();
387 /* sync this routine when you update HttpReply struct */
389 HttpReply::hdrCacheClean()
391 content_type
.clean();
394 httpHdrCcDestroy(cache_control
);
395 cache_control
= NULL
;
398 if (surrogate_control
) {
399 httpHdrScDestroy(surrogate_control
);
400 surrogate_control
= NULL
;
404 httpHdrContRangeDestroy(content_range
);
405 content_range
= NULL
;
410 * Returns the body size of a HTTP response
413 HttpReply::bodySize(method_t method
) const
415 if (sline
.version
.major
< 1)
417 else if (METHOD_HEAD
== method
)
419 else if (sline
.status
== HTTP_OK
)
420 (void) 0; /* common case, continue */
421 else if (sline
.status
== HTTP_NO_CONTENT
)
423 else if (sline
.status
== HTTP_NOT_MODIFIED
)
425 else if (sline
.status
< HTTP_OK
)
428 return content_length
;
431 bool HttpReply::sanityCheckStartLine(MemBuf
*buf
, http_status
*error
)
433 if (buf
->contentSize() >= protoPrefix
.size() && protoPrefix
.cmp(buf
->content(), protoPrefix
.size()) != 0) {
434 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix
.buf() << ") in '" << buf
->content() << "'");
435 *error
= HTTP_INVALID_HEADER
;
442 void HttpReply::packFirstLineInto(Packer
*p
, bool unused
) const
444 httpStatusLinePackInto(&sline
, p
);
447 bool HttpReply::parseFirstLine(const char *blk_start
, const char *blk_end
)
449 return httpStatusLineParse(&sline
, protoPrefix
, blk_start
, blk_end
);
453 * Indicate whether or not we would usually expect an entity-body
454 * along with this response
457 HttpReply::expectingBody(method_t req_method
, ssize_t
& theSize
) const
459 bool expectBody
= true;
461 if (req_method
== METHOD_HEAD
)
463 else if (sline
.status
== HTTP_NO_CONTENT
)
465 else if (sline
.status
== HTTP_NOT_MODIFIED
)
467 else if (sline
.status
< HTTP_OK
)
473 if (httpHeaderHasListMember(&header
, HDR_TRANSFER_ENCODING
, "chunked", ','))
475 else if (content_length
>= 0)
476 theSize
= content_length
;