diff --git a/websocket-sharp/Net/WebHeaderCollection.cs b/websocket-sharp/Net/WebHeaderCollection.cs index ece5a592..47c4c259 100644 --- a/websocket-sharp/Net/WebHeaderCollection.cs +++ b/websocket-sharp/Net/WebHeaderCollection.cs @@ -1,36 +1,44 @@ -// -// WebHeaderCollection.cs -// Copied from System.Net.WebHeaderCollection.cs -// -// Authors: -// Lawrence Pit (loz@cable.a2000.nl) -// Gonzalo Paniagua Javier (gonzalo@ximian.com) -// Miguel de Icaza (miguel@novell.com) -// sta (sta.blockhead@gmail.com) -// -// Copyright (c) 2003 Ximian, Inc. (http://www.ximian.com) -// Copyright (c) 2007 Novell, Inc. (http://www.novell.com) -// Copyright (c) 2012-2013 sta.blockhead -// -// 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. -// +#region License +/* + * WebHeaderCollection.cs + * + * This code is derived from System.Net.WebHeaderCollection.cs of Mono + * (http://www.mono-project.com). + * + * The MIT License + * + * Copyright (c) 2003 Ximian, Inc. (http://www.ximian.com) + * Copyright (c) 2007 Novell, Inc. (http://www.novell.com) + * Copyright (c) 2012-2014 sta.blockhead + * + * 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. + */ +#endregion + +#region Authors +/* + * Authors: + * - Lawrence Pit + * - Gonzalo Paniagua Javier + * - Miguel de Icaza + */ +#endregion using System; using System.Collections; @@ -45,1184 +53,1474 @@ using System.Text; namespace WebSocketSharp.Net { - /// - /// Provides a collection of the HTTP headers associated with a request or response. - /// - [Serializable] - [ComVisible (true)] - public class WebHeaderCollection : NameValueCollection, ISerializable - { - #region Fields - - static readonly Dictionary headers; - - bool internallyCreated; - HttpHeaderType state; - - #endregion - - #region Constructors - - static WebHeaderCollection () - { - headers = new Dictionary (StringComparer.InvariantCultureIgnoreCase) - { - { "Accept", new HttpHeaderInfo () { - Name = "Accept", - Type = HttpHeaderType.Request | HttpHeaderType.Restricted | HttpHeaderType.MultiValue } }, - { "AcceptCharset", new HttpHeaderInfo () { - Name = "Accept-Charset", - Type = HttpHeaderType.Request | HttpHeaderType.MultiValue } }, - { "AcceptEncoding", new HttpHeaderInfo () { - Name = "Accept-Encoding", - Type = HttpHeaderType.Request | HttpHeaderType.MultiValue } }, - { "AcceptLanguage", new HttpHeaderInfo () { - Name = "Accept-language", - Type = HttpHeaderType.Request | HttpHeaderType.MultiValue } }, - { "AcceptRanges", new HttpHeaderInfo () { - Name = "Accept-Ranges", - Type = HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "Age", new HttpHeaderInfo () { - Name = "Age", - Type = HttpHeaderType.Response } }, - { "Allow", new HttpHeaderInfo () { - Name = "Allow", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "Authorization", new HttpHeaderInfo () { - Name = "Authorization", - Type = HttpHeaderType.Request | HttpHeaderType.MultiValue } }, - { "CacheControl", new HttpHeaderInfo () { - Name = "Cache-Control", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "Connection", new HttpHeaderInfo () { - Name = "Connection", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted | HttpHeaderType.MultiValue } }, - { "ContentEncoding", new HttpHeaderInfo () { - Name = "Content-Encoding", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "ContentLanguage", new HttpHeaderInfo () { - Name = "Content-Language", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "ContentLength", new HttpHeaderInfo () { - Name = "Content-Length", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted } }, - { "ContentLocation", new HttpHeaderInfo () { - Name = "Content-Location", - Type = HttpHeaderType.Request | HttpHeaderType.Response } }, - { "ContentMd5", new HttpHeaderInfo () { - Name = "Content-MD5", - Type = HttpHeaderType.Request | HttpHeaderType.Response } }, - { "ContentRange", new HttpHeaderInfo () { - Name = "Content-Range", - Type = HttpHeaderType.Request | HttpHeaderType.Response } }, - { "ContentType", new HttpHeaderInfo () { - Name = "Content-Type", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted } }, - { "Cookie", new HttpHeaderInfo () { - Name = "Cookie", - Type = HttpHeaderType.Request } }, - { "Cookie2", new HttpHeaderInfo () { - Name = "Cookie2", - Type = HttpHeaderType.Request } }, - { "Date", new HttpHeaderInfo () { - Name = "Date", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted } }, - { "Expect", new HttpHeaderInfo () { - Name = "Expect", - Type = HttpHeaderType.Request | HttpHeaderType.Restricted | HttpHeaderType.MultiValue } }, - { "Expires", new HttpHeaderInfo () { - Name = "Expires", - Type = HttpHeaderType.Request | HttpHeaderType.Response } }, - { "ETag", new HttpHeaderInfo () { - Name = "ETag", - Type = HttpHeaderType.Response } }, - { "From", new HttpHeaderInfo () { - Name = "From", - Type = HttpHeaderType.Request } }, - { "Host", new HttpHeaderInfo () { - Name = "Host", - Type = HttpHeaderType.Request | HttpHeaderType.Restricted } }, - { "IfMatch", new HttpHeaderInfo () { - Name = "If-Match", - Type = HttpHeaderType.Request | HttpHeaderType.MultiValue } }, - { "IfModifiedSince", new HttpHeaderInfo () { - Name = "If-Modified-Since", - Type = HttpHeaderType.Request | HttpHeaderType.Restricted } }, - { "IfNoneMatch", new HttpHeaderInfo () { - Name = "If-None-Match", - Type = HttpHeaderType.Request | HttpHeaderType.MultiValue } }, - { "IfRange", new HttpHeaderInfo () { - Name = "If-Range", - Type = HttpHeaderType.Request } }, - { "IfUnmodifiedSince", new HttpHeaderInfo () { - Name = "If-Unmodified-Since", - Type = HttpHeaderType.Request } }, - { "KeepAlive", new HttpHeaderInfo () { - Name = "Keep-Alive", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "LastModified", new HttpHeaderInfo () { - Name = "Last-Modified", - Type = HttpHeaderType.Request | HttpHeaderType.Response } }, - { "Location", new HttpHeaderInfo () { - Name = "Location", - Type = HttpHeaderType.Response } }, - { "MaxForwards", new HttpHeaderInfo () { - Name = "Max-Forwards", - Type = HttpHeaderType.Request } }, - { "Pragma", new HttpHeaderInfo () { - Name = "Pragma", - Type = HttpHeaderType.Request | HttpHeaderType.Response } }, - { "ProxyConnection", new HttpHeaderInfo () { - Name = "Proxy-Connection", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted } }, - { "ProxyAuthenticate", new HttpHeaderInfo () { - Name = "Proxy-Authenticate", - Type = HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "ProxyAuthorization", new HttpHeaderInfo () { - Name = "Proxy-Authorization", - Type = HttpHeaderType.Request } }, - { "Public", new HttpHeaderInfo () { - Name = "Public", - Type = HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "Range", new HttpHeaderInfo () { - Name = "Range", - Type = HttpHeaderType.Request | HttpHeaderType.Restricted | HttpHeaderType.MultiValue } }, - { "Referer", new HttpHeaderInfo () { - Name = "Referer", - Type = HttpHeaderType.Request | HttpHeaderType.Restricted } }, - { "RetryAfter", new HttpHeaderInfo () { - Name = "Retry-After", - Type = HttpHeaderType.Response } }, - { "SecWebSocketAccept", new HttpHeaderInfo () { - Name = "Sec-WebSocket-Accept", - Type = HttpHeaderType.Response | HttpHeaderType.Restricted } }, - { "SecWebSocketExtensions", new HttpHeaderInfo () { - Name = "Sec-WebSocket-Extensions", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted | HttpHeaderType.MultiValueInRequest } }, - { "SecWebSocketKey", new HttpHeaderInfo () { - Name = "Sec-WebSocket-Key", - Type = HttpHeaderType.Request | HttpHeaderType.Restricted } }, - { "SecWebSocketProtocol", new HttpHeaderInfo () { - Name = "Sec-WebSocket-Protocol", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValueInRequest } }, - { "SecWebSocketVersion", new HttpHeaderInfo () { - Name = "Sec-WebSocket-Version", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted | HttpHeaderType.MultiValueInResponse } }, - { "Server", new HttpHeaderInfo () { - Name = "Server", - Type = HttpHeaderType.Response } }, - { "SetCookie", new HttpHeaderInfo () { - Name = "Set-Cookie", - Type = HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "SetCookie2", new HttpHeaderInfo () { - Name = "Set-Cookie2", - Type = HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "Te", new HttpHeaderInfo () { - Name = "TE", - Type = HttpHeaderType.Request } }, - { "Trailer", new HttpHeaderInfo () { - Name = "Trailer", - Type = HttpHeaderType.Request | HttpHeaderType.Response } }, - { "TransferEncoding", new HttpHeaderInfo () { - Name = "Transfer-Encoding", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted | HttpHeaderType.MultiValue } }, - { "Translate", new HttpHeaderInfo () { - Name = "Translate", - Type = HttpHeaderType.Request } }, - { "Upgrade", new HttpHeaderInfo () { - Name = "Upgrade", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "UserAgent", new HttpHeaderInfo () { - Name = "User-Agent", - Type = HttpHeaderType.Request | HttpHeaderType.Restricted } }, - { "Vary", new HttpHeaderInfo () { - Name = "Vary", - Type = HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "Via", new HttpHeaderInfo () { - Name = "Via", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "Warning", new HttpHeaderInfo () { - Name = "Warning", - Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue } }, - { "WwwAuthenticate", new HttpHeaderInfo () { - Name = "WWW-Authenticate", - Type = HttpHeaderType.Response | HttpHeaderType.Restricted | HttpHeaderType.MultiValue } } - }; - } - - internal WebHeaderCollection (bool internallyCreated) - { - this.internallyCreated = internallyCreated; - state = HttpHeaderType.Unspecified; - } - - /// - /// Initializes a new instance of the class - /// with the specified and . - /// - /// - /// A that contains the data to need to serialize the object. - /// - /// - /// A that contains the source of the serialized stream associated with the new . - /// - /// - /// is . - /// - /// - /// An element with the specified name is not found in . - /// - protected WebHeaderCollection ( - SerializationInfo serializationInfo, StreamingContext streamingContext) - { - if (serializationInfo == null) - throw new ArgumentNullException ("serializationInfo"); - - try { - internallyCreated = serializationInfo.GetBoolean ("InternallyCreated"); - state = (HttpHeaderType) serializationInfo.GetInt32 ("State"); - - int count = serializationInfo.GetInt32 ("Count"); - for (int i = 0; i < count; i++) { - base.Add ( - serializationInfo.GetString (i.ToString ()), - serializationInfo.GetString ((count + i).ToString ())); - } - } catch (SerializationException ex) { - throw new ArgumentException (ex.Message, "serializationInfo", ex); - } - } - - /// - /// Initializes a new instance of the class. - /// - public WebHeaderCollection () - { - internallyCreated = false; - state = HttpHeaderType.Unspecified; - } - - #endregion - - #region Properties - - /// - /// Gets all header names in the collection. - /// - /// - /// An array of that contains all header names in the collection. - /// - public override string [] AllKeys - { - get { - return base.AllKeys; - } - } - - /// - /// Gets the number of headers in the collection. - /// - /// - /// An that indicates the number of headers in the collection. - /// - public override int Count - { - get { - return base.Count; - } - } - - /// - /// Gets or sets the specified request in the collection. - /// - /// - /// A that contains the value of the specified request . - /// - /// - /// A that indicates a request header. - /// - /// - /// The current instance does not allow any of values. - /// - /// - /// - /// is a restricted header. - /// - /// - /// -or- - /// - /// - /// contains invalid characters. - /// - /// - /// - /// The length of is greater than 65535. - /// - public string this [HttpRequestHeader header] - { - get { - return Get (Convert (header)); - } - - set { - Add (header, value); - } - } - - /// - /// Gets or sets the specified response in the collection. - /// - /// - /// A that contains the value of the specified response . - /// - /// - /// A that indicates a response header. - /// - /// - /// The current instance does not allow any of values. - /// - /// - /// - /// is a restricted header. - /// - /// - /// -or- - /// - /// - /// contains invalid characters. - /// - /// - /// - /// The length of is greater than 65535. - /// - public string this [HttpResponseHeader header] - { - get { - return Get (Convert (header)); - } - - set { - Add (header, value); - } - } - - /// - /// Gets a collection of header names in the collection. - /// - /// - /// A that contains a collection - /// of header names in the collection. - /// - public override KeysCollection Keys - { - get { - return base.Keys; - } - } - - #endregion - - #region Private Methods - - void Add (string name, string value, bool ignoreRestricted) - { - Action add; - if (ignoreRestricted) - add = AddWithoutCheckingNameAndRestricted; - else - add = AddWithoutCheckingName; - - DoWithCheckingState (add, CheckName (name), value, true); - } - - void AddWithoutCheckingName (string name, string value) - { - DoWithoutCheckingName (base.Add, name, value); - } - - void AddWithoutCheckingNameAndRestricted (string name, string value) - { - base.Add (name, CheckValue (value)); - } - - static int CheckColonSeparated (string header) - { - int i = header.IndexOf (':'); - if (i == -1) - throw new ArgumentException ("No colon found.", "header"); - - return i; - } - - static HttpHeaderType CheckHeaderType (string name) - { - HttpHeaderInfo info; - return !TryGetHeaderInfo (name, out info) - ? HttpHeaderType.Unspecified - : info.IsRequest && !info.IsResponse - ? HttpHeaderType.Request - : !info.IsRequest && info.IsResponse - ? HttpHeaderType.Response - : HttpHeaderType.Unspecified; - } - - static string CheckName (string name) - { - if (name.IsNullOrEmpty ()) - throw new ArgumentNullException ("name"); - - name = name.Trim (); - if (!IsHeaderName (name)) - throw new ArgumentException ("Contains invalid characters.", "name"); - - return name; - } - - void CheckRestricted (string name) - { - if (!internallyCreated && ContainsInRestricted (name, true)) - throw new ArgumentException ("This header must be modified with the appropiate property."); - } - - void CheckState (bool response) - { - if (state == HttpHeaderType.Unspecified) - return; - - if (response && state == HttpHeaderType.Request) - throw new InvalidOperationException ("This collection has already been used to store the request headers."); - - if (!response && state == HttpHeaderType.Response) - throw new InvalidOperationException ("This collection has already been used to store the response headers."); - } - - static string CheckValue (string value) - { - if (value.IsNullOrEmpty ()) - return String.Empty; - - value = value.Trim (); - if (value.Length > 65535) - throw new ArgumentOutOfRangeException ("value", "The length must not be greater than 65535."); - - if (!IsHeaderValue (value)) - throw new ArgumentException ("Contains invalid characters.", "value"); - - return value; - } - - static string Convert (string key) - { - HttpHeaderInfo info; - return headers.TryGetValue (key, out info) - ? info.Name - : String.Empty; - } - - static bool ContainsInRestricted (string name, bool response) - { - HttpHeaderInfo info; - return TryGetHeaderInfo (name, out info) - ? info.IsRestricted (response) - : false; - } - - void DoWithCheckingState ( - Action act, string name, string value, bool setState) - { - var type = CheckHeaderType (name); - if (type == HttpHeaderType.Request) - DoWithCheckingState (act, name, value, false, setState); - else if (type == HttpHeaderType.Response) - DoWithCheckingState (act, name, value, true, setState); - else - act (name, value); - } - - void DoWithCheckingState ( - Action act, string name, string value, bool response, bool setState) - { - CheckState (response); - act (name, value); - if (setState) - SetState (response); - } - - void DoWithoutCheckingName (Action act, string name, string value) - { - CheckRestricted (name); - act (name, CheckValue (value)); - } - - static HttpHeaderInfo GetHeaderInfo (string name) - { - return (from HttpHeaderInfo info in headers.Values - where info.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase) - select info).FirstOrDefault (); - } - - void RemoveWithoutCheckingName (string name, string unuse) - { - CheckRestricted (name); - base.Remove (name); - } - - void SetState (bool response) - { - if (state == HttpHeaderType.Unspecified) - state = response - ? HttpHeaderType.Response - : HttpHeaderType.Request; - } - - void SetWithoutCheckingName (string name, string value) - { - DoWithoutCheckingName (base.Set, name, value); - } - - static bool TryGetHeaderInfo (string name, out HttpHeaderInfo info) - { - info = GetHeaderInfo (name); - return info != null; - } - - #endregion - - #region Internal Methods - - internal static string Convert (HttpRequestHeader header) - { - return Convert (header.ToString ()); - } - - internal static string Convert (HttpResponseHeader header) - { - return Convert (header.ToString ()); - } - - internal static bool IsHeaderName (string name) - { - return name.IsNullOrEmpty () - ? false - : name.IsToken (); - } - - internal static bool IsHeaderValue (string value) - { - return value.IsText (); - } - - internal static bool IsMultiValue (string headerName, bool response) - { - if (headerName.IsNullOrEmpty ()) - return false; - - HttpHeaderInfo info; - return TryGetHeaderInfo (headerName, out info) - ? info.IsMultiValue (response) - : false; - } - - internal void RemoveInternal (string name) - { - base.Remove (name); - } - - internal void SetInternal (string header, bool response) - { - int pos = CheckColonSeparated (header); - SetInternal (header.Substring (0, pos), header.Substring (pos + 1), response); - } - - internal void SetInternal (string name, string value, bool response) - { - value = CheckValue (value); - if (IsMultiValue (name, response)) - base.Add (name, value); - else - base.Set (name, value); - } - - internal string ToStringMultiValue (bool response) - { - var sb = new StringBuilder (); - Count.Times (i => { - string key = GetKey (i); - if (IsMultiValue (key, response)) { - foreach (string value in GetValues (i)) - sb.AppendFormat ("{0}: {1}\r\n", key, value); - } else { - sb.AppendFormat ("{0}: {1}\r\n", key, Get (i)); - } - }); - - return sb.Append ("\r\n").ToString (); - } - - #endregion - - #region Explicit Interface Implementation - - /// - /// Populates the specified with the data to need to - /// serialize the object. - /// - /// - /// A that holds the data to need to serialize the object. - /// - /// - /// A that specifies the destination for the serialization. - /// - /// - /// is . - /// - [SecurityPermission (SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter, SerializationFormatter = true)] - void ISerializable.GetObjectData ( - SerializationInfo serializationInfo, StreamingContext streamingContext) - { - GetObjectData (serializationInfo, streamingContext); - } - - #endregion - - #region Protected Methods - - /// - /// Adds a header to the collection without checking whether the header is on the restricted header list. - /// - /// - /// A that contains the name of the header to add. - /// - /// - /// A that contains the value of the header to add. - /// - /// - /// is or . - /// - /// - /// or contains invalid characters. - /// - /// - /// The length of is greater than 65535. - /// - /// - /// The current instance does not allow the . - /// - protected void AddWithoutValidate (string headerName, string headerValue) - { - Add (headerName, headerValue, true); - } - - #endregion - - #region Public Methods - - /// - /// Adds the specified to the collection. - /// - /// - /// A that contains a header with the name and value separated by a colon (:). - /// - /// - /// is , , or - /// the name part of is . - /// - /// - /// - /// does not contain a colon. - /// - /// - /// -or- - /// - /// - /// is a restricted header. - /// - /// - /// -or- - /// - /// - /// The name or value part of contains invalid characters. - /// - /// - /// - /// The length of the value part of is greater than 65535. - /// - /// - /// The current instance does not allow the . - /// - public void Add (string header) - { - if (header.IsNullOrEmpty ()) - throw new ArgumentNullException ("header"); - - int pos = CheckColonSeparated (header); - Add (header.Substring (0, pos), header.Substring (pos + 1)); - } - - /// - /// Adds the specified request with the specified to the collection. - /// - /// - /// A is a request header to add. - /// - /// - /// A that contains the value of the header to add. - /// - /// - /// The current instance does not allow any of values. - /// - /// - /// - /// is a restricted header. - /// - /// - /// -or- - /// - /// - /// contains invalid characters. - /// - /// - /// - /// The length of is greater than 65535. - /// - public void Add (HttpRequestHeader header, string value) - { - DoWithCheckingState (AddWithoutCheckingName, Convert (header), value, false, true); - } - - /// - /// Adds the specified response with the specified to the collection. - /// - /// - /// A is a response header to add. - /// - /// - /// A that contains the value of the header to add. - /// - /// - /// The current instance does not allow any of values. - /// - /// - /// - /// is a restricted header. - /// - /// - /// -or- - /// - /// - /// contains invalid characters. - /// - /// - /// - /// The length of is greater than 65535. - /// - public void Add (HttpResponseHeader header, string value) - { - DoWithCheckingState (AddWithoutCheckingName, Convert (header), value, true, true); - } - - /// - /// Adds a header with the specified and to the collection. - /// - /// - /// A that contains the name of the header to add. - /// - /// - /// A that contains the value of the header to add. - /// - /// - /// is or . - /// - /// - /// - /// or contains invalid characters. - /// - /// - /// -or- - /// - /// - /// is a restricted header name. - /// - /// - /// - /// The length of is greater than 65535. - /// - /// - /// The current instance does not allow the header . - /// - public override void Add (string name, string value) - { - Add (name, value, false); - } - - /// - /// Removes all headers from the collection. - /// - public override void Clear () - { - base.Clear (); - state = HttpHeaderType.Unspecified; - } - - /// - /// Get the value of the header with the specified in the collection. - /// - /// - /// A that receives the value of the header. - /// - /// - /// An that is the zero-based index of the header to get. - /// - public override string Get (int index) - { - return base.Get (index); - } - - /// - /// Get the value of the header with the specified in the collection. - /// - /// - /// A that receives the value of the header. - /// if there is no header with in the collection. - /// - /// - /// A that contains the name of the header to get. - /// - public override string Get (string name) - { - return base.Get (name); - } - - /// - /// Gets the enumerator to use to iterate through the . - /// - /// - /// An instance of an implementation of the interface - /// to use to iterate through the . - /// - public override IEnumerator GetEnumerator () - { - return base.GetEnumerator (); - } - - /// - /// Get the header name at the specified position in the collection. - /// - /// - /// A that receives the header name. - /// - /// - /// An is the zero-based index of the key to get from the collection. - /// - public override string GetKey (int index) - { - return base.GetKey (index); - } - - /// - /// Gets an array of header values stored in the specified name. - /// - /// - /// An array of that receives the header values. - /// - /// - /// A that contains a header name. - /// - public override string [] GetValues (string header) - { - string [] values = base.GetValues (header); - return values == null || values.Length == 0 - ? null - : values; - } - - /// - /// Gets an array of header values stored in the specified position of the header collection. - /// - /// - /// An array of that receives the header values. - /// - /// - /// An is the zero-based index of the header in the collection. - /// - public override string [] GetValues (int index) - { - string [] values = base.GetValues (index); - return values == null || values.Length == 0 - ? null - : values; - } - - /// - /// Populates the specified with the data to need to - /// serialize the object. - /// - /// - /// A that holds the data to need to serialize the object. - /// - /// - /// A that specifies the destination for the serialization. - /// - /// - /// is . - /// - [SecurityPermission (SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] - public override void GetObjectData ( - SerializationInfo serializationInfo, StreamingContext streamingContext) - { - if (serializationInfo == null) - throw new ArgumentNullException ("serializationInfo"); - - serializationInfo.AddValue ("InternallyCreated", internallyCreated); - serializationInfo.AddValue ("State", (int) state); - - int count = Count; - serializationInfo.AddValue ("Count", count); - count.Times (i => { - serializationInfo.AddValue (i.ToString (), GetKey (i)); - serializationInfo.AddValue ((count + i).ToString (), Get (i)); - }); - } - - /// - /// Determines whether the specified header can be set for the request. - /// - /// - /// true if the header is restricted; otherwise, false. - /// - /// - /// A that contains the name of the header to test. - /// - /// - /// is or . - /// - /// - /// contains invalid characters. - /// - public static bool IsRestricted (string headerName) - { - return IsRestricted (headerName, false); - } - - /// - /// Determines whether the specified header can be set for the request or the response. - /// - /// - /// true if the header is restricted; otherwise, false. - /// - /// - /// A that contains the name of the header to test. - /// - /// - /// true if does the test for the response; for the request, false. - /// - /// - /// is or . - /// - /// - /// contains invalid characters. - /// - public static bool IsRestricted (string headerName, bool response) - { - return ContainsInRestricted (CheckName (headerName), response); - } - - /// - /// Implements the interface and raises the deserialization event - /// when the deserialization is complete. - /// - /// - /// An that contains the source of the deserialization event. - /// - public override void OnDeserialization (object sender) - { - } - - /// - /// Removes the specified header from the collection. - /// - /// - /// A to remove from the collection. - /// - /// - /// The current instance does not allow any of values. - /// - /// - /// is a restricted header. - /// - public void Remove (HttpRequestHeader header) - { - DoWithCheckingState (RemoveWithoutCheckingName, Convert (header), null, false, false); - } - - /// - /// Removes the specified header from the collection. - /// - /// - /// A to remove from the collection. - /// - /// - /// The current instance does not allow any of values. - /// - /// - /// is a restricted header. - /// - public void Remove (HttpResponseHeader header) - { - DoWithCheckingState (RemoveWithoutCheckingName, Convert (header), null, true, false); - } - - /// - /// Removes the specified header from the collection. - /// - /// - /// A that contains the name of the header to remove from the collection. - /// - /// - /// is or . - /// - /// - /// - /// contains invalid characters. - /// - /// - /// -or- - /// - /// - /// is a restricted header name. - /// - /// - /// - /// The current instance does not allow the header . - /// - public override void Remove (string name) - { - DoWithCheckingState (RemoveWithoutCheckingName, CheckName (name), null, false); - } - - /// - /// Sets the specified header to the specified value. - /// - /// - /// A to set. - /// - /// - /// A that contains the value of the header to set. - /// - /// - /// The current instance does not allow any of values. - /// - /// - /// - /// is a restricted header. - /// - /// - /// -or- - /// - /// - /// contains invalid characters. - /// - /// - /// - /// The length of is greater than 65535. - /// - public void Set (HttpRequestHeader header, string value) - { - DoWithCheckingState (SetWithoutCheckingName, Convert (header), value, false, true); - } - - /// - /// Sets the specified header to the specified value. - /// - /// - /// A to set. - /// - /// - /// A that contains the value of the header to set. - /// - /// - /// The current instance does not allow any of values. - /// - /// - /// - /// is a restricted header. - /// - /// - /// -or- - /// - /// - /// contains invalid characters. - /// - /// - /// - /// The length of is greater than 65535. - /// - public void Set (HttpResponseHeader header, string value) - { - DoWithCheckingState (SetWithoutCheckingName, Convert (header), value, true, true); - } - - /// - /// Sets the specified header to the specified value. - /// - /// - /// A that contains the name of the header to set. - /// - /// - /// A that contains the value of the header to set. - /// - /// - /// is or . - /// - /// - /// - /// or contain invalid characters. - /// - /// - /// -or- - /// - /// - /// is a restricted header name. - /// - /// - /// - /// The length of is greater than 65535. - /// - /// - /// The current instance does not allow the header . - /// - public override void Set (string name, string value) - { - DoWithCheckingState (SetWithoutCheckingName, CheckName (name), value, true); - } - - /// - /// Converts the current to an array of . - /// - /// - /// An array of that receives the converted current . - /// - public byte [] ToByteArray () - { - return Encoding.UTF8.GetBytes (ToString ()); - } - - /// - /// Returns a that represents the current . - /// - /// - /// A that represents the current . - /// - public override string ToString () - { - var sb = new StringBuilder(); - Count.Times (i => { - sb.AppendFormat ("{0}: {1}\r\n", GetKey (i), Get (i)); - }); - - return sb.Append ("\r\n").ToString (); - } - - #endregion - } + /// + /// Provides a collection of the HTTP headers associated with a request or response. + /// + [Serializable] + [ComVisible (true)] + public class WebHeaderCollection : NameValueCollection, ISerializable + { + #region Private Static Fields + + private static readonly Dictionary _headers; + + #endregion + + #region Private Fields + + private bool _internallyCreated; + private HttpHeaderType _state; + + #endregion + + #region Static Constructor + + static WebHeaderCollection () + { + _headers = + new Dictionary (StringComparer.InvariantCultureIgnoreCase) { + { + "Accept", + new HttpHeaderInfo () { + Name = "Accept", + Type = HttpHeaderType.Request | HttpHeaderType.Restricted | HttpHeaderType.MultiValue + } + }, + { + "AcceptCharset", + new HttpHeaderInfo () { + Name = "Accept-Charset", + Type = HttpHeaderType.Request | HttpHeaderType.MultiValue + } + }, + { + "AcceptEncoding", + new HttpHeaderInfo () { + Name = "Accept-Encoding", + Type = HttpHeaderType.Request | HttpHeaderType.MultiValue + } + }, + { + "AcceptLanguage", + new HttpHeaderInfo () { + Name = "Accept-language", + Type = HttpHeaderType.Request | HttpHeaderType.MultiValue + } + }, + { + "AcceptRanges", + new HttpHeaderInfo () { + Name = "Accept-Ranges", + Type = HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "Age", + new HttpHeaderInfo () { + Name = "Age", + Type = HttpHeaderType.Response + } + }, + { + "Allow", + new HttpHeaderInfo () { + Name = "Allow", + Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "Authorization", + new HttpHeaderInfo () { + Name = "Authorization", + Type = HttpHeaderType.Request | HttpHeaderType.MultiValue + } + }, + { + "CacheControl", + new HttpHeaderInfo () { + Name = "Cache-Control", + Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "Connection", + new HttpHeaderInfo () { + Name = "Connection", + Type = HttpHeaderType.Request | + HttpHeaderType.Response | + HttpHeaderType.Restricted | + HttpHeaderType.MultiValue + } + }, + { + "ContentEncoding", + new HttpHeaderInfo () { + Name = "Content-Encoding", + Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "ContentLanguage", + new HttpHeaderInfo () { + Name = "Content-Language", + Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "ContentLength", + new HttpHeaderInfo () { + Name = "Content-Length", + Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted + } + }, + { + "ContentLocation", + new HttpHeaderInfo () { + Name = "Content-Location", + Type = HttpHeaderType.Request | HttpHeaderType.Response + } + }, + { + "ContentMd5", + new HttpHeaderInfo () { + Name = "Content-MD5", + Type = HttpHeaderType.Request | HttpHeaderType.Response + } + }, + { + "ContentRange", + new HttpHeaderInfo () { + Name = "Content-Range", + Type = HttpHeaderType.Request | HttpHeaderType.Response + } + }, + { + "ContentType", + new HttpHeaderInfo () { + Name = "Content-Type", + Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted + } + }, + { + "Cookie", + new HttpHeaderInfo () { + Name = "Cookie", + Type = HttpHeaderType.Request + } + }, + { + "Cookie2", + new HttpHeaderInfo () { + Name = "Cookie2", + Type = HttpHeaderType.Request + } + }, + { + "Date", + new HttpHeaderInfo () { + Name = "Date", + Type = HttpHeaderType.Request | + HttpHeaderType.Response | + HttpHeaderType.Restricted + } + }, + { + "Expect", + new HttpHeaderInfo () { + Name = "Expect", + Type = HttpHeaderType.Request | HttpHeaderType.Restricted | HttpHeaderType.MultiValue + } + }, + { + "Expires", + new HttpHeaderInfo () { + Name = "Expires", + Type = HttpHeaderType.Request | HttpHeaderType.Response + } + }, + { + "ETag", + new HttpHeaderInfo () { + Name = "ETag", + Type = HttpHeaderType.Response + } + }, + { + "From", + new HttpHeaderInfo () { + Name = "From", + Type = HttpHeaderType.Request + } + }, + { + "Host", + new HttpHeaderInfo () { + Name = "Host", + Type = HttpHeaderType.Request | HttpHeaderType.Restricted + } + }, + { + "IfMatch", + new HttpHeaderInfo () { + Name = "If-Match", + Type = HttpHeaderType.Request | HttpHeaderType.MultiValue + } + }, + { + "IfModifiedSince", + new HttpHeaderInfo () { + Name = "If-Modified-Since", + Type = HttpHeaderType.Request | HttpHeaderType.Restricted + } + }, + { + "IfNoneMatch", + new HttpHeaderInfo () { + Name = "If-None-Match", + Type = HttpHeaderType.Request | HttpHeaderType.MultiValue + } + }, + { + "IfRange", + new HttpHeaderInfo () { + Name = "If-Range", + Type = HttpHeaderType.Request + } + }, + { + "IfUnmodifiedSince", + new HttpHeaderInfo () { + Name = "If-Unmodified-Since", + Type = HttpHeaderType.Request + } + }, + { + "KeepAlive", + new HttpHeaderInfo () { + Name = "Keep-Alive", + Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "LastModified", + new HttpHeaderInfo () { + Name = "Last-Modified", + Type = HttpHeaderType.Request | HttpHeaderType.Response + } + }, + { + "Location", + new HttpHeaderInfo () { + Name = "Location", + Type = HttpHeaderType.Response + } + }, + { + "MaxForwards", + new HttpHeaderInfo () { + Name = "Max-Forwards", + Type = HttpHeaderType.Request + } + }, + { + "Pragma", + new HttpHeaderInfo () { + Name = "Pragma", + Type = HttpHeaderType.Request | HttpHeaderType.Response + } + }, + { + "ProxyConnection", + new HttpHeaderInfo () { + Name = "Proxy-Connection", + Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted + } + }, + { + "ProxyAuthenticate", + new HttpHeaderInfo () { + Name = "Proxy-Authenticate", + Type = HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "ProxyAuthorization", + new HttpHeaderInfo () { + Name = "Proxy-Authorization", + Type = HttpHeaderType.Request + } + }, + { + "Public", + new HttpHeaderInfo () { + Name = "Public", + Type = HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "Range", + new HttpHeaderInfo () { + Name = "Range", + Type = HttpHeaderType.Request | HttpHeaderType.Restricted | HttpHeaderType.MultiValue + } + }, + { + "Referer", + new HttpHeaderInfo () { + Name = "Referer", + Type = HttpHeaderType.Request | HttpHeaderType.Restricted + } + }, + { + "RetryAfter", + new HttpHeaderInfo () { + Name = "Retry-After", + Type = HttpHeaderType.Response + } + }, + { + "SecWebSocketAccept", + new HttpHeaderInfo () { + Name = "Sec-WebSocket-Accept", + Type = HttpHeaderType.Response | HttpHeaderType.Restricted + } + }, + { + "SecWebSocketExtensions", + new HttpHeaderInfo () { + Name = "Sec-WebSocket-Extensions", + Type = HttpHeaderType.Request | + HttpHeaderType.Response | + HttpHeaderType.Restricted | + HttpHeaderType.MultiValueInRequest + } + }, + { + "SecWebSocketKey", + new HttpHeaderInfo () { + Name = "Sec-WebSocket-Key", + Type = HttpHeaderType.Request | HttpHeaderType.Restricted + } + }, + { + "SecWebSocketProtocol", + new HttpHeaderInfo () { + Name = "Sec-WebSocket-Protocol", + Type = HttpHeaderType.Request | + HttpHeaderType.Response | + HttpHeaderType.MultiValueInRequest + } + }, + { + "SecWebSocketVersion", + new HttpHeaderInfo () { + Name = "Sec-WebSocket-Version", + Type = HttpHeaderType.Request | + HttpHeaderType.Response | + HttpHeaderType.Restricted | + HttpHeaderType.MultiValueInResponse + } + }, + { + "Server", + new HttpHeaderInfo () { + Name = "Server", + Type = HttpHeaderType.Response + } + }, + { + "SetCookie", + new HttpHeaderInfo () { + Name = "Set-Cookie", + Type = HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "SetCookie2", + new HttpHeaderInfo () { + Name = "Set-Cookie2", + Type = HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "Te", + new HttpHeaderInfo () { + Name = "TE", + Type = HttpHeaderType.Request + } + }, + { + "Trailer", + new HttpHeaderInfo () { + Name = "Trailer", + Type = HttpHeaderType.Request | HttpHeaderType.Response + } + }, + { + "TransferEncoding", + new HttpHeaderInfo () { + Name = "Transfer-Encoding", + Type = HttpHeaderType.Request | + HttpHeaderType.Response | + HttpHeaderType.Restricted | + HttpHeaderType.MultiValue + } + }, + { + "Translate", + new HttpHeaderInfo () { + Name = "Translate", + Type = HttpHeaderType.Request + } + }, + { + "Upgrade", + new HttpHeaderInfo () { + Name = "Upgrade", + Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "UserAgent", + new HttpHeaderInfo () { + Name = "User-Agent", + Type = HttpHeaderType.Request | HttpHeaderType.Restricted + } + }, + { + "Vary", + new HttpHeaderInfo () { + Name = "Vary", + Type = HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "Via", + new HttpHeaderInfo () { + Name = "Via", + Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "Warning", + new HttpHeaderInfo () { + Name = "Warning", + Type = HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue + } + }, + { + "WwwAuthenticate", + new HttpHeaderInfo () { + Name = "WWW-Authenticate", + Type = HttpHeaderType.Response | HttpHeaderType.Restricted | HttpHeaderType.MultiValue + } + } + }; + } + + #endregion + + #region Internal Constructors + + internal WebHeaderCollection (bool internallyCreated) + { + _internallyCreated = internallyCreated; + _state = HttpHeaderType.Unspecified; + } + + #endregion + + #region Protected Constructors + + /// + /// Initializes a new instance of the class from + /// the specified and . + /// + /// + /// A that contains the serialized object data. + /// + /// + /// A that specifies the source for the deserialization. + /// + /// + /// is . + /// + /// + /// An element with the specified name isn't found in . + /// + protected WebHeaderCollection ( + SerializationInfo serializationInfo, StreamingContext streamingContext) + { + if (serializationInfo == null) + throw new ArgumentNullException ("serializationInfo"); + + try { + _internallyCreated = serializationInfo.GetBoolean ("InternallyCreated"); + _state = (HttpHeaderType) serializationInfo.GetInt32 ("State"); + + var count = serializationInfo.GetInt32 ("Count"); + for (int i = 0; i < count; i++) { + base.Add ( + serializationInfo.GetString (i.ToString ()), + serializationInfo.GetString ((count + i).ToString ())); + } + } + catch (SerializationException ex) { + throw new ArgumentException (ex.Message, "serializationInfo", ex); + } + } + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + public WebHeaderCollection () + { + _internallyCreated = false; + _state = HttpHeaderType.Unspecified; + } + + #endregion + + #region Public Properties + + /// + /// Gets all header names in the collection. + /// + /// + /// An array of that contains all header names in the collection. + /// + public override string [] AllKeys { + get { + return base.AllKeys; + } + } + + /// + /// Gets the number of headers in the collection. + /// + /// + /// An that represents the number of headers in the collection. + /// + public override int Count { + get { + return base.Count; + } + } + + /// + /// Gets or sets the specified request in the collection. + /// + /// + /// A that represents the value of the specified request + /// . + /// + /// + /// A that represents the request header. + /// + /// + /// The current instance doesn't allow any of + /// enum values. + /// + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// contains invalid characters. + /// + /// + /// + /// The length of is greater than 65535. + /// + public string this [HttpRequestHeader header] { + get { + return Get (Convert (header)); + } + + set { + Add (header, value); + } + } + + /// + /// Gets or sets the specified response in the collection. + /// + /// + /// A that represents the value of the specified response + /// . + /// + /// + /// A that represents the response header. + /// + /// + /// The current instance doesn't allow any of + /// enum values. + /// + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// contains invalid characters. + /// + /// + /// + /// The length of is greater than 65535. + /// + public string this [HttpResponseHeader header] { + get { + return Get (Convert (header)); + } + + set { + Add (header, value); + } + } + + /// + /// Gets a collection of header names in the collection. + /// + /// + /// A that contains all header names in the collection. + /// + public override KeysCollection Keys { + get { + return base.Keys; + } + } + + #endregion + + #region Private Methods + + private void add (string name, string value, bool ignoreRestricted) + { + Action act; + if (ignoreRestricted) + act = addWithoutCheckingNameAndRestricted; + else + act = addWithoutCheckingName; + + doWithCheckingState (act, checkName (name), value, true); + } + + private void addWithoutCheckingName (string name, string value) + { + doWithoutCheckingName (base.Add, name, value); + } + + private void addWithoutCheckingNameAndRestricted (string name, string value) + { + base.Add (name, checkValue (value)); + } + + private static int checkColonSeparated (string header) + { + var i = header.IndexOf (':'); + if (i == -1) + throw new ArgumentException ("No colon found.", "header"); + + return i; + } + + private static HttpHeaderType checkHeaderType (string name) + { + HttpHeaderInfo info; + return !tryGetHeaderInfo (name, out info) + ? HttpHeaderType.Unspecified + : info.IsRequest && !info.IsResponse + ? HttpHeaderType.Request + : !info.IsRequest && info.IsResponse + ? HttpHeaderType.Response + : HttpHeaderType.Unspecified; + } + + private static string checkName (string name) + { + if (name.IsNullOrEmpty ()) + throw new ArgumentNullException ("name"); + + name = name.Trim (); + if (!IsHeaderName (name)) + throw new ArgumentException ("Contains invalid characters.", "name"); + + return name; + } + + private void checkRestricted (string name) + { + if (!_internallyCreated && isRestricted (name, true)) + throw new ArgumentException ("This header must be modified with the appropiate property."); + } + + private void checkState (bool response) + { + if (_state == HttpHeaderType.Unspecified) + return; + + if (response && _state == HttpHeaderType.Request) + throw new InvalidOperationException ( + "This collection has already been used to store the request headers."); + + if (!response && _state == HttpHeaderType.Response) + throw new InvalidOperationException ( + "This collection has already been used to store the response headers."); + } + + private static string checkValue (string value) + { + if (value.IsNullOrEmpty ()) + return String.Empty; + + value = value.Trim (); + if (value.Length > 65535) + throw new ArgumentOutOfRangeException ( + "value", "The length must not be greater than 65535."); + + if (!IsHeaderValue (value)) + throw new ArgumentException ("Contains invalid characters.", "value"); + + return value; + } + + private static string convert (string key) + { + HttpHeaderInfo info; + return _headers.TryGetValue (key, out info) + ? info.Name + : String.Empty; + } + + private void doWithCheckingState ( + Action action, string name, string value, bool setState) + { + var type = checkHeaderType (name); + if (type == HttpHeaderType.Request) + doWithCheckingState (action, name, value, false, setState); + else if (type == HttpHeaderType.Response) + doWithCheckingState (action, name, value, true, setState); + else + action (name, value); + } + + private void doWithCheckingState ( + Action action, string name, string value, bool response, bool setState) + { + checkState (response); + action (name, value); + if (setState) + setDefaultState (response); + } + + private void doWithoutCheckingName (Action action, string name, string value) + { + checkRestricted (name); + action (name, checkValue (value)); + } + + private static HttpHeaderInfo getHeaderInfo (string name) + { + return (from HttpHeaderInfo info in _headers.Values + where info.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase) + select info).FirstOrDefault (); + } + + private static bool isRestricted (string name, bool response) + { + HttpHeaderInfo info; + return tryGetHeaderInfo (name, out info) && info.IsRestricted (response); + } + + private void removeWithoutCheckingName (string name, string unuse) + { + checkRestricted (name); + base.Remove (name); + } + + private void setDefaultState (bool response) + { + if (_state == HttpHeaderType.Unspecified) + _state = response ? HttpHeaderType.Response : HttpHeaderType.Request; + } + + private void setWithoutCheckingName (string name, string value) + { + doWithoutCheckingName (base.Set, name, value); + } + + private static bool tryGetHeaderInfo (string name, out HttpHeaderInfo info) + { + info = getHeaderInfo (name); + return info != null; + } + + #endregion + + #region Internal Methods + + internal static string Convert (HttpRequestHeader header) + { + return convert (header.ToString ()); + } + + internal static string Convert (HttpResponseHeader header) + { + return convert (header.ToString ()); + } + + internal static bool IsHeaderName (string name) + { + return !name.IsNullOrEmpty () && name.IsToken (); + } + + internal static bool IsHeaderValue (string value) + { + return value.IsText (); + } + + internal static bool IsMultiValue (string headerName, bool response) + { + if (headerName.IsNullOrEmpty ()) + return false; + + HttpHeaderInfo info; + return tryGetHeaderInfo (headerName, out info) && info.IsMultiValue (response); + } + + internal void RemoveInternal (string name) + { + base.Remove (name); + } + + internal void SetInternal (string header, bool response) + { + var pos = checkColonSeparated (header); + SetInternal (header.Substring (0, pos), header.Substring (pos + 1), response); + } + + internal void SetInternal (string name, string value, bool response) + { + value = checkValue (value); + if (IsMultiValue (name, response)) + base.Add (name, value); + else + base.Set (name, value); + } + + internal string ToStringMultiValue (bool response) + { + var buffer = new StringBuilder (); + Count.Times ( + i => { + var key = GetKey (i); + if (IsMultiValue (key, response)) + foreach (var value in GetValues (i)) + buffer.AppendFormat ("{0}: {1}\r\n", key, value); + else + buffer.AppendFormat ("{0}: {1}\r\n", key, Get (i)); + }); + + return buffer.Append ("\r\n").ToString (); + } + + #endregion + + #region Protected Methods + + /// + /// Adds a header to the collection without checking whether the header is on the restricted + /// header list. + /// + /// + /// A that represents the name of the header to add. + /// + /// + /// A that represents the value of the header to add. + /// + /// + /// is or empty. + /// + /// + /// or contains invalid characters. + /// + /// + /// The length of is greater than 65535. + /// + /// + /// The current instance doesn't allow + /// the . + /// + protected void AddWithoutValidate (string headerName, string headerValue) + { + add (headerName, headerValue, true); + } + + #endregion + + #region Public Methods + + /// + /// Adds the specified to the collection. + /// + /// + /// A that represents the header with the name and value separated by + /// a colon (:). + /// + /// + /// is , empty, or the name part of + /// is empty. + /// + /// + /// + /// doesn't contain a colon. + /// + /// + /// -or- + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// The name or value part of contains invalid characters. + /// + /// + /// + /// The length of the value part of is greater than 65535. + /// + /// + /// The current instance doesn't allow + /// the . + /// + public void Add (string header) + { + if (header.IsNullOrEmpty ()) + throw new ArgumentNullException ("header"); + + var pos = checkColonSeparated (header); + Add (header.Substring (0, pos), header.Substring (pos + 1)); + } + + /// + /// Adds the specified request with the specified + /// to the collection. + /// + /// + /// A that represents the request header to add. + /// + /// + /// A that represents the value of the header to add. + /// + /// + /// The current instance doesn't allow any of + /// enum values. + /// + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// contains invalid characters. + /// + /// + /// + /// The length of is greater than 65535. + /// + public void Add (HttpRequestHeader header, string value) + { + doWithCheckingState (addWithoutCheckingName, Convert (header), value, false, true); + } + + /// + /// Adds the specified response with the specified + /// to the collection. + /// + /// + /// A that represents the response header to add. + /// + /// + /// A that represents the value of the header to add. + /// + /// + /// The current instance doesn't allow any of + /// enum values. + /// + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// contains invalid characters. + /// + /// + /// + /// The length of is greater than 65535. + /// + public void Add (HttpResponseHeader header, string value) + { + doWithCheckingState (addWithoutCheckingName, Convert (header), value, true, true); + } + + /// + /// Adds a header with the specified and to + /// the collection. + /// + /// + /// A that represents the name of the header to add. + /// + /// + /// A that represents the value of the header to add. + /// + /// + /// is or empty. + /// + /// + /// + /// or contains invalid characters. + /// + /// + /// -or- + /// + /// + /// is a restricted header name. + /// + /// + /// + /// The length of is greater than 65535. + /// + /// + /// The current instance doesn't allow the header + /// . + /// + public override void Add (string name, string value) + { + add (name, value, false); + } + + /// + /// Removes all headers from the collection. + /// + public override void Clear () + { + base.Clear (); + _state = HttpHeaderType.Unspecified; + } + + /// + /// Get the value of the header with the specified in the collection. + /// + /// + /// A that receives the value of the header. + /// + /// + /// An that is the zero-based index of the header to get. + /// + public override string Get (int index) + { + return base.Get (index); + } + + /// + /// Get the value of the header with the specified in the collection. + /// + /// + /// A that receives the value of the header. + /// if there is no header with in the collection. + /// + /// + /// A that represents the name of the header to get. + /// + public override string Get (string name) + { + return base.Get (name); + } + + /// + /// Gets the enumerator used to iterate through the . + /// + /// + /// An instance used to iterate through + /// the . + /// + public override IEnumerator GetEnumerator () + { + return base.GetEnumerator (); + } + + /// + /// Get the header name at the specified position in the collection. + /// + /// + /// A that receives the header name. + /// + /// + /// An is the zero-based index of the key to get from the collection. + /// + public override string GetKey (int index) + { + return base.GetKey (index); + } + + /// + /// Gets an array of header values stored in the specified . + /// + /// + /// An array of that receives the header values. + /// + /// + /// A that represents the name of the header. + /// + public override string [] GetValues (string header) + { + var values = base.GetValues (header); + return values != null && values.Length > 0 + ? values + : null; + } + + /// + /// Gets an array of header values stored in the specified position of + /// the collection. + /// + /// + /// An array of that receives the header values. + /// + /// + /// An is the zero-based index of the header in the collection. + /// + public override string [] GetValues (int index) + { + var values = base.GetValues (index); + return values != null && values.Length > 0 + ? values + : null; + } + + /// + /// Populates the specified with the data needed to serialize + /// the . + /// + /// + /// A that holds the serialized object data. + /// + /// + /// A that specifies the destination for the serialization. + /// + /// + /// is . + /// + [SecurityPermission ( + SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] + public override void GetObjectData ( + SerializationInfo serializationInfo, StreamingContext streamingContext) + { + if (serializationInfo == null) + throw new ArgumentNullException ("serializationInfo"); + + serializationInfo.AddValue ("InternallyCreated", _internallyCreated); + serializationInfo.AddValue ("State", (int) _state); + + var count = Count; + serializationInfo.AddValue ("Count", count); + count.Times ( + i => { + serializationInfo.AddValue (i.ToString (), GetKey (i)); + serializationInfo.AddValue ((count + i).ToString (), Get (i)); + }); + } + + /// + /// Determines whether the specified header can be set for the request. + /// + /// + /// true if the header is restricted; otherwise, false. + /// + /// + /// A that represents the name of the header to test. + /// + /// + /// is or empty. + /// + /// + /// contains invalid characters. + /// + public static bool IsRestricted (string headerName) + { + return isRestricted (checkName (headerName), false); + } + + /// + /// Determines whether the specified header can be set for the request or the response. + /// + /// + /// true if the header is restricted; otherwise, false. + /// + /// + /// A that represents the name of the header to test. + /// + /// + /// true if does the test for the response; for the request, false. + /// + /// + /// is or empty. + /// + /// + /// contains invalid characters. + /// + public static bool IsRestricted (string headerName, bool response) + { + return isRestricted (checkName (headerName), response); + } + + /// + /// Implements the interface and raises the deserialization event + /// when the deserialization is complete. + /// + /// + /// An that represents the source of the deserialization event. + /// + public override void OnDeserialization (object sender) + { + } + + /// + /// Removes the specified request header from the collection. + /// + /// + /// A that represents the request header to remove from + /// the collection. + /// + /// + /// The current instance doesn't allow any of + /// enum values. + /// + /// + /// is a restricted header. + /// + public void Remove (HttpRequestHeader header) + { + doWithCheckingState (removeWithoutCheckingName, Convert (header), null, false, false); + } + + /// + /// Removes the specified response header from the collection. + /// + /// + /// A that represents the response header to remove from + /// the collection. + /// + /// + /// The current instance doesn't allow any of + /// enum values. + /// + /// + /// is a restricted header. + /// + public void Remove (HttpResponseHeader header) + { + doWithCheckingState (removeWithoutCheckingName, Convert (header), null, true, false); + } + + /// + /// Removes the specified header from the collection. + /// + /// + /// A that represents the name of the header to remove from the collection. + /// + /// + /// is or empty. + /// + /// + /// + /// contains invalid characters. + /// + /// + /// -or- + /// + /// + /// is a restricted header name. + /// + /// + /// + /// The current instance doesn't allow the header + /// . + /// + public override void Remove (string name) + { + doWithCheckingState (removeWithoutCheckingName, checkName (name), null, false); + } + + /// + /// Sets the specified request header to the specified value. + /// + /// + /// A that represents the request header to set. + /// + /// + /// A that represents the value of the request header to set. + /// + /// + /// The current instance doesn't allow any of + /// enum values. + /// + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// contains invalid characters. + /// + /// + /// + /// The length of is greater than 65535. + /// + public void Set (HttpRequestHeader header, string value) + { + doWithCheckingState (setWithoutCheckingName, Convert (header), value, false, true); + } + + /// + /// Sets the specified response header to the specified value. + /// + /// + /// A that represents the response header to set. + /// + /// + /// A that represents the value of the response header to set. + /// + /// + /// The current instance doesn't allow any of + /// enum values. + /// + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// contains invalid characters. + /// + /// + /// + /// The length of is greater than 65535. + /// + public void Set (HttpResponseHeader header, string value) + { + doWithCheckingState (setWithoutCheckingName, Convert (header), value, true, true); + } + + /// + /// Sets the specified header to the specified value. + /// + /// + /// A that represents the name of the header to set. + /// + /// + /// A that represents the value of the header to set. + /// + /// + /// is or empty. + /// + /// + /// + /// or contains invalid characters. + /// + /// + /// -or- + /// + /// + /// is a restricted header name. + /// + /// + /// + /// The length of is greater than 65535. + /// + /// + /// The current instance doesn't allow the header + /// . + /// + public override void Set (string name, string value) + { + doWithCheckingState (setWithoutCheckingName, checkName (name), value, true); + } + + /// + /// Converts the current to an array of . + /// + /// + /// An array of that receives the converted current + /// . + /// + public byte [] ToByteArray () + { + return Encoding.UTF8.GetBytes (ToString ()); + } + + /// + /// Returns a that represents the current + /// . + /// + /// + /// A that represents the current . + /// + public override string ToString () + { + var buffer = new StringBuilder (); + Count.Times (i => buffer.AppendFormat ("{0}: {1}\r\n", GetKey (i), Get (i))); + + return buffer.Append ("\r\n").ToString (); + } + + #endregion + + #region Explicit Interface Implementation + + /// + /// Populates the specified with the data needed to serialize + /// the current . + /// + /// + /// A that holds the serialized object data. + /// + /// + /// A that specifies the destination for the serialization. + /// + /// + /// is . + /// + [SecurityPermission ( + SecurityAction.LinkDemand, + Flags = SecurityPermissionFlag.SerializationFormatter, + SerializationFormatter = true)] + void ISerializable.GetObjectData ( + SerializationInfo serializationInfo, StreamingContext streamingContext) + { + GetObjectData (serializationInfo, streamingContext); + } + + #endregion + } }