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