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 InitializeSystem.Net.Cache.HttpRequestCacheValidator.Vars75 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 // ZeroPrivateVars()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 CreateValidator()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 HttpRequestCacheValidator(bool strictCacheErrors, TimeSpan unspecifiedMaxAge)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. ValidateRequest()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. ValidateFreshness()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> ValidateCache()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 // RevalidateCache()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> ValidateResponse()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> UpdateCache()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 // FinallyUpdateCacheEntry()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 // UpdateStringCollection(StringCollection result, NameValueCollection cc, bool winInetCompat)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 // ParseStatusLine()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 // CreateCacheHeaders(bool ignoreFirstString)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 // CreateSystemMeta()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 // ParseNameValues(NameValueCollection cc, StringCollection sc, int start)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 // FetchHeaderValues(bool forCache)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 // FetchCacheControl(string s, bool forCache)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 sync. 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 */ RemoveWarnings_1xx()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); ParseWarningsCallbackMethod(string s, int start, int end, IList list)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 // ParseCallback(string s, int start, int end, IList list)1178 internal delegate void ParseCallback(string s, int start, int end, IList list); 1179 internal static readonly ParseCallback ParseValuesCallback = new ParseCallback(ParseValuesCallbackMethod); ParseValuesCallbackMethod(string s, int start, int end, IList list)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 // ParseHeaderValues(string[] values, ParseCallback calback, IList list)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 ResponseCacheControl()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 ToString()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 1344