2 Copyright (c) Microsoft Corporation
6 HttpRequestCacheValidator.cs
9 The class implements HTTP Caching validators as per RFC2616
14 Alexei Vopilov 21-Dec-2002
18 Jan 25 2004 - Changed the visibility of the class from public to internal.
21 namespace System.Net.Cache {
25 using System.Collections;
27 using System.Collections.Specialized;
28 using System.Globalization;
29 using System.Threading;
32 /// <summary> The class represents an adavanced way for an application to control caching protocol </summary>
33 internal class HttpRequestCacheValidator: RequestCacheValidator {
34 internal const string Warning_110 = "110 Response is stale";
35 internal const string Warning_111 = "111 Revalidation failed";
36 internal const string Warning_112 = "112 Disconnected operation";
37 internal const string Warning_113 = "113 Heuristic expiration";
39 private struct RequestVars {
40 internal HttpMethod Method;
41 internal bool IsCacheRange;
42 internal bool IsUserRange;
43 internal string IfHeader1;
44 internal string Validator1;
45 internal string IfHeader2;
46 internal string Validator2;
49 private HttpRequestCachePolicy m_HttpPolicy;
51 private HttpStatusCode m_StatusCode;
52 private string m_StatusDescription;
53 private Version m_HttpVersion;
54 private WebHeaderCollection m_Headers;
55 private NameValueCollection m_SystemMeta;
57 private bool m_DontUpdateHeaders;
58 private bool m_HeuristicExpiration;
60 private Vars m_CacheVars;
61 private Vars m_ResponseVars;
62 private RequestVars m_RequestVars;
65 internal DateTime Date;
66 internal DateTime Expires;
67 internal DateTime LastModified;
68 internal long EntityLength;
69 internal TimeSpan Age;
70 internal TimeSpan MaxAge;
71 internal ResponseCacheControl CacheControl;
72 internal long RangeStart;
73 internal long RangeEnd;
75 internal void Initialize() {
76 EntityLength = RangeStart = RangeEnd = -1;
77 Date = DateTime.MinValue;
78 Expires = DateTime.MinValue;
79 LastModified = DateTime.MinValue;
80 Age = TimeSpan.MinValue;
81 MaxAge = TimeSpan.MinValue;
87 internal HttpStatusCode CacheStatusCode {get{return m_StatusCode;} set{m_StatusCode = value;}}
89 internal string CacheStatusDescription {get{return m_StatusDescription;} set{m_StatusDescription = value;}}
91 internal Version CacheHttpVersion {get{return m_HttpVersion;} set{m_HttpVersion = value;}}
94 internal WebHeaderCollection CacheHeaders {get{return m_Headers;} set{m_Headers = value;}}
97 internal new HttpRequestCachePolicy Policy {
99 if(m_HttpPolicy != null) return m_HttpPolicy;
100 m_HttpPolicy = base.Policy as HttpRequestCachePolicy;
101 if(m_HttpPolicy != null) return m_HttpPolicy;
102 // promote base policy to Http one
103 m_HttpPolicy = new HttpRequestCachePolicy((HttpRequestCacheLevel)base.Policy.Level);
108 internal NameValueCollection SystemMeta {get{return m_SystemMeta;} set{m_SystemMeta = value;}}
109 internal HttpMethod RequestMethod {get{return m_RequestVars.Method;} set{m_RequestVars.Method = value;}}
110 internal bool RequestRangeCache {get{return m_RequestVars.IsCacheRange;} set{m_RequestVars.IsCacheRange = value;}}
111 internal bool RequestRangeUser {get{return m_RequestVars.IsUserRange;} set{m_RequestVars.IsUserRange = value;}}
112 internal string RequestIfHeader1 {get{return m_RequestVars.IfHeader1;} set{m_RequestVars.IfHeader1 = value;}}
113 internal string RequestValidator1 {get{return m_RequestVars.Validator1;} set{m_RequestVars.Validator1 = value;}}
114 internal string RequestIfHeader2 {get{return m_RequestVars.IfHeader2;} set{m_RequestVars.IfHeader2 = value;}}
115 internal string RequestValidator2 {get{return m_RequestVars.Validator2;} set{m_RequestVars.Validator2 = value;}}
117 internal bool CacheDontUpdateHeaders {get{return m_DontUpdateHeaders;} set{m_DontUpdateHeaders = value;}}
119 internal DateTime CacheDate {get{return m_CacheVars.Date;} set{m_CacheVars.Date = value;}}
120 internal DateTime CacheExpires {get{return m_CacheVars.Expires;} set{m_CacheVars.Expires = value;}}
121 internal DateTime CacheLastModified {get{return m_CacheVars.LastModified;} set{m_CacheVars.LastModified = value;}}
122 internal long CacheEntityLength {get{return m_CacheVars.EntityLength ;} set{m_CacheVars.EntityLength = value;}}
123 internal TimeSpan CacheAge {get{return m_CacheVars.Age;} set{m_CacheVars.Age = value;}}
124 internal TimeSpan CacheMaxAge {get{return m_CacheVars.MaxAge;} set{m_CacheVars.MaxAge = value;}}
125 internal bool HeuristicExpiration {get{return m_HeuristicExpiration;} set{m_HeuristicExpiration = value;}}
127 internal ResponseCacheControl CacheCacheControl {get{return m_CacheVars.CacheControl;} set{m_CacheVars.CacheControl = value;}}
129 internal DateTime ResponseDate {get{return m_ResponseVars.Date;} set{m_ResponseVars.Date = value;}}
130 internal DateTime ResponseExpires {get{return m_ResponseVars.Expires;} set{m_ResponseVars.Expires = value;}}
131 internal DateTime ResponseLastModified {get{return m_ResponseVars.LastModified;} set{m_ResponseVars.LastModified = value;}}
132 internal long ResponseEntityLength {get{return m_ResponseVars.EntityLength ;}set{m_ResponseVars.EntityLength = value;}}
133 internal long ResponseRangeStart {get{return m_ResponseVars.RangeStart;} set{m_ResponseVars.RangeStart = value;}}
134 internal long ResponseRangeEnd {get{return m_ResponseVars.RangeEnd;} set{m_ResponseVars.RangeEnd = value;}}
135 internal TimeSpan ResponseAge {get{return m_ResponseVars.Age;} set{m_ResponseVars.Age = value;}}
136 internal ResponseCacheControl ResponseCacheControl {get{return m_ResponseVars.CacheControl;} set{m_ResponseVars.CacheControl = value;}}
139 private void ZeroPrivateVars()
141 // Set default values for private members here
142 m_RequestVars = new RequestVars();
145 m_StatusCode = (HttpStatusCode)0;
146 m_StatusDescription = null;
147 m_HttpVersion = null;
150 m_DontUpdateHeaders = false;
151 m_HeuristicExpiration = false;
153 m_CacheVars = new Vars();
154 m_CacheVars.Initialize();
156 m_ResponseVars= new Vars();
157 m_ResponseVars.Initialize();
161 internal override RequestCacheValidator CreateValidator()
163 return new HttpRequestCacheValidator(StrictCacheErrors, UnspecifiedMaxAge);
168 // Consider removing.
169 internal HttpRequestCacheValidator(): base()
175 internal HttpRequestCacheValidator(bool strictCacheErrors, TimeSpan unspecifiedMaxAge): base(strictCacheErrors, unspecifiedMaxAge)
180 // This validation method is called first and before any Cache access is done.
181 // Given the request instance the code has to decide whether the request is ever suitable for caching.
184 // Continue = Proceed to the next protocol stage.
185 // DoNotTakeFromCache = Don't used caches value for this request
186 // DoNotUseCache = Cache is not used for this request and response is not cached.
187 protected internal override CacheValidationStatus ValidateRequest() {
189 // cleanup context after previous request
192 string method = Request.Method.ToUpper(CultureInfo.InvariantCulture);
193 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_request_method, method));
196 case "GET" : RequestMethod = HttpMethod.Get; break;
197 case "POST": RequestMethod = HttpMethod.Post; break;
198 case "HEAD": RequestMethod = HttpMethod.Head; break;
199 case "PUT" : RequestMethod = HttpMethod.Put; break;
200 case "DELETE": RequestMethod = HttpMethod.Delete; break;
201 case "OPTIONS": RequestMethod = HttpMethod.Options; break;
202 case "TRACE": RequestMethod = HttpMethod.Trace; break;
203 case "CONNECT": RequestMethod = HttpMethod.Connect; break;
204 default: RequestMethod = HttpMethod.Other; break;
207 // Apply our best knowledge of HTTP caching and return the result
208 // that can be hooked up and revised by the upper level
209 return Rfc2616.OnValidateRequest(this);
213 // This validation method is called after caching protocol has retrieved the metadata of a cached entry.
214 // Given the cached entry context, the request instance and the effective caching policy,
215 // the handler has to decide whether a cached item can be considered as fresh.
216 protected internal override CacheFreshnessStatus ValidateFreshness()
219 // Transfer cache entry metadata into status line and headers.
220 string s = ParseStatusLine();
223 if ((int) CacheStatusCode == 0) {
224 Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_http_status_parse_failure, (s == null ? "null" : s)));
227 Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_http_status_line, (CacheHttpVersion != null ? CacheHttpVersion.ToString() : "null"), (int)CacheStatusCode, CacheStatusDescription));
232 CreateCacheHeaders((int)CacheStatusCode != 0);
235 // We will need quick access to cache-control and other headers coming with the cached item
236 FetchHeaderValues(true);
238 if(Logging.On) Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_cache_control, CacheCacheControl.ToString()));
240 // Now we try to apply our best knowledge of HTTP caching and return the result
241 // that can be hooked up and revised on the upper level
242 return Rfc2616.OnValidateFreshness(this);
245 /// <remarks> This method may add headers under the "Warning" header name </remarks>
246 protected internal override CacheValidationStatus ValidateCache() {
248 if (this.Policy.Level != HttpRequestCacheLevel.Revalidate && base.Policy.Level >= RequestCacheLevel.Reload)
250 // For those policies cache is never returned
251 GlobalLog.Assert("OnValidateCache()", "This validator should not be called for policy = " + Policy.ToString());
252 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_validator_invalid_for_policy, Policy.ToString()));
253 return CacheValidationStatus.DoNotTakeFromCache;
256 // First check is do we have a cached entry at all?
257 // Also we include some very special case where cache got a 304 (NotModified) response somehow
258 if (CacheStream == Stream.Null || (int)CacheStatusCode == 0 || CacheStatusCode == HttpStatusCode.NotModified)
260 if (this.Policy.Level == HttpRequestCacheLevel.CacheOnly)
262 // Throw because entry was not found and it's cache-only policy
263 FailRequest(WebExceptionStatus.CacheEntryNotFound);
265 return CacheValidationStatus.DoNotTakeFromCache;
268 if (RequestMethod == HttpMethod.Head)
270 // For a HEAD request we release the cache entry stream asap since we will have to suppress it anyway
272 CacheStream = new SyncMemoryStream(new byte[] {});
275 // Apply our best knowledge of HTTP caching and return the result
276 // that can be hooked up and revised by the upper level
278 CacheValidationStatus result = CacheValidationStatus.DoNotTakeFromCache;
281 // Before request submission validation
284 // If we return from cache we should remove existing 1xx warnings
285 RemoveWarnings_1xx();
287 // default values for a response from cache.
288 CacheStreamOffset = 0;
289 CacheStreamLength = CacheEntry.StreamSize;
291 result = Rfc2616.OnValidateCache(this);
292 if (result != CacheValidationStatus.ReturnCachedResponse && this.Policy.Level == HttpRequestCacheLevel.CacheOnly) {
293 // Throw because entry was not found and it's cache-only policy
294 FailRequest(WebExceptionStatus.CacheEntryNotFound);
297 if (result == CacheValidationStatus.ReturnCachedResponse)
299 if (CacheFreshnessStatus == CacheFreshnessStatus.Stale) {
300 CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_110);
302 if (base.Policy.Level == RequestCacheLevel.CacheOnly) {
303 CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_112);
305 if (HeuristicExpiration && (int)CacheAge.TotalSeconds >= 24*3600) {
306 CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_113);
310 if (result == CacheValidationStatus.DoNotTakeFromCache) {
311 // We signal that current cache entry can be only replaced and not updated
312 CacheStatusCode = (HttpStatusCode) 0;
314 else if (result == CacheValidationStatus.ReturnCachedResponse) {
315 CacheHeaders[HttpKnownHeaderNames.Age] = ((int)(CacheAge.TotalSeconds)).ToString(NumberFormatInfo.InvariantInfo);
320 // This is (optionally) called after receiveing a live response
322 protected internal override CacheValidationStatus RevalidateCache()
324 if (this.Policy.Level != HttpRequestCacheLevel.Revalidate && base.Policy.Level >= RequestCacheLevel.Reload)
326 // For those policies cache is never returned
327 GlobalLog.Assert("RevalidateCache()", "This validator should not be called for policy = " + Policy.ToString());
328 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_validator_invalid_for_policy, Policy.ToString()));
329 return CacheValidationStatus.DoNotTakeFromCache;
332 // First check is do we have a cached entry at all?
333 // Also we include some very special case where cache got a 304 (NotModified) response somehow
334 if (CacheStream == Stream.Null || (int)CacheStatusCode == 0 || CacheStatusCode == HttpStatusCode.NotModified)
336 return CacheValidationStatus.DoNotTakeFromCache;
340 // This is a second+ time validation after receiving at least one response
343 // Apply our best knowledge of HTTP caching and return the result
344 // that can be hooked up and revised by the upper level
345 CacheValidationStatus result = CacheValidationStatus.DoNotTakeFromCache;
347 HttpWebResponse resp = Response as HttpWebResponse;
350 // This will result to an application error
351 return CacheValidationStatus.DoNotTakeFromCache;
354 if (resp.StatusCode >= HttpStatusCode.InternalServerError) {
355 // If server returned a 5XX server error
356 if (Rfc2616.Common.ValidateCacheOn5XXResponse(this) == CacheValidationStatus.ReturnCachedResponse) {
357 // We can substitute the response from cache
358 if (CacheFreshnessStatus == CacheFreshnessStatus.Stale) {
359 CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_110);
361 if (HeuristicExpiration && (int)CacheAge.TotalSeconds >= 24*3600) {
362 CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_113);
364 // We actually failed to reach the origin server hence we don't reset the current Cache Age
369 // if there was already one retry, then cache should not be taken into account
370 if (ResponseCount > 1) {
371 result = CacheValidationStatus.DoNotTakeFromCache;
376 HTTP/1.1 uses the Age response-header to convey the estimated age
377 of the response message when obtained from a cache.
378 The Age field value is the cache's estimate of the amount of time
379 since the response was generated or >>revalidated<< by the origin server.
381 // Reset Cache Age to be 0 seconds
382 CacheAge = TimeSpan.Zero;
383 result = Rfc2616.Common.ValidateCacheAfterResponse(this, resp);
387 if (result == CacheValidationStatus.ReturnCachedResponse)
389 CacheHeaders[HttpKnownHeaderNames.Age] = ((int)(CacheAge.TotalSeconds)).ToString(NumberFormatInfo.InvariantInfo);
396 /// This validation method is responsible to answer whether the live response is sufficient to make
397 /// the final decision for caching protocol.
398 /// This is useful in case of possible failure or inconsistent results received from
399 /// the remote cache.
402 /// <remarks> Invalid response from this method means the request was internally modified and should be retried </remarks>
403 protected internal override CacheValidationStatus ValidateResponse() {
405 if (this.Policy.Level != HttpRequestCacheLevel.CacheOrNextCacheOnly &&
406 this.Policy.Level != HttpRequestCacheLevel.Default &&
407 this.Policy.Level != HttpRequestCacheLevel.Revalidate)
409 // Those policy levels do not modify requests
410 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_response_valid_based_on_policy, Policy.ToString()));
411 return CacheValidationStatus.Continue;
414 // We will need quick access to cache controls coming with the live response
415 HttpWebResponse resp = Response as HttpWebResponse;
417 if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_null_response_failure));
418 return CacheValidationStatus.Continue;
421 FetchHeaderValues(false);
422 if(Logging.On) Logging.PrintInfo(Logging.RequestCache, "StatusCode=" + ((int)resp.StatusCode).ToString(CultureInfo.InvariantCulture) + ' ' +resp.StatusCode.ToString() +
423 (resp.StatusCode == HttpStatusCode.PartialContent
424 ?", Content-Range: " + resp.Headers[HttpKnownHeaderNames.ContentRange]
429 // Apply our best knowledge of HTTP caching and return the result
430 // that can be hooked up and revised by the upper level
431 return Rfc2616.OnValidateResponse(this);
436 /// This action handler is responsible for making final decision on whether
437 /// a received response can be cached.
440 /// <remarks> Invalid result from this method means the response must not be cached </remarks>
441 protected internal override CacheValidationStatus UpdateCache() {
443 if (this.Policy.Level == HttpRequestCacheLevel.NoCacheNoStore) {
444 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_removed_existing_based_on_policy, Policy.ToString()));
445 return CacheValidationStatus.RemoveFromCache;
447 if (this.Policy.Level == HttpRequestCacheLevel.CacheOnly) {
448 if(Logging.On)Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_not_updated_based_on_policy, Policy.ToString()));
449 return CacheValidationStatus.DoNotUpdateCache;
452 if (CacheHeaders == null)
453 CacheHeaders = new WebHeaderCollection();
455 if (SystemMeta == null)
456 SystemMeta = new NameValueCollection(1, CaseInsensitiveAscii.StaticInstance);
458 if (ResponseCacheControl == null) {
459 //ValidateResponse was not invoked
460 FetchHeaderValues(false);
463 // Apply our best knowledge of HTTP caching and return the result
464 // that can be hooked up and revised by the upper level
465 CacheValidationStatus result = Rfc2616.OnUpdateCache(this);
467 if (result == CacheValidationStatus.UpdateResponseInformation || result == CacheValidationStatus.CacheResponse)
469 FinallyUpdateCacheEntry();
477 private void FinallyUpdateCacheEntry() {
478 // Transfer the context status line back to the metadata
480 CacheEntry.EntryMetadata = null;
481 CacheEntry.SystemMetadata = null;
483 if (CacheHeaders == null) {
484 //must be an entry update without updating the headers
488 CacheEntry.EntryMetadata = new StringCollection();
489 CacheEntry.SystemMetadata = new StringCollection();
491 if (CacheHttpVersion == null) {
492 if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_invalid_http_version));
493 CacheHttpVersion = new Version(1, 0);
496 System.Text.StringBuilder sb = new System.Text.StringBuilder(CacheStatusDescription.Length + 20);
498 sb.Append(CacheHttpVersion.ToString(2));
500 sb.Append(((int)CacheStatusCode).ToString(NumberFormatInfo.InvariantInfo));
502 sb.Append(CacheStatusDescription);
504 // Fetch the status line into cache metadata
505 CacheEntry.EntryMetadata.Add(sb.ToString());
507 UpdateStringCollection(CacheEntry.EntryMetadata, CacheHeaders, false);
509 if (SystemMeta != null)
511 UpdateStringCollection(CacheEntry.SystemMetadata, SystemMeta, true);
514 // Update other entry values
515 if (ResponseExpires != DateTime.MinValue) {
516 CacheEntry.ExpiresUtc = ResponseExpires;
519 if (ResponseLastModified != DateTime.MinValue)
521 CacheEntry.LastModifiedUtc = ResponseLastModified;
524 if (this.Policy.Level == HttpRequestCacheLevel.Default)
526 CacheEntry.MaxStale = this.Policy.MaxStale;
529 CacheEntry.LastSynchronizedUtc = DateTime.UtcNow;
534 private static void UpdateStringCollection(StringCollection result, NameValueCollection cc, bool winInetCompat)
539 for (int i=0; i < cc.Count; ++i)
541 sb = new StringBuilder(40);
542 string key = cc.GetKey(i) as string;
543 sb.Append(key).Append(':');
545 string[] val = cc.GetValues(i);
551 {sb.Append(' ').Append(val[0]);}
554 for (int j = 1; j < val.Length; ++j)
556 sb.Append(key).Append(", ").Append(val[j]);
558 result.Add(sb.ToString());
560 // Transfer last \r\n
561 result.Add(string.Empty);
565 // HTTP/X.Y SP NUMBER SP STRING
568 private string ParseStatusLine() {
570 // This will indicate an invalid result
571 CacheStatusCode = (HttpStatusCode)0;
573 if (CacheEntry.EntryMetadata == null || CacheEntry.EntryMetadata.Count == 0)
578 string s = CacheEntry.EntryMetadata[0];
586 while (++idx < s.Length && (ch=s[idx]) != '/') {
590 if (idx == s.Length) {return s;}
596 while (++idx < s.Length && (ch=s[idx]) >= '0' && ch <= '9') {
597 major = (major<0? 0: major*10) +(ch - '0');
600 if (major < 0 || ch != '.') {return s;}
602 while (++idx < s.Length && (ch=s[idx]) >= '0' && ch <= '9') {
603 minor = (minor<0? 0: minor*10) + (ch - '0');
606 if (minor < 0 || (ch != ' ' && ch != '\t')) {return s;}
608 while (++idx < s.Length && ((ch=s[idx]) == ' ' || ch == '\t'))
611 if (idx >= s.Length) {return s;}
613 while (ch >= '0' && ch <= '9')
615 status = (status<0? 0: status*10) +(ch - '0');
616 if (++idx == s.Length)
621 if (status < 0 || (idx <= s.Length && (ch != ' ' && ch != '\t'))) {return s;}
623 while (idx < s.Length && (s[idx] == ' ' || s[idx] == '\t'))
626 CacheStatusDescription = s.Substring(idx);
628 CacheHttpVersion = new Version(major, minor);
629 CacheStatusCode = (HttpStatusCode)status;
633 private void CreateCacheHeaders(bool ignoreFirstString)
636 if (CacheHeaders == null)
637 CacheHeaders = new WebHeaderCollection();
639 if (CacheEntry.EntryMetadata == null || CacheEntry.EntryMetadata.Count == 0)
641 if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_no_http_response_header));
645 string s = ParseNameValues(CacheHeaders, CacheEntry.EntryMetadata, ignoreFirstString?1:0);
648 if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_http_header_parse_error, s));
649 CacheHeaders.Clear();
653 private void CreateSystemMeta()
655 if (SystemMeta == null)
657 SystemMeta = new NameValueCollection((CacheEntry.EntryMetadata == null || CacheEntry.EntryMetadata.Count == 0? 2: CacheEntry.EntryMetadata.Count),
658 CaseInsensitiveAscii.StaticInstance);
660 if (CacheEntry.EntryMetadata == null || CacheEntry.EntryMetadata.Count == 0)
663 string s = ParseNameValues(SystemMeta, CacheEntry.SystemMetadata, 0);
666 if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_metadata_name_value_parse_error, s));
670 // Returns null on success, otherwise the offending string.
672 private string ParseNameValues(NameValueCollection cc, StringCollection sc, int start)
674 WebHeaderCollection wc = cc as WebHeaderCollection;
676 string lastHeaderName = null;
679 for (int i = start; i < sc.Count; ++i)
682 if (s == null || s.Length == 0)
684 //An empty string stands for \r\n
685 //Treat that as the end of headers and ignore the rest
689 if (s[0] == ' ' || s[0] == '\t')
691 if (lastHeaderName == null) {return s;}
693 wc.AddInternal(lastHeaderName, s);
695 cc.Add(lastHeaderName, s);
698 int colpos = s.IndexOf(':');
701 lastHeaderName = s.Substring(0, colpos);
702 while (++colpos < s.Length && (s[colpos] == ' ' || s[colpos] == '\t'))
707 wc.AddInternal(lastHeaderName, s.Substring(colpos));
709 cc.Add(lastHeaderName, s.Substring(colpos));
712 if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException)
714 // Otherwise the value of 's' will be used to log an error.
715 // The fact that we cannot parse headers may stand for corrupted metadata that we try to ignore
725 private void FetchHeaderValues(bool forCache) {
727 WebHeaderCollection cc = forCache? CacheHeaders: Response.Headers;
730 FetchCacheControl(cc.CacheControl, forCache);
735 DateTime date = DateTime.MinValue;
736 if (s != null && HttpDateParse.ParseHttpDate(s, out date)) {
737 date = date.ToUniversalTime();
746 // Parse Expires Header
749 date = DateTime.MinValue;
750 if (s != null && HttpDateParse.ParseHttpDate(s, out date)) {
751 date = date.ToUniversalTime();
757 ResponseExpires = date;
760 // Parse LastModified Header
763 date = DateTime.MinValue;
764 if (s != null && HttpDateParse.ParseHttpDate(s, out date)) {
765 date = date.ToUniversalTime();
768 CacheLastModified = date;
771 ResponseLastModified = date;
774 long totalLength = -1;
775 long startRange = -1;
778 HttpWebResponse resp = Response as HttpWebResponse;
779 if ((forCache? CacheStatusCode: resp.StatusCode) != HttpStatusCode.PartialContent) {
781 // Parse Content-Length Header
782 s = cc.ContentLength;
783 if (s != null && s.Length != 0) {
786 while (i < s.Length && ch == ' ') {
789 if (i != s.Length && ch >= '0' && ch <= '9') {
790 totalLength = ch-'0';
791 while(++i < s.Length && (ch = s[i]) >= '0' && ch <= '9') {
792 totalLength = totalLength*10+(ch-'0');
798 //Parse Content-Range
799 s = cc[HttpKnownHeaderNames.ContentRange];
800 if(s == null || !Rfc2616.Common.GetBytesRange(s, ref startRange, ref end, ref totalLength, false)) {
801 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_content_range_error, (s==null ? "<null>" : s)));
802 startRange=end=totalLength = -1;
804 else if (forCache && totalLength == CacheEntry.StreamSize)
806 // This is a whole response, step back to 200
809 CacheStatusCode = HttpStatusCode.OK;
810 CacheStatusDescription = Rfc2616.Common.OkDescription;
815 CacheEntityLength = totalLength;
816 ResponseRangeStart = startRange;
817 ResponseRangeEnd = end;
821 ResponseEntityLength = totalLength;
822 ResponseRangeStart = startRange;
823 ResponseRangeEnd = end;
827 TimeSpan span = TimeSpan.MinValue;
828 s = cc[HttpKnownHeaderNames.Age];
832 while(i < s.Length && s[i++] == ' ') {
835 while(i < s.Length && s[i] >= '0' && s[i] <= '9') {
836 sec = sec*10 + (s[i++] - '0');
838 span = TimeSpan.FromSeconds(sec);
849 const long LO = 0x0020002000200020L;
850 const int LOI = 0x00200020;
851 const long _prox = 'p'|('r'<<16)|((long)'o'<<32)|((long)'x'<<48);
852 const long _y_re = 'y'|('-'<<16)|((long)'r'<<32)|((long)'e'<<48);
853 const long _vali = 'v'|('a'<<16)|((long)'l'<<32)|((long)'i'<<48);
854 const long _date = 'd'|('a'<<16)|((long)'t'<<32)|((long)'e'<<48);
856 const long _publ = 'p'|('u'<<16)|((long)'b'<<32)|((long)'l'<<48);
857 const int _ic = 'i'|('c'<<16);
859 const long _priv = 'p'|('r'<<16)|((long)'i'<<32)|((long)'v'<<48);
860 const int _at = 'a'|('t'<<16);
862 const long _no_c = 'n'|('o'<<16)|((long)'-'<<32)|((long)'c'<<48);
863 const long _ache = 'a'|('c'<<16)|((long)'h'<<32)|((long)'e'<<48);
865 const long _no_s = 'n'|('o'<<16)|((long)'-'<<32)|((long)'s'<<48);
866 const long _tore = 't'|('o'<<16)|((long)'r'<<32)|((long)'e'<<48);
868 const long _must = 'm'|('u'<<16)|((long)'s'<<32)|((long)'t'<<48);
869 const long __rev = '-'|('r'<<16)|((long)'e'<<32)|((long)'v'<<48);
870 const long _alid = 'a'|('l'<<16)|((long)'i'<<32)|((long)'d'<<48);
872 const long _max_ = 'm'|('a'<<16)|((long)'x'<<32)|((long)'-'<<48);
873 const int _ag = 'a'|('g'<<16);
875 const long _s_ma = 's'|('-'<<16)|((long)'m'<<32)|((long)'a'<<48);
876 const long _xage = 'x'|('a'<<16)|((long)'g'<<32)|((long)'e'<<48);
880 private unsafe void FetchCacheControl(string s, bool forCache) {
882 ResponseCacheControl control = new ResponseCacheControl();
884 CacheCacheControl = control;
887 ResponseCacheControl = control;
890 if (s != null && s.Length != 0) {
891 fixed (char *sp = s) {
893 for (int i = 0; i < len-4; ++i) {
894 if (sp[i] < ' ' || sp[i] >= 0x7F) {
895 if(Logging.On)Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_cache_control_error, s));
899 if (sp[i] == ' ' || sp[i] == ',') {
903 // These if-else are two logically identical blocks that differ only in the way of how text search is done.
904 // The text search is done differently for 32 and X-bits platforms.
905 // ATTN: You are responsible for keeping the rest of the logic in [....].
906 if (IntPtr.Size == 4) {
907 // We are on 32-bits platform
909 long *mask = (long*)&(sp[i]);
910 //making interested chars lowercase, others are ignored anyway
913 case _prox: if (i+16 > len) continue;
914 if ((*(mask+1)|LO) != _y_re || (*(mask+2)|LO) != _vali || (*(mask+3)|LO) != _date) continue;
915 control.ProxyRevalidate = true;
919 case _publ: if (i+6 > len) return;
920 if ((*((int*)(mask+1))|LOI) != _ic) continue;
921 control.Public = true;
925 case _priv: if (i+7 > len) return;
926 if ((*((int*)(mask+1))|LOI) != _at || (sp[i+6]|0x20) != 'e') continue;
927 control.Private = true;
929 // Check for a case: private = "name1,name2"
930 while (i < len && sp[i] == ' ') {++i;}
932 if (i >= len || sp[i] != '=') {--i;break;}
934 while (i < len && sp[++i] == ' ') {;}
936 if (i >= len || sp[i] != '\"') {--i;break;}
938 System.Collections.ArrayList privateList = new System.Collections.ArrayList();
940 while(i < len && sp[i] != '\"') {
942 while (i < len && sp[i] == ' ') {++i;}
944 while (i < len && sp[i] != ' ' && sp[i] != ',' && sp[i] != '\"') {++i;}
946 privateList.Add(s.Substring(start, i-start));
948 while (i < len && sp[i] != ',' && sp[i] != '\"') {++i;}
950 if (privateList.Count != 0) {
951 control.PrivateHeaders = (string[])privateList.ToArray(typeof(string));
955 case _no_c: if (i+8 > len) return;
956 if ((*(mask+1)|LOI) != _ache) continue;
957 control.NoCache = true;
959 // Check for a case: no-cache = "name1,name2"
960 while (i < len && sp[i] == ' ') {++i;}
962 if (i >= len || sp[i] != '=') {--i;break;}
964 while (i < len && sp[++i] == ' ') {;}
966 if (i >= len || sp[i] != '\"') {--i;break;}
968 System.Collections.ArrayList nocacheList = new System.Collections.ArrayList();
970 while(i < len && sp[i] != '\"') {
972 while (i < len && sp[i] == ' ') {++i;}
974 while (i < len && sp[i] != ' ' && sp[i] != ',' && sp[i] != '\"') {++i;}
976 nocacheList.Add(s.Substring(start, i-start));
978 while (i < len && sp[i] != ',' && sp[i] != '\"') {++i;}
980 if (nocacheList.Count != 0) {
981 control.NoCacheHeaders = (string[])nocacheList.ToArray(typeof(string));
985 case _no_s: if (i+8 > len) return;
986 if ((*(mask+1)|LOI) != _tore) continue;
987 control.NoStore = true;
991 case _must: if (i+15 > len) continue;
993 if ((*(mask+1)|LO) != __rev || (*(mask+2)|LO) != _alid || (*(int*)(mask+3)|LOI) != _at || (sp[i+14]|0x20) != 'e') continue;
994 control.MustRevalidate = true;
998 case _max_: if (i+7 > len) return;
999 if ((*((int*)(mask+1))|LOI) != _ag || (sp[i+6]|0x20) != 'e') continue;
1001 while (i < len && sp[i] == ' ') {
1004 if (i == len || sp[i++] != '=') return;
1005 while (i < len && sp[i] == ' ') {
1008 if (i == len) return;
1010 while (i < len && sp[i] >= '0' && sp[i] <= '9') {
1011 control.MaxAge =control.MaxAge*10 + (sp[i++]-'0');
1016 case _s_ma: if (i+8 > len) return;
1017 if ((*(mask+1)|LOI) != _xage) continue;
1019 while (i < len && sp[i] == ' ') {
1022 if (i == len || sp[i++] != '=') return;
1023 while (i < len && sp[i] == ' ') {
1026 if (i == len) return;
1027 control.SMaxAge = 0;
1028 while (i < len && sp[i] >= '0' && sp[i] <= '9') {
1029 control.SMaxAge = control.SMaxAge*10 + (sp[i++]-'0');
1036 // We cannot use optimized code path due to IA-64 memory alligment problems see VSWhidbey 118967
1037 if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "proxy-revalidate")) {
1038 control.ProxyRevalidate = true;
1041 else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "public")) {
1042 control.Public = true;
1045 else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "private")) {
1046 control.Private = true;
1048 // Check for a case: private = "name1,name2"
1049 while (i < len && sp[i] == ' ') {++i;}
1051 if (i >= len || sp[i] != '=') {--i;break;}
1053 while (i < len && sp[++i] == ' ') {;}
1055 if (i >= len || sp[i] != '\"') {--i;break;}
1057 System.Collections.ArrayList privateList = new System.Collections.ArrayList();
1059 while(i < len && sp[i] != '\"') {
1061 while (i < len && sp[i] == ' ') {++i;}
1063 while (i < len && sp[i] != ' ' && sp[i] != ',' && sp[i] != '\"') {++i;}
1065 privateList.Add(s.Substring(start, i-start));
1067 while (i < len && sp[i] != ',' && sp[i] != '\"') {++i;}
1069 if (privateList.Count != 0) {
1070 control.PrivateHeaders = (string[])privateList.ToArray(typeof(string));
1073 else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "no-cache")) {
1074 control.NoCache = true;
1076 // Check for a case: no-cache = "name1,name2"
1077 while (i < len && sp[i] == ' ') {++i;}
1079 if (i >= len || sp[i] != '=') {--i;break;}
1081 while (i < len && sp[++i] == ' ') {;}
1083 if (i >= len || sp[i] != '\"') {--i;break;}
1085 System.Collections.ArrayList nocacheList = new System.Collections.ArrayList();
1087 while(i < len && sp[i] != '\"') {
1089 while (i < len && sp[i] == ' ') {++i;}
1091 while (i < len && sp[i] != ' ' && sp[i] != ',' && sp[i] != '\"') {++i;}
1093 nocacheList.Add(s.Substring(start, i-start));
1095 while (i < len && sp[i] != ',' && sp[i] != '\"') {++i;}
1097 if (nocacheList.Count != 0) {
1098 control.NoCacheHeaders = (string[])nocacheList.ToArray(typeof(string));
1101 else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "no-store")) {
1102 control.NoStore = true;
1105 else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "must-revalidate")) {
1106 control.MustRevalidate = true;
1109 else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "max-age")) {
1111 while (i < len && sp[i] == ' ') {
1114 if (i == len || sp[i++] != '=') return;
1115 while (i < len && sp[i] == ' ') {
1118 if (i == len) return;
1120 while (i < len && sp[i] >= '0' && sp[i] <= '9') {
1121 control.MaxAge =control.MaxAge*10 + (sp[i++]-'0');
1125 else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "smax-age")) {
1127 while (i < len && sp[i] == ' ') {
1130 if (i == len || sp[i++] != '=') return;
1131 while (i < len && sp[i] == ' ') {
1134 if (i == len) return;
1135 control.SMaxAge = 0;
1136 while (i < len && sp[i] >= '0' && sp[i] <= '9') {
1137 control.SMaxAge = control.SMaxAge*10 + (sp[i++]-'0');
1148 - any stored Warning headers with warn-code 1xx (see section
1149 14.46) MUST be deleted from the cache entry and the forwarded
1152 - any stored Warning headers with warn-code 2xx MUST be retained
1153 in the cache entry and the forwarded response.
1155 private void RemoveWarnings_1xx() {
1157 string[] warnings = CacheHeaders.GetValues(HttpKnownHeaderNames.Warning);
1158 if (warnings == null) {
1161 ArrayList remainingWarnings = new ArrayList();
1162 ParseHeaderValues(warnings, ParseWarningsCallback, remainingWarnings);
1163 CacheHeaders.Remove(HttpKnownHeaderNames.Warning);
1164 for (int i=0; i < remainingWarnings.Count; ++i) {
1165 CacheHeaders.Add(HttpKnownHeaderNames.Warning, (string)remainingWarnings[i]);
1169 private static readonly ParseCallback ParseWarningsCallback = new ParseCallback(ParseWarningsCallbackMethod);
1170 private static void ParseWarningsCallbackMethod(string s, int start, int end, IList list) {
1171 if (end >= start && s[start] != '1') {
1172 ParseValuesCallbackMethod(s, start, end, list);
1176 // This is used by other classes to get the list if values from a header string
1178 internal delegate void ParseCallback(string s, int start, int end, IList list);
1179 internal static readonly ParseCallback ParseValuesCallback = new ParseCallback(ParseValuesCallbackMethod);
1180 private static void ParseValuesCallbackMethod(string s, int start, int end, IList list) {
1182 // Deal with the cases: '' ' ' 'value' 'value '
1183 while (end >= start && s[end] == ' ') {
1187 list.Add(s.Substring(start, end-start+1));
1193 // Parses header values calls a callback one value after other.
1194 // Note a single string can contain multiple values and any value may have a quoted string in.
1195 // The parser will not cut trailing spaces when invoking a callback
1197 internal static void ParseHeaderValues(string[] values, ParseCallback calback, IList list) {
1199 if (values == null) {
1202 for (int i = 0; i < values.Length; ++i) {
1203 string val = values[i];
1207 while (end < val.Length) {
1209 while (start < val.Length && val[start] == ' ') {
1213 if (start == val.Length ) {
1214 //empty header value
1218 // find comma or quote
1221 while (end < val.Length && val[end] != ',' && val[end] != '\"') {
1225 if (end == val.Length ) {
1226 calback(val, start, end-1, list);
1230 if (val[end] == '\"') {
1231 while (++end < val.Length && val[end] != '"') {
1234 if (end == val.Length ) {
1235 //warning: no closing quote, accepting
1236 calback(val, start, end-1, list);
1243 calback(val, start, end-1, list);
1244 // skip leading spaces
1245 while (++end < val.Length && val[end] == ' ') {
1248 if (end >= val.Length) {
1260 //ATTN: The values order is importent
1261 internal enum HttpMethod {
1275 internal class ResponseCacheControl {
1276 internal bool Public;
1277 internal bool Private;
1278 internal string[] PrivateHeaders;
1279 internal bool NoCache;
1280 internal string[] NoCacheHeaders;
1281 internal bool NoStore;
1282 internal bool MustRevalidate;
1283 internal bool ProxyRevalidate;
1284 internal int MaxAge;
1285 internal int SMaxAge;
1287 internal ResponseCacheControl() {
1288 MaxAge = SMaxAge = -1;
1291 internal bool IsNotEmpty {
1293 return (Public || Private || NoCache || NoStore || MustRevalidate || ProxyRevalidate || MaxAge != -1 || SMaxAge != -1);
1297 public override string ToString() {
1298 System.Text.StringBuilder sb = new System.Text.StringBuilder();
1301 sb.Append(" public");
1304 sb.Append(" private");
1305 if (PrivateHeaders != null) {
1307 for (int i = 0; i < PrivateHeaders.Length-1; ++i) {
1308 sb.Append(PrivateHeaders[i]).Append(',');
1310 sb.Append(PrivateHeaders[PrivateHeaders.Length-1]);
1314 sb.Append(" no-cache");
1315 if (NoCacheHeaders != null) {
1317 for (int i = 0; i < NoCacheHeaders.Length-1; ++i) {
1318 sb.Append(NoCacheHeaders[i]).Append(',');
1320 sb.Append(NoCacheHeaders[NoCacheHeaders.Length-1]);
1324 sb.Append(" no-store");
1326 if (MustRevalidate) {
1327 sb.Append(" must-revalidate");
1329 if (ProxyRevalidate) {
1330 sb.Append(" proxy-revalidate");
1333 sb.Append(" max-age=").Append(MaxAge);
1335 if (SMaxAge != -1) {
1336 sb.Append(" s-maxage=").Append(SMaxAge);
1338 return sb.ToString();