// // HttpHeaders.cs // // Authors: // Marek Safar // // Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System.Collections; using System.Collections.Generic; using System.Linq; namespace System.Net.Http.Headers { public abstract class HttpHeaders : IEnumerable>> { class HeaderBucket { // // headers can hold an object of 3 kinds // - simple type for parsed single values (e.g. DateTime) // - CollectionHeader for multi-value headers // - List for not checked single values // public object Parsed; List values; public HeaderBucket (object parsed) { this.Parsed = parsed; } public bool HasStringValues { get { return values != null && values.Count > 0; } } public bool IsEmpty { get { return Parsed == null && (values == null || values.Count == 0); } } public List Values { get { return values ?? (values = new List ()); } } } static readonly Dictionary known_headers; static HttpHeaders () { var headers = new[] { HeaderInfo.CreateMulti ("Accept", MediaTypeWithQualityHeaderValue.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateMulti ("Accept-Charset", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateMulti ("Accept-Encoding", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateMulti ("Accept-Language", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateMulti ("Accept-Ranges", StringWithQualityHeaderValue.TryParse, HttpHeaderKind.Response), HeaderInfo.CreateSingle ("Age", Parser.TimeSpanSeconds.TryParse, HttpHeaderKind.Response), HeaderInfo.CreateSingle ("Authorization", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateSingle ("Cache-Control", CacheControlHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response), HeaderInfo.CreateSingle ("Connection", Parser.Token.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response), HeaderInfo.CreateSingle ("Date", Parser.DateTime.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response), HeaderInfo.CreateSingle ("ETag", EntityTagHeaderValue.TryParse, HttpHeaderKind.Response), HeaderInfo.CreateMulti ("Expect", NameValueWithParametersHeaderValue.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateSingle ("From", Parser.EmailAddress.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateSingle ("Host", Parser.Uri.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateMulti ("If-Match", EntityTagHeaderValue.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateSingle ("If-Modified-Since", Parser.DateTime.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateMulti ("If-None-Match", EntityTagHeaderValue.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateSingle ("If-Range", RangeConditionHeaderValue.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateSingle ("If-Unmodified-Since", Parser.DateTime.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateSingle ("Location", Parser.Uri.TryParse, HttpHeaderKind.Response), HeaderInfo.CreateSingle ("Max-Forwards", Parser.Int.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateMulti ("Pragma", NameValueHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response), HeaderInfo.CreateSingle ("Proxy-Authenticate", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Response), HeaderInfo.CreateSingle ("Proxy-Authorization", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateSingle ("Range", RangeHeaderValue.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateSingle ("Referer", Parser.Uri.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateSingle ("Retry-After", RetryConditionHeaderValue.TryParse, HttpHeaderKind.Response), HeaderInfo.CreateSingle ("Server", ProductInfoHeaderValue.TryParse, HttpHeaderKind.Response), HeaderInfo.CreateMulti ("TE", TransferCodingWithQualityHeaderValue.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateMulti ("Trailer", Parser.Token.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response), HeaderInfo.CreateMulti ("Transfer-Encoding", TransferCodingHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response), HeaderInfo.CreateMulti ("Upgrade", ProductHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response), HeaderInfo.CreateMulti ("User-Agent", ProductInfoHeaderValue.TryParse, HttpHeaderKind.Request), HeaderInfo.CreateMulti ("Vary", Parser.Token.TryParse, HttpHeaderKind.Response), HeaderInfo.CreateMulti ("Via", ViaHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response), HeaderInfo.CreateMulti ("Warning", WarningHeaderValue.TryParse, HttpHeaderKind.Request | HttpHeaderKind.Response), HeaderInfo.CreateMulti ("WWW-Authenticate", AuthenticationHeaderValue.TryParse, HttpHeaderKind.Response) }; known_headers = new Dictionary (StringComparer.OrdinalIgnoreCase); foreach (var header in headers) { known_headers.Add (header.Name, header); } } readonly Dictionary headers; readonly HttpHeaderKind HeaderKind; protected HttpHeaders () { headers = new Dictionary (StringComparer.OrdinalIgnoreCase); } internal HttpHeaders (HttpHeaderKind headerKind) : this () { this.HeaderKind = headerKind; } public void Add (string name, string value) { Add (name, new[] { value }); } public void Add (string name, IEnumerable values) { if (values == null) throw new ArgumentNullException ("values"); AddInternal (name, values, CheckName (name), false); } internal bool AddValue (string value, HeaderInfo headerInfo, bool ignoreInvalid) { return AddInternal (headerInfo.Name, new [] { value }, headerInfo, ignoreInvalid); } bool AddInternal (string name, IEnumerable values, HeaderInfo headerInfo, bool ignoreInvalid) { HeaderBucket bucket; headers.TryGetValue (name, out bucket); bool ok = true; foreach (var value in values) { bool first_entry = bucket == null; if (headerInfo != null) { object parsed_value; if (!headerInfo.TryParse (value, out parsed_value)) { if (ignoreInvalid) { ok = false; continue; } throw new FormatException (); } if (headerInfo.AllowsMany) { if (bucket == null) bucket = new HeaderBucket (headerInfo.CreateCollection (this)); headerInfo.AddToCollection (bucket.Parsed, parsed_value); } else { if (bucket != null) throw new FormatException (); bucket = new HeaderBucket (parsed_value); } } else { if (bucket == null) bucket = new HeaderBucket (null); bucket.Values.Add (value ?? string.Empty); } if (first_entry) { headers.Add (name, bucket); } } return ok; } public void AddWithoutValidation (string name, string value) { AddWithoutValidation (name, new[] { value }); } public void AddWithoutValidation (string name, IEnumerable values) { if (values == null) throw new ArgumentNullException ("values"); CheckName (name); AddInternal (name, values, null, true); } HeaderInfo CheckName (string name) { if (string.IsNullOrEmpty (name)) throw new ArgumentException ("name"); if (!Parser.Token.IsValid (name)) throw new FormatException (); HeaderInfo headerInfo; if (known_headers.TryGetValue (name, out headerInfo) && (headerInfo.HeaderKind & HeaderKind) == 0) throw new InvalidOperationException (name); return headerInfo; } public void Clear () { headers.Clear (); } public bool Contains (string name) { CheckName (name); return headers.ContainsKey (name); } public IEnumerator>> GetEnumerator () { foreach (var entry in headers) { var bucket = headers[entry.Key]; if (bucket.IsEmpty) continue; HeaderInfo headerInfo; known_headers.TryGetValue (entry.Key, out headerInfo); yield return new KeyValuePair> ( entry.Key, GetAllHeaderValues (bucket, headerInfo)); } } IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator (); } public IEnumerable GetValues (string name) { IEnumerable values; if (!TryGetValues (name, out values)) throw new InvalidOperationException (); return values; } public bool Remove (string name) { CheckName (name); return headers.Remove (name); } public bool TryGetValues (string name, out IEnumerable values) { var header_info = CheckName (name); HeaderBucket bucket; if (!headers.TryGetValue (name, out bucket)) { values = null; return false; } values = GetAllHeaderValues (bucket, header_info); return true; } internal void AddOrRemove (string name, string value) { if (string.IsNullOrEmpty (value)) Remove (name); else SetValue (name, value); } internal void AddOrRemove (string name, T value) where T : class { if (value == null) Remove (name); else SetValue (name, value); } internal void AddOrRemove (string name, T? value) where T : struct { if (!value.HasValue) Remove (name); else SetValue (name, value); } List GetAllHeaderValues (HeaderBucket bucket, HeaderInfo headerInfo) { List string_values = new List (); if (headerInfo != null && headerInfo.AllowsMany) { headerInfo.AddToStringCollection (string_values, bucket.Parsed); } else { if (bucket.Parsed != null) string_values.Add (bucket.Parsed.ToString ()); } if (bucket.HasStringValues) string_values.AddRange (bucket.Values); return string_values; } internal T GetValue (string name) { HeaderBucket value; if (!headers.TryGetValue (name, out value)) return default (T); var res = (T) value.Parsed; if (value.HasStringValues && typeof (T) == typeof (string) && (object) res == null) res = (T) (object) value.Values[0]; return res; } internal HttpHeaderValueCollection GetValues (string name) where T : class { HeaderBucket value; if (!headers.TryGetValue (name, out value)) { value = new HeaderBucket (new HttpHeaderValueCollection (this, known_headers [name])); headers.Add (name, value); } return (HttpHeaderValueCollection) value.Parsed; } void SetValue (string name, T value) { headers[name] = new HeaderBucket (value); } } }