michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim:set ts=4 sw=4 sts=4 et cin: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // HttpLog.h should generally be included first michael@0: #include "HttpLog.h" michael@0: michael@0: #include "nsHttpResponseHead.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "prtime.h" michael@0: #include "nsURLHelper.h" michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpResponseHead michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsHttpResponseHead::SetHeader(nsHttpAtom hdr, michael@0: const nsACString &val, michael@0: bool merge) michael@0: { michael@0: nsresult rv = mHeaders.SetHeader(hdr, val, merge); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // respond to changes in these headers. we need to reparse the entire michael@0: // header since the change may have merged in additional values. michael@0: if (hdr == nsHttp::Cache_Control) michael@0: ParseCacheControl(mHeaders.PeekHeader(hdr)); michael@0: else if (hdr == nsHttp::Pragma) michael@0: ParsePragma(mHeaders.PeekHeader(hdr)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHttpResponseHead::SetContentLength(int64_t len) michael@0: { michael@0: mContentLength = len; michael@0: if (len < 0) michael@0: mHeaders.ClearHeader(nsHttp::Content_Length); michael@0: else michael@0: mHeaders.SetHeader(nsHttp::Content_Length, nsPrintfCString("%lld", len)); michael@0: } michael@0: michael@0: void michael@0: nsHttpResponseHead::Flatten(nsACString &buf, bool pruneTransients) michael@0: { michael@0: if (mVersion == NS_HTTP_VERSION_0_9) michael@0: return; michael@0: michael@0: buf.AppendLiteral("HTTP/"); michael@0: if (mVersion == NS_HTTP_VERSION_2_0) michael@0: buf.AppendLiteral("2.0 "); michael@0: else if (mVersion == NS_HTTP_VERSION_1_1) michael@0: buf.AppendLiteral("1.1 "); michael@0: else michael@0: buf.AppendLiteral("1.0 "); michael@0: michael@0: buf.Append(nsPrintfCString("%u", unsigned(mStatus)) + michael@0: NS_LITERAL_CSTRING(" ") + michael@0: mStatusText + michael@0: NS_LITERAL_CSTRING("\r\n")); michael@0: michael@0: if (!pruneTransients) { michael@0: mHeaders.Flatten(buf, false); michael@0: return; michael@0: } michael@0: michael@0: // otherwise, we need to iterate over the headers and only flatten michael@0: // those that are appropriate. michael@0: uint32_t i, count = mHeaders.Count(); michael@0: for (i=0; i dateValue) michael@0: *result = now - dateValue; michael@0: michael@0: // Compute corrected received age michael@0: if (NS_SUCCEEDED(GetAgeValue(&ageValue))) michael@0: *result = std::max(*result, ageValue); michael@0: michael@0: MOZ_ASSERT(now >= requestTime, "bogus request time"); michael@0: michael@0: // Compute current age michael@0: *result += (now - requestTime); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // From section 13.2.4 of RFC2616, we compute the freshness lifetime of a cached michael@0: // response as follows: michael@0: // michael@0: // freshnessLifetime = max_age_value michael@0: // michael@0: // freshnessLifetime = expires_value - date_value michael@0: // michael@0: // freshnessLifetime = (date_value - last_modified_value) * 0.10 michael@0: // michael@0: // freshnessLifetime = 0 michael@0: // michael@0: nsresult michael@0: nsHttpResponseHead::ComputeFreshnessLifetime(uint32_t *result) const michael@0: { michael@0: *result = 0; michael@0: michael@0: // Try HTTP/1.1 style max-age directive... michael@0: if (NS_SUCCEEDED(GetMaxAgeValue(result))) michael@0: return NS_OK; michael@0: michael@0: *result = 0; michael@0: michael@0: uint32_t date = 0, date2 = 0; michael@0: if (NS_FAILED(GetDateValue(&date))) michael@0: date = NowInSeconds(); // synthesize a date header if none exists michael@0: michael@0: // Try HTTP/1.0 style expires header... michael@0: if (NS_SUCCEEDED(GetExpiresValue(&date2))) { michael@0: if (date2 > date) michael@0: *result = date2 - date; michael@0: // the Expires header can specify a date in the past. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Fallback on heuristic using last modified header... michael@0: if (NS_SUCCEEDED(GetLastModifiedValue(&date2))) { michael@0: LOG(("using last-modified to determine freshness-lifetime\n")); michael@0: LOG(("last-modified = %u, date = %u\n", date2, date)); michael@0: if (date2 <= date) { michael@0: // this only makes sense if last-modified is actually in the past michael@0: *result = (date - date2) / 10; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // These responses can be cached indefinitely. michael@0: if ((mStatus == 300) || nsHttp::IsPermanentRedirect(mStatus)) { michael@0: *result = uint32_t(-1); michael@0: return NS_OK; michael@0: } michael@0: michael@0: LOG(("nsHttpResponseHead::ComputeFreshnessLifetime [this = %x] " michael@0: "Insufficient information to compute a non-zero freshness " michael@0: "lifetime!\n", this)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsHttpResponseHead::MustValidate() const michael@0: { michael@0: LOG(("nsHttpResponseHead::MustValidate ??\n")); michael@0: michael@0: // Some response codes are cacheable, but the rest are not. This switch michael@0: // should stay in sync with the list in nsHttpChannel::ProcessResponse michael@0: switch (mStatus) { michael@0: // Success codes michael@0: case 200: michael@0: case 203: michael@0: case 206: michael@0: // Cacheable redirects michael@0: case 300: michael@0: case 301: michael@0: case 302: michael@0: case 304: michael@0: case 307: michael@0: case 308: michael@0: break; michael@0: // Uncacheable redirects michael@0: case 303: michael@0: case 305: michael@0: // Other known errors michael@0: case 401: michael@0: case 407: michael@0: case 412: michael@0: case 416: michael@0: default: // revalidate unknown error pages michael@0: LOG(("Must validate since response is an uncacheable error page\n")); michael@0: return true; michael@0: } michael@0: michael@0: // The no-cache response header indicates that we must validate this michael@0: // cached response before reusing. michael@0: if (NoCache()) { michael@0: LOG(("Must validate since response contains 'no-cache' header\n")); michael@0: return true; michael@0: } michael@0: michael@0: // Likewise, if the response is no-store, then we must validate this michael@0: // cached response before reusing. NOTE: it may seem odd that a no-store michael@0: // response may be cached, but indeed all responses are cached in order michael@0: // to support File->SaveAs, View->PageSource, and other browser features. michael@0: if (NoStore()) { michael@0: LOG(("Must validate since response contains 'no-store' header\n")); michael@0: return true; michael@0: } michael@0: michael@0: // Compare the Expires header to the Date header. If the server sent an michael@0: // Expires header with a timestamp in the past, then we must validate this michael@0: // cached response before reusing. michael@0: if (ExpiresInPast()) { michael@0: LOG(("Must validate since Expires < Date\n")); michael@0: return true; michael@0: } michael@0: michael@0: LOG(("no mandatory validation requirement\n")); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsHttpResponseHead::MustValidateIfExpired() const michael@0: { michael@0: // according to RFC2616, section 14.9.4: michael@0: // michael@0: // When the must-revalidate directive is present in a response received by a michael@0: // cache, that cache MUST NOT use the entry after it becomes stale to respond to michael@0: // a subsequent request without first revalidating it with the origin server. michael@0: // michael@0: return HasHeaderValue(nsHttp::Cache_Control, "must-revalidate"); michael@0: } michael@0: michael@0: bool michael@0: nsHttpResponseHead::IsResumable() const michael@0: { michael@0: // even though some HTTP/1.0 servers may support byte range requests, we're not michael@0: // going to bother with them, since those servers wouldn't understand If-Range. michael@0: // Also, while in theory it may be possible to resume when the status code michael@0: // is not 200, it is unlikely to be worth the trouble, especially for michael@0: // non-2xx responses. michael@0: return mStatus == 200 && michael@0: mVersion >= NS_HTTP_VERSION_1_1 && michael@0: PeekHeader(nsHttp::Content_Length) && michael@0: (PeekHeader(nsHttp::ETag) || PeekHeader(nsHttp::Last_Modified)) && michael@0: HasHeaderValue(nsHttp::Accept_Ranges, "bytes"); michael@0: } michael@0: michael@0: bool michael@0: nsHttpResponseHead::ExpiresInPast() const michael@0: { michael@0: uint32_t maxAgeVal, expiresVal, dateVal; michael@0: michael@0: // Bug #203271. Ensure max-age directive takes precedence over Expires michael@0: if (NS_SUCCEEDED(GetMaxAgeValue(&maxAgeVal))) { michael@0: return false; michael@0: } michael@0: michael@0: return NS_SUCCEEDED(GetExpiresValue(&expiresVal)) && michael@0: NS_SUCCEEDED(GetDateValue(&dateVal)) && michael@0: expiresVal < dateVal; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpResponseHead::UpdateHeaders(const nsHttpHeaderArray &headers) michael@0: { michael@0: LOG(("nsHttpResponseHead::UpdateHeaders [this=%p]\n", this)); michael@0: michael@0: uint32_t i, count = headers.Count(); michael@0: for (i=0; i 2) || ((major == 2) && (minor >= 0))) michael@0: mVersion = NS_HTTP_VERSION_2_0; michael@0: else if ((major == 1) && (minor >= 1)) michael@0: // at least HTTP/1.1 michael@0: mVersion = NS_HTTP_VERSION_1_1; michael@0: else michael@0: // treat anything else as version 1.0 michael@0: mVersion = NS_HTTP_VERSION_1_0; michael@0: } michael@0: michael@0: void michael@0: nsHttpResponseHead::ParseCacheControl(const char *val) michael@0: { michael@0: if (!(val && *val)) { michael@0: // clear flags michael@0: mCacheControlNoCache = false; michael@0: mCacheControlNoStore = false; michael@0: return; michael@0: } michael@0: michael@0: // search header value for occurrence(s) of "no-cache" but ignore michael@0: // occurrence(s) of "no-cache=blah" michael@0: if (nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS)) michael@0: mCacheControlNoCache = true; michael@0: michael@0: // search header value for occurrence of "no-store" michael@0: if (nsHttp::FindToken(val, "no-store", HTTP_HEADER_VALUE_SEPS)) michael@0: mCacheControlNoStore = true; michael@0: } michael@0: michael@0: void michael@0: nsHttpResponseHead::ParsePragma(const char *val) michael@0: { michael@0: LOG(("nsHttpResponseHead::ParsePragma [val=%s]\n", val)); michael@0: michael@0: if (!(val && *val)) { michael@0: // clear no-cache flag michael@0: mPragmaNoCache = false; michael@0: return; michael@0: } michael@0: michael@0: // Although 'Pragma: no-cache' is not a standard HTTP response header (it's michael@0: // a request header), caching is inhibited when this header is present so michael@0: // as to match existing Navigator behavior. michael@0: if (nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS)) michael@0: mPragmaNoCache = true; michael@0: } michael@0: michael@0: } // namespace mozilla::net michael@0: } // namespace mozilla