fixes bug 112564 "Cache-Control: no-cache should not affect back/forward

buttons" r=dougt, sr=brendan
This commit is contained in:
darin%netscape.com 2002-01-09 04:18:48 +00:00
Родитель 89c84455fe
Коммит a8fec387ad
5 изменённых файлов: 222 добавлений и 100 удалений

Просмотреть файл

@ -5162,8 +5162,8 @@ nsDocShell::AddToSessionHistory(nsIURI * aURI,
nsCOMPtr<nsIURI> referrerURI;
nsCOMPtr<nsISupports> cacheKey;
nsCOMPtr<nsISupports> cacheToken;
PRPackedBool expired = PR_FALSE;
nsXPIDLCString val;
PRBool expired = PR_FALSE;
PRBool discardLayoutState = PR_FALSE;
if (aChannel) {
nsCOMPtr<nsICachingChannel>
cacheChannel(do_QueryInterface(aChannel));
@ -5183,7 +5183,16 @@ nsDocShell::AddToSessionHistory(nsIURI * aURI,
if (httpChannel) {
httpChannel->GetUploadStream(getter_AddRefs(inputStream));
httpChannel->GetReferrer(getter_AddRefs(referrerURI));
httpChannel->GetResponseHeader("Cache-Control", getter_Copies(val));
// figure out if SH should be saving layout state (see bug 112564)
nsCOMPtr<nsISupports> securityInfo;
PRBool noStore = PR_FALSE, noCache = PR_FALSE;
httpChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
httpChannel->IsNoStoreResponse(&noStore);
httpChannel->IsNoCacheResponse(&noCache);
discardLayoutState = noStore || (noCache && securityInfo);
}
}
@ -5199,15 +5208,15 @@ nsDocShell::AddToSessionHistory(nsIURI * aURI,
* HistoryLayoutState. By default, SH will set this
* flag to PR_TRUE and save HistoryLayoutState.
*/
if (val && (PL_strcasestr(val, "no-store") || PL_strcasestr(val, "no-cache"))) {
if (discardLayoutState) {
entry->SetSaveLayoutStateFlag(PR_FALSE);
}
if (cacheToken) {
// Check if the page has expired from cache
nsCOMPtr<nsICacheEntryDescriptor> cacheEntryDesc(do_QueryInterface(cacheToken));
if (cacheEntryDesc) {
nsCOMPtr<nsICacheEntryInfo> cacheEntryInfo(do_QueryInterface(cacheToken));
if (cacheEntryInfo) {
PRUint32 expTime;
cacheEntryDesc->GetExpirationTime(&expTime);
cacheEntryInfo->GetExpirationTime(&expTime);
PRUint32 now = PRTimeToSeconds(PR_Now());
if (expTime <= now)
expired = PR_TRUE;

Просмотреть файл

@ -79,6 +79,18 @@ interface nsIHttpChannel : nsIChannel
void setResponseHeader(in string header, in string value);
void visitResponseHeaders(in nsIHttpHeaderVisitor visitor);
/**
* True if the server sent a "Cache-control: no-store" response header.
*/
boolean isNoStoreResponse();
/**
* True if the server sent the equivalent of a "Cache-control: no-cache"
* response header. Other equivalent response headers include: "Pragma:
* no-cache" and "Expires" with a date-value in the past.
*/
boolean isNoCacheResponse();
/**
* Get the charset for the response, which may be NULL if not specified
* by the server (ie. the Content-Type header may not specify a charset).

Просмотреть файл

@ -742,7 +742,7 @@ nsHttpChannel::UpdateExpirationTime()
NS_ENSURE_TRUE(mResponseHead, NS_ERROR_FAILURE);
if (!mResponseHead->MustRevalidate()) {
if (!mResponseHead->MustValidate()) {
nsresult rv;
PRUint32 freshnessLifetime, currentAge;
@ -841,33 +841,36 @@ nsHttpChannel::CheckCache()
if (mLoadFlags & LOAD_FROM_CACHE) {
LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
doValidation = PR_FALSE;
goto end;
}
// If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
// it's revalidated with the server.
if (mLoadFlags & VALIDATE_ALWAYS) {
else if (mLoadFlags & VALIDATE_ALWAYS) {
LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
doValidation = PR_TRUE;
goto end;
}
// check revalidation is strictly required.
if (mCachedResponseHead->MustRevalidate()) {
// Even if the VALIDATE_NEVER flag is set, there are still some cases in
// which we must validate the cached response with the server.
else if (mLoadFlags & VALIDATE_NEVER) {
LOG(("VALIDATE_NEVER set\n"));
// if no-store or if no-cache and ssl, validate cached response (see
// bug 112564 for an explanation of this logic)
if (mCachedResponseHead->NoStore() ||
(mCachedResponseHead->NoCache() && mConnectionInfo->UsingSSL())) {
LOG(("Validating based on (no-store || (no-cache && ssl)) logic\n"));
doValidation = PR_TRUE;
}
else {
LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
doValidation = PR_FALSE;
}
}
// check if validation is strictly required...
else if (mCachedResponseHead->MustValidate()) {
LOG(("Validating based on MustValidate() returning TRUE\n"));
doValidation = PR_TRUE;
goto end;
}
// delay checking this flag until we've verified that the response headers
// do not require mandatory revalidation.
if (mLoadFlags & VALIDATE_NEVER) {
LOG(("Not validating based on VALIDATE_NEVER flag\n"));
doValidation = PR_FALSE;
goto end;
}
// Check if the cache entry has expired...
{
else {
PRUint32 time = 0; // a temporary variable for storing time values...
rv = mCacheEntry->GetExpirationTime(&time);
@ -875,6 +878,8 @@ nsHttpChannel::CheckCache()
if (NowInSeconds() <= time)
doValidation = PR_FALSE;
else if (mCachedResponseHead->MustValidateIfExpired())
doValidation = PR_TRUE;
else if (mLoadFlags & VALIDATE_ONCE_PER_SESSION) {
// If the cached response does not include expiration infor-
// mation, then we must validate the response, despite whether
@ -894,7 +899,6 @@ nsHttpChannel::CheckCache()
// has been accessed in this session, and validate if so.
doValidation = (nsHttpHandler::get()->SessionStartTime() > time);
}
}
else
doValidation = PR_TRUE;
@ -902,10 +906,11 @@ nsHttpChannel::CheckCache()
LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
}
end:
mCachedContentIsValid = !doValidation;
if (doValidation) {
// add validation headers unless the cached response is marked no-store...
// this'll force no-store content to be refetched each time from the server.
if (doValidation && !mCachedResponseHead->NoStore()) {
const char *val;
// Add If-Modified-Since header if a Last-Modified was given
val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
@ -1016,7 +1021,6 @@ nsHttpChannel::CloseCacheEntry(nsresult status)
nsresult
nsHttpChannel::InitCacheEntry()
{
const char *val;
nsresult rv;
NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
@ -1029,16 +1033,10 @@ nsHttpChannel::InitCacheEntry()
LOG(("nsHttpChannel::InitCacheEntry [this=%x entry=%x]\n",
this, mCacheEntry.get()));
// XXX blow away any existing cache meta data
// The no-store directive within the 'Cache-Control:' header indicates
// that we should not store the response in the cache.
val = mResponseHead->PeekHeader(nsHttp::Cache_Control);
if (val && PL_strcasestr(val, "no-store")) {
LOG(("Inhibiting caching because of \"%s\"\n", val));
CloseCacheEntry(NS_ERROR_ABORT);
return NS_OK;
}
// that we must not store the response in a persistent cache.
if (mResponseHead->NoStore())
mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
// For HTTPS transactions, the storage policy will already be IN_MEMORY.
// We are concerned instead about load attributes which may have changed.
@ -1077,16 +1075,6 @@ nsHttpChannel::FinalizeCacheEntry()
LOG(("nsHttpChannel::FinalizeCacheEntry [this=%x]\n", this));
if (mResponseHead && mResponseHeadersModified) {
// The no-store directive within the 'Cache-Control:' header indicates
// that we should not store the response in the cache.
// XXX this should probably be done from within SetResponseHeader.
const char *val = mResponseHead->PeekHeader(nsHttp::Cache_Control);
if (val && PL_strcasestr(val, "no-store")) {
LOG(("Dooming cache entry because of \"%s\"\n", val));
mCacheEntry->Doom();
return NS_OK;
}
// Set the expiration time for this cache entry
nsresult rv = UpdateExpirationTime();
if (NS_FAILED(rv)) return rv;
@ -2281,6 +2269,26 @@ nsHttpChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *visitor)
return mResponseHead->Headers().VisitHeaders(visitor);
}
NS_IMETHODIMP
nsHttpChannel::IsNoStoreResponse(PRBool *value)
{
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
*value = mResponseHead->NoStore();
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::IsNoCacheResponse(PRBool *value)
{
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
*value = mResponseHead->NoCache();
if (!*value)
*value = mResponseHead->ExpiresInPast();
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetCharset(char **value)
{

Просмотреть файл

@ -31,6 +31,21 @@
// nsHttpResponseHead <public>
//-----------------------------------------------------------------------------
nsresult
nsHttpResponseHead::SetHeader(nsHttpAtom hdr, const char *val)
{
nsresult rv = mHeaders.SetHeader(hdr, val);
if (NS_FAILED(rv)) return rv;
// response to changes in these headers
if (hdr == nsHttp::Cache_Control)
ParseCacheControl(val);
else if (hdr == nsHttp::Pragma)
ParsePragma(val);
return NS_OK;
}
void
nsHttpResponseHead::SetContentLength(PRInt32 len)
{
@ -143,7 +158,7 @@ nsHttpResponseHead::ParseStatusLine(char *line)
if ((mVersion == NS_HTTP_VERSION_0_9) || !(line = PL_strchr(line, ' '))) {
mStatus = 200;
mStatusText.Adopt(nsCRT::strdup("OK"));
mStatusText = nsCRT::strdup("OK");
}
else {
// Status-Code
@ -156,14 +171,14 @@ nsHttpResponseHead::ParseStatusLine(char *line)
// Reason-Phrase is whatever is remaining of the line
if (!(line = PL_strchr(line, ' '))) {
LOG(("mal-formed response status line; assuming statusText = 'OK'\n"));
mStatusText.Adopt(nsCRT::strdup("OK"));
mStatusText = nsCRT::strdup("OK");
}
else
mStatusText.Adopt(nsCRT::strdup(++line));
mStatusText = nsCRT::strdup(++line);
}
LOG(("Have status line [version=%u status=%u statusText=%s]\n",
PRUintn(mVersion), PRUintn(mStatus), mStatusText.get()));
PRUintn(mVersion), PRUintn(mStatus), mStatusText));
}
void
@ -179,6 +194,10 @@ nsHttpResponseHead::ParseHeaderLine(char *line)
mContentLength = atoi(val);
else if (hdr == nsHttp::Content_Type)
ParseContentType(val);
else if (hdr == nsHttp::Cache_Control)
ParseCacheControl(val);
else if (hdr == nsHttp::Pragma)
ParsePragma(val);
}
// From section 13.2.3 of RFC2616, we compute the current age of a cached
@ -280,47 +299,33 @@ nsHttpResponseHead::ComputeFreshnessLifetime(PRUint32 *result)
}
PRBool
nsHttpResponseHead::MustRevalidate()
nsHttpResponseHead::MustValidate()
{
const char *val;
LOG(("nsHttpResponseHead::MustRevalidate ??\n"));
LOG(("nsHttpResponseHead::MustValidate ??\n"));
// If the must-revalidate directive is present in the cached response, data
// must always be revalidated with the server, even if the user has
// configured validation to be turned off (See RFC 2616, section 14.9.4).
val = PeekHeader(nsHttp::Cache_Control);
if (val && PL_strcasestr(val, "must-revalidate")) {
LOG(("Must revalidate based on \"%s\" header\n", val));
// 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;
}
// The no-cache directive within the 'Cache-Control:' header indicates
// that we must validate this cached response before reusing.
if (val && PL_strcasestr(val, "no-cache")) {
LOG(("Must revalidate based on \"%s\" header\n", val));
return PR_TRUE;
}
// XXX we are not quite handling no-cache correctly in this case. We really
// should check for field-names and only force validation if they match
// existing response headers. See RFC2616 section 14.9.1 for details.
// 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.
val = PeekHeader(nsHttp::Pragma);
if (val && PL_strcasestr(val, "no-cache")) {
LOG(("Must revalidate based on \"%s\" header\n", val));
// 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.
PRUint32 expiresVal, dateVal;
if (NS_SUCCEEDED(GetExpiresValue(&expiresVal)) &&
NS_SUCCEEDED(GetDateValue(&dateVal)) &&
expiresVal < dateVal) {
LOG(("Must revalidate since Expires < Date\n"));
if (ExpiresInPast()) {
LOG(("Must validate since Expires < Date\n"));
return PR_TRUE;
}
@ -339,14 +344,36 @@ nsHttpResponseHead::MustRevalidate()
if (val && (PL_strstr(val, "*") ||
PL_strcasestr(val, "accept-charset") ||
PL_strcasestr(val, "accept-language"))) {
LOG(("Must revalidate based on \"%s\" header\n", val));
LOG(("Must validate based on \"%s\" header\n", val));
return PR_TRUE;
}
LOG(("no mandatory revalidation requirement\n"));
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::ExpiresInPast()
{
PRUint32 expiresVal, dateVal;
return NS_SUCCEEDED(GetExpiresValue(&expiresVal)) &&
NS_SUCCEEDED(GetDateValue(&dateVal)) &&
expiresVal < dateVal;
}
nsresult
nsHttpResponseHead::UpdateHeaders(nsHttpHeaderArray &headers)
{
@ -410,10 +437,13 @@ nsHttpResponseHead::Reset()
mVersion = NS_HTTP_VERSION_1_1;
mStatus = 200;
mStatusText.Adopt(0);
mContentLength = -1;
mContentType.Adopt(0);
mContentCharset.Adopt(0);
mCacheControlNoStore = PR_FALSE;
mCacheControlNoCache = PR_FALSE;
mPragmaNoCache = PR_FALSE;
CRTFREEIF(mStatusText);
CRTFREEIF(mContentType);
CRTFREEIF(mContentCharset);
}
nsresult
@ -510,14 +540,19 @@ nsHttpResponseHead::ParseVersion(const char *str)
// This code is duplicated in nsMultiMixedConv.cpp. If you change it
// here, change it there, too!
nsresult
void
nsHttpResponseHead::ParseContentType(char *type)
{
LOG(("nsHttpResponseHead::ParseContentType [type=%s]\n", type));
// don't bother with an empty content-type header - bug 83465
if (!*type)
return NS_OK;
return;
// a response could have multiple content type headers... we'll honor
// the last one.
CRTFREEIF(mContentCharset);
CRTFREEIF(mContentType);
// we don't care about comments (although they are invalid here)
char *p = PL_strchr(type, '(');
@ -541,7 +576,7 @@ nsHttpResponseHead::ParseContentType(char *type)
} while ((*p3 == ' ') || (*p3 == '\t'));
*++p3 = 0; // overwrite first char after the charset field
mContentCharset.Adopt(nsCRT::strdup(p2));
mContentCharset = nsCRT::strdup(p2);
}
}
else
@ -556,7 +591,47 @@ nsHttpResponseHead::ParseContentType(char *type)
while (--p >= type)
*p = nsCRT::ToLower(*p);
mContentType.Adopt(nsCRT::strdup(type));
return NS_OK;
mContentType = nsCRT::strdup(type);
}
void
nsHttpResponseHead::ParseCacheControl(const char *val)
{
if (!val) {
// clear no-cache flag
mCacheControlNoCache = PR_FALSE;
return;
}
else if (!*val)
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")) {
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)
{
if (!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 (*val && !PL_strcasestr(val, "no-cache"))
mPragmaNoCache = PR_TRUE;
}

Просмотреть файл

@ -38,7 +38,13 @@ class nsHttpResponseHead
public:
nsHttpResponseHead() : mVersion(NS_HTTP_VERSION_1_1)
, mStatus(200)
, mContentLength(-1) {}
, mStatusText(nsnull)
, mContentLength(-1)
, mContentType(nsnull)
, mContentCharset(nsnull)
, mCacheControlNoStore(PR_FALSE)
, mCacheControlNoCache(PR_FALSE)
, mPragmaNoCache(PR_FALSE) {}
~nsHttpResponseHead() {}
nsHttpHeaderArray &Headers() { return mHeaders; }
@ -48,13 +54,15 @@ public:
PRInt32 ContentLength() { return mContentLength; }
const char *ContentType() { return mContentType; }
const char *ContentCharset() { return mContentCharset; }
PRBool NoStore() { return mCacheControlNoStore; }
PRBool NoCache() { return (mCacheControlNoCache || mPragmaNoCache); }
const char *PeekHeader(nsHttpAtom h) { return mHeaders.PeekHeader(h); }
nsresult SetHeader(nsHttpAtom h, const char *v) { return mHeaders.SetHeader(h, v); }
nsresult SetHeader(nsHttpAtom h, const char *v);
nsresult GetHeader(nsHttpAtom h, char **v) { return mHeaders.GetHeader(h, v); }
void ClearHeaders() { mHeaders.Clear(); }
void SetContentType(const char *s) { mContentType.Adopt(s ? nsCRT::strdup(s) : 0); }
void SetContentType(const char *s) { CRTFREEIF(mContentType); mContentType = (s ? nsCRT::strdup(s) : 0); }
void SetContentLength(PRInt32);
// write out the response status line and headers as a single text block,
@ -75,7 +83,12 @@ public:
// cache validation support methods
nsresult ComputeFreshnessLifetime(PRUint32 *);
nsresult ComputeCurrentAge(PRUint32 now, PRUint32 requestTime, PRUint32 *result);
PRBool MustRevalidate();
PRBool MustValidate();
PRBool MustValidateIfExpired();
// returns true if the Expires header has a value in the past relative to the
// value of the Date header.
PRBool ExpiresInPast();
// update headers...
nsresult UpdateHeaders(nsHttpHeaderArray &headers);
@ -93,16 +106,21 @@ public:
private:
void ParseVersion(const char *);
nsresult ParseContentType(char *);
void ParseContentType(char *);
void ParseCacheControl(const char *);
void ParsePragma(const char *);
private:
nsHttpHeaderArray mHeaders;
nsHttpVersion mVersion;
PRUint16 mStatus;
nsXPIDLCString mStatusText;
char *mStatusText;
PRInt32 mContentLength;
nsXPIDLCString mContentType;
nsXPIDLCString mContentCharset;
char *mContentType;
char *mContentCharset;
PRPackedBool mCacheControlNoStore;
PRPackedBool mCacheControlNoCache;
PRPackedBool mPragmaNoCache;
};
#endif // nsHttpResponseHead_h__