HeaderValue.cs 3.6 KB

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