HeaderValue.cs 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Best.HTTP.Shared.PlatformSupport.Text;
  6. namespace Best.HTTP.Shared.Extensions
  7. {
  8. /// <summary>
  9. /// Used in string parsers. Its Value is optional.
  10. /// </summary>
  11. public sealed class HeaderValue
  12. {
  13. #region Public Properties
  14. public string Key { get; set; }
  15. public string Value { get; set; }
  16. public List<HeaderValue> Options { get; set; }
  17. public bool HasValue { get { return !string.IsNullOrEmpty(this.Value); } }
  18. #endregion
  19. #region Constructors
  20. public HeaderValue()
  21. { }
  22. public HeaderValue(string key)
  23. {
  24. this.Key = key;
  25. }
  26. #endregion
  27. #region Public Helper Functions
  28. public void Parse(string headerStr, ref int pos)
  29. {
  30. ParseImplementation(headerStr, ref pos, true);
  31. }
  32. public bool TryGetOption(string key, out HeaderValue option)
  33. {
  34. option = null;
  35. if (Options == null || Options.Count == 0)
  36. return false;
  37. for (int i = 0; i < Options.Count; ++i)
  38. if (String.Equals(Options[i].Key, key, StringComparison.OrdinalIgnoreCase))
  39. {
  40. option = Options[i];
  41. return true;
  42. }
  43. return false;
  44. }
  45. #endregion
  46. #region Private Helper Functions
  47. private void ParseImplementation(string headerStr, ref int pos, bool isOptionIsAnOption)
  48. {
  49. // According to https://tools.ietf.org/html/rfc7234#section-5.2.1.1
  50. // Max-Age has a form "max-age=5", but some (imgur.com specifically) sends it as "max-age:5"
  51. string key = headerStr.Read(ref pos, (ch) => ch != ';' && ch != '=' && ch != ':' && ch != ',', true);
  52. this.Key = key;
  53. char? skippedChar = headerStr.Peek(pos - 1);
  54. bool isValue = skippedChar == '=' || skippedChar == ':';
  55. bool isOption = isOptionIsAnOption && skippedChar == ';';
  56. while ((skippedChar != null && isValue || isOption) && pos < headerStr.Length)
  57. {
  58. if (isValue)
  59. {
  60. string value = headerStr.ReadPossibleQuotedText(ref pos);
  61. this.Value = value;
  62. }
  63. else if (isOption)
  64. {
  65. HeaderValue option = new HeaderValue();
  66. option.ParseImplementation(headerStr, ref pos, false);
  67. if (this.Options == null)
  68. this.Options = new List<HeaderValue>();
  69. this.Options.Add(option);
  70. }
  71. if (!isOptionIsAnOption)
  72. return;
  73. skippedChar = headerStr.Peek(pos - 1);
  74. isValue = skippedChar == '=';
  75. isOption = isOptionIsAnOption && skippedChar == ';';
  76. }
  77. }
  78. #endregion
  79. #region Overrides
  80. public override string ToString()
  81. {
  82. if (this.Options != null && this.Options.Count > 0)
  83. {
  84. StringBuilder sb = StringBuilderPool.Get(4); //new StringBuilder(4);
  85. sb.Append(Key);
  86. sb.Append("=");
  87. sb.Append(Value);
  88. foreach(var option in Options)
  89. {
  90. sb.Append(";");
  91. sb.Append(option.ToString());
  92. }
  93. return StringBuilderPool.ReleaseAndGrab(sb);
  94. }
  95. else if (!string.IsNullOrEmpty(Value))
  96. return Key + '=' + Value;
  97. else
  98. return Key;
  99. }
  100. #endregion
  101. }
  102. }