1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim:set ts=4 sw=4 sts=4 et cin: */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is Mozilla.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications.
20 * Portions created by the Initial Developer are Copyright (C) 2001
21 * the Initial Developer. All Rights Reserved.
24 * Darin Fisher <darin@netscape.com> (original author)
25 * Andreas M. Schneider <clarence@clarence.de>
26 * Christian Biesinger <cbiesinger@web.de>
28 * Alternatively, the contents of this file may be used under the terms of
29 * either the GNU General Public License Version 2 or later (the "GPL"), or
30 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
40 * ***** END LICENSE BLOCK ***** */
43 #include "nsHttpResponseHead.h"
44 #include "nsPrintfCString.h"
49 //-----------------------------------------------------------------------------
50 // nsHttpResponseHead <public>
51 //-----------------------------------------------------------------------------
54 nsHttpResponseHead::SetHeader(nsHttpAtom hdr
,
55 const nsACString
&val
,
58 nsresult rv
= mHeaders
.SetHeader(hdr
, val
, merge
);
59 if (NS_FAILED(rv
)) return rv
;
61 // respond to changes in these headers. we need to reparse the entire
62 // header since the change may have merged in additional values.
63 if (hdr
== nsHttp::Cache_Control
)
64 ParseCacheControl(mHeaders
.PeekHeader(hdr
));
65 else if (hdr
== nsHttp::Pragma
)
66 ParsePragma(mHeaders
.PeekHeader(hdr
));
72 nsHttpResponseHead::SetContentLength(PRInt64 len
)
75 if (!LL_GE_ZERO(len
)) // < 0
76 mHeaders
.ClearHeader(nsHttp::Content_Length
);
78 mHeaders
.SetHeader(nsHttp::Content_Length
, nsPrintfCString(20, "%lld", len
));
82 nsHttpResponseHead::Flatten(nsACString
&buf
, PRBool pruneTransients
)
84 if (mVersion
== NS_HTTP_VERSION_0_9
)
87 buf
.AppendLiteral("HTTP/");
88 if (mVersion
== NS_HTTP_VERSION_1_1
)
89 buf
.AppendLiteral("1.1 ");
91 buf
.AppendLiteral("1.0 ");
93 buf
.Append(nsPrintfCString("%u", PRUintn(mStatus
)) +
94 NS_LITERAL_CSTRING(" ") +
96 NS_LITERAL_CSTRING("\r\n"));
98 if (!pruneTransients
) {
99 mHeaders
.Flatten(buf
, PR_FALSE
);
103 // otherwise, we need to iterate over the headers and only flatten
104 // those that are appropriate.
105 PRUint32 i
, count
= mHeaders
.Count();
106 for (i
=0; i
<count
; ++i
) {
108 const char *value
= mHeaders
.PeekHeaderAt(i
, header
);
110 if (!value
|| header
== nsHttp::Connection
111 || header
== nsHttp::Proxy_Connection
112 || header
== nsHttp::Keep_Alive
113 || header
== nsHttp::WWW_Authenticate
114 || header
== nsHttp::Proxy_Authenticate
115 || header
== nsHttp::Trailer
116 || header
== nsHttp::Transfer_Encoding
117 || header
== nsHttp::Upgrade
118 // XXX this will cause problems when we start honoring
119 // Cache-Control: no-cache="set-cookie", what to do?
120 || header
== nsHttp::Set_Cookie
)
123 // otherwise, write out the "header: value\r\n" line
124 buf
.Append(nsDependentCString(header
.get()) +
125 NS_LITERAL_CSTRING(": ") +
126 nsDependentCString(value
) +
127 NS_LITERAL_CSTRING("\r\n"));
132 nsHttpResponseHead::Parse(char *block
)
135 LOG(("nsHttpResponseHead::Parse [this=%x]\n", this));
137 // this command works on a buffer as prepared by Flatten, as such it is
138 // not very forgiving ;-)
140 char *p
= PL_strstr(block
, "\r\n");
142 return NS_ERROR_UNEXPECTED
;
145 ParseStatusLine(block
);
153 p
= PL_strstr(block
, "\r\n");
155 return NS_ERROR_UNEXPECTED
;
158 ParseHeaderLine(block
);
166 nsHttpResponseHead::ParseStatusLine(char *line
)
169 // Parse Status-Line:: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
175 if ((mVersion
== NS_HTTP_VERSION_0_9
) || !(line
= PL_strchr(line
, ' '))) {
177 mStatusText
.AssignLiteral("OK");
181 mStatus
= (PRUint16
) atoi(++line
);
183 LOG(("mal-formed response status; assuming status = 200\n"));
187 // Reason-Phrase is whatever is remaining of the line
188 if (!(line
= PL_strchr(line
, ' '))) {
189 LOG(("mal-formed response status line; assuming statusText = 'OK'\n"));
190 mStatusText
.AssignLiteral("OK");
193 mStatusText
= ++line
;
196 LOG(("Have status line [version=%u status=%u statusText=%s]\n",
197 PRUintn(mVersion
), PRUintn(mStatus
), mStatusText
.get()));
201 nsHttpResponseHead::ParseHeaderLine(char *line
)
203 nsHttpAtom hdr
= {0};
206 mHeaders
.ParseHeaderLine(line
, &hdr
, &val
);
207 // leading and trailing LWS has been removed from |val|
209 // handle some special case headers...
210 if (hdr
== nsHttp::Content_Length
) {
212 // permit only a single value here.
213 if (nsHttp::ParseInt64(val
, &len
))
214 mContentLength
= len
;
216 LOG(("invalid content-length!\n"));
218 else if (hdr
== nsHttp::Content_Type
) {
219 LOG(("ParseContentType [type=%s]\n", val
));
221 net_ParseContentType(nsDependentCString(val
),
222 mContentType
, mContentCharset
, &dummy
);
224 else if (hdr
== nsHttp::Cache_Control
)
225 ParseCacheControl(val
);
226 else if (hdr
== nsHttp::Pragma
)
230 // From section 13.2.3 of RFC2616, we compute the current age of a cached
231 // response as follows:
233 // currentAge = max(max(0, responseTime - dateValue), ageValue)
234 // + now - requestTime
236 // where responseTime == now
238 // This is typically a very small number.
241 nsHttpResponseHead::ComputeCurrentAge(PRUint32 now
,
242 PRUint32 requestTime
,
250 if (NS_FAILED(GetDateValue(&dateValue
))) {
251 LOG(("nsHttpResponseHead::ComputeCurrentAge [this=%x] "
252 "Date response header not set!\n", this));
253 // Assume we have a fast connection and that our clock
254 // is in sync with the server.
258 // Compute apparent age
260 *result
= now
- dateValue
;
262 // Compute corrected received age
263 if (NS_SUCCEEDED(GetAgeValue(&ageValue
)))
264 *result
= PR_MAX(*result
, ageValue
);
266 NS_ASSERTION(now
>= requestTime
, "bogus request time");
268 // Compute current age
269 *result
+= (now
- requestTime
);
273 // From section 13.2.4 of RFC2616, we compute the freshness lifetime of a cached
274 // response as follows:
276 // freshnessLifetime = max_age_value
278 // freshnessLifetime = expires_value - date_value
280 // freshnessLifetime = (date_value - last_modified_value) * 0.10
282 // freshnessLifetime = 0
285 nsHttpResponseHead::ComputeFreshnessLifetime(PRUint32
*result
)
289 // Try HTTP/1.1 style max-age directive...
290 if (NS_SUCCEEDED(GetMaxAgeValue(result
)))
295 PRUint32 date
= 0, date2
= 0;
296 if (NS_FAILED(GetDateValue(&date
)))
297 date
= NowInSeconds(); // synthesize a date header if none exists
299 // Try HTTP/1.0 style expires header...
300 if (NS_SUCCEEDED(GetExpiresValue(&date2
))) {
302 *result
= date2
- date
;
303 // the Expires header can specify a date in the past.
307 // Fallback on heuristic using last modified header...
308 if (NS_SUCCEEDED(GetLastModifiedValue(&date2
))) {
309 LOG(("using last-modified to determine freshness-lifetime\n"));
310 LOG(("last-modified = %u, date = %u\n", date2
, date
));
312 // this only makes sense if last-modified is actually in the past
313 *result
= (date
- date2
) / 10;
318 // These responses can be cached indefinitely.
319 if ((mStatus
== 300) || (mStatus
== 301)) {
320 *result
= PRUint32(-1);
324 LOG(("nsHttpResponseHead::ComputeFreshnessLifetime [this = %x] "
325 "Insufficient information to compute a non-zero freshness "
326 "lifetime!\n", this));
332 nsHttpResponseHead::MustValidate()
334 LOG(("nsHttpResponseHead::MustValidate ??\n"));
336 // Some response codes are cacheable, but the rest are not. This switch
337 // should stay in sync with the list in nsHttpChannel::ProcessResponse
343 // Cacheable redirects
350 // Uncacheable redirects
353 // Other known errors
358 default: // revalidate unknown error pages
359 LOG(("Must validate since response is an uncacheable error page\n"));
363 // The no-cache response header indicates that we must validate this
364 // cached response before reusing.
366 LOG(("Must validate since response contains 'no-cache' header\n"));
370 // Likewise, if the response is no-store, then we must validate this
371 // cached response before reusing. NOTE: it may seem odd that a no-store
372 // response may be cached, but indeed all responses are cached in order
373 // to support File->SaveAs, View->PageSource, and other browser features.
375 LOG(("Must validate since response contains 'no-store' header\n"));
379 // Compare the Expires header to the Date header. If the server sent an
380 // Expires header with a timestamp in the past, then we must validate this
381 // cached response before reusing.
382 if (ExpiresInPast()) {
383 LOG(("Must validate since Expires < Date\n"));
387 LOG(("no mandatory validation requirement\n"));
392 nsHttpResponseHead::MustValidateIfExpired()
394 // according to RFC2616, section 14.9.4:
396 // When the must-revalidate directive is present in a response received by a
397 // cache, that cache MUST NOT use the entry after it becomes stale to respond to
398 // a subsequent request without first revalidating it with the origin server.
400 return HasHeaderValue(nsHttp::Cache_Control
, "must-revalidate");
404 nsHttpResponseHead::IsResumable()
406 // even though some HTTP/1.0 servers may support byte range requests, we're not
407 // going to bother with them, since those servers wouldn't understand If-Range.
408 return mVersion
>= NS_HTTP_VERSION_1_1
&&
409 PeekHeader(nsHttp::Content_Length
) &&
410 (PeekHeader(nsHttp::ETag
) || PeekHeader(nsHttp::Last_Modified
)) &&
411 HasHeaderValue(nsHttp::Accept_Ranges
, "bytes");
415 nsHttpResponseHead::ExpiresInPast()
417 PRUint32 expiresVal
, dateVal
;
418 return NS_SUCCEEDED(GetExpiresValue(&expiresVal
)) &&
419 NS_SUCCEEDED(GetDateValue(&dateVal
)) &&
420 expiresVal
< dateVal
;
424 nsHttpResponseHead::UpdateHeaders(nsHttpHeaderArray
&headers
)
426 LOG(("nsHttpResponseHead::UpdateHeaders [this=%x]\n", this));
428 PRUint32 i
, count
= headers
.Count();
429 for (i
=0; i
<count
; ++i
) {
431 const char *val
= headers
.PeekHeaderAt(i
, header
);
434 NS_NOTREACHED("null header value");
438 // Ignore any hop-by-hop headers...
439 if (header
== nsHttp::Connection
||
440 header
== nsHttp::Proxy_Connection
||
441 header
== nsHttp::Keep_Alive
||
442 header
== nsHttp::Proxy_Authenticate
||
443 header
== nsHttp::Proxy_Authorization
|| // not a response header!
444 header
== nsHttp::TE
||
445 header
== nsHttp::Trailer
||
446 header
== nsHttp::Transfer_Encoding
||
447 header
== nsHttp::Upgrade
||
448 // Ignore any non-modifiable headers...
449 header
== nsHttp::Content_Location
||
450 header
== nsHttp::Content_MD5
||
451 header
== nsHttp::ETag
||
452 // Assume Cache-Control: "no-transform"
453 header
== nsHttp::Content_Encoding
||
454 header
== nsHttp::Content_Range
||
455 header
== nsHttp::Content_Type
||
456 // Ignore wacky headers too...
457 // this one is for MS servers that send "Content-Length: 0"
459 header
== nsHttp::Content_Length
) {
460 LOG(("ignoring response header [%s: %s]\n", header
.get(), val
));
463 LOG(("new response header [%s: %s]\n", header
.get(), val
));
465 // overwrite the current header value with the new value...
466 SetHeader(header
, nsDependentCString(val
));
474 nsHttpResponseHead::Reset()
476 LOG(("nsHttpResponseHead::Reset\n"));
480 mVersion
= NS_HTTP_VERSION_1_1
;
482 mContentLength
= LL_MAXUINT
;
483 mCacheControlNoStore
= PR_FALSE
;
484 mCacheControlNoCache
= PR_FALSE
;
485 mCacheControlPublic
= PR_FALSE
;
486 mPragmaNoCache
= PR_FALSE
;
487 mStatusText
.Truncate();
488 mContentType
.Truncate();
489 mContentCharset
.Truncate();
493 nsHttpResponseHead::ParseDateHeader(nsHttpAtom header
, PRUint32
*result
)
495 const char *val
= PeekHeader(header
);
497 return NS_ERROR_NOT_AVAILABLE
;
500 PRStatus st
= PR_ParseTimeString(val
, PR_TRUE
, &time
);
501 if (st
!= PR_SUCCESS
)
502 return NS_ERROR_NOT_AVAILABLE
;
504 *result
= PRTimeToSeconds(time
);
509 nsHttpResponseHead::GetAgeValue(PRUint32
*result
)
511 const char *val
= PeekHeader(nsHttp::Age
);
513 return NS_ERROR_NOT_AVAILABLE
;
515 *result
= (PRUint32
) atoi(val
);
519 // Return the value of the (HTTP 1.1) max-age directive, which itself is a
520 // component of the Cache-Control response header
522 nsHttpResponseHead::GetMaxAgeValue(PRUint32
*result
)
524 const char *val
= PeekHeader(nsHttp::Cache_Control
);
526 return NS_ERROR_NOT_AVAILABLE
;
528 const char *p
= PL_strcasestr(val
, "max-age=");
530 return NS_ERROR_NOT_AVAILABLE
;
532 *result
= (PRUint32
) atoi(p
+ 8);
537 nsHttpResponseHead::GetExpiresValue(PRUint32
*result
)
539 const char *val
= PeekHeader(nsHttp::Expires
);
541 return NS_ERROR_NOT_AVAILABLE
;
544 PRStatus st
= PR_ParseTimeString(val
, PR_TRUE
, &time
);
545 if (st
!= PR_SUCCESS
) {
546 // parsing failed... RFC 2616 section 14.21 says we should treat this
547 // as an expiration time in the past.
552 if (LL_CMP(time
, <, LL_Zero()))
555 *result
= PRTimeToSeconds(time
);
560 nsHttpResponseHead::TotalEntitySize()
562 const char* contentRange
= PeekHeader(nsHttp::Content_Range
);
564 return ContentLength();
566 // Total length is after a slash
567 const char* slash
= strrchr(contentRange
, '/');
569 return -1; // No idea what the length is
572 if (*slash
== '*') // Server doesn't know the length
576 if (!nsHttp::ParseInt64(slash
, &size
))
581 //-----------------------------------------------------------------------------
582 // nsHttpResponseHead <private>
583 //-----------------------------------------------------------------------------
586 nsHttpResponseHead::ParseVersion(const char *str
)
588 // Parse HTTP-Version:: "HTTP" "/" 1*DIGIT "." 1*DIGIT
590 LOG(("nsHttpResponseHead::ParseVersion [version=%s]\n", str
));
592 // make sure we have HTTP at the beginning
593 if (PL_strncasecmp(str
, "HTTP", 4) != 0) {
594 LOG(("looks like a HTTP/0.9 response\n"));
595 mVersion
= NS_HTTP_VERSION_0_9
;
601 LOG(("server did not send a version number; assuming HTTP/1.0\n"));
602 // NCSA/1.5.2 has a bug in which it fails to send a version number
603 // if the request version is HTTP/1.1, so we fall back on HTTP/1.0
604 mVersion
= NS_HTTP_VERSION_1_0
;
608 char *p
= PL_strchr(str
, '.');
610 LOG(("mal-formed server version; assuming HTTP/1.0\n"));
611 mVersion
= NS_HTTP_VERSION_1_0
;
615 ++p
; // let b point to the minor version
617 int major
= atoi(str
+ 1);
620 if ((major
> 1) || ((major
== 1) && (minor
>= 1)))
622 mVersion
= NS_HTTP_VERSION_1_1
;
624 // treat anything else as version 1.0
625 mVersion
= NS_HTTP_VERSION_1_0
;
629 nsHttpResponseHead::ParseCacheControl(const char *val
)
631 if (!(val
&& *val
)) {
633 mCacheControlNoCache
= PR_FALSE
;
634 mCacheControlNoStore
= PR_FALSE
;
635 mCacheControlPublic
= PR_FALSE
;
639 // search header value for occurrence(s) of "no-cache" but ignore
640 // occurrence(s) of "no-cache=blah"
641 if (nsHttp::FindToken(val
, "no-cache", HTTP_HEADER_VALUE_SEPS
))
642 mCacheControlNoCache
= PR_TRUE
;
644 // search header value for occurrence of "no-store"
645 if (nsHttp::FindToken(val
, "no-store", HTTP_HEADER_VALUE_SEPS
))
646 mCacheControlNoStore
= PR_TRUE
;
648 // search header value for occurrence of "public"
649 if (nsHttp::FindToken(val
, "public", HTTP_HEADER_VALUE_SEPS
))
650 mCacheControlPublic
= PR_TRUE
;
654 nsHttpResponseHead::ParsePragma(const char *val
)
656 LOG(("nsHttpResponseHead::ParsePragma [val=%s]\n", val
));
658 if (!(val
&& *val
)) {
659 // clear no-cache flag
660 mPragmaNoCache
= PR_FALSE
;
664 // Although 'Pragma: no-cache' is not a standard HTTP response header (it's
665 // a request header), caching is inhibited when this header is present so
666 // as to match existing Navigator behavior.
667 if (nsHttp::FindToken(val
, "no-cache", HTTP_HEADER_VALUE_SEPS
))
668 mPragmaNoCache
= PR_TRUE
;