HttpHeaders.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. //
  2. // HttpHeaders.cs
  3. //
  4. // Authors:
  5. // Marek Safar <[email protected]>
  6. //
  7. // Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com)
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining
  10. // a copy of this software and associated documentation files (the
  11. // "Software"), to deal in the Software without restriction, including
  12. // without limitation the rights to use, copy, modify, merge, publish,
  13. // distribute, sublicense, and/or sell copies of the Software, and to
  14. // permit persons to whom the Software is furnished to do so, subject to
  15. // the following conditions:
  16. //
  17. // The above copyright notice and this permission notice shall be
  18. // included in all copies or substantial portions of the Software.
  19. //
  20. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  21. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  22. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  23. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  24. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  25. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  26. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  27. //
  28. using System.Collections;
  29. using System.Collections.Generic;
  30. using System.Linq;
  31. namespace System.Net.Http.Headers
  32. {
  33. public abstract class HttpHeaders : IEnumerable<KeyValuePair<string, IEnumerable<string>>>
  34. {
  35. class HeaderBucket
  36. {
  37. //
  38. // headers can hold an object of 3 kinds
  39. // - simple type for parsed single values (e.g. DateTime)
  40. // - CollectionHeader for multi-value headers
  41. // - List<string> for not checked single values
  42. //
  43. public object Parsed;
  44. List<string> values;
  45. public HeaderBucket (object parsed)
  46. {
  47. this.Parsed = parsed;
  48. }
  49. public bool HasStringValues {
  50. get {
  51. return values != null && values.Count > 0;
  52. }
  53. }
  54. public List<string> Values {
  55. get {
  56. return values ?? (values = new List<string> ());
  57. }
  58. }
  59. }
  60. static readonly Dictionary<string, HeaderInfo> known_headers;
  61. static HttpHeaders ()
  62. {
  63. var headers = new[] {
  64. HeaderInfo.CreateMulti<MediaTypeWithQualityHeaderValue> ("Accept", MediaTypeWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
  65. HeaderInfo.CreateMulti<StringWithQualityHeaderValue> ("Accept-Charset", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
  66. HeaderInfo.CreateMulti<StringWithQualityHeaderValue> ("Accept-Encoding", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
  67. HeaderInfo.CreateMulti<StringWithQualityHeaderValue> ("Accept-Language", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
  68. HeaderInfo.CreateMulti<StringWithQualityHeaderValue> ("Accept-Ranges", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Response),
  69. HeaderInfo.CreateSingle<TimeSpan> ("Age", Parser.TimeSpanSeconds.TryParse, HttpHeaderKind.Response),
  70. HeaderInfo.CreateSingle<AuthenticationHeaderValue> ("Authorization", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Request),
  71. HeaderInfo.CreateSingle<CacheControlHeaderValue> ("Cache-Control", CacheControlHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
  72. HeaderInfo.CreateSingle<string> ("Connection", Parser.Token.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
  73. HeaderInfo.CreateSingle<DateTimeOffset> ("Date", Parser.DateTime.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
  74. HeaderInfo.CreateSingle<EntityTagHeaderValue> ("ETag", EntityTagHeaderValue.TryParse, HttpHeaderKind.Response),
  75. HeaderInfo.CreateMulti<NameValueWithParametersHeaderValue> ("Expect", NameValueWithParametersHeaderValue.TryParse, HttpHeaderKind.Request),
  76. HeaderInfo.CreateSingle<string> ("From", Parser.EmailAddress.TryParse, HttpHeaderKind.Request),
  77. HeaderInfo.CreateSingle<Uri> ("Host", Parser.Uri.TryParse, HttpHeaderKind.Request),
  78. HeaderInfo.CreateMulti<EntityTagHeaderValue> ("If-Match", EntityTagHeaderValue.TryParse, HttpHeaderKind.Request),
  79. HeaderInfo.CreateSingle<DateTimeOffset> ("If-Modified-Since", Parser.DateTime.TryParse, HttpHeaderKind.Request),
  80. HeaderInfo.CreateMulti<EntityTagHeaderValue> ("If-None-Match", EntityTagHeaderValue.TryParse, HttpHeaderKind.Request),
  81. HeaderInfo.CreateSingle<RangeConditionHeaderValue> ("If-Range", RangeConditionHeaderValue.TryParse, HttpHeaderKind.Request),
  82. HeaderInfo.CreateSingle<DateTimeOffset> ("If-Unmodified-Since", Parser.DateTime.TryParse, HttpHeaderKind.Request),
  83. HeaderInfo.CreateSingle<Uri> ("Location", Parser.Uri.TryParse, HttpHeaderKind.Response),
  84. HeaderInfo.CreateSingle<int> ("Max-Forwards", Parser.Int.TryParse, HttpHeaderKind.Request),
  85. HeaderInfo.CreateMulti<NameValueHeaderValue> ("Pragma", NameValueHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
  86. HeaderInfo.CreateSingle<AuthenticationHeaderValue> ("Proxy-Authenticate", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Response),
  87. HeaderInfo.CreateSingle<AuthenticationHeaderValue> ("Proxy-Authorization", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Request),
  88. HeaderInfo.CreateSingle<RangeHeaderValue> ("Range", RangeHeaderValue.TryParse, HttpHeaderKind.Request),
  89. HeaderInfo.CreateSingle<Uri> ("Referer", Parser.Uri.TryParse, HttpHeaderKind.Request),
  90. HeaderInfo.CreateSingle<RetryConditionHeaderValue> ("Retry-After", RetryConditionHeaderValue.TryParse, HttpHeaderKind.Response),
  91. HeaderInfo.CreateSingle<ProductInfoHeaderValue> ("Server", ProductInfoHeaderValue.TryParse, HttpHeaderKind.Response),
  92. HeaderInfo.CreateMulti<TransferCodingWithQualityHeaderValue> ("TE", TransferCodingWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
  93. HeaderInfo.CreateMulti<string> ("Trailer", Parser.Token.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
  94. HeaderInfo.CreateMulti<TransferCodingHeaderValue> ("Transfer-Encoding", TransferCodingHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
  95. HeaderInfo.CreateMulti<ProductHeaderValue> ("Upgrade", ProductHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
  96. HeaderInfo.CreateMulti<ProductInfoHeaderValue> ("User-Agent", ProductInfoHeaderValue.TryParse, HttpHeaderKind.Request),
  97. HeaderInfo.CreateMulti<string> ("Vary", Parser.Token.TryParse, HttpHeaderKind.Response),
  98. HeaderInfo.CreateMulti<ViaHeaderValue> ("Via", ViaHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
  99. HeaderInfo.CreateMulti<WarningHeaderValue> ("Warning", WarningHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
  100. HeaderInfo.CreateMulti<AuthenticationHeaderValue> ("WWW-Authenticate", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Response)
  101. };
  102. known_headers = new Dictionary<string, HeaderInfo> (StringComparer.OrdinalIgnoreCase);
  103. foreach (var header in headers) {
  104. known_headers.Add (header.Name, header);
  105. }
  106. }
  107. readonly Dictionary<string, HeaderBucket> headers;
  108. readonly HttpHeaderKind HeaderKind;
  109. protected HttpHeaders ()
  110. {
  111. headers = new Dictionary<string, HeaderBucket> (StringComparer.OrdinalIgnoreCase);
  112. }
  113. internal HttpHeaders (HttpHeaderKind headerKind)
  114. : this ()
  115. {
  116. this.HeaderKind = headerKind;
  117. }
  118. public void Add (string name, string value)
  119. {
  120. Add (name, new[] { value });
  121. }
  122. public void Add (string name, IEnumerable<string> values)
  123. {
  124. if (values == null)
  125. throw new ArgumentNullException ("values");
  126. AddInternal (name, values, CheckName (name), false);
  127. }
  128. internal bool AddValue (string value, HeaderInfo headerInfo, bool ignoreInvalid)
  129. {
  130. return AddInternal (headerInfo.Name, new [] { value }, headerInfo, ignoreInvalid);
  131. }
  132. bool AddInternal (string name, IEnumerable<string> values, HeaderInfo headerInfo, bool ignoreInvalid)
  133. {
  134. HeaderBucket bucket;
  135. headers.TryGetValue (name, out bucket);
  136. bool ok = true;
  137. foreach (var value in values) {
  138. bool first_entry = bucket == null;
  139. if (headerInfo != null) {
  140. object parsed_value;
  141. if (!headerInfo.TryParse (value, out parsed_value)) {
  142. if (ignoreInvalid) {
  143. ok = false;
  144. continue;
  145. }
  146. throw new FormatException ();
  147. }
  148. if (headerInfo.AllowsMany) {
  149. if (bucket == null)
  150. bucket = new HeaderBucket (headerInfo.CreateCollection (this));
  151. headerInfo.AddToCollection (bucket.Parsed, parsed_value);
  152. } else {
  153. if (bucket != null)
  154. throw new FormatException ();
  155. bucket = new HeaderBucket (parsed_value);
  156. }
  157. } else {
  158. if (bucket == null)
  159. bucket = new HeaderBucket (null);
  160. bucket.Values.Add (value ?? string.Empty);
  161. }
  162. if (first_entry) {
  163. headers.Add (name, bucket);
  164. }
  165. }
  166. return ok;
  167. }
  168. public void AddWithoutValidation (string name, string value)
  169. {
  170. AddWithoutValidation (name, new[] { value });
  171. }
  172. public void AddWithoutValidation (string name, IEnumerable<string> values)
  173. {
  174. if (values == null)
  175. throw new ArgumentNullException ("values");
  176. CheckName (name);
  177. AddInternal (name, values, null, true);
  178. }
  179. HeaderInfo CheckName (string name)
  180. {
  181. if (string.IsNullOrEmpty (name))
  182. throw new ArgumentException ("name");
  183. Parser.Token.Check (name);
  184. HeaderInfo headerInfo;
  185. if (known_headers.TryGetValue (name, out headerInfo) && (headerInfo.HeaderKind & HeaderKind) == 0)
  186. throw new InvalidOperationException (name);
  187. return headerInfo;
  188. }
  189. public void Clear ()
  190. {
  191. headers.Clear ();
  192. }
  193. public bool Contains (string name)
  194. {
  195. CheckName (name);
  196. return headers.ContainsKey (name);
  197. }
  198. public IEnumerator<KeyValuePair<string, IEnumerable<string>>> GetEnumerator ()
  199. {
  200. foreach (var entry in headers) {
  201. var bucket = headers[entry.Key];
  202. HeaderInfo headerInfo;
  203. known_headers.TryGetValue (entry.Key, out headerInfo);
  204. var svalues = GetAllHeaderValues (bucket, headerInfo);
  205. if (svalues == null)
  206. continue;
  207. yield return new KeyValuePair<string, IEnumerable<string>> (entry.Key, svalues);
  208. }
  209. }
  210. IEnumerator IEnumerable.GetEnumerator ()
  211. {
  212. return GetEnumerator ();
  213. }
  214. public IEnumerable<string> GetValues (string name)
  215. {
  216. IEnumerable<string> values;
  217. if (!TryGetValues (name, out values))
  218. throw new InvalidOperationException ();
  219. return values;
  220. }
  221. public bool Remove (string name)
  222. {
  223. CheckName (name);
  224. return headers.Remove (name);
  225. }
  226. public bool TryGetValues (string name, out IEnumerable<string> values)
  227. {
  228. var header_info = CheckName (name);
  229. HeaderBucket bucket;
  230. if (!headers.TryGetValue (name, out bucket)) {
  231. values = null;
  232. return false;
  233. }
  234. values = GetAllHeaderValues (bucket, header_info);
  235. return true;
  236. }
  237. internal void AddOrRemove (string name, string value)
  238. {
  239. if (string.IsNullOrEmpty (value))
  240. Remove (name);
  241. else
  242. SetValue (name, value);
  243. }
  244. internal void AddOrRemove<T> (string name, T value) where T : class
  245. {
  246. if (value == null)
  247. Remove (name);
  248. else
  249. SetValue (name, value);
  250. }
  251. internal void AddOrRemove<T> (string name, T? value) where T : struct
  252. {
  253. if (!value.HasValue)
  254. Remove (name);
  255. else
  256. SetValue (name, value);
  257. }
  258. List<string> GetAllHeaderValues (HeaderBucket bucket, HeaderInfo headerInfo)
  259. {
  260. List<string> string_values = null;
  261. if (headerInfo != null && headerInfo.AllowsMany) {
  262. string_values = headerInfo.ToStringCollection (bucket.Parsed);
  263. } else {
  264. if (bucket.Parsed != null) {
  265. string s = bucket.Parsed.ToString ();
  266. if (!string.IsNullOrEmpty (s)) {
  267. string_values = new List<string> ();
  268. string_values.Add (s);
  269. }
  270. }
  271. }
  272. if (bucket.HasStringValues) {
  273. if (string_values == null)
  274. string_values = new List<string> ();
  275. string_values.AddRange (bucket.Values);
  276. }
  277. return string_values;
  278. }
  279. internal T GetValue<T> (string name)
  280. {
  281. HeaderBucket value;
  282. if (!headers.TryGetValue (name, out value))
  283. return default (T);
  284. var res = (T) value.Parsed;
  285. if (value.HasStringValues && typeof (T) == typeof (string) && (object) res == null)
  286. res = (T) (object) value.Values[0];
  287. return res;
  288. }
  289. internal HttpHeaderValueCollection<T> GetValues<T> (string name) where T : class
  290. {
  291. HeaderBucket value;
  292. if (!headers.TryGetValue (name, out value)) {
  293. value = new HeaderBucket (new HttpHeaderValueCollection<T> (this, known_headers [name]));
  294. headers.Add (name, value);
  295. }
  296. if (value.HasStringValues) {
  297. var hinfo = known_headers[name];
  298. object pvalue;
  299. for (int i = 0; i < value.Values.Count; ++i) {
  300. if (!hinfo.TryParse (value.Values[i], out pvalue))
  301. continue;
  302. hinfo.AddToCollection (value, pvalue);
  303. value.Values.RemoveAt (i);
  304. --i;
  305. }
  306. }
  307. return (HttpHeaderValueCollection<T>) value.Parsed;
  308. }
  309. void SetValue<T> (string name, T value)
  310. {
  311. headers[name] = new HeaderBucket (value);
  312. }
  313. }
  314. }