The Labs \ Source Viewer \ SSCLI \ System.Net.Cache \ Rfc2616

  1. //------------------------------------------------------------------------------
  2. // <copyright file="_Rfc2616CacheValidators.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. /*++   
  16. Abstract:
  17.     The class implements set of HTTP validators as per RFC2616
  18. Author:
  19.     Alexei Vopilov    21-Dec-2002
  20. Revision History:
  21. --*/
  22. namespace System.Net.Cache
  23. {
  24.     using System;
  25.     using System.Net;
  26.     using System.IO;
  27.     using System.Globalization;
  28.     using System.Collections;
  29.     using System.Collections.Specialized;
  30.    
  31.    
  32.     //
  33.     // Caching RFC
  34.     //
  35.     internal class Rfc2616
  36.     {
  37.        
  38.         private Rfc2616()
  39.         {
  40.         }
  41.        
  42.         internal enum TriState
  43.         {
  44.             Unknown,
  45.             Valid,
  46.             Invalid
  47.         }
  48.        
  49. /*----------*/       
  50.         // Continue = Proceed to the next protocol stage.
  51.         // DoNotTakeFromCache = Don't used caches value for this request
  52.         // DoNotUseCache = Cache is not used for this request and response is not cached.
  53.         public static CacheValidationStatus OnValidateRequest(HttpRequestCacheValidator ctx)
  54.         {
  55.            
  56.             CacheValidationStatus result = Common.OnValidateRequest(ctx);
  57.            
  58.             if (result == CacheValidationStatus.DoNotUseCache) {
  59.                 return result;
  60.             }
  61.            
  62.             /*
  63.               HTTP/1.1 caches SHOULD treat "Pragma: no-cache" as if the client had
  64.               sent "Cache-Control: no-cache". No new Pragma directives will be
  65.               defined in HTTP.
  66.               we use above information to nuke pragma header (we control it itself)
  67.             */           
  68. ctx.Request.Headers.RemoveInternal(HttpKnownHeaderNames.Pragma);
  69.            
  70.             /*
  71.                 we want to control cache-control header as well, any specifi extensions should be done
  72.                 using a derived validator class and custom policy
  73.             */           
  74. ctx.Request.Headers.RemoveInternal(HttpKnownHeaderNames.CacheControl);
  75.            
  76.             if (ctx.Policy.Level == HttpRequestCacheLevel.NoCacheNoStore) {
  77.                 //adjust request headers since retrieval validators will be suppressed upon return.
  78.                 ctx.Request.Headers.AddInternal(HttpKnownHeaderNames.CacheControl, "no-store");
  79.                 ctx.Request.Headers.AddInternal(HttpKnownHeaderNames.CacheControl, "no-cache");
  80.                 ctx.Request.Headers.AddInternal(HttpKnownHeaderNames.Pragma, "no-cache");
  81.                 result = CacheValidationStatus.DoNotTakeFromCache;
  82.             }
  83.             else if (result == CacheValidationStatus.Continue) {
  84.                 if (ctx.Policy.Level == HttpRequestCacheLevel.Reload || ctx.Policy.Level == HttpRequestCacheLevel.NoCacheNoStore) {
  85.                     //adjust request headers since retrieval validators will be suppressed upon return.
  86.                     ctx.Request.Headers.AddInternal(HttpKnownHeaderNames.CacheControl, "no-cache");
  87.                     ctx.Request.Headers.AddInternal(HttpKnownHeaderNames.Pragma, "no-cache");
  88.                     result = CacheValidationStatus.DoNotTakeFromCache;
  89.                 }
  90.                 else if (ctx.Policy.Level == HttpRequestCacheLevel.Refresh) {
  91.                     //adjust request headers since retrieval validators will be suppressed upon return.
  92.                     ctx.Request.Headers.AddInternal(HttpKnownHeaderNames.CacheControl, "max-age=0");
  93.                     ctx.Request.Headers.AddInternal(HttpKnownHeaderNames.Pragma, "no-cache");
  94.                     result = CacheValidationStatus.DoNotTakeFromCache;
  95.                 }
  96.                 else if (ctx.Policy.Level == HttpRequestCacheLevel.Default) {
  97.                     //Transfer Policy into CacheControl directives
  98.                     if (ctx.Policy.MinFresh > TimeSpan.Zero) {
  99.                         ctx.Request.Headers.AddInternal(HttpKnownHeaderNames.CacheControl, "min-fresh=" + (int)ctx.Policy.MinFresh.TotalSeconds);
  100.                     }
  101.                     if (ctx.Policy.MaxAge != TimeSpan.MaxValue) {
  102.                         ctx.Request.Headers.AddInternal(HttpKnownHeaderNames.CacheControl, "max-age=" + (int)ctx.Policy.MaxAge.TotalSeconds);
  103.                     }
  104.                     if (ctx.Policy.MaxStale > TimeSpan.Zero) {
  105.                         ctx.Request.Headers.AddInternal(HttpKnownHeaderNames.CacheControl, "max-stale=" + (int)ctx.Policy.MaxStale.TotalSeconds);
  106.                     }
  107.                 }
  108.                 else if (ctx.Policy.Level == HttpRequestCacheLevel.CacheOnly || ctx.Policy.Level == HttpRequestCacheLevel.CacheOrNextCacheOnly) {
  109.                     // In case other validators will not be called
  110.                     ctx.Request.Headers.AddInternal(HttpKnownHeaderNames.CacheControl, "only-if-cached");
  111.                 }
  112.             }
  113.             return result;
  114.         }
  115. /*----------*/       
  116.         public static CacheFreshnessStatus OnValidateFreshness(HttpRequestCacheValidator ctx)
  117.         {
  118.             // This will figure out ctx.CacheAge and ctx.CacheMaxAge memebers
  119.             CacheFreshnessStatus result = Common.ComputeFreshness(ctx);
  120.            
  121.             /*
  122.               We note one exception to this rule: since some applications have
  123.               traditionally used GETs and HEADs with query URLs (those containing a
  124.               "?" in the rel_path part) to perform operations with significant side
  125.               effects, caches MUST NOT treat responses to such URIs as fresh unless
  126.               the server provides an explicit expiration time. This specifically
  127.               means that responses from HTTP/1.0 servers for such URIs SHOULD NOT
  128.               be taken from a cache. See section 9.1.1 for related information.
  129.             */           
  130. if (ctx.Uri.Query.Length != 0) {
  131.                 if (ctx.CacheHeaders.Expires == null && (ctx.CacheEntry.IsPrivateEntry ? ctx.CacheCacheControl.MaxAge == -1 : ctx.CacheCacheControl.SMaxAge == -1)) {
  132.                     if (Logging.On)
  133.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_uri_with_query_has_no_expiration));
  134.                     return CacheFreshnessStatus.Stale;
  135.                 }
  136.                 if (ctx.CacheHttpVersion.Major <= 1 && ctx.CacheHttpVersion.Minor < 1) {
  137.                     if (Logging.On)
  138.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_uri_with_query_and_cached_resp_from_http_10));
  139.                     return CacheFreshnessStatus.Stale;
  140.                 }
  141.             }
  142.            
  143.             return result;
  144.            
  145.         }
  146.        
  147. /*----------*/       
  148.         // ReturnCachedResponse = Return cached response to the application
  149.         // DoNotTakeFromCache = Don't used caches value for this request
  150.         // Continue = Proceed to the next protocol stage.
  151.         public static CacheValidationStatus OnValidateCache(HttpRequestCacheValidator ctx)
  152.         {
  153.            
  154.             if (Common.ValidateCacheByVaryHeader(ctx) == TriState.Invalid) {
  155.                 // RFC 2616 is tricky on this. In theory we could make a conditional request.
  156.                 // However we rather will not.
  157.                 // And the reason can be deducted from the RFC definitoin of the response Vary Header.
  158.                 return CacheValidationStatus.DoNotTakeFromCache;
  159.             }
  160.            
  161.            
  162.             // For Revalidate option we perform a wire request anyway
  163.             if (ctx.Policy.Level == HttpRequestCacheLevel.Revalidate) {
  164.                 return Common.TryConditionalRequest(ctx);
  165.             }
  166.            
  167.             if (Common.ValidateCacheBySpecialCases(ctx) == TriState.Invalid) {
  168.                 // This takes over the cache policy since the cache content may be sematically incorrect
  169.                 if (ctx.Policy.Level == HttpRequestCacheLevel.CacheOnly) {
  170.                     // Cannot do a wire request
  171.                     return CacheValidationStatus.DoNotTakeFromCache;
  172.                 }
  173.                 return Common.TryConditionalRequest(ctx);
  174.             }
  175.            
  176.            
  177.             bool enoughFresh = Common.ValidateCacheByClientPolicy(ctx);
  178.            
  179.             if (enoughFresh || ctx.Policy.Level == HttpRequestCacheLevel.CacheOnly || ctx.Policy.Level == HttpRequestCacheLevel.CacheIfAvailable || ctx.Policy.Level == HttpRequestCacheLevel.CacheOrNextCacheOnly) {
  180.                 // The freshness does not matter, check does user requested Range fits into cached entry
  181.                 CacheValidationStatus result = Common.TryResponseFromCache(ctx);
  182.                
  183.                 if (result != CacheValidationStatus.ReturnCachedResponse) {
  184.                     if (ctx.Policy.Level == HttpRequestCacheLevel.CacheOnly) {
  185.                         // Cannot do a wire request
  186.                         return CacheValidationStatus.DoNotTakeFromCache;
  187.                     }
  188.                     return result;
  189.                 }
  190.                
  191.                 if (Logging.On)
  192.                     Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_valid_as_fresh_or_because_policy, ctx.Policy.ToString()));
  193.                 return CacheValidationStatus.ReturnCachedResponse;
  194.             }
  195.             // This will return either Continue=conditional request or DoNotTakeFromCache==Unconditional request
  196.             return Common.TryConditionalRequest(ctx);
  197.         }
  198.        
  199. /*----------*/       
  200.         // Returns
  201.         // RetryResponseFromServer = Retry this request as the result of invalid response received
  202.         // Continue = The response can be accepted
  203.         public static CacheValidationStatus OnValidateResponse(HttpRequestCacheValidator ctx)
  204.         {
  205.             //
  206.             // At this point we assume that policy >= CacheOrNextCacheOnly && policy < Refresh
  207.             //
  208.            
  209.            
  210.             // If there was a retry already, it should go with cache disabled so by default we won't retry it again
  211.             if (ctx.ResponseCount > 1) {
  212.                 if (Logging.On)
  213.                     Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_accept_based_on_retry_count, ctx.ResponseCount));
  214.                 return CacheValidationStatus.Continue;
  215.             }
  216.            
  217.             // We don't convert user-range request to a conditional one
  218.             if (ctx.RequestRangeUser) {
  219.                 // was a user range request, we did not touch it.
  220.                 return CacheValidationStatus.Continue;
  221.             }
  222.            
  223.             //If a live response has older Date, then request should be retried
  224.             if (ctx.CacheDate != DateTime.MinValue && ctx.ResponseDate != DateTime.MinValue && ctx.CacheDate > ctx.ResponseDate) {
  225.                 if (Logging.On)
  226.                     Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_date_header_older_than_cache_entry));
  227.                 Common.ConstructUnconditionalRefreshRequest(ctx);
  228.                 return CacheValidationStatus.RetryResponseFromServer;
  229.             }
  230.            
  231.             HttpWebResponse resp = ctx.Response as HttpWebResponse;
  232.             if (ctx.RequestRangeCache && resp.StatusCode == HttpStatusCode.RequestedRangeNotSatisfiable) {
  233.                
  234.                 if (Logging.On)
  235.                     Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_server_didnt_satisfy_range, ctx.Request.Headers[HttpKnownHeaderNames.Range]));
  236.                 Common.ConstructUnconditionalRefreshRequest(ctx);
  237.                 return CacheValidationStatus.RetryResponseFromServer;
  238.             }
  239.            
  240.            
  241.             if (resp.StatusCode == HttpStatusCode.NotModified) {
  242.                 if (ctx.RequestIfHeader1 == null) {
  243.                     if (Logging.On)
  244.                         Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_304_received_on_unconditional_request));
  245.                     Common.ConstructUnconditionalRefreshRequest(ctx);
  246.                     return CacheValidationStatus.RetryResponseFromServer;
  247.                 }
  248.                 else if (ctx.RequestRangeCache) {
  249.                     // The way _we_ create range requests shoyuld never result in 304
  250.                     if (Logging.On)
  251.                         Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_304_received_on_unconditional_request_expected_200_206));
  252.                     Common.ConstructUnconditionalRefreshRequest(ctx);
  253.                     return CacheValidationStatus.RetryResponseFromServer;
  254.                 }
  255.             }
  256.            
  257.             if (ctx.CacheHttpVersion.Major <= 1 && resp.ProtocolVersion.Major <= 1 && ctx.CacheHttpVersion.Minor < 1 && resp.ProtocolVersion.Minor < 1 && ctx.CacheLastModified > ctx.ResponseLastModified) {
  258.                 if (Logging.On)
  259.                     Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_last_modified_header_older_than_cache_entry));
  260.                 // On http <= 1.0 cache LastModified > resp LastModified
  261.                 Common.ConstructUnconditionalRefreshRequest(ctx);
  262.                 return CacheValidationStatus.RetryResponseFromServer;
  263.             }
  264.            
  265.             if (ctx.Policy.Level == HttpRequestCacheLevel.Default && ctx.ResponseAge != TimeSpan.MinValue) {
  266.                 // If the client has requested MaxAge/MinFresh/MaxStale
  267.                 // check does the response meet the requirements
  268.                 if ((ctx.ResponseAge > ctx.Policy.MaxAge) || (ctx.ResponseExpires != DateTime.MinValue && (ctx.Policy.MinFresh > TimeSpan.Zero && (ctx.ResponseExpires - DateTime.UtcNow) < ctx.Policy.MinFresh) || (ctx.Policy.MaxStale > TimeSpan.Zero && (DateTime.UtcNow - ctx.ResponseExpires) > ctx.Policy.MaxStale))) {
  269.                     if (Logging.On)
  270.                         Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_freshness_outside_policy_limits));
  271.                     Common.ConstructUnconditionalRefreshRequest(ctx);
  272.                     return CacheValidationStatus.RetryResponseFromServer;
  273.                 }
  274.             }
  275.            
  276.             //Cleanup what we've done to this request since protcol can resubmit for auth or redirect.
  277.             if (ctx.RequestIfHeader1 != null) {
  278.                 ctx.Request.Headers.RemoveInternal(ctx.RequestIfHeader1);
  279.                 ctx.RequestIfHeader1 = null;
  280.             }
  281.             if (ctx.RequestIfHeader2 != null) {
  282.                 ctx.Request.Headers.RemoveInternal(ctx.RequestIfHeader2);
  283.                 ctx.RequestIfHeader2 = null;
  284.             }
  285.             if (ctx.RequestRangeCache) {
  286.                 ctx.Request.Headers.RemoveInternal(HttpKnownHeaderNames.Range);
  287.                 ctx.RequestRangeCache = false;
  288.             }
  289.             return CacheValidationStatus.Continue;
  290.         }
  291.        
  292. /*----------*/       
  293.         // Returns:
  294.         // CacheResponse = Replace cache entry with received live response
  295.         // UpdateResponseInformation = Update Metadata of cache entry using live response headers
  296.         // RemoveFromCache = Remove cache entry referenced to by a cache key.
  297.         // Continue = Simply do not update cache.
  298.         //
  299.         public static CacheValidationStatus OnUpdateCache(HttpRequestCacheValidator ctx)
  300.         {
  301.            
  302.             if (ctx.CacheStatusCode == HttpStatusCode.NotModified) {
  303.                 if (Logging.On)
  304.                     Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_need_to_remove_invalid_cache_entry_304));
  305.                 return CacheValidationStatus.RemoveFromCache;
  306.             }
  307.            
  308.             HttpWebResponse resp = ctx.Response as HttpWebResponse;
  309.             if (Logging.On)
  310.                 Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_status, resp.StatusCode));
  311.            
  312.            
  313.             /*********
  314.                 Vs Whidbey#127214
  315.                 It was decided not to play with ResponseContentLocation in our implementation.
  316.                 A derived class may still want to play.
  317.             // Compute new Cache Update Key if Content-Location is present on the response
  318.             if (ctx.ResponseContentLocation != null) {
  319.                 if (!Uri.TryParse(ctx.ResponseContentLocation, true, true, out cacheUri)) {
  320.                     if(Logging.On)Logging.PrintError(Logging.RequestCache, "Cannot parse Uri from Response Content-Location: " + ctx.ResponseContentLocation);
  321.                     return CacheValidationStatus.RemoveFromCache;
  322.                 }
  323.                 if (!cacheUri.IsAbsoluteUri) {
  324.                     try {
  325.                         ctx.CacheKey = new Uri(ctx.RequestUri, cacheUri);
  326.                     }
  327.                     catch {
  328.                         return CacheValidationStatus.RemoveFromCache;
  329.                     }
  330.                 }
  331.             }
  332.             *********/           
  333.            
  334. if (ctx.ValidationStatus == CacheValidationStatus.RemoveFromCache) {
  335.                 return CacheValidationStatus.RemoveFromCache;
  336.             }
  337.            
  338.             CacheValidationStatus noUpdateResult = (ctx.RequestMethod >= HttpMethod.Post && ctx.RequestMethod <= HttpMethod.Delete || ctx.RequestMethod == HttpMethod.Other) ? CacheValidationStatus.RemoveFromCache : CacheValidationStatus.DoNotUpdateCache;
  339.            
  340.             if (Common.OnUpdateCache(ctx, resp) != TriState.Valid) {
  341.                 return noUpdateResult;
  342.             }
  343.            
  344.             CacheValidationStatus result = CacheValidationStatus.CacheResponse;
  345.             ctx.CacheEntry.IsPartialEntry = false;
  346.            
  347.             if (resp.StatusCode == HttpStatusCode.NotModified || ctx.RequestMethod == HttpMethod.Head) {
  348.                 result = CacheValidationStatus.UpdateResponseInformation;
  349.                
  350.                 // This may take a shorter path when updating the entry
  351.                 if (Logging.On)
  352.                     Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_304_or_request_head));
  353.                 if (ctx.CacheDontUpdateHeaders) {
  354.                     if (Logging.On)
  355.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_dont_update_cached_headers));
  356.                     ctx.CacheHeaders = null;
  357.                     ctx.CacheEntry.ExpiresUtc = ctx.ResponseExpires;
  358.                     ctx.CacheEntry.LastModifiedUtc = ctx.ResponseLastModified;
  359.                     if (ctx.Policy.Level == HttpRequestCacheLevel.Default) {
  360.                         ctx.CacheEntry.MaxStale = ctx.Policy.MaxStale;
  361.                     }
  362.                     else {
  363.                         ctx.CacheEntry.MaxStale = TimeSpan.MinValue;
  364.                     }
  365.                     ctx.CacheEntry.LastSynchronizedUtc = DateTime.UtcNow;
  366.                 }
  367.                 else {
  368.                     if (Logging.On)
  369.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_update_cached_headers));
  370.                 }
  371.             }
  372.             else if (resp.StatusCode == HttpStatusCode.PartialContent) {
  373.                 // Check on whether the user requested range can be appended to the cache entry
  374.                 // We only support combining of non-overlapped increasing bytes ranges
  375.                 if (ctx.CacheEntry.StreamSize != ctx.ResponseRangeStart && ctx.ResponseRangeStart != 0) {
  376.                     if (Logging.On)
  377.                         Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_partial_resp_not_combined_with_existing_entry, ctx.CacheEntry.StreamSize, ctx.ResponseRangeStart));
  378.                     return noUpdateResult;
  379.                 }
  380.                
  381.                 // We might be appending a live stream to cache BUT user has asked for a specific range.
  382.                 // Hence don't reset CacheStreamOffset here so the protocol will create a cache forwarding stream that will hide first bytes from the user
  383.                 if (!ctx.RequestRangeUser) {
  384.                     ctx.CacheStreamOffset = 0;
  385.                 }
  386.                
  387.                 // Below code assumes that a combined response has been given to the user,
  388.                
  389.                 Common.ReplaceOrUpdateCacheHeaders(ctx, resp);
  390.                
  391.                 ctx.CacheHttpVersion = resp.ProtocolVersion;
  392.                 ctx.CacheEntityLength = ctx.ResponseEntityLength;
  393.                 ctx.CacheStreamLength = ctx.CacheEntry.StreamSize = ctx.ResponseRangeEnd + 1;
  394.                 if (ctx.CacheEntityLength > 0 && ctx.CacheEntityLength == ctx.CacheEntry.StreamSize) {
  395.                     //eventually cache is about to store a complete response
  396.                     Common.Construct200ok(ctx);
  397.                 }
  398.                 else
  399.                     Common.Construct206PartialContent(ctx, 0);
  400.             }
  401.             else {
  402.                 Common.ReplaceOrUpdateCacheHeaders(ctx, resp);
  403.                
  404.                 ctx.CacheHttpVersion = resp.ProtocolVersion;
  405.                 ctx.CacheStatusCode = resp.StatusCode;
  406.                 ctx.CacheStatusDescription = resp.StatusDescription;
  407.                 ctx.CacheEntry.StreamSize = resp.ContentLength;
  408.             }
  409.            
  410.             return result;
  411.         }
  412.        
  413.        
  414.         //
  415.         // Implements various cache validation helper methods
  416.         //
  417.         static internal class Common
  418.         {
  419.             public const string PartialContentDescription = "Partial Content";
  420.             public const string OkDescription = "OK";
  421.             //
  422.             // Implements logic as of the Request caching suitability.
  423.             //
  424.             // Returns:
  425.             // Continue = Proceed to the next protocol stage.
  426.             // DoNotTakeFromCache = Don't use cached response for this request
  427.             // DoNotUseCache = Cache is not used for this request and response is not cached.
  428.             public static CacheValidationStatus OnValidateRequest(HttpRequestCacheValidator ctx)
  429.             {
  430.                
  431.                 /*
  432.                   Some HTTP methods MUST cause a cache to invalidate an entity. This is
  433.                   either the entity referred to by the Request-URI, or by the Location
  434.                   or Content-Location headers (if present). These methods are:
  435.                   PUT, DELETE, POST.
  436.                   A cache that passes through requests for methods it does not
  437.                   understand SHOULD invalidate any entities referred to by the
  438.                   Request-URI
  439.                 */               
  440. if (ctx.RequestMethod >= HttpMethod.Post && ctx.RequestMethod <= HttpMethod.Delete) {
  441.                     if (ctx.Policy.Level == HttpRequestCacheLevel.CacheOnly) {
  442.                         // Throw because the request must hit the wire and it's cache-only policy
  443.                         ctx.FailRequest(WebExceptionStatus.RequestProhibitedByCachePolicy);
  444.                     }
  445.                     // here we could return a hint on removing existing entry, but UpdateCache should handle this case correctly
  446.                     return CacheValidationStatus.DoNotTakeFromCache;
  447.                 }
  448.                 //
  449.                 // Additionally to said above we can only cache GET or HEAD, for any other methods we request bypassing cache.
  450.                 //
  451.                 if (ctx.RequestMethod < HttpMethod.Head || ctx.RequestMethod > HttpMethod.Get) {
  452.                     if (ctx.Policy.Level == HttpRequestCacheLevel.CacheOnly) {
  453.                         // Throw because the request must hit the wire and it's cache-only policy
  454.                         ctx.FailRequest(WebExceptionStatus.RequestProhibitedByCachePolicy);
  455.                     }
  456.                     return CacheValidationStatus.DoNotUseCache;
  457.                 }
  458.                
  459.                
  460.                 if (ctx.Request.Headers[HttpKnownHeaderNames.IfModifiedSince] != null || ctx.Request.Headers[HttpKnownHeaderNames.IfNoneMatch] != null || ctx.Request.Headers[HttpKnownHeaderNames.IfRange] != null || ctx.Request.Headers[HttpKnownHeaderNames.IfMatch] != null || ctx.Request.Headers[HttpKnownHeaderNames.IfUnmodifiedSince] != null) {
  461.                     // The _user_ request contains conditonal cache directives
  462.                     // Those will conflict with the caching engine => do not lookup a cached item.
  463.                     if (Logging.On)
  464.                         Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_request_contains_conditional_header));
  465.                    
  466.                     if (ctx.Policy.Level == HttpRequestCacheLevel.CacheOnly) {
  467.                         // Throw because the request must hit the wire and it's cache-only policy
  468.                         ctx.FailRequest(WebExceptionStatus.RequestProhibitedByCachePolicy);
  469.                     }
  470.                    
  471.                     return CacheValidationStatus.DoNotTakeFromCache;
  472.                    
  473.                 }
  474.                 return CacheValidationStatus.Continue;
  475.             }
  476.             //
  477.             // Implements logic as to compute cache freshness.
  478.             // Client Policy is not considered
  479.             //
  480.             public static CacheFreshnessStatus ComputeFreshness(HttpRequestCacheValidator ctx)
  481.             {
  482.                
  483.                 if (Logging.On)
  484.                     Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_now_time, DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture)));
  485.                
  486.                 /*
  487.                     apparent_age = max(0, response_time - date_value);
  488.                 */               
  489.                
  490. DateTime nowDate = DateTime.UtcNow;
  491.                
  492.                 TimeSpan age = TimeSpan.MaxValue;
  493.                 DateTime date = ctx.CacheDate;
  494.                
  495.                 if (date != DateTime.MinValue) {
  496.                     age = (nowDate - date);
  497.                     if (Logging.On)
  498.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_age1_date_header, ((int)age.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo), ctx.CacheDate.ToString("r", CultureInfo.InvariantCulture)));
  499.                 }
  500.                 else if (ctx.CacheEntry.LastSynchronizedUtc != DateTime.MinValue) {
  501.                     /*
  502.                         Another way to compute cache age but only if Date header is absent.
  503.                     */                   
  504. age = nowDate - ctx.CacheEntry.LastSynchronizedUtc;
  505.                     if (ctx.CacheAge != TimeSpan.MinValue) {
  506.                         age += ctx.CacheAge;
  507.                     }
  508.                     if (Logging.On) {
  509.                         if (ctx.CacheAge != TimeSpan.MinValue)
  510.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_age1_last_synchronized_age_header, ((int)age.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo), ctx.CacheEntry.LastSynchronizedUtc.ToString("r", CultureInfo.InvariantCulture), ((int)ctx.CacheAge.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo)));
  511.                         else
  512.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_age1_last_synchronized, ((int)age.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo), ctx.CacheEntry.LastSynchronizedUtc.ToString("r", CultureInfo.InvariantCulture)));
  513.                     }
  514.                 }
  515.                
  516.                 /*
  517.                     corrected_received_age = max(apparent_age, age_value);
  518.                 */               
  519. if (ctx.CacheAge != TimeSpan.MinValue) {
  520.                     if (Logging.On)
  521.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_age2, ((int)ctx.CacheAge.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo)));
  522.                     if (ctx.CacheAge > age || age == TimeSpan.MaxValue) {
  523.                         age = ctx.CacheAge;
  524.                     }
  525.                 }
  526.                
  527.                 // Updating CacheAge ...
  528.                 // Note we don't account on response "transit" delay
  529.                 // Also undefined cache entry Age is reported as TimeSpan.MaxValue (which is impossble to get from HTTP)
  530.                 // Also a negative age is reset to 0 as per RFC
  531.                 ctx.CacheAge = (age < TimeSpan.Zero ? TimeSpan.Zero : age);
  532.                
  533.                 // Now we start checking the server specified requirements
  534.                
  535.                 /*
  536.                 The calculation to determine if a response has expired is quite simple:
  537.                 response_is_fresh = (freshness_lifetime > current_age)
  538.                 */               
  539.                
  540.                 // If we managed to compute the Cache Age
  541. if (ctx.CacheAge != TimeSpan.MinValue) {
  542.                    
  543.                     /*
  544.                         s-maxage
  545.                         If a response includes an s-maxage directive, then for a shared
  546.                         cache (but not for a private cache), the maximum age specified by
  547.                         this directive overrides the maximum age specified by either the
  548.                         max-age directive or the Expires header.
  549.                     */                   
  550. if (!ctx.CacheEntry.IsPrivateEntry && ctx.CacheCacheControl.SMaxAge != -1) {
  551.                         ctx.CacheMaxAge = TimeSpan.FromSeconds(ctx.CacheCacheControl.SMaxAge);
  552.                         if (Logging.On)
  553.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_max_age_cache_s_max_age, ((int)ctx.CacheMaxAge.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo)));
  554.                         if (ctx.CacheAge < ctx.CacheMaxAge) {
  555.                             return CacheFreshnessStatus.Fresh;
  556.                         }
  557.                         return CacheFreshnessStatus.Stale;
  558.                     }
  559.                    
  560.                     /*
  561.                     The max-age directive takes priority over Expires, so if max-age is
  562.                     present in a response, the calculation is simply:
  563.                             freshness_lifetime = max_age_value
  564.                     */                   
  565. if (ctx.CacheCacheControl.MaxAge != -1) {
  566.                         ctx.CacheMaxAge = TimeSpan.FromSeconds(ctx.CacheCacheControl.MaxAge);
  567.                         if (Logging.On)
  568.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_max_age_cache_max_age, ((int)ctx.CacheMaxAge.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo)));
  569.                         if (ctx.CacheAge < ctx.CacheMaxAge) {
  570.                             return CacheFreshnessStatus.Fresh;
  571.                         }
  572.                         return CacheFreshnessStatus.Stale;
  573.                     }
  574.                 }
  575.                
  576.                 /*
  577.                 Otherwise, if Expires is present in the response, the calculation is:
  578.                         freshness_lifetime = expires_value - date_value
  579.                 */               
  580. if (date == DateTime.MinValue) {
  581.                     date = ctx.CacheEntry.LastSynchronizedUtc;
  582.                 }
  583.                
  584.                 DateTime expiresDate = ctx.CacheEntry.ExpiresUtc;
  585.                 if (ctx.CacheExpires != DateTime.MinValue && ctx.CacheExpires < expiresDate) {
  586.                     expiresDate = ctx.CacheExpires;
  587.                 }
  588.                
  589.                 // If absolute Expires and Response Date and Cache Age can be recovered
  590.                 if (expiresDate != DateTime.MinValue && date != DateTime.MinValue && ctx.CacheAge != TimeSpan.MinValue) {
  591.                     ctx.CacheMaxAge = expiresDate - date;
  592.                     if (Logging.On)
  593.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_max_age_expires_date, ((int)((expiresDate - date).TotalSeconds)).ToString(NumberFormatInfo.InvariantInfo), expiresDate.ToString("r", CultureInfo.InvariantCulture)));
  594.                     if (ctx.CacheAge < ctx.CacheMaxAge) {
  595.                         return CacheFreshnessStatus.Fresh;
  596.                     }
  597.                     return CacheFreshnessStatus.Stale;
  598.                 }
  599.                
  600.                 // If absolute Expires can be recovered
  601.                 if (expiresDate != DateTime.MinValue) {
  602.                     ctx.CacheMaxAge = expiresDate - DateTime.UtcNow;
  603.                     //Take absolute Expires value
  604.                     if (Logging.On)
  605.                         Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_max_age_absolute, expiresDate.ToString("r", CultureInfo.InvariantCulture)));
  606.                     if (expiresDate < DateTime.UtcNow) {
  607.                         return CacheFreshnessStatus.Fresh;
  608.                     }
  609.                     return CacheFreshnessStatus.Stale;
  610.                 }
  611.                
  612.                 /*
  613.                   If none of Expires, Cache-Control: max-age, or Cache-Control: s-
  614.                   maxage (see section 14.9.3) appears in the response, and the response
  615.                   does not include other restrictions on caching, the cache MAY compute
  616.                   a freshness lifetime using a heuristic. The cache MUST attach Warning
  617.                   113 to any response whose age is more than 24 hours if such warning
  618.                   has not already been added.
  619.                   Also, if the response does have a Last-Modified time, the heuristic
  620.                   expiration value SHOULD be no more than some fraction of the interval
  621.                   since that time. A typical setting of this fraction might be 10%.
  622.                         response_is_fresh = (freshness_lifetime > current_age)
  623.               */               
  624.                
  625. ctx.HeuristicExpiration = true;
  626.                
  627.                 DateTime lastModifiedDate = ctx.CacheEntry.LastModifiedUtc;
  628.                 if (ctx.CacheLastModified > lastModifiedDate) {
  629.                     lastModifiedDate = ctx.CacheLastModified;
  630.                 }
  631.                 ctx.CacheMaxAge = ctx.UnspecifiedMaxAge;
  632.                
  633.                 if (lastModifiedDate != DateTime.MinValue) {
  634.                     TimeSpan span = (nowDate - lastModifiedDate);
  635.                     int maxAgeSeconds = (int)(span.TotalSeconds / 10);
  636.                     ctx.CacheMaxAge = TimeSpan.FromSeconds(maxAgeSeconds);
  637.                     if (Logging.On)
  638.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_no_max_age_use_10_percent, maxAgeSeconds.ToString(NumberFormatInfo.InvariantInfo), lastModifiedDate.ToString("r", CultureInfo.InvariantCulture)));
  639.                     if (ctx.CacheAge.TotalSeconds < maxAgeSeconds) {
  640.                         return CacheFreshnessStatus.Fresh;
  641.                     }
  642.                     return CacheFreshnessStatus.Stale;
  643.                 }
  644.                
  645.                 // Else we can only rely on UnspecifiedMaxAge hint
  646.                 ctx.CacheMaxAge = ctx.UnspecifiedMaxAge;
  647.                 if (Logging.On)
  648.                     Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_no_max_age_use_default, ((int)(ctx.UnspecifiedMaxAge.TotalSeconds)).ToString(NumberFormatInfo.InvariantInfo)));
  649.                 if (ctx.CacheMaxAge >= ctx.CacheAge) {
  650.                     return CacheFreshnessStatus.Fresh;
  651.                 }
  652.                 return CacheFreshnessStatus.Stale;
  653.             }
  654.            
  655. /*
  656.                 Returns:
  657.                 - Valid    : The cache can be updated with the response
  658.                 - Unknown  : The response should not go into cache
  659.             */           
  660.             static internal TriState OnUpdateCache(HttpRequestCacheValidator ctx, HttpWebResponse resp)
  661.             {
  662.                 /*
  663.                     Quick check on supported methods.
  664.                 */               
  665. if (ctx.RequestMethod != HttpMethod.Head && ctx.RequestMethod != HttpMethod.Get && ctx.RequestMethod != HttpMethod.Post) {
  666.                     if (Logging.On)
  667.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_not_a_get_head_post));
  668.                     return TriState.Unknown;
  669.                 }
  670.                
  671.                 //If the entry did not exist ...
  672.                 if (ctx.CacheStream == Stream.Null || (int)ctx.CacheStatusCode == 0) {
  673.                     if (resp.StatusCode == HttpStatusCode.NotModified) {
  674.                         if (Logging.On)
  675.                             Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_cannot_update_cache_if_304));
  676.                         return TriState.Unknown;
  677.                     }
  678.                     if (ctx.RequestMethod == HttpMethod.Head) {
  679.                         // Protection from some caching Head response when entry does not exist.
  680.                         if (Logging.On)
  681.                             Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_cannot_update_cache_with_head_resp));
  682.                         return TriState.Unknown;
  683.                     }
  684.                 }
  685.                
  686.                
  687.                 if (resp == null) {
  688.                     if (Logging.On)
  689.                         Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_http_resp_is_null));
  690.                     return TriState.Unknown;
  691.                 }
  692.                
  693.                 //
  694.                 // We assume that ctx.ResponseCacheControl is already updated based on a live response
  695.                 //
  696.                
  697.                 /*
  698.                 no-store
  699.                       ... If sent in a response,
  700.                       a cache MUST NOT store any part of either this response or the
  701.                       request that elicited it.
  702.                 */               
  703. if (ctx.ResponseCacheControl.NoStore) {
  704.                     if (Logging.On)
  705.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_cache_control_is_no_store));
  706.                     return TriState.Unknown;
  707.                 }
  708.                
  709.                
  710.                 /*
  711.                 If there is neither a cache validator nor an explicit expiration time
  712.                   associated with a response, we do not expect it to be cached, but
  713.                   certain caches MAY violate this expectation (for example, when little
  714.                   or no network connectivity is available). A client can usually detect
  715.                   that such a response was taken from a cache by comparing the Date
  716.                   header to the current time.
  717.                 */               
  718.                
  719.                 // NOTE: If no Expire and no Validator peresnt we choose to CACHE
  720.                 //===============================================================
  721.                
  722.                
  723. /*
  724.                     Note: a new response that has an older Date header value than
  725.                     existing cached responses is not cacheable.
  726.                 */               
  727. if (ctx.ResponseDate != DateTime.MinValue && ctx.CacheDate != DateTime.MinValue) {
  728.                     if (ctx.ResponseDate < ctx.CacheDate) {
  729.                         if (Logging.On)
  730.                             Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_older_than_cache));
  731.                         return TriState.Unknown;
  732.                     }
  733.                 }
  734.                
  735.                 /*
  736.                 public
  737.                       Indicates that the response MAY be cached by any cache, even if it
  738.                       would normally be non-cacheable or cacheable only within a non-
  739.                       shared cache. (See also Authorization, section 14.8, for
  740.                       additional details.)
  741.                 */               
  742. if (ctx.ResponseCacheControl.Public) {
  743.                     if (Logging.On)
  744.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_cache_control_is_public));
  745.                     return TriState.Valid;
  746.                 }
  747.                
  748.                 // sometiem public cache can cache a response with "private" directive, subject to other restrictions
  749.                 TriState result = TriState.Unknown;
  750.                
  751.                 /*
  752.                 private
  753.                       Indicates that all or part of the response message is intended for
  754.                       a single user and MUST NOT be cached by a shared cache. This
  755.                       allows an origin server to state that the specified parts of the
  756.                       response are intended for only one user and are not a valid
  757.                       response for requests by other users. A private (non-shared) cache
  758.                       MAY cache the response.
  759.                 */               
  760. if (ctx.ResponseCacheControl.Private) {
  761.                     if (!ctx.CacheEntry.IsPrivateEntry) {
  762.                         if (ctx.ResponseCacheControl.PrivateHeaders == null) {
  763.                             if (Logging.On)
  764.                                 Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_cache_control_is_private));
  765.                             return TriState.Unknown;
  766.                         }
  767.                         if (Logging.On)
  768.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_cache_control_is_private_plus_headers));
  769.                         for (int i = 0; i < ctx.ResponseCacheControl.PrivateHeaders.Length; ++i) {
  770.                             ctx.CacheHeaders.Remove(ctx.ResponseCacheControl.PrivateHeaders[i]);
  771.                             result = TriState.Valid;
  772.                         }
  773.                     }
  774.                     else {
  775.                         result = TriState.Valid;
  776.                     }
  777.                 }
  778.                
  779.                
  780.                 /*
  781.                     The RFC is funky on no-cache directive.
  782.                     But the bottom line is sometime you CAN cache no-cache responses.
  783.                 */               
  784. if (ctx.ResponseCacheControl.NoCache) {
  785.                     if (ctx.ResponseLastModified == DateTime.MinValue && ctx.Response.Headers.ETag == null) {
  786.                         if (Logging.On)
  787.                             Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_revalidation_required));
  788.                         return TriState.Unknown;
  789.                     }
  790.                     if (Logging.On)
  791.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_needs_revalidation));
  792.                     return TriState.Valid;
  793.                 }
  794.                
  795.                 if (ctx.ResponseCacheControl.SMaxAge != -1 || ctx.ResponseCacheControl.MaxAge != -1) {
  796.                     if (Logging.On)
  797.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_allows_caching, ctx.ResponseCacheControl.ToString()));
  798.                     return TriState.Valid;
  799.                 }
  800.                
  801.                 /*
  802.                   When a shared cache (see section 13.7) receives a request
  803.                   containing an Authorization field, it MUST NOT return the
  804.                   corresponding response as a reply to any other request, unless one
  805.                   of the following specific exceptions holds:
  806.                   1. If the response includes the "s-maxage" cache-control
  807.                   2. If the response includes the "must-revalidate" cache-control
  808.                   3. If the response includes the "public" cache-control directive,
  809.                 */               
  810. if (!ctx.CacheEntry.IsPrivateEntry && ctx.Request.Headers[HttpKnownHeaderNames.Authorization] != null) {
  811.                     // we've already passed an opportunity to cache.
  812.                     if (Logging.On)
  813.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_auth_header_and_no_s_max_age));
  814.                     return TriState.Unknown;
  815.                 }
  816.                
  817.                 /*
  818.                     POST
  819.                     Responses to this method are not cacheable, unless the response
  820.                     includes appropriate Cache-Control or Expires header fields.
  821.                 */               
  822. if (ctx.RequestMethod == HttpMethod.Post && resp.Headers.Expires == null) {
  823.                     if (Logging.On)
  824.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_post_resp_without_cache_control_or_expires));
  825.                     return TriState.Unknown;
  826.                 }
  827.                
  828.                 /*
  829.                 A response received with a status code of 200, 203, 206, 300, 301 or
  830.                   410 MAY be stored by a cache and used in reply to a subsequent
  831.                   request, subject to the expiration mechanism, unless a cache-control
  832.                   directive prohibits caching. However, a cache that does not support
  833.                   the Range and Content-Range headers MUST NOT cache 206 (Partial
  834.                   Content) responses.
  835.                   NOTE: We added 304 here which is correct
  836.                 */               
  837. 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) {
  838.                     if (Logging.On)
  839.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_valid_based_on_status_code, (int)resp.StatusCode));
  840.                     return TriState.Valid;
  841.                 }
  842.                
  843.                 /*
  844.                   A response received with any other status code (e.g. status codes 302
  845.                   and 307) MUST NOT be returned in a reply to a subsequent request
  846.                   unless there are cache-control directives or another header(s) that
  847.                   explicitly allow it. For example, these include the following: an
  848.                   Expires header (section 14.21); a "max-age", "s-maxage",  "must-
  849.                   revalidate", "proxy-revalidate", "public" or "private" cache-control
  850.                   directive (section 14.9).
  851.                 */               
  852. if (result != TriState.Valid) {
  853.                     // otheriwse there was a "safe" private directive that allows caching
  854.                     if (Logging.On)
  855.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_no_cache_control, (int)resp.StatusCode));
  856.                 }
  857.                 return result;
  858.             }
  859.            
  860. /*----------*/           
  861.             //
  862.             // This method checks sutability of cached entry based on the client policy.
  863.             //
  864. /*
  865.                 Returns:
  866.                 - true      : The cache is still good
  867.                 - false    : The cache age does not fit into client policy
  868.             */           
  869.             public static bool ValidateCacheByClientPolicy(HttpRequestCacheValidator ctx)
  870.             {
  871.                
  872.                 if (ctx.Policy.Level == HttpRequestCacheLevel.Default) {
  873.                     if (Logging.On)
  874.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_age, (ctx.CacheAge != TimeSpan.MinValue ? ((int)ctx.CacheAge.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo) : SR.GetString(SR.net_log_unknown)), (ctx.CacheMaxAge != TimeSpan.MinValue ? ((int)ctx.CacheMaxAge.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo) : SR.GetString(SR.net_log_unknown))));
  875.                    
  876.                     if (ctx.Policy.MinFresh > TimeSpan.Zero) {
  877.                         if (Logging.On)
  878.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_policy_min_fresh, ((int)ctx.Policy.MinFresh.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo)));
  879.                         if (ctx.CacheAge + ctx.Policy.MinFresh >= ctx.CacheMaxAge) {
  880.                             return false;
  881.                         }
  882.                     }
  883.                    
  884.                     if (ctx.Policy.MaxAge != TimeSpan.MaxValue) {
  885.                         if (Logging.On)
  886.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_policy_max_age, ((int)ctx.Policy.MaxAge.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo)));
  887.                         if (ctx.CacheAge >= ctx.Policy.MaxAge) {
  888.                             return false;
  889.                         }
  890.                     }
  891.                    
  892.                     if (ctx.Policy.InternalCacheSyncDateUtc != DateTime.MinValue) {
  893.                         if (Logging.On)
  894.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_policy_cache_sync_date, ctx.Policy.InternalCacheSyncDateUtc.ToString("r", CultureInfo.CurrentCulture), ctx.CacheEntry.LastSynchronizedUtc.ToString(CultureInfo.CurrentCulture)));
  895.                         if (ctx.CacheEntry.LastSynchronizedUtc < ctx.Policy.InternalCacheSyncDateUtc) {
  896.                             return false;
  897.                         }
  898.                     }
  899.                    
  900.                     TimeSpan adjustedMaxAge = ctx.CacheMaxAge;
  901.                     if (ctx.Policy.MaxStale > TimeSpan.Zero) {
  902.                         if (Logging.On)
  903.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_policy_max_stale, ((int)ctx.Policy.MaxStale.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo)));
  904.                         if (adjustedMaxAge < TimeSpan.MaxValue - ctx.Policy.MaxStale) {
  905.                             adjustedMaxAge = adjustedMaxAge + ctx.Policy.MaxStale;
  906.                         }
  907.                         else {
  908.                             adjustedMaxAge = TimeSpan.MaxValue;
  909.                         }
  910.                        
  911.                         if (ctx.CacheAge >= adjustedMaxAge)
  912.                             return false;
  913.                         else
  914.                             return true;
  915.                     }
  916.                    
  917.                 }
  918.                 // not stale means "fresh enough"
  919.                 return ctx.CacheFreshnessStatus == CacheFreshnessStatus.Fresh;
  920.             }
  921.            
  922. /*
  923.                 This Validator should be called ONLY before submitting any response
  924.             */           
  925. /*
  926.                 Returns:
  927.                 - Valid    : Cache can be returned to the app subject to effective policy
  928.                 - Invalid  : A Conditional request MUST be made (unconditional request is also fine)
  929.             */           
  930.             static internal TriState ValidateCacheBySpecialCases(HttpRequestCacheValidator ctx)
  931.             {
  932.                
  933.                 /*
  934.                   no-cache
  935.                       If the no-cache directive does not specify a field-name, then a
  936.                       cache MUST NOT use the response to satisfy a subsequent request
  937.                       without successful revalidation with the origin server. This
  938.                       allows an origin server to prevent caching even by caches that
  939.                       have been configured to return stale responses to client requests.
  940.                 */               
  941. if (ctx.CacheCacheControl.NoCache) {
  942.                     if (ctx.CacheCacheControl.NoCacheHeaders == null) {
  943.                         if (Logging.On)
  944.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_control_no_cache));
  945.                         return TriState.Invalid;
  946.                     }
  947.                     /*
  948.                         If the no-cache directive does specify one or more field-names, then a cache MAY
  949.                         use the response to satisfy a subsequent request, subject to any other restrictions
  950.                         on caching.
  951.                         However, the specified field-name(s) MUST NOT be sent in the response to
  952.                         a subsequent request without successful revalidation with the origin server.
  953.                         This allows an origin server to prevent the re-use of certain header fields
  954.                         in a response, while still allowing caching of the rest of the response.
  955.                     */                   
  956. if (Logging.On)
  957.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_control_no_cache_removing_some_headers));
  958.                     for (int i = 0; i < ctx.CacheCacheControl.NoCacheHeaders.Length; ++i) {
  959.                         ctx.CacheHeaders.Remove(ctx.CacheCacheControl.NoCacheHeaders[i]);
  960.                     }
  961.                 }
  962.                
  963.                 /*
  964.                 must-revalidate
  965.                     When the must-revalidate
  966.                     directive is present in a response received by a cache, that cache
  967.                     MUST NOT use the entry after it becomes stale to respond to a
  968.                     subsequent request without first revalidating it with the origin
  969.                     server. (I.e., the cache MUST do an end-to-end revalidation every
  970.                     time, if, based solely on the origin server's Expires or max-age
  971.                     value, the cached response is stale.)
  972.                 proxy-revalidate
  973.                     The proxy-revalidate directive has the same meaning as the must-
  974.                     revalidate directive, except that it does not apply to non-shared
  975.                     user agent caches.
  976.                 */               
  977. if (ctx.CacheCacheControl.MustRevalidate || (!ctx.CacheEntry.IsPrivateEntry && ctx.CacheCacheControl.ProxyRevalidate)) {
  978.                     if (ctx.CacheFreshnessStatus != CacheFreshnessStatus.Fresh) {
  979.                         if (Logging.On)
  980.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_control_must_revalidate));
  981.                         return TriState.Invalid;
  982.                     }
  983.                 }
  984.                 /*
  985.                   When a shared cache (see section 13.7) receives a request
  986.                   containing an Authorization field, it MUST NOT return the
  987.                   corresponding response as a reply to any other request, unless one
  988.                   of the following specific exceptions holds:
  989.                   1. If the response includes the "s-maxage" cache-control
  990.                     directive, the cache MAY use that response in replying to a
  991.                     subsequent request. But (if the specified maximum age has
  992.                     passed) a proxy cache MUST first revalidate it with the origin
  993.                     server, using the request-headers from the new request to allow
  994.                     the origin server to authenticate the new request. (This is the
  995.                     defined behavior for s-maxage.) If the response includes "s-
  996.                     maxage=0", the proxy MUST always revalidate it before re-using
  997.                     it.
  998.                   2. If the response includes the "must-revalidate" cache-control
  999.                     directive, the cache MAY use that response in replying to a
  1000.                     subsequent request. But if the response is stale, all caches
  1001.                     MUST first revalidate it with the origin server, using the
  1002.                     request-headers from the new request to allow the origin server
  1003.                     to authenticate the new request.
  1004.                   3. If the response includes the "public" cache-control directive,
  1005.                     it MAY be returned in reply to any subsequent request.
  1006.                 */               
  1007. if (ctx.Request.Headers[HttpKnownHeaderNames.Authorization] != null) {
  1008.                     if (ctx.CacheFreshnessStatus != CacheFreshnessStatus.Fresh) {
  1009.                         if (Logging.On)
  1010.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_cached_auth_header));
  1011.                         return TriState.Invalid;
  1012.                     }
  1013.                    
  1014.                     if (!ctx.CacheEntry.IsPrivateEntry && ctx.CacheCacheControl.SMaxAge == -1 && !ctx.CacheCacheControl.MustRevalidate && !ctx.CacheCacheControl.Public) {
  1015.                         if (Logging.On)
  1016.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_cached_auth_header_no_control_directive));
  1017.                         return TriState.Invalid;
  1018.                     }
  1019.                 }
  1020.                 return TriState.Valid;
  1021.             }
  1022.            
  1023.            
  1024.             //
  1025.             // Second Time (after response) cache validation always goes through this method.
  1026.             //
  1027.             // Returns
  1028.             // - ReturnCachedResponse = Take from cache, cache stream may be replaced and response stream is closed
  1029.             // - DoNotTakeFromCache = Disregard the cache
  1030.             // - RemoveFromCache = Disregard and remove cache entry
  1031.             // - CombineCachedAndServerResponse = The combined cache+live stream has been constructed.
  1032.             //
  1033.             public static CacheValidationStatus ValidateCacheAfterResponse(HttpRequestCacheValidator ctx, HttpWebResponse resp)
  1034.             {
  1035.                
  1036.                 if (Logging.On)
  1037.                     Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_after_validation));
  1038.                
  1039.                 if ((ctx.CacheStream == Stream.Null || (int)ctx.CacheStatusCode == 0) && resp.StatusCode == HttpStatusCode.NotModified) {
  1040.                     if (Logging.On)
  1041.                         Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_resp_status_304));
  1042.                     return CacheValidationStatus.DoNotTakeFromCache;
  1043.                 }
  1044.                
  1045.                 if (ctx.RequestMethod == HttpMethod.Head) {
  1046.                     /*
  1047.                           The response to a HEAD request MAY be cacheable in the sense that the
  1048.                           information contained in the response MAY be used to update a
  1049.                           previously cached entity from that resource. If the new field values
  1050.                           indicate that the cached entity differs from the current entity (as
  1051.                           would be indicated by a change in Content-Length, Content-MD5, ETag
  1052.                           or Last-Modified), then the cache MUST treat the cache entry as
  1053.                           stale.
  1054.                     */                   
  1055. bool invalidate = false;
  1056.                    
  1057.                     if (ctx.ResponseEntityLength != -1 && ctx.ResponseEntityLength != ctx.CacheEntityLength) {
  1058.                         if (Logging.On)
  1059.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_head_resp_has_different_content_length));
  1060.                         invalidate = true;
  1061.                     }
  1062.                     if (resp.Headers[HttpKnownHeaderNames.ContentMD5] != ctx.CacheHeaders[HttpKnownHeaderNames.ContentMD5]) {
  1063.                         if (Logging.On)
  1064.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_head_resp_has_different_content_md5));
  1065.                         invalidate = true;
  1066.                     }
  1067.                     if (resp.Headers.ETag != ctx.CacheHeaders.ETag) {
  1068.                         if (Logging.On)
  1069.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_head_resp_has_different_etag));
  1070.                         invalidate = true;
  1071.                     }
  1072.                     if (resp.StatusCode != HttpStatusCode.NotModified && resp.Headers.LastModified != ctx.CacheHeaders.LastModified) {
  1073.                         if (Logging.On)
  1074.                             Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_304_head_resp_has_different_last_modified));
  1075.                         invalidate = true;
  1076.                     }
  1077.                     if (invalidate) {
  1078.                         if (Logging.On)
  1079.                             Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_existing_entry_has_to_be_discarded));
  1080.                         return CacheValidationStatus.RemoveFromCache;
  1081.                     }
  1082.                 }
  1083.                
  1084.                 // If server has returned 206 partial content
  1085.                 if (resp.StatusCode == HttpStatusCode.PartialContent) {
  1086.                     /*
  1087.                           A cache MUST NOT combine a 206 response with other previously cached
  1088.                           content if the ETag or Last-Modified headers do not match exactly,
  1089.                           see 13.5.4.
  1090.                     */                   
  1091.                    
  1092.                     // Sometime if ETag has been used the server won't include Last-Modified, which seems to be OK
  1093. 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))) {
  1094.                         if (Logging.On)
  1095.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_206_resp_non_matching_entry));
  1096.                         if (Logging.On)
  1097.                             Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_existing_entry_should_be_discarded));
  1098.                         return CacheValidationStatus.RemoveFromCache;
  1099.                     }
  1100.                    
  1101.                    
  1102.                     // check does the live stream fit exactly into our cache tail
  1103.                     if (ctx.CacheEntry.StreamSize != ctx.ResponseRangeStart) {
  1104.                         if (Logging.On)
  1105.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_206_resp_starting_position_not_adjusted));
  1106.                         return CacheValidationStatus.DoNotTakeFromCache;
  1107.                     }
  1108.                    
  1109.                     Common.ReplaceOrUpdateCacheHeaders(ctx, resp);
  1110.                     if (ctx.RequestRangeUser) {
  1111.                         // This happens when a response is being downloaded page by page
  1112.                        
  1113.                         // We request combining the streams
  1114.                         // A user will see data starting CacheStreamOffset of a combined stream
  1115.                         ctx.CacheStreamOffset = ctx.CacheEntry.StreamSize;
  1116.                         // This is a user response content length
  1117.                         ctx.CacheStreamLength = ctx.ResponseRangeEnd - ctx.ResponseRangeStart + 1;
  1118.                         // This is a new cache stream size
  1119.                         ctx.CacheEntityLength = ctx.ResponseEntityLength;
  1120.                        
  1121.                         ctx.CacheStatusCode = resp.StatusCode;
  1122.                         ctx.CacheStatusDescription = resp.StatusDescription;
  1123.                         ctx.CacheHttpVersion = resp.ProtocolVersion;
  1124.                     }
  1125.                     else {
  1126.                         // This happens when previous response was downloaded partly
  1127.                        
  1128.                         ctx.CacheStreamOffset = 0;
  1129.                         ctx.CacheStreamLength = ctx.ResponseEntityLength;
  1130.                         ctx.CacheEntityLength = ctx.ResponseEntityLength;
  1131.                        
  1132.                         ctx.CacheStatusCode = HttpStatusCode.OK;
  1133.                         ctx.CacheStatusDescription = Common.OkDescription;
  1134.                         ctx.CacheHttpVersion = resp.ProtocolVersion;
  1135.                         ctx.CacheHeaders.Remove(HttpKnownHeaderNames.ContentRange);
  1136.                        
  1137.                         if (ctx.CacheStreamLength == -1) {
  1138.                             ctx.CacheHeaders.Remove(HttpKnownHeaderNames.ContentLength);
  1139.                         }
  1140.                         else {
  1141.                             ctx.CacheHeaders[HttpKnownHeaderNames.ContentLength] = ctx.CacheStreamLength.ToString(NumberFormatInfo.InvariantInfo);
  1142.                         }
  1143.                        
  1144.                     }
  1145.                     // At this point the protocol should create a combined stream made up of the cached and live streams
  1146.                     if (Logging.On)
  1147.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_combined_resp_requested));
  1148.                     return CacheValidationStatus.CombineCachedAndServerResponse;
  1149.                 }
  1150.                
  1151.                 /*
  1152.                 304 Not Modified
  1153.                     The response MUST include the following header fields:
  1154.                       - Date, unless its omission is required by section 14.18.1
  1155.                   If a clockless origin server obeys these rules, and proxies and
  1156.                   clients add their own Date to any response received without one (as
  1157.                   already specified by [RFC 2068], section 14.19), caches will operate
  1158.                   correctly.
  1159.                       - ETag and/or Content-Location, if the header would have been sent
  1160.                         in a 200 response to the same request
  1161.                       - Expires, Cache-Control, and/or Vary, if the field-value might
  1162.                         differ from that sent in any previous response for the same
  1163.                         variant
  1164.                 */               
  1165.                
  1166. if (resp.StatusCode == HttpStatusCode.NotModified) {
  1167.                    
  1168.                    
  1169.                     WebHeaderCollection cc = resp.Headers;
  1170.                    
  1171.                     string location = null;
  1172.                     string etag = null;
  1173.                    
  1174.                     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)) {
  1175.                         // Headers have to be updated
  1176.                         // Note that would allow a new E-Tag header to come in without changing the content.
  1177.                         if (Logging.On)
  1178.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_updating_headers_on_304));
  1179.                         Common.ReplaceOrUpdateCacheHeaders(ctx, resp);
  1180.                         return CacheValidationStatus.ReturnCachedResponse;
  1181.                     }
  1182.                    
  1183.                     //Try to not update headers if they are invariant or the same
  1184.                     int ignoredHeaders = 0;
  1185.                     if (etag != null) {
  1186.                         ++ignoredHeaders;
  1187.                     }
  1188.                     if (location != null) {
  1189.                         ++ignoredHeaders;
  1190.                     }
  1191.                     if (ctx.ResponseAge != TimeSpan.MinValue) {
  1192.                         ++ignoredHeaders;
  1193.                     }
  1194.                     if (ctx.ResponseLastModified != DateTime.MinValue) {
  1195.                         ++ignoredHeaders;
  1196.                     }
  1197.                     if (ctx.ResponseExpires != DateTime.MinValue) {
  1198.                         ++ignoredHeaders;
  1199.                     }
  1200.                     if (ctx.ResponseDate != DateTime.MinValue) {
  1201.                         ++ignoredHeaders;
  1202.                     }
  1203.                     if (cc.Via != null) {
  1204.                         ++ignoredHeaders;
  1205.                     }
  1206.                     if (cc[HttpKnownHeaderNames.Connection] != null) {
  1207.                         ++ignoredHeaders;
  1208.                     }
  1209.                     if (cc[HttpKnownHeaderNames.KeepAlive] != null) {
  1210.                         ++ignoredHeaders;
  1211.                     }
  1212.                     if (cc.ProxyAuthenticate != null) {
  1213.                         ++ignoredHeaders;
  1214.                     }
  1215.                     if (cc[HttpKnownHeaderNames.ProxyAuthorization] != null) {
  1216.                         ++ignoredHeaders;
  1217.                     }
  1218.                     if (cc[HttpKnownHeaderNames.TE] != null) {
  1219.                         ++ignoredHeaders;
  1220.                     }
  1221.                     if (cc[HttpKnownHeaderNames.TransferEncoding] != null) {
  1222.                         ++ignoredHeaders;
  1223.                     }
  1224.                     if (cc[HttpKnownHeaderNames.Trailer] != null) {
  1225.                         ++ignoredHeaders;
  1226.                     }
  1227.                     if (cc[HttpKnownHeaderNames.Upgrade] != null) {
  1228.                         ++ignoredHeaders;
  1229.                     }
  1230.                    
  1231.                     if (resp.Headers.Count <= ignoredHeaders) {
  1232.                         if (Logging.On)
  1233.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_suppressing_headers_update_on_304));
  1234.                         ctx.CacheDontUpdateHeaders = true;
  1235.                     }
  1236.                     else {
  1237.                         Common.ReplaceOrUpdateCacheHeaders(ctx, resp);
  1238.                     }
  1239.                     return CacheValidationStatus.ReturnCachedResponse;
  1240.                 }
  1241.                
  1242.                 // Any other response
  1243.                 if (Logging.On)
  1244.                     Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_status_code_not_304_206));
  1245.                 return CacheValidationStatus.DoNotTakeFromCache;
  1246.             }
  1247.            
  1248. /*
  1249.                 Returns:
  1250.                 - ReturnCachedResponse  : Cache may be returned to the app
  1251.                 - DoNotTakeFromCache    : Cache must not be returned to the app
  1252.             */           
  1253.             public static CacheValidationStatus ValidateCacheOn5XXResponse(HttpRequestCacheValidator ctx)
  1254.             {
  1255.                 /*
  1256.                   If a cache receives a 5xx response while attempting to revalidate an
  1257.                   entry, it MAY either forward this response to the requesting client,
  1258.                   or act as if the server failed to respond. In the latter case, it MAY
  1259.                   return a previously received response unless the cached entry
  1260.                   includes the "must-revalidate" cache-control directive
  1261.                 */               
  1262.                 // Do we have cached item?
  1263. if (ctx.CacheStream == Stream.Null || ctx.CacheStatusCode == (HttpStatusCode)0) {
  1264.                     return CacheValidationStatus.DoNotTakeFromCache;
  1265.                 }
  1266.                
  1267.                 if (ctx.CacheEntityLength != ctx.CacheEntry.StreamSize || ctx.CacheStatusCode == HttpStatusCode.PartialContent) {
  1268.                     // Partial cache remains partial, user will not know that.
  1269.                     // This is because user either did not provide a Range Header or
  1270.                     // the user range was just forwarded to the server bypassing cache
  1271.                     return CacheValidationStatus.DoNotTakeFromCache;
  1272.                 }
  1273.                
  1274.                 if (ValidateCacheBySpecialCases(ctx) != TriState.Valid) {
  1275.                     // This response cannot be used without _successful_ revalidation
  1276.                     return CacheValidationStatus.DoNotTakeFromCache;
  1277.                 }
  1278.                
  1279.                 if (ctx.Policy.Level == HttpRequestCacheLevel.CacheOnly || ctx.Policy.Level == HttpRequestCacheLevel.CacheIfAvailable || ctx.Policy.Level == HttpRequestCacheLevel.CacheOrNextCacheOnly) {
  1280.                     // that was a cache only request
  1281.                     if (Logging.On)
  1282.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_sxx_resp_cache_only));
  1283.                     return CacheValidationStatus.ReturnCachedResponse;
  1284.                 }
  1285.                
  1286.                 if (ctx.Policy.Level == HttpRequestCacheLevel.Default || ctx.Policy.Level == HttpRequestCacheLevel.Revalidate) {
  1287.                     if (ValidateCacheByClientPolicy(ctx)) {
  1288.                         if (Logging.On)
  1289.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_sxx_resp_can_be_replaced));
  1290.                         ctx.CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_111);
  1291.                         return CacheValidationStatus.ReturnCachedResponse;
  1292.                     }
  1293.                 }
  1294.                 return CacheValidationStatus.DoNotTakeFromCache;
  1295.             }
  1296.            
  1297.            
  1298. /*
  1299.               When the cache receives a subsequent request whose Request-URI
  1300.               specifies one or more cache entries including a Vary header field,
  1301.               the cache MUST NOT use such a cache entry to construct a response to
  1302.               the new request unless all of the selecting request-headers present
  1303.               in the new request match the corresponding stored request-headers in
  1304.               the original request.
  1305.               The selecting request-headers from two requests are defined to match
  1306.               if and only if the selecting request-headers in the first request can
  1307.               be transformed to the selecting request-headers in the second request
  1308.               by adding or removing linear white space (LWS) at places where this
  1309.               is allowed by the corresponding BNF, and/or combining multiple
  1310.               message-header fields with the same field name following the rules
  1311.               about message headers in section 4.2.
  1312.               A Vary header field-value of "*" always fails to match and subsequent
  1313.               requests on that resource can only be properly interpreted by the
  1314.               origin server.
  1315.             */           
  1316. /*
  1317.                 Returns:
  1318.                 - Valid    : Vary header values match in both request and cache
  1319.                 - Invalid  : Vary header values do not match
  1320.                 - Unknown  : Vary header is not present in cache
  1321.             */           
  1322.             static internal TriState ValidateCacheByVaryHeader(HttpRequestCacheValidator ctx)
  1323.             {
  1324.                 string[] cacheVary = ctx.CacheHeaders.GetValues(HttpKnownHeaderNames.Vary);
  1325.                 if (cacheVary == null) {
  1326.                     return TriState.Unknown;
  1327.                 }
  1328.                
  1329.                 ArrayList varyValues = new ArrayList();
  1330.                 HttpRequestCacheValidator.ParseHeaderValues(cacheVary, HttpRequestCacheValidator.ParseValuesCallback, varyValues);
  1331.                 if (varyValues.Count == 0) {
  1332.                     if (Logging.On)
  1333.                         Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_vary_header_empty));
  1334.                     return TriState.Invalid;
  1335.                 }
  1336.                
  1337.                 if (((string)(varyValues[0]))[0] == '*') {
  1338.                     if (Logging.On)
  1339.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_vary_header_contains_asterisks));
  1340.                     return TriState.Invalid;
  1341.                 }
  1342.                
  1343.                 if (ctx.SystemMeta == null || ctx.SystemMeta.Count == 0) {
  1344.                     // We keep there previous request headers
  1345.                     if (Logging.On)
  1346.                         Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_no_headers_in_metadata));
  1347.                     return TriState.Invalid;
  1348.                 }
  1349.                
  1350.                 /*
  1351.                   A Vary field value consisting of a list of field-names signals that
  1352.                   the representation selected for the response is based on a selection
  1353.                   algorithm which considers ONLY the listed request-header field values
  1354.                   in selecting the most appropriate representation. A cache MAY assume
  1355.                   that the same selection will be made for future requests with the
  1356.                   same values for the listed field names, for the duration of time for
  1357.                   which the response is fresh.
  1358.                 */               
  1359.                
  1360. for (int i = 0; i < varyValues.Count; ++i) {
  1361.                    
  1362.                     string[] requestValues = ctx.Request.Headers.GetValues((string)varyValues[i]);
  1363.                     ArrayList requestFields = new ArrayList();
  1364.                     if (requestValues != null) {
  1365.                         HttpRequestCacheValidator.ParseHeaderValues(requestValues, HttpRequestCacheValidator.ParseValuesCallback, requestFields);
  1366.                     }
  1367.                    
  1368.                     string[] cacheValues = ctx.SystemMeta.GetValues((string)varyValues[i]);
  1369.                     ArrayList cacheFields = new ArrayList();
  1370.                     if (cacheValues != null) {
  1371.                         HttpRequestCacheValidator.ParseHeaderValues(cacheValues, HttpRequestCacheValidator.ParseValuesCallback, cacheFields);
  1372.                     }
  1373.                    
  1374.                     if (requestFields.Count != cacheFields.Count) {
  1375.                         if (Logging.On)
  1376.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_vary_header_mismatched_count, (string)varyValues[i]));
  1377.                         return TriState.Invalid;
  1378.                     }
  1379.                    
  1380.                     // NB: fields order is significant as per RFC.
  1381.                     for (int j = 0; j < cacheFields.Count; ++j) {
  1382.                         if (!AsciiLettersNoCaseEqual((string)cacheFields[j], (string)requestFields[j])) {
  1383.                             if (Logging.On)
  1384.                                 Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_vary_header_mismatched_field, (string)varyValues[i], (string)cacheFields[j], (string)requestFields[j]));
  1385.                             return TriState.Invalid;
  1386.                         }
  1387.                     }
  1388.                 }
  1389.                 if (Logging.On)
  1390.                     Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_vary_header_match));
  1391.                 // The Vary header is in cache and all headers values referenced to are equal to those in the Request.
  1392.                 return TriState.Valid;
  1393.             }
  1394.            
  1395.             // Returns
  1396.             // - DoNotTakeFromCache = A request shall go as is and the cache will be dropped
  1397.             // - Continue = Cache should be preserved and after-response validator should be called
  1398.             public static CacheValidationStatus TryConditionalRequest(HttpRequestCacheValidator ctx)
  1399.             {
  1400.                
  1401.                 string ranges;
  1402.                 TriState isPartial = CheckForRangeRequest(ctx, out ranges);
  1403.                
  1404.                 if (isPartial == TriState.Invalid) {
  1405.                     // This is a user requested range, pass it as is
  1406.                     return CacheValidationStatus.Continue;
  1407.                 }
  1408.                
  1409.                 if (isPartial == TriState.Valid) {
  1410.                     // Not all proxy servers, support requesting a range on an FTP
  1411.                     // command, so to be safe, never try to mix the cache with a range
  1412.                     // response. Always get the whole thing fresh in the case of FTP
  1413.                     // over proxy.
  1414.                     if (ctx is FtpRequestCacheValidator)
  1415.                         return CacheValidationStatus.DoNotTakeFromCache;
  1416.                     // We only have a partial response, need to complete it
  1417.                     if (TryConditionalRangeRequest(ctx)) {
  1418.                         // We can do a conditional range request
  1419.                         ctx.RequestRangeCache = true;
  1420.                         ((HttpWebRequest)ctx.Request).AddRange((int)ctx.CacheEntry.StreamSize);
  1421.                         if (Logging.On)
  1422.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_range, ctx.Request.Headers[HttpKnownHeaderNames.Range]));
  1423.                         return CacheValidationStatus.Continue;
  1424.                     }
  1425.                     return CacheValidationStatus.DoNotTakeFromCache;
  1426.                 }
  1427.                
  1428.                 //This is not a range request
  1429.                 return ConstructConditionalRequest(ctx);
  1430.             }
  1431.            
  1432.            
  1433.             // Returns:
  1434.             // ReturnFromCache = Take it from cache
  1435.             // DoNotTakeFromCache= Reload from server and disregard current cache
  1436.             // Continue = Send a request that may have added a conditional header
  1437.             public static CacheValidationStatus TryResponseFromCache(HttpRequestCacheValidator ctx)
  1438.             {
  1439.                
  1440.                 string ranges;
  1441.                 TriState isRange = CheckForRangeRequest(ctx, out ranges);
  1442.                
  1443.                 if (isRange == TriState.Unknown) {
  1444.                     return CacheValidationStatus.ReturnCachedResponse;
  1445.                 }
  1446.                
  1447.                 if (isRange == TriState.Invalid) {
  1448.                     // user range request
  1449.                     long start = 0;
  1450.                     long end = 0;
  1451.                     long total = 0;
  1452.                    
  1453.                     if (!GetBytesRange(ranges, ref start, ref end, ref total, true)) {
  1454.                         if (Logging.On)
  1455.                             Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_range_invalid_format, ranges));
  1456.                         return CacheValidationStatus.DoNotTakeFromCache;
  1457.                     }
  1458.                    
  1459.                     if (start >= ctx.CacheEntry.StreamSize || end > ctx.CacheEntry.StreamSize || (end == -1 && ctx.CacheEntityLength == -1) || (end == -1 && ctx.CacheEntityLength > ctx.CacheEntry.StreamSize) || (start == -1 && (end == -1 || ctx.CacheEntityLength == -1 || (ctx.CacheEntityLength - end >= ctx.CacheEntry.StreamSize)))) {
  1460.                         // we don't have such a range in cache
  1461.                         if (Logging.On)
  1462.                             Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_range_not_in_cache, ranges));
  1463.                         return CacheValidationStatus.Continue;
  1464.                     }
  1465.                    
  1466.                     if (start == -1) {
  1467.                         start = ctx.CacheEntityLength - end;
  1468.                     }
  1469.                    
  1470.                     if (end <= 0) {
  1471.                         end = ctx.CacheEntry.StreamSize - 1;
  1472.                     }
  1473.                    
  1474.                     ctx.CacheStreamOffset = start;
  1475.                     ctx.CacheStreamLength = end - start + 1;
  1476.                     Construct206PartialContent(ctx, (int)start);
  1477.                    
  1478.                     if (Logging.On)
  1479.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_range_in_cache, ctx.CacheHeaders[HttpKnownHeaderNames.ContentRange]));
  1480.                    
  1481.                     return CacheValidationStatus.ReturnCachedResponse;
  1482.                 }
  1483.                 //
  1484.                 // Here we got a partially cached response and the user wants a whole response
  1485.                 //
  1486.                 if (ctx.Policy.Level == HttpRequestCacheLevel.CacheOnly && ((object)ctx.Uri.Scheme == (object)Uri.UriSchemeHttp || (object)ctx.Uri.Scheme == (object)Uri.UriSchemeHttps)) {
  1487.                     // Here we should strictly report a failure
  1488.                     // Only for HTTP and HTTPS we choose to return a partial content even user did not ask for it
  1489.                     ctx.CacheStreamOffset = 0;
  1490.                     ctx.CacheStreamLength = ctx.CacheEntry.StreamSize;
  1491.                     Construct206PartialContent(ctx, 0);
  1492.                    
  1493.                     if (Logging.On)
  1494.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_partial_resp, ctx.CacheHeaders[HttpKnownHeaderNames.ContentRange]));
  1495.                     return CacheValidationStatus.ReturnCachedResponse;
  1496.                 }
  1497.                
  1498.                 if (ctx.CacheEntry.StreamSize >= Int32.MaxValue) {
  1499.                     if (Logging.On)
  1500.                         Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_entry_size_too_big, ctx.CacheEntry.StreamSize));
  1501.                     return CacheValidationStatus.DoNotTakeFromCache;
  1502.                 }
  1503.                
  1504.                 if (TryConditionalRangeRequest(ctx)) {
  1505.                     ctx.RequestRangeCache = true;
  1506.                     ((HttpWebRequest)ctx.Request).AddRange((int)ctx.CacheEntry.StreamSize);
  1507.                     if (Logging.On)
  1508.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_range, ctx.Request.Headers[HttpKnownHeaderNames.Range]));
  1509.                     return CacheValidationStatus.Continue;
  1510.                 }
  1511.                 // This will let an unconditional request go
  1512.                 return CacheValidationStatus.Continue;
  1513.             }
  1514.            
  1515. /*
  1516.                 Discovers the fact that cached response is a partial one.
  1517.                 Returns:
  1518.                 - Invalid  : It's a user range request
  1519.                 - Valid    : It's a partial cached response
  1520.                 - Unknown  : It's neither a range request nor the cache does have a partial response
  1521.             */           
  1522.             private static TriState CheckForRangeRequest(HttpRequestCacheValidator ctx, out string ranges)
  1523.             {
  1524.                
  1525.                 if ((ranges = ctx.Request.Headers[HttpKnownHeaderNames.Range]) != null) {
  1526.                     // A request already contains range.
  1527.                     // The caller will either return it from cache or pass as is to the server
  1528.                     ctx.RequestRangeUser = true;
  1529.                     if (Logging.On)
  1530.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_range_request_range, ctx.Request.Headers[HttpKnownHeaderNames.Range]));
  1531.                     return TriState.Invalid;
  1532.                 }
  1533.                
  1534.                 if (ctx.CacheStatusCode == HttpStatusCode.PartialContent && ctx.CacheEntityLength == ctx.CacheEntry.StreamSize) {
  1535.                     // this is a whole resposne
  1536.                     ctx.CacheStatusCode = HttpStatusCode.OK;
  1537.                     ctx.CacheStatusDescription = Common.OkDescription;
  1538.                     return TriState.Unknown;
  1539.                 }
  1540.                 if (ctx.CacheEntry.IsPartialEntry || (ctx.CacheEntityLength != -1 && ctx.CacheEntityLength != ctx.CacheEntry.StreamSize) || ctx.CacheStatusCode == HttpStatusCode.PartialContent) {
  1541.                     //The cache may contain a partial response
  1542.                     if (Logging.On)
  1543.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_could_be_partial, ctx.CacheEntry.StreamSize, ctx.CacheEntityLength));
  1544.                     return TriState.Valid;
  1545.                 }
  1546.                
  1547.                 return TriState.Unknown;
  1548.             }
  1549.            
  1550. /*
  1551.                 HTTP/1.1 clients:
  1552.                 - If an entity tag has been provided by the origin server, MUST
  1553.                 use that entity tag in any cache-conditional request (using If-
  1554.                 Match or If-None-Match).
  1555.                 - If only a Last-Modified value has been provided by the origin
  1556.                 server, SHOULD use that value in non-subrange cache-conditional
  1557.                 requests (using If-Modified-Since).
  1558.                 - If only a Last-Modified value has been provided by an HTTP/1.0
  1559.                 origin server, MAY use that value in subrange cache-conditional
  1560.                 requests (using If-Unmodified-Since:). The user agent SHOULD
  1561.                 provide a way to disable this, in case of difficulty.
  1562.                 - If both an entity tag and a Last-Modified value have been
  1563.                 provided by the origin server, SHOULD use both validators in
  1564.                 cache-conditional requests. This allows both HTTP/1.0 and
  1565.                 HTTP/1.1 caches to respond appropriately.
  1566.             */           
  1567. /*
  1568.                 Returns:
  1569.                 - Continue            : Conditional request has been constructed
  1570.                 - DoNotTakeFromCache  : Conditional request cannot be constructed
  1571.             */           
  1572.             public static CacheValidationStatus ConstructConditionalRequest(HttpRequestCacheValidator ctx)
  1573.             {
  1574.                
  1575.                 CacheValidationStatus result = CacheValidationStatus.DoNotTakeFromCache;
  1576.                
  1577.                 // The assumption is that a _user_ conditional request was already filtered out
  1578.                
  1579.                 bool validator2 = false;
  1580.                 string str = ctx.CacheHeaders.ETag;
  1581.                 if (str != null) {
  1582.                     result = CacheValidationStatus.Continue;
  1583.                     ctx.Request.Headers[HttpKnownHeaderNames.IfNoneMatch] = str;
  1584.                     ctx.RequestIfHeader1 = HttpKnownHeaderNames.IfNoneMatch;
  1585.                     ctx.RequestValidator1 = str;
  1586.                     validator2 = true;
  1587.                     if (Logging.On)
  1588.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_condition_if_none_match, ctx.Request.Headers[HttpKnownHeaderNames.IfNoneMatch]));
  1589.                 }
  1590.                
  1591.                 if (ctx.CacheEntry.LastModifiedUtc != DateTime.MinValue) {
  1592.                     result = CacheValidationStatus.Continue;
  1593.                     str = ctx.CacheEntry.LastModifiedUtc.ToString("r", CultureInfo.InvariantCulture);
  1594.                     ctx.Request.Headers.ChangeInternal(HttpKnownHeaderNames.IfModifiedSince, str);
  1595.                     if (validator2) {
  1596.                         ctx.RequestIfHeader2 = HttpKnownHeaderNames.IfModifiedSince;
  1597.                         ctx.RequestValidator2 = str;
  1598.                     }
  1599.                     else {
  1600.                         ctx.RequestIfHeader1 = HttpKnownHeaderNames.IfModifiedSince;
  1601.                         ctx.RequestValidator1 = str;
  1602.                     }
  1603.                     if (Logging.On)
  1604.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_condition_if_modified_since, ctx.Request.Headers[HttpKnownHeaderNames.IfModifiedSince]));
  1605.                 }
  1606.                
  1607.                 if (Logging.On) {
  1608.                     if (result == CacheValidationStatus.DoNotTakeFromCache) {
  1609.                         Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_cannot_construct_conditional_request));
  1610.                     }
  1611.                 }
  1612.                 return result;
  1613.             }
  1614.            
  1615.            
  1616. /*
  1617.                 Returns:
  1618.                 - true: Conditional Partial request has been constructed
  1619.                 - false: Conditional Partial request cannot be constructed
  1620.             */           
  1621.             private static bool TryConditionalRangeRequest(HttpRequestCacheValidator ctx)
  1622.             {
  1623.                 //
  1624.                 // The response is partially cached (that has been checked before calling this method)
  1625.                 //
  1626.                 if (ctx.CacheEntry.StreamSize >= Int32.MaxValue) {
  1627.                     //This is a restriction of HttpWebRequest implementation as on 01/28/03
  1628.                     if (Logging.On)
  1629.                         Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_entry_size_too_big, ctx.CacheEntry.StreamSize));
  1630.                     return false;
  1631.                 }
  1632.                
  1633.                 /*
  1634.                     If the entity tag given in the If-Range header matches the current
  1635.                     entity tag for the entity, then the server SHOULD provide the
  1636.                     specified sub-range of the entity using a 206 (Partial content)
  1637.                     response. If the entity tag does not match, then the server SHOULD
  1638.                     return the entire entity using a 200 (OK) response.
  1639.                 */               
  1640. string str = ctx.CacheHeaders.ETag;
  1641.                 if (str != null) {
  1642.                     ctx.Request.Headers[HttpKnownHeaderNames.IfRange] = str;
  1643.                     ctx.RequestIfHeader1 = HttpKnownHeaderNames.IfRange;
  1644.                     ctx.RequestValidator1 = str;
  1645.                     if (Logging.On)
  1646.                         Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_condition_if_range, ctx.Request.Headers[HttpKnownHeaderNames.IfRange]));
  1647.                     return true;
  1648.                 }
  1649.                
  1650.                 /*
  1651.                     - If only a Last-Modified value has been provided by an HTTP/1.0
  1652.                     origin server, MAY use that value in subrange cache-conditional
  1653.                     requests (using If-Unmodified-Since:). The user agent SHOULD
  1654.                     provide a way to disable this, in case of difficulty.
  1655.                 */               
  1656.                
  1657. if (ctx.CacheEntry.LastModifiedUtc != DateTime.MinValue) {
  1658.                     str = ctx.CacheEntry.LastModifiedUtc.ToString("r", CultureInfo.InvariantCulture);
  1659.                     if (ctx.CacheHttpVersion.Major == 1 && ctx.CacheHttpVersion.Minor == 0) {
  1660.                         // Well If-Unmodified-Since would require an additional request in case it WAS modified
  1661.                         // A User may want to excerise this path without relying on our implementation.
  1662.                         if (Logging.On)
  1663.                             Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_conditional_range_not_implemented_on_http_10));
  1664.                         return false;
  1665.                         /*
  1666.                         //Http == 1.0
  1667.                         ctx.Request.Headers[HttpKnownHeaderNames.IfUnmodifiedSince] = str;
  1668.                         ctx.RequestIfHeader1 = HttpKnownHeaderNames.IfUnmodifiedSince;
  1669.                         ctx.RequestValidator1 = str;
  1670.                         if(Logging.On)Logging.PrintInfo(Logging.RequestCache, "Request Condition = If-Unmodified-Since:" + ctx.Request.Headers[HttpKnownHeaderNames.IfUnmodifiedSince]);
  1671.                         return true;
  1672.                     */                       
  1673.                     }
  1674.                     else {
  1675.                         //Http > 1.0
  1676.                         ctx.Request.Headers[HttpKnownHeaderNames.IfRange] = str;
  1677.                         ctx.RequestIfHeader1 = HttpKnownHeaderNames.IfRange;
  1678.                         ctx.RequestValidator1 = str;
  1679.                         if (Logging.On)
  1680.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_condition_if_range, ctx.Request.Headers[HttpKnownHeaderNames.IfRange]));
  1681.                         return true;
  1682.                     }
  1683.                 }
  1684.                
  1685.                 if (Logging.On)
  1686.                     Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_cannot_construct_conditional_range_request));
  1687.                 //Cannot construct a conditional request
  1688.                 return false;
  1689.             }
  1690.            
  1691.             //
  1692.             // A template for 206 response that we serve from cache on a user range request
  1693.             // It's also used for cache update.
  1694.             //
  1695.             public static void Construct206PartialContent(HttpRequestCacheValidator ctx, int rangeStart)
  1696.             {
  1697.                 ctx.CacheStatusCode = HttpStatusCode.PartialContent;
  1698.                 ctx.CacheStatusDescription = PartialContentDescription;
  1699.                 if (ctx.CacheHttpVersion == null) {
  1700.                     ctx.CacheHttpVersion = new Version(1, 1);
  1701.                 }
  1702.                 string ranges = "bytes " + rangeStart + '-' + (rangeStart + ctx.CacheStreamLength - 1) + '/' + (ctx.CacheEntityLength <= 0 ? "*" : ctx.CacheEntityLength.ToString(NumberFormatInfo.InvariantInfo));
  1703.                 ctx.CacheHeaders[HttpKnownHeaderNames.ContentRange] = ranges;
  1704.                 ctx.CacheHeaders[HttpKnownHeaderNames.ContentLength] = ctx.CacheStreamLength.ToString(NumberFormatInfo.InvariantInfo);
  1705.                 ctx.CacheEntry.IsPartialEntry = true;
  1706.             }
  1707.             //
  1708.             // A template for 200 response, used by cache update
  1709.             //
  1710.             public static void Construct200ok(HttpRequestCacheValidator ctx)
  1711.             {
  1712.                 ctx.CacheStatusCode = HttpStatusCode.OK;
  1713.                 ctx.CacheStatusDescription = Common.OkDescription;
  1714.                 if (ctx.CacheHttpVersion == null)
  1715.                     ctx.CacheHttpVersion = new Version(1, 1);
  1716.                
  1717.                 ctx.CacheHeaders.Remove(HttpKnownHeaderNames.ContentRange);
  1718.                
  1719.                 if (ctx.CacheEntityLength == -1) {
  1720.                     ctx.CacheHeaders.Remove(HttpKnownHeaderNames.ContentLength);
  1721.                 }
  1722.                 else {
  1723.                     ctx.CacheHeaders[HttpKnownHeaderNames.ContentLength] = ctx.CacheEntityLength.ToString(NumberFormatInfo.InvariantInfo);
  1724.                 }
  1725.                 ctx.CacheEntry.IsPartialEntry = false;
  1726.             }
  1727.             //
  1728.             // Clear the request from any conditional headers and request no-cache
  1729.             //
  1730.             public static void ConstructUnconditionalRefreshRequest(HttpRequestCacheValidator ctx)
  1731.             {
  1732.                
  1733.                 WebHeaderCollection cc = ctx.Request.Headers;
  1734.                 cc[HttpKnownHeaderNames.CacheControl] = "max-age=0";
  1735.                 cc[HttpKnownHeaderNames.Pragma] = "no-cache";
  1736.                 if (ctx.RequestIfHeader1 != null) {
  1737.                     cc.RemoveInternal(ctx.RequestIfHeader1);
  1738.                     ctx.RequestIfHeader1 = null;
  1739.                 }
  1740.                 if (ctx.RequestIfHeader2 != null) {
  1741.                     cc.RemoveInternal(ctx.RequestIfHeader2);
  1742.                     ctx.RequestIfHeader2 = null;
  1743.                 }
  1744.                
  1745.                 if (ctx.RequestRangeCache) {
  1746.                     cc.RemoveInternal(HttpKnownHeaderNames.Range);
  1747.                     ctx.RequestRangeCache = false;
  1748.                 }
  1749.             }
  1750.            
  1751.             //
  1752.             // This is called when we have decided to take from Cache or update Cache
  1753.             //
  1754.             public static void ReplaceOrUpdateCacheHeaders(HttpRequestCacheValidator ctx, HttpWebResponse resp)
  1755.             {
  1756.                 /*
  1757.                   In other words, the set of end-to-end headers received in the
  1758.                   incoming response overrides all corresponding end-to-end headers
  1759.                   stored with the cache entry (except for stored Warning headers with
  1760.                   warn-code 1xx, which are deleted even if not overridden).
  1761.                     This rule does not allow an origin server to use
  1762.                     a 304 (Not Modified) or a 206 (Partial Content) response to
  1763.                     entirely delete a header that it had provided with a previous
  1764.                     response.
  1765.               */               
  1766.                
  1767. if (ctx.CacheHeaders == null || (resp.StatusCode != HttpStatusCode.NotModified && resp.StatusCode != HttpStatusCode.PartialContent)) {
  1768.                     // existing context is dropped
  1769.                     ctx.CacheHeaders = new WebHeaderCollection();
  1770.                 }
  1771.                
  1772.                 // Here we preserve Request headers that are present in the response Vary header
  1773.                 string[] respVary = resp.Headers.GetValues(HttpKnownHeaderNames.Vary);
  1774.                 if (respVary != null) {
  1775.                     ArrayList varyValues = new ArrayList();
  1776.                     HttpRequestCacheValidator.ParseHeaderValues(respVary, HttpRequestCacheValidator.ParseValuesCallback, varyValues);
  1777.                     if (varyValues.Count != 0 && ((string)(varyValues[0]))[0] != '*') {
  1778.                         // we got some request headers to save
  1779.                         if (Logging.On)
  1780.                             Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_saving_request_headers, resp.Headers[HttpKnownHeaderNames.Vary]));
  1781.                         if (ctx.SystemMeta == null) {
  1782.                             ctx.SystemMeta = new NameValueCollection(varyValues.Count + 1, CaseInsensitiveAscii.StaticInstance);
  1783.                         }
  1784.                         for (int i = 0; i < varyValues.Count; ++i) {
  1785.                             string headerValue = ctx.Request.Headers[(string)varyValues[i]];
  1786.                             ctx.SystemMeta[(string)varyValues[i]] = headerValue;
  1787.                         }
  1788.                     }
  1789.                 }
  1790.                
  1791.                
  1792.                 /*
  1793.                       - Hop-by-hop headers, which are meaningful only for a single
  1794.                         transport-level connection, and are not stored by caches or
  1795.                         forwarded by proxies.
  1796.                   The following HTTP/1.1 headers are hop-by-hop headers:
  1797.                       - Connection
  1798.                       - Keep-Alive
  1799.                       - Proxy-Authenticate
  1800.                       - Proxy-Authorization
  1801.                       - TE
  1802.                       - Trailers
  1803.                       - Transfer-Encoding
  1804.                       - Upgrade
  1805.                 */               
  1806.                
  1807.                
  1808.                 // We add or Replace headers from the live response
  1809. for (int i = 0; i < ctx.Response.Headers.Count; ++i) {
  1810.                     string key = ctx.Response.Headers.GetKey(i);
  1811.                     if (AsciiLettersNoCaseEqual(key, HttpKnownHeaderNames.Connection) || AsciiLettersNoCaseEqual(key, HttpKnownHeaderNames.KeepAlive) || AsciiLettersNoCaseEqual(key, HttpKnownHeaderNames.ProxyAuthenticate) || AsciiLettersNoCaseEqual(key, HttpKnownHeaderNames.ProxyAuthorization) || AsciiLettersNoCaseEqual(key, HttpKnownHeaderNames.TE) || AsciiLettersNoCaseEqual(key, HttpKnownHeaderNames.TransferEncoding) || AsciiLettersNoCaseEqual(key, HttpKnownHeaderNames.Trailer) || AsciiLettersNoCaseEqual(key, HttpKnownHeaderNames.Upgrade)) {
  1812.                         continue;
  1813.                        
  1814.                     }
  1815.                     if (resp.StatusCode == HttpStatusCode.NotModified && AsciiLettersNoCaseEqual(key, HttpKnownHeaderNames.ContentLength)) {
  1816.                         continue;
  1817.                     }
  1818.                     ctx.CacheHeaders.ChangeInternal(key, ctx.Response.Headers[i]);
  1819.                 }
  1820.             }
  1821.             //
  1822.             //
  1823.             //
  1824.             private static bool AsciiLettersNoCaseEqual(string s1, string s2)
  1825.             {
  1826.                 if (s1.Length != s2.Length) {
  1827.                     return false;
  1828.                 }
  1829.                 for (int i = 0; i < s1.Length; ++i) {
  1830.                     if ((s1[i] | 32) != (s2[i] | 32)) {
  1831.                         return false;
  1832.                     }
  1833.                 }
  1834.                 return true;
  1835.             }
  1836.             //
  1837.             //
  1838.             //
  1839.             unsafe static internal bool UnsafeAsciiLettersNoCaseEqual(char* s1, int start, int length, string s2)
  1840.             {
  1841.                 if (length - start < s2.Length) {
  1842.                     return false;
  1843.                 }
  1844.                 for (int i = 0; i < s2.Length; ++i) {
  1845.                     if ((s1[start + i] | 32) != (s2[i] | 32)) {
  1846.                         return false;
  1847.                     }
  1848.                 }
  1849.                 return true;
  1850.             }
  1851.            
  1852.             //
  1853.             // Parses the string on "bytes = start - end" or "bytes start-end/xxx"
  1854.             //
  1855.             // Returns
  1856.             // true = take start/end for range
  1857.             // false = parsing error
  1858.             public static bool GetBytesRange(string ranges, ref long start, ref long end, ref long total, bool isRequest)
  1859.             {
  1860.                
  1861.                 ranges = ranges.ToLower(CultureInfo.InvariantCulture);
  1862.                
  1863.                 int idx = 0;
  1864.                 while (idx < ranges.Length && ranges[idx] == ' ') {
  1865.                     ++idx;
  1866.                 }
  1867.                
  1868.                 idx += 5;
  1869.                 // The "ranges" string is already in lowercase
  1870.                 if (idx >= ranges.Length || ranges[idx - 5] != 'b' || ranges[idx - 4] != 'y' || ranges[idx - 3] != 't' || ranges[idx - 2] != 'e' || ranges[idx - 1] != 's') {
  1871.                     if (Logging.On)
  1872.                         Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_only_byte_range_implemented));
  1873.                     return false;
  1874.                 }
  1875.                
  1876.                 if (isRequest) {
  1877.                     while (idx < ranges.Length && ranges[idx] == ' ') {
  1878.                         ++idx;
  1879.                     }
  1880.                     if (ranges[idx] != '=') {
  1881.                         return false;
  1882.                     }
  1883.                 }
  1884.                 else {
  1885.                     if (ranges[idx] != ' ') {
  1886.                         return false;
  1887.                     }
  1888.                 }
  1889.                
  1890.                 char ch = (char)0;
  1891.                 while (++idx < ranges.Length && (ch = ranges[idx]) == ' ') {
  1892.                     ;
  1893.                 }
  1894.                
  1895.                 start = -1;
  1896.                 if (ch != '-') {
  1897.                     // parsing start
  1898.                     if (idx < ranges.Length && ch >= '0' && ch <= '9') {
  1899.                         start = ch - '0';
  1900.                         while (++idx < ranges.Length && (ch = ranges[idx]) >= '0' && ch <= '9') {
  1901.                             start = start * 10 + (ch - '0');
  1902.                         }
  1903.                     }
  1904.                    
  1905.                     while (idx < ranges.Length && ch == ' ') {
  1906.                         ch = ranges[++idx];
  1907.                     }
  1908.                     if (ch != '-') {
  1909.                         return false;
  1910.                     }
  1911.                 }
  1912.                
  1913.                 // parsing end
  1914.                 while (idx < ranges.Length && (ch = ranges[++idx]) == ' ') {
  1915.                     ;
  1916.                 }
  1917.                
  1918.                 end = -1;
  1919.                 if (idx < ranges.Length && ch >= '0' && ch <= '9') {
  1920.                     end = ch - '0';
  1921.                     while (++idx < ranges.Length && (ch = ranges[idx]) >= '0' && ch <= '9') {
  1922.                         end = end * 10 + (ch - '0');
  1923.                     }
  1924.                 }
  1925.                 if (isRequest) {
  1926.                     while (idx < ranges.Length) {
  1927.                         if (ranges[idx++] != ' ') {
  1928.                             if (Logging.On)
  1929.                                 Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_multiple_complex_range_not_implemented));
  1930.                             return false;
  1931.                         }
  1932.                     }
  1933.                 }
  1934.                 else {
  1935.                     // parsing total
  1936.                     while (idx < ranges.Length && (ch = ranges[idx]) == ' ') {
  1937.                         ++idx;
  1938.                     }
  1939.                    
  1940.                     if (ch != '/') {
  1941.                         return false;
  1942.                     }
  1943.                     while (++idx < ranges.Length && (ch = ranges[idx]) == ' ') {
  1944.                         ;
  1945.                     }
  1946.                    
  1947.                     total = -1;
  1948.                     if (ch != '*') {
  1949.                         if (idx < ranges.Length && ch >= '0' && ch <= '9') {
  1950.                             total = ch - '0';
  1951.                             while (++idx < ranges.Length && (ch = ranges[idx]) >= '0' && ch <= '9') {
  1952.                                 total = total * 10 + (ch - '0');
  1953.                             }
  1954.                         }
  1955.                     }
  1956.                 }
  1957.                
  1958.                 if (!isRequest && (start == -1 || end == -1)) {
  1959.                     return false;
  1960.                 }
  1961.                 return true;
  1962.             }
  1963.         }
  1964.     }
  1965.    
  1966. }

Developer Fusion