9dcb1b44d8fa180aca172bda55d4293c2746b111
[mono.git] / mcs / class / System.Net.Http / System.Net.Http.Headers / HttpHeaders.cs
1 //
2 // HttpHeaders.cs
3 //
4 // Authors:
5 //      Marek Safar  <marek.safar@gmail.com>
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
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Linq;
32
33 namespace System.Net.Http.Headers
34 {
35         public abstract class HttpHeaders : IEnumerable<KeyValuePair<string, IEnumerable<string>>>
36         {
37                 class HeaderBucket
38                 {
39                         //
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
44                         //
45                         public object Parsed;
46                         List<string> values;
47
48                         public HeaderBucket (object parsed)
49                         {
50                                 this.Parsed = parsed;
51                         }
52
53                         public bool HasStringValues {
54                                 get {
55                                         return values != null && values.Count > 0;
56                                 }
57                         }
58
59                         public List<string> Values {
60                                 get {
61                                         return values ?? (values = new List<string> ());
62                                 }
63                         }
64                 }
65
66                 static readonly Dictionary<string, HeaderInfo> known_headers;
67
68                 static HttpHeaders ()
69                 {
70                         var headers = new[] {
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)
100                         };
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
108                 readonly Dictionary<string, HeaderBucket> headers;
109                 readonly HttpHeaderKind HeaderKind;
110
111                 protected HttpHeaders ()
112                 {
113                         headers = new Dictionary<string, HeaderBucket> (StringComparer.OrdinalIgnoreCase);
114                 }
115
116                 internal HttpHeaders (HttpHeaderKind headerKind)
117                         : this ()
118                 {
119                         this.HeaderKind = headerKind;
120                 }
121
122                 public void Add (string name, string value)
123                 {
124                         Add (name, new[] { value });
125                 }
126
127                 public void Add (string name, IEnumerable<string> values)
128                 {
129                         if (values == null)
130                                 throw new ArgumentNullException ("values");
131
132                         AddInternal (name, values, CheckName (name), false);
133                 }
134
135                 internal bool AddValue (string value, HeaderInfo headerInfo, bool ignoreInvalid)
136                 {
137                         return AddInternal (headerInfo.Name, new [] { value }, headerInfo, ignoreInvalid);
138                 }
139
140                 bool AddInternal (string name, IEnumerable<string> values, HeaderInfo headerInfo, bool ignoreInvalid)
141                 {
142                         HeaderBucket bucket;
143                         headers.TryGetValue (name, out bucket);
144                         bool ok = true;
145
146                         foreach (var value in values) {
147                                 bool first_entry = bucket == null;
148
149                                 if (headerInfo != null) {
150                                         object parsed_value;
151                                         if (!headerInfo.TryParse (value, out parsed_value)) {
152                                                 if (ignoreInvalid) {
153                                                         ok = false;
154                                                         continue;
155                                                 }
156
157                                                 throw new FormatException ();
158                                         }
159
160                                         if (headerInfo.AllowsMany) {
161                                                 if (bucket == null)
162                                                         bucket = new HeaderBucket (headerInfo.CreateCollection (this));
163
164                                                 headerInfo.AddToCollection (bucket.Parsed, parsed_value);
165                                         } else {
166                                                 if (bucket != null)
167                                                         throw new FormatException ();
168
169                                                 bucket = new HeaderBucket (parsed_value);
170                                         }
171                                 } else {
172                                         if (bucket == null)
173                                                 bucket = new HeaderBucket (null);
174
175                                         bucket.Values.Add (value ?? string.Empty);
176                                 }
177
178                                 if (first_entry) {
179                                         headers.Add (name, bucket);
180                                 }
181                         }
182
183                         return ok;
184                 }
185
186                 public void AddWithoutValidation (string name, string value)
187                 {
188                         AddWithoutValidation (name, new[] { value });
189                 }
190
191                 public void AddWithoutValidation (string name, IEnumerable<string> values)
192                 {
193                         if (values == null)
194                                 throw new ArgumentNullException ("values");
195
196                         CheckName (name);
197                         AddInternal (name, values, null, true);
198                 }
199
200                 HeaderInfo CheckName (string name)
201                 {
202                         if (string.IsNullOrEmpty (name))
203                                 throw new ArgumentException ("name");
204
205                         if (!Parser.Token.IsValid (name))
206                                 throw new FormatException ();
207
208                         HeaderInfo headerInfo;
209                         if (known_headers.TryGetValue (name, out headerInfo) && (headerInfo.HeaderKind & HeaderKind) == 0)
210                                 throw new InvalidOperationException (name);
211
212                         return headerInfo;
213                 }
214
215                 public void Clear ()
216                 {
217                         headers.Clear ();
218                 }
219
220                 public bool Contains (string name)
221                 {
222                         CheckName (name);
223
224                         return headers.ContainsKey (name);
225                 }
226
227                 public IEnumerator<KeyValuePair<string, IEnumerable<string>>> GetEnumerator ()
228                 {
229                         foreach (var entry in headers) {
230                                 IEnumerable<string> values = null; // TODO:
231                                 yield return new KeyValuePair<string, IEnumerable<string>> (entry.Key, values);
232                         }
233                 }
234
235                 IEnumerator IEnumerable.GetEnumerator ()
236                 {
237                         return GetEnumerator ();
238                 }
239
240                 public IEnumerable<string> GetValues (string name)
241                 {
242                         IEnumerable<string> values;
243                         if (!TryGetValues (name, out values))
244                                 throw new InvalidOperationException ();
245
246                         return values;
247                 }
248
249                 public bool Remove (string name)
250                 {
251                         CheckName (name);
252                         return headers.Remove (name);
253                 }
254
255                 public bool TryGetValues (string name, out IEnumerable<string> values)
256                 {
257                         var header_info = CheckName (name);
258
259                         HeaderBucket bucket;
260                         if (!headers.TryGetValue (name, out bucket)) {
261                                 values = null;
262                                 return false;
263                         }
264
265                         List<string> string_values = new List<string> ();
266                         if (header_info != null && header_info.AllowsMany) {
267                                 header_info.AddToStringCollection (string_values, bucket.Parsed);
268                         } else {
269                                 if (bucket.Parsed != null)
270                                         string_values.Add (bucket.Parsed.ToString ());
271                         }
272
273                         if (bucket.HasStringValues)
274                                 string_values.AddRange (bucket.Values);
275
276                         values = string_values;
277                         return true;
278                 }
279
280                 internal void AddOrRemove (string name, string value)
281                 {
282                         if (string.IsNullOrEmpty (value))
283                                 Remove (name);
284                         else
285                                 SetValue (name, value);
286                 }
287
288                 internal void AddOrRemove<T> (string name, T value) where T : class
289                 {
290                         if (value == null)
291                                 Remove (name);
292                         else
293                                 SetValue (name, value);
294                 }
295
296                 internal void AddOrRemove<T> (string name, T? value) where T : struct
297                 {
298                         if (!value.HasValue)
299                                 Remove (name);
300                         else
301                                 SetValue (name, value);
302                 }
303
304                 internal T GetValue<T> (string name)
305                 {
306                         HeaderBucket value;
307
308                         if (!headers.TryGetValue (name, out value))
309                                 return default (T);
310
311                         var res = (T) value.Parsed;
312                         if (value.HasStringValues && typeof (T) == typeof (string) && (object) res == null)
313                                 res = (T) (object) value.Values[0];
314
315                         return res;
316                 }
317
318                 internal HttpHeaderValueCollection<T> GetValues<T> (string name) where T : class
319                 {
320                         HeaderBucket value;
321
322                         if (!headers.TryGetValue (name, out value)) {
323                                 value = new HeaderBucket (new HttpHeaderValueCollection<T> (this, known_headers [name]));
324                                 headers.Add (name, value);
325                         }
326
327                         return (HttpHeaderValueCollection<T>) value.Parsed;
328                 }
329
330                 void SetValue<T> (string name, T value)
331                 {
332                         headers[name] = new HeaderBucket (value);
333                 }
334         }
335 }