//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Net { using System.Net.Cache; using System.Collections; using System.Collections.Specialized; using System.Text; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Globalization; using System.Security.Permissions; using System.Diagnostics.CodeAnalysis; internal enum WebHeaderCollectionType : ushort { Unknown, WebRequest, WebResponse, HttpWebRequest, HttpWebResponse, HttpListenerRequest, HttpListenerResponse, FtpWebRequest, FtpWebResponse, FileWebRequest, FileWebResponse, } // // HttpHeaders - this is our main HttpHeaders object, // which is a simple collection of name-value pairs, // along with additional methods that provide HTTP parsing // collection to sendable buffer capablities and other enhansments // We also provide validation of what headers are allowed to be added. // /// /// /// Contains protocol headers associated with a /// request or response. /// /// [ComVisible(true), Serializable] public class WebHeaderCollection : NameValueCollection, ISerializable { // // Data and Constants // private const int ApproxAveHeaderLineSize = 30; private const int ApproxHighAvgNumHeaders = 16; private static readonly HeaderInfoTable HInfo = new HeaderInfoTable(); // // Common Headers - used only when receiving a response, and internally. If the user ever requests a header, // all the common headers are moved into the hashtable. // private string[] m_CommonHeaders; private int m_NumCommonHeaders; // Grouped by first character, so lookup is faster. The table s_CommonHeaderHints maps first letters to indexes in this array. // After first character, sort by decreasing length. It's ok if two headers have the same first character and length. private static readonly string[] s_CommonHeaderNames = new string[] { HttpKnownHeaderNames.AcceptRanges, // "Accept-Ranges" 13 HttpKnownHeaderNames.ContentLength, // "Content-Length" 14 HttpKnownHeaderNames.CacheControl, // "Cache-Control" 13 HttpKnownHeaderNames.ContentType, // "Content-Type" 12 HttpKnownHeaderNames.Date, // "Date" 4 HttpKnownHeaderNames.Expires, // "Expires" 7 HttpKnownHeaderNames.ETag, // "ETag" 4 HttpKnownHeaderNames.LastModified, // "Last-Modified" 13 HttpKnownHeaderNames.Location, // "Location" 8 HttpKnownHeaderNames.ProxyAuthenticate, // "Proxy-Authenticate" 18 HttpKnownHeaderNames.P3P, // "P3P" 3 HttpKnownHeaderNames.SetCookie2, // "Set-Cookie2" 11 HttpKnownHeaderNames.SetCookie, // "Set-Cookie" 10 HttpKnownHeaderNames.Server, // "Server" 6 HttpKnownHeaderNames.Via, // "Via" 3 HttpKnownHeaderNames.WWWAuthenticate, // "WWW-Authenticate" 16 HttpKnownHeaderNames.XAspNetVersion, // "X-AspNet-Version" 16 HttpKnownHeaderNames.XPoweredBy, // "X-Powered-By" 12 "[" }; // This sentinel will never match. (This character isn't in the hint table.) // Mask off all but the bottom five bits, and look up in this array. private static readonly sbyte[] s_CommonHeaderHints = new sbyte[] { -1, 0, -1, 1, 4, 5, -1, -1, // - a b c d e f g -1, -1, -1, -1, 7, -1, -1, -1, // h i j k l m n o 9, -1, -1, 11, -1, -1, 14, 15, // p q r s t u v w 16, -1, -1, -1, -1, -1, -1, -1 }; // x y z [ - - - - private const int c_AcceptRanges = 0; private const int c_ContentLength = 1; private const int c_CacheControl = 2; private const int c_ContentType = 3; private const int c_Date = 4; private const int c_Expires = 5; private const int c_ETag = 6; private const int c_LastModified = 7; private const int c_Location = 8; private const int c_ProxyAuthenticate = 9; private const int c_P3P = 10; private const int c_SetCookie2 = 11; private const int c_SetCookie = 12; private const int c_Server = 13; private const int c_Via = 14; private const int c_WwwAuthenticate = 15; private const int c_XAspNetVersion = 16; private const int c_XPoweredBy = 17; // Easy fast lookups for common headers. More can be added. internal string ContentLength { get { return m_CommonHeaders != null ? m_CommonHeaders[c_ContentLength] : Get(s_CommonHeaderNames[c_ContentLength]); } } internal string CacheControl { get { return m_CommonHeaders != null ? m_CommonHeaders[c_CacheControl] : Get(s_CommonHeaderNames[c_CacheControl]); } } internal string ContentType { get { return m_CommonHeaders != null ? m_CommonHeaders[c_ContentType] : Get(s_CommonHeaderNames[c_ContentType]); } } internal string Date { get { return m_CommonHeaders != null ? m_CommonHeaders[c_Date] : Get(s_CommonHeaderNames[c_Date]); } } internal string Expires { get { return m_CommonHeaders != null ? m_CommonHeaders[c_Expires] : Get(s_CommonHeaderNames[c_Expires]); } } internal string ETag { get { return m_CommonHeaders != null ? m_CommonHeaders[c_ETag] : Get(s_CommonHeaderNames[c_ETag]); } } internal string LastModified { get { return m_CommonHeaders != null ? m_CommonHeaders[c_LastModified] : Get(s_CommonHeaderNames[c_LastModified]); } } internal string Location { get { string location = m_CommonHeaders != null ? m_CommonHeaders[c_Location] : Get(s_CommonHeaderNames[c_Location]); // The normal header parser just casts bytes to chars. Check if there is a UTF8 host name. return HeaderEncoding.DecodeUtf8FromString(location); } } internal string ProxyAuthenticate { get { return m_CommonHeaders != null ? m_CommonHeaders[c_ProxyAuthenticate] : Get(s_CommonHeaderNames[c_ProxyAuthenticate]); } } internal string SetCookie2 { get { return m_CommonHeaders != null ? m_CommonHeaders[c_SetCookie2] : Get(s_CommonHeaderNames[c_SetCookie2]); } } internal string SetCookie { get { return m_CommonHeaders != null ? m_CommonHeaders[c_SetCookie] : Get(s_CommonHeaderNames[c_SetCookie]); } } internal string Server { get { return m_CommonHeaders != null ? m_CommonHeaders[c_Server] : Get(s_CommonHeaderNames[c_Server]); } } internal string Via { get { return m_CommonHeaders != null ? m_CommonHeaders[c_Via] : Get(s_CommonHeaderNames[c_Via]); } } private void NormalizeCommonHeaders() { if (m_CommonHeaders == null) return; for (int i = 0; i < m_CommonHeaders.Length; i++) if (m_CommonHeaders[i] != null) InnerCollection.Add(s_CommonHeaderNames[i], m_CommonHeaders[i]); m_CommonHeaders = null; m_NumCommonHeaders = 0; } // // To ensure C++ and IL callers can't pollute the underlying collection by calling overridden base members directly, we // will use a member collection instead. private NameValueCollection m_InnerCollection; private NameValueCollection InnerCollection { get { if (m_InnerCollection == null) m_InnerCollection = new NameValueCollection(ApproxHighAvgNumHeaders, CaseInsensitiveAscii.StaticInstance); return m_InnerCollection; } } // this is the object that created the header collection. private WebHeaderCollectionType m_Type; #if MONO internal bool AllowMultiValues (string name) { return HInfo[name].AllowMultiValues; } #endif #if !FEATURE_PAL || MONO private bool AllowHttpRequestHeader { get { if (m_Type==WebHeaderCollectionType.Unknown) { m_Type = WebHeaderCollectionType.WebRequest; } return m_Type==WebHeaderCollectionType.WebRequest || m_Type==WebHeaderCollectionType.HttpWebRequest || m_Type==WebHeaderCollectionType.HttpListenerRequest; } } internal bool AllowHttpResponseHeader { get { if (m_Type==WebHeaderCollectionType.Unknown) { m_Type = WebHeaderCollectionType.WebResponse; } return m_Type==WebHeaderCollectionType.WebResponse || m_Type==WebHeaderCollectionType.HttpWebResponse || m_Type==WebHeaderCollectionType.HttpListenerResponse; } } public string this[HttpRequestHeader header] { get { if (!AllowHttpRequestHeader) { throw new InvalidOperationException(SR.GetString(SR.net_headers_req)); } return this[UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header)]; } set { if (!AllowHttpRequestHeader) { throw new InvalidOperationException(SR.GetString(SR.net_headers_req)); } this[UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header)] = value; } } public string this[HttpResponseHeader header] { get { if (!AllowHttpResponseHeader) { throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp)); } // Some of these can be mapped to Common Headers. Other cases can be added as needed for perf. if (m_CommonHeaders != null) { switch (header) { case HttpResponseHeader.ProxyAuthenticate: return m_CommonHeaders[c_ProxyAuthenticate]; case HttpResponseHeader.WwwAuthenticate: return m_CommonHeaders[c_WwwAuthenticate]; } } return this[UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header)]; } set { if (!AllowHttpResponseHeader) { throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp)); } if (m_Type==WebHeaderCollectionType.HttpListenerResponse) { if (value!=null && value.Length>ushort.MaxValue) { throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue)); } } this[UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header)] = value; } } public void Add(HttpRequestHeader header, string value) { if (!AllowHttpRequestHeader) { throw new InvalidOperationException(SR.GetString(SR.net_headers_req)); } this.Add(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header), value); } public void Add(HttpResponseHeader header, string value) { if (!AllowHttpResponseHeader) { throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp)); } if (m_Type==WebHeaderCollectionType.HttpListenerResponse) { if (value!=null && value.Length>ushort.MaxValue) { throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue)); } } this.Add(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header), value); } public void Set(HttpRequestHeader header, string value) { if (!AllowHttpRequestHeader) { throw new InvalidOperationException(SR.GetString(SR.net_headers_req)); } this.Set(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header), value); } public void Set(HttpResponseHeader header, string value) { if (!AllowHttpResponseHeader) { throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp)); } if (m_Type==WebHeaderCollectionType.HttpListenerResponse) { if (value!=null && value.Length>ushort.MaxValue) { throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue)); } } this.Set(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header), value); } internal void SetInternal(HttpResponseHeader header, string value) { if (!AllowHttpResponseHeader) { throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp)); } if (m_Type==WebHeaderCollectionType.HttpListenerResponse) { if (value!=null && value.Length>ushort.MaxValue) { throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue)); } } this.SetInternal(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header), value); } public void Remove(HttpRequestHeader header) { if (!AllowHttpRequestHeader) { throw new InvalidOperationException(SR.GetString(SR.net_headers_req)); } this.Remove(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header)); } public void Remove(HttpResponseHeader header) { if (!AllowHttpResponseHeader) { throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp)); } this.Remove(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header)); } #endif // !FEATURE_PAL // In general, HttpWebResponse headers aren't modified, so these methods don't support common headers. /// /// [To be supplied.] /// protected void AddWithoutValidate(string headerName, string headerValue) { headerName = CheckBadChars(headerName, false); headerValue = CheckBadChars(headerValue, true); GlobalLog.Print("WebHeaderCollection::AddWithoutValidate() calling InnerCollection.Add() key:[" + headerName + "], value:[" + headerValue + "]"); if (m_Type==WebHeaderCollectionType.HttpListenerResponse) { if (headerValue!=null && headerValue.Length>ushort.MaxValue) { throw new ArgumentOutOfRangeException("headerValue", headerValue, SR.GetString(SR.net_headers_toolong, ushort.MaxValue)); } } NormalizeCommonHeaders(); InvalidateCachedArrays(); InnerCollection.Add(headerName, headerValue); } internal void SetAddVerified(string name, string value) { if(HInfo[name].AllowMultiValues) { GlobalLog.Print("WebHeaderCollection::SetAddVerified() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]"); NormalizeCommonHeaders(); InvalidateCachedArrays(); InnerCollection.Add(name, value); } else { GlobalLog.Print("WebHeaderCollection::SetAddVerified() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]"); NormalizeCommonHeaders(); InvalidateCachedArrays(); InnerCollection.Set(name, value); } } // Below three methods are for fast headers manipulation, bypassing all the checks internal void AddInternal(string name, string value) { GlobalLog.Print("WebHeaderCollection::AddInternal() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]"); NormalizeCommonHeaders(); InvalidateCachedArrays(); InnerCollection.Add(name, value); } internal void ChangeInternal(string name, string value) { GlobalLog.Print("WebHeaderCollection::ChangeInternal() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]"); NormalizeCommonHeaders(); InvalidateCachedArrays(); InnerCollection.Set(name, value); } internal void RemoveInternal(string name) { GlobalLog.Print("WebHeaderCollection::RemoveInternal() calling InnerCollection.Remove() key:[" + name + "]"); NormalizeCommonHeaders(); if (m_InnerCollection != null) { InvalidateCachedArrays(); m_InnerCollection.Remove(name); } } internal void CheckUpdate(string name, string value) { value = CheckBadChars(value, true); ChangeInternal(name, value); } // This even faster one can be used to add headers when it's known not to be a common header or that common headers aren't active. private void AddInternalNotCommon(string name, string value) { GlobalLog.Print("WebHeaderCollection::AddInternalNotCommon() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]"); InvalidateCachedArrays(); InnerCollection.Add(name, value); } private static readonly char[] HttpTrimCharacters = new char[]{(char)0x09,(char)0xA,(char)0xB,(char)0xC,(char)0xD,(char)0x20}; // // CheckBadChars - throws on invalid chars to be not found in header name/value // internal static string CheckBadChars(string name, bool isHeaderValue) { if (name == null || name.Length == 0) { // emtpy name is invlaid if (!isHeaderValue) { throw name == null ? new ArgumentNullException("name") : new ArgumentException(SR.GetString(SR.net_emptystringcall, "name"), "name"); } //empty value is OK return string.Empty; } if (isHeaderValue) { // VALUE check //Trim spaces from both ends name = name.Trim(HttpTrimCharacters); //First, check for correctly formed multi-line value //Second, check for absenece of CTL characters int crlf = 0; for(int i = 0; i < name.Length; ++i) { char c = (char) (0x000000ff & (uint) name[i]); switch (crlf) { case 0: if (c == '\r') { crlf = 1; } else if (c == '\n') { // Technically this is bad HTTP. But it would be a breaking change to throw here. // Is there an exploit? crlf = 2; } else if (c == 127 || (c < ' ' && c != '\t')) { throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidControlChars), "value"); } break; case 1: if (c == '\n') { crlf = 2; break; } throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidCRLFChars), "value"); case 2: if (c == ' ' || c == '\t') { crlf = 0; break; } throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidCRLFChars), "value"); } } if (crlf != 0) { throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidCRLFChars), "value"); } } else { // NAME check //First, check for absence of separators and spaces if (name.IndexOfAny(ValidationHelper.InvalidParamChars) != -1) { throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidHeaderChars), "name"); } //Second, check for non CTL ASCII-7 characters (32-126) if (ContainsNonAsciiChars(name)) { throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidNonAsciiChars), "name"); } } return name; } internal static bool IsValidToken(string token) { return (token.Length > 0) && (token.IndexOfAny(ValidationHelper.InvalidParamChars) == -1) && !ContainsNonAsciiChars(token); } internal static bool ContainsNonAsciiChars(string token) { for (int i = 0; i < token.Length; ++i) { if ((token[i] < 0x20) || (token[i] > 0x7e)) { return true; } } return false; } // // ThrowOnRestrictedHeader - generates an error if the user, // passed in a reserved string as the header name // internal void ThrowOnRestrictedHeader(string headerName) { if (m_Type == WebHeaderCollectionType.HttpWebRequest) { if (HInfo[headerName].IsRequestRestricted) { throw new ArgumentException(SR.GetString(SR.net_headerrestrict, headerName), "name"); } } else if (m_Type == WebHeaderCollectionType.HttpListenerResponse) { if (HInfo[headerName].IsResponseRestricted) { throw new ArgumentException(SR.GetString(SR.net_headerrestrict, headerName), "name"); } } } // // Our Public METHOD set, most are inherited from NameValueCollection, // not all methods from NameValueCollection are listed, even though usable - // // this includes // Add(name, value) // Add(header) // this[name] {set, get} // Remove(name), returns bool // Remove(name), returns void // Set(name, value) // ToString() // // SplitValue(name, value) // ToByteArray() // ParseHeaders(char [], ...) // ParseHeaders(byte [], ...) // // Add more headers; if "name" already exists it will // add concatenated value // Add - // Routine Description: // Adds headers with validation to see if they are "proper" headers. // Will cause header to be concat to existing if already found. // If the header is a special header, listed in RestrictedHeaders object, // then this call will cause an exception indication as such. // Arguments: // name - header-name to add // value - header-value to add, a header is already there, will concat this value // Return Value: // None /// /// /// Adds a new header with the indicated name and value. /// /// public override void Add(string name, string value) { name = CheckBadChars(name, false); ThrowOnRestrictedHeader(name); value = CheckBadChars(value, true); GlobalLog.Print("WebHeaderCollection::Add() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]"); if (m_Type==WebHeaderCollectionType.HttpListenerResponse) { if (value!=null && value.Length>ushort.MaxValue) { throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue)); } } NormalizeCommonHeaders(); InvalidateCachedArrays(); InnerCollection.Add(name, value); } // Add - // Routine Description: // Adds headers with validation to see if they are "proper" headers. // Assumes a combined a "Name: Value" string, and parses the two parts out. // Will cause header to be concat to existing if already found. // If the header is a speical header, listed in RestrictedHeaders object, // then this call will cause an exception indication as such. // Arguments: // header - header name: value pair // Return Value: // None /// /// /// Adds the indicated header. /// /// public void Add(string header) { if ( ValidationHelper.IsBlankString(header) ) { throw new ArgumentNullException("header"); } int colpos = header.IndexOf(':'); // check for badly formed header passed in if (colpos<0) { throw new ArgumentException(SR.GetString(SR.net_WebHeaderMissingColon), "header"); } string name = header.Substring(0, colpos); string value = header.Substring(colpos+1); name = CheckBadChars(name, false); ThrowOnRestrictedHeader(name); value = CheckBadChars(value, true); GlobalLog.Print("WebHeaderCollection::Add(" + header + ") calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]"); if (m_Type==WebHeaderCollectionType.HttpListenerResponse) { if (value!=null && value.Length>ushort.MaxValue) { throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue)); } } NormalizeCommonHeaders(); InvalidateCachedArrays(); InnerCollection.Add(name, value); } // Set - // Routine Description: // Sets headers with validation to see if they are "proper" headers. // If the header is a special header, listed in RestrictedHeaders object, // then this call will cause an exception indication as such. // Arguments: // name - header-name to set // value - header-value to set // Return Value: // None /// /// /// Sets the specified header to the specified value. /// /// public override void Set(string name, string value) { if (ValidationHelper.IsBlankString(name)) { throw new ArgumentNullException("name"); } name = CheckBadChars(name, false); ThrowOnRestrictedHeader(name); value = CheckBadChars(value, true); GlobalLog.Print("WebHeaderCollection::Set() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]"); if (m_Type==WebHeaderCollectionType.HttpListenerResponse) { if (value!=null && value.Length>ushort.MaxValue) { throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue)); } } NormalizeCommonHeaders(); InvalidateCachedArrays(); InnerCollection.Set(name, value); } internal void SetInternal(string name, string value) { if (ValidationHelper.IsBlankString(name)) { throw new ArgumentNullException("name"); } name = CheckBadChars(name, false); value = CheckBadChars(value, true); GlobalLog.Print("WebHeaderCollection::Set() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]"); if (m_Type==WebHeaderCollectionType.HttpListenerResponse) { if (value!=null && value.Length>ushort.MaxValue) { throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue)); } } NormalizeCommonHeaders(); InvalidateCachedArrays(); InnerCollection.Set(name, value); } // Remove - // Routine Description: // Removes give header with validation to see if they are "proper" headers. // If the header is a speical header, listed in RestrictedHeaders object, // then this call will cause an exception indication as such. // Arguments: // name - header-name to remove // Return Value: // None /// /// Removes the specified header. /// public override void Remove(string name) { if ( ValidationHelper.IsBlankString(name) ) { throw new ArgumentNullException("name"); } ThrowOnRestrictedHeader(name); name = CheckBadChars(name, false); GlobalLog.Print("WebHeaderCollection::Remove() calling InnerCollection.Remove() key:[" + name + "]"); NormalizeCommonHeaders(); if (m_InnerCollection != null) { InvalidateCachedArrays(); m_InnerCollection.Remove(name); } } // GetValues // Routine Description: // This method takes a header name and returns a string array representing // the individual values for that headers. For example, if the headers // contained the line Accept: text/plain, text/html then // GetValues("Accept") would return an array of two strings: "text/plain" // and "text/html". // Arguments: // header - Name of the header. // Return Value: // string[] - array of parsed string objects /// /// /// Gets an array of header values stored in a /// header. /// /// public override string[] GetValues(string header) { // This method doesn't work with common headers. Dump common headers into the pool. NormalizeCommonHeaders(); // First get the information about the header and the values for // the header. HeaderInfo Info = HInfo[header]; string[] Values = InnerCollection.GetValues(header); // If we have no information about the header or it doesn't allow // multiple values, just return the values. if (Info == null || Values == null || !Info.AllowMultiValues) { return Values; } // Here we have a multi value header. We need to go through // each entry in the multi values array, and if an entry itself // has multiple values we'll need to combine those in. // // We do some optimazation here, where we try not to copy the // values unless there really is one that have multiple values. string[] TempValues; ArrayList ValueList = null; int i; for (i = 0; i < Values.Length; i++) { // Parse this value header. TempValues = Info.Parser(Values[i]); // If we don't have an array list yet, see if this // value has multiple values. if (ValueList == null) { // See if it has multiple values. if (TempValues.Length > 1) { // It does, so we need to create an array list that // represents the Values, then trim out this one and // the ones after it that haven't been parsed yet. ValueList = new ArrayList(Values); ValueList.RemoveRange(i, Values.Length - i); ValueList.AddRange(TempValues); } } else { // We already have an ArrayList, so just add the values. ValueList.AddRange(TempValues); } } // See if we have an ArrayList. If we don't, just return the values. // Otherwise convert the ArrayList to a string array and return that. if (ValueList != null) { string[] ReturnArray = new string[ValueList.Count]; ValueList.CopyTo(ReturnArray); return ReturnArray; } return Values; } // ToString() - // Routine Description: // Generates a string representation of the headers, that is ready to be sent except for it being in string format: // the format looks like: // // Header-Name: Header-Value\r\n // Header-Name2: Header-Value2\r\n // ... // Header-NameN: Header-ValueN\r\n // \r\n // // Uses the string builder class to Append the elements together. // Arguments: // None. // Return Value: // string /// /// /// /// Obsolete. /// /// public override string ToString() { string result = GetAsString(this, false, false); GlobalLog.Print("WebHeaderCollection::ToString: \r\n" + result); return result; } internal string ToString(bool forTrace) { return GetAsString(this, false, true); } // // if winInetCompat = true then it will not insert spaces after ':' // and it will output "~U" header first // internal static string GetAsString(NameValueCollection cc, bool winInetCompat, bool forTrace) { #if FEATURE_PAL if (winInetCompat) { throw new InvalidOperationException(); } #endif // FEATURE_PAL if (cc == null || cc.Count == 0) { return "\r\n"; } StringBuilder sb = new StringBuilder(ApproxAveHeaderLineSize*cc.Count); string statusLine; statusLine = cc[string.Empty]; if (statusLine != null) { sb.Append(statusLine).Append("\r\n"); } for (int i = 0; i < cc.Count ; i++) { string key = cc.GetKey(i) as string; string val = cc.Get(i) as string; /* if (forTrace) { // Put a condition here that if we are using basic auth, // we shouldn't put the authorization header. Otherwise // the password will get saved in the trace. if (using basic) continue; } */ if (ValidationHelper.IsBlankString(key)) { continue; } sb.Append(key); if (winInetCompat) { sb.Append(':'); } else { sb.Append(": "); } sb.Append(val).Append("\r\n"); } if (!forTrace) sb.Append("\r\n"); return sb.ToString(); } // ToByteArray() - // Routine Description: // Generates a byte array representation of the headers, that is ready to be sent. // So it Serializes our headers into a byte array suitable for sending over the net. // // the format looks like: // // Header-Name1: Header-Value1\r\n // Header-Name2: Header-Value2\r\n // ... // Header-NameN: Header-ValueN\r\n // \r\n // // Uses the ToString() method to generate, and then performs conversion. // // Performance Note: Why are we not doing a single copy/covert run? // As the code before used to know the size of the output! // Because according to Demitry, its cheaper to copy the headers twice, // then it is to call the UNICODE to ANSI conversion code many times. // Arguments: // None. // Return Value: // byte [] - array of bytes values /// /// /// /// Obsolete. /// /// public byte[] ToByteArray() { // Make sure the buffer is big enough. string tempStr = ToString(); // // Use the string of headers, convert to Char Array, // then convert to Bytes, // serializing finally into the buffer, along the way. // byte[] buffer = HeaderEncoding.GetBytes(tempStr); return buffer; } /// /// Tests if access to the HTTP header with the provided name is accessible for setting. /// public static bool IsRestricted(string headerName) { return IsRestricted(headerName, false); } public static bool IsRestricted(string headerName, bool response) { return response ? HInfo[CheckBadChars(headerName, false)].IsResponseRestricted : HInfo[CheckBadChars(headerName, false)].IsRequestRestricted; } /// /// /// Initializes a new instance of the /// class. /// /// public WebHeaderCollection() : base(DBNull.Value) { } internal WebHeaderCollection(WebHeaderCollectionType type) : base(DBNull.Value) { m_Type = type; if (type == WebHeaderCollectionType.HttpWebResponse) m_CommonHeaders = new string[s_CommonHeaderNames.Length - 1]; // Minus one for the sentinel. } //This is for Cache internal WebHeaderCollection(NameValueCollection cc): base(DBNull.Value) { m_InnerCollection = new NameValueCollection(cc.Count + 2, CaseInsensitiveAscii.StaticInstance); int len = cc.Count; for (int i = 0; i < len; ++i) { String key = cc.GetKey(i); String[] values = cc.GetValues(i); if (values != null) { for (int j = 0; j < values.Length; j++) { InnerCollection.Add(key, values[j]); } } else { InnerCollection.Add(key, null); } } } // // ISerializable constructor // /// /// [To be supplied.] /// protected WebHeaderCollection(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(DBNull.Value) { int count = serializationInfo.GetInt32("Count"); m_InnerCollection = new NameValueCollection(count + 2, CaseInsensitiveAscii.StaticInstance); for (int i = 0; i < count; i++) { string headerName = serializationInfo.GetString(i.ToString(NumberFormatInfo.InvariantInfo)); string headerValue = serializationInfo.GetString((i+count).ToString(NumberFormatInfo.InvariantInfo)); GlobalLog.Print("WebHeaderCollection::.ctor(ISerializable) calling InnerCollection.Add() key:[" + headerName + "], value:[" + headerValue + "]"); InnerCollection.Add(headerName, headerValue); } } public override void OnDeserialization(object sender) { // } // // ISerializable method // /// [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)] public override void GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext) { // // for now disregard streamingContext. // NormalizeCommonHeaders(); serializationInfo.AddValue("Count", Count); for (int i = 0; i < Count; i++) { serializationInfo.AddValue(i.ToString(NumberFormatInfo.InvariantInfo), GetKey(i)); serializationInfo.AddValue((i + Count).ToString(NumberFormatInfo.InvariantInfo), Get(i)); } } // we use this static class as a helper class to encode/decode HTTP headers. // what we need is a 1-1 correspondence between a char in the range U+0000-U+00FF // and a byte in the range 0x00-0xFF (which is the range that can hit the network). // The Latin-1 encoding (ISO-88591-1) (GetEncoding(28591)) works for byte[] to string, but is a little slow. // It doesn't work for string -> byte[] because of best-fit-mapping problems. internal static class HeaderEncoding { internal static unsafe string GetString(byte[] bytes, int byteIndex, int byteCount) { fixed(byte* pBytes = bytes) return GetString(pBytes + byteIndex, byteCount); } internal static unsafe string GetString(byte* pBytes, int byteCount) { if (byteCount < 1) return ""; string s = new String('\0', byteCount); fixed (char* pStr = s) { char* pString = pStr; while (byteCount >= 8) { pString[0] = (char) pBytes[0]; pString[1] = (char) pBytes[1]; pString[2] = (char) pBytes[2]; pString[3] = (char) pBytes[3]; pString[4] = (char) pBytes[4]; pString[5] = (char) pBytes[5]; pString[6] = (char) pBytes[6]; pString[7] = (char) pBytes[7]; pString += 8; pBytes += 8; byteCount -= 8; } for (int i = 0; i < byteCount; i++) { pString[i] = (char) pBytes[i]; } } return s; } internal static int GetByteCount(string myString) { return myString.Length; } internal unsafe static void GetBytes(string myString, int charIndex, int charCount, byte[] bytes, int byteIndex) { if (myString.Length==0) { return; } fixed (byte *bufferPointer = bytes) { byte* newBufferPointer = bufferPointer + byteIndex; int finalIndex = charIndex + charCount; while (charIndex (char)255) { return input; // This couldn't have come from the wire, someone assigned it directly. } else if (input[i] > (char)127) { possibleUtf8 = true; break; } } if (possibleUtf8) { byte[] rawBytes = new byte[input.Length]; for (int i = 0; i < input.Length; i++) { if (input[i] > (char)255) { return input; // This couldn't have come from the wire, someone assigned it directly. } rawBytes[i] = (byte)input[i]; } try { // We don't want '?' replacement characters, just fail. Encoding decoder = Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); return decoder.GetString(rawBytes); } catch (ArgumentException) { } // Not actually Utf-8 } return input; } } // ParseHeaders - // Routine Description: // // This code is optimized for the case in which all the headers fit in the buffer. // we support multiple re-entrance, but we won't save intermediate // state, we will just roll back all the parsing done for the current header if we can't // parse a whole one (including multiline) or decide something else ("invalid data" or "done parsing"). // // we're going to cycle through the loop until we // // 1) find an HTTP violation (in this case we return DataParseStatus.Invalid) // 2) we need more data (in this case we return DataParseStatus.NeedMoreData) // 3) we found the end of the headers and the beginning of the entity body (in this case we return DataParseStatus.Done) // // // Arguments: // // buffer - buffer containing the data to be parsed // size - size of the buffer // unparsed - offset of data yet to be parsed // // Return Value: // // DataParseStatus - status of parsing // // Revision: // // 02/13/2001 rewrote the method from scratch. // // BreakPoint: // // b system.dll!System.Net.WebHeaderCollection::ParseHeaders internal unsafe DataParseStatus ParseHeaders( byte[] buffer, int size, ref int unparsed, ref int totalResponseHeadersLength, int maximumResponseHeadersLength, ref WebParseError parseError) { fixed (byte * byteBuffer = buffer) { char ch; // quick check in the boundaries (as we use unsafe pointer) if (buffer.Length < size) { return DataParseStatus.NeedMoreData; } int headerNameStartOffset = -1; int headerNameEndOffset = -1; int headerValueStartOffset = -1; int headerValueEndOffset = -1; int numberOfLf = -1; int index = unparsed; bool spaceAfterLf; string headerMultiLineValue; string headerName; string headerValue; // we need this because this method is entered multiple times. int localTotalResponseHeadersLength = totalResponseHeadersLength; WebParseErrorCode parseErrorCode = WebParseErrorCode.Generic; DataParseStatus parseStatus = DataParseStatus.Invalid; #if TRAVE GlobalLog.Enter("WebHeaderCollection::ParseHeaders(): ANSI size:" + size.ToString() + ", unparsed:" + unparsed.ToString() + " buffer:[" + Encoding.ASCII.GetString(buffer, unparsed, Math.Min(256, size-unparsed)) + "]"); #endif // // according to RFC216 a header can have the following syntax: // // message-header = field-name ":" [ field-value ] // field-name = token // field-value = *( field-content | LWS ) // field-content = // TEXT = // CTL = // SP = // HT = // CR = // LF = // LWS = [CR LF] 1*( SP | HT ) // CHAR = // token = 1* // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) // qdtext = > // quoted-pair = "\" CHAR // // // At each iteration of the following loop we expect to parse a single HTTP header entirely. // for (;;) { // // trim leading whitespaces (LWS) just for extra robustness, in fact if there are leading white spaces then: // 1) it could be that after the status line we might have spaces. handle this. // 2) this should have been detected to be a multiline header so there'll be no spaces and we'll spend some time here. // headerName = string.Empty; headerValue = string.Empty; spaceAfterLf = false; headerMultiLineValue = null; if (Count == 0) { // // so, restrict this extra trimming only on the first header line // while (index < size) { ch = (char) byteBuffer[index]; if (ch == ' ' || ch == '\t') { ++index; if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } } else { break; } } if (index==size) { // // we reached the end of the buffer. ask for more data. // parseStatus = DataParseStatus.NeedMoreData; goto quit; } } // // what we have here is the beginning of a new header // headerNameStartOffset = index; while (index < size) { ch = (char) byteBuffer[index]; if (ch != ':' && ch != '\n') { if (ch > ' ') { // // if there's an illegal character we should return DataParseStatus.Invalid // instead we choose to be flexible, try to trim it, but include it in the string // headerNameEndOffset = index; } ++index; if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } } else { if (ch == ':') { ++index; if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } } break; } } if (index==size) { // // we reached the end of the buffer. ask for more data. // parseStatus = DataParseStatus.NeedMoreData; goto quit; } startOfValue: // // skip all [' ','\t','\r','\n'] characters until HeaderValue starts // if we didn't find any headers yet, we set numberOfLf to 1 // so that we take the '\n' from the status line into account // numberOfLf = (Count == 0 && headerNameEndOffset < 0) ? 1 : 0; while (index=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } } else { break; } } if (numberOfLf==2 || (numberOfLf==1 && !spaceAfterLf)) { // // if we've counted two '\n' we got at the end of the headers even if we're past the end of the buffer // if we've counted one '\n' and the first character after that was a ' ' or a '\t' // no matter if we found a ':' or not, treat this as an empty header name. // goto addHeader; } if (index==size) { // // we reached the end of the buffer. ask for more data. // parseStatus = DataParseStatus.NeedMoreData; goto quit; } headerValueStartOffset = index; while (index ' ') { headerValueEndOffset = index; } ++index; if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } } else { break; } } if (index==size) { // // we reached the end of the buffer. ask for more data. // parseStatus = DataParseStatus.NeedMoreData; goto quit; } // // at this point we found either a '\n' or the end of the headers // hence we are at the end of the Header Line. 4 options: // 1) need more data // 2) if we find two '\n' => end of headers // 3) if we find one '\n' and a ' ' or a '\t' => multiline header // 4) if we find one '\n' and a valid char => next header // numberOfLf = 0; while (index=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } } else { break; } } if (index==size && numberOfLf<2) { // // we reached the end of the buffer but not of the headers. ask for more data. // parseStatus = DataParseStatus.NeedMoreData; goto quit; } addHeader: if (headerValueStartOffset>=0 && headerValueStartOffset>headerNameEndOffset && headerValueEndOffset>=headerValueStartOffset) { // // Encoding fastest way to build the UNICODE string off the byte[] // headerValue = HeaderEncoding.GetString(byteBuffer + headerValueStartOffset, headerValueEndOffset - headerValueStartOffset + 1); } // // if we got here from the beginning of the for loop, headerMultiLineValue will be null // otherwise it will contain the headerValue constructed for the multiline header // add this line as well to it, separated by a single space // headerMultiLineValue = (headerMultiLineValue==null ? headerValue : headerMultiLineValue + " " + headerValue); if (index < size && numberOfLf == 1) { ch = (char) byteBuffer[index]; if (ch == ' ' || ch == '\t') { // // since we found only one Lf and the next header line begins with a Lws, // this is the beginning of a multiline header. // parse the next line into headerValue later it will be added to headerMultiLineValue // ++index; if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } goto startOfValue; } } if (headerNameStartOffset>=0 && headerNameEndOffset>=headerNameStartOffset) { // // Encoding is the fastest way to build the UNICODE string off the byte[] // headerName = HeaderEncoding.GetString(byteBuffer + headerNameStartOffset, headerNameEndOffset - headerNameStartOffset + 1); } // // now it's finally safe to add the header if we have a name for it // if (headerName.Length>0) { // // the base clasee will check for pre-existing headerValue and append // it using commas as indicated in the RFC // GlobalLog.Print("WebHeaderCollection::ParseHeaders() calling AddInternal() key:[" + headerName + "], value:[" + headerMultiLineValue + "]"); AddInternal(headerName, headerMultiLineValue); } // // and update unparsed // totalResponseHeadersLength = localTotalResponseHeadersLength; unparsed = index; if (numberOfLf==2) { parseStatus = DataParseStatus.Done; goto quit; } } // for (;;) quit: GlobalLog.Leave("WebHeaderCollection::ParseHeaders() returning parseStatus:" + parseStatus.ToString()); if (parseStatus == DataParseStatus.Invalid) { parseError.Section = WebParseErrorSection.ResponseHeader; parseError.Code = parseErrorCode; } return parseStatus; } } // // Alternative parsing that follows RFC2616. Like the above, this trims both sides of the header value and replaces // folding with a single space. // private enum RfcChar : byte { High = 0, Reg, Ctl, CR, LF, WS, Colon, Delim } private static RfcChar[] RfcCharMap = new RfcChar[128] { RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.WS, RfcChar.LF, RfcChar.Ctl, RfcChar.Ctl, RfcChar.CR, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.WS, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Colon, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Ctl, }; internal unsafe DataParseStatus ParseHeadersStrict( byte[] buffer, int size, ref int unparsed, ref int totalResponseHeadersLength, int maximumResponseHeadersLength, ref WebParseError parseError) { GlobalLog.Enter("WebHeaderCollection::ParseHeadersStrict(): size:" + size.ToString() + ", unparsed:" + unparsed.ToString() + " buffer:[" + Encoding.ASCII.GetString(buffer, unparsed, Math.Min(256, size-unparsed)) + "]"); WebParseErrorCode parseErrorCode = WebParseErrorCode.Generic; DataParseStatus parseStatus = DataParseStatus.Invalid; int i = unparsed; RfcChar ch; int effectiveSize = maximumResponseHeadersLength <= 0 ? Int32.MaxValue : maximumResponseHeadersLength - totalResponseHeadersLength + i; DataParseStatus sizeError = DataParseStatus.DataTooBig; if (size < effectiveSize) { effectiveSize = size; sizeError = DataParseStatus.NeedMoreData; } // Verify the size. if (i >= effectiveSize) { parseStatus = sizeError; goto quit; } fixed (byte* byteBuffer = buffer) { while (true) { // If this is CRLF, actually we're done. if (byteBuffer[i] == '\r') { if (++i == effectiveSize) { parseStatus = sizeError; goto quit; } if (byteBuffer[i++] == '\n') { totalResponseHeadersLength += i - unparsed; unparsed = i; parseStatus = DataParseStatus.Done; goto quit; } parseStatus = DataParseStatus.Invalid; parseErrorCode = WebParseErrorCode.CrLfError; goto quit; } // Find the header name; only regular characters allowed. int iBeginName = i; for (; i < effectiveSize && (ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) == RfcChar.Reg; i++); if (i == effectiveSize) { parseStatus = sizeError; goto quit; } if (i == iBeginName) { parseStatus = DataParseStatus.Invalid; parseErrorCode = WebParseErrorCode.InvalidHeaderName; goto quit; } // Read to a colon. int iEndName = i - 1; int crlf = 0; // 1 = cr, 2 = crlf for (; i < effectiveSize && (ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) != RfcChar.Colon; i++) { switch (ch) { case RfcChar.WS: if (crlf == 1) { break; } crlf = 0; continue; case RfcChar.CR: if (crlf == 0) { crlf = 1; continue; } break; case RfcChar.LF: if (crlf == 1) { crlf = 2; continue; } break; } parseStatus = DataParseStatus.Invalid; parseErrorCode = WebParseErrorCode.CrLfError; goto quit; } if (i == effectiveSize) { parseStatus = sizeError; goto quit; } if (crlf != 0) { parseStatus = DataParseStatus.Invalid; parseErrorCode = WebParseErrorCode.IncompleteHeaderLine; goto quit; } // Skip the colon. if (++i == effectiveSize) { parseStatus = sizeError; goto quit; } // Read the value. crlf = 3 means in the whitespace after a CRLF int iBeginValue = -1; int iEndValue = -1; StringBuilder valueAccumulator = null; for (; i < effectiveSize && ((ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) == RfcChar.WS || crlf != 2); i++) { switch (ch) { case RfcChar.WS: if (crlf == 1) { break; } if (crlf == 2) { crlf = 3; } continue; case RfcChar.CR: if (crlf == 0) { crlf = 1; continue; } break; case RfcChar.LF: if (crlf == 1) { crlf = 2; continue; } break; case RfcChar.High: case RfcChar.Colon: case RfcChar.Delim: case RfcChar.Reg: if (crlf == 1) { break; } if (crlf == 3) { crlf = 0; if (iBeginValue != -1) { string s = HeaderEncoding.GetString(byteBuffer + iBeginValue, iEndValue - iBeginValue + 1); if (valueAccumulator == null) { valueAccumulator = new StringBuilder(s, s.Length * 5); } else { valueAccumulator.Append(" "); valueAccumulator.Append(s); } } iBeginValue = -1; } if (iBeginValue == -1) { iBeginValue = i; } iEndValue = i; continue; } parseStatus = DataParseStatus.Invalid; parseErrorCode = WebParseErrorCode.CrLfError; goto quit; } if (i == effectiveSize) { parseStatus = sizeError; goto quit; } // Make the value. string sValue = iBeginValue == -1 ? "" : HeaderEncoding.GetString(byteBuffer + iBeginValue, iEndValue - iBeginValue + 1); if (valueAccumulator != null) { if (sValue.Length != 0) { valueAccumulator.Append(" "); valueAccumulator.Append(sValue); } sValue = valueAccumulator.ToString(); } // Make the name. See if it's a common header first. string sName = null; int headerNameLength = iEndName - iBeginName + 1; if (m_CommonHeaders != null) { int iHeader = s_CommonHeaderHints[byteBuffer[iBeginName] & 0x1f]; if (iHeader >= 0) { while (true) { string s = s_CommonHeaderNames[iHeader++]; // Not found if we get to a shorter header or one with a different first character. if (s.Length < headerNameLength || CaseInsensitiveAscii.AsciiToLower[byteBuffer[iBeginName]] != CaseInsensitiveAscii.AsciiToLower[s[0]]) break; // Keep looking if the common header is too long. if (s.Length > headerNameLength) continue; int j; byte* pBuffer = byteBuffer + iBeginName + 1; for (j = 1; j < s.Length; j++) { // Avoid the case-insensitive compare in the common case where they match. if (*(pBuffer++) != s[j] && CaseInsensitiveAscii.AsciiToLower[*(pBuffer - 1)] != CaseInsensitiveAscii.AsciiToLower[s[j]]) break; } if (j == s.Length) { // Set it to the appropriate index. m_NumCommonHeaders++; iHeader--; if (m_CommonHeaders[iHeader] == null) { m_CommonHeaders[iHeader] = sValue; } else { // Don't currently handle combining multiple header instances in the common header case. // Nothing to do but punt them all to the NameValueCollection. NormalizeCommonHeaders(); AddInternalNotCommon(s, sValue); } sName = s; break; } } } } // If it wasn't a common header, add it to the hash. if (sName == null) { sName = HeaderEncoding.GetString(byteBuffer + iBeginName, headerNameLength); AddInternalNotCommon(sName, sValue); } totalResponseHeadersLength += i - unparsed; unparsed = i; } } quit: GlobalLog.Leave("WebHeaderCollection::ParseHeadersStrict() returning parseStatus:" + parseStatus.ToString()); if (parseStatus == DataParseStatus.Invalid) { parseError.Section = WebParseErrorSection.ResponseHeader; parseError.Code = parseErrorCode; } return parseStatus; } // // Keeping this version for backwards compatibility (mostly with reflection). Remove some day, along with the interface // explicit reimplementation. // /// [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase", Justification = "System.dll is still using pre-v4 security model and needs this demand")] [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter, SerializationFormatter=true)] void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext) { GetObjectData(serializationInfo, streamingContext); } // Override Get() to check the common headers. public override string Get(string name) { // In this case, need to make sure name doesn't have any Unicode since it's being used as an index into tables. if (m_CommonHeaders != null && name != null && name.Length > 0 && name[0] < 256) { int iHeader = s_CommonHeaderHints[name[0] & 0x1f]; if (iHeader >= 0) { while (true) { string s = s_CommonHeaderNames[iHeader++]; // Not found if we get to a shorter header or one with a different first character. if (s.Length < name.Length || CaseInsensitiveAscii.AsciiToLower[name[0]] != CaseInsensitiveAscii.AsciiToLower[s[0]]) break; // Keep looking if the common header is too long. if (s.Length > name.Length) continue; int j; for (j = 1; j < s.Length; j++) { // Avoid the case-insensitive compare in the common case where they match. if (name[j] != s[j] && (name[j] > 255 || CaseInsensitiveAscii.AsciiToLower[name[j]] != CaseInsensitiveAscii.AsciiToLower[s[j]])) break; } if (j == s.Length) { // Get the appropriate header. return m_CommonHeaders[iHeader - 1]; } } } } // Fall back to normal lookup. if (m_InnerCollection == null) return null; return m_InnerCollection.Get(name); } // // Additional overrides required to fully orphan the base implementation. // public override IEnumerator GetEnumerator() { NormalizeCommonHeaders(); return new NameObjectKeysEnumerator(InnerCollection); } public override int Count { get { return (m_InnerCollection == null ? 0 : m_InnerCollection.Count) + m_NumCommonHeaders; } } public override KeysCollection Keys { get { NormalizeCommonHeaders(); return InnerCollection.Keys; } } internal override bool InternalHasKeys() { NormalizeCommonHeaders(); if (m_InnerCollection == null) return false; return m_InnerCollection.HasKeys(); } public override string Get(int index) { NormalizeCommonHeaders(); return InnerCollection.Get(index); } public override string[] GetValues(int index) { NormalizeCommonHeaders(); return InnerCollection.GetValues(index); } public override string GetKey(int index) { NormalizeCommonHeaders(); return InnerCollection.GetKey(index); } public override string[] AllKeys { get { NormalizeCommonHeaders(); return InnerCollection.AllKeys; } } public override void Clear() { m_CommonHeaders = null; m_NumCommonHeaders = 0; InvalidateCachedArrays(); if (m_InnerCollection != null) m_InnerCollection.Clear(); } } // class WebHeaderCollection internal class CaseInsensitiveAscii : IEqualityComparer, IComparer{ // ASCII char ToLower table internal static readonly CaseInsensitiveAscii StaticInstance = new CaseInsensitiveAscii(); internal static readonly byte[] AsciiToLower = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99,100,101, // 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 102,103,104,105,106,107,108,109,110,111, // 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 112,113,114,115,116,117,118,119,120,121, // 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, // 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100,101,102,103,104,105,106,107,108,109, 110,111,112,113,114,115,116,117,118,119, 120,121,122,123,124,125,126,127,128,129, 130,131,132,133,134,135,136,137,138,139, 140,141,142,143,144,145,146,147,148,149, 150,151,152,153,154,155,156,157,158,159, 160,161,162,163,164,165,166,167,168,169, 170,171,172,173,174,175,176,177,178,179, 180,181,182,183,184,185,186,187,188,189, 190,191,192,193,194,195,196,197,198,199, 200,201,202,203,204,205,206,207,208,209, 210,211,212,213,214,215,216,217,218,219, 220,221,222,223,224,225,226,227,228,229, 230,231,232,233,234,235,236,237,238,239, 240,241,242,243,244,245,246,247,248,249, 250,251,252,253,254,255 }; // ASCII string case insensitive hash function public int GetHashCode(object myObject) { string myString = myObject as string; if (myObject == null) { return 0; } int myHashCode = myString.Length; if (myHashCode == 0) { return 0; } myHashCode ^= AsciiToLower[(byte)myString[0]]<<24 ^ AsciiToLower[(byte)myString[myHashCode-1]]<<16; return myHashCode; } // ASCII string case insensitive comparer public int Compare(object firstObject, object secondObject) { string firstString = firstObject as string; string secondString = secondObject as string; if (firstString==null) { return secondString == null ? 0 : -1; } if (secondString == null) { return 1; } int result = firstString.Length - secondString.Length; int comparisons = result > 0 ? secondString.Length : firstString.Length; int difference, index = 0; while ( index < comparisons ) { difference = (int)(AsciiToLower[ firstString[index] ] - AsciiToLower[ secondString[index] ]); if ( difference != 0 ) { result = difference; break; } index++; } return result; } // ASCII string case insensitive hash function int FastGetHashCode(string myString) { int myHashCode = myString.Length; if (myHashCode!=0) { myHashCode ^= AsciiToLower[(byte)myString[0]]<<24 ^ AsciiToLower[(byte)myString[myHashCode-1]]<<16; } return myHashCode; } // ASCII string case insensitive comparer public new bool Equals(object firstObject, object secondObject) { string firstString = firstObject as string; string secondString = secondObject as string; if (firstString==null) { return secondString==null; } if (secondString!=null) { int index = firstString.Length; if (index==secondString.Length) { if (FastGetHashCode(firstString)==FastGetHashCode(secondString)) { int comparisons = firstString.Length; while (index>0) { index--; if (AsciiToLower[firstString[index]]!=AsciiToLower[secondString[index]]) { return false; } } return true; } } } return false; } } internal class HostHeaderString { private bool m_Converted; private string m_String; private byte[] m_Bytes; internal HostHeaderString() { Init(null); } internal HostHeaderString(string s) { Init(s); } private void Init(string s) { m_String = s; m_Converted = false; m_Bytes = null; } private void Convert() { if (m_String != null && !m_Converted) { m_Bytes = Encoding.Default.GetBytes(m_String); string copy = Encoding.Default.GetString(m_Bytes); if (!(string.Compare(m_String, copy, StringComparison.Ordinal) == 0)) { m_Bytes = Encoding.UTF8.GetBytes(m_String); } } } internal string String { get { return m_String; } set { Init(value); } } internal int ByteCount { get { Convert(); return m_Bytes.Length; } } internal byte[] Bytes { get { Convert(); return m_Bytes; } } internal void Copy(byte[] destBytes, int destByteIndex) { Convert(); Array.Copy(m_Bytes, 0, destBytes, destByteIndex, m_Bytes.Length); } } }