public static CacheValidationStatus ValidateCacheAfterResponse(HttpRequestCacheValidator ctx, HttpWebResponse resp) {
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_after_validation));
if ((ctx.CacheStream == Stream.Null || (int)ctx.CacheStatusCode == 0) && resp.StatusCode == HttpStatusCode.NotModified) {
if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_status_304));
return CacheValidationStatus.DoNotTakeFromCache;
}
if (ctx.RequestMethod == HttpMethod.Head) {
/*
The response to a HEAD request MAY be cacheable in the sense that the
information contained in the response MAY be used to update a
previously cached entity from that resource. If the new field values
indicate that the cached entity differs from the current entity (as
would be indicated by a change in Content-Length, Content-MD5, ETag
or Last-Modified), then the cache MUST treat the cache entry as
stale.
*/
bool invalidate = false;
if (ctx.ResponseEntityLength != -1 && ctx.ResponseEntityLength != ctx.CacheEntityLength) {
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_head_resp_has_different_content_length));
invalidate = true;
}
if (resp.Headers[HttpKnownHeaderNames.ContentMD5] != ctx.CacheHeaders[HttpKnownHeaderNames.ContentMD5]) {
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_head_resp_has_different_content_md5));
invalidate = true;
}
if (resp.Headers.ETag != ctx.CacheHeaders.ETag) {
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_head_resp_has_different_etag));
invalidate = true;
}
if (resp.StatusCode != HttpStatusCode.NotModified && resp.Headers.LastModified != ctx.CacheHeaders.LastModified)
{
if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_304_head_resp_has_different_last_modified));
invalidate = true;
}
if (invalidate) {
if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_existing_entry_has_to_be_discarded));
return CacheValidationStatus.RemoveFromCache;
}
}
// If server has returned 206 partial content
if (resp.StatusCode == HttpStatusCode.PartialContent) {
/*
A cache MUST NOT combine a 206 response with other previously cached
content if the ETag or Last-Modified headers do not match exactly,
see 13.5.4.
*/
// Sometime if ETag has been used the server won't include Last-Modified, which seems to be OK
if (ctx.CacheHeaders.ETag != ctx.Response.Headers.ETag ||
(ctx.CacheHeaders.LastModified != ctx.Response.Headers.LastModified
&& (ctx.Response.Headers.LastModified != null || ctx.Response.Headers.ETag == null)))
{
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_206_resp_non_matching_entry));
if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_existing_entry_should_be_discarded));
return CacheValidationStatus.RemoveFromCache;
}
// check does the live stream fit exactly into our cache tail
if (ctx.CacheEntry.StreamSize != ctx.ResponseRangeStart) {
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_206_resp_starting_position_not_adjusted));
return CacheValidationStatus.DoNotTakeFromCache;
}
Common.ReplaceOrUpdateCacheHeaders(ctx, resp);
if (ctx.RequestRangeUser) {
// This happens when a response is being downloaded page by page
// We request combining the streams
// A user will see data starting CacheStreamOffset of a combined stream
ctx.CacheStreamOffset = ctx.CacheEntry.StreamSize;
// This is a user response content length
ctx.CacheStreamLength = ctx.ResponseRangeEnd - ctx.ResponseRangeStart + 1;
// This is a new cache stream size
ctx.CacheEntityLength = ctx.ResponseEntityLength;
ctx.CacheStatusCode = resp.StatusCode;
ctx.CacheStatusDescription = resp.StatusDescription;
ctx.CacheHttpVersion = resp.ProtocolVersion;
}
else {
// This happens when previous response was downloaded partly
ctx.CacheStreamOffset = 0;
ctx.CacheStreamLength = ctx.ResponseEntityLength;
ctx.CacheEntityLength = ctx.ResponseEntityLength;
ctx.CacheStatusCode = HttpStatusCode.OK;
ctx.CacheStatusDescription = Common.OkDescription;
ctx.CacheHttpVersion = resp.ProtocolVersion;
ctx.CacheHeaders.Remove(HttpKnownHeaderNames.ContentRange);
if (ctx.CacheStreamLength == -1)
{ctx.CacheHeaders.Remove(HttpKnownHeaderNames.ContentLength);}
else
{ctx.CacheHeaders[HttpKnownHeaderNames.ContentLength] = ctx.CacheStreamLength.ToString(NumberFormatInfo.InvariantInfo);}
}
// At this point the protocol should create a combined stream made up of the cached and live streams
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_combined_resp_requested));
return CacheValidationStatus.CombineCachedAndServerResponse;
}
/*
304 Not Modified
The response MUST include the following header fields:
- Date, unless its omission is required by section 14.18.1
If a clockless origin server obeys these rules, and proxies and
clients add their own Date to any response received without one (as
already specified by [RFC 2068], section 14.19), caches will operate
correctly.
- ETag and/or Content-Location, if the header would have been sent
in a 200 response to the same request
- Expires, Cache-Control, and/or Vary, if the field-value might
differ from that sent in any previous response for the same
variant
*/
if (resp.StatusCode == HttpStatusCode.NotModified) {
WebHeaderCollection cc = resp.Headers;
string location = null;
string etag = null;
if ((ctx.CacheExpires != ctx.ResponseExpires) ||
(ctx.CacheLastModified != ctx.ResponseLastModified) ||
(ctx.CacheDate != ctx.ResponseDate) ||
(ctx.ResponseCacheControl.IsNotEmpty) ||
((location=cc[HttpKnownHeaderNames.ContentLocation]) != null && location != ctx.CacheHeaders[HttpKnownHeaderNames.ContentLocation]) ||
((etag=cc.ETag) != null && etag != ctx.CacheHeaders.ETag)) {
// Headers have to be updated
// Note that would allow a new E-Tag header to come in without changing the content.
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_updating_headers_on_304));
Common.ReplaceOrUpdateCacheHeaders(ctx, resp);
return CacheValidationStatus.ReturnCachedResponse;
}
//Try to not update headers if they are invariant or the same
int ignoredHeaders = 0;
if (etag != null) {
++ignoredHeaders;
}
if (location != null) {
++ignoredHeaders;
}
if (ctx.ResponseAge != TimeSpan.MinValue) {
++ignoredHeaders;
}
if (ctx.ResponseLastModified != DateTime.MinValue) {
++ignoredHeaders;
}
if (ctx.ResponseExpires != DateTime.MinValue) {
++ignoredHeaders;
}
if (ctx.ResponseDate != DateTime.MinValue) {
++ignoredHeaders;
}
if (cc.Via != null) {
++ignoredHeaders;
}
if (cc[HttpKnownHeaderNames.Connection] != null) {
++ignoredHeaders;
}
if (cc[HttpKnownHeaderNames.KeepAlive] != null) {
++ignoredHeaders;
}
if (cc.ProxyAuthenticate != null) {
++ignoredHeaders;
}
if (cc[HttpKnownHeaderNames.ProxyAuthorization] != null) {
++ignoredHeaders;
}
if (cc[HttpKnownHeaderNames.TE] != null) {
++ignoredHeaders;
}
if (cc[HttpKnownHeaderNames.TransferEncoding] != null) {
++ignoredHeaders;
}
if (cc[HttpKnownHeaderNames.Trailer] != null) {
++ignoredHeaders;
}
if (cc[HttpKnownHeaderNames.Upgrade] != null) {
++ignoredHeaders;
}
if (resp.Headers.Count <= ignoredHeaders) {
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_suppressing_headers_update_on_304));
ctx.CacheDontUpdateHeaders = true;
}
else {
Common.ReplaceOrUpdateCacheHeaders(ctx, resp);
}
return CacheValidationStatus.ReturnCachedResponse;
}
// Any other response
if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_status_code_not_304_206));
return CacheValidationStatus.DoNotTakeFromCache;
}