internal static TriState OnUpdateCache(HttpRequestCacheValidator ctx, HttpWebResponse resp) {
/*
Quick check on supported methods.
*/
if (ctx.RequestMethod != HttpMethod.Head &&
ctx.RequestMethod != HttpMethod.Get &&
ctx.RequestMethod != HttpMethod.Post) {
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_not_a_get_head_post));
return TriState.Unknown;
}
//If the entry did not exist ...
if (ctx.CacheStream == Stream.Null || (int)ctx.CacheStatusCode == 0) {
if(resp.StatusCode == HttpStatusCode.NotModified) {
if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_cannot_update_cache_if_304));
return TriState.Unknown;
}
if (ctx.RequestMethod == HttpMethod.Head) {
// Protection from some caching Head response when entry does not exist.
if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_cannot_update_cache_with_head_resp));
return TriState.Unknown;
}
}
if (resp == null) {
if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_http_resp_is_null));
return TriState.Unknown;
}
//
// We assume that ctx.ResponseCacheControl is already updated based on a live response
//
/*
no-store
... If sent in a response,
a cache MUST NOT store any part of either this response or the
request that elicited it.
*/
if (ctx.ResponseCacheControl.NoStore) {
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_cache_control_is_no_store));
return TriState.Unknown;
}
/*
If there is neither a cache validator nor an explicit expiration time
associated with a response, we do not expect it to be cached, but
certain caches MAY violate this expectation (for example, when little
or no network connectivity is available). A client can usually detect
that such a response was taken from a cache by comparing the Date
header to the current time.
*/
// NOTE: If no Expire and no Validator peresnt we choose to CACHE
//===============================================================
/*
Note: a new response that has an older Date header value than
existing cached responses is not cacheable.
*/
if (ctx.ResponseDate != DateTime.MinValue && ctx.CacheDate != DateTime.MinValue) {
if (ctx.ResponseDate < ctx.CacheDate) {
if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_older_than_cache));
return TriState.Unknown;
}
}
/*
public
Indicates that the response MAY be cached by any cache, even if it
would normally be non-cacheable or cacheable only within a non-
shared cache. (See also Authorization, section 14.8, for
additional details.)
*/
if (ctx.ResponseCacheControl.Public) {
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_cache_control_is_public));
return TriState.Valid;
}
// sometiem public cache can cache a response with "private" directive, subject to other restrictions
TriState result = TriState.Unknown;
/*
private
Indicates that all or part of the response message is intended for
a single user and MUST NOT be cached by a shared cache. This
allows an origin server to state that the specified parts of the
response are intended for only one user and are not a valid
response for requests by other users. A private (non-shared) cache
MAY cache the response.
*/
if (ctx.ResponseCacheControl.Private) {
if (!ctx.CacheEntry.IsPrivateEntry) {
if (ctx.ResponseCacheControl.PrivateHeaders == null) {
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_cache_control_is_private));
return TriState.Unknown;
}
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_cache_control_is_private_plus_headers));
for (int i = 0; i < ctx.ResponseCacheControl.PrivateHeaders.Length; ++i) {
ctx.CacheHeaders.Remove(ctx.ResponseCacheControl.PrivateHeaders[i]);
result = TriState.Valid;
}
}
else {
result = TriState.Valid;
}
}
/*
The RFC is funky on no-cache directive.
But the bottom line is sometime you CAN cache no-cache responses.
*/
if (ctx.ResponseCacheControl.NoCache)
{
if (ctx.ResponseLastModified == DateTime.MinValue && ctx.Response.Headers.ETag == null) {
if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_revalidation_required));
return TriState.Unknown;
}
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_needs_revalidation));
return TriState.Valid;
}
if (ctx.ResponseCacheControl.SMaxAge != -1 || ctx.ResponseCacheControl.MaxAge != -1) {
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_allows_caching, ctx.ResponseCacheControl.ToString()));
return TriState.Valid;
}
/*
When a shared cache (see section 13.7) receives a request
containing an Authorization field, it MUST NOT return the
corresponding response as a reply to any other request, unless one
of the following specific exceptions holds:
1. If the response includes the "s-maxage" cache-control
2. If the response includes the "must-revalidate" cache-control
3. If the response includes the "public" cache-control directive,
*/
if (!ctx.CacheEntry.IsPrivateEntry && ctx.Request.Headers[HttpKnownHeaderNames.Authorization] != null) {
// we've already passed an opportunity to cache.
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_auth_header_and_no_s_max_age));
return TriState.Unknown;
}
/*
POST
Responses to this method are not cacheable, unless the response
includes appropriate Cache-Control or Expires header fields.
*/
if (ctx.RequestMethod == HttpMethod.Post && resp.Headers.Expires == null) {
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_post_resp_without_cache_control_or_expires));
return TriState.Unknown;
}
/*
A response received with a status code of 200, 203, 206, 300, 301 or
410 MAY be stored by a cache and used in reply to a subsequent
request, subject to the expiration mechanism, unless a cache-control
directive prohibits caching. However, a cache that does not support
the Range and Content-Range headers MUST NOT cache 206 (Partial
Content) responses.
NOTE: We added 304 here which is correct
*/
if (resp.StatusCode == HttpStatusCode.NotModified ||
resp.StatusCode == HttpStatusCode.OK ||
resp.StatusCode == HttpStatusCode.NonAuthoritativeInformation ||
resp.StatusCode == HttpStatusCode.PartialContent ||
resp.StatusCode == HttpStatusCode.MultipleChoices ||
resp.StatusCode == HttpStatusCode.MovedPermanently ||
resp.StatusCode == HttpStatusCode.Gone)
{
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_valid_based_on_status_code, (int)resp.StatusCode));
return TriState.Valid;
}
/*
A response received with any other status code (e.g. status codes 302
and 307) MUST NOT be returned in a reply to a subsequent request
unless there are cache-control directives or another header(s) that
explicitly allow it. For example, these include the following: an
Expires header (section 14.21); a "max-age", "s-maxage", "must-
revalidate", "proxy-revalidate", "public" or "private" cache-control
directive (section 14.9).
*/
if (result != TriState.Valid) {
// otheriwse there was a "safe" private directive that allows caching
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_no_cache_control, (int)resp.StatusCode));
}
return result;
}