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

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

#
C# | 647 lines | 523 code | 102 blank | 22 comment | 104 complexity | 887f335742010ab98fd924bfb8e610b6 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, Apache-2.0
  1. using System.Collections.Generic;
  2. using System.Diagnostics.Contracts;
  3. using System.Globalization;
  4. using System.Text;
  5. namespace System.Net.Http.Headers
  6. {
  7. public class CacheControlHeaderValue : ICloneable
  8. {
  9. private const string maxAgeString = "max-age";
  10. private const string maxStaleString = "max-stale";
  11. private const string minFreshString = "min-fresh";
  12. private const string mustRevalidateString = "must-revalidate";
  13. private const string noCacheString = "no-cache";
  14. private const string noStoreString = "no-store";
  15. private const string noTransformString = "no-transform";
  16. private const string onlyIfCachedString = "only-if-cached";
  17. private const string privateString = "private";
  18. private const string proxyRevalidateString = "proxy-revalidate";
  19. private const string publicString = "public";
  20. private const string sharedMaxAgeString = "s-maxage";
  21. private static readonly HttpHeaderParser nameValueListParser = GenericHeaderParser.MultipleValueNameValueParser;
  22. private static readonly Action<string> checkIsValidToken = CheckIsValidToken;
  23. private bool noCache;
  24. private ICollection<string> noCacheHeaders;
  25. private bool noStore;
  26. private TimeSpan? maxAge;
  27. private TimeSpan? sharedMaxAge;
  28. private bool maxStale;
  29. private TimeSpan? maxStaleLimit;
  30. private TimeSpan? minFresh;
  31. private bool noTransform;
  32. private bool onlyIfCached;
  33. private bool publicField;
  34. private bool privateField;
  35. private ICollection<string> privateHeaders;
  36. private bool mustRevalidate;
  37. private bool proxyRevalidate;
  38. private ICollection<NameValueHeaderValue> extensions;
  39. public bool NoCache
  40. {
  41. get { return noCache; }
  42. set { noCache = value; }
  43. }
  44. public ICollection<string> NoCacheHeaders
  45. {
  46. get
  47. {
  48. if (noCacheHeaders == null)
  49. {
  50. noCacheHeaders = new ObjectCollection<string>(checkIsValidToken);
  51. }
  52. return noCacheHeaders;
  53. }
  54. }
  55. public bool NoStore
  56. {
  57. get { return noStore; }
  58. set { noStore = value; }
  59. }
  60. public TimeSpan? MaxAge
  61. {
  62. get { return maxAge; }
  63. set { maxAge = value; }
  64. }
  65. public TimeSpan? SharedMaxAge
  66. {
  67. get { return sharedMaxAge; }
  68. set { sharedMaxAge = value; }
  69. }
  70. public bool MaxStale
  71. {
  72. get { return maxStale; }
  73. set { maxStale = value; }
  74. }
  75. public TimeSpan? MaxStaleLimit
  76. {
  77. get { return maxStaleLimit; }
  78. set { maxStaleLimit = value; }
  79. }
  80. public TimeSpan? MinFresh
  81. {
  82. get { return minFresh; }
  83. set { minFresh = value; }
  84. }
  85. public bool NoTransform
  86. {
  87. get { return noTransform; }
  88. set { noTransform = value; }
  89. }
  90. public bool OnlyIfCached
  91. {
  92. get { return onlyIfCached; }
  93. set { onlyIfCached = value; }
  94. }
  95. public bool Public
  96. {
  97. get { return publicField; }
  98. set { publicField = value; }
  99. }
  100. public bool Private
  101. {
  102. get { return privateField; }
  103. set { privateField = value; }
  104. }
  105. public ICollection<string> PrivateHeaders
  106. {
  107. get
  108. {
  109. if (privateHeaders == null)
  110. {
  111. privateHeaders = new ObjectCollection<string>(checkIsValidToken);
  112. }
  113. return privateHeaders;
  114. }
  115. }
  116. public bool MustRevalidate
  117. {
  118. get { return mustRevalidate; }
  119. set { mustRevalidate = value; }
  120. }
  121. public bool ProxyRevalidate
  122. {
  123. get { return proxyRevalidate; }
  124. set { proxyRevalidate = value; }
  125. }
  126. public ICollection<NameValueHeaderValue> Extensions
  127. {
  128. get
  129. {
  130. if (extensions == null)
  131. {
  132. extensions = new ObjectCollection<NameValueHeaderValue>();
  133. }
  134. return extensions;
  135. }
  136. }
  137. public CacheControlHeaderValue()
  138. {
  139. }
  140. private CacheControlHeaderValue(CacheControlHeaderValue source)
  141. {
  142. Contract.Requires(source != null);
  143. noCache = source.noCache;
  144. noStore = source.noStore;
  145. maxAge = source.maxAge;
  146. sharedMaxAge = source.sharedMaxAge;
  147. maxStale = source.maxStale;
  148. maxStaleLimit = source.maxStaleLimit;
  149. minFresh = source.minFresh;
  150. noTransform = source.noTransform;
  151. onlyIfCached = source.onlyIfCached;
  152. publicField = source.publicField;
  153. privateField = source.privateField;
  154. mustRevalidate = source.mustRevalidate;
  155. proxyRevalidate = source.proxyRevalidate;
  156. if (source.noCacheHeaders != null)
  157. {
  158. foreach (var noCacheHeader in source.noCacheHeaders)
  159. {
  160. NoCacheHeaders.Add(noCacheHeader);
  161. }
  162. }
  163. if (source.privateHeaders != null)
  164. {
  165. foreach (var privateHeader in source.privateHeaders)
  166. {
  167. PrivateHeaders.Add(privateHeader);
  168. }
  169. }
  170. if (source.extensions != null)
  171. {
  172. foreach (var extension in source.extensions)
  173. {
  174. Extensions.Add((NameValueHeaderValue)((ICloneable)extension).Clone());
  175. }
  176. }
  177. }
  178. public override string ToString()
  179. {
  180. StringBuilder sb = new StringBuilder();
  181. AppendValueIfRequired(sb, noStore, noStoreString);
  182. AppendValueIfRequired(sb, noTransform, noTransformString);
  183. AppendValueIfRequired(sb, onlyIfCached, onlyIfCachedString);
  184. AppendValueIfRequired(sb, publicField, publicString);
  185. AppendValueIfRequired(sb, mustRevalidate, mustRevalidateString);
  186. AppendValueIfRequired(sb, proxyRevalidate, proxyRevalidateString);
  187. if (noCache)
  188. {
  189. AppendValueWithSeparatorIfRequired(sb, noCacheString);
  190. if ((noCacheHeaders != null) && (noCacheHeaders.Count > 0))
  191. {
  192. sb.Append("=\"");
  193. AppendValues(sb, noCacheHeaders);
  194. sb.Append('\"');
  195. }
  196. }
  197. if (maxAge.HasValue)
  198. {
  199. AppendValueWithSeparatorIfRequired(sb, maxAgeString);
  200. sb.Append('=');
  201. sb.Append(((int)maxAge.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
  202. }
  203. if (sharedMaxAge.HasValue)
  204. {
  205. AppendValueWithSeparatorIfRequired(sb, sharedMaxAgeString);
  206. sb.Append('=');
  207. sb.Append(((int)sharedMaxAge.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
  208. }
  209. if (maxStale)
  210. {
  211. AppendValueWithSeparatorIfRequired(sb, maxStaleString);
  212. if (maxStaleLimit.HasValue)
  213. {
  214. sb.Append('=');
  215. sb.Append(((int)maxStaleLimit.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
  216. }
  217. }
  218. if (minFresh.HasValue)
  219. {
  220. AppendValueWithSeparatorIfRequired(sb, minFreshString);
  221. sb.Append('=');
  222. sb.Append(((int)minFresh.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
  223. }
  224. if (privateField)
  225. {
  226. AppendValueWithSeparatorIfRequired(sb, privateString);
  227. if ((privateHeaders != null) && (privateHeaders.Count > 0))
  228. {
  229. sb.Append("=\"");
  230. AppendValues(sb, privateHeaders);
  231. sb.Append('\"');
  232. }
  233. }
  234. NameValueHeaderValue.ToString(extensions, ',', false, sb);
  235. return sb.ToString();
  236. }
  237. public override bool Equals(object obj)
  238. {
  239. CacheControlHeaderValue other = obj as CacheControlHeaderValue;
  240. if (other == null)
  241. {
  242. return false;
  243. }
  244. if ((noCache != other.noCache) || (noStore != other.noStore) || (maxAge != other.maxAge) ||
  245. (sharedMaxAge != other.sharedMaxAge) || (maxStale != other.maxStale) ||
  246. (maxStaleLimit != other.maxStaleLimit) || (minFresh != other.minFresh) ||
  247. (noTransform != other.noTransform) || (onlyIfCached != other.onlyIfCached) ||
  248. (publicField != other.publicField) || (privateField != other.privateField) ||
  249. (mustRevalidate != other.mustRevalidate) || (proxyRevalidate != other.proxyRevalidate))
  250. {
  251. return false;
  252. }
  253. if (!HeaderUtilities.AreEqualCollections(noCacheHeaders, other.noCacheHeaders,
  254. HeaderUtilities.CaseInsensitiveStringComparer))
  255. {
  256. return false;
  257. }
  258. if (!HeaderUtilities.AreEqualCollections(privateHeaders, other.privateHeaders,
  259. HeaderUtilities.CaseInsensitiveStringComparer))
  260. {
  261. return false;
  262. }
  263. if (!HeaderUtilities.AreEqualCollections(extensions, other.extensions))
  264. {
  265. return false;
  266. }
  267. return true;
  268. }
  269. public override int GetHashCode()
  270. {
  271. // Use a different bit for bool fields: bool.GetHashCode() will return 0 (false) or 1 (true). So we would
  272. // end up having the same hash code for e.g. two instances where one has only noCache set and the other
  273. // only noStore.
  274. int result = noCache.GetHashCode() ^ (noStore.GetHashCode() << 1) ^ (maxStale.GetHashCode() << 2) ^
  275. (noTransform.GetHashCode() << 3) ^ (onlyIfCached.GetHashCode() << 4) ^
  276. (publicField.GetHashCode() << 5) ^ (privateField.GetHashCode() << 6) ^
  277. (mustRevalidate.GetHashCode() << 7) ^ (proxyRevalidate.GetHashCode() << 8);
  278. // XOR the hashcode of timespan values with different numbers to make sure two instances with the same
  279. // timespan set on different fields result in different hashcodes.
  280. result = result ^ (maxAge.HasValue ? maxAge.Value.GetHashCode() ^ 1 : 0) ^
  281. (sharedMaxAge.HasValue ? sharedMaxAge.Value.GetHashCode() ^ 2 : 0) ^
  282. (maxStaleLimit.HasValue ? maxStaleLimit.Value.GetHashCode() ^ 4 : 0) ^
  283. (minFresh.HasValue ? minFresh.Value.GetHashCode() ^ 8 : 0);
  284. if ((noCacheHeaders != null) && (noCacheHeaders.Count > 0))
  285. {
  286. foreach (var noCacheHeader in noCacheHeaders)
  287. {
  288. result = result ^ noCacheHeader.ToLowerInvariant().GetHashCode();
  289. }
  290. }
  291. if ((privateHeaders != null) && (privateHeaders.Count > 0))
  292. {
  293. foreach (var privateHeader in privateHeaders)
  294. {
  295. result = result ^ privateHeader.ToLowerInvariant().GetHashCode();
  296. }
  297. }
  298. if ((extensions != null) && (extensions.Count > 0))
  299. {
  300. foreach (var extension in extensions)
  301. {
  302. result = result ^ extension.GetHashCode();
  303. }
  304. }
  305. return result;
  306. }
  307. public static CacheControlHeaderValue Parse(string input)
  308. {
  309. int index = 0;
  310. return (CacheControlHeaderValue)CacheControlHeaderParser.Parser.ParseValue(input, null, ref index);
  311. }
  312. public static bool TryParse(string input, out CacheControlHeaderValue parsedValue)
  313. {
  314. int index = 0;
  315. object output;
  316. parsedValue = null;
  317. if (CacheControlHeaderParser.Parser.TryParseValue(input, null, ref index, out output))
  318. {
  319. parsedValue = (CacheControlHeaderValue)output;
  320. return true;
  321. }
  322. return false;
  323. }
  324. internal static int GetCacheControlLength(string input, int startIndex, CacheControlHeaderValue storeValue,
  325. out CacheControlHeaderValue parsedValue)
  326. {
  327. Contract.Requires(startIndex >= 0);
  328. parsedValue = null;
  329. if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
  330. {
  331. return 0;
  332. }
  333. // Cache-Control header consists of a list of name/value pairs, where the value is optional. So use an
  334. // instance of NameValueHeaderParser to parse the string.
  335. int current = startIndex;
  336. object nameValue = null;
  337. List<NameValueHeaderValue> nameValueList = new List<NameValueHeaderValue>();
  338. while (current < input.Length)
  339. {
  340. if (!nameValueListParser.TryParseValue(input, null, ref current, out nameValue))
  341. {
  342. return 0;
  343. }
  344. nameValueList.Add(nameValue as NameValueHeaderValue);
  345. }
  346. // If we get here, we were able to successfully parse the string as list of name/value pairs. Now analyze
  347. // the name/value pairs.
  348. // Cache-Control is a header supporting lists of values. However, expose the header as an instance of
  349. // CacheControlHeaderValue. So if we already have an instance of CacheControlHeaderValue, add the values
  350. // from this string to the existing instances.
  351. CacheControlHeaderValue result = storeValue;
  352. if (result == null)
  353. {
  354. result = new CacheControlHeaderValue();
  355. }
  356. if (!TrySetCacheControlValues(result, nameValueList))
  357. {
  358. return 0;
  359. }
  360. // If we had an existing store value and we just updated that instance, return 'null' to indicate that
  361. // we don't have a new instance of CacheControlHeaderValue, but just updated an existing one. This is the
  362. // case if we have multiple 'Cache-Control' headers set in a request/response message.
  363. if (storeValue == null)
  364. {
  365. parsedValue = result;
  366. }
  367. // If we get here we successfully parsed the whole string.
  368. return input.Length - startIndex;
  369. }
  370. private static bool TrySetCacheControlValues(CacheControlHeaderValue cc,
  371. List<NameValueHeaderValue> nameValueList)
  372. {
  373. foreach (NameValueHeaderValue nameValue in nameValueList)
  374. {
  375. bool success = true;
  376. string name = nameValue.Name.ToLowerInvariant();
  377. switch (name)
  378. {
  379. case noCacheString:
  380. success = TrySetOptionalTokenList(nameValue, ref cc.noCache, ref cc.noCacheHeaders);
  381. break;
  382. case noStoreString:
  383. success = TrySetTokenOnlyValue(nameValue, ref cc.noStore);
  384. break;
  385. case maxAgeString:
  386. success = TrySetTimeSpan(nameValue, ref cc.maxAge);
  387. break;
  388. case maxStaleString:
  389. success = ((nameValue.Value == null) || TrySetTimeSpan(nameValue, ref cc.maxStaleLimit));
  390. if (success)
  391. {
  392. cc.maxStale = true;
  393. }
  394. break;
  395. case minFreshString:
  396. success = TrySetTimeSpan(nameValue, ref cc.minFresh);
  397. break;
  398. case noTransformString:
  399. success = TrySetTokenOnlyValue(nameValue, ref cc.noTransform);
  400. break;
  401. case onlyIfCachedString:
  402. success = TrySetTokenOnlyValue(nameValue, ref cc.onlyIfCached);
  403. break;
  404. case publicString:
  405. success = TrySetTokenOnlyValue(nameValue, ref cc.publicField);
  406. break;
  407. case privateString:
  408. success = TrySetOptionalTokenList(nameValue, ref cc.privateField, ref cc.privateHeaders);
  409. break;
  410. case mustRevalidateString:
  411. success = TrySetTokenOnlyValue(nameValue, ref cc.mustRevalidate);
  412. break;
  413. case proxyRevalidateString:
  414. success = TrySetTokenOnlyValue(nameValue, ref cc.proxyRevalidate);
  415. break;
  416. case sharedMaxAgeString:
  417. success = TrySetTimeSpan(nameValue, ref cc.sharedMaxAge);
  418. break;
  419. default:
  420. cc.Extensions.Add(nameValue); // success is always true
  421. break;
  422. }
  423. if (!success)
  424. {
  425. return false;
  426. }
  427. }
  428. return true;
  429. }
  430. private static bool TrySetTokenOnlyValue(NameValueHeaderValue nameValue, ref bool boolField)
  431. {
  432. if (nameValue.Value != null)
  433. {
  434. return false;
  435. }
  436. boolField = true;
  437. return true;
  438. }
  439. private static bool TrySetOptionalTokenList(NameValueHeaderValue nameValue, ref bool boolField,
  440. ref ICollection<string> destination)
  441. {
  442. Contract.Requires(nameValue != null);
  443. if (nameValue.Value == null)
  444. {
  445. boolField = true;
  446. return true;
  447. }
  448. // We need the string to be at least 3 chars long: 2x quotes and at least 1 character. Also make sure we
  449. // have a quoted string. Note that NameValueHeaderValue will never have leading/trailing whitespaces.
  450. string valueString = nameValue.Value;
  451. if ((valueString.Length < 3) || (valueString[0] != '\"') || (valueString[valueString.Length - 1] != '\"'))
  452. {
  453. return false;
  454. }
  455. // We have a quoted string. Now verify that the string contains a list of valid tokens separated by ','.
  456. int current = 1; // skip the initial '"' character.
  457. int maxLength = valueString.Length - 1; // -1 because we don't want to parse the final '"'.
  458. bool separatorFound = false;
  459. int originalValueCount = destination == null ? 0 : destination.Count;
  460. while (current < maxLength)
  461. {
  462. current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(valueString, current, true,
  463. out separatorFound);
  464. if (current == maxLength)
  465. {
  466. break;
  467. }
  468. int tokenLength = HttpRuleParser.GetTokenLength(valueString, current);
  469. if (tokenLength == 0)
  470. {
  471. // We already skipped whitespaces and separators. If we don't have a token it must be an invalid
  472. // character.
  473. return false;
  474. }
  475. if (destination == null)
  476. {
  477. destination = new ObjectCollection<string>(checkIsValidToken);
  478. }
  479. destination.Add(valueString.Substring(current, tokenLength));
  480. current = current + tokenLength;
  481. }
  482. // After parsing a valid token list, we expect to have at least one value
  483. if ((destination != null) && (destination.Count > originalValueCount))
  484. {
  485. boolField = true;
  486. return true;
  487. }
  488. return false;
  489. }
  490. private static bool TrySetTimeSpan(NameValueHeaderValue nameValue, ref TimeSpan? timeSpan)
  491. {
  492. Contract.Requires(nameValue != null);
  493. if (nameValue.Value == null)
  494. {
  495. return false;
  496. }
  497. int seconds;
  498. if (!HeaderUtilities.TryParseInt32(nameValue.Value, out seconds))
  499. {
  500. return false;
  501. }
  502. timeSpan = new TimeSpan(0, 0, seconds);
  503. return true;
  504. }
  505. private static void AppendValueIfRequired(StringBuilder sb, bool appendValue, string value)
  506. {
  507. if (appendValue)
  508. {
  509. AppendValueWithSeparatorIfRequired(sb, value);
  510. }
  511. }
  512. private static void AppendValueWithSeparatorIfRequired(StringBuilder sb, string value)
  513. {
  514. if (sb.Length > 0)
  515. {
  516. sb.Append(", ");
  517. }
  518. sb.Append(value);
  519. }
  520. private static void AppendValues(StringBuilder sb, IEnumerable<string> values)
  521. {
  522. bool first = true;
  523. foreach (string value in values)
  524. {
  525. if (first)
  526. {
  527. first = false;
  528. }
  529. else
  530. {
  531. sb.Append(", ");
  532. }
  533. sb.Append(value);
  534. }
  535. }
  536. private static void CheckIsValidToken(string item)
  537. {
  538. HeaderUtilities.CheckValidToken(item, "item");
  539. }
  540. object ICloneable.Clone()
  541. {
  542. return new CacheControlHeaderValue(this);
  543. }
  544. }
  545. }