1 //------------------------------------------------------------------------------
2 // <copyright file="WebHeaderCollection.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
8 using System.Net.Cache;
9 using System.Collections;
10 using System.Collections.Specialized;
12 using System.Runtime.InteropServices;
13 using System.Runtime.Serialization;
14 using System.Globalization;
15 using System.Security.Permissions;
16 using System.Diagnostics.CodeAnalysis;
18 internal enum WebHeaderCollectionType : ushort {
33 // HttpHeaders - this is our main HttpHeaders object,
34 // which is a simple collection of name-value pairs,
35 // along with additional methods that provide HTTP parsing
36 // collection to sendable buffer capablities and other enhansments
37 // We also provide validation of what headers are allowed to be added.
42 /// Contains protocol headers associated with a
43 /// request or response.
46 [ComVisible(true), Serializable]
47 public class WebHeaderCollection : NameValueCollection, ISerializable {
51 private const int ApproxAveHeaderLineSize = 30;
52 private const int ApproxHighAvgNumHeaders = 16;
53 private static readonly HeaderInfoTable HInfo = new HeaderInfoTable();
56 // Common Headers - used only when receiving a response, and internally. If the user ever requests a header,
57 // all the common headers are moved into the hashtable.
59 private string[] m_CommonHeaders;
60 private int m_NumCommonHeaders;
62 // Grouped by first character, so lookup is faster. The table s_CommonHeaderHints maps first letters to indexes in this array.
63 // After first character, sort by decreasing length. It's ok if two headers have the same first character and length.
64 private static readonly string[] s_CommonHeaderNames = new string[] {
65 HttpKnownHeaderNames.AcceptRanges, // "Accept-Ranges" 13
66 HttpKnownHeaderNames.ContentLength, // "Content-Length" 14
67 HttpKnownHeaderNames.CacheControl, // "Cache-Control" 13
68 HttpKnownHeaderNames.ContentType, // "Content-Type" 12
69 HttpKnownHeaderNames.Date, // "Date" 4
70 HttpKnownHeaderNames.Expires, // "Expires" 7
71 HttpKnownHeaderNames.ETag, // "ETag" 4
72 HttpKnownHeaderNames.LastModified, // "Last-Modified" 13
73 HttpKnownHeaderNames.Location, // "Location" 8
74 HttpKnownHeaderNames.ProxyAuthenticate, // "Proxy-Authenticate" 18
75 HttpKnownHeaderNames.P3P, // "P3P" 3
76 HttpKnownHeaderNames.SetCookie2, // "Set-Cookie2" 11
77 HttpKnownHeaderNames.SetCookie, // "Set-Cookie" 10
78 HttpKnownHeaderNames.Server, // "Server" 6
79 HttpKnownHeaderNames.Via, // "Via" 3
80 HttpKnownHeaderNames.WWWAuthenticate, // "WWW-Authenticate" 16
81 HttpKnownHeaderNames.XAspNetVersion, // "X-AspNet-Version" 16
82 HttpKnownHeaderNames.XPoweredBy, // "X-Powered-By" 12
83 "[" }; // This sentinel will never match. (This character isn't in the hint table.)
85 // Mask off all but the bottom five bits, and look up in this array.
86 private static readonly sbyte[] s_CommonHeaderHints = new sbyte[] {
87 -1, 0, -1, 1, 4, 5, -1, -1, // - a b c d e f g
88 -1, -1, -1, -1, 7, -1, -1, -1, // h i j k l m n o
89 9, -1, -1, 11, -1, -1, 14, 15, // p q r s t u v w
90 16, -1, -1, -1, -1, -1, -1, -1 }; // x y z [ - - - -
92 private const int c_AcceptRanges = 0;
93 private const int c_ContentLength = 1;
94 private const int c_CacheControl = 2;
95 private const int c_ContentType = 3;
96 private const int c_Date = 4;
97 private const int c_Expires = 5;
98 private const int c_ETag = 6;
99 private const int c_LastModified = 7;
100 private const int c_Location = 8;
101 private const int c_ProxyAuthenticate = 9;
102 private const int c_P3P = 10;
103 private const int c_SetCookie2 = 11;
104 private const int c_SetCookie = 12;
105 private const int c_Server = 13;
106 private const int c_Via = 14;
107 private const int c_WwwAuthenticate = 15;
108 private const int c_XAspNetVersion = 16;
109 private const int c_XPoweredBy = 17;
111 // Easy fast lookups for common headers. More can be added.
112 internal string ContentLength
116 return m_CommonHeaders != null ? m_CommonHeaders[c_ContentLength] : Get(s_CommonHeaderNames[c_ContentLength]);
120 internal string CacheControl
124 return m_CommonHeaders != null ? m_CommonHeaders[c_CacheControl] : Get(s_CommonHeaderNames[c_CacheControl]);
128 internal string ContentType
132 return m_CommonHeaders != null ? m_CommonHeaders[c_ContentType] : Get(s_CommonHeaderNames[c_ContentType]);
140 return m_CommonHeaders != null ? m_CommonHeaders[c_Date] : Get(s_CommonHeaderNames[c_Date]);
144 internal string Expires
148 return m_CommonHeaders != null ? m_CommonHeaders[c_Expires] : Get(s_CommonHeaderNames[c_Expires]);
156 return m_CommonHeaders != null ? m_CommonHeaders[c_ETag] : Get(s_CommonHeaderNames[c_ETag]);
160 internal string LastModified
164 return m_CommonHeaders != null ? m_CommonHeaders[c_LastModified] : Get(s_CommonHeaderNames[c_LastModified]);
168 internal string Location
172 string location = m_CommonHeaders != null
173 ? m_CommonHeaders[c_Location] : Get(s_CommonHeaderNames[c_Location]);
174 // The normal header parser just casts bytes to chars. Check if there is a UTF8 host name.
175 return HeaderEncoding.DecodeUtf8FromString(location);
179 internal string ProxyAuthenticate
183 return m_CommonHeaders != null ? m_CommonHeaders[c_ProxyAuthenticate] : Get(s_CommonHeaderNames[c_ProxyAuthenticate]);
187 internal string SetCookie2
191 return m_CommonHeaders != null ? m_CommonHeaders[c_SetCookie2] : Get(s_CommonHeaderNames[c_SetCookie2]);
195 internal string SetCookie
199 return m_CommonHeaders != null ? m_CommonHeaders[c_SetCookie] : Get(s_CommonHeaderNames[c_SetCookie]);
203 internal string Server
207 return m_CommonHeaders != null ? m_CommonHeaders[c_Server] : Get(s_CommonHeaderNames[c_Server]);
215 return m_CommonHeaders != null ? m_CommonHeaders[c_Via] : Get(s_CommonHeaderNames[c_Via]);
219 private void NormalizeCommonHeaders()
221 if (m_CommonHeaders == null)
223 for (int i = 0; i < m_CommonHeaders.Length; i++)
224 if (m_CommonHeaders[i] != null)
225 InnerCollection.Add(s_CommonHeaderNames[i], m_CommonHeaders[i]);
227 m_CommonHeaders = null;
228 m_NumCommonHeaders = 0;
232 // To ensure C++ and IL callers can't pollute the underlying collection by calling overridden base members directly, we
233 // will use a member collection instead.
234 private NameValueCollection m_InnerCollection;
236 private NameValueCollection InnerCollection
240 if (m_InnerCollection == null)
241 m_InnerCollection = new NameValueCollection(ApproxHighAvgNumHeaders, CaseInsensitiveAscii.StaticInstance);
242 return m_InnerCollection;
246 // this is the object that created the header collection.
247 private WebHeaderCollectionType m_Type;
250 internal bool AllowMultiValues (string name)
252 return HInfo[name].AllowMultiValues;
256 #if !FEATURE_PAL || MONO
257 private bool AllowHttpRequestHeader {
259 if (m_Type==WebHeaderCollectionType.Unknown) {
260 m_Type = WebHeaderCollectionType.WebRequest;
262 return m_Type==WebHeaderCollectionType.WebRequest || m_Type==WebHeaderCollectionType.HttpWebRequest || m_Type==WebHeaderCollectionType.HttpListenerRequest;
266 internal bool AllowHttpResponseHeader {
268 if (m_Type==WebHeaderCollectionType.Unknown) {
269 m_Type = WebHeaderCollectionType.WebResponse;
271 return m_Type==WebHeaderCollectionType.WebResponse || m_Type==WebHeaderCollectionType.HttpWebResponse || m_Type==WebHeaderCollectionType.HttpListenerResponse;
275 public string this[HttpRequestHeader header] {
277 if (!AllowHttpRequestHeader) {
278 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
280 return this[UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header)];
283 if (!AllowHttpRequestHeader) {
284 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
286 this[UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header)] = value;
289 public string this[HttpResponseHeader header] {
291 if (!AllowHttpResponseHeader) {
292 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
295 // Some of these can be mapped to Common Headers. Other cases can be added as needed for perf.
296 if (m_CommonHeaders != null)
300 case HttpResponseHeader.ProxyAuthenticate:
301 return m_CommonHeaders[c_ProxyAuthenticate];
303 case HttpResponseHeader.WwwAuthenticate:
304 return m_CommonHeaders[c_WwwAuthenticate];
308 return this[UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header)];
311 if (!AllowHttpResponseHeader) {
312 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
314 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
315 if (value!=null && value.Length>ushort.MaxValue) {
316 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
319 this[UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header)] = value;
323 public void Add(HttpRequestHeader header, string value) {
324 if (!AllowHttpRequestHeader) {
325 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
327 this.Add(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header), value);
330 public void Add(HttpResponseHeader header, string value) {
331 if (!AllowHttpResponseHeader) {
332 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
334 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
335 if (value!=null && value.Length>ushort.MaxValue) {
336 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
339 this.Add(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header), value);
342 public void Set(HttpRequestHeader header, string value) {
343 if (!AllowHttpRequestHeader) {
344 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
346 this.Set(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header), value);
349 public void Set(HttpResponseHeader header, string value) {
350 if (!AllowHttpResponseHeader) {
351 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
353 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
354 if (value!=null && value.Length>ushort.MaxValue) {
355 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
358 this.Set(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header), value);
362 internal void SetInternal(HttpResponseHeader header, string value) {
363 if (!AllowHttpResponseHeader) {
364 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
366 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
367 if (value!=null && value.Length>ushort.MaxValue) {
368 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
371 this.SetInternal(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header), value);
375 public void Remove(HttpRequestHeader header) {
376 if (!AllowHttpRequestHeader) {
377 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
379 this.Remove(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header));
382 public void Remove(HttpResponseHeader header) {
383 if (!AllowHttpResponseHeader) {
384 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
386 this.Remove(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header));
388 #endif // !FEATURE_PAL
390 // In general, HttpWebResponse headers aren't modified, so these methods don't support common headers.
393 /// <para>[To be supplied.]</para>
395 protected void AddWithoutValidate(string headerName, string headerValue) {
396 headerName = CheckBadChars(headerName, false);
397 headerValue = CheckBadChars(headerValue, true);
398 GlobalLog.Print("WebHeaderCollection::AddWithoutValidate() calling InnerCollection.Add() key:[" + headerName + "], value:[" + headerValue + "]");
399 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
400 if (headerValue!=null && headerValue.Length>ushort.MaxValue) {
401 throw new ArgumentOutOfRangeException("headerValue", headerValue, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
404 NormalizeCommonHeaders();
405 InvalidateCachedArrays();
406 InnerCollection.Add(headerName, headerValue);
409 internal void SetAddVerified(string name, string value) {
410 if(HInfo[name].AllowMultiValues) {
411 GlobalLog.Print("WebHeaderCollection::SetAddVerified() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
412 NormalizeCommonHeaders();
413 InvalidateCachedArrays();
414 InnerCollection.Add(name, value);
417 GlobalLog.Print("WebHeaderCollection::SetAddVerified() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]");
418 NormalizeCommonHeaders();
419 InvalidateCachedArrays();
420 InnerCollection.Set(name, value);
424 // Below three methods are for fast headers manipulation, bypassing all the checks
425 internal void AddInternal(string name, string value) {
426 GlobalLog.Print("WebHeaderCollection::AddInternal() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
427 NormalizeCommonHeaders();
428 InvalidateCachedArrays();
429 InnerCollection.Add(name, value);
432 internal void ChangeInternal(string name, string value) {
433 GlobalLog.Print("WebHeaderCollection::ChangeInternal() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]");
434 NormalizeCommonHeaders();
435 InvalidateCachedArrays();
436 InnerCollection.Set(name, value);
440 internal void RemoveInternal(string name) {
441 GlobalLog.Print("WebHeaderCollection::RemoveInternal() calling InnerCollection.Remove() key:[" + name + "]");
442 NormalizeCommonHeaders();
443 if (m_InnerCollection != null)
445 InvalidateCachedArrays();
446 m_InnerCollection.Remove(name);
450 internal void CheckUpdate(string name, string value) {
451 value = CheckBadChars(value, true);
452 ChangeInternal(name, value);
455 // 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.
456 private void AddInternalNotCommon(string name, string value)
458 GlobalLog.Print("WebHeaderCollection::AddInternalNotCommon() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
459 InvalidateCachedArrays();
460 InnerCollection.Add(name, value);
464 private static readonly char[] HttpTrimCharacters = new char[]{(char)0x09,(char)0xA,(char)0xB,(char)0xC,(char)0xD,(char)0x20};
467 // CheckBadChars - throws on invalid chars to be not found in header name/value
469 internal static string CheckBadChars(string name, bool isHeaderValue) {
471 if (name == null || name.Length == 0) {
472 // emtpy name is invlaid
473 if (!isHeaderValue) {
474 throw name == null ? new ArgumentNullException("name") :
475 new ArgumentException(SR.GetString(SR.net_emptystringcall, "name"), "name");
483 //Trim spaces from both ends
484 name = name.Trim(HttpTrimCharacters);
486 //First, check for correctly formed multi-line value
487 //Second, check for absenece of CTL characters
489 for(int i = 0; i < name.Length; ++i) {
490 char c = (char) (0x000000ff & (uint) name[i]);
500 // Technically this is bad HTTP. But it would be a breaking change to throw here.
501 // Is there an exploit?
504 else if (c == 127 || (c < ' ' && c != '\t'))
506 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidControlChars), "value");
516 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidCRLFChars), "value");
519 if (c == ' ' || c == '\t')
524 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidCRLFChars), "value");
529 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidCRLFChars), "value");
534 //First, check for absence of separators and spaces
535 if (name.IndexOfAny(ValidationHelper.InvalidParamChars) != -1) {
536 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidHeaderChars), "name");
539 //Second, check for non CTL ASCII-7 characters (32-126)
540 if (ContainsNonAsciiChars(name)) {
541 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidNonAsciiChars), "name");
547 internal static bool IsValidToken(string token) {
548 return (token.Length > 0)
549 && (token.IndexOfAny(ValidationHelper.InvalidParamChars) == -1)
550 && !ContainsNonAsciiChars(token);
553 internal static bool ContainsNonAsciiChars(string token) {
554 for (int i = 0; i < token.Length; ++i) {
555 if ((token[i] < 0x20) || (token[i] > 0x7e)) {
563 // ThrowOnRestrictedHeader - generates an error if the user,
564 // passed in a reserved string as the header name
566 internal void ThrowOnRestrictedHeader(string headerName)
568 if (m_Type == WebHeaderCollectionType.HttpWebRequest)
570 if (HInfo[headerName].IsRequestRestricted)
572 throw new ArgumentException(SR.GetString(SR.net_headerrestrict, headerName), "name");
575 else if (m_Type == WebHeaderCollectionType.HttpListenerResponse)
577 if (HInfo[headerName].IsResponseRestricted)
579 throw new ArgumentException(SR.GetString(SR.net_headerrestrict, headerName), "name");
585 // Our Public METHOD set, most are inherited from NameValueCollection,
586 // not all methods from NameValueCollection are listed, even though usable -
591 // this[name] {set, get}
592 // Remove(name), returns bool
593 // Remove(name), returns void
597 // SplitValue(name, value)
599 // ParseHeaders(char [], ...)
600 // ParseHeaders(byte [], ...)
603 // Add more headers; if "name" already exists it will
604 // add concatenated value
608 // Routine Description:
609 // Adds headers with validation to see if they are "proper" headers.
610 // Will cause header to be concat to existing if already found.
611 // If the header is a special header, listed in RestrictedHeaders object,
612 // then this call will cause an exception indication as such.
614 // name - header-name to add
615 // value - header-value to add, a header is already there, will concat this value
621 /// Adds a new header with the indicated name and value.
624 public override void Add(string name, string value) {
625 name = CheckBadChars(name, false);
626 ThrowOnRestrictedHeader(name);
627 value = CheckBadChars(value, true);
628 GlobalLog.Print("WebHeaderCollection::Add() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
629 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
630 if (value!=null && value.Length>ushort.MaxValue) {
631 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
634 NormalizeCommonHeaders();
635 InvalidateCachedArrays();
636 InnerCollection.Add(name, value);
641 // Routine Description:
642 // Adds headers with validation to see if they are "proper" headers.
643 // Assumes a combined a "Name: Value" string, and parses the two parts out.
644 // Will cause header to be concat to existing if already found.
645 // If the header is a speical header, listed in RestrictedHeaders object,
646 // then this call will cause an exception indication as such.
648 // header - header name: value pair
654 /// Adds the indicated header.
657 public void Add(string header) {
658 if ( ValidationHelper.IsBlankString(header) ) {
659 throw new ArgumentNullException("header");
661 int colpos = header.IndexOf(':');
662 // check for badly formed header passed in
664 throw new ArgumentException(SR.GetString(SR.net_WebHeaderMissingColon), "header");
666 string name = header.Substring(0, colpos);
667 string value = header.Substring(colpos+1);
668 name = CheckBadChars(name, false);
669 ThrowOnRestrictedHeader(name);
670 value = CheckBadChars(value, true);
671 GlobalLog.Print("WebHeaderCollection::Add(" + header + ") calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
672 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
673 if (value!=null && value.Length>ushort.MaxValue) {
674 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
677 NormalizeCommonHeaders();
678 InvalidateCachedArrays();
679 InnerCollection.Add(name, value);
683 // Routine Description:
684 // Sets headers with validation to see if they are "proper" headers.
685 // If the header is a special header, listed in RestrictedHeaders object,
686 // then this call will cause an exception indication as such.
688 // name - header-name to set
689 // value - header-value to set
695 /// Sets the specified header to the specified value.
698 public override void Set(string name, string value) {
699 if (ValidationHelper.IsBlankString(name)) {
700 throw new ArgumentNullException("name");
702 name = CheckBadChars(name, false);
703 ThrowOnRestrictedHeader(name);
704 value = CheckBadChars(value, true);
705 GlobalLog.Print("WebHeaderCollection::Set() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]");
706 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
707 if (value!=null && value.Length>ushort.MaxValue) {
708 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
711 NormalizeCommonHeaders();
712 InvalidateCachedArrays();
713 InnerCollection.Set(name, value);
717 internal void SetInternal(string name, string value) {
718 if (ValidationHelper.IsBlankString(name)) {
719 throw new ArgumentNullException("name");
721 name = CheckBadChars(name, false);
722 value = CheckBadChars(value, true);
723 GlobalLog.Print("WebHeaderCollection::Set() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]");
724 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
725 if (value!=null && value.Length>ushort.MaxValue) {
726 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
729 NormalizeCommonHeaders();
730 InvalidateCachedArrays();
731 InnerCollection.Set(name, value);
736 // Routine Description:
737 // Removes give header with validation to see if they are "proper" headers.
738 // If the header is a speical header, listed in RestrictedHeaders object,
739 // then this call will cause an exception indication as such.
741 // name - header-name to remove
746 /// <para>Removes the specified header.</para>
748 public override void Remove(string name) {
749 if ( ValidationHelper.IsBlankString(name) ) {
750 throw new ArgumentNullException("name");
752 ThrowOnRestrictedHeader(name);
753 name = CheckBadChars(name, false);
754 GlobalLog.Print("WebHeaderCollection::Remove() calling InnerCollection.Remove() key:[" + name + "]");
755 NormalizeCommonHeaders();
756 if (m_InnerCollection != null)
758 InvalidateCachedArrays();
759 m_InnerCollection.Remove(name);
765 // Routine Description:
766 // This method takes a header name and returns a string array representing
767 // the individual values for that headers. For example, if the headers
768 // contained the line Accept: text/plain, text/html then
769 // GetValues("Accept") would return an array of two strings: "text/plain"
772 // header - Name of the header.
774 // string[] - array of parsed string objects
778 /// Gets an array of header values stored in a
782 public override string[] GetValues(string header) {
783 // This method doesn't work with common headers. Dump common headers into the pool.
784 NormalizeCommonHeaders();
786 // First get the information about the header and the values for
788 HeaderInfo Info = HInfo[header];
789 string[] Values = InnerCollection.GetValues(header);
790 // If we have no information about the header or it doesn't allow
791 // multiple values, just return the values.
792 if (Info == null || Values == null || !Info.AllowMultiValues) {
795 // Here we have a multi value header. We need to go through
796 // each entry in the multi values array, and if an entry itself
797 // has multiple values we'll need to combine those in.
799 // We do some optimazation here, where we try not to copy the
800 // values unless there really is one that have multiple values.
802 ArrayList ValueList = null;
804 for (i = 0; i < Values.Length; i++) {
805 // Parse this value header.
806 TempValues = Info.Parser(Values[i]);
807 // If we don't have an array list yet, see if this
808 // value has multiple values.
809 if (ValueList == null) {
810 // See if it has multiple values.
811 if (TempValues.Length > 1) {
812 // It does, so we need to create an array list that
813 // represents the Values, then trim out this one and
814 // the ones after it that haven't been parsed yet.
815 ValueList = new ArrayList(Values);
816 ValueList.RemoveRange(i, Values.Length - i);
817 ValueList.AddRange(TempValues);
821 // We already have an ArrayList, so just add the values.
822 ValueList.AddRange(TempValues);
825 // See if we have an ArrayList. If we don't, just return the values.
826 // Otherwise convert the ArrayList to a string array and return that.
827 if (ValueList != null) {
828 string[] ReturnArray = new string[ValueList.Count];
829 ValueList.CopyTo(ReturnArray);
837 // Routine Description:
838 // Generates a string representation of the headers, that is ready to be sent except for it being in string format:
839 // the format looks like:
841 // Header-Name: Header-Value\r\n
842 // Header-Name2: Header-Value2\r\n
844 // Header-NameN: Header-ValueN\r\n
847 // Uses the string builder class to Append the elements together.
859 public override string ToString() {
860 string result = GetAsString(this, false, false);
861 GlobalLog.Print("WebHeaderCollection::ToString: \r\n" + result);
865 internal string ToString(bool forTrace)
867 return GetAsString(this, false, true);
872 // if winInetCompat = true then it will not insert spaces after ':'
873 // and it will output "~U" header first
875 internal static string GetAsString(NameValueCollection cc,
880 throw new InvalidOperationException();
882 #endif // FEATURE_PAL
884 if (cc == null || cc.Count == 0) {
887 StringBuilder sb = new StringBuilder(ApproxAveHeaderLineSize*cc.Count);
889 statusLine = cc[string.Empty];
890 if (statusLine != null) {
891 sb.Append(statusLine).Append("\r\n");
893 for (int i = 0; i < cc.Count ; i++) {
894 string key = cc.GetKey(i) as string;
895 string val = cc.Get(i) as string;
899 // Put a condition here that if we are using basic auth,
900 // we shouldn't put the authorization header. Otherwise
901 // the password will get saved in the trace.
906 if (ValidationHelper.IsBlankString(key)) {
916 sb.Append(val).Append("\r\n");
920 return sb.ToString();
925 // Routine Description:
926 // Generates a byte array representation of the headers, that is ready to be sent.
927 // So it Serializes our headers into a byte array suitable for sending over the net.
929 // the format looks like:
931 // Header-Name1: Header-Value1\r\n
932 // Header-Name2: Header-Value2\r\n
934 // Header-NameN: Header-ValueN\r\n
937 // Uses the ToString() method to generate, and then performs conversion.
939 // Performance Note: Why are we not doing a single copy/covert run?
940 // As the code before used to know the size of the output!
941 // Because according to Demitry, its cheaper to copy the headers twice,
942 // then it is to call the UNICODE to ANSI conversion code many times.
946 // byte [] - array of bytes values
954 public byte[] ToByteArray() {
955 // Make sure the buffer is big enough.
956 string tempStr = ToString();
958 // Use the string of headers, convert to Char Array,
959 // then convert to Bytes,
960 // serializing finally into the buffer, along the way.
962 byte[] buffer = HeaderEncoding.GetBytes(tempStr);
967 /// <para>Tests if access to the HTTP header with the provided name is accessible for setting.</para>
969 public static bool IsRestricted(string headerName)
971 return IsRestricted(headerName, false);
974 public static bool IsRestricted(string headerName, bool response)
976 return response ? HInfo[CheckBadChars(headerName, false)].IsResponseRestricted : HInfo[CheckBadChars(headerName, false)].IsRequestRestricted;
982 /// Initializes a new instance of the <see cref='System.Net.WebHeaderCollection'/>
986 public WebHeaderCollection() : base(DBNull.Value)
990 internal WebHeaderCollection(WebHeaderCollectionType type) : base(DBNull.Value)
993 if (type == WebHeaderCollectionType.HttpWebResponse)
994 m_CommonHeaders = new string[s_CommonHeaderNames.Length - 1]; // Minus one for the sentinel.
998 internal WebHeaderCollection(NameValueCollection cc): base(DBNull.Value)
1000 m_InnerCollection = new NameValueCollection(cc.Count + 2, CaseInsensitiveAscii.StaticInstance);
1002 for (int i = 0; i < len; ++i) {
1003 String key = cc.GetKey(i);
1004 String[] values = cc.GetValues(i);
1005 if (values != null) {
1006 for (int j = 0; j < values.Length; j++) {
1007 InnerCollection.Add(key, values[j]);
1011 InnerCollection.Add(key, null);
1017 // ISerializable constructor
1020 /// <para>[To be supplied.]</para>
1022 protected WebHeaderCollection(SerializationInfo serializationInfo, StreamingContext streamingContext) :
1025 int count = serializationInfo.GetInt32("Count");
1026 m_InnerCollection = new NameValueCollection(count + 2, CaseInsensitiveAscii.StaticInstance);
1027 for (int i = 0; i < count; i++) {
1028 string headerName = serializationInfo.GetString(i.ToString(NumberFormatInfo.InvariantInfo));
1029 string headerValue = serializationInfo.GetString((i+count).ToString(NumberFormatInfo.InvariantInfo));
1030 GlobalLog.Print("WebHeaderCollection::.ctor(ISerializable) calling InnerCollection.Add() key:[" + headerName + "], value:[" + headerValue + "]");
1031 InnerCollection.Add(headerName, headerValue);
1035 public override void OnDeserialization(object sender) {
1042 // ISerializable method
1045 [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)]
1046 public override void GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext) {
1048 // for now disregard streamingContext.
1050 NormalizeCommonHeaders();
1051 serializationInfo.AddValue("Count", Count);
1052 for (int i = 0; i < Count; i++)
1054 serializationInfo.AddValue(i.ToString(NumberFormatInfo.InvariantInfo), GetKey(i));
1055 serializationInfo.AddValue((i + Count).ToString(NumberFormatInfo.InvariantInfo), Get(i));
1060 // we use this static class as a helper class to encode/decode HTTP headers.
1061 // what we need is a 1-1 correspondence between a char in the range U+0000-U+00FF
1062 // and a byte in the range 0x00-0xFF (which is the range that can hit the network).
1063 // The Latin-1 encoding (ISO-88591-1) (GetEncoding(28591)) works for byte[] to string, but is a little slow.
1064 // It doesn't work for string -> byte[] because of best-fit-mapping problems.
1065 internal static class HeaderEncoding
1067 internal static unsafe string GetString(byte[] bytes, int byteIndex, int byteCount)
1069 fixed(byte* pBytes = bytes)
1070 return GetString(pBytes + byteIndex, byteCount);
1073 internal static unsafe string GetString(byte* pBytes, int byteCount)
1078 string s = new String('\0', byteCount);
1080 fixed (char* pStr = s)
1082 char* pString = pStr;
1083 while (byteCount >= 8)
1085 pString[0] = (char) pBytes[0];
1086 pString[1] = (char) pBytes[1];
1087 pString[2] = (char) pBytes[2];
1088 pString[3] = (char) pBytes[3];
1089 pString[4] = (char) pBytes[4];
1090 pString[5] = (char) pBytes[5];
1091 pString[6] = (char) pBytes[6];
1092 pString[7] = (char) pBytes[7];
1097 for (int i = 0; i < byteCount; i++)
1099 pString[i] = (char) pBytes[i];
1106 internal static int GetByteCount(string myString) {
1107 return myString.Length;
1109 internal unsafe static void GetBytes(string myString, int charIndex, int charCount, byte[] bytes, int byteIndex) {
1110 if (myString.Length==0) {
1113 fixed (byte *bufferPointer = bytes) {
1114 byte* newBufferPointer = bufferPointer + byteIndex;
1115 int finalIndex = charIndex + charCount;
1116 while (charIndex<finalIndex) {
1117 *newBufferPointer++ = (byte)myString[charIndex++];
1121 internal unsafe static byte[] GetBytes(string myString) {
1122 byte[] bytes = new byte[myString.Length];
1123 if (myString.Length!=0) {
1124 GetBytes(myString, 0, myString.Length, bytes, 0);
1129 // The normal client header parser just casts bytes to chars (see GetString).
1130 // Check if those bytes were actually utf-8 instead of ASCII.
1131 // If not, just return the input value.
1132 [System.Runtime.CompilerServices.FriendAccessAllowed]
1133 internal static string DecodeUtf8FromString(string input) {
1134 if (string.IsNullOrWhiteSpace(input)) {
1138 bool possibleUtf8 = false;
1139 for (int i = 0; i < input.Length; i++) {
1140 if (input[i] > (char)255) {
1141 return input; // This couldn't have come from the wire, someone assigned it directly.
1143 else if (input[i] > (char)127) {
1144 possibleUtf8 = true;
1149 byte[] rawBytes = new byte[input.Length];
1150 for (int i = 0; i < input.Length; i++) {
1151 if (input[i] > (char)255) {
1152 return input; // This couldn't have come from the wire, someone assigned it directly.
1154 rawBytes[i] = (byte)input[i];
1157 // We don't want '?' replacement characters, just fail.
1158 Encoding decoder = Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback,
1159 DecoderFallback.ExceptionFallback);
1160 return decoder.GetString(rawBytes);
1162 catch (ArgumentException) { } // Not actually Utf-8
1170 // Routine Description:
1172 // This code is optimized for the case in which all the headers fit in the buffer.
1173 // we support multiple re-entrance, but we won't save intermediate
1174 // state, we will just roll back all the parsing done for the current header if we can't
1175 // parse a whole one (including multiline) or decide something else ("invalid data" or "done parsing").
1177 // we're going to cycle through the loop until we
1179 // 1) find an HTTP violation (in this case we return DataParseStatus.Invalid)
1180 // 2) we need more data (in this case we return DataParseStatus.NeedMoreData)
1181 // 3) we found the end of the headers and the beginning of the entity body (in this case we return DataParseStatus.Done)
1186 // buffer - buffer containing the data to be parsed
1187 // size - size of the buffer
1188 // unparsed - offset of data yet to be parsed
1192 // DataParseStatus - status of parsing
1196 // 02/13/2001 rewrote the method from scratch.
1200 // b system.dll!System.Net.WebHeaderCollection::ParseHeaders
1201 internal unsafe DataParseStatus ParseHeaders(
1205 ref int totalResponseHeadersLength,
1206 int maximumResponseHeadersLength,
1207 ref WebParseError parseError) {
1209 fixed (byte * byteBuffer = buffer) {
1213 // quick check in the boundaries (as we use unsafe pointer)
1214 if (buffer.Length < size) {
1215 return DataParseStatus.NeedMoreData;
1218 int headerNameStartOffset = -1;
1219 int headerNameEndOffset = -1;
1220 int headerValueStartOffset = -1;
1221 int headerValueEndOffset = -1;
1222 int numberOfLf = -1;
1223 int index = unparsed;
1225 string headerMultiLineValue;
1229 // we need this because this method is entered multiple times.
1230 int localTotalResponseHeadersLength = totalResponseHeadersLength;
1232 WebParseErrorCode parseErrorCode = WebParseErrorCode.Generic;
1233 DataParseStatus parseStatus = DataParseStatus.Invalid;
1235 GlobalLog.Enter("WebHeaderCollection::ParseHeaders(): ANSI size:" + size.ToString() + ", unparsed:" + unparsed.ToString() + " buffer:[" + Encoding.ASCII.GetString(buffer, unparsed, Math.Min(256, size-unparsed)) + "]");
1239 // according to RFC216 a header can have the following syntax:
1241 // message-header = field-name ":" [ field-value ]
1242 // field-name = token
1243 // field-value = *( field-content | LWS )
1244 // field-content = <the OCTETs making up the field-value and consisting of either *TEXT or combinations of token, separators, and quoted-string>
1245 // TEXT = <any OCTET except CTLs, but including LWS>
1246 // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
1247 // SP = <US-ASCII SP, space (32)>
1248 // HT = <US-ASCII HT, horizontal-tab (9)>
1249 // CR = <US-ASCII CR, carriage return (13)>
1250 // LF = <US-ASCII LF, linefeed (10)>
1251 // LWS = [CR LF] 1*( SP | HT )
1252 // CHAR = <any US-ASCII character (octets 0 - 127)>
1253 // token = 1*<any CHAR except CTLs or separators>
1254 // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
1255 // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
1256 // qdtext = <any TEXT except <">>
1257 // quoted-pair = "\" CHAR
1261 // At each iteration of the following loop we expect to parse a single HTTP header entirely.
1265 // trim leading whitespaces (LWS) just for extra robustness, in fact if there are leading white spaces then:
1266 // 1) it could be that after the status line we might have spaces. handle this.
1267 // 2) this should have been detected to be a multiline header so there'll be no spaces and we'll spend some time here.
1269 headerName = string.Empty;
1270 headerValue = string.Empty;
1271 spaceAfterLf = false;
1272 headerMultiLineValue = null;
1276 // so, restrict this extra trimming only on the first header line
1278 while (index < size) {
1279 ch = (char) byteBuffer[index];
1280 if (ch == ' ' || ch == '\t') {
1282 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1283 parseStatus = DataParseStatus.DataTooBig;
1294 // we reached the end of the buffer. ask for more data.
1296 parseStatus = DataParseStatus.NeedMoreData;
1302 // what we have here is the beginning of a new header
1304 headerNameStartOffset = index;
1306 while (index < size) {
1307 ch = (char) byteBuffer[index];
1308 if (ch != ':' && ch != '\n') {
1311 // if there's an illegal character we should return DataParseStatus.Invalid
1312 // instead we choose to be flexible, try to trim it, but include it in the string
1314 headerNameEndOffset = index;
1317 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1318 parseStatus = DataParseStatus.DataTooBig;
1325 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1326 parseStatus = DataParseStatus.DataTooBig;
1335 // we reached the end of the buffer. ask for more data.
1337 parseStatus = DataParseStatus.NeedMoreData;
1343 // skip all [' ','\t','\r','\n'] characters until HeaderValue starts
1344 // if we didn't find any headers yet, we set numberOfLf to 1
1345 // so that we take the '\n' from the status line into account
1348 numberOfLf = (Count == 0 && headerNameEndOffset < 0) ? 1 : 0;
1349 while (index<size && numberOfLf<2) {
1350 ch = (char) byteBuffer[index];
1354 // In this case, need to check for a space.
1355 if (numberOfLf == 1)
1357 if (index + 1 == size)
1360 // we reached the end of the buffer. ask for more data.
1361 // need to be able to peek after the \n and see if there's some space.
1363 parseStatus = DataParseStatus.NeedMoreData;
1366 spaceAfterLf = (char) byteBuffer[index + 1] == ' ' || (char) byteBuffer[index + 1] == '\t';
1370 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1371 parseStatus = DataParseStatus.DataTooBig;
1379 if (numberOfLf==2 || (numberOfLf==1 && !spaceAfterLf)) {
1381 // if we've counted two '\n' we got at the end of the headers even if we're past the end of the buffer
1382 // if we've counted one '\n' and the first character after that was a ' ' or a '\t'
1383 // no matter if we found a ':' or not, treat this as an empty header name.
1389 // we reached the end of the buffer. ask for more data.
1391 parseStatus = DataParseStatus.NeedMoreData;
1395 headerValueStartOffset = index;
1397 while (index<size) {
1398 ch = (char) byteBuffer[index];
1401 headerValueEndOffset = index;
1404 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1405 parseStatus = DataParseStatus.DataTooBig;
1415 // we reached the end of the buffer. ask for more data.
1417 parseStatus = DataParseStatus.NeedMoreData;
1422 // at this point we found either a '\n' or the end of the headers
1423 // hence we are at the end of the Header Line. 4 options:
1424 // 1) need more data
1425 // 2) if we find two '\n' => end of headers
1426 // 3) if we find one '\n' and a ' ' or a '\t' => multiline header
1427 // 4) if we find one '\n' and a valid char => next header
1430 while (index<size && numberOfLf<2) {
1431 ch = (char) byteBuffer[index];
1432 if (ch =='\r' || ch == '\n') {
1437 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1438 parseStatus = DataParseStatus.DataTooBig;
1446 if (index==size && numberOfLf<2) {
1448 // we reached the end of the buffer but not of the headers. ask for more data.
1450 parseStatus = DataParseStatus.NeedMoreData;
1455 if (headerValueStartOffset>=0 && headerValueStartOffset>headerNameEndOffset && headerValueEndOffset>=headerValueStartOffset) {
1457 // Encoding fastest way to build the UNICODE string off the byte[]
1459 headerValue = HeaderEncoding.GetString(byteBuffer + headerValueStartOffset, headerValueEndOffset - headerValueStartOffset + 1);
1463 // if we got here from the beginning of the for loop, headerMultiLineValue will be null
1464 // otherwise it will contain the headerValue constructed for the multiline header
1465 // add this line as well to it, separated by a single space
1467 headerMultiLineValue = (headerMultiLineValue==null ? headerValue : headerMultiLineValue + " " + headerValue);
1469 if (index < size && numberOfLf == 1) {
1470 ch = (char) byteBuffer[index];
1471 if (ch == ' ' || ch == '\t') {
1473 // since we found only one Lf and the next header line begins with a Lws,
1474 // this is the beginning of a multiline header.
1475 // parse the next line into headerValue later it will be added to headerMultiLineValue
1478 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1479 parseStatus = DataParseStatus.DataTooBig;
1486 if (headerNameStartOffset>=0 && headerNameEndOffset>=headerNameStartOffset) {
1488 // Encoding is the fastest way to build the UNICODE string off the byte[]
1490 headerName = HeaderEncoding.GetString(byteBuffer + headerNameStartOffset, headerNameEndOffset - headerNameStartOffset + 1);
1494 // now it's finally safe to add the header if we have a name for it
1496 if (headerName.Length>0) {
1498 // the base clasee will check for pre-existing headerValue and append
1499 // it using commas as indicated in the RFC
1501 GlobalLog.Print("WebHeaderCollection::ParseHeaders() calling AddInternal() key:[" + headerName + "], value:[" + headerMultiLineValue + "]");
1502 AddInternal(headerName, headerMultiLineValue);
1506 // and update unparsed
1508 totalResponseHeadersLength = localTotalResponseHeadersLength;
1511 if (numberOfLf==2) {
1512 parseStatus = DataParseStatus.Done;
1519 GlobalLog.Leave("WebHeaderCollection::ParseHeaders() returning parseStatus:" + parseStatus.ToString());
1520 if (parseStatus == DataParseStatus.Invalid) {
1521 parseError.Section = WebParseErrorSection.ResponseHeader;
1522 parseError.Code = parseErrorCode;
1530 // Alternative parsing that follows RFC2616. Like the above, this trims both sides of the header value and replaces
1531 // folding with a single space.
1533 private enum RfcChar : byte
1545 private static RfcChar[] RfcCharMap = new RfcChar[128]
1547 RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl,
1548 RfcChar.Ctl, RfcChar.WS, RfcChar.LF, RfcChar.Ctl, RfcChar.Ctl, RfcChar.CR, RfcChar.Ctl, RfcChar.Ctl,
1549 RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl,
1550 RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl,
1551 RfcChar.WS, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1552 RfcChar.Delim, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim,
1553 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1554 RfcChar.Reg, RfcChar.Reg, RfcChar.Colon, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim,
1555 RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1556 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1557 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1558 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg,
1559 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1560 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1561 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1562 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Ctl,
1565 internal unsafe DataParseStatus ParseHeadersStrict(
1569 ref int totalResponseHeadersLength,
1570 int maximumResponseHeadersLength,
1571 ref WebParseError parseError)
1573 GlobalLog.Enter("WebHeaderCollection::ParseHeadersStrict(): size:" + size.ToString() + ", unparsed:" + unparsed.ToString() + " buffer:[" + Encoding.ASCII.GetString(buffer, unparsed, Math.Min(256, size-unparsed)) + "]");
1575 WebParseErrorCode parseErrorCode = WebParseErrorCode.Generic;
1576 DataParseStatus parseStatus = DataParseStatus.Invalid;
1580 int effectiveSize = maximumResponseHeadersLength <= 0 ? Int32.MaxValue : maximumResponseHeadersLength - totalResponseHeadersLength + i;
1581 DataParseStatus sizeError = DataParseStatus.DataTooBig;
1582 if (size < effectiveSize)
1584 effectiveSize = size;
1585 sizeError = DataParseStatus.NeedMoreData;
1589 if (i >= effectiveSize)
1591 parseStatus = sizeError;
1595 fixed (byte* byteBuffer = buffer)
1599 // If this is CRLF, actually we're done.
1600 if (byteBuffer[i] == '\r')
1602 if (++i == effectiveSize)
1604 parseStatus = sizeError;
1608 if (byteBuffer[i++] == '\n')
1610 totalResponseHeadersLength += i - unparsed;
1612 parseStatus = DataParseStatus.Done;
1616 parseStatus = DataParseStatus.Invalid;
1617 parseErrorCode = WebParseErrorCode.CrLfError;
1621 // Find the header name; only regular characters allowed.
1623 for (; i < effectiveSize && (ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) == RfcChar.Reg; i++);
1624 if (i == effectiveSize)
1626 parseStatus = sizeError;
1629 if (i == iBeginName)
1631 parseStatus = DataParseStatus.Invalid;
1632 parseErrorCode = WebParseErrorCode.InvalidHeaderName;
1637 int iEndName = i - 1;
1638 int crlf = 0; // 1 = cr, 2 = crlf
1639 for (; i < effectiveSize && (ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) != RfcChar.Colon; i++)
1667 parseStatus = DataParseStatus.Invalid;
1668 parseErrorCode = WebParseErrorCode.CrLfError;
1671 if (i == effectiveSize)
1673 parseStatus = sizeError;
1678 parseStatus = DataParseStatus.Invalid;
1679 parseErrorCode = WebParseErrorCode.IncompleteHeaderLine;
1684 if (++i == effectiveSize)
1686 parseStatus = sizeError;
1690 // Read the value. crlf = 3 means in the whitespace after a CRLF
1691 int iBeginValue = -1;
1693 StringBuilder valueAccumulator = null;
1694 for (; i < effectiveSize && ((ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) == RfcChar.WS || crlf != 2); i++)
1736 if (iBeginValue != -1)
1738 string s = HeaderEncoding.GetString(byteBuffer + iBeginValue, iEndValue - iBeginValue + 1);
1739 if (valueAccumulator == null)
1741 valueAccumulator = new StringBuilder(s, s.Length * 5);
1745 valueAccumulator.Append(" ");
1746 valueAccumulator.Append(s);
1751 if (iBeginValue == -1)
1758 parseStatus = DataParseStatus.Invalid;
1759 parseErrorCode = WebParseErrorCode.CrLfError;
1762 if (i == effectiveSize)
1764 parseStatus = sizeError;
1769 string sValue = iBeginValue == -1 ? "" : HeaderEncoding.GetString(byteBuffer + iBeginValue, iEndValue - iBeginValue + 1);
1770 if (valueAccumulator != null)
1772 if (sValue.Length != 0)
1774 valueAccumulator.Append(" ");
1775 valueAccumulator.Append(sValue);
1777 sValue = valueAccumulator.ToString();
1780 // Make the name. See if it's a common header first.
1781 string sName = null;
1782 int headerNameLength = iEndName - iBeginName + 1;
1783 if (m_CommonHeaders != null)
1785 int iHeader = s_CommonHeaderHints[byteBuffer[iBeginName] & 0x1f];
1790 string s = s_CommonHeaderNames[iHeader++];
1792 // Not found if we get to a shorter header or one with a different first character.
1793 if (s.Length < headerNameLength || CaseInsensitiveAscii.AsciiToLower[byteBuffer[iBeginName]] != CaseInsensitiveAscii.AsciiToLower[s[0]])
1796 // Keep looking if the common header is too long.
1797 if (s.Length > headerNameLength)
1801 byte* pBuffer = byteBuffer + iBeginName + 1;
1802 for (j = 1; j < s.Length; j++)
1804 // Avoid the case-insensitive compare in the common case where they match.
1805 if (*(pBuffer++) != s[j] && CaseInsensitiveAscii.AsciiToLower[*(pBuffer - 1)] != CaseInsensitiveAscii.AsciiToLower[s[j]])
1810 // Set it to the appropriate index.
1811 m_NumCommonHeaders++;
1813 if (m_CommonHeaders[iHeader] == null)
1815 m_CommonHeaders[iHeader] = sValue;
1819 // Don't currently handle combining multiple header instances in the common header case.
1820 // Nothing to do but punt them all to the NameValueCollection.
1821 NormalizeCommonHeaders();
1822 AddInternalNotCommon(s, sValue);
1832 // If it wasn't a common header, add it to the hash.
1835 sName = HeaderEncoding.GetString(byteBuffer + iBeginName, headerNameLength);
1836 AddInternalNotCommon(sName, sValue);
1839 totalResponseHeadersLength += i - unparsed;
1845 GlobalLog.Leave("WebHeaderCollection::ParseHeadersStrict() returning parseStatus:" + parseStatus.ToString());
1847 if (parseStatus == DataParseStatus.Invalid) {
1848 parseError.Section = WebParseErrorSection.ResponseHeader;
1849 parseError.Code = parseErrorCode;
1856 // Keeping this version for backwards compatibility (mostly with reflection). Remove some day, along with the interface
1857 // explicit reimplementation.
1860 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase", Justification = "System.dll is still using pre-v4 security model and needs this demand")]
1861 [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter, SerializationFormatter=true)]
1862 void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext)
1864 GetObjectData(serializationInfo, streamingContext);
1867 // Override Get() to check the common headers.
1868 public override string Get(string name)
1870 // In this case, need to make sure name doesn't have any Unicode since it's being used as an index into tables.
1871 if (m_CommonHeaders != null && name != null && name.Length > 0 && name[0] < 256)
1873 int iHeader = s_CommonHeaderHints[name[0] & 0x1f];
1878 string s = s_CommonHeaderNames[iHeader++];
1880 // Not found if we get to a shorter header or one with a different first character.
1881 if (s.Length < name.Length || CaseInsensitiveAscii.AsciiToLower[name[0]] != CaseInsensitiveAscii.AsciiToLower[s[0]])
1884 // Keep looking if the common header is too long.
1885 if (s.Length > name.Length)
1889 for (j = 1; j < s.Length; j++)
1891 // Avoid the case-insensitive compare in the common case where they match.
1892 if (name[j] != s[j] && (name[j] > 255 || CaseInsensitiveAscii.AsciiToLower[name[j]] != CaseInsensitiveAscii.AsciiToLower[s[j]]))
1897 // Get the appropriate header.
1898 return m_CommonHeaders[iHeader - 1];
1904 // Fall back to normal lookup.
1905 if (m_InnerCollection == null)
1907 return m_InnerCollection.Get(name);
1912 // Additional overrides required to fully orphan the base implementation.
1914 public override IEnumerator GetEnumerator()
1916 NormalizeCommonHeaders();
1917 return new NameObjectKeysEnumerator(InnerCollection);
1920 public override int Count
1924 return (m_InnerCollection == null ? 0 : m_InnerCollection.Count) + m_NumCommonHeaders;
1928 public override KeysCollection Keys
1932 NormalizeCommonHeaders();
1933 return InnerCollection.Keys;
1937 internal override bool InternalHasKeys()
1939 NormalizeCommonHeaders();
1940 if (m_InnerCollection == null)
1942 return m_InnerCollection.HasKeys();
1945 public override string Get(int index)
1947 NormalizeCommonHeaders();
1948 return InnerCollection.Get(index);
1951 public override string[] GetValues(int index)
1953 NormalizeCommonHeaders();
1954 return InnerCollection.GetValues(index);
1957 public override string GetKey(int index)
1959 NormalizeCommonHeaders();
1960 return InnerCollection.GetKey(index);
1963 public override string[] AllKeys
1967 NormalizeCommonHeaders();
1968 return InnerCollection.AllKeys;
1972 public override void Clear()
1974 m_CommonHeaders = null;
1975 m_NumCommonHeaders = 0;
1976 InvalidateCachedArrays();
1977 if (m_InnerCollection != null)
1978 m_InnerCollection.Clear();
1980 } // class WebHeaderCollection
1983 internal class CaseInsensitiveAscii : IEqualityComparer, IComparer{
1984 // ASCII char ToLower table
1985 internal static readonly CaseInsensitiveAscii StaticInstance = new CaseInsensitiveAscii();
1986 internal static readonly byte[] AsciiToLower = new byte[] {
1987 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
1988 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
1989 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
1990 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
1991 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
1992 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
1993 60, 61, 62, 63, 64, 97, 98, 99,100,101, // 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
1994 102,103,104,105,106,107,108,109,110,111, // 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
1995 112,113,114,115,116,117,118,119,120,121, // 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
1996 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, // 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
1997 100,101,102,103,104,105,106,107,108,109,
1998 110,111,112,113,114,115,116,117,118,119,
1999 120,121,122,123,124,125,126,127,128,129,
2000 130,131,132,133,134,135,136,137,138,139,
2001 140,141,142,143,144,145,146,147,148,149,
2002 150,151,152,153,154,155,156,157,158,159,
2003 160,161,162,163,164,165,166,167,168,169,
2004 170,171,172,173,174,175,176,177,178,179,
2005 180,181,182,183,184,185,186,187,188,189,
2006 190,191,192,193,194,195,196,197,198,199,
2007 200,201,202,203,204,205,206,207,208,209,
2008 210,211,212,213,214,215,216,217,218,219,
2009 220,221,222,223,224,225,226,227,228,229,
2010 230,231,232,233,234,235,236,237,238,239,
2011 240,241,242,243,244,245,246,247,248,249,
2012 250,251,252,253,254,255
2015 // ASCII string case insensitive hash function
2016 public int GetHashCode(object myObject) {
2017 string myString = myObject as string;
2018 if (myObject == null) {
2021 int myHashCode = myString.Length;
2022 if (myHashCode == 0) {
2025 myHashCode ^= AsciiToLower[(byte)myString[0]]<<24 ^ AsciiToLower[(byte)myString[myHashCode-1]]<<16;
2029 // ASCII string case insensitive comparer
2030 public int Compare(object firstObject, object secondObject) {
2031 string firstString = firstObject as string;
2032 string secondString = secondObject as string;
2033 if (firstString==null) {
2034 return secondString == null ? 0 : -1;
2036 if (secondString == null) {
2039 int result = firstString.Length - secondString.Length;
2040 int comparisons = result > 0 ? secondString.Length : firstString.Length;
2041 int difference, index = 0;
2042 while ( index < comparisons ) {
2043 difference = (int)(AsciiToLower[ firstString[index] ] - AsciiToLower[ secondString[index] ]);
2044 if ( difference != 0 ) {
2045 result = difference;
2053 // ASCII string case insensitive hash function
2054 int FastGetHashCode(string myString) {
2055 int myHashCode = myString.Length;
2056 if (myHashCode!=0) {
2057 myHashCode ^= AsciiToLower[(byte)myString[0]]<<24 ^ AsciiToLower[(byte)myString[myHashCode-1]]<<16;
2062 // ASCII string case insensitive comparer
2063 public new bool Equals(object firstObject, object secondObject) {
2064 string firstString = firstObject as string;
2065 string secondString = secondObject as string;
2066 if (firstString==null) {
2067 return secondString==null;
2069 if (secondString!=null) {
2070 int index = firstString.Length;
2071 if (index==secondString.Length) {
2072 if (FastGetHashCode(firstString)==FastGetHashCode(secondString)) {
2073 int comparisons = firstString.Length;
2076 if (AsciiToLower[firstString[index]]!=AsciiToLower[secondString[index]]) {
2088 internal class HostHeaderString {
2090 private bool m_Converted;
2091 private string m_String;
2092 private byte[] m_Bytes;
2094 internal HostHeaderString() {
2098 internal HostHeaderString(string s) {
2102 private void Init(string s) {
2104 m_Converted = false;
2108 private void Convert() {
2109 if (m_String != null && !m_Converted) {
2110 m_Bytes = Encoding.Default.GetBytes(m_String);
2111 string copy = Encoding.Default.GetString(m_Bytes);
2112 if (!(string.Compare(m_String, copy, StringComparison.Ordinal) == 0)) {
2113 m_Bytes = Encoding.UTF8.GetBytes(m_String);
2118 internal string String {
2119 get { return m_String; }
2125 internal int ByteCount {
2128 return m_Bytes.Length;
2132 internal byte[] Bytes {
2139 internal void Copy(byte[] destBytes, int destByteIndex) {
2141 Array.Copy(m_Bytes, 0, destBytes, destByteIndex, m_Bytes.Length);