3 * $Id: HttpReply.cc,v 1.79 2005/11/07 22:00:38 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
,
55 /* module initialization */
57 httpReplyInitModule(void)
59 assert(HTTP_STATUS_NONE
== 0); // HttpReply::parse() interface assumes that
60 httpHeaderMaskInit(&Denied304HeadersMask
, 0);
61 httpHeaderCalcMask(&Denied304HeadersMask
, (const int *) Denied304HeadersArr
, countof(Denied304HeadersArr
));
65 HttpReply::HttpReply() : HttpMsg(hoReply
), date (0), last_modified (0), expires (0), surrogate_control (NULL
), content_range (NULL
), keep_alive (0), protoPrefix("HTTP/")
70 HttpReply::~HttpReply()
81 httpStatusLineInit(&sline
);
85 void HttpReply::reset()
88 // reset should not reset the protocol; could have made protoPrefix a
89 // virtual function instead, but it is not clear whether virtual methods
90 // are allowed with MEMPROXY_CLASS() and whether some cbdata void*
91 // conversions are not going to kill virtual tables
92 const String pfx
= protoPrefix
;
101 httpBodyClean(&body
);
103 httpHeaderClean(&header
);
104 httpStatusLineClean(&sline
);
107 /* absorb: copy the contents of a new reply to the old one, destroy new one */
109 HttpReply::absorb(HttpReply
* new_rep
)
114 new_rep
->header
.entries
.clean();
115 /* cannot use Clean() on new reply now! */
116 new_rep
->do_clean
= false;
117 new_rep
->cache_control
= NULL
; // helps with debugging
122 HttpReply::packHeadersInto(Packer
* p
) const
124 httpStatusLinePackInto(&sline
, p
);
125 httpHeaderPackInto(&header
, p
);
126 packerAppend(p
, "\r\n", 2);
130 HttpReply::packInto(Packer
* p
)
133 httpBodyPackInto(&body
, p
);
136 /* create memBuf, create mem-based packer, pack, destroy packer, return MemBuf */
140 MemBuf
*mb
= new MemBuf
;
144 packerToMemInit(&p
, mb
);
151 * swap: create swap-based packer, pack, destroy packer
152 * This eats the reply.
155 HttpReply::swapOut(StoreEntry
* e
)
159 storeEntryReplaceObject(e
, this);
163 httpPackedReply(HttpVersion ver
, http_status status
, const char *ctype
,
164 int clen
, time_t lmt
, time_t expires
)
166 HttpReply
*rep
= new HttpReply
;
167 rep
->setHeaders(ver
, status
, ctype
, NULL
, clen
, lmt
, expires
);
168 MemBuf
*mb
= rep
->pack();
174 HttpReply::make304 () const
176 static const http_hdr_type ImsEntries
[] = {HDR_DATE
, HDR_CONTENT_TYPE
, HDR_EXPIRES
, HDR_LAST_MODIFIED
, /* eof */ HDR_OTHER
};
178 HttpReply
*rv
= new HttpReply
;
182 /* rv->content_length; */
184 rv
->last_modified
= last_modified
;
185 rv
->expires
= expires
;
186 rv
->content_type
= content_type
;
187 /* rv->cache_control */
188 /* rv->content_range */
190 HttpVersion
ver(1,0);
191 httpStatusLineSet(&rv
->sline
, ver
,
192 HTTP_NOT_MODIFIED
, "");
194 for (t
= 0; ImsEntries
[t
] != HDR_OTHER
; ++t
)
195 if ((e
= httpHeaderFindEntry(&header
, ImsEntries
[t
])))
196 httpHeaderAddEntry(&rv
->header
, httpHeaderEntryClone(e
));
203 HttpReply::packed304Reply()
205 /* Not as efficient as skipping the header duplication,
206 * but easier to maintain
208 HttpReply
*temp
= make304 ();
209 MemBuf
*rv
= temp
->pack();
215 HttpReply::setHeaders(HttpVersion ver
, http_status status
, const char *reason
,
216 const char *ctype
, int clen
, time_t lmt
, time_t expires
)
219 httpStatusLineSet(&sline
, ver
, status
, reason
);
221 httpHeaderPutStr(hdr
, HDR_SERVER
, visible_appname_string
);
222 httpHeaderPutStr(hdr
, HDR_MIME_VERSION
, "1.0");
223 httpHeaderPutTime(hdr
, HDR_DATE
, squid_curtime
);
226 httpHeaderPutStr(hdr
, HDR_CONTENT_TYPE
, ctype
);
227 content_type
= ctype
;
229 content_type
= String();
232 httpHeaderPutInt(hdr
, HDR_CONTENT_LENGTH
, clen
);
235 httpHeaderPutTime(hdr
, HDR_EXPIRES
, expires
);
237 if (lmt
> 0) /* this used to be lmt != 0 @?@ */
238 httpHeaderPutTime(hdr
, HDR_LAST_MODIFIED
, lmt
);
240 date
= squid_curtime
;
242 content_length
= clen
;
250 HttpReply::redirect(http_status status
, const char *loc
)
253 HttpVersion
ver(1,0);
254 httpStatusLineSet(&sline
, ver
, status
, httpStatusString(status
));
256 httpHeaderPutStr(hdr
, HDR_SERVER
, full_appname_string
);
257 httpHeaderPutTime(hdr
, HDR_DATE
, squid_curtime
);
258 httpHeaderPutInt(hdr
, HDR_CONTENT_LENGTH
, 0);
259 httpHeaderPutStr(hdr
, HDR_LOCATION
, loc
);
260 date
= squid_curtime
;
264 /* compare the validators of two replies.
266 * 0 = they do not match
269 HttpReply::validatorsMatch(HttpReply
const * otherRep
) const
273 /* Numbers first - easiest to check */
275 /* TODO: remove -1 bypass */
277 if (content_length
!= otherRep
->content_length
278 && content_length
> -1 &&
279 otherRep
->content_length
> -1)
283 one
= httpHeaderGetStrOrList(&header
, HDR_ETAG
);
285 two
= httpHeaderGetStrOrList(&otherRep
->header
, HDR_ETAG
);
287 if (!one
.buf() || !two
.buf() || strcasecmp (one
.buf(), two
.buf())) {
293 if (last_modified
!= otherRep
->last_modified
)
297 one
= httpHeaderGetStrOrList(&header
, HDR_CONTENT_MD5
);
299 two
= httpHeaderGetStrOrList(&otherRep
->header
, HDR_CONTENT_MD5
);
301 if (!one
.buf() || !two
.buf() || strcasecmp (one
.buf(), two
.buf())) {
312 HttpReply::updateOnNotModified(HttpReply
const * freshRep
)
315 /* Can not update modified headers that don't match! */
316 assert (validatorsMatch(freshRep
));
319 /* update raw headers */
320 httpHeaderUpdate(&header
, &freshRep
->header
,
321 (const HttpHeaderMask
*) &Denied304HeadersMask
);
327 /* internal routines */
330 HttpReply::hdrExpirationTime()
332 /* The s-maxage and max-age directive takes priority over Expires */
336 if (cache_control
->s_maxage
>= 0)
337 return date
+ cache_control
->s_maxage
;
339 if (cache_control
->max_age
>= 0)
340 return date
+ cache_control
->max_age
;
343 * Conservatively handle the case when we have a max-age
344 * header, but no Date for reference?
347 if (cache_control
->s_maxage
>= 0)
348 return squid_curtime
;
350 if (cache_control
->max_age
>= 0)
351 return squid_curtime
;
355 if (Config
.onoff
.vary_ignore_expire
&&
356 httpHeaderHas(&header
, HDR_VARY
)) {
357 const time_t d
= httpHeaderGetTime(&header
, HDR_DATE
);
358 const time_t e
= httpHeaderGetTime(&header
, HDR_EXPIRES
);
364 if (httpHeaderHas(&header
, HDR_EXPIRES
)) {
365 const time_t e
= httpHeaderGetTime(&header
, HDR_EXPIRES
);
367 * HTTP/1.0 says that robust implementations should consider
368 * bad or malformed Expires header as equivalent to "expires
371 return e
< 0 ? squid_curtime
: e
;
377 /* sync this routine when you update HttpReply struct */
379 HttpReply::hdrCacheInit()
381 HttpMsg::hdrCacheInit();
383 content_length
= httpHeaderGetInt(&header
, HDR_CONTENT_LENGTH
);
384 date
= httpHeaderGetTime(&header
, HDR_DATE
);
385 last_modified
= httpHeaderGetTime(&header
, HDR_LAST_MODIFIED
);
386 surrogate_control
= httpHeaderGetSc(&header
);
387 content_range
= httpHeaderGetContRange(&header
);
388 keep_alive
= httpMsgIsPersistent(sline
.version
, &header
);
389 const char *str
= httpHeaderGetStr(&header
, HDR_CONTENT_TYPE
);
392 content_type
.limitInit(str
, strcspn(str
, ";\t "));
394 content_type
= String();
396 /* be sure to set expires after date and cache-control */
397 expires
= hdrExpirationTime();
400 /* sync this routine when you update HttpReply struct */
402 HttpReply::hdrCacheClean()
404 content_type
.clean();
407 httpHdrCcDestroy(cache_control
);
408 cache_control
= NULL
;
411 if (surrogate_control
) {
412 httpHdrScDestroy(surrogate_control
);
413 surrogate_control
= NULL
;
417 httpHdrContRangeDestroy(content_range
);
418 content_range
= NULL
;
423 * Returns the body size of a HTTP response
426 HttpReply::bodySize(method_t method
) const
428 if (sline
.version
.major
< 1)
430 else if (METHOD_HEAD
== method
)
432 else if (sline
.status
== HTTP_OK
)
433 (void) 0; /* common case, continue */
434 else if (sline
.status
== HTTP_NO_CONTENT
)
436 else if (sline
.status
== HTTP_NOT_MODIFIED
)
438 else if (sline
.status
< HTTP_OK
)
441 return content_length
;
444 bool HttpReply::sanityCheckStartLine(MemBuf
*buf
, http_status
*error
)
446 if (buf
->contentSize() >= protoPrefix
.size() && protoPrefix
.cmp(buf
->content(), protoPrefix
.size()) != 0) {
447 debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix
.buf() << ") in '" << buf
->content() << "'");
448 *error
= HTTP_INVALID_HEADER
;
455 void HttpReply::packFirstLineInto(Packer
*p
, bool unused
) const
457 httpStatusLinePackInto(&sline
, p
);
460 bool HttpReply::parseFirstLine(const char *blk_start
, const char *blk_end
)
462 return httpStatusLineParse(&sline
, protoPrefix
, blk_start
, blk_end
);