Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System / net / System / Net / Cache / HttpRequestCacheValidator.cs
1 /*++
2 Copyright (c) Microsoft Corporation
3
4 Module Name:
5
6     HttpRequestCacheValidator.cs
7
8 Abstract:
9     The class implements HTTP Caching validators as per RFC2616
10
11
12 Author:
13
14     Alexei Vopilov    21-Dec-2002
15
16 Revision History:
17
18     Jan 25 2004 - Changed the visibility of the class from public to internal.
19
20 --*/
21 namespace System.Net.Cache {
22     using System;
23     using System.Net;
24     using System.IO;
25     using System.Collections;
26     using System.Text;
27     using System.Collections.Specialized;
28     using System.Globalization;
29     using System.Threading;
30
31
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";
38
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;
47         }
48
49         private HttpRequestCachePolicy m_HttpPolicy;
50
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;
56
57         private bool            m_DontUpdateHeaders;
58         private bool            m_HeuristicExpiration;
59
60         private Vars            m_CacheVars;
61         private Vars            m_ResponseVars;
62         private RequestVars     m_RequestVars;
63
64         private struct Vars {
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;
74
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;
82             }
83         }
84
85
86         //public
87         internal HttpStatusCode CacheStatusCode           {get{return m_StatusCode;}          set{m_StatusCode = value;}}
88         //public
89         internal string         CacheStatusDescription    {get{return m_StatusDescription;}   set{m_StatusDescription = value;}}
90         //public
91         internal Version        CacheHttpVersion          {get{return m_HttpVersion;}         set{m_HttpVersion = value;}}
92
93         //public
94         internal WebHeaderCollection CacheHeaders         {get{return m_Headers;}             set{m_Headers = value;}}
95
96         //public
97         internal new HttpRequestCachePolicy   Policy      {
98                                                             get {
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);
104                                                                 return m_HttpPolicy;
105                                                             }
106                                                         }
107
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;}}
116
117         internal bool         CacheDontUpdateHeaders    {get{return m_DontUpdateHeaders;}           set{m_DontUpdateHeaders = value;}}
118
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;}}
126
127         internal ResponseCacheControl     CacheCacheControl    {get{return m_CacheVars.CacheControl;}      set{m_CacheVars.CacheControl = value;}}
128
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;}}
137
138         //
139         private void ZeroPrivateVars()
140         {
141             // Set default values for private members here
142             m_RequestVars = new RequestVars();
143
144             m_HttpPolicy        = null;
145             m_StatusCode        = (HttpStatusCode)0;
146             m_StatusDescription = null;
147             m_HttpVersion       = null;
148             m_Headers           = null;
149             m_SystemMeta        = null;
150             m_DontUpdateHeaders = false;
151             m_HeuristicExpiration = false;
152
153             m_CacheVars   = new Vars();
154             m_CacheVars.Initialize();
155
156             m_ResponseVars= new Vars();
157             m_ResponseVars.Initialize();
158         }
159
160         //public
161         internal override RequestCacheValidator CreateValidator()
162         {
163             return new HttpRequestCacheValidator(StrictCacheErrors, UnspecifiedMaxAge);
164         }
165
166         /*
167         //public
168         // Consider removing.
169         internal HttpRequestCacheValidator(): base()
170         {
171         }
172         */
173
174         //public
175         internal HttpRequestCacheValidator(bool strictCacheErrors, TimeSpan unspecifiedMaxAge): base(strictCacheErrors, unspecifiedMaxAge)
176         {
177         }
178
179         //
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.
182         //
183         // Returns:
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() {
188
189             // cleanup context after previous  request
190             ZeroPrivateVars();
191
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));
194
195             switch (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;
205             }
206
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);
210         }
211
212         //
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()
217         {
218
219             // Transfer cache entry metadata into status line and headers.
220             string s = ParseStatusLine();
221
222             if(Logging.On) {
223                 if ((int) CacheStatusCode == 0) {
224                     Logging.PrintError(Logging.RequestCache, SR.GetString(SR.net_log_cache_http_status_parse_failure, (s == null ? "null" : s)));
225                 }
226                 else {
227                     Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_http_status_line, (CacheHttpVersion != null ? CacheHttpVersion.ToString() : "null"), (int)CacheStatusCode, CacheStatusDescription));
228                 }
229
230             }
231
232             CreateCacheHeaders((int)CacheStatusCode != 0);
233             CreateSystemMeta();
234
235             // We will need quick access to cache-control and other headers coming with the cached item
236             FetchHeaderValues(true);
237
238             if(Logging.On) Logging.PrintInfo(Logging.RequestCache, SR.GetString(SR.net_log_cache_cache_control, CacheCacheControl.ToString()));
239
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);
243         }
244
245         /// <remarks>  This method may add headers under the "Warning" header name </remarks>
246         protected internal override CacheValidationStatus ValidateCache() {
247
248             if (this.Policy.Level != HttpRequestCacheLevel.Revalidate && base.Policy.Level >= RequestCacheLevel.Reload)
249             {
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;
254             }
255
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)
259             {
260                 if (this.Policy.Level == HttpRequestCacheLevel.CacheOnly)
261                 {
262                     // Throw because entry was not found and it's cache-only policy
263                     FailRequest(WebExceptionStatus.CacheEntryNotFound);
264                 }
265                 return CacheValidationStatus.DoNotTakeFromCache;
266             }
267
268             if (RequestMethod == HttpMethod.Head)
269             {
270                 // For a HEAD request we release the cache entry stream asap since we will have to suppress it anyway
271                 CacheStream.Close();
272                 CacheStream = new SyncMemoryStream(new byte[] {});
273             }
274
275             // Apply our best knowledge of HTTP caching and return the result
276             // that can be hooked up and revised by the upper level
277
278             CacheValidationStatus result = CacheValidationStatus.DoNotTakeFromCache;
279
280             //
281             // Before request submission validation
282             //
283
284             // If we return from cache we should remove existing 1xx warnings
285             RemoveWarnings_1xx();
286
287             // default values for a response from cache.
288             CacheStreamOffset       = 0;
289             CacheStreamLength       = CacheEntry.StreamSize;
290
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);
295             }
296
297             if (result == CacheValidationStatus.ReturnCachedResponse)
298             {
299                 if (CacheFreshnessStatus == CacheFreshnessStatus.Stale) {
300                     CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_110);
301                 }
302                 if (base.Policy.Level == RequestCacheLevel.CacheOnly) {
303                     CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_112);
304                 }
305                 if (HeuristicExpiration && (int)CacheAge.TotalSeconds >= 24*3600) {
306                     CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_113);
307                 }
308             }
309
310             if (result == CacheValidationStatus.DoNotTakeFromCache) {
311                 // We signal that current cache entry can be only replaced and not updated
312                 CacheStatusCode = (HttpStatusCode) 0;
313             }
314             else if (result == CacheValidationStatus.ReturnCachedResponse) {
315                 CacheHeaders[HttpKnownHeaderNames.Age] = ((int)(CacheAge.TotalSeconds)).ToString(NumberFormatInfo.InvariantInfo);
316             }
317             return result;
318         }
319         //
320         // This is (optionally) called after receiveing a live response
321         //
322         protected internal override CacheValidationStatus RevalidateCache()
323         {
324             if (this.Policy.Level != HttpRequestCacheLevel.Revalidate && base.Policy.Level >= RequestCacheLevel.Reload)
325             {
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;
330             }
331
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)
335             {
336                 return CacheValidationStatus.DoNotTakeFromCache;
337             }
338
339             //
340             // This is a second+ time validation after receiving at least one response
341             //
342
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;
346
347             HttpWebResponse resp = Response as HttpWebResponse;
348             if (resp == null)
349             {
350                 // This will result to an application error
351                 return CacheValidationStatus.DoNotTakeFromCache;
352             }
353
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);
360                     }
361                     if (HeuristicExpiration && (int)CacheAge.TotalSeconds >= 24*3600) {
362                         CacheHeaders.Add(HttpKnownHeaderNames.Warning, HttpRequestCacheValidator.Warning_113);
363                     }
364                     // We actually failed to reach the origin server hence we don't reset the current Cache Age
365                 }
366             }
367             else {
368
369                 // if there was already one retry, then cache should not be taken into account
370                 if (ResponseCount > 1) {
371                     result =  CacheValidationStatus.DoNotTakeFromCache;
372                 }
373                 else {
374                     /*
375                     Section 13.2.3:
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.
380                     */
381                     // Reset Cache Age to be 0 seconds
382                     CacheAge = TimeSpan.Zero;
383                     result = Rfc2616.Common.ValidateCacheAfterResponse(this, resp);
384                 }
385             }
386
387             if (result == CacheValidationStatus.ReturnCachedResponse)
388             {
389                 CacheHeaders[HttpKnownHeaderNames.Age] = ((int)(CacheAge.TotalSeconds)).ToString(NumberFormatInfo.InvariantInfo);
390             }
391             return result;
392         }
393
394         /// <summary>
395         /// <para>
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.
400         /// </para>
401         /// </summary>
402         /// <remarks>  Invalid response from this method means the request was internally modified and should be retried </remarks>
403         protected internal override CacheValidationStatus ValidateResponse() {
404
405             if (this.Policy.Level != HttpRequestCacheLevel.CacheOrNextCacheOnly &&
406                 this.Policy.Level != HttpRequestCacheLevel.Default &&
407                 this.Policy.Level != HttpRequestCacheLevel.Revalidate)
408             {
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;
412             }
413
414             // We will need quick access to cache controls coming with the live response
415             HttpWebResponse resp = Response as HttpWebResponse;
416             if (resp == null) {
417                 if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_null_response_failure));
418                 return CacheValidationStatus.Continue;
419             }
420
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]
425                                                        :string.Empty)
426                                                       );
427
428
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);
432         }
433
434         /// <summary>
435         /// <para>
436         /// This action handler is responsible for making final decision on whether
437         /// a received response can be cached.
438         /// </para>
439         /// </summary>
440         /// <remarks>  Invalid result from this method means the response must not be cached </remarks>
441         protected internal override CacheValidationStatus UpdateCache() {
442
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;
446             }
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;
450             }
451
452             if (CacheHeaders == null)
453                 CacheHeaders = new WebHeaderCollection();
454
455             if (SystemMeta == null)
456                 SystemMeta = new NameValueCollection(1, CaseInsensitiveAscii.StaticInstance);
457
458             if (ResponseCacheControl == null) {
459                 //ValidateResponse was not invoked
460                 FetchHeaderValues(false);
461             }
462
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);
466
467             if (result == CacheValidationStatus.UpdateResponseInformation || result == CacheValidationStatus.CacheResponse)
468             {
469                 FinallyUpdateCacheEntry();
470             }
471             return result;
472         }
473
474         //
475         //
476         //
477         private void FinallyUpdateCacheEntry() {
478             // Transfer the context status line back to the metadata
479
480             CacheEntry.EntryMetadata  = null;
481             CacheEntry.SystemMetadata = null;
482
483             if (CacheHeaders == null) {
484                 //must be an entry update without updating the headers
485                 return;
486             }
487
488             CacheEntry.EntryMetadata  = new StringCollection();
489             CacheEntry.SystemMetadata = new StringCollection();
490
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);
494             }
495             // HTTP/1.1 200 OK
496             System.Text.StringBuilder sb = new System.Text.StringBuilder(CacheStatusDescription.Length + 20);
497             sb.Append("HTTP/");
498             sb.Append(CacheHttpVersion.ToString(2));
499             sb.Append(' ');
500             sb.Append(((int)CacheStatusCode).ToString(NumberFormatInfo.InvariantInfo));
501             sb.Append(' ');
502             sb.Append(CacheStatusDescription);
503
504             // Fetch the status line into cache metadata
505             CacheEntry.EntryMetadata.Add(sb.ToString());
506
507             UpdateStringCollection(CacheEntry.EntryMetadata,  CacheHeaders, false);
508
509             if (SystemMeta != null)
510             {
511                 UpdateStringCollection(CacheEntry.SystemMetadata, SystemMeta, true);
512             }
513
514             // Update other entry values
515             if (ResponseExpires != DateTime.MinValue) {
516                 CacheEntry.ExpiresUtc = ResponseExpires;
517             }
518
519             if (ResponseLastModified != DateTime.MinValue)
520             {
521                 CacheEntry.LastModifiedUtc = ResponseLastModified;
522             }
523
524             if (this.Policy.Level == HttpRequestCacheLevel.Default)
525             {
526                     CacheEntry.MaxStale = this.Policy.MaxStale;
527             }
528
529             CacheEntry.LastSynchronizedUtc = DateTime.UtcNow;
530         }
531         //
532         //
533         //
534         private static void UpdateStringCollection(StringCollection result, NameValueCollection cc, bool winInetCompat)
535         {
536             StringBuilder sb;
537
538             // Transfer headers
539             for (int i=0; i < cc.Count; ++i)
540             {
541                     sb = new StringBuilder(40);
542                     string key   = cc.GetKey(i) as string;
543                     sb.Append(key).Append(':');
544
545                     string[] val = cc.GetValues(i);
546                     if (val.Length != 0)
547                     {
548                         if (winInetCompat)
549                             {sb.Append(val[0]);}
550                         else
551                             {sb.Append(' ').Append(val[0]);}
552                     }
553
554                     for (int j = 1; j < val.Length; ++j)
555                     {
556                         sb.Append(key).Append(", ").Append(val[j]);
557                     }
558                     result.Add(sb.ToString());
559             }
560             // Transfer last \r\n
561             result.Add(string.Empty);
562         }
563
564         // The format is
565         // HTTP/X.Y SP NUMBER SP STRING
566         // HTTP/1.1 200 OK
567         //
568         private string ParseStatusLine() {
569
570             // This will indicate an invalid result
571             CacheStatusCode = (HttpStatusCode)0;
572
573             if (CacheEntry.EntryMetadata == null || CacheEntry.EntryMetadata.Count == 0)
574             {
575                 return null;
576             }
577
578             string s = CacheEntry.EntryMetadata[0];
579
580             if (s == null) {
581                 return null;
582             }
583
584             int  idx = 0;
585             char ch = (char)0;
586             while (++idx < s.Length && (ch=s[idx]) != '/') {
587                 ;
588             }
589
590             if (idx == s.Length) {return s;}
591
592             int major = -1;
593             int minor = -1;
594             int status= -1;
595
596             while (++idx < s.Length && (ch=s[idx]) >= '0' && ch <= '9') {
597                 major = (major<0? 0: major*10) +(ch - '0');
598             }
599
600             if (major < 0 || ch != '.') {return s;}
601
602             while (++idx < s.Length && (ch=s[idx]) >= '0' && ch <= '9') {
603                 minor = (minor<0? 0: minor*10) + (ch - '0');
604             }
605
606             if (minor < 0 || (ch != ' ' && ch != '\t')) {return s;}
607
608             while (++idx < s.Length && ((ch=s[idx]) == ' ' || ch == '\t'))
609                 ;
610
611             if (idx >= s.Length) {return s;}
612
613             while (ch >= '0' && ch <= '9')
614             {
615                 status = (status<0? 0: status*10) +(ch - '0');
616                 if (++idx == s.Length)
617                     break;
618                 ch=s[idx];
619             }
620
621             if (status < 0 || (idx <= s.Length && (ch != ' ' && ch != '\t'))) {return s;}
622
623             while (idx < s.Length && (s[idx] == ' ' || s[idx] == '\t'))
624                 ++idx;
625
626             CacheStatusDescription = s.Substring(idx);
627
628             CacheHttpVersion = new Version(major, minor);
629             CacheStatusCode = (HttpStatusCode)status;
630             return s;
631         }
632         //
633         private void CreateCacheHeaders(bool ignoreFirstString)
634         {
635
636             if (CacheHeaders == null)
637                 CacheHeaders = new WebHeaderCollection();
638
639             if (CacheEntry.EntryMetadata == null || CacheEntry.EntryMetadata.Count == 0)
640             {
641                 if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_no_http_response_header));
642                 return;
643             }
644
645             string s = ParseNameValues(CacheHeaders, CacheEntry.EntryMetadata, ignoreFirstString?1:0);
646             if (s != null)
647             {
648                 if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_http_header_parse_error, s));
649                 CacheHeaders.Clear();
650             }
651         }
652         //
653         private void CreateSystemMeta()
654         {
655             if (SystemMeta == null)
656             {
657                 SystemMeta = new NameValueCollection((CacheEntry.EntryMetadata == null || CacheEntry.EntryMetadata.Count == 0? 2: CacheEntry.EntryMetadata.Count),
658                                                      CaseInsensitiveAscii.StaticInstance);
659             }
660             if (CacheEntry.EntryMetadata == null || CacheEntry.EntryMetadata.Count == 0)
661                 {return;}
662
663             string s = ParseNameValues(SystemMeta, CacheEntry.SystemMetadata, 0);
664             if (s != null)
665             {
666                 if(Logging.On)Logging.PrintWarning(Logging.RequestCache, SR.GetString(SR.net_log_cache_metadata_name_value_parse_error, s));
667             }
668         }
669         //
670         // Returns null on success, otherwise the offending string.
671         //
672         private string ParseNameValues(NameValueCollection cc, StringCollection sc, int start)
673         {
674             WebHeaderCollection wc = cc as WebHeaderCollection;
675
676             string lastHeaderName = null;
677             if (sc != null)
678             {
679                 for (int i = start; i < sc.Count; ++i)
680                 {
681                     string s = sc[i];
682                     if (s == null || s.Length == 0)
683                     {
684                         //An empty string stands for \r\n
685                         //Treat that as the end of headers and ignore the rest
686                         return null;
687                     }
688
689                     if (s[0] == ' ' || s[0] == '\t')
690                     {
691                         if (lastHeaderName == null) {return s;}
692                         if (wc != null)
693                             wc.AddInternal(lastHeaderName, s);
694                         else
695                             cc.Add(lastHeaderName, s);
696                     }
697
698                     int colpos = s.IndexOf(':');
699                     if (colpos < 0)
700                         {return s;}
701                     lastHeaderName = s.Substring(0, colpos);
702                     while (++colpos < s.Length && (s[colpos] == ' ' || s[colpos] == '\t'))
703                         {;}
704
705                     try {
706                         if (wc != null)
707                             wc.AddInternal(lastHeaderName, s.Substring(colpos));
708                         else
709                             cc.Add(lastHeaderName, s.Substring(colpos));
710                     }
711                     catch(Exception e) {
712                         if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException)
713                             throw;
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
716                         return s;
717                     }
718                 }
719             }
720             return null;
721         }
722         //
723         //
724         //
725         private void FetchHeaderValues(bool forCache) {
726
727             WebHeaderCollection cc = forCache? CacheHeaders: Response.Headers;
728
729
730             FetchCacheControl(cc.CacheControl, forCache);
731
732             // Parse Date Header
733             string s = cc.Date;
734
735             DateTime date = DateTime.MinValue;
736             if (s != null && HttpDateParse.ParseHttpDate(s, out date)) {
737                 date = date.ToUniversalTime();
738             }
739             if (forCache) {
740                 CacheDate = date;
741             }
742             else {
743                 ResponseDate = date;
744             }
745
746             // Parse Expires Header
747             s = cc.Expires;
748
749             date = DateTime.MinValue;
750             if (s != null && HttpDateParse.ParseHttpDate(s, out date)) {
751                 date = date.ToUniversalTime();
752             }
753             if (forCache) {
754                 CacheExpires = date;
755             }
756             else {
757                 ResponseExpires = date;
758             }
759
760             // Parse LastModified Header
761             s = cc.LastModified;
762
763             date = DateTime.MinValue;
764             if (s != null && HttpDateParse.ParseHttpDate(s, out date)) {
765                 date = date.ToUniversalTime();
766             }
767             if (forCache) {
768                 CacheLastModified = date;
769             }
770             else {
771                 ResponseLastModified = date;
772             }
773
774             long totalLength = -1;
775             long startRange = -1;
776             long end = -1;
777
778             HttpWebResponse resp = Response as HttpWebResponse;
779             if ((forCache? CacheStatusCode: resp.StatusCode) != HttpStatusCode.PartialContent) {
780
781                 // Parse Content-Length Header
782                 s = cc.ContentLength;
783                 if (s != null && s.Length != 0) {
784                     int i = 0;
785                     char ch = s[0];
786                     while (i < s.Length && ch == ' ') {
787                         ch = s[++i];
788                     }
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');
793                         }
794                     }
795                 }
796             }
797             else {
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;
803                 }
804                 else if (forCache && totalLength == CacheEntry.StreamSize)
805                 {
806                     // This is a whole response, step back to 200
807                     startRange = -1;
808                     end = -1;
809                     CacheStatusCode = HttpStatusCode.OK;
810                     CacheStatusDescription = Rfc2616.Common.OkDescription;
811                 }
812             }
813
814             if (forCache) {
815                 CacheEntityLength  = totalLength;
816                 ResponseRangeStart = startRange;
817                 ResponseRangeEnd   = end;
818
819             }
820             else {
821                 ResponseEntityLength = totalLength;
822                 ResponseRangeStart = startRange;
823                 ResponseRangeEnd   = end;
824             }
825
826             //Parse Age Header
827             TimeSpan span = TimeSpan.MinValue;
828             s = cc[HttpKnownHeaderNames.Age];
829             if (s != null) {
830                 int i = 0;
831                 int sec = 0;
832                 while(i < s.Length && s[i++] == ' ') {
833                     ;
834                 }
835                 while(i < s.Length && s[i] >= '0' && s[i] <= '9') {
836                     sec = sec*10 + (s[i++] - '0');
837                 }
838                 span = TimeSpan.FromSeconds(sec);
839             }
840
841             if (forCache) {
842                 CacheAge = span;
843             }
844             else {
845                 ResponseAge = span;
846             }
847         }
848
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);
855
856         const long _publ = 'p'|('u'<<16)|((long)'b'<<32)|((long)'l'<<48);
857         const int  _ic   = 'i'|('c'<<16);
858
859         const long _priv = 'p'|('r'<<16)|((long)'i'<<32)|((long)'v'<<48);
860         const int  _at   = 'a'|('t'<<16);
861
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);
864
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);
867
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);
871
872         const long _max_ = 'm'|('a'<<16)|((long)'x'<<32)|((long)'-'<<48);
873         const int  _ag   = 'a'|('g'<<16);
874
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);
877         //
878         //
879         //
880         private unsafe void FetchCacheControl(string s, bool forCache) {
881             //Initialize it
882             ResponseCacheControl control = new ResponseCacheControl();
883             if (forCache) {
884                 CacheCacheControl = control;
885             }
886             else {
887                 ResponseCacheControl = control;
888             }
889
890             if (s != null && s.Length != 0) {
891                 fixed (char *sp = s) {
892                     int len = s.Length;
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));
896                             //invalid format
897                             return;
898                         }
899                         if (sp[i] == ' ' || sp[i] == ',') {
900                             continue;
901                         }
902
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
908
909                             long *mask = (long*)&(sp[i]);
910                             //making interested chars lowercase, others are ignored anyway
911                             switch(*mask|LO) {
912
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;
916                                     i+=15;
917                                     break;
918
919                                 case _publ: if (i+6 > len) return;
920                                     if ((*((int*)(mask+1))|LOI) != _ic) continue;
921                                     control.Public = true;
922                                     i+=5;
923                                     break;
924
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;
928                                     i+=6;
929                                     // Check for a case: private = "name1,name2"
930                                     while (i < len && sp[i] == ' ') {++i;}
931
932                                     if (i >= len || sp[i] != '=') {--i;break;}
933
934                                     while (i < len && sp[++i] == ' ') {;}
935
936                                     if (i >= len || sp[i] != '\"') {--i;break;}
937
938                                     System.Collections.ArrayList privateList = new System.Collections.ArrayList();
939                                     ++i;
940                                     while(i < len && sp[i] != '\"') {
941
942                                         while (i < len && sp[i] == ' ') {++i;}
943                                         int start = i;
944                                         while (i < len && sp[i] != ' ' && sp[i] != ',' && sp[i] != '\"') {++i;}
945                                         if (start != i) {
946                                             privateList.Add(s.Substring(start, i-start));
947                                         }
948                                         while (i < len && sp[i] != ',' && sp[i] != '\"') {++i;}
949                                     }
950                                     if (privateList.Count != 0) {
951                                         control.PrivateHeaders = (string[])privateList.ToArray(typeof(string));
952                                     }
953                                     break;
954
955                                 case _no_c: if (i+8 > len) return;
956                                     if ((*(mask+1)|LOI) != _ache) continue;
957                                     control.NoCache = true;
958                                     i+=7;
959                                     // Check for a case: no-cache = "name1,name2"
960                                     while (i < len && sp[i] == ' ') {++i;}
961
962                                     if (i >= len || sp[i] != '=') {--i;break;}
963
964                                     while (i < len && sp[++i] == ' ') {;}
965
966                                     if (i >= len || sp[i] != '\"') {--i;break;}
967
968                                     System.Collections.ArrayList nocacheList = new System.Collections.ArrayList();
969                                     ++i;
970                                     while(i < len && sp[i] != '\"') {
971
972                                         while (i < len && sp[i] == ' ') {++i;}
973                                         int start = i;
974                                         while (i < len && sp[i] != ' ' && sp[i] != ',' && sp[i] != '\"') {++i;}
975                                         if (start != i) {
976                                             nocacheList.Add(s.Substring(start, i-start));
977                                         }
978                                         while (i < len && sp[i] != ',' && sp[i] != '\"') {++i;}
979                                     }
980                                     if (nocacheList.Count != 0) {
981                                         control.NoCacheHeaders = (string[])nocacheList.ToArray(typeof(string));
982                                     }
983                                     break;
984
985                                 case _no_s: if (i+8 > len) return;
986                                     if ((*(mask+1)|LOI) != _tore) continue;
987                                     control.NoStore = true;
988                                     i+=7;
989                                     break;
990
991                                 case _must: if (i+15 > len) continue;
992
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;
995                                     i+=14;
996                                     break;
997
998                                 case _max_: if (i+7 > len) return;
999                                     if ((*((int*)(mask+1))|LOI) != _ag || (sp[i+6]|0x20) != 'e') continue;
1000                                     i+=7;
1001                                     while (i < len && sp[i] == ' ') {
1002                                         ++i;
1003                                     }
1004                                     if (i == len || sp[i++] != '=') return;
1005                                     while (i < len && sp[i] == ' ') {
1006                                         ++i;
1007                                     }
1008                                     if (i == len) return;
1009                                     control.MaxAge = 0;
1010                                     while (i < len && sp[i] >= '0' && sp[i] <= '9') {
1011                                         control.MaxAge =control.MaxAge*10 + (sp[i++]-'0');
1012                                     }
1013                                     --i;
1014                                     break;
1015
1016                                 case _s_ma: if (i+8 > len) return;
1017                                     if ((*(mask+1)|LOI) != _xage) continue;
1018                                     i+=8;
1019                                     while (i < len && sp[i] == ' ') {
1020                                         ++i;
1021                                     }
1022                                     if (i == len || sp[i++] != '=') return;
1023                                     while (i < len && sp[i] == ' ') {
1024                                         ++i;
1025                                     }
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');
1030                                     }
1031                                     --i;
1032                                     break;
1033                             }
1034                         }
1035                         else {
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;
1039                                 i+=15;
1040                             }
1041                             else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "public")) {
1042                                 control.Public = true;
1043                                 i+=5;
1044                             }
1045                             else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "private")) {
1046                                 control.Private = true;
1047                                 i+=6;
1048                                 // Check for a case: private = "name1,name2"
1049                                 while (i < len && sp[i] == ' ') {++i;}
1050
1051                                 if (i >= len || sp[i] != '=') {--i;break;}
1052
1053                                 while (i < len && sp[++i] == ' ') {;}
1054
1055                                 if (i >= len || sp[i] != '\"') {--i;break;}
1056
1057                                 System.Collections.ArrayList privateList = new System.Collections.ArrayList();
1058                                 ++i;
1059                                 while(i < len && sp[i] != '\"') {
1060
1061                                     while (i < len && sp[i] == ' ') {++i;}
1062                                     int start = i;
1063                                     while (i < len && sp[i] != ' ' && sp[i] != ',' && sp[i] != '\"') {++i;}
1064                                     if (start != i) {
1065                                         privateList.Add(s.Substring(start, i-start));
1066                                     }
1067                                     while (i < len && sp[i] != ',' && sp[i] != '\"') {++i;}
1068                                 }
1069                                 if (privateList.Count != 0) {
1070                                     control.PrivateHeaders = (string[])privateList.ToArray(typeof(string));
1071                                 }
1072                             }
1073                             else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "no-cache")) {
1074                                 control.NoCache = true;
1075                                 i+=7;
1076                                 // Check for a case: no-cache = "name1,name2"
1077                                 while (i < len && sp[i] == ' ') {++i;}
1078
1079                                 if (i >= len || sp[i] != '=') {--i;break;}
1080
1081                                 while (i < len && sp[++i] == ' ') {;}
1082
1083                                 if (i >= len || sp[i] != '\"') {--i;break;}
1084
1085                                 System.Collections.ArrayList nocacheList = new System.Collections.ArrayList();
1086                                 ++i;
1087                                 while(i < len && sp[i] != '\"') {
1088
1089                                     while (i < len && sp[i] == ' ') {++i;}
1090                                     int start = i;
1091                                     while (i < len && sp[i] != ' ' && sp[i] != ',' && sp[i] != '\"') {++i;}
1092                                     if (start != i) {
1093                                         nocacheList.Add(s.Substring(start, i-start));
1094                                     }
1095                                     while (i < len && sp[i] != ',' && sp[i] != '\"') {++i;}
1096                                 }
1097                                 if (nocacheList.Count != 0) {
1098                                     control.NoCacheHeaders = (string[])nocacheList.ToArray(typeof(string));
1099                                 }
1100                             }
1101                             else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "no-store")) {
1102                                 control.NoStore = true;
1103                                 i+=7;
1104                             }
1105                             else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "must-revalidate")) {
1106                                 control.MustRevalidate = true;
1107                                 i+=14;
1108                             }
1109                             else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "max-age")) {
1110                                 i+=7;
1111                                 while (i < len && sp[i] == ' ') {
1112                                     ++i;
1113                                 }
1114                                 if (i == len || sp[i++] != '=') return;
1115                                 while (i < len && sp[i] == ' ') {
1116                                     ++i;
1117                                 }
1118                                 if (i == len) return;
1119                                 control.MaxAge = 0;
1120                                 while (i < len && sp[i] >= '0' && sp[i] <= '9') {
1121                                     control.MaxAge =control.MaxAge*10 + (sp[i++]-'0');
1122                                 }
1123                                 --i;
1124                             }
1125                             else if (Rfc2616.Common.UnsafeAsciiLettersNoCaseEqual(sp, i, len, "smax-age")) {
1126                                 i+=8;
1127                                 while (i < len && sp[i] == ' ') {
1128                                     ++i;
1129                                 }
1130                                 if (i == len || sp[i++] != '=') return;
1131                                 while (i < len && sp[i] == ' ') {
1132                                     ++i;
1133                                 }
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');
1138                                 }
1139                                 --i;
1140                             }
1141                         }
1142                     }
1143                 }
1144             }
1145         }
1146
1147         /*
1148           - any stored Warning headers with warn-code 1xx (see section
1149         14.46) MUST be deleted from the cache entry and the forwarded
1150         response.
1151
1152           - any stored Warning headers with warn-code 2xx MUST be retained
1153         in the cache entry and the forwarded response.
1154         */
1155         private void RemoveWarnings_1xx() {
1156
1157             string[] warnings = CacheHeaders.GetValues(HttpKnownHeaderNames.Warning);
1158             if (warnings == null) {
1159                 return;
1160             }
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]);
1166             }
1167         }
1168
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);
1173             }
1174         }
1175         //
1176         // This is used by other classes to get the list if values from a header string
1177         //
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) {
1181
1182             // Deal with the cases: '' ' ' 'value' 'value   '
1183             while (end >= start && s[end] == ' ') {
1184                 --end;
1185             }
1186             if (end >= start) {
1187                 list.Add(s.Substring(start, end-start+1));
1188             }
1189         }
1190
1191
1192         //
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
1196         //
1197         internal static void ParseHeaderValues(string[] values, ParseCallback calback, IList list) {
1198
1199             if (values == null) {
1200                 return;
1201             }
1202             for (int i = 0; i < values.Length; ++i) {
1203                 string val = values[i];
1204
1205                 int end = 0;
1206                 int start = 0;
1207                 while (end < val.Length) {
1208                     //skip spaces
1209                     while (start < val.Length && val[start] == ' ') {
1210                         ++start;
1211                     }
1212
1213                     if (start == val.Length ) {
1214                         //empty header value
1215                         break;
1216                     }
1217
1218                     // find comma or quote
1219                     end = start;
1220                 find_comma:
1221                     while (end < val.Length && val[end] != ',' && val[end] != '\"') {
1222                         ++end;
1223                     }
1224
1225                     if (end == val.Length ) {
1226                         calback(val, start, end-1, list);
1227                         break;
1228                     }
1229
1230                     if (val[end] == '\"') {
1231                         while (++end < val.Length && val[end] != '"') {
1232                             ;
1233                         }
1234                         if (end == val.Length ) {
1235                             //warning: no closing quote, accepting
1236                             calback(val, start, end-1, list);
1237                             break;
1238                         }
1239                         goto find_comma;
1240                     }
1241                     else {
1242                         //Comma
1243                         calback(val, start, end-1, list);
1244                         // skip leading spaces
1245                         while (++end < val.Length && val[end] == ' ') {
1246                             ;
1247                         }
1248                         if (end >= val.Length) {
1249                             break;
1250                         }
1251                         start = end;
1252                     }
1253                 }
1254             }
1255         }
1256     }
1257     //
1258     //
1259     //
1260     //ATTN: The values order is importent
1261     internal enum HttpMethod {
1262         Other   = -1,
1263         Head    = 0,
1264         Get,
1265         Post,
1266         Put,
1267         Delete,
1268         Options,
1269         Trace,
1270         Connect
1271     }
1272     //
1273     //
1274     //
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;
1286
1287         internal ResponseCacheControl() {
1288             MaxAge = SMaxAge = -1;
1289         }
1290
1291         internal bool IsNotEmpty {
1292             get {
1293                 return (Public || Private || NoCache || NoStore || MustRevalidate || ProxyRevalidate || MaxAge != -1 || SMaxAge != -1);
1294             }
1295         }
1296
1297         public override string  ToString() {
1298             System.Text.StringBuilder sb = new System.Text.StringBuilder();
1299
1300             if (Public) {
1301                 sb.Append(" public");
1302             }
1303             if (Private) {
1304                 sb.Append(" private");
1305                 if (PrivateHeaders != null) {
1306                     sb.Append('=');
1307                     for (int i = 0; i < PrivateHeaders.Length-1; ++i) {
1308                         sb.Append(PrivateHeaders[i]).Append(',');
1309                     }
1310                     sb.Append(PrivateHeaders[PrivateHeaders.Length-1]);
1311                 }
1312             }
1313             if (NoCache) {
1314                 sb.Append(" no-cache");
1315                 if (NoCacheHeaders != null) {
1316                     sb.Append('=');
1317                     for (int i = 0; i < NoCacheHeaders.Length-1; ++i) {
1318                         sb.Append(NoCacheHeaders[i]).Append(',');
1319                     }
1320                     sb.Append(NoCacheHeaders[NoCacheHeaders.Length-1]);
1321                 }
1322             }
1323             if (NoStore) {
1324                 sb.Append(" no-store");
1325             }
1326             if (MustRevalidate) {
1327                 sb.Append(" must-revalidate");
1328             }
1329             if (ProxyRevalidate) {
1330                 sb.Append(" proxy-revalidate");
1331             }
1332             if (MaxAge != -1) {
1333                 sb.Append(" max-age=").Append(MaxAge);
1334             }
1335             if (SMaxAge != -1) {
1336                 sb.Append(" s-maxage=").Append(SMaxAge);
1337             }
1338             return sb.ToString();
1339         }
1340     }
1341
1342 }
1343