/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * 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 Netscape Communications are * Copyright (C) 2001 by Netscape Communications. All * Rights Reserved. * * Contributor(s): * Darin Fisher (original author) * Andreas M. Schneider */ #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(PRInt32 len) { mContentLength = len; if (len < 0) mHeaders.ClearHeader(nsHttp::Content_Length); else mHeaders.SetHeader(nsHttp::Content_Length, nsPrintfCString("%d", len)); } void nsHttpResponseHead::Flatten(nsACString &buf, PRBool pruneTransients) { if (mVersion == NS_HTTP_VERSION_0_9) return; buf.Append(NS_LITERAL_CSTRING("HTTP/")); if (mVersion == NS_HTTP_VERSION_1_1) buf.Append(NS_LITERAL_CSTRING("1.1 ")); else buf.Append(NS_LITERAL_CSTRING("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)); *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")); // 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. // const char *val = PeekHeader(nsHttp::Cache_Control); return val && PL_strcasestr(val, "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)) && PL_strcasestr(PeekHeader(nsHttp::Accept_Ranges), "bytes"); } PRBool nsHttpResponseHead::ExpiresInPast() { PRUint32 expiresVal, dateVal; 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 //----------------------------------------------------------------------------- void nsHttpResponseHead::ParseVersion(const char *str) { // Parse HTTP-Version:: "HTTP" "/" 1*DIGIT "." 1*DIGIT LOG(("nsHttpResponseHead::ParseVersion [version=%s]\n", str)); // make sure we have HTTP at the beginning if (PL_strncasecmp(str, "HTTP", 4) != 0) { LOG(("looks like a HTTP/0.9 response\n")); mVersion = NS_HTTP_VERSION_0_9; return; } str += 4; if (*str != '/') { LOG(("server did not send a version number; assuming HTTP/1.0\n")); // NCSA/1.5.2 has a bug in which it fails to send a version number // if the request version is HTTP/1.1, so we fall back on HTTP/1.0 mVersion = NS_HTTP_VERSION_1_0; return; } char *p = PL_strchr(str, '.'); if (p == nsnull) { LOG(("mal-formed server version; assuming HTTP/1.0\n")); mVersion = NS_HTTP_VERSION_1_0; return; } ++p; // let b point to the minor version int major = atoi(str + 1); int minor = atoi(p); if ((major > 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::ParseContentType(char *type) { LOG(("nsHttpResponseHead::ParseContentType [type=%s]\n", type)); // // Augmented BNF (from RFC 2616 section 3.7): // // header-value = media-type *( LWS "," LWS media-type ) // media-type = type "/" subtype *( LWS ";" LWS parameter ) // type = token // subtype = token // parameter = attribute "=" value // attribute = token // value = token | quoted-string // // // Examples: // // text/html // text/html, text/html // text/html,text/html; charset=ISO-8859-1 // text/html;charset=ISO-8859-1, text/html // application/octet-stream // // iterate over media-types char *nextType; do { nextType = (char *) strchr(type, ','); if (nextType) { *nextType = '\0'; ++nextType; } // type points at this media-type; locate first parameter if any char *charset = ""; char *param = (char *) strchr(type, ';'); if (param) { *param = '\0'; ++param; // iterate over parameters char *nextParam; do { nextParam = (char *) strchr(param, ';'); if (nextParam) { *nextParam = '\0'; ++nextParam; } // param points at this parameter param = net_FindCharNotInSet(param, HTTP_LWS); if (PL_strncasecmp(param, "charset=", 8) == 0) charset = param + 8; } while ((param = nextParam) != nsnull); } // trim LWS leading and trailing whitespace from type and charset. // charset cannot have leading whitespace. we include '(' in the // trailing trim set to catch media-type comments, which are not // at all standard, but may occur in rare cases. type = net_FindCharNotInSet(type, HTTP_LWS); char *typeEnd = net_FindCharInSet(type, HTTP_LWS "("); char *charsetEnd = net_FindCharInSet(charset, HTTP_LWS "("); // force content-type to be lowercase net_ToLowerCase(type, typeEnd - type); // if the server sent "*/*", it is meaningless, so do not store it. // also, if type is the same as mContentType, then just update the // charset. however, if charset is empty and mContentType hasn't // changed, then don't wipe-out an existing mContentCharset. we // also want to reject a mime-type if it does not include a slash. // some servers give junk after the charset parameter, which may // include a comma, so this check makes us a bit more tolerant. if (*type && strcmp(type, "*/*") != 0 && strchr(type, '/')) { PRBool eq = mContentType.Equals(Substring(type, typeEnd)); if (!eq) mContentType.Assign(type, typeEnd - type); if (!eq || *charset) mContentCharset.Assign(charset, charsetEnd - charset); } } while ((type = nextType) != nsnull); } void nsHttpResponseHead::ParseCacheControl(const char *val) { if (!(val && *val)) { // clear flags mCacheControlNoCache = PR_FALSE; mCacheControlNoStore = PR_FALSE; return; } const char *s = val; // search header value for occurance(s) of "no-cache" but ignore // occurance(s) of "no-cache=blah" while ((s = PL_strcasestr(s, "no-cache")) != nsnull) { s += (sizeof("no-cache") - 1); if (*s != '=') mCacheControlNoCache = PR_TRUE; } // search header value for occurance of "no-store" if (PL_strcasestr(val, "no-store")) 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 (PL_strcasestr(val, "no-cache")) mPragmaNoCache = PR_TRUE; }