/* -*- 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 "plstr.h" #include "nsURLHelper.h" #include #include "mozilla-config.h" #include "plvmx.h" 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 now) { // for calculation purposes lets not allow the request to happen in the future requestTime = now; } if (NS_FAILED(GetDateValue(&dateValue))) { LOG(("nsHttpResponseHead::ComputeCurrentAge [this=%p] " "Date response header not set!\n", this)); // Assume we have a fast connection and that our clock // is in sync with the server. dateValue = now; } // Compute apparent age if (now > dateValue) *result = now - dateValue; // Compute corrected received age if (NS_SUCCEEDED(GetAgeValue(&ageValue))) *result = std::max(*result, ageValue); // 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 mCacheControlPrivate = false; mCacheControlNoCache = false; mCacheControlNoStore = false; return; } // search header value for occurrence of "private" if (nsHttp::FindToken(val, "private", HTTP_HEADER_VALUE_SEPS)) mCacheControlPrivate = true; // 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 net } // namespace mozilla