PageRenderTime 54ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/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
Possible License(s): CC-BY-SA-3.0, Apache-2.0

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

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

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