Merge pull request #1899 from saper/resgencond
[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 using System.Text;
33
34 namespace System.Net.Http.Headers
35 {
36         public abstract class HttpHeaders : IEnumerable<KeyValuePair<string, IEnumerable<string>>>
37         {
38                 class HeaderBucket
39                 {
40                         //
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
45                         //
46                         public object Parsed;
47                         List<string> values;
48
49                         public readonly Func<object, string> CustomToString;
50
51                         public HeaderBucket (object parsed, Func<object, string> converter)
52                         {
53                                 this.Parsed = parsed;
54                                 this.CustomToString = converter;
55                         }
56
57                         public bool HasStringValues {
58                                 get {
59                                         return values != null && values.Count > 0;
60                                 }
61                         }
62
63                         public List<string> Values {
64                                 get {
65                                         return values ?? (values = new List<string> ());
66                                 }
67                                 set {
68                                         values = value;
69                                 }
70                         }
71
72                         public string ParsedToString ()
73                         {
74                                 if (Parsed == null)
75                                         return null;
76
77                                 if (CustomToString != null)
78                                         return CustomToString (Parsed);
79
80                                 return Parsed.ToString ();
81                         }
82                 }
83
84                 static readonly Dictionary<string, HeaderInfo> known_headers;
85
86                 static HttpHeaders ()
87                 {
88                         var headers = new[] {
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)
137                         };
138
139                         known_headers = new Dictionary<string, HeaderInfo> (StringComparer.OrdinalIgnoreCase);
140                         foreach (var header in headers) {
141                                 known_headers.Add (header.Name, header);
142                         }
143                 }
144
145                 readonly Dictionary<string, HeaderBucket> headers;
146                 readonly HttpHeaderKind HeaderKind;
147
148                 internal bool? connectionclose, transferEncodingChunked;
149
150                 protected HttpHeaders ()
151                 {
152                         headers = new Dictionary<string, HeaderBucket> (StringComparer.OrdinalIgnoreCase);
153                 }
154
155                 internal HttpHeaders (HttpHeaderKind headerKind)
156                         : this ()
157                 {
158                         this.HeaderKind = headerKind;
159                 }
160
161                 public void Add (string name, string value)
162                 {
163                         Add (name, new[] { value });
164                 }
165
166                 public void Add (string name, IEnumerable<string> values)
167                 {
168                         if (values == null)
169                                 throw new ArgumentNullException ("values");
170
171                         AddInternal (name, values, CheckName (name), false);
172                 }
173
174                 internal bool AddValue (string value, HeaderInfo headerInfo, bool ignoreInvalid)
175                 {
176                         return AddInternal (headerInfo.Name, new [] { value }, headerInfo, ignoreInvalid);
177                 }
178
179                 bool AddInternal (string name, IEnumerable<string> values, HeaderInfo headerInfo, bool ignoreInvalid)
180                 {
181                         HeaderBucket bucket;
182                         headers.TryGetValue (name, out bucket);
183                         bool ok = true;
184
185                         foreach (var value in values) {
186                                 bool first_entry = bucket == null;
187
188                                 if (headerInfo != null) {
189                                         object parsed_value;
190                                         if (!headerInfo.TryParse (value, out parsed_value)) {
191                                                 if (ignoreInvalid) {
192                                                         ok = false;
193                                                         continue;
194                                                 }
195
196                                                 throw new FormatException ();
197                                         }
198
199                                         if (headerInfo.AllowsMany) {
200                                                 if (bucket == null)
201                                                         bucket = new HeaderBucket (headerInfo.CreateCollection (this), headerInfo.CustomToString);
202
203                                                 headerInfo.AddToCollection (bucket.Parsed, parsed_value);
204                                         } else {
205                                                 if (bucket != null)
206                                                         throw new FormatException ();
207
208                                                 bucket = new HeaderBucket (parsed_value, headerInfo.CustomToString);
209                                         }
210                                 } else {
211                                         if (bucket == null)
212                                                 bucket = new HeaderBucket (null, null);
213
214                                         bucket.Values.Add (value ?? string.Empty);
215                                 }
216
217                                 if (first_entry) {
218                                         headers.Add (name, bucket);
219                                 }
220                         }
221
222                         return ok;
223                 }
224
225                 public bool TryAddWithoutValidation (string name, string value)
226                 {
227                         return TryAddWithoutValidation (name, new[] { value });
228                 }
229
230                 public bool TryAddWithoutValidation (string name, IEnumerable<string> values)
231                 {
232                         if (values == null)
233                                 throw new ArgumentNullException ("values");
234
235                         HeaderInfo headerInfo;
236                         if (!TryCheckName (name, out headerInfo))
237                                 return false;
238
239                         AddInternal (name, values, null, true);
240                         return true;
241                 }
242
243                 HeaderInfo CheckName (string name)
244                 {
245                         if (string.IsNullOrEmpty (name))
246                                 throw new ArgumentException ("name");
247
248                         Parser.Token.Check (name);
249
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);
254
255                                 return null;
256                         }
257
258                         return headerInfo;
259                 }
260
261                 bool TryCheckName (string name, out HeaderInfo headerInfo)
262                 {
263                         if (!Parser.Token.TryCheck (name)) {
264                                 headerInfo = null;
265                                 return false;
266                         }
267
268                         if (known_headers.TryGetValue (name, out headerInfo) && (headerInfo.HeaderKind & HeaderKind) == 0) {
269                                 if (HeaderKind != HttpHeaderKind.None && ((HeaderKind | headerInfo.HeaderKind) & HttpHeaderKind.Content) != 0)
270                                         return false;
271                         }
272
273                         return true;
274                 }
275
276                 public void Clear ()
277                 {
278                         connectionclose = null;
279                         transferEncodingChunked = null;
280                         headers.Clear ();
281                 }
282
283                 public bool Contains (string name)
284                 {
285                         CheckName (name);
286
287                         return headers.ContainsKey (name);
288                 }
289
290                 public IEnumerator<KeyValuePair<string, IEnumerable<string>>> GetEnumerator ()
291                 {
292                         foreach (var entry in headers) {
293                                 var bucket = headers[entry.Key];
294
295                                 HeaderInfo headerInfo;
296                                 known_headers.TryGetValue (entry.Key, out headerInfo);
297
298                                 var svalues = GetAllHeaderValues (bucket, headerInfo);
299                                 if (svalues == null)
300                                         continue;
301
302                                 yield return new KeyValuePair<string, IEnumerable<string>> (entry.Key, svalues);
303                         }
304                 }
305
306                 IEnumerator IEnumerable.GetEnumerator ()
307                 {
308                         return GetEnumerator ();
309                 }
310
311                 public IEnumerable<string> GetValues (string name)
312                 {
313                         CheckName (name);
314
315                         IEnumerable<string> values;
316                         if (!TryGetValues (name, out values))
317                                 throw new InvalidOperationException ();
318
319                         return values;
320                 }
321
322                 public bool Remove (string name)
323                 {
324                         CheckName (name);
325                         return headers.Remove (name);
326                 }
327
328                 public bool TryGetValues (string name, out IEnumerable<string> values)
329                 {
330                         HeaderInfo headerInfo;
331                         if (!TryCheckName (name, out headerInfo)) {
332                                 values = null;
333                                 return false;
334                         }
335
336                         HeaderBucket bucket;
337                         if (!headers.TryGetValue (name, out bucket)) {
338                                 values = null;
339                                 return false;
340                         }
341
342                         values = GetAllHeaderValues (bucket, headerInfo);
343                         return true;
344                 }
345
346                 internal static string GetSingleHeaderString (string key, IEnumerable<string> values)
347                 {
348                         string separator = ",";
349                         HeaderInfo headerInfo;
350                         if (known_headers.TryGetValue (key, out headerInfo) && headerInfo.AllowsMany)
351                                 separator = headerInfo.Separator;
352
353                         var sb = new StringBuilder ();
354                         bool first = true;
355                         foreach (var v in values) {
356                                 if (!first) {
357                                         sb.Append (separator);
358                                         if (separator != " ")
359                                                 sb.Append (" ");
360                                 }
361
362                                 sb.Append (v);
363                                 first = false;
364                         }
365
366                         // Return null for empty values list
367                         if (first)
368                                 return null;
369
370                         return sb.ToString ();
371                 }
372
373                 public override string ToString ()
374                 {
375                         var sb = new StringBuilder ();
376                         foreach (var entry in this) {
377                                 sb.Append (entry.Key);
378                                 sb.Append (": ");
379                                 sb.Append (GetSingleHeaderString (entry.Key, entry.Value));
380                                 sb.Append ("\r\n");
381                         }
382
383                         return sb.ToString ();
384                 }
385
386                 internal void AddOrRemove (string name, string value)
387                 {
388                         if (string.IsNullOrEmpty (value))
389                                 Remove (name);
390                         else
391                                 SetValue (name, value);
392                 }
393
394                 internal void AddOrRemove<T> (string name, T value, Func<object, string> converter = null) where T : class
395                 {
396                         if (value == null)
397                                 Remove (name);
398                         else
399                                 SetValue (name, value, converter);
400                 }
401
402                 internal void AddOrRemove<T> (string name, T? value) where T : struct
403                 {
404                         AddOrRemove<T> (name, value, null);
405                 }
406
407                 internal void AddOrRemove<T> (string name, T? value, Func<object, string> converter) where T : struct
408                 {
409                         if (!value.HasValue)
410                                 Remove (name);
411                         else
412                                 SetValue (name, value, converter);
413                 }
414
415                 List<string> GetAllHeaderValues (HeaderBucket bucket, HeaderInfo headerInfo)
416                 {
417                         List<string> string_values = null;
418                         if (headerInfo != null && headerInfo.AllowsMany) {
419                                 string_values = headerInfo.ToStringCollection (bucket.Parsed);
420                         } else {
421                                 if (bucket.Parsed != null) {
422                                         string s = bucket.ParsedToString ();
423                                         if (!string.IsNullOrEmpty (s)) {
424                                                 string_values = new List<string> ();
425                                                 string_values.Add (s);
426                                         }
427                                 }
428                         }
429
430                         if (bucket.HasStringValues) {
431                                 if (string_values == null)
432                                         string_values = new List<string> ();
433
434                                 string_values.AddRange (bucket.Values);
435                         }
436
437                         return string_values;
438                 }
439
440                 internal static HttpHeaderKind GetKnownHeaderKind (string name)
441                 {
442                         if (string.IsNullOrEmpty (name))
443                                 throw new ArgumentException ("name");
444
445                         HeaderInfo headerInfo;
446                         if (known_headers.TryGetValue (name, out headerInfo))
447                                 return headerInfo.HeaderKind;
448
449                         return HttpHeaderKind.None;
450                 }
451
452                 internal T GetValue<T> (string name)
453                 {
454                         HeaderBucket value;
455
456                         if (!headers.TryGetValue (name, out value))
457                                 return default (T);
458
459                         if (value.HasStringValues) {
460                                 var hinfo = known_headers[name];
461
462                                 object pvalue;
463                                 if (!hinfo.TryParse (value.Values [0], out pvalue)) {
464                                         return typeof (T) == typeof (string) ? (T) (object) value.Values[0] : default (T);
465                                 }
466
467                                 value.Parsed = pvalue;
468                                 value.Values = null;
469                         }
470
471                         return (T) value.Parsed;
472                 }
473
474                 internal HttpHeaderValueCollection<T> GetValues<T> (string name) where T : class
475                 {
476                         HeaderBucket value;
477
478                         if (!headers.TryGetValue (name, out value)) {
479                                 var hinfo = known_headers[name];
480                                 value = new HeaderBucket (new HttpHeaderValueCollection<T> (this, hinfo), hinfo.CustomToString);
481                                 headers.Add (name, value);
482                         }
483
484                         if (value.HasStringValues) {
485                                 var hinfo = known_headers[name];
486                                 if (value.Parsed == null)
487                                         value.Parsed = hinfo.CreateCollection (this);
488
489                                 object pvalue;
490                                 for (int i = 0; i < value.Values.Count; ++i) {
491                                         if (!hinfo.TryParse (value.Values[i], out pvalue))
492                                                 continue;
493
494                                         hinfo.AddToCollection (value.Parsed, pvalue);
495                                         value.Values.RemoveAt (i);
496                                         --i;
497                                 }
498                         }
499
500                         return (HttpHeaderValueCollection<T>) value.Parsed;
501                 }
502
503                 internal void SetValue<T> (string name, T value, Func<object, string> toStringConverter = null)
504                 {
505                         headers[name] = new HeaderBucket (value, toStringConverter);
506                 }
507         }
508 }