File: net\System\Net\Cache\HttpRequestCacheValidator.cs
Project: ndp\fx\src\System.csproj (System)
/*++
Copyright (c) Microsoft Corporation
 
Module Name:
 
    HttpRequestCacheValidator.cs
 
Abstract:
    The class implements HTTP Caching validators as per RFC2616
 
 
Author:
 
    Alexei Vopilov    21-Dec-2002
 
Revision History:
 
    Jan 25 2004 - Changed the visibility of the class from public to internal.
 
--*/
namespace System.Net.Cache {
    using System;
    using System.Net;
    using System.IO;
    using System.Collections;
    using System.Text;
    using System.Collections.Specialized;
    using System.Globalization;
    using System.Threading;
 
 
    /// <summary> The class represents an adavanced way for an application to control caching protocol </summary>
    internal class HttpRequestCacheValidator: RequestCacheValidator {
        internal const string Warning_110 = "110 Response is stale";
        internal const string Warning_111 = "111 Revalidation failed";
        internal const string Warning_112 = "112 Disconnected operation";
        internal const string Warning_113 = "113 Heuristic expiration";
 
        private struct RequestVars {
            internal HttpMethod   Method;
            internal bool         IsCacheRange;
            internal bool         IsUserRange;
            internal string       IfHeader1;
            internal string       Validator1;
            internal string       IfHeader2;
            internal string       Validator2;
        }
 
        private HttpRequestCachePolicy m_HttpPolicy;
 
        private HttpStatusCode  m_StatusCode;
        private string          m_StatusDescription;
        private Version         m_HttpVersion;
        private WebHeaderCollection m_Headers;
        private NameValueCollection m_SystemMeta;
 
        private bool            m_DontUpdateHeaders;
        private bool            m_HeuristicExpiration;
 
        private Vars            m_CacheVars;
        private Vars            m_ResponseVars;
        private RequestVars     m_RequestVars;
 
        private struct Vars {
            internal DateTime         Date;
            internal DateTime         Expires;
            internal DateTime         LastModified;
            internal long             EntityLength;
            internal TimeSpan         Age;
            internal TimeSpan         MaxAge;
            internal ResponseCacheControl CacheControl;
            internal long             RangeStart;
            internal long             RangeEnd;
 
            internal void Initialize() {
                EntityLength = RangeStart = RangeEnd = -1;
                Date = DateTime.MinValue;
                Expires = DateTime.MinValue;
                LastModified = DateTime.MinValue;
                Age = TimeSpan.MinValue;
                MaxAge = TimeSpan.MinValue;
            }
        }
 
 
        //public
        internal HttpStatusCode CacheStatusCode           {get{return m_StatusCode;}          set{m_StatusCode = value;}}
        //public
        internal string         CacheStatusDescription    {get{return m_StatusDescription;}   set{m_StatusDescription = value;}}
        //public
        internal Version        CacheHttpVersion          {get{return m_HttpVersion;}         set{m_HttpVersion = value;}}
 
        //public
        internal WebHeaderCollection CacheHeaders         {get{return m_Headers;}             set{m_Headers = value;}}
 
        //public
        internal new HttpRequestCachePolicy   Policy      {
                                                            get {
                                                                if(m_HttpPolicy != null) return m_HttpPolicy;
                                                                m_HttpPolicy = base.Policy as HttpRequestCachePolicy;
                                                                if(m_HttpPolicy != null) return m_HttpPolicy;
                                                                // promote base policy to Http one
                                                                m_HttpPolicy = new HttpRequestCachePolicy((HttpRequestCacheLevel)base.Policy.Level);
                                                                return m_HttpPolicy;
                                                            }
                                                        }
 
        internal NameValueCollection SystemMeta         {get{return m_SystemMeta;}                  set{m_SystemMeta = value;}}
        internal HttpMethod   RequestMethod             {get{return m_RequestVars.Method;}          set{m_RequestVars.Method = value;}}
        internal bool         RequestRangeCache         {get{return m_RequestVars.IsCacheRange;}    set{m_RequestVars.IsCacheRange = value;}}
        internal bool         RequestRangeUser          {get{return m_RequestVars.IsUserRange;}     set{m_RequestVars.IsUserRange = value;}}
        internal string       RequestIfHeader1          {get{return m_RequestVars.IfHeader1;}       set{m_RequestVars.IfHeader1 = value;}}
        internal string       RequestValidator1         {get{return m_RequestVars.Validator1;}      set{m_RequestVars.Validator1 = value;}}
        internal string       RequestIfHeader2          {get{return m_RequestVars.IfHeader2;}       set{m_RequestVars.IfHeader2 = value;}}
        internal string       RequestValidator2         {get{return m_RequestVars.Validator2;}      set{m_RequestVars.Validator2 = value;}}
 
        internal bool         CacheDontUpdateHeaders    {get{return m_DontUpdateHeaders;}           set{m_DontUpdateHeaders = value;}}
 
        internal DateTime      CacheDate               {get{return m_CacheVars.Date;}              set{m_CacheVars.Date = value;}}
        internal DateTime      CacheExpires            {get{return m_CacheVars.Expires;}           set{m_CacheVars.Expires = value;}}
        internal DateTime      CacheLastModified       {get{return m_CacheVars.LastModified;}      set{m_CacheVars.LastModified = value;}}
        internal long          CacheEntityLength       {get{return m_CacheVars.EntityLength ;}     set{m_CacheVars.EntityLength  = value;}}
        internal TimeSpan      CacheAge                {get{return m_CacheVars.Age;}               set{m_CacheVars.Age = value;}}
        internal TimeSpan      CacheMaxAge             {get{return m_CacheVars.MaxAge;}            set{m_CacheVars.MaxAge = value;}}
        internal bool          HeuristicExpiration     {get{return m_HeuristicExpiration;}         set{m_HeuristicExpiration = value;}}
 
        internal ResponseCacheControl     CacheCacheControl    {get{return m_CacheVars.CacheControl;}      set{m_CacheVars.CacheControl = value;}}
 
        internal DateTime      ResponseDate            {get{return m_ResponseVars.Date;}         set{m_ResponseVars.Date = value;}}
        internal DateTime      ResponseExpires         {get{return m_ResponseVars.Expires;}      set{m_ResponseVars.Expires = value;}}
        internal DateTime      ResponseLastModified    {get{return m_ResponseVars.LastModified;} set{m_ResponseVars.LastModified = value;}}
        internal long          ResponseEntityLength    {get{return m_ResponseVars.EntityLength ;}set{m_ResponseVars.EntityLength  = value;}}
        internal long          ResponseRangeStart      {get{return m_ResponseVars.RangeStart;}   set{m_ResponseVars.RangeStart = value;}}
        internal long          ResponseRangeEnd        {get{return m_ResponseVars.RangeEnd;}     set{m_ResponseVars.RangeEnd = value;}}
        internal TimeSpan      ResponseAge             {get{return m_ResponseVars.Age;}          set{m_ResponseVars.Age = value;}}
        internal ResponseCacheControl  ResponseCacheControl {get{return m_ResponseVars.CacheControl;} set{m_ResponseVars.CacheControl = value;}}
 
        //
        private void ZeroPrivateVars()
        {
            // Set default values for private members here
            m_RequestVars = new RequestVars();
 
            m_HttpPolicy        = null;
            m_StatusCode        = (HttpStatusCode)0;
            m_StatusDescription = null;
            m_HttpVersion       = null;
            m_Headers           = null;
            m_SystemMeta        = null;
            m_DontUpdateHeaders = false;
            m_HeuristicExpiration = false;
 
            m_CacheVars   = new Vars();
            m_CacheVars.Initialize();
 
            m_ResponseVars= new Vars();
            m_ResponseVars.Initialize();
        }
 
        //public
        internal override RequestCacheValidator CreateValidator()
        {
            return new HttpRequestCacheValidator(StrictCacheErrors, UnspecifiedMaxAge);
        }
 
        /*
        //public
        // Consider removing.
        internal HttpRequestCacheValidator(): base()
        {
        }
        */
 
        //public
        internal HttpRequestCacheValidator(bool strictCacheErrors, TimeSpan unspecifiedMaxAge): base(strictCacheErrors, unspecifiedMaxAge)
        {
        }
 
        //
        // This validation method is called first and before any Cache access is done.
        // Given the request instance the code has to decide whether the request is ever suitable for caching.
        //
        // Returns:
        // Continue           = Proceed to the next protocol stage.
        // DoNotTakeFromCache = Don't used caches value for this request
        // DoNotUseCache      = Cache is not used for this request and response is not cached.
        protected internal override CacheValidationStatus ValidateRequest() {
 
            // cleanup context after previous  request
            ZeroPrivateVars();
 
            string method = Request.Method.ToUpper(CultureInfo.InvariantCulture);
            if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_request_method, method));
 
            switch (method) {
                case "GET" :    RequestMethod = HttpMethod.Get;      break;
                case "POST":    RequestMethod = HttpMethod.Post;     break;
                case "HEAD":    RequestMethod = HttpMethod.Head;     break;
                case "PUT" :    RequestMethod = HttpMethod.Put;      break;
                case "DELETE":  RequestMethod = HttpMethod.Delete;   break;
                case "OPTIONS": RequestMethod = HttpMethod.Options;  break;
                case "TRACE":   RequestMethod = HttpMethod.Trace;    break;
                case "CONNECT": RequestMethod = HttpMethod.Connect;  break;
                default:        RequestMethod = HttpMethod.Other;    break;
            }
 
            // Apply our best knowledge of HTTP caching and return the result
            // that can be hooked up and revised by the upper level
            return Rfc2616.OnValidateRequest(this);
        }
 
        //
        // This validation method is called after caching protocol has retrieved the metadata of a cached entry.
        // Given the cached entry context, the request instance and the effective caching policy,
        // the handler has to decide whether a cached item can be considered as fresh.
        protected internal override CacheFreshnessStatus ValidateFreshness()
        {
 
            // Transfer cache entry metadata into status line and headers.
            string s = ParseStatusLine();
 
            if(Logging.On) {
                if ((int) CacheStatusCode == 0) {
                    Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_http_status_parse_failure, (s == null ? "null" : s)));
                }
                else {
                    Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_http_status_line, (CacheHttpVersion != null ? CacheHttpVersion.ToString() : "null"), (int)CacheStatusCode, CacheStatusDescription));
                }
 
            }
 
            CreateCacheHeaders((int)CacheStatusCode != 0);
            CreateSystemMeta();
 
            // We will need quick access to cache-control and other headers coming with the cached item
            FetchHeaderValues(true);
 
            if(Logging.On) Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_cache_control, CacheCacheControl.ToString()));
 
            // Now we try to apply our best knowledge of HTTP caching and return the result
            // that can be hooked up and revised on the upper level
            return Rfc2616.OnValidateFreshness(this);
        }
 
        /// <remarks>  This method may add headers under the "Warning" header name </remarks>
        protected internal override CacheValidationStatus ValidateCache() {
 
            if (this.Policy.Level != HttpRequestCacheLevel.Revalidate && base.Policy.Level >= RequestCacheLevel.Reload)
            {
                // For those policies cache is never returned
                GlobalLog.Assert("OnValidateCache()", "This validator should not be called for policy = " + Policy.ToString());
                if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_validator_invalid_for_policy, Policy.ToString()));
                return CacheValidationStatus.DoNotTakeFromCache;
            }
 
            // First check is do we have a cached entry at all?
            // Also we include some very special case where cache got a 304 (NotModified) response somehow
            if (CacheStream == Stream.Null || (int)CacheStatusCode == 0 || CacheStatusCode == HttpStatusCode.NotModified)
            {
                if (this.Policy.Level == HttpRequestCacheLevel.CacheOnly)
                {
                    // Throw because entry was not found and it's cache-only policy
                    FailRequest(WebExceptionStatus.CacheEntryNotFound);
                }
                return CacheValidationStatus.DoNotTakeFromCache;
            }
 
            if (RequestMethod == HttpMethod.Head)
            {
                // For a HEAD request we release the cache entry stream asap since we will have to suppress it anyway
                CacheStream.Close();
                CacheStream = new SyncMemoryStream(new byte[] {});
            }
 
            // Apply our best knowledge of HTTP caching and return the result
            // that can be hooked up and revised by the upper level
 
            CacheValidationStatus result = CacheValidationStatus.DoNotTakeFromCache;
 
            //
            // Before request submission validation
            //
 
            // If we return from cache we should remove existing 1xx warnings
            RemoveWarnings_1xx();
 
            // default values for a response from cache.
            CacheStreamOffset       = 0;
            CacheStreamLength       = CacheEntry.StreamSize;
 
            result = Rfc2616.OnValidateCache(this);
            if (result != CacheValidationStatus.ReturnCachedResponse && this.Policy.Level == HttpRequestCacheLevel.CacheOnly) {
                // Throw because entry was not found and it's cache-only policy
                FailRequest(WebExceptionStatus.CacheEntryNotFound);
            }
 
            if (result == CacheValidationStatus.ReturnCachedResponse)
            {
                if (CacheFreshnessStatus == CacheFreshnessStatus.Stale) {
                    CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_110);
                }
                if (base.Policy.Level == RequestCacheLevel.CacheOnly) {
                    CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_112);
                }
                if (HeuristicExpiration && (int)CacheAge.TotalSeconds >= 24*3600) {
                    CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_113);
                }
            }
 
            if (result == CacheValidationStatus.DoNotTakeFromCache) {
                // We signal that current cache entry can be only replaced and not updated
                CacheStatusCode = (HttpStatusCode) 0;
            }
            else if (result == CacheValidationStatus.ReturnCachedResponse) {
                CacheHeaders[HttpKnownHeaderNames.Age] = ((int)(CacheAge.TotalSeconds)).ToString(NumberFormatInfo.InvariantInfo);
            }
            return result;
        }
        //
        // This is (optionally) called after receiveing a live response
        //
        protected internal override CacheValidationStatus RevalidateCache()
        {
            if (this.Policy.Level != HttpRequestCacheLevel.Revalidate && base.Policy.Level >= RequestCacheLevel.Reload)
            {
                // For those policies cache is never returned
                GlobalLog.Assert("RevalidateCache()", "This validator should not be called for policy = " + Policy.ToString());
                if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_validator_invalid_for_policy, Policy.ToString()));
                return CacheValidationStatus.DoNotTakeFromCache;
            }
 
            // First check is do we have a cached entry at all?
            // Also we include some very special case where cache got a 304 (NotModified) response somehow
            if (CacheStream == Stream.Null || (int)CacheStatusCode == 0 || CacheStatusCode == HttpStatusCode.NotModified)
            {
                return CacheValidationStatus.DoNotTakeFromCache;
            }
 
            //
            // This is a second+ time validation after receiving at least one response
            //
 
            // Apply our best knowledge of HTTP caching and return the result
            // that can be hooked up and revised by the upper level
            CacheValidationStatus result = CacheValidationStatus.DoNotTakeFromCache;
 
            HttpWebResponse resp = Response as HttpWebResponse;
            if (resp == null)
            {
                // This will result to an application error
                return CacheValidationStatus.DoNotTakeFromCache;
            }
 
            if (resp.StatusCode >= HttpStatusCode.InternalServerError) {
                // If server returned a 5XX server error
                if (Rfc2616.Common.ValidateCacheOn5XXResponse(this) == CacheValidationStatus.ReturnCachedResponse) {
                    // We can substitute the response from cache
                    if (CacheFreshnessStatus == CacheFreshnessStatus.Stale) {
                        CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_110);
                    }
                    if (HeuristicExpiration && (int)CacheAge.TotalSeconds >= 24*3600) {
                        CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_113);
                    }
                    // We actually failed to reach the origin server hence we don't reset the current Cache Age
                }
            }
            else {
 
                // if there was already one retry, then cache should not be taken into account
                if (ResponseCount > 1) {
                    result =  CacheValidationStatus.DoNotTakeFromCache;
                }
                else {
                    /*
                    Section 13.2.3:
                    HTTP/1.1 uses the Age response-header to convey the estimated age
                    of the response message when obtained from a cache.
                    The Age field value is the cache's estimate of the amount of time
                    since the response was generated or >>revalidated<< by the origin server.
                    */
                    // Reset Cache Age to be 0 seconds
                    CacheAge = TimeSpan.Zero;
                    result = Rfc2616.Common.ValidateCacheAfterResponse(this, resp);
                }
            }
 
            if (result == CacheValidationStatus.ReturnCachedResponse)
            {
                CacheHeaders[HttpKnownHeaderNames.Age] = ((int)(CacheAge.TotalSeconds)).ToString(NumberFormatInfo.InvariantInfo);
            }
            return result;
        }
 
        /// <summary>
        /// <para>
        /// This validation method is responsible to answer whether the live response is sufficient to make
        /// the final decision for caching protocol.
        /// This is useful in case of possible failure or inconsistent results received from
        /// the remote cache.
        /// </para>
        /// </summary>
        /// <remarks>  Invalid response from this method means the request was internally modified and should be retried </remarks>
        protected internal override CacheValidationStatus ValidateResponse() {
 
            if (this.Policy.Level != HttpRequestCacheLevel.CacheOrNextCacheOnly &&
                this.Policy.Level != HttpRequestCacheLevel.Default &&
                this.Policy.Level != HttpRequestCacheLevel.Revalidate)
            {
                // Those policy levels do not modify requests
                if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_response_valid_based_on_policy, Policy.ToString()));
                return CacheValidationStatus.Continue;
            }
 
            // We will need quick access to cache controls coming with the live response
            HttpWebResponse resp = Response as HttpWebResponse;
            if (resp == null) {
                if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_null_response_failure));
                return CacheValidationStatus.Continue;
            }
 
            FetchHeaderValues(false);
            if(Logging.On) Logging.PrintInfo(Logging.RequestCache, "StatusCode=" + ((int)resp.StatusCode).ToString(CultureInfo.InvariantCulture) + ' ' +resp.StatusCode.ToString() +
                                                      (resp.StatusCode == HttpStatusCode.PartialContent
                                                       ?", Content-Range: " + resp.Headers[HttpKnownHeaderNames.ContentRange]
                                                       :string.Empty)
                                                      );
 
 
            // Apply our best knowledge of HTTP caching and return the result
            // that can be hooked up and revised by the upper level
            return Rfc2616.OnValidateResponse(this);
        }
 
        /// <summary>
        /// <para>
        /// This action handler is responsible for making final decision on whether
        /// a received response can be cached.
        /// </para>
        /// </summary>
        /// <remarks>  Invalid result from this method means the response must not be cached </remarks>
        protected internal override CacheValidationStatus UpdateCache() {
 
            if (this.Policy.Level == HttpRequestCacheLevel.NoCacheNoStore) {
                if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_removed_existing_based_on_policy, Policy.ToString()));
                return CacheValidationStatus.RemoveFromCache;
            }
            if (this.Policy.Level == HttpRequestCacheLevel.CacheOnly) {
                if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_not_updated_based_on_policy, Policy.ToString()));
                return CacheValidationStatus.DoNotUpdateCache;
            }
 
            if (CacheHeaders == null)
                CacheHeaders = new WebHeaderCollection();
 
            if (SystemMeta == null)
                SystemMeta = new NameValueCollection(1, CaseInsensitiveAscii.StaticInstance);
 
            if (ResponseCacheControl == null) {
                //ValidateResponse was not invoked
                FetchHeaderValues(false);
            }
 
            // Apply our best knowledge of HTTP caching and return the result
            // that can be hooked up and revised by the upper level
            CacheValidationStatus result = Rfc2616.OnUpdateCache(this);
 
            if (result == CacheValidationStatus.UpdateResponseInformation || result == CacheValidationStatus.CacheResponse)
            {
                FinallyUpdateCacheEntry();
            }
            return result;
        }
 
        //
        //
        //
        private void FinallyUpdateCacheEntry() {
            // Transfer the context status line back to the metadata
 
            CacheEntry.EntryMetadata  = null;
            CacheEntry.SystemMetadata = null;
 
            if (CacheHeaders == null) {
                //must be an entry update without updating the headers
                return;
            }
 
            CacheEntry.EntryMetadata  = new StringCollection();
            CacheEntry.SystemMetadata = new StringCollection();
 
            if (CacheHttpVersion == null) {
                if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_invalid_http_version));
                CacheHttpVersion = new Version(1, 0);
            }
            // HTTP/1.1 200 OK
            System.Text.StringBuilder sb = new System.Text.StringBuilder(CacheStatusDescription.Length + 20);
            sb.Append("HTTP/");
            sb.Append(CacheHttpVersion.ToString(2));
            sb.Append(' ');
            sb.Append(((int)CacheStatusCode).ToString(NumberFormatInfo.InvariantInfo));
            sb.Append(' ');
            sb.Append(CacheStatusDescription);
 
            // Fetch the status line into cache metadata
            CacheEntry.EntryMetadata.Add(sb.ToString());
 
            UpdateStringCollection(CacheEntry.EntryMetadata,  CacheHeaders, false);
 
            if (SystemMeta != null)
            {
                UpdateStringCollection(CacheEntry.SystemMetadata, SystemMeta, true);
            }
 
            // Update other entry values
            if (ResponseExpires != DateTime.MinValue) {
                CacheEntry.ExpiresUtc = ResponseExpires;
            }
 
            if (ResponseLastModified != DateTime.MinValue)
            {
                CacheEntry.LastModifiedUtc = ResponseLastModified;
            }
 
            if (this.Policy.Level == HttpRequestCacheLevel.Default)
            {
                    CacheEntry.MaxStale = this.Policy.MaxStale;
            }
 
            CacheEntry.LastSynchronizedUtc = DateTime.UtcNow;
        }
        //
        //
        //
        private static void UpdateStringCollection(StringCollection result, NameValueCollection cc, bool winInetCompat)
        {
            StringBuilder sb;
 
            // Transfer headers
            for (int i=0; i < cc.Count; ++i)
            {
                    sb = new StringBuilder(40);
                    string key   = cc.GetKey(i) as string;
                    sb.Append(key).Append(':');
 
                    string[] val = cc.GetValues(i);
                    if (val.Length != 0)
                    {
                        if (winInetCompat)
                            {sb.Append(val[0]);}
                        else
                            {sb.Append(' ').Append(val[0]);}
                    }
 
                    for (int j = 1; j < val.Length; ++j)
                    {
                        sb.Append(key).Append(", ").Append(val[j]);
                    }
                    result.Add(sb.ToString());
            }
            // Transfer last \r\n
            result.Add(string.Empty);
        }
 
        // The format is
        // HTTP/X.Y SP NUMBER SP STRING
        // HTTP/1.1 200 OK
        //
        private string ParseStatusLine() {
 
            // This will indicate an invalid result
            CacheStatusCode = (HttpStatusCode)0;
 
            if (CacheEntry.EntryMetadata == null || CacheEntry.EntryMetadata.Count == 0)
            {
                return null;
            }
 
            string s = CacheEntry.EntryMetadata[0];
 
            if (s == null) {
                return null;
            }
 
            int  idx = 0;
            char ch = (char)0;
            while (++idx < s.Length && (ch=s[idx]) != '/') {
                ;
            }
 
            if (idx == s.Length) {return s;}
 
            int major = -1;
            int minor = -1;
            int status= -1;
 
            while (++idx < s.Length && (ch=s[idx]) >= '0' && ch <= '9') {
                major = (major<0? 0: major*10) +(ch - '0');
            }
 
            if (major < 0 || ch != '.') {return s;}
 
            while (++idx < s.Length && (ch=s[idx]) >= '0' && ch <= '9') {
                minor = (minor<0? 0: minor*10) + (ch - '0');
            }
 
            if (minor < 0 || (ch != ' ' && ch != '\t')) {return s;}
 
            while (++idx < s.Length && ((ch=s[idx]) == ' ' || ch == '\t'))
                ;
 
            if (idx >= s.Length) {return s;}
 
            while (ch >= '0' && ch <= '9')
            {
                status = (status<0? 0: status*10) +(ch - '0');
                if (++idx == s.Length)
                    break;
                ch=s[idx];
            }
 
            if (status < 0 || (idx <= s.Length && (ch != ' ' && ch != '\t'))) {return s;}
 
            while (idx < s.Length && (s[idx] == ' ' || s[idx] == '\t'))
                ++idx;
 
            CacheStatusDescription = s.Substring(idx);
 
            CacheHttpVersion = new Version(major, minor);
            CacheStatusCode = (HttpStatusCode)status;
            return s;
        }
        //
        private void CreateCacheHeaders(bool ignoreFirstString)
        {
 
            if (CacheHeaders == null)
                CacheHeaders = new WebHeaderCollection();
 
            if (CacheEntry.EntryMetadata == null || CacheEntry.EntryMetadata.Count == 0)
            {
                if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_no_http_response_header));
                return;
            }
 
            string s = ParseNameValues(CacheHeaders, CacheEntry.EntryMetadata, ignoreFirstString?1:0);
            if (s != null)
            {
                if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_http_header_parse_error, s));
                CacheHeaders.Clear();
            }
        }
        //
        private void CreateSystemMeta()
        {
            if (SystemMeta == null)
            {
                SystemMeta = new NameValueCollection((CacheEntry.EntryMetadata == null || CacheEntry.EntryMetadata.Count == 0? 2: CacheEntry.EntryMetadata.Count),
                                                     CaseInsensitiveAscii.StaticInstance);
            }
            if (CacheEntry.EntryMetadata == null || CacheEntry.EntryMetadata.Count == 0)
                {return;}
 
            string s = ParseNameValues(SystemMeta, CacheEntry.SystemMetadata, 0);
            if (s != null)
            {
                if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_metadata_name_value_parse_error, s));
            }
        }
        //
        // Returns null on success, otherwise the offending string.
        //
        private string ParseNameValues(NameValueCollection cc, StringCollection sc, int start)
        {
            WebHeaderCollection wc = cc as WebHeaderCollection;
 
            string lastHeaderName = null;
            if (sc != null)
            {
                for (int i = start; i < sc.Count; ++i)
                {
                    string s = sc[i];
                    if (s == null || s.Length == 0)
                    {
                        //An empty string stands for \r\n
                        //Treat that as the end of headers and ignore the rest
                        return null;
                    }
 
                    if (s[0] == ' ' || s[0] == '\t')
                    {
                        if (lastHeaderName == null) {return s;}
                        if (wc != null)
                            wc.AddInternal(lastHeaderName, s);
                        else
                            cc.Add(lastHeaderName, s);
                    }
 
                    int colpos = s.IndexOf(':');
                    if (colpos < 0)
                        {return s;}
                    lastHeaderName = s.Substring(0, colpos);
                    while (++colpos < s.Length && (s[colpos] == ' ' || s[colpos] == '\t'))
                        {;}
 
                    try {
                        if (wc != null)
                            wc.AddInternal(lastHeaderName, s.Substring(colpos));
                        else
                            cc.Add(lastHeaderName, s.Substring(colpos));
                    }
                    catch(Exception e) {
                        if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException)
                            throw;
                        // Otherwise the value of 's' will be used to log an error.
                        // The fact that we cannot parse headers may stand for corrupted metadata that we try to ignore
                        return s;
                    }
                }
            }
            return null;
        }
        //
        //
        //
        private void FetchHeaderValues(bool forCache) {
 
            WebHeaderCollection cc = forCache? CacheHeaders: Response.Headers;
 
 
            FetchCacheControl(cc.CacheControl, forCache);
 
            // Parse Date Header
            string s = cc.Date;
 
            DateTime date = DateTime.MinValue;
            if (s != null && HttpDateParse.ParseHttpDate(s, out date)) {
                date = date.ToUniversalTime();
            }
            if (forCache) {
                CacheDate = date;
            }
            else {
                ResponseDate = date;
            }
 
            // Parse Expires Header
            s = cc.Expires;
 
            date = DateTime.MinValue;
            if (s != null && HttpDateParse.ParseHttpDate(s, out date)) {
                date = date.ToUniversalTime();
            }
            if (forCache) {
                CacheExpires = date;
            }
            else {
                ResponseExpires = date;
            }
 
            // Parse LastModified Header
            s = cc.LastModified;
 
            date = DateTime.MinValue;
            if (s != null && HttpDateParse.ParseHttpDate(s, out date)) {
                date = date.ToUniversalTime();
            }
            if (forCache) {
                CacheLastModified = date;
            }
            else {
                ResponseLastModified = date;
            }
 
            long totalLength = -1;
            long startRange = -1;
            long end = -1;
 
            HttpWebResponse resp = Response as HttpWebResponse;
            if ((forCache? CacheStatusCode: resp.StatusCode) != HttpStatusCode.PartialContent) {
 
                // Parse Content-Length Header
                s = cc.ContentLength;
                if (s != null && s.Length != 0) {
                    int i = 0;
                    char ch = s[0];
                    while (i < s.Length && ch == ' ') {
                        ch = s[++i];
                    }
                    if (i != s.Length && ch >= '0' && ch <= '9') {
                        totalLength = ch-'0';
                        while(++i < s.Length && (ch = s[i]) >= '0' && ch <= '9') {
                            totalLength = totalLength*10+(ch-'0');
                        }
                    }
                }
            }
            else {
                //Parse Content-Range
                s = cc[HttpKnownHeaderNames.ContentRange];
                if(s == null || !Rfc2616.Common.GetBytesRange(s, ref startRange, ref end, ref totalLength, false)) {
                    if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_content_range_error, (s==null ? "<null>" : s)));
                    startRange=end=totalLength = -1;
                }
                else if (forCache && totalLength == CacheEntry.StreamSize)
                {
                    // This is a whole response, step back to 200
                    startRange = -1;
                    end = -1;
                    CacheStatusCode = HttpStatusCode.OK;
                    CacheStatusDescription = Rfc2616.Common.OkDescription;
                }
            }
 
            if (forCache) {
                CacheEntityLength  = totalLength;
                ResponseRangeStart = startRange;
                ResponseRangeEnd   = end;
 
            }
            else {
                ResponseEntityLength = totalLength;
                ResponseRangeStart = startRange;
                ResponseRangeEnd   = end;
            }
 
            //Parse Age Header
            TimeSpan span = TimeSpan.MinValue;
            s = cc[HttpKnownHeaderNames.Age];
            if (s != null) {
                int i = 0;
                int sec = 0;
                while(i < s.Length && s[i++] == ' ') {
                    ;
                }
                while(i < s.Length && s[i] >= '0' && s[i] <= '9') {
                    sec = sec*10 + (s[i++] - '0');
                }
                span = TimeSpan.FromSeconds(sec);
            }
 
            if (forCache) {
                CacheAge = span;
            }
            else {
                ResponseAge = span;
            }
        }
 
        const long LO = 0x0020002000200020L;
        const int  LOI = 0x00200020;
        const long _prox = 'p'|('r'<<16)|((long)'o'<<32)|((long)'x'<<48);
        const long _y_re = 'y'|('-'<<16)|((long)'r'<<32)|((long)'e'<<48);
        const long _vali = 'v'|('a'<<16)|((long)'l'<<32)|((long)'i'<<48);
        const long _date = 'd'|('a'<<16)|((long)'t'<<32)|((long)'e'<<48);
 
        const long _publ = 'p'|('u'<<16)|((long)'b'<<32)|((long)'l'<<48);
        const int  _ic   = 'i'|('c'<<16);
 
        const long _priv = 'p'|('r'<<16)|((long)'i'<<32)|((long)'v'<<48);
        const int  _at   = 'a'|('t'<<16);
 
        const long _no_c = 'n'|('o'<<16)|((long)'-'<<32)|((long)'c'<<48);
        const long _ache = 'a'|('c'<<16)|((long)'h'<<32)|((long)'e'<<48);
 
        const long _no_s = 'n'|('o'<<16)|((long)'-'<<32)|((long)'s'<<48);
        const long _tore = 't'|('o'<<16)|((long)'r'<<32)|((long)'e'<<48);
 
        const long _must = 'm'|('u'<<16)|((long)'s'<<32)|((long)'t'<<48);
        const long __rev = '-'|('r'<<16)|((long)'e'<<32)|((long)'v'<<48);
        const long _alid = 'a'|('l'<<16)|((long)'i'<<32)|((long)'d'<<48);
 
        const long _max_ = 'm'|('a'<<16)|((long)'x'<<32)|((long)'-'<<48);
        const int  _ag   = 'a'|('g'<<16);
 
        const long _s_ma = 's'|('-'<<16)|((long)'m'<<32)|((long)'a'<<48);
        const long _xage = 'x'|('a'<<16)|((long)'g'<<32)|((long)'e'<<48);
        //
        //
        //
        private unsafe void FetchCacheControl(string s, bool forCache) {
            //Initialize it
            ResponseCacheControl control = new ResponseCacheControl();
            if (forCache) {
                CacheCacheControl = control;
            }
            else {
                ResponseCacheControl = control;
            }
 
            if (s != null && s.Length != 0) {
                fixed (char *sp = s) {
                    int len = s.Length;
                    for (int i = 0; i < len-4; ++i) {
                        if (sp[i] < ' ' || sp[i] >= 0x7F) {
                            if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_cache_control_error, s));
                            //invalid format
                            return;
                        }
                        if (sp[i] == ' ' || sp[i] == ',') {
                            continue;
                        }
 
                        // These if-else are two logically identical blocks that differ only in the way of how text search is done.
                        // The text search is done differently for 32 and X-bits platforms.
                        // ATTN: You are responsible for keeping the rest of the logic in sync.
                        if (IntPtr.Size == 4) {
                            // We are on 32-bits platform
 
                            long *mask = (long*)&(sp[i]);
                            //making interested chars lowercase, others are ignored anyway
                            switch(*mask|LO) {
 
                                case _prox: if (i+16 > len) continue;
                                    if ((*(mask+1)|LO) != _y_re || (*(mask+2)|LO) != _vali || (*(mask+3)|LO) != _date) continue;
                                    control.ProxyRevalidate = true;
                                    i+=15;
                                    break;
 
                                case _publ: if (i+6 > len) return;
                                    if ((*((int*)(mask+1))|LOI) != _ic) continue;
                                    control.Public = true;
                                    i+=5;
                                    break;
 
                                case _priv: if (i+7 > len) return;
                                    if ((*((int*)(mask+1))|LOI) != _at || (sp[i+6]|0x20) != 'e') continue;
                                    control.Private = true;
                                    i+=6;
                                    // Check for a case: private = "name1,name2"
                                    while (i < len && sp[i] == ' ') {++i;}
 
                                    if (i >= len || sp[i] != '=') {--i;break;}
 
                                    while (i < len && sp[++i] == ' ') {;}
 
                                    if (i >= len || sp[i] != '\"') {--i;break;}
 
                                    System.Collections.ArrayList privateList = new System.Collections.ArrayList();
                                    ++i;
                                    while(i < len && sp[i] != '\"') {
 
                                        while (i < len && sp[i] == ' ') {++i;}
                                        int start = i;
                                        while (i < len && sp[i] != ' ' && sp[i] != ',' && sp[i] != '\"') {++i;}
                                        if (start != i) {
                                            privateList.Add(s.Substring(start, i-start));
                                        }
                                        while (i < len && sp[i] != ',' && sp[i] != '\"') {++i;}
                                    }
                                    if (privateList.Count != 0) {
                                        control.PrivateHeaders = (string[])privateList.ToArray(typeof(string));
                                    }
                                    break;
 
                                case _no_c: if (i+8 > len) return;
                                    if ((*(mask+1)|LOI) != _ache) continue;
                                    control.NoCache = true;
                                    i+=7;
                                    // Check for a case: no-cache = "name1,name2"
                                    while (i < len && sp[i] == ' ') {++i;}
 
                                    if (i >= len || sp[i] != '=') {--i;break;}
 
                                    while (i < len && sp[++i] == ' ') {;}
 
                                    if (i >= len || sp[i] != '\"') {--i;break;}
 
                                    System.Collections.ArrayList nocacheList = new System.Collections.ArrayList();
                                    ++i;
                                    while(i < len && sp[i] != '\"') {
 
                                        while (i < len && sp[i] == ' ') {++i;}
                                        int start = i;
                                        while (i < len && sp[i] != ' ' && sp[i] != ',' && sp[i] != '\"') {++i;}
                                        if (start != i) {
                                            nocacheList.Add(s.Substring(start, i-start));
                                        }
                                        while (i < len && sp[i] != ',' && sp[i] != '\"') {++i;}
                                    }
                                    if (nocacheList.Count != 0) {
                                        control.NoCacheHeaders = (string[])nocacheList.ToArray(typeof(string));
                                    }
                                    break;
 
                                case _no_s: if (i+8 > len) return;
                                    if ((*(mask+1)|LOI) != _tore) continue;
                                    control.NoStore = true;
                                    i+=7;
                                    break;
 
                                case _must: if (i+15 > len) continue;
 
                                    if ((*(mask+1)|LO) != __rev || (*(mask+2)|LO) != _alid || (*(int*)(mask+3)|LOI) != _at || (sp[i+14]|0x20) != 'e') continue;
                                    control.MustRevalidate = true;
                                    i+=14;
                                    break;
 
                                case _max_: if (i+7 > len) return;
                                    if ((*((int*)(mask+1))|LOI) != _ag || (sp[i+6]|0x20) != 'e') continue;
                                    i+=7;
                                    while (i < len && sp[i] == ' ') {
                                        ++i;
                                    }
                                    if (i == len || sp[i++] != '=') return;
                                    while (i < len && sp[i] == ' ') {
                                        ++i;
                                    }
                                    if (i == len) return;
                                    control.MaxAge = 0;
                                    while (i < len && sp[i] >= '0' && sp[i] <= '9') {
                                        control.MaxAge =control.MaxAge*10 + (sp[i++]-'0');
                                    }
                                    --i;
                                    break;
 
                                case _s_ma: if (i+8 > len) return;
                                    if ((*(mask+1)|LOI) != _xage) continue;
                                    i+=8;
                                    while (i < len && sp[i] == ' ') {
                                        ++i;
                                    }
                                    if (i == len || sp[i++] != '=') return;
                                    while (i < len && sp[i] == ' ') {
                                        ++i;
                                    }
                                    if (i == len) return;
                                    control.SMaxAge = 0;
                                    while (i < len && sp[i] >= '0' && sp[i] <= '9') {
                                        control.SMaxAge = control.SMaxAge*10 + (sp[i++]-'0');
                                    }
                                    --i;
                                    break;
                            }
                        }
                        else {
                            // We cannot use optimized code path due to IA-64 memory alligment problems see VSWhidbey 118967
                            if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "proxy-revalidate")) {
                                control.ProxyRevalidate = true;
                                i+=15;
                            }
                            else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "public")) {
                                control.Public = true;
                                i+=5;
                            }
                            else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "private")) {
                                control.Private = true;
                                i+=6;
                                // Check for a case: private = "name1,name2"
                                while (i < len && sp[i] == ' ') {++i;}
 
                                if (i >= len || sp[i] != '=') {--i;break;}
 
                                while (i < len && sp[++i] == ' ') {;}
 
                                if (i >= len || sp[i] != '\"') {--i;break;}
 
                                System.Collections.ArrayList privateList = new System.Collections.ArrayList();
                                ++i;
                                while(i < len && sp[i] != '\"') {
 
                                    while (i < len && sp[i] == ' ') {++i;}
                                    int start = i;
                                    while (i < len && sp[i] != ' ' && sp[i] != ',' && sp[i] != '\"') {++i;}
                                    if (start != i) {
                                        privateList.Add(s.Substring(start, i-start));
                                    }
                                    while (i < len && sp[i] != ',' && sp[i] != '\"') {++i;}
                                }
                                if (privateList.Count != 0) {
                                    control.PrivateHeaders = (string[])privateList.ToArray(typeof(string));
                                }
                            }
                            else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "no-cache")) {
                                control.NoCache = true;
                                i+=7;
                                // Check for a case: no-cache = "name1,name2"
                                while (i < len && sp[i] == ' ') {++i;}
 
                                if (i >= len || sp[i] != '=') {--i;break;}
 
                                while (i < len && sp[++i] == ' ') {;}
 
                                if (i >= len || sp[i] != '\"') {--i;break;}
 
                                System.Collections.ArrayList nocacheList = new System.Collections.ArrayList();
                                ++i;
                                while(i < len && sp[i] != '\"') {
 
                                    while (i < len && sp[i] == ' ') {++i;}
                                    int start = i;
                                    while (i < len && sp[i] != ' ' && sp[i] != ',' && sp[i] != '\"') {++i;}
                                    if (start != i) {
                                        nocacheList.Add(s.Substring(start, i-start));
                                    }
                                    while (i < len && sp[i] != ',' && sp[i] != '\"') {++i;}
                                }
                                if (nocacheList.Count != 0) {
                                    control.NoCacheHeaders = (string[])nocacheList.ToArray(typeof(string));
                                }
                            }
                            else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "no-store")) {
                                control.NoStore = true;
                                i+=7;
                            }
                            else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "must-revalidate")) {
                                control.MustRevalidate = true;
                                i+=14;
                            }
                            else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "max-age")) {
                                i+=7;
                                while (i < len && sp[i] == ' ') {
                                    ++i;
                                }
                                if (i == len || sp[i++] != '=') return;
                                while (i < len && sp[i] == ' ') {
                                    ++i;
                                }
                                if (i == len) return;
                                control.MaxAge = 0;
                                while (i < len && sp[i] >= '0' && sp[i] <= '9') {
                                    control.MaxAge =control.MaxAge*10 + (sp[i++]-'0');
                                }
                                --i;
                            }
                            else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "smax-age")) {
                                i+=8;
                                while (i < len && sp[i] == ' ') {
                                    ++i;
                                }
                                if (i == len || sp[i++] != '=') return;
                                while (i < len && sp[i] == ' ') {
                                    ++i;
                                }
                                if (i == len) return;
                                control.SMaxAge = 0;
                                while (i < len && sp[i] >= '0' && sp[i] <= '9') {
                                    control.SMaxAge = control.SMaxAge*10 + (sp[i++]-'0');
                                }
                                --i;
                            }
                        }
                    }
                }
            }
        }
 
        /*
          - any stored Warning headers with warn-code 1xx (see section
        14.46) MUST be deleted from the cache entry and the forwarded
        response.
 
          - any stored Warning headers with warn-code 2xx MUST be retained
        in the cache entry and the forwarded response.
        */
        private void RemoveWarnings_1xx() {
 
            string[] warnings = CacheHeaders.GetValues(HttpKnownHeaderNames.Warning);
            if (warnings == null) {
                return;
            }
            ArrayList remainingWarnings = new ArrayList();
            ParseHeaderValues(warnings, ParseWarningsCallback, remainingWarnings);
            CacheHeaders.Remove(HttpKnownHeaderNames.Warning);
            for (int i=0; i < remainingWarnings.Count; ++i) {
                CacheHeaders.Add(HttpKnownHeaderNames.Warning, (string)remainingWarnings[i]);
            }
        }
 
        private static readonly ParseCallback ParseWarningsCallback = new ParseCallback(ParseWarningsCallbackMethod);
        private static void ParseWarningsCallbackMethod(string s, int start, int end, IList list) {
            if (end >= start && s[start] != '1') {
                ParseValuesCallbackMethod(s, start, end, list);
            }
        }
        //
        // This is used by other classes to get the list if values from a header string
        //
        internal delegate void ParseCallback(string s, int start, int end, IList list);
        internal static readonly ParseCallback ParseValuesCallback = new ParseCallback(ParseValuesCallbackMethod);
        private static void ParseValuesCallbackMethod(string s, int start, int end, IList list) {
 
            // Deal with the cases: '' ' ' 'value' 'value   '
            while (end >= start && s[end] == ' ') {
                --end;
            }
            if (end >= start) {
                list.Add(s.Substring(start, end-start+1));
            }
        }
 
 
        //
        // Parses header values calls a callback one value after other.
        // Note a single string can contain multiple values and any value may have a quoted string in.
        // The parser will not cut trailing spaces when invoking a callback
        //
        internal static void ParseHeaderValues(string[] values, ParseCallback calback, IList list) {
 
            if (values == null) {
                return;
            }
            for (int i = 0; i < values.Length; ++i) {
                string val = values[i];
 
                int end = 0;
                int start = 0;
                while (end < val.Length) {
                    //skip spaces
                    while (start < val.Length && val[start] == ' ') {
                        ++start;
                    }
 
                    if (start == val.Length ) {
                        //empty header value
                        break;
                    }
 
                    // find comma or quote
                    end = start;
                find_comma:
                    while (end < val.Length && val[end] != ',' && val[end] != '\"') {
                        ++end;
                    }
 
                    if (end == val.Length ) {
                        calback(val, start, end-1, list);
                        break;
                    }
 
                    if (val[end] == '\"') {
                        while (++end < val.Length && val[end] != '"') {
                            ;
                        }
                        if (end == val.Length ) {
                            //warning: no closing quote, accepting
                            calback(val, start, end-1, list);
                            break;
                        }
                        goto find_comma;
                    }
                    else {
                        //Comma
                        calback(val, start, end-1, list);
                        // skip leading spaces
                        while (++end < val.Length && val[end] == ' ') {
                            ;
                        }
                        if (end >= val.Length) {
                            break;
                        }
                        start = end;
                    }
                }
            }
        }
    }
    //
    //
    //
    //ATTN: The values order is importent
    internal enum HttpMethod {
        Other   = -1,
        Head    = 0,
        Get,
        Post,
        Put,
        Delete,
        Options,
        Trace,
        Connect
    }
    //
    //
    //
    internal class ResponseCacheControl {
        internal bool Public;
        internal bool Private;
        internal string[] PrivateHeaders;
        internal bool NoCache;
        internal string[] NoCacheHeaders;
        internal bool NoStore;
        internal bool MustRevalidate;
        internal bool ProxyRevalidate;
        internal int  MaxAge;
        internal int  SMaxAge;
 
        internal ResponseCacheControl() {
            MaxAge = SMaxAge = -1;
        }
 
        internal bool IsNotEmpty {
            get {
                return (Public || Private || NoCache || NoStore || MustRevalidate || ProxyRevalidate || MaxAge != -1 || SMaxAge != -1);
            }
        }
 
        public override string  ToString() {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
 
            if (Public) {
                sb.Append(" public");
            }
            if (Private) {
                sb.Append(" private");
                if (PrivateHeaders != null) {
                    sb.Append('=');
                    for (int i = 0; i < PrivateHeaders.Length-1; ++i) {
                        sb.Append(PrivateHeaders[i]).Append(',');
                    }
                    sb.Append(PrivateHeaders[PrivateHeaders.Length-1]);
                }
            }
            if (NoCache) {
                sb.Append(" no-cache");
                if (NoCacheHeaders != null) {
                    sb.Append('=');
                    for (int i = 0; i < NoCacheHeaders.Length-1; ++i) {
                        sb.Append(NoCacheHeaders[i]).Append(',');
                    }
                    sb.Append(NoCacheHeaders[NoCacheHeaders.Length-1]);
                }
            }
            if (NoStore) {
                sb.Append(" no-store");
            }
            if (MustRevalidate) {
                sb.Append(" must-revalidate");
            }
            if (ProxyRevalidate) {
                sb.Append(" proxy-revalidate");
            }
            if (MaxAge != -1) {
                sb.Append(" max-age=").Append(MaxAge);
            }
            if (SMaxAge != -1) {
                sb.Append(" s-maxage=").Append(SMaxAge);
            }
            return sb.ToString();
        }
    }
 
}