/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim:set ts=4 sw=4 sts=4 et cin: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla. * * The Initial Developer of the Original Code is * Netscape Communications. * Portions created by the Initial Developer are Copyright (C) 2001 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Darin Fisher (original author) * Andreas M. Schneider * Christian Biesinger * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include #include "nsHttpResponseHead.h" #include "nsPrintfCString.h" #include "prprf.h" #include "prtime.h" #include "nsCRT.h" //----------------------------------------------------------------------------- // nsHttpResponseHead //----------------------------------------------------------------------------- nsresult nsHttpResponseHead::SetHeader(nsHttpAtom hdr, const nsACString &val, PRBool 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(PRInt64 len) { mContentLength = len; if (!LL_GE_ZERO(len)) // < 0 mHeaders.ClearHeader(nsHttp::Content_Length); else mHeaders.SetHeader(nsHttp::Content_Length, nsPrintfCString(20, "%lld", len)); } void nsHttpResponseHead::Flatten(nsACString &buf, PRBool pruneTransients) { if (mVersion == NS_HTTP_VERSION_0_9) return; buf.AppendLiteral("HTTP/"); if (mVersion == NS_HTTP_VERSION_1_1) buf.AppendLiteral("1.1 "); else buf.AppendLiteral("1.0 "); buf.Append(nsPrintfCString("%u", PRUintn(mStatus)) + NS_LITERAL_CSTRING(" ") + mStatusText + NS_LITERAL_CSTRING("\r\n")); if (!pruneTransients) { mHeaders.Flatten(buf, PR_FALSE); return; } // otherwise, we need to iterate over the headers and only flatten // those that are appropriate. PRUint32 i, count = mHeaders.Count(); for (i=0; i dateValue) *result = now - dateValue; // Compute corrected received age if (NS_SUCCEEDED(GetAgeValue(&ageValue))) *result = PR_MAX(*result, ageValue); NS_ASSERTION(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(PRUint32 *result) { *result = 0; // Try HTTP/1.1 style max-age directive... if (NS_SUCCEEDED(GetMaxAgeValue(result))) return NS_OK; *result = 0; PRUint32 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) || (mStatus == 301)) { *result = PRUint32(-1); return NS_OK; } LOG(("nsHttpResponseHead::ComputeFreshnessLifetime [this = %x] " "Insufficient information to compute a non-zero freshness " "lifetime!\n", this)); return NS_OK; } PRBool nsHttpResponseHead::MustValidate() { 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: 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 PR_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 PR_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 PR_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 PR_TRUE; } LOG(("no mandatory validation requirement\n")); return PR_FALSE; } PRBool nsHttpResponseHead::MustValidateIfExpired() { // 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"); } PRBool nsHttpResponseHead::IsResumable() { // 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. return mVersion >= NS_HTTP_VERSION_1_1 && PeekHeader(nsHttp::Content_Length) && (PeekHeader(nsHttp::ETag) || PeekHeader(nsHttp::Last_Modified)) && HasHeaderValue(nsHttp::Accept_Ranges, "bytes"); } PRBool nsHttpResponseHead::ExpiresInPast() { PRUint32 maxAgeVal, expiresVal, dateVal; // Bug #203271. Ensure max-age directive takes precedence over Expires if (NS_SUCCEEDED(GetMaxAgeValue(&maxAgeVal))) { return PR_FALSE; } return NS_SUCCEEDED(GetExpiresValue(&expiresVal)) && NS_SUCCEEDED(GetDateValue(&dateVal)) && expiresVal < dateVal; } nsresult nsHttpResponseHead::UpdateHeaders(nsHttpHeaderArray &headers) { LOG(("nsHttpResponseHead::UpdateHeaders [this=%x]\n", this)); PRUint32 i, count = headers.Count(); for (i=0; i 1) || ((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 = PR_FALSE; mCacheControlNoStore = PR_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 = PR_TRUE; // search header value for occurrence of "no-store" if (nsHttp::FindToken(val, "no-store", HTTP_HEADER_VALUE_SEPS)) mCacheControlNoStore = PR_TRUE; } void nsHttpResponseHead::ParsePragma(const char *val) { LOG(("nsHttpResponseHead::ParsePragma [val=%s]\n", val)); if (!(val && *val)) { // clear no-cache flag mPragmaNoCache = PR_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 = PR_TRUE; }