PageRenderTime 45ms CodeModel.GetById 2ms app.highlight 31ms RepoModel.GetById 1ms app.codeStats 1ms

/WCFWebApi/src/System.Net.Http/System/Net/Http/Headers/HttpHeaders.cs

#
C# | 1328 lines | 982 code | 194 blank | 152 comment | 254 complexity | b155e5db3ead62a1d55092b10d222639 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1using System.Collections;
   2using System.Collections.Generic;
   3using System.Diagnostics.CodeAnalysis;
   4using System.Diagnostics.Contracts;
   5using System.Text;
   6
   7namespace System.Net.Http.Headers
   8{
   9    // This type is used to store a collection of headers in 'headerStore':
  10    // - A header can have multiple values. 
  11    // - A header can have an associated parser which is able to parse the raw string value into a strongly typed object.
  12    // - If a header has an associated parser and the provided raw value can't be parsed, the value is considered
  13    //   invaled. Invalid values are stored if added using AddWithoutValidation(). If the value was added using Add(),
  14    //   Add() will throw FormatException.
  15    // - Since parsing header values is expensive and users usually only care about a few headers, header values are
  16    //   lazily initialized.
  17    //
  18    // Given the properties above, a header value can have three states:
  19    // - 'raw': The header value was added using AddWithoutValidation() and it wasn't parsed yet.
  20    // - 'parsed': The header value was successfully parsed. It was either added using Add() where the value was parsed 
  21    //   immediately, or if added using AddWithoutValidation() a user already accessed a property/method triggering the 
  22    //   value to be parsed.
  23    // - 'invalid': The header value was parsed, but parsing failed because the value is invalid. Storing invalid values
  24    //   allows users to still retrieve the value (by calling GetValues()), but it will not be exposed as strongly typed
  25    //   object. E.g. the client receives a response with the following header: 'Via: 1.1 proxy, invalid'
  26    //   - HttpHeaders.GetValues() will return "1.1 proxy", "invalid"
  27    //   - HttpResponseHeaders.Via collection will only contain one ViaHeaderValue object with value "1.1 proxy"
  28    [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix",
  29        Justification = "This is not a collection")]
  30    public abstract class HttpHeaders : IEnumerable<KeyValuePair<string, IEnumerable<string>>>
  31    {
  32        private Dictionary<string, HeaderStoreItemInfo> headerStore;
  33        private Dictionary<string, HttpHeaderParser> parserStore;
  34        private HashSet<string> invalidHeaders;
  35
  36        private enum StoreLocation
  37        {
  38            Raw,
  39            Invalid,
  40            Parsed
  41        }
  42
  43        protected HttpHeaders()
  44        {
  45        }
  46
  47        public void Add(string name, string value)
  48        {
  49            CheckHeaderName(name);
  50
  51            // We don't use GetOrCreateHeaderInfo() here, since this would create a new header in the store. If parsing
  52            // the value then throws, we would have to remove the header from the store again. So just get a 
  53            // HeaderStoreItemInfo object and try to parse the value. If it works, we'll add the header.
  54            HeaderStoreItemInfo info;
  55            bool addToStore;
  56            PrepareHeaderInfoForAdd(name, out info, out addToStore);
  57
  58            ParseAndAddValue(name, info, value);
  59
  60            // If we get here, then the value could be parsed correctly. If we created a new HeaderStoreItemInfo, add
  61            // it to the store if we added at least one value.
  62            if (addToStore && (info.ParsedValue != null))
  63            {
  64                AddHeaderToStore(name, info);
  65            }
  66        }
  67
  68        public void Add(string name, IEnumerable<string> values)
  69        {
  70            if (values == null)
  71            {
  72                throw new ArgumentNullException("values");
  73            }
  74            CheckHeaderName(name);
  75
  76            HeaderStoreItemInfo info;
  77            bool addToStore;
  78            PrepareHeaderInfoForAdd(name, out info, out addToStore);
  79
  80            try
  81            {
  82                // Note that if the first couple of values are valid followed by an invalid value, the valid values
  83                // will be added to the store before the exception for the invalid value is thrown.
  84                foreach (string value in values)
  85                {
  86                    ParseAndAddValue(name, info, value);
  87                }
  88            }
  89            finally
  90            {
  91                // Even if one of the values was invalid, make sure we add the header for the valid ones. We need to be
  92                // consistent here: If values get added to an _existing_ header, then all values until the invalid one
  93                // get added. Same here: If multiple values get added to a _new_ header, make sure the header gets added
  94                // with the valid values.
  95                // However, if all values for a _new_ header were invalid, then don't add the header.
  96                if (addToStore && (info.ParsedValue != null))
  97                {
  98                    AddHeaderToStore(name, info);
  99                }
 100            }
 101        }
 102
 103        public void AddWithoutValidation(string name, string value)
 104        {
 105            CheckHeaderName(name);
 106
 107            if (value == null)
 108            {
 109                // We allow empty header values. (e.g. "My-Header: "). If the user adds multiple null/empty
 110                // values, we'll just add them to the collection. This will result in delimiter-only values:
 111                // E.g. adding two null-strings (or empty, or whitespace-only) results in "My-Header: ,".
 112                value = string.Empty;
 113            }
 114
 115            HeaderStoreItemInfo info = GetOrCreateHeaderInfo(name, false);
 116            AddValue(info, value, StoreLocation.Raw);
 117        }
 118
 119        public void AddWithoutValidation(string name, IEnumerable<string> values)
 120        {
 121            if (values == null)
 122            {
 123                throw new ArgumentNullException("values");
 124            }
 125            CheckHeaderName(name);
 126            HeaderStoreItemInfo info = GetOrCreateHeaderInfo(name, false);
 127            foreach (string value in values)
 128            {
 129                // We allow empty header values. (e.g. "My-Header: "). If the user adds multiple null/empty
 130                // values, we'll just add them to the collection. This will result in delimiter-only values:
 131                // E.g. adding two null-strings (or empty, or whitespace-only) results in "My-Header: ,".
 132                AddValue(info, value ?? string.Empty, StoreLocation.Raw);
 133            }
 134        }
 135
 136        public void Clear()
 137        {
 138            if (headerStore != null)
 139            {
 140                headerStore.Clear();
 141            }
 142        }
 143
 144        public bool Remove(string name)
 145        {
 146            CheckHeaderName(name);
 147
 148            if (headerStore == null)
 149            {
 150                return false;
 151            }
 152
 153            return headerStore.Remove(name);
 154        }
 155
 156        public IEnumerable<string> GetValues(string name)
 157        {
 158            CheckHeaderName(name);
 159
 160            IEnumerable<string> values;
 161            if (!TryGetValues(name, out values))
 162            {
 163                throw new InvalidOperationException(SR.net_http_headers_not_found);
 164            }
 165
 166            return values;
 167        }
 168
 169        public bool TryGetValues(string name, out IEnumerable<string> values)
 170        {
 171            CheckHeaderName(name);
 172
 173            if (headerStore == null)
 174            {
 175                values = null;
 176                return false;
 177            }
 178
 179            HeaderStoreItemInfo info = null;
 180            if (TryGetAndParseHeaderInfo(name, out info))
 181            {
 182                values = GetValuesAsStrings(info);
 183                return true;
 184            }
 185
 186            values = null;
 187            return false;
 188        }
 189
 190        public bool Contains(string name)
 191        {
 192            CheckHeaderName(name);
 193
 194            if (headerStore == null)
 195            {
 196                return false;
 197            }
 198
 199            // We can't just call headerStore.ContainsKey() since after parsing the value the header may not exist
 200            // anymore (if the value contains invalid newline chars, we remove the header). So try to parse the 
 201            // header value.
 202            HeaderStoreItemInfo info = null;
 203            return TryGetAndParseHeaderInfo(name, out info);
 204        }
 205
 206        public override string ToString()
 207        {
 208            // Return all headers as string similar to: 
 209            // HeaderName1: Value1, Value2
 210            // HeaderName2: Value1
 211            // ...
 212            StringBuilder sb = new StringBuilder();
 213            foreach (var header in this)
 214            {
 215                sb.Append(header.Key);
 216                sb.Append(": ");
 217                sb.Append(this.GetHeaderString(header.Key));
 218                sb.Append("\r\n");
 219            }
 220
 221            return sb.ToString();
 222        }
 223        
 224        internal IEnumerable<KeyValuePair<string, string>> GetHeaderStrings()
 225        {
 226            if (headerStore == null)
 227            {
 228                yield break;
 229            }
 230
 231            foreach (var header in headerStore)
 232            {
 233                HeaderStoreItemInfo info = header.Value;
 234
 235                string stringValue = GetHeaderString(info);
 236
 237                yield return new KeyValuePair<string, string>(header.Key, stringValue);
 238            }
 239        }
 240
 241        internal string GetHeaderString(string headerName)
 242        {
 243            return GetHeaderString(headerName, null);
 244        }
 245
 246        internal string GetHeaderString(string headerName, object exclude)
 247        {
 248            HeaderStoreItemInfo info;
 249            if (!TryGetHeaderInfo(headerName, out info))
 250            {
 251                return string.Empty;
 252            }
 253
 254            return GetHeaderString(info, exclude);
 255        }
 256
 257        private string GetHeaderString(HeaderStoreItemInfo info)
 258        {
 259            return GetHeaderString(info, null);
 260        }
 261
 262        private string GetHeaderString(HeaderStoreItemInfo info, object exclude)
 263        {
 264            string stringValue = string.Empty; // returned if values.Length == 0
 265            
 266            string[] values = GetValuesAsStrings(info, exclude);
 267
 268            if (values.Length == 1)
 269            {
 270                stringValue = values[0];
 271            }
 272            else
 273            {
 274                // Note that if we get multiple values for a header that doesn't support multiple values, we'll
 275                // just separate the values using a comma (default separator).
 276                string separator = HttpHeaderParser.DefaultSeparator;
 277                if ((info.Parser != null) && (info.Parser.SupportsMultipleValues))
 278                {
 279                    separator = info.Parser.Separator;
 280                }
 281                stringValue = string.Join(separator, values);
 282            }
 283
 284            return stringValue;
 285        }
 286
 287        #region IEnumerable<KeyValuePair<string, IEnumerable<string>>> Members
 288
 289        public IEnumerator<KeyValuePair<string, IEnumerable<string>>> GetEnumerator()
 290        {
 291            if (headerStore == null)
 292            {
 293                yield break;
 294            }
 295
 296            List<string> invalidHeaders = null;
 297
 298            foreach (var header in headerStore)
 299            {
 300                HeaderStoreItemInfo info = header.Value;
 301
 302                // Make sure we parse all raw values before returning the result. Note that this has to be
 303                // done before we calculate the array length (next line): A raw value may contain a list of
 304                // values.
 305                if (!ParseRawHeaderValues(header.Key, info, false))
 306                {
 307                    // We have an invalid header value (contains invalid newline chars). Mark it as "to-be-deleted"
 308                    // and skip this header.
 309                    if (invalidHeaders == null)
 310                    {
 311                        invalidHeaders = new List<string>();
 312                    }
 313                    invalidHeaders.Add(header.Key);
 314                }
 315                else
 316                {
 317                    string[] values = GetValuesAsStrings(info);
 318                    yield return new KeyValuePair<string, IEnumerable<string>>(header.Key, values);
 319                }
 320            }
 321
 322            // While we were enumerating headers, we also parsed header values. If during parsing it turned out that
 323            // the header value was invalid (contains invalid newline chars), remove the header from the store after
 324            // completing the enumeration.
 325            if (invalidHeaders != null)
 326            {
 327                Contract.Assert(headerStore != null);
 328                foreach (string invalidHeader in invalidHeaders)
 329                {
 330                    headerStore.Remove(invalidHeader);
 331                }
 332            }
 333        }
 334
 335        #endregion
 336
 337        #region IEnumerable Members
 338
 339        Collections.IEnumerator Collections.IEnumerable.GetEnumerator()
 340        {
 341            return GetEnumerator();
 342        }
 343
 344        #endregion
 345
 346        internal void SetConfiguration(Dictionary<string, HttpHeaderParser> parserStore,
 347            HashSet<string> invalidHeaders)
 348        {
 349            Contract.Assert(this.parserStore == null, "Parser store was already set.");
 350
 351            this.parserStore = parserStore;
 352            this.invalidHeaders = invalidHeaders;
 353        }
 354
 355        internal void AddParsedValue(string name, object value)
 356        {
 357            Contract.Requires((name != null) && (name.Length > 0));
 358            Contract.Requires(HttpRuleParser.GetTokenLength(name, 0) == name.Length);
 359            Contract.Requires(value != null);
 360
 361            HeaderStoreItemInfo info = GetOrCreateHeaderInfo(name, true);
 362            Contract.Assert(info.Parser != null, "Can't add parsed value if there is no parser available.");
 363
 364            // If the current header has only one value, we can't add another value. The strongly typed property
 365            // must not call AddParsedValue(), but SetParsedValue(). E.g. for headers like 'Date', 'Host'.
 366            Contract.Assert(info.CanAddValue, "Header '" + name + "' doesn't support multiple values");
 367
 368            AddValue(info, value, StoreLocation.Parsed);
 369        }
 370
 371        internal void SetParsedValue(string name, object value)
 372        {
 373            Contract.Requires((name != null) && (name.Length > 0));
 374            Contract.Requires(HttpRuleParser.GetTokenLength(name, 0) == name.Length);
 375            Contract.Requires(value != null);
 376
 377            // This method will first clear all values. This is used e.g. when setting the 'Date' or 'Host' header.
 378            // I.e. headers not supporting collections.
 379            HeaderStoreItemInfo info = GetOrCreateHeaderInfo(name, true);
 380            Contract.Assert(info.Parser != null, "Can't add parsed value if there is no parser available.");
 381
 382            info.InvalidValue = null;
 383            info.ParsedValue = null;
 384            info.RawValue = null;
 385
 386            AddValue(info, value, StoreLocation.Parsed);
 387        }
 388
 389        internal void SetOrRemoveParsedValue(string name, object value)
 390        {
 391            if (value == null)
 392            {
 393                Remove(name);
 394            }
 395            else
 396            {
 397                SetParsedValue(name, value);
 398            }
 399        }
 400
 401        internal bool RemoveParsedValue(string name, object value)
 402        {
 403            Contract.Requires((name != null) && (name.Length > 0));
 404            Contract.Requires(HttpRuleParser.GetTokenLength(name, 0) == name.Length);
 405            Contract.Requires(value != null);
 406
 407            if (headerStore == null)
 408            {
 409                return false;
 410            }
 411
 412            // If we have a value for this header, then verify if we have a single value. If so, compare that
 413            // value with 'item'. If we have a list of values, then remove 'item' from the list.
 414            HeaderStoreItemInfo info = null;
 415            if (TryGetAndParseHeaderInfo(name, out info))
 416            {
 417                Contract.Assert(info.Parser != null, "Can't add parsed value if there is no parser available.");
 418                Contract.Assert(info.Parser.SupportsMultipleValues,
 419                    "This method should not be used for single-value headers. Use Remove(string) instead.");
 420
 421                bool result = false;
 422
 423                // If there is no entry, just return.
 424                if (info.ParsedValue == null)
 425                {
 426                    return false;
 427                }
 428
 429                IEqualityComparer comparer = info.Parser.Comparer;
 430
 431                List<object> parsedValues = info.ParsedValue as List<object>;
 432                if (parsedValues == null)
 433                {
 434                    Contract.Assert(info.ParsedValue.GetType() == value.GetType(),
 435                        "Stored value does not have the same type as 'value'.");
 436
 437                    if (AreEqual(value, info.ParsedValue, comparer))
 438                    {
 439                        info.ParsedValue = null;
 440                        result = true;
 441                    }
 442                }
 443                else
 444                {
 445                    foreach (object item in parsedValues)
 446                    {
 447                        Contract.Assert(item.GetType() == value.GetType(),
 448                            "One of the stored values does not have the same type as 'value'.");
 449
 450                        if (AreEqual(value, item, comparer))
 451                        {
 452                            // Remove 'item' rather than 'value', since the 'comparer' may consider two values
 453                            // equal even though the default obj.Equals() may not (e.g. if 'comparer' does
 454                            // case-insentive comparison for strings, but string.Equals() is case-sensitive).
 455                            result = parsedValues.Remove(item);
 456                            break;
 457                        }
 458                    }
 459
 460                    // If we removed the last item in a list, remove the list.
 461                    if (parsedValues.Count == 0)
 462                    {
 463                        info.ParsedValue = null;
 464                    }
 465                }
 466
 467                // If there is no value for the header left, remove the header.
 468                if (info.IsEmpty)
 469                {
 470                    bool headerRemoved = Remove(name);
 471                    Contract.Assert(headerRemoved, "Existing header '" + name + "' couldn't be removed.");
 472                }
 473
 474                return result;
 475            }
 476            return false;
 477        }
 478
 479        internal bool ContainsParsedValue(string name, object value)
 480        {
 481            Contract.Requires((name != null) && (name.Length > 0));
 482            Contract.Requires(HttpRuleParser.GetTokenLength(name, 0) == name.Length);
 483            Contract.Requires(value != null);
 484
 485            if (headerStore == null)
 486            {
 487                return false;
 488            }
 489
 490            // If we have a value for this header, then verify if we have a single value. If so, compare that
 491            // value with 'item'. If we have a list of values, then compare each item in the list with 'item'.
 492            HeaderStoreItemInfo info = null;
 493            if (TryGetAndParseHeaderInfo(name, out info))
 494            {
 495                Contract.Assert(info.Parser != null, "Can't add parsed value if there is no parser available.");
 496                Contract.Assert(info.Parser.SupportsMultipleValues,
 497                    "This method should not be used for single-value headers. Use equality comparer instead.");
 498
 499                // If there is no entry, just return.
 500                if (info.ParsedValue == null)
 501                {
 502                    return false;
 503                }
 504
 505                List<object> parsedValues = info.ParsedValue as List<object>;
 506
 507                IEqualityComparer comparer = info.Parser.Comparer;
 508
 509                if (parsedValues == null)
 510                {
 511                    Contract.Assert(info.ParsedValue.GetType() == value.GetType(),
 512                        "Stored value does not have the same type as 'value'.");
 513
 514                    return AreEqual(value, info.ParsedValue, comparer);
 515                }
 516                else
 517                {
 518                    foreach (object item in parsedValues)
 519                    {
 520                        Contract.Assert(item.GetType() == value.GetType(),
 521                            "One of the stored values does not have the same type as 'value'.");
 522
 523                        if (AreEqual(value, item, comparer))
 524                        {
 525                            return true;
 526                        }
 527                    }
 528                    return false;
 529                }
 530            }
 531
 532            return false;
 533        }
 534
 535        internal virtual void AddHeaders(HttpHeaders sourceHeaders)
 536        {
 537            Contract.Requires(sourceHeaders != null);
 538            Contract.Assert(this.parserStore == sourceHeaders.parserStore,
 539                "Can only copy headers from an instance with the same header parsers.");
 540
 541            if (sourceHeaders.headerStore == null)
 542            {
 543                return;
 544            }
 545
 546            List<string> invalidHeaders = null;
 547
 548            foreach (var header in sourceHeaders.headerStore)
 549            {
 550                // Only add header values if they're not already set on the message. Note that we don't merge 
 551                // collections: If both the default headers and the message have set some values for a certain
 552                // header, then we don't try to merge the values.
 553                if ((headerStore == null) || (!headerStore.ContainsKey(header.Key)))
 554                {
 555                    HeaderStoreItemInfo sourceInfo = header.Value;
 556
 557                    // If DefaultRequestHeaders values are copied to multiple messages, it is useful to parse these
 558                    // default header values only once. This is what we're doing here: By parsing raw headers in 
 559                    // 'sourceHeaders' before copying values to our header store.
 560                    if (!sourceHeaders.ParseRawHeaderValues(header.Key, sourceInfo, false))
 561                    {
 562                        // If after trying to parse source header values no value is left (i.e. all values contain 
 563                        // invalid newline chars), mark this header as 'to-be-deleted' and skip to the next header.
 564                        if (invalidHeaders == null)
 565                        {
 566                            invalidHeaders = new List<string>();
 567                        }
 568
 569                        invalidHeaders.Add(header.Key);
 570                    }
 571                    else
 572                    {
 573                        AddHeaderInfo(header.Key, sourceInfo);
 574                    }
 575                }
 576            }
 577
 578            if (invalidHeaders != null)
 579            {
 580                Contract.Assert(sourceHeaders.headerStore != null);
 581                foreach (string invalidHeader in invalidHeaders)
 582                {
 583                    sourceHeaders.headerStore.Remove(invalidHeader);
 584                }
 585            }
 586        }
 587
 588        private void AddHeaderInfo(string headerName, HeaderStoreItemInfo sourceInfo)
 589        {
 590            HeaderStoreItemInfo destinationInfo = CreateAndAddHeaderToStore(headerName);
 591            Contract.Assert(sourceInfo.Parser == destinationInfo.Parser,
 592                "Expected same parser on both source and destination header store for header '" + headerName +
 593                "'.");
 594
 595            // We have custom header values. The parsed values are strings.
 596            if (destinationInfo.Parser == null)
 597            {
 598                Contract.Assert((sourceInfo.RawValue == null) && (sourceInfo.InvalidValue == null),
 599                    "No raw or invalid values expected for custom headers.");
 600
 601                // Custom header values are always stored as string or list of strings.
 602                destinationInfo.ParsedValue = CloneStringHeaderInfoValues(sourceInfo.ParsedValue);
 603            }
 604            else
 605            {
 606                // We have a parser, so we have to copy invalid values and clone parsed values.
 607
 608                // Invalid values are always strings. Strings are immutable. So we only have to clone the 
 609                // collection (if there is one).
 610                destinationInfo.InvalidValue = CloneStringHeaderInfoValues(sourceInfo.InvalidValue);
 611
 612                // Now clone and add parsed values (if any).
 613                if (sourceInfo.ParsedValue != null)
 614                {
 615                    List<object> sourceValues = sourceInfo.ParsedValue as List<object>;
 616                    if (sourceValues == null)
 617                    {
 618                        CloneAndAddValue(destinationInfo, sourceInfo.ParsedValue);
 619                    }
 620                    else
 621                    {
 622                        foreach (object item in sourceValues)
 623                        {
 624                            CloneAndAddValue(destinationInfo, item);
 625                        }
 626                    }
 627                }
 628            }
 629        }
 630
 631        private static void CloneAndAddValue(HeaderStoreItemInfo destinationInfo, object source)
 632        {
 633            // We only have one value. Clone it and assign it to the store.
 634            ICloneable cloneableValue = source as ICloneable;
 635
 636            if (cloneableValue != null)
 637            {
 638                AddValue(destinationInfo, cloneableValue.Clone(), StoreLocation.Parsed);
 639            }
 640            else
 641            {
 642                // If it doesn't implement ICloneable, it's a value type or an immutable type like String/Uri. 
 643                AddValue(destinationInfo, source, StoreLocation.Parsed);
 644            }
 645        }
 646
 647        private static object CloneStringHeaderInfoValues(object source)
 648        {
 649            if (source == null)
 650            {
 651                return null;
 652            }
 653
 654            List<object> sourceValues = source as List<object>;
 655            if (sourceValues == null)
 656            {
 657                // If we just have one value, return the reference to the string (strings are immutable so it's OK
 658                // to use the reference).
 659                return source;
 660            }
 661            else
 662            {
 663                // If we have a list of strings, create a new list and copy all strings to the new list.
 664                return new List<object>(sourceValues);
 665            }
 666        }
 667
 668        private HeaderStoreItemInfo GetOrCreateHeaderInfo(string name, bool parseRawValues)
 669        {
 670            Contract.Requires((name != null) && (name.Length > 0));
 671            Contract.Requires(HttpRuleParser.GetTokenLength(name, 0) == name.Length);
 672            Contract.Ensures(Contract.Result<HeaderStoreItemInfo>() != null);
 673
 674            HeaderStoreItemInfo result = null;
 675            bool found = false;
 676            if (parseRawValues)
 677            {
 678                found = TryGetAndParseHeaderInfo(name, out result);
 679            }
 680            else
 681            {
 682                found = TryGetHeaderInfo(name, out result);
 683            }
 684
 685            if (!found)
 686            {
 687                result = CreateAndAddHeaderToStore(name);
 688            }
 689
 690            return result;
 691        }
 692
 693        private HeaderStoreItemInfo CreateAndAddHeaderToStore(string name)
 694        {
 695            // If we don't have the header in the store yet, add it now.
 696            HeaderStoreItemInfo result = new HeaderStoreItemInfo(GetParser(name));
 697
 698            AddHeaderToStore(name, result);
 699
 700            return result;
 701        }
 702
 703        private void AddHeaderToStore(string name, HeaderStoreItemInfo info)
 704        {
 705            if (headerStore == null)
 706            {
 707                headerStore = new Dictionary<string, HeaderStoreItemInfo>(
 708                    HeaderUtilities.CaseInsensitiveStringComparer);
 709            }
 710            headerStore.Add(name, info);
 711        }
 712
 713        private bool TryGetHeaderInfo(string name, out HeaderStoreItemInfo info)
 714        {
 715            if (headerStore == null)
 716            {
 717                info = null;
 718                return false;
 719            }
 720
 721            return headerStore.TryGetValue(name, out info);
 722        }
 723
 724        private bool TryGetAndParseHeaderInfo(string name, out HeaderStoreItemInfo info)
 725        {
 726            if (TryGetHeaderInfo(name, out info))
 727            {
 728                return ParseRawHeaderValues(name, info, true);
 729            }
 730
 731            return false;
 732        }
 733
 734        private bool ParseRawHeaderValues(string name, HeaderStoreItemInfo info, bool removeEmptyHeader)
 735        {
 736            Contract.Ensures(info.RawValue == null);
 737
 738            // Unlike TryGetHeaderInfo() this method tries to parse all non-validated header values (if any)
 739            // before returning to the caller.
 740            if (info.RawValue != null)
 741            {
 742                List<string> rawValues = info.RawValue as List<string>;
 743
 744                if (rawValues == null)
 745                {
 746                    ParseSingleRawHeaderValue(name, info);
 747                }
 748                else
 749                {
 750                    ParseMultipleRawHeaderValues(name, info, rawValues);
 751                }
 752
 753                // At this point all values are either in info.ParsedValue, info.InvalidValue, or were removed since they
 754                // contain invalid newline chars. Reset RawValue.
 755                info.RawValue = null;
 756
 757                // During parsing, we removed tha value since it contains invalid newline chars. Return false to indicate that
 758                // this is an empty header. If the caller specified to remove empty headers, we'll remove the header before
 759                // returning.
 760                if ((info.InvalidValue == null) && (info.ParsedValue == null))
 761                {
 762                    if (removeEmptyHeader)
 763                    {
 764                        // After parsing the raw value, no value is left because all values contain invalid newline 
 765                        // chars.
 766                        Contract.Assert(headerStore != null);
 767                        headerStore.Remove(name);
 768                    }
 769                    return false;
 770                }
 771            }
 772            
 773            return true;
 774        }
 775
 776        private static void ParseMultipleRawHeaderValues(string name, HeaderStoreItemInfo info, List<string> rawValues)
 777        {
 778            if (info.Parser == null)
 779            {
 780                foreach (string rawValue in rawValues)
 781                {
 782                    if (!ContainsInvalidNewLine(rawValue, name))
 783                    {
 784                        AddValue(info, rawValue, StoreLocation.Parsed);
 785                    }
 786                }
 787            }
 788            else
 789            {
 790                foreach (string rawValue in rawValues)
 791                {
 792                    if (!TryParseAndAddRawHeaderValue(name, info, rawValue, true))
 793                    {
 794                        if (Logging.On) Logging.PrintWarning(Logging.Http, string.Format(System.Globalization.CultureInfo.InvariantCulture, SR.net_http_log_headers_invalid_value, name, rawValue));
 795                    }
 796                }
 797            }
 798        }
 799
 800        private static void ParseSingleRawHeaderValue(string name, HeaderStoreItemInfo info)
 801        {
 802            string rawValue = info.RawValue as string;
 803            Contract.Assert(rawValue != null, "RawValue must either be List<string> or string.");
 804
 805            if (info.Parser == null)
 806            {
 807                if (!ContainsInvalidNewLine(rawValue, name))
 808                {
 809                    info.ParsedValue = info.RawValue;
 810                }
 811            }
 812            else
 813            {
 814                if (!TryParseAndAddRawHeaderValue(name, info, rawValue, true))
 815                {
 816                    if (Logging.On) Logging.PrintWarning(Logging.Http, string.Format(System.Globalization.CultureInfo.InvariantCulture, SR.net_http_log_headers_invalid_value, name, rawValue));
 817                }
 818            }
 819        }
 820
 821        // See Add(name, string)
 822        internal bool TryParseAndAddValue(string name, string value)
 823        {
 824            // We don't use GetOrCreateHeaderInfo() here, since this would create a new header in the store. If parsing
 825            // the value then throws, we would have to remove the header from the store again. So just get a 
 826            // HeaderStoreItemInfo object and try to parse the value. If it works, we'll add the header.
 827            HeaderStoreItemInfo info;
 828            bool addToStore;
 829            PrepareHeaderInfoForAdd(name, out info, out addToStore);
 830
 831            bool result = TryParseAndAddRawHeaderValue(name, info, value, false);
 832
 833            if (result && addToStore && (info.ParsedValue != null))
 834            {
 835                // If we get here, then the value could be parsed correctly. If we created a new HeaderStoreItemInfo, add
 836                // it to the store if we added at least one value.
 837                AddHeaderToStore(name, info);
 838            }
 839
 840            return result;
 841        }
 842
 843        // See ParseAndAddValue
 844        private static bool TryParseAndAddRawHeaderValue(string name, HeaderStoreItemInfo info, string value, bool addWhenInvalid)
 845        {
 846            Contract.Requires(info != null);
 847            Contract.Requires(info.Parser != null);
 848
 849            // Values are added as 'invalid' if we either can't parse the value OR if we already have a value
 850            // and the current header doesn't support multiple values: e.g. trying to add a date/time value
 851            // to the 'Date' header if we already have a date/time value will result in the second value being
 852            // added to the 'invalid' header values.
 853            if (!info.CanAddValue)
 854            {
 855                if (addWhenInvalid)
 856                {
 857                    AddValue(info, value ?? string.Empty, StoreLocation.Invalid);
 858                }
 859                return false;
 860            }
 861
 862            int index = 0;
 863            object parsedValue = null;
 864
 865            if (info.Parser.TryParseValue(value, info.ParsedValue, ref index, out parsedValue))
 866            {
 867                // The raw string only represented one value (which was successfully parsed). Add the value and return.
 868                if ((value == null) || (index == value.Length))
 869                {
 870                    if (parsedValue != null)
 871                    {
 872                        AddValue(info, parsedValue, StoreLocation.Parsed);
 873                    }
 874                    return true;
 875                }
 876                Contract.Assert(index < value.Length, "Parser must return an index value within the string length.");
 877
 878                // If we successfully parsed a value, but there are more left to read, store the results in a temp
 879                // list. Only when all values are parsed successfully write the list to the store.
 880                List<object> parsedValues = new List<object>();
 881                if (parsedValue != null)
 882                {
 883                    parsedValues.Add(parsedValue);
 884                }
 885
 886                while (index < value.Length)
 887                {
 888                    if (info.Parser.TryParseValue(value, info.ParsedValue, ref index, out parsedValue))
 889                    {
 890                        if (parsedValue != null)
 891                        {
 892                            parsedValues.Add(parsedValue);
 893                        }
 894                    }
 895                    else
 896                    {
 897                        if (!ContainsInvalidNewLine(value, name) && addWhenInvalid)
 898                        {
 899                            AddValue(info, value, StoreLocation.Invalid);
 900                        }
 901                        return false;
 902                    }
 903                }
 904
 905                // All values were parsed correctly. Copy results to the store.
 906                foreach (object item in parsedValues)
 907                {
 908                    AddValue(info, item, StoreLocation.Parsed);
 909                }
 910                return true;
 911            }
 912
 913            if (!ContainsInvalidNewLine(value, name) && addWhenInvalid)
 914            {
 915                AddValue(info, value ?? string.Empty, StoreLocation.Invalid);
 916            }
 917            return false;
 918        }
 919
 920        private static void AddValue(HeaderStoreItemInfo info, object value, StoreLocation location)
 921        {
 922            // Since we have the same pattern for all three store locations (raw, invalid, parsed), we use
 923            // this helper method to deal with adding values:
 924            // - if 'null' just set the store property to 'value'
 925            // - if 'List<T>' append 'value' to the end of the list
 926            // - if 'T', i.e. we have already a value stored (but no list), create a list, add the stored value
 927            //   to the list and append 'value' at the end of the newly created list.
 928
 929            Contract.Assert((info.Parser != null) || ((info.Parser == null) && (value.GetType() == typeof(string))),
 930                "If no parser is defined, then the value must be string.");
 931
 932            object currentStoreValue = null;
 933            switch (location)
 934            {
 935                case StoreLocation.Raw:
 936                    currentStoreValue = info.RawValue;
 937                    AddValueToStoreValue<string>(info, value, ref currentStoreValue);
 938                    info.RawValue = currentStoreValue;
 939                    break;
 940
 941                case StoreLocation.Invalid:
 942                    currentStoreValue = info.InvalidValue;
 943                    AddValueToStoreValue<string>(info, value, ref currentStoreValue);
 944                    info.InvalidValue = currentStoreValue;
 945                    break;
 946
 947                case StoreLocation.Parsed:
 948                    Contract.Assert((value == null) || (!(value is List<object>)), 
 949                        "Header value types must not derive from List<object> since this type is used internally to store " +
 950                        "lists of values. So we would not be able to distinguish between a single value and a list of values.");
 951                    currentStoreValue = info.ParsedValue;
 952                    AddValueToStoreValue<object>(info, value, ref currentStoreValue);
 953                    info.ParsedValue = currentStoreValue;
 954                    break;
 955
 956                default:
 957                    Contract.Assert(false, "Unknown StoreLocation value: " + location.ToString());
 958                    break;
 959            }
 960        }
 961
 962        private static void AddValueToStoreValue<T>(HeaderStoreItemInfo info, object value,
 963            ref object currentStoreValue) where T : class
 964        {
 965            // If there is no value set yet, then add current item as value (we don't create a list
 966            // if not required). If 'info.Value' is already assigned then make sure 'info.Value' is a
 967            // List<T> and append 'item' to the list.
 968            if (currentStoreValue == null)
 969            {
 970                currentStoreValue = value;
 971            }
 972            else
 973            {
 974                List<T> storeValues = currentStoreValue as List<T>;
 975
 976                if (storeValues == null)
 977                {
 978                    storeValues = new List<T>(2);
 979                    Contract.Assert(value is T);
 980                    storeValues.Add(currentStoreValue as T);
 981                    currentStoreValue = storeValues;
 982                }
 983                Contract.Assert(value is T);
 984                storeValues.Add(value as T);
 985            }
 986        }
 987
 988        // Since most of the time we just have 1 value, we don't create a List<object> for one value, but we change
 989        // the return type to 'object'. The caller has to deal with the return type (object vs. List<object>). This 
 990        // is to optimize the most common scenario where a header has only one value.
 991        internal object GetParsedValues(string name)
 992        {
 993            Contract.Requires((name != null) && (name.Length > 0));
 994            Contract.Requires(HttpRuleParser.GetTokenLength(name, 0) == name.Length);
 995
 996            HeaderStoreItemInfo info = null;
 997
 998            if (!TryGetAndParseHeaderInfo(name, out info))
 999            {
1000                return null;
1001            }
1002
1003            return info.ParsedValue;
1004        }
1005
1006        private void PrepareHeaderInfoForAdd(string name, out HeaderStoreItemInfo info, out bool addToStore)
1007        {
1008            info = null;
1009            addToStore = false;
1010            if (!TryGetAndParseHeaderInfo(name, out info))
1011            {
1012                info = new HeaderStoreItemInfo(GetParser(name));
1013                addToStore = true;
1014            }
1015        }
1016
1017        private void ParseAndAddValue(string name, HeaderStoreItemInfo info, string value)
1018        {
1019            Contract.Requires(info != null);
1020
1021            if (info.Parser == null)
1022            {
1023                // If we don't have a parser for the header, we consider the value valid if it doesn't contains 
1024                // invalid newline characters. We add the values as "parsed value". Note that we allow empty values.
1025                CheckInvalidNewLine(value);
1026                AddValue(info, value  ?? string.Empty, StoreLocation.Parsed);
1027                return;
1028            }
1029
1030            // If the header only supports 1 value, we can add the current value only if there is no
1031            // value already set.
1032            if (!info.CanAddValue)
1033            {
1034                throw new FormatException(string.Format(System.Globalization.CultureInfo.InvariantCulture, SR.net_http_headers_single_value_header, name));
1035            }
1036
1037            int index = 0;
1038            object parsedValue = info.Parser.ParseValue(value, info.ParsedValue, ref index);
1039
1040            // The raw string only represented one value (which was successfully parsed). Add the value and return.
1041            // If value is null we still have to first call ParseValue() to allow the parser to decide whether null is
1042            // a valid value. If it is (i.e. no exception thrown), we set the parsed value (if any) and return.
1043            if ((value == null) || (index == value.Length))
1044            {
1045                // If the returned value is null, then it means the header accepts empty values. I.e. we don't throw
1046                // but we don't add 'null' to the store either.
1047                if (parsedValue != null)
1048                {
1049                    AddValue(info, parsedValue, StoreLocation.Parsed);
1050                }
1051                return;
1052            }
1053            Contract.Assert(index < value.Length, "Parser must return an index value within the string length.");
1054
1055            // If we successfully parsed a value, but there are more left to read, store the results in a temp
1056            // list. Only when all values are parsed successfully write the list to the store.
1057            List<object> parsedValues = new List<object>();
1058            if (parsedValue != null)
1059            {
1060                parsedValues.Add(parsedValue);
1061            }
1062
1063            while (index < value.Length)
1064            {
1065                parsedValue = info.Parser.ParseValue(value, info.ParsedValue, ref index);
1066                if (parsedValue != null)
1067                {
1068                    parsedValues.Add(parsedValue);
1069                }
1070            }
1071
1072            // All values were parsed correctly. Copy results to the store.
1073            foreach (object item in parsedValues)
1074            {
1075                AddValue(info, item, StoreLocation.Parsed);
1076            }
1077        }
1078
1079        private HttpHeaderParser GetParser(string name)
1080        {
1081            if (parserStore == null)
1082            {
1083                return null;
1084            }
1085
1086            HttpHeaderParser parser = null;
1087            if (parserStore.TryGetValue(name, out parser))
1088            {
1089                return parser;
1090            }
1091
1092            return null;
1093        }
1094
1095        private void CheckHeaderName(string name)
1096        {
1097            if (string.IsNullOrEmpty(name))
1098            {
1099                throw new ArgumentException(SR.net_http_argument_empty_string, "name");
1100            }
1101
1102            if (HttpRuleParser.GetTokenLength(name, 0) != name.Length)
1103            {
1104                throw new FormatException(SR.net_http_headers_invalid_header_name);
1105            }
1106
1107            if ((invalidHeaders != null) && (invalidHeaders.Contains(name)))
1108            {
1109                throw new InvalidOperationException(SR.net_http_headers_not_allowed_header_name);
1110            }
1111        }
1112
1113        private static void CheckInvalidNewLine(string value)
1114        {
1115            if (value == null)
1116            {
1117                return;
1118            }
1119
1120            if (HttpRuleParser.ContainsInvalidNewLine(value))
1121            {
1122                throw new FormatException(SR.net_http_headers_no_newlines);
1123            }
1124        }
1125
1126        private static bool ContainsInvalidNewLine(string value, string name)
1127        {
1128            if (HttpRuleParser.ContainsInvalidNewLine(value))
1129            {
1130                if (Logging.On) Logging.PrintError(Logging.Http, string.Format(System.Globalization.CultureInfo.InvariantCulture, SR.net_http_log_headers_no_newlines, name, value));
1131                return true;
1132            }
1133            return false;
1134        }
1135
1136        private static string[] GetValuesAsStrings(HeaderStoreItemInfo info)
1137        {
1138            return GetValuesAsStrings(info, null);
1139        }
1140
1141        // When doing exclusion comparison, assume raw values have been parsed.
1142        private static string[] GetValuesAsStrings(HeaderStoreItemInfo info, object exclude)
1143        {
1144            Contract.Ensures(Contract.Result<string[]>() != null);
1145
1146            int length = GetValueCount(info);
1147            string[] values = new string[length];
1148
1149            if (length > 0)
1150            {
1151                int currentIndex = 0;
1152
1153                ReadStoreValues<string>(values, info.RawValue, null, null, ref currentIndex);
1154                ReadStoreValues<object>(values, info.ParsedValue, info.Parser, exclude, ref currentIndex);
1155
1156                // Set parser parameter to 'null' for invalid values: The invalid values is always a string so we 
1157                // don't need the parser to "serialize" the value to a string.
1158                ReadStoreValues<string>(values, info.InvalidValue, null, null, ref currentIndex);
1159
1160                // The values array may not be full because some values were excluded
1161                if (currentIndex < length)
1162                {
1163                    string[] trimmedValues = new string[currentIndex];
1164                    Array.Copy(values, trimmedValues, currentIndex);
1165                    values = trimmedValues;
1166                }
1167            }
1168            return values;
1169        }
1170
1171        private static int GetValueCount(HeaderStoreItemInfo info)
1172        {
1173            Contract.Requires(info != null);
1174
1175            int valueCount = 0;
1176            UpdateValueCount<string>(info.RawValue, ref valueCount);
1177            UpdateValueCount<string>(info.InvalidValue, ref valueCount);
1178            UpdateValueCount<object>(info.ParsedValue, ref valueCount);
1179
1180            return valueCount;
1181        }
1182
1183        private static void UpdateValueCount<T>(object valueStore, ref int valueCount)
1184        {
1185            if (valueStore == null)
1186            {
1187                return;
1188            }
1189
1190            List<T> values = valueStore as List<T>;
1191            if (values != null)
1192            {
1193                valueCount += values.Count;
1194            }
1195            else
1196            {
1197                valueCount++;
1198            }
1199        }
1200
1201        private static void ReadStoreValues<T>(string[] values, object storeValue, HttpHeaderParser parser,
1202            T exclude, ref int currentIndex)
1203        {
1204            Contract.Requires(values != null);
1205
1206            if (storeValue != null)
1207            {
1208                List<T> storeValues = storeValue as List<T>;
1209
1210                if (storeValues == null)
1211                {
1212                    if (ShouldAdd<T>(storeValue, parser, exclude))
1213                    {
1214                        values[currentIndex] = parser == null ? storeValue.ToString() : parser.ToString(storeValue);
1215                        currentIndex++;
1216                    }
1217                }
1218                else
1219                {
1220                    foreach (object item in storeValues)
1221                    {
1222                        if (ShouldAdd<T>(item, parser, exclude))
1223                        {
1224                            values[currentIndex] = parser == null ? item.ToString() : parser.ToString(item);
1225                            currentIndex++;
1226                        }
1227                    }
1228                }
1229            }
1230        }
1231
1232        private static bool ShouldAdd<T>(object storeValue, HttpHeaderParser parser, T exclude)
1233        {
1234            bool add = true;
1235            if (parser != null && exclude != nu

Large files files are truncated, but you can click here to view the full file