5 // Marek Safar <marek.safar@gmail.com>
7 // Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com)
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
29 using System.Collections;
30 using System.Collections.Generic;
34 namespace System.Net.Http.Headers
36 public abstract class HttpHeaders : IEnumerable<KeyValuePair<string, IEnumerable<string>>>
41 // headers can hold an object of 3 kinds
42 // - simple type for parsed single values (e.g. DateTime)
43 // - CollectionHeader for multi-value headers
44 // - List<string> for not checked single values
49 public readonly Func<object, string> CustomToString;
51 public HeaderBucket (object parsed, Func<object, string> converter)
54 this.CustomToString = converter;
57 public bool HasStringValues {
59 return values != null && values.Count > 0;
63 public List<string> Values {
65 return values ?? (values = new List<string> ());
72 public string ParsedToString ()
77 if (CustomToString != null)
78 return CustomToString (Parsed);
80 return Parsed.ToString ();
84 static readonly Dictionary<string, HeaderInfo> known_headers;
89 HeaderInfo.CreateMulti<MediaTypeWithQualityHeaderValue> ("Accept", MediaTypeWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
90 HeaderInfo.CreateMulti<StringWithQualityHeaderValue> ("Accept-Charset", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
91 HeaderInfo.CreateMulti<StringWithQualityHeaderValue> ("Accept-Encoding", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
92 HeaderInfo.CreateMulti<StringWithQualityHeaderValue> ("Accept-Language", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
93 HeaderInfo.CreateMulti<string> ("Accept-Ranges", CollectionParser.TryParse, HttpHeaderKind.Response),
94 HeaderInfo.CreateSingle<TimeSpan> ("Age", Parser.TimeSpanSeconds.TryParse, HttpHeaderKind.Response),
95 HeaderInfo.CreateMulti<string> ("Allow", CollectionParser.TryParse, HttpHeaderKind.Content, 0),
96 HeaderInfo.CreateSingle<AuthenticationHeaderValue> ("Authorization", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Request),
97 HeaderInfo.CreateSingle<CacheControlHeaderValue> ("Cache-Control", CacheControlHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
98 HeaderInfo.CreateMulti<string> ("Connection", CollectionParser.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
99 HeaderInfo.CreateSingle<ContentDispositionHeaderValue> ("Content-Disposition", ContentDispositionHeaderValue.TryParse, HttpHeaderKind.Content),
100 HeaderInfo.CreateMulti<string> ("Content-Encoding", CollectionParser.TryParse, HttpHeaderKind.Content),
101 HeaderInfo.CreateMulti<string> ("Content-Language", CollectionParser.TryParse, HttpHeaderKind.Content),
102 HeaderInfo.CreateSingle<long> ("Content-Length", Parser.Long.TryParse, HttpHeaderKind.Content),
103 HeaderInfo.CreateSingle<Uri> ("Content-Location", Parser.Uri.TryParse, HttpHeaderKind.Content),
104 HeaderInfo.CreateSingle<byte[]> ("Content-MD5", Parser.MD5.TryParse, HttpHeaderKind.Content),
105 HeaderInfo.CreateSingle<ContentRangeHeaderValue> ("Content-Range", ContentRangeHeaderValue.TryParse, HttpHeaderKind.Content),
106 HeaderInfo.CreateSingle<MediaTypeHeaderValue> ("Content-Type", MediaTypeHeaderValue.TryParse, HttpHeaderKind.Content),
107 HeaderInfo.CreateSingle<DateTimeOffset> ("Date", Parser.DateTime.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response, Parser.DateTime.ToString),
108 HeaderInfo.CreateSingle<EntityTagHeaderValue> ("ETag", EntityTagHeaderValue.TryParse, HttpHeaderKind.Response),
109 HeaderInfo.CreateMulti<NameValueWithParametersHeaderValue> ("Expect", NameValueWithParametersHeaderValue.TryParse, HttpHeaderKind.Request),
110 HeaderInfo.CreateSingle<DateTimeOffset> ("Expires", Parser.DateTime.TryParse, HttpHeaderKind.Content, Parser.DateTime.ToString),
111 HeaderInfo.CreateSingle<string> ("From", Parser.EmailAddress.TryParse, HttpHeaderKind.Request),
112 HeaderInfo.CreateSingle<string> ("Host", Parser.Host.TryParse, HttpHeaderKind.Request),
113 HeaderInfo.CreateMulti<EntityTagHeaderValue> ("If-Match", EntityTagHeaderValue.TryParse, HttpHeaderKind.Request),
114 HeaderInfo.CreateSingle<DateTimeOffset> ("If-Modified-Since", Parser.DateTime.TryParse, HttpHeaderKind.Request, Parser.DateTime.ToString),
115 HeaderInfo.CreateMulti<EntityTagHeaderValue> ("If-None-Match", EntityTagHeaderValue.TryParse, HttpHeaderKind.Request),
116 HeaderInfo.CreateSingle<RangeConditionHeaderValue> ("If-Range", RangeConditionHeaderValue.TryParse, HttpHeaderKind.Request),
117 HeaderInfo.CreateSingle<DateTimeOffset> ("If-Unmodified-Since", Parser.DateTime.TryParse, HttpHeaderKind.Request, Parser.DateTime.ToString),
118 HeaderInfo.CreateSingle<DateTimeOffset> ("Last-Modified", Parser.DateTime.TryParse, HttpHeaderKind.Content, Parser.DateTime.ToString),
119 HeaderInfo.CreateSingle<Uri> ("Location", Parser.Uri.TryParse, HttpHeaderKind.Response),
120 HeaderInfo.CreateSingle<int> ("Max-Forwards", Parser.Int.TryParse, HttpHeaderKind.Request),
121 HeaderInfo.CreateMulti<NameValueHeaderValue> ("Pragma", NameValueHeaderValue.TryParsePragma, HttpHeaderKind.Request | HttpHeaderKind.Response),
122 HeaderInfo.CreateMulti<AuthenticationHeaderValue> ("Proxy-Authenticate", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Response),
123 HeaderInfo.CreateSingle<AuthenticationHeaderValue> ("Proxy-Authorization", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Request),
124 HeaderInfo.CreateSingle<RangeHeaderValue> ("Range", RangeHeaderValue.TryParse, HttpHeaderKind.Request),
125 HeaderInfo.CreateSingle<Uri> ("Referer", Parser.Uri.TryParse, HttpHeaderKind.Request),
126 HeaderInfo.CreateSingle<RetryConditionHeaderValue> ("Retry-After", RetryConditionHeaderValue.TryParse, HttpHeaderKind.Response),
127 HeaderInfo.CreateMulti<ProductInfoHeaderValue> ("Server", ProductInfoHeaderValue.TryParse, HttpHeaderKind.Response, separator: " "),
128 HeaderInfo.CreateMulti<TransferCodingWithQualityHeaderValue> ("TE", TransferCodingWithQualityHeaderValue.TryParse, HttpHeaderKind.Request, 0),
129 HeaderInfo.CreateMulti<string> ("Trailer", CollectionParser.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
130 HeaderInfo.CreateMulti<TransferCodingHeaderValue> ("Transfer-Encoding", TransferCodingHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
131 HeaderInfo.CreateMulti<ProductHeaderValue> ("Upgrade", ProductHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
132 HeaderInfo.CreateMulti<ProductInfoHeaderValue> ("User-Agent", ProductInfoHeaderValue.TryParse, HttpHeaderKind.Request, separator: " "),
133 HeaderInfo.CreateMulti<string> ("Vary", CollectionParser.TryParse, HttpHeaderKind.Response),
134 HeaderInfo.CreateMulti<ViaHeaderValue> ("Via", ViaHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
135 HeaderInfo.CreateMulti<WarningHeaderValue> ("Warning", WarningHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response),
136 HeaderInfo.CreateMulti<AuthenticationHeaderValue> ("WWW-Authenticate", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Response)
139 known_headers = new Dictionary<string, HeaderInfo> (StringComparer.OrdinalIgnoreCase);
140 foreach (var header in headers) {
141 known_headers.Add (header.Name, header);
145 readonly Dictionary<string, HeaderBucket> headers;
146 readonly HttpHeaderKind HeaderKind;
148 internal bool? connectionclose, transferEncodingChunked;
150 protected HttpHeaders ()
152 headers = new Dictionary<string, HeaderBucket> (StringComparer.OrdinalIgnoreCase);
155 internal HttpHeaders (HttpHeaderKind headerKind)
158 this.HeaderKind = headerKind;
161 public void Add (string name, string value)
163 Add (name, new[] { value });
166 public void Add (string name, IEnumerable<string> values)
169 throw new ArgumentNullException ("values");
171 AddInternal (name, values, CheckName (name), false);
174 internal bool AddValue (string value, HeaderInfo headerInfo, bool ignoreInvalid)
176 return AddInternal (headerInfo.Name, new [] { value }, headerInfo, ignoreInvalid);
179 bool AddInternal (string name, IEnumerable<string> values, HeaderInfo headerInfo, bool ignoreInvalid)
182 headers.TryGetValue (name, out bucket);
185 foreach (var value in values) {
186 bool first_entry = bucket == null;
188 if (headerInfo != null) {
190 if (!headerInfo.TryParse (value, out parsed_value)) {
196 throw new FormatException ();
199 if (headerInfo.AllowsMany) {
201 bucket = new HeaderBucket (headerInfo.CreateCollection (this), headerInfo.CustomToString);
203 headerInfo.AddToCollection (bucket.Parsed, parsed_value);
206 throw new FormatException ();
208 bucket = new HeaderBucket (parsed_value, headerInfo.CustomToString);
212 bucket = new HeaderBucket (null, null);
214 bucket.Values.Add (value ?? string.Empty);
218 headers.Add (name, bucket);
225 public bool TryAddWithoutValidation (string name, string value)
227 return TryAddWithoutValidation (name, new[] { value });
230 public bool TryAddWithoutValidation (string name, IEnumerable<string> values)
233 throw new ArgumentNullException ("values");
235 HeaderInfo headerInfo;
236 if (!TryCheckName (name, out headerInfo))
239 AddInternal (name, values, null, true);
243 HeaderInfo CheckName (string name)
245 if (string.IsNullOrEmpty (name))
246 throw new ArgumentException ("name");
248 Parser.Token.Check (name);
250 HeaderInfo headerInfo;
251 if (known_headers.TryGetValue (name, out headerInfo) && (headerInfo.HeaderKind & HeaderKind) == 0) {
252 if (HeaderKind != HttpHeaderKind.None && ((HeaderKind | headerInfo.HeaderKind) & HttpHeaderKind.Content) != 0)
253 throw new InvalidOperationException (name);
261 bool TryCheckName (string name, out HeaderInfo headerInfo)
263 if (!Parser.Token.TryCheck (name)) {
268 if (known_headers.TryGetValue (name, out headerInfo) && (headerInfo.HeaderKind & HeaderKind) == 0) {
269 if (HeaderKind != HttpHeaderKind.None && ((HeaderKind | headerInfo.HeaderKind) & HttpHeaderKind.Content) != 0)
278 connectionclose = null;
279 transferEncodingChunked = null;
283 public bool Contains (string name)
287 return headers.ContainsKey (name);
290 public IEnumerator<KeyValuePair<string, IEnumerable<string>>> GetEnumerator ()
292 foreach (var entry in headers) {
293 var bucket = headers[entry.Key];
295 HeaderInfo headerInfo;
296 known_headers.TryGetValue (entry.Key, out headerInfo);
298 var svalues = GetAllHeaderValues (bucket, headerInfo);
302 yield return new KeyValuePair<string, IEnumerable<string>> (entry.Key, svalues);
306 IEnumerator IEnumerable.GetEnumerator ()
308 return GetEnumerator ();
311 public IEnumerable<string> GetValues (string name)
315 IEnumerable<string> values;
316 if (!TryGetValues (name, out values))
317 throw new InvalidOperationException ();
322 public bool Remove (string name)
325 return headers.Remove (name);
328 public bool TryGetValues (string name, out IEnumerable<string> values)
330 HeaderInfo headerInfo;
331 if (!TryCheckName (name, out headerInfo)) {
337 if (!headers.TryGetValue (name, out bucket)) {
342 values = GetAllHeaderValues (bucket, headerInfo);
346 internal static string GetSingleHeaderString (string key, IEnumerable<string> values)
348 string separator = ",";
349 HeaderInfo headerInfo;
350 if (known_headers.TryGetValue (key, out headerInfo) && headerInfo.AllowsMany)
351 separator = headerInfo.Separator;
353 var sb = new StringBuilder ();
355 foreach (var v in values) {
357 sb.Append (separator);
358 if (separator != " ")
366 return sb.ToString ();
369 public override string ToString ()
371 var sb = new StringBuilder ();
372 foreach (var entry in this) {
373 sb.Append (entry.Key);
375 sb.Append (GetSingleHeaderString (entry.Key, entry.Value));
379 return sb.ToString ();
382 internal void AddOrRemove (string name, string value)
384 if (string.IsNullOrEmpty (value))
387 SetValue (name, value);
390 internal void AddOrRemove<T> (string name, T value, Func<object, string> converter = null) where T : class
395 SetValue (name, value, converter);
398 internal void AddOrRemove<T> (string name, T? value) where T : struct
400 AddOrRemove<T> (name, value, null);
403 internal void AddOrRemove<T> (string name, T? value, Func<object, string> converter) where T : struct
408 SetValue (name, value, converter);
411 List<string> GetAllHeaderValues (HeaderBucket bucket, HeaderInfo headerInfo)
413 List<string> string_values = null;
414 if (headerInfo != null && headerInfo.AllowsMany) {
415 string_values = headerInfo.ToStringCollection (bucket.Parsed);
417 if (bucket.Parsed != null) {
418 string s = bucket.ParsedToString ();
419 if (!string.IsNullOrEmpty (s)) {
420 string_values = new List<string> ();
421 string_values.Add (s);
426 if (bucket.HasStringValues) {
427 if (string_values == null)
428 string_values = new List<string> ();
430 string_values.AddRange (bucket.Values);
433 return string_values;
436 internal static HttpHeaderKind GetKnownHeaderKind (string name)
438 if (string.IsNullOrEmpty (name))
439 throw new ArgumentException ("name");
441 HeaderInfo headerInfo;
442 if (known_headers.TryGetValue (name, out headerInfo))
443 return headerInfo.HeaderKind;
445 return HttpHeaderKind.None;
448 internal T GetValue<T> (string name)
452 if (!headers.TryGetValue (name, out value))
455 if (value.HasStringValues) {
456 var hinfo = known_headers[name];
459 if (!hinfo.TryParse (value.Values [0], out pvalue)) {
460 return typeof (T) == typeof (string) ? (T) (object) value.Values[0] : default (T);
463 value.Parsed = pvalue;
467 return (T) value.Parsed;
470 internal HttpHeaderValueCollection<T> GetValues<T> (string name) where T : class
474 if (!headers.TryGetValue (name, out value)) {
475 var hinfo = known_headers[name];
476 value = new HeaderBucket (new HttpHeaderValueCollection<T> (this, hinfo), hinfo.CustomToString);
477 headers.Add (name, value);
480 if (value.HasStringValues) {
481 var hinfo = known_headers[name];
482 if (value.Parsed == null)
483 value.Parsed = hinfo.CreateCollection (this);
486 for (int i = 0; i < value.Values.Count; ++i) {
487 if (!hinfo.TryParse (value.Values[i], out pvalue))
490 hinfo.AddToCollection (value.Parsed, pvalue);
491 value.Values.RemoveAt (i);
496 return (HttpHeaderValueCollection<T>) value.Parsed;
499 internal void SetValue<T> (string name, T value, Func<object, string> toStringConverter = null)
501 headers[name] = new HeaderBucket (value, toStringConverter);