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;
33 namespace System.Net.Http.Headers
35 public abstract class HttpHeaders : IEnumerable<KeyValuePair<string, IEnumerable<string>>>
40 // headers can hold an object of 3 kinds
41 // - simple type for parsed single values (e.g. DateTime)
42 // - CollectionHeader for multi-value headers
43 // - List<string> for not checked single values
48 public HeaderBucket (object parsed)
53 public bool HasStringValues {
55 return values != null && values.Count > 0;
59 public List<string> Values {
61 return values ?? (values = new List<string> ());
66 static readonly Dictionary<string, HeaderInfo> known_headers;
71 HeaderInfo.CreateMulti<MediaTypeWithQualityHeaderValue> ("Accept", MediaTypeWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
72 HeaderInfo.CreateMulti<StringWithQualityHeaderValue> ("Accept-Charset", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
73 HeaderInfo.CreateMulti<StringWithQualityHeaderValue> ("Accept-Encoding", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
74 HeaderInfo.CreateMulti<StringWithQualityHeaderValue> ("Accept-Language", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
75 HeaderInfo.CreateSingle<TimeSpan> ("Age", Parser.TimeSpanSeconds.TryParse, HttpHeaderKind.Response),
76 HeaderInfo.CreateSingle<AuthenticationHeaderValue> ("Authorization", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Request),
77 HeaderInfo.CreateSingle<CacheControlHeaderValue> ("Cache-Control", CacheControlHeaderValue.TryParse, HttpHeaderKind.Request),
78 HeaderInfo.CreateSingle<string> ("Connection", Parser.Token.TryParse, HttpHeaderKind.Request),
79 HeaderInfo.CreateSingle<DateTimeOffset> ("Date", Parser.DateTime.TryParse, HttpHeaderKind.Request),
80 HeaderInfo.CreateMulti<NameValueWithParametersHeaderValue> ("Expect", NameValueWithParametersHeaderValue.TryParse, HttpHeaderKind.Request),
81 HeaderInfo.CreateSingle<string> ("From", Parser.EmailAddress.TryParse, HttpHeaderKind.Request),
82 HeaderInfo.CreateSingle<Uri> ("Host", Parser.Uri.TryParse, HttpHeaderKind.Request),
83 HeaderInfo.CreateMulti<EntityTagHeaderValue> ("If-Match", EntityTagHeaderValue.TryParse, HttpHeaderKind.Request),
84 HeaderInfo.CreateSingle<DateTimeOffset> ("If-Modified-Since", Parser.DateTime.TryParse, HttpHeaderKind.Request),
85 HeaderInfo.CreateMulti<EntityTagHeaderValue> ("If-None-Match", EntityTagHeaderValue.TryParse, HttpHeaderKind.Request),
86 HeaderInfo.CreateSingle<RangeConditionHeaderValue> ("If-Range", RangeConditionHeaderValue.TryParse, HttpHeaderKind.Request),
87 HeaderInfo.CreateSingle<DateTimeOffset> ("If-Unmodified-Since", Parser.DateTime.TryParse, HttpHeaderKind.Request),
88 HeaderInfo.CreateSingle<int> ("Max-Forwards", Parser.Int.TryParse, HttpHeaderKind.Request),
89 HeaderInfo.CreateMulti<NameValueHeaderValue> ("Pragma", NameValueHeaderValue.TryParse, HttpHeaderKind.Request),
90 HeaderInfo.CreateSingle<AuthenticationHeaderValue> ("Proxy-Authorization", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Request),
91 HeaderInfo.CreateSingle<RangeHeaderValue> ("Range", RangeHeaderValue.TryParse, HttpHeaderKind.Request),
92 HeaderInfo.CreateSingle<Uri> ("Referer", Parser.Uri.TryParse, HttpHeaderKind.Request),
93 HeaderInfo.CreateMulti<TransferCodingWithQualityHeaderValue> ("TE", TransferCodingWithQualityHeaderValue.TryParse, HttpHeaderKind.Request),
94 HeaderInfo.CreateMulti<string> ("Trailer", Parser.Token.TryParse, HttpHeaderKind.Request),
95 HeaderInfo.CreateMulti<TransferCodingHeaderValue> ("Transfer-Encoding", TransferCodingHeaderValue.TryParse, HttpHeaderKind.Request),
96 HeaderInfo.CreateMulti<ProductHeaderValue> ("Upgrade", ProductHeaderValue.TryParse, HttpHeaderKind.Request),
97 HeaderInfo.CreateMulti<ProductInfoHeaderValue> ("User-Agent", ProductInfoHeaderValue.TryParse, HttpHeaderKind.Request),
98 HeaderInfo.CreateMulti<ViaHeaderValue> ("Via", ViaHeaderValue.TryParse, HttpHeaderKind.Request),
99 HeaderInfo.CreateMulti<WarningHeaderValue> ("Warning", WarningHeaderValue.TryParse, HttpHeaderKind.Request)
102 known_headers = new Dictionary<string, HeaderInfo> (StringComparer.OrdinalIgnoreCase);
103 foreach (var header in headers) {
104 known_headers.Add (header.Name, header);
108 readonly Dictionary<string, HeaderBucket> headers;
109 readonly HttpHeaderKind HeaderKind;
111 protected HttpHeaders ()
113 headers = new Dictionary<string, HeaderBucket> (StringComparer.OrdinalIgnoreCase);
116 internal HttpHeaders (HttpHeaderKind headerKind)
119 this.HeaderKind = headerKind;
122 public void Add (string name, string value)
124 Add (name, new[] { value });
127 public void Add (string name, IEnumerable<string> values)
130 throw new ArgumentNullException ("values");
132 AddInternal (name, values, CheckName (name), false);
135 internal bool AddValue (string value, HeaderInfo headerInfo, bool ignoreInvalid)
137 return AddInternal (headerInfo.Name, new [] { value }, headerInfo, ignoreInvalid);
140 bool AddInternal (string name, IEnumerable<string> values, HeaderInfo headerInfo, bool ignoreInvalid)
143 headers.TryGetValue (name, out bucket);
146 foreach (var value in values) {
147 bool first_entry = bucket == null;
149 if (headerInfo != null) {
151 if (!headerInfo.TryParse (value, out parsed_value)) {
157 throw new FormatException ();
160 if (headerInfo.AllowsMany) {
162 bucket = new HeaderBucket (headerInfo.CreateCollection (this));
164 headerInfo.AddToCollection (bucket.Parsed, parsed_value);
167 throw new FormatException ();
169 bucket = new HeaderBucket (parsed_value);
173 bucket = new HeaderBucket (null);
175 bucket.Values.Add (value ?? string.Empty);
179 headers.Add (name, bucket);
186 public void AddWithoutValidation (string name, string value)
188 AddWithoutValidation (name, new[] { value });
191 public void AddWithoutValidation (string name, IEnumerable<string> values)
194 throw new ArgumentNullException ("values");
197 AddInternal (name, values, null, true);
200 HeaderInfo CheckName (string name)
202 if (string.IsNullOrEmpty (name))
203 throw new ArgumentException ("name");
205 if (!Parser.Token.IsValid (name))
206 throw new FormatException ();
208 HeaderInfo headerInfo;
209 if (known_headers.TryGetValue (name, out headerInfo) && (headerInfo.HeaderKind & HeaderKind) == 0)
210 throw new InvalidOperationException (name);
220 public bool Contains (string name)
224 return headers.ContainsKey (name);
227 public IEnumerator<KeyValuePair<string, IEnumerable<string>>> GetEnumerator ()
229 foreach (var entry in headers) {
230 IEnumerable<string> values = null; // TODO:
231 yield return new KeyValuePair<string, IEnumerable<string>> (entry.Key, values);
235 IEnumerator IEnumerable.GetEnumerator ()
237 return GetEnumerator ();
240 public IEnumerable<string> GetValues (string name)
242 IEnumerable<string> values;
243 if (!TryGetValues (name, out values))
244 throw new InvalidOperationException ();
249 public bool Remove (string name)
252 return headers.Remove (name);
255 public bool TryGetValues (string name, out IEnumerable<string> values)
257 var header_info = CheckName (name);
260 if (!headers.TryGetValue (name, out bucket)) {
265 List<string> string_values = new List<string> ();
266 if (header_info != null && header_info.AllowsMany) {
267 header_info.AddToStringCollection (string_values, bucket.Parsed);
269 if (bucket.Parsed != null)
270 string_values.Add (bucket.Parsed.ToString ());
273 if (bucket.HasStringValues)
274 string_values.AddRange (bucket.Values);
276 values = string_values;
280 internal void AddOrRemove (string name, string value)
282 if (string.IsNullOrEmpty (value))
285 SetValue (name, value);
288 internal void AddOrRemove<T> (string name, T value) where T : class
293 SetValue (name, value);
296 internal void AddOrRemove<T> (string name, T? value) where T : struct
301 SetValue (name, value);
304 internal T GetValue<T> (string name)
308 if (!headers.TryGetValue (name, out value))
311 var res = (T) value.Parsed;
312 if (value.HasStringValues && typeof (T) == typeof (string) && (object) res == null)
313 res = (T) (object) value.Values[0];
318 internal HttpHeaderValueCollection<T> GetValues<T> (string name) where T : class
322 if (!headers.TryGetValue (name, out value)) {
323 value = new HeaderBucket (new HttpHeaderValueCollection<T> (this, known_headers [name]));
324 headers.Add (name, value);
327 return (HttpHeaderValueCollection<T>) value.Parsed;
330 void SetValue<T> (string name, T value)
332 headers[name] = new HeaderBucket (value);