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 private bool AllowHttpRequestHeader {
252 if (m_Type==WebHeaderCollectionType.Unknown) {
253 m_Type = WebHeaderCollectionType.WebRequest;
255 return m_Type==WebHeaderCollectionType.WebRequest || m_Type==WebHeaderCollectionType.HttpWebRequest || m_Type==WebHeaderCollectionType.HttpListenerRequest;
259 internal bool AllowHttpResponseHeader {
261 if (m_Type==WebHeaderCollectionType.Unknown) {
262 m_Type = WebHeaderCollectionType.WebResponse;
264 return m_Type==WebHeaderCollectionType.WebResponse || m_Type==WebHeaderCollectionType.HttpWebResponse || m_Type==WebHeaderCollectionType.HttpListenerResponse;
268 public string this[HttpRequestHeader header] {
270 if (!AllowHttpRequestHeader) {
271 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
273 return this[UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header)];
276 if (!AllowHttpRequestHeader) {
277 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
279 this[UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header)] = value;
282 public string this[HttpResponseHeader header] {
284 if (!AllowHttpResponseHeader) {
285 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
288 // Some of these can be mapped to Common Headers. Other cases can be added as needed for perf.
289 if (m_CommonHeaders != null)
293 case HttpResponseHeader.ProxyAuthenticate:
294 return m_CommonHeaders[c_ProxyAuthenticate];
296 case HttpResponseHeader.WwwAuthenticate:
297 return m_CommonHeaders[c_WwwAuthenticate];
301 return this[UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header)];
304 if (!AllowHttpResponseHeader) {
305 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
307 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
308 if (value!=null && value.Length>ushort.MaxValue) {
309 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
312 this[UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header)] = value;
316 public void Add(HttpRequestHeader header, string value) {
317 if (!AllowHttpRequestHeader) {
318 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
320 this.Add(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header), value);
323 public void Add(HttpResponseHeader header, string value) {
324 if (!AllowHttpResponseHeader) {
325 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
327 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
328 if (value!=null && value.Length>ushort.MaxValue) {
329 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
332 this.Add(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header), value);
335 public void Set(HttpRequestHeader header, string value) {
336 if (!AllowHttpRequestHeader) {
337 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
339 this.Set(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header), value);
342 public void Set(HttpResponseHeader header, string value) {
343 if (!AllowHttpResponseHeader) {
344 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
346 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
347 if (value!=null && value.Length>ushort.MaxValue) {
348 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
351 this.Set(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header), value);
355 internal void SetInternal(HttpResponseHeader header, string value) {
356 if (!AllowHttpResponseHeader) {
357 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
359 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
360 if (value!=null && value.Length>ushort.MaxValue) {
361 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
364 this.SetInternal(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header), value);
368 public void Remove(HttpRequestHeader header) {
369 if (!AllowHttpRequestHeader) {
370 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
372 this.Remove(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header));
375 public void Remove(HttpResponseHeader header) {
376 if (!AllowHttpResponseHeader) {
377 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
379 this.Remove(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header));
381 #endif // !FEATURE_PAL
383 // In general, HttpWebResponse headers aren't modified, so these methods don't support common headers.
386 /// <para>[To be supplied.]</para>
388 protected void AddWithoutValidate(string headerName, string headerValue) {
389 headerName = CheckBadChars(headerName, false);
390 headerValue = CheckBadChars(headerValue, true);
391 GlobalLog.Print("WebHeaderCollection::AddWithoutValidate() calling InnerCollection.Add() key:[" + headerName + "], value:[" + headerValue + "]");
392 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
393 if (headerValue!=null && headerValue.Length>ushort.MaxValue) {
394 throw new ArgumentOutOfRangeException("headerValue", headerValue, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
397 NormalizeCommonHeaders();
398 InvalidateCachedArrays();
399 InnerCollection.Add(headerName, headerValue);
402 internal void SetAddVerified(string name, string value) {
403 if(HInfo[name].AllowMultiValues) {
404 GlobalLog.Print("WebHeaderCollection::SetAddVerified() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
405 NormalizeCommonHeaders();
406 InvalidateCachedArrays();
407 InnerCollection.Add(name, value);
410 GlobalLog.Print("WebHeaderCollection::SetAddVerified() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]");
411 NormalizeCommonHeaders();
412 InvalidateCachedArrays();
413 InnerCollection.Set(name, value);
417 // Below three methods are for fast headers manipulation, bypassing all the checks
418 internal void AddInternal(string name, string value) {
419 GlobalLog.Print("WebHeaderCollection::AddInternal() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
420 NormalizeCommonHeaders();
421 InvalidateCachedArrays();
422 InnerCollection.Add(name, value);
425 internal void ChangeInternal(string name, string value) {
426 GlobalLog.Print("WebHeaderCollection::ChangeInternal() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]");
427 NormalizeCommonHeaders();
428 InvalidateCachedArrays();
429 InnerCollection.Set(name, value);
433 internal void RemoveInternal(string name) {
434 GlobalLog.Print("WebHeaderCollection::RemoveInternal() calling InnerCollection.Remove() key:[" + name + "]");
435 NormalizeCommonHeaders();
436 if (m_InnerCollection != null)
438 InvalidateCachedArrays();
439 m_InnerCollection.Remove(name);
443 internal void CheckUpdate(string name, string value) {
444 value = CheckBadChars(value, true);
445 ChangeInternal(name, value);
448 // 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.
449 private void AddInternalNotCommon(string name, string value)
451 GlobalLog.Print("WebHeaderCollection::AddInternalNotCommon() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
452 InvalidateCachedArrays();
453 InnerCollection.Add(name, value);
457 private static readonly char[] HttpTrimCharacters = new char[]{(char)0x09,(char)0xA,(char)0xB,(char)0xC,(char)0xD,(char)0x20};
460 // CheckBadChars - throws on invalid chars to be not found in header name/value
462 internal static string CheckBadChars(string name, bool isHeaderValue) {
464 if (name == null || name.Length == 0) {
465 // emtpy name is invlaid
466 if (!isHeaderValue) {
467 throw name == null ? new ArgumentNullException("name") :
468 new ArgumentException(SR.GetString(SR.net_emptystringcall, "name"), "name");
476 //Trim spaces from both ends
477 name = name.Trim(HttpTrimCharacters);
479 //First, check for correctly formed multi-line value
480 //Second, check for absenece of CTL characters
482 for(int i = 0; i < name.Length; ++i) {
483 char c = (char) (0x000000ff & (uint) name[i]);
493 // Technically this is bad HTTP. But it would be a breaking change to throw here.
494 // Is there an exploit?
497 else if (c == 127 || (c < ' ' && c != '\t'))
499 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidControlChars), "value");
509 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidCRLFChars), "value");
512 if (c == ' ' || c == '\t')
517 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidCRLFChars), "value");
522 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidCRLFChars), "value");
527 //First, check for absence of separators and spaces
528 if (name.IndexOfAny(ValidationHelper.InvalidParamChars) != -1) {
529 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidHeaderChars), "name");
532 //Second, check for non CTL ASCII-7 characters (32-126)
533 if (ContainsNonAsciiChars(name)) {
534 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidNonAsciiChars), "name");
540 internal static bool IsValidToken(string token) {
541 return (token.Length > 0)
542 && (token.IndexOfAny(ValidationHelper.InvalidParamChars) == -1)
543 && !ContainsNonAsciiChars(token);
546 internal static bool ContainsNonAsciiChars(string token) {
547 for (int i = 0; i < token.Length; ++i) {
548 if ((token[i] < 0x20) || (token[i] > 0x7e)) {
556 // ThrowOnRestrictedHeader - generates an error if the user,
557 // passed in a reserved string as the header name
559 internal void ThrowOnRestrictedHeader(string headerName)
561 if (m_Type == WebHeaderCollectionType.HttpWebRequest)
563 if (HInfo[headerName].IsRequestRestricted)
565 throw new ArgumentException(SR.GetString(SR.net_headerrestrict, headerName), "name");
568 else if (m_Type == WebHeaderCollectionType.HttpListenerResponse)
570 if (HInfo[headerName].IsResponseRestricted)
572 throw new ArgumentException(SR.GetString(SR.net_headerrestrict, headerName), "name");
578 // Our Public METHOD set, most are inherited from NameValueCollection,
579 // not all methods from NameValueCollection are listed, even though usable -
584 // this[name] {set, get}
585 // Remove(name), returns bool
586 // Remove(name), returns void
590 // SplitValue(name, value)
592 // ParseHeaders(char [], ...)
593 // ParseHeaders(byte [], ...)
596 // Add more headers; if "name" already exists it will
597 // add concatenated value
601 // Routine Description:
602 // Adds headers with validation to see if they are "proper" headers.
603 // Will cause header to be concat to existing if already found.
604 // If the header is a special header, listed in RestrictedHeaders object,
605 // then this call will cause an exception indication as such.
607 // name - header-name to add
608 // value - header-value to add, a header is already there, will concat this value
614 /// Adds a new header with the indicated name and value.
617 public override void Add(string name, string value) {
618 name = CheckBadChars(name, false);
619 ThrowOnRestrictedHeader(name);
620 value = CheckBadChars(value, true);
621 GlobalLog.Print("WebHeaderCollection::Add() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
622 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
623 if (value!=null && value.Length>ushort.MaxValue) {
624 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
627 NormalizeCommonHeaders();
628 InvalidateCachedArrays();
629 InnerCollection.Add(name, value);
634 // Routine Description:
635 // Adds headers with validation to see if they are "proper" headers.
636 // Assumes a combined a "Name: Value" string, and parses the two parts out.
637 // Will cause header to be concat to existing if already found.
638 // If the header is a speical header, listed in RestrictedHeaders object,
639 // then this call will cause an exception indication as such.
641 // header - header name: value pair
647 /// Adds the indicated header.
650 public void Add(string header) {
651 if ( ValidationHelper.IsBlankString(header) ) {
652 throw new ArgumentNullException("header");
654 int colpos = header.IndexOf(':');
655 // check for badly formed header passed in
657 throw new ArgumentException(SR.GetString(SR.net_WebHeaderMissingColon), "header");
659 string name = header.Substring(0, colpos);
660 string value = header.Substring(colpos+1);
661 name = CheckBadChars(name, false);
662 ThrowOnRestrictedHeader(name);
663 value = CheckBadChars(value, true);
664 GlobalLog.Print("WebHeaderCollection::Add(" + header + ") calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
665 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
666 if (value!=null && value.Length>ushort.MaxValue) {
667 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
670 NormalizeCommonHeaders();
671 InvalidateCachedArrays();
672 InnerCollection.Add(name, value);
676 // Routine Description:
677 // Sets headers with validation to see if they are "proper" headers.
678 // If the header is a special header, listed in RestrictedHeaders object,
679 // then this call will cause an exception indication as such.
681 // name - header-name to set
682 // value - header-value to set
688 /// Sets the specified header to the specified value.
691 public override void Set(string name, string value) {
692 if (ValidationHelper.IsBlankString(name)) {
693 throw new ArgumentNullException("name");
695 name = CheckBadChars(name, false);
696 ThrowOnRestrictedHeader(name);
697 value = CheckBadChars(value, true);
698 GlobalLog.Print("WebHeaderCollection::Set() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]");
699 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
700 if (value!=null && value.Length>ushort.MaxValue) {
701 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
704 NormalizeCommonHeaders();
705 InvalidateCachedArrays();
706 InnerCollection.Set(name, value);
710 internal void SetInternal(string name, string value) {
711 if (ValidationHelper.IsBlankString(name)) {
712 throw new ArgumentNullException("name");
714 name = CheckBadChars(name, false);
715 value = CheckBadChars(value, true);
716 GlobalLog.Print("WebHeaderCollection::Set() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]");
717 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
718 if (value!=null && value.Length>ushort.MaxValue) {
719 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
722 NormalizeCommonHeaders();
723 InvalidateCachedArrays();
724 InnerCollection.Set(name, value);
729 // Routine Description:
730 // Removes give header with validation to see if they are "proper" headers.
731 // If the header is a speical header, listed in RestrictedHeaders object,
732 // then this call will cause an exception indication as such.
734 // name - header-name to remove
739 /// <para>Removes the specified header.</para>
741 public override void Remove(string name) {
742 if ( ValidationHelper.IsBlankString(name) ) {
743 throw new ArgumentNullException("name");
745 ThrowOnRestrictedHeader(name);
746 name = CheckBadChars(name, false);
747 GlobalLog.Print("WebHeaderCollection::Remove() calling InnerCollection.Remove() key:[" + name + "]");
748 NormalizeCommonHeaders();
749 if (m_InnerCollection != null)
751 InvalidateCachedArrays();
752 m_InnerCollection.Remove(name);
758 // Routine Description:
759 // This method takes a header name and returns a string array representing
760 // the individual values for that headers. For example, if the headers
761 // contained the line Accept: text/plain, text/html then
762 // GetValues("Accept") would return an array of two strings: "text/plain"
765 // header - Name of the header.
767 // string[] - array of parsed string objects
771 /// Gets an array of header values stored in a
775 public override string[] GetValues(string header) {
776 // This method doesn't work with common headers. Dump common headers into the pool.
777 NormalizeCommonHeaders();
779 // First get the information about the header and the values for
781 HeaderInfo Info = HInfo[header];
782 string[] Values = InnerCollection.GetValues(header);
783 // If we have no information about the header or it doesn't allow
784 // multiple values, just return the values.
785 if (Info == null || Values == null || !Info.AllowMultiValues) {
788 // Here we have a multi value header. We need to go through
789 // each entry in the multi values array, and if an entry itself
790 // has multiple values we'll need to combine those in.
792 // We do some optimazation here, where we try not to copy the
793 // values unless there really is one that have multiple values.
795 ArrayList ValueList = null;
797 for (i = 0; i < Values.Length; i++) {
798 // Parse this value header.
799 TempValues = Info.Parser(Values[i]);
800 // If we don't have an array list yet, see if this
801 // value has multiple values.
802 if (ValueList == null) {
803 // See if it has multiple values.
804 if (TempValues.Length > 1) {
805 // It does, so we need to create an array list that
806 // represents the Values, then trim out this one and
807 // the ones after it that haven't been parsed yet.
808 ValueList = new ArrayList(Values);
809 ValueList.RemoveRange(i, Values.Length - i);
810 ValueList.AddRange(TempValues);
814 // We already have an ArrayList, so just add the values.
815 ValueList.AddRange(TempValues);
818 // See if we have an ArrayList. If we don't, just return the values.
819 // Otherwise convert the ArrayList to a string array and return that.
820 if (ValueList != null) {
821 string[] ReturnArray = new string[ValueList.Count];
822 ValueList.CopyTo(ReturnArray);
830 // Routine Description:
831 // Generates a string representation of the headers, that is ready to be sent except for it being in string format:
832 // the format looks like:
834 // Header-Name: Header-Value\r\n
835 // Header-Name2: Header-Value2\r\n
837 // Header-NameN: Header-ValueN\r\n
840 // Uses the string builder class to Append the elements together.
852 public override string ToString() {
853 string result = GetAsString(this, false, false);
854 GlobalLog.Print("WebHeaderCollection::ToString: \r\n" + result);
858 internal string ToString(bool forTrace)
860 return GetAsString(this, false, true);
865 // if winInetCompat = true then it will not insert spaces after ':'
866 // and it will output "~U" header first
868 internal static string GetAsString(NameValueCollection cc,
873 throw new InvalidOperationException();
875 #endif // FEATURE_PAL
877 if (cc == null || cc.Count == 0) {
880 StringBuilder sb = new StringBuilder(ApproxAveHeaderLineSize*cc.Count);
882 statusLine = cc[string.Empty];
883 if (statusLine != null) {
884 sb.Append(statusLine).Append("\r\n");
886 for (int i = 0; i < cc.Count ; i++) {
887 string key = cc.GetKey(i) as string;
888 string val = cc.Get(i) as string;
892 // Put a condition here that if we are using basic auth,
893 // we shouldn't put the authorization header. Otherwise
894 // the password will get saved in the trace.
899 if (ValidationHelper.IsBlankString(key)) {
909 sb.Append(val).Append("\r\n");
913 return sb.ToString();
918 // Routine Description:
919 // Generates a byte array representation of the headers, that is ready to be sent.
920 // So it Serializes our headers into a byte array suitable for sending over the net.
922 // the format looks like:
924 // Header-Name1: Header-Value1\r\n
925 // Header-Name2: Header-Value2\r\n
927 // Header-NameN: Header-ValueN\r\n
930 // Uses the ToString() method to generate, and then performs conversion.
932 // Performance Note: Why are we not doing a single copy/covert run?
933 // As the code before used to know the size of the output!
934 // Because according to Demitry, its cheaper to copy the headers twice,
935 // then it is to call the UNICODE to ANSI conversion code many times.
939 // byte [] - array of bytes values
947 public byte[] ToByteArray() {
948 // Make sure the buffer is big enough.
949 string tempStr = ToString();
951 // Use the string of headers, convert to Char Array,
952 // then convert to Bytes,
953 // serializing finally into the buffer, along the way.
955 byte[] buffer = HeaderEncoding.GetBytes(tempStr);
960 /// <para>Tests if access to the HTTP header with the provided name is accessible for setting.</para>
962 public static bool IsRestricted(string headerName)
964 return IsRestricted(headerName, false);
967 public static bool IsRestricted(string headerName, bool response)
969 return response ? HInfo[CheckBadChars(headerName, false)].IsResponseRestricted : HInfo[CheckBadChars(headerName, false)].IsRequestRestricted;
975 /// Initializes a new instance of the <see cref='System.Net.WebHeaderCollection'/>
979 public WebHeaderCollection() : base(DBNull.Value)
983 internal WebHeaderCollection(WebHeaderCollectionType type) : base(DBNull.Value)
986 if (type == WebHeaderCollectionType.HttpWebResponse)
987 m_CommonHeaders = new string[s_CommonHeaderNames.Length - 1]; // Minus one for the sentinel.
991 internal WebHeaderCollection(NameValueCollection cc): base(DBNull.Value)
993 m_InnerCollection = new NameValueCollection(cc.Count + 2, CaseInsensitiveAscii.StaticInstance);
995 for (int i = 0; i < len; ++i) {
996 String key = cc.GetKey(i);
997 String[] values = cc.GetValues(i);
998 if (values != null) {
999 for (int j = 0; j < values.Length; j++) {
1000 InnerCollection.Add(key, values[j]);
1004 InnerCollection.Add(key, null);
1010 // ISerializable constructor
1013 /// <para>[To be supplied.]</para>
1015 protected WebHeaderCollection(SerializationInfo serializationInfo, StreamingContext streamingContext) :
1018 int count = serializationInfo.GetInt32("Count");
1019 m_InnerCollection = new NameValueCollection(count + 2, CaseInsensitiveAscii.StaticInstance);
1020 for (int i = 0; i < count; i++) {
1021 string headerName = serializationInfo.GetString(i.ToString(NumberFormatInfo.InvariantInfo));
1022 string headerValue = serializationInfo.GetString((i+count).ToString(NumberFormatInfo.InvariantInfo));
1023 GlobalLog.Print("WebHeaderCollection::.ctor(ISerializable) calling InnerCollection.Add() key:[" + headerName + "], value:[" + headerValue + "]");
1024 InnerCollection.Add(headerName, headerValue);
1028 public override void OnDeserialization(object sender) {
1035 // ISerializable method
1038 [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)]
1039 public override void GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext) {
1041 // for now disregard streamingContext.
1043 NormalizeCommonHeaders();
1044 serializationInfo.AddValue("Count", Count);
1045 for (int i = 0; i < Count; i++)
1047 serializationInfo.AddValue(i.ToString(NumberFormatInfo.InvariantInfo), GetKey(i));
1048 serializationInfo.AddValue((i + Count).ToString(NumberFormatInfo.InvariantInfo), Get(i));
1053 // we use this static class as a helper class to encode/decode HTTP headers.
1054 // what we need is a 1-1 correspondence between a char in the range U+0000-U+00FF
1055 // and a byte in the range 0x00-0xFF (which is the range that can hit the network).
1056 // The Latin-1 encoding (ISO-88591-1) (GetEncoding(28591)) works for byte[] to string, but is a little slow.
1057 // It doesn't work for string -> byte[] because of best-fit-mapping problems.
1058 internal static class HeaderEncoding
1060 internal static unsafe string GetString(byte[] bytes, int byteIndex, int byteCount)
1062 fixed(byte* pBytes = bytes)
1063 return GetString(pBytes + byteIndex, byteCount);
1066 internal static unsafe string GetString(byte* pBytes, int byteCount)
1071 string s = new String('\0', byteCount);
1073 fixed (char* pStr = s)
1075 char* pString = pStr;
1076 while (byteCount >= 8)
1078 pString[0] = (char) pBytes[0];
1079 pString[1] = (char) pBytes[1];
1080 pString[2] = (char) pBytes[2];
1081 pString[3] = (char) pBytes[3];
1082 pString[4] = (char) pBytes[4];
1083 pString[5] = (char) pBytes[5];
1084 pString[6] = (char) pBytes[6];
1085 pString[7] = (char) pBytes[7];
1090 for (int i = 0; i < byteCount; i++)
1092 pString[i] = (char) pBytes[i];
1099 internal static int GetByteCount(string myString) {
1100 return myString.Length;
1102 internal unsafe static void GetBytes(string myString, int charIndex, int charCount, byte[] bytes, int byteIndex) {
1103 if (myString.Length==0) {
1106 fixed (byte *bufferPointer = bytes) {
1107 byte* newBufferPointer = bufferPointer + byteIndex;
1108 int finalIndex = charIndex + charCount;
1109 while (charIndex<finalIndex) {
1110 *newBufferPointer++ = (byte)myString[charIndex++];
1114 internal unsafe static byte[] GetBytes(string myString) {
1115 byte[] bytes = new byte[myString.Length];
1116 if (myString.Length!=0) {
1117 GetBytes(myString, 0, myString.Length, bytes, 0);
1122 // The normal client header parser just casts bytes to chars (see GetString).
1123 // Check if those bytes were actually utf-8 instead of ASCII.
1124 // If not, just return the input value.
1125 [System.Runtime.CompilerServices.FriendAccessAllowed]
1126 internal static string DecodeUtf8FromString(string input) {
1127 if (string.IsNullOrWhiteSpace(input)) {
1131 bool possibleUtf8 = false;
1132 for (int i = 0; i < input.Length; i++) {
1133 if (input[i] > (char)255) {
1134 return input; // This couldn't have come from the wire, someone assigned it directly.
1136 else if (input[i] > (char)127) {
1137 possibleUtf8 = true;
1142 byte[] rawBytes = new byte[input.Length];
1143 for (int i = 0; i < input.Length; i++) {
1144 if (input[i] > (char)255) {
1145 return input; // This couldn't have come from the wire, someone assigned it directly.
1147 rawBytes[i] = (byte)input[i];
1150 // We don't want '?' replacement characters, just fail.
1151 Encoding decoder = Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback,
1152 DecoderFallback.ExceptionFallback);
1153 return decoder.GetString(rawBytes);
1155 catch (ArgumentException) { } // Not actually Utf-8
1163 // Routine Description:
1165 // This code is optimized for the case in which all the headers fit in the buffer.
1166 // we support multiple re-entrance, but we won't save intermediate
1167 // state, we will just roll back all the parsing done for the current header if we can't
1168 // parse a whole one (including multiline) or decide something else ("invalid data" or "done parsing").
1170 // we're going to cycle through the loop until we
1172 // 1) find an HTTP violation (in this case we return DataParseStatus.Invalid)
1173 // 2) we need more data (in this case we return DataParseStatus.NeedMoreData)
1174 // 3) we found the end of the headers and the beginning of the entity body (in this case we return DataParseStatus.Done)
1179 // buffer - buffer containing the data to be parsed
1180 // size - size of the buffer
1181 // unparsed - offset of data yet to be parsed
1185 // DataParseStatus - status of parsing
1189 // 02/13/2001 rewrote the method from scratch.
1193 // b system.dll!System.Net.WebHeaderCollection::ParseHeaders
1194 internal unsafe DataParseStatus ParseHeaders(
1198 ref int totalResponseHeadersLength,
1199 int maximumResponseHeadersLength,
1200 ref WebParseError parseError) {
1202 fixed (byte * byteBuffer = buffer) {
1206 // quick check in the boundaries (as we use unsafe pointer)
1207 if (buffer.Length < size) {
1208 return DataParseStatus.NeedMoreData;
1211 int headerNameStartOffset = -1;
1212 int headerNameEndOffset = -1;
1213 int headerValueStartOffset = -1;
1214 int headerValueEndOffset = -1;
1215 int numberOfLf = -1;
1216 int index = unparsed;
1218 string headerMultiLineValue;
1222 // we need this because this method is entered multiple times.
1223 int localTotalResponseHeadersLength = totalResponseHeadersLength;
1225 WebParseErrorCode parseErrorCode = WebParseErrorCode.Generic;
1226 DataParseStatus parseStatus = DataParseStatus.Invalid;
1228 GlobalLog.Enter("WebHeaderCollection::ParseHeaders(): ANSI size:" + size.ToString() + ", unparsed:" + unparsed.ToString() + " buffer:[" + Encoding.ASCII.GetString(buffer, unparsed, Math.Min(256, size-unparsed)) + "]");
1232 // according to RFC216 a header can have the following syntax:
1234 // message-header = field-name ":" [ field-value ]
1235 // field-name = token
1236 // field-value = *( field-content | LWS )
1237 // field-content = <the OCTETs making up the field-value and consisting of either *TEXT or combinations of token, separators, and quoted-string>
1238 // TEXT = <any OCTET except CTLs, but including LWS>
1239 // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
1240 // SP = <US-ASCII SP, space (32)>
1241 // HT = <US-ASCII HT, horizontal-tab (9)>
1242 // CR = <US-ASCII CR, carriage return (13)>
1243 // LF = <US-ASCII LF, linefeed (10)>
1244 // LWS = [CR LF] 1*( SP | HT )
1245 // CHAR = <any US-ASCII character (octets 0 - 127)>
1246 // token = 1*<any CHAR except CTLs or separators>
1247 // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
1248 // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
1249 // qdtext = <any TEXT except <">>
1250 // quoted-pair = "\" CHAR
1254 // At each iteration of the following loop we expect to parse a single HTTP header entirely.
1258 // trim leading whitespaces (LWS) just for extra robustness, in fact if there are leading white spaces then:
1259 // 1) it could be that after the status line we might have spaces. handle this.
1260 // 2) this should have been detected to be a multiline header so there'll be no spaces and we'll spend some time here.
1262 headerName = string.Empty;
1263 headerValue = string.Empty;
1264 spaceAfterLf = false;
1265 headerMultiLineValue = null;
1269 // so, restrict this extra trimming only on the first header line
1271 while (index < size) {
1272 ch = (char) byteBuffer[index];
1273 if (ch == ' ' || ch == '\t') {
1275 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1276 parseStatus = DataParseStatus.DataTooBig;
1287 // we reached the end of the buffer. ask for more data.
1289 parseStatus = DataParseStatus.NeedMoreData;
1295 // what we have here is the beginning of a new header
1297 headerNameStartOffset = index;
1299 while (index < size) {
1300 ch = (char) byteBuffer[index];
1301 if (ch != ':' && ch != '\n') {
1304 // if there's an illegal character we should return DataParseStatus.Invalid
1305 // instead we choose to be flexible, try to trim it, but include it in the string
1307 headerNameEndOffset = index;
1310 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1311 parseStatus = DataParseStatus.DataTooBig;
1318 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1319 parseStatus = DataParseStatus.DataTooBig;
1328 // we reached the end of the buffer. ask for more data.
1330 parseStatus = DataParseStatus.NeedMoreData;
1336 // skip all [' ','\t','\r','\n'] characters until HeaderValue starts
1337 // if we didn't find any headers yet, we set numberOfLf to 1
1338 // so that we take the '\n' from the status line into account
1341 numberOfLf = (Count == 0 && headerNameEndOffset < 0) ? 1 : 0;
1342 while (index<size && numberOfLf<2) {
1343 ch = (char) byteBuffer[index];
1347 // In this case, need to check for a space.
1348 if (numberOfLf == 1)
1350 if (index + 1 == size)
1353 // we reached the end of the buffer. ask for more data.
1354 // need to be able to peek after the \n and see if there's some space.
1356 parseStatus = DataParseStatus.NeedMoreData;
1359 spaceAfterLf = (char) byteBuffer[index + 1] == ' ' || (char) byteBuffer[index + 1] == '\t';
1363 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1364 parseStatus = DataParseStatus.DataTooBig;
1372 if (numberOfLf==2 || (numberOfLf==1 && !spaceAfterLf)) {
1374 // if we've counted two '\n' we got at the end of the headers even if we're past the end of the buffer
1375 // if we've counted one '\n' and the first character after that was a ' ' or a '\t'
1376 // no matter if we found a ':' or not, treat this as an empty header name.
1382 // we reached the end of the buffer. ask for more data.
1384 parseStatus = DataParseStatus.NeedMoreData;
1388 headerValueStartOffset = index;
1390 while (index<size) {
1391 ch = (char) byteBuffer[index];
1394 headerValueEndOffset = index;
1397 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1398 parseStatus = DataParseStatus.DataTooBig;
1408 // we reached the end of the buffer. ask for more data.
1410 parseStatus = DataParseStatus.NeedMoreData;
1415 // at this point we found either a '\n' or the end of the headers
1416 // hence we are at the end of the Header Line. 4 options:
1417 // 1) need more data
1418 // 2) if we find two '\n' => end of headers
1419 // 3) if we find one '\n' and a ' ' or a '\t' => multiline header
1420 // 4) if we find one '\n' and a valid char => next header
1423 while (index<size && numberOfLf<2) {
1424 ch = (char) byteBuffer[index];
1425 if (ch =='\r' || ch == '\n') {
1430 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1431 parseStatus = DataParseStatus.DataTooBig;
1439 if (index==size && numberOfLf<2) {
1441 // we reached the end of the buffer but not of the headers. ask for more data.
1443 parseStatus = DataParseStatus.NeedMoreData;
1448 if (headerValueStartOffset>=0 && headerValueStartOffset>headerNameEndOffset && headerValueEndOffset>=headerValueStartOffset) {
1450 // Encoding fastest way to build the UNICODE string off the byte[]
1452 headerValue = HeaderEncoding.GetString(byteBuffer + headerValueStartOffset, headerValueEndOffset - headerValueStartOffset + 1);
1456 // if we got here from the beginning of the for loop, headerMultiLineValue will be null
1457 // otherwise it will contain the headerValue constructed for the multiline header
1458 // add this line as well to it, separated by a single space
1460 headerMultiLineValue = (headerMultiLineValue==null ? headerValue : headerMultiLineValue + " " + headerValue);
1462 if (index < size && numberOfLf == 1) {
1463 ch = (char) byteBuffer[index];
1464 if (ch == ' ' || ch == '\t') {
1466 // since we found only one Lf and the next header line begins with a Lws,
1467 // this is the beginning of a multiline header.
1468 // parse the next line into headerValue later it will be added to headerMultiLineValue
1471 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1472 parseStatus = DataParseStatus.DataTooBig;
1479 if (headerNameStartOffset>=0 && headerNameEndOffset>=headerNameStartOffset) {
1481 // Encoding is the fastest way to build the UNICODE string off the byte[]
1483 headerName = HeaderEncoding.GetString(byteBuffer + headerNameStartOffset, headerNameEndOffset - headerNameStartOffset + 1);
1487 // now it's finally safe to add the header if we have a name for it
1489 if (headerName.Length>0) {
1491 // the base clasee will check for pre-existing headerValue and append
1492 // it using commas as indicated in the RFC
1494 GlobalLog.Print("WebHeaderCollection::ParseHeaders() calling AddInternal() key:[" + headerName + "], value:[" + headerMultiLineValue + "]");
1495 AddInternal(headerName, headerMultiLineValue);
1499 // and update unparsed
1501 totalResponseHeadersLength = localTotalResponseHeadersLength;
1504 if (numberOfLf==2) {
1505 parseStatus = DataParseStatus.Done;
1512 GlobalLog.Leave("WebHeaderCollection::ParseHeaders() returning parseStatus:" + parseStatus.ToString());
1513 if (parseStatus == DataParseStatus.Invalid) {
1514 parseError.Section = WebParseErrorSection.ResponseHeader;
1515 parseError.Code = parseErrorCode;
1523 // Alternative parsing that follows RFC2616. Like the above, this trims both sides of the header value and replaces
1524 // folding with a single space.
1526 private enum RfcChar : byte
1538 private static RfcChar[] RfcCharMap = new RfcChar[128]
1540 RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl,
1541 RfcChar.Ctl, RfcChar.WS, RfcChar.LF, RfcChar.Ctl, RfcChar.Ctl, RfcChar.CR, RfcChar.Ctl, RfcChar.Ctl,
1542 RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl,
1543 RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl,
1544 RfcChar.WS, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1545 RfcChar.Delim, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim,
1546 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1547 RfcChar.Reg, RfcChar.Reg, RfcChar.Colon, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim,
1548 RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1549 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1550 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1551 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg,
1552 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1553 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1554 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1555 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Ctl,
1558 internal unsafe DataParseStatus ParseHeadersStrict(
1562 ref int totalResponseHeadersLength,
1563 int maximumResponseHeadersLength,
1564 ref WebParseError parseError)
1566 GlobalLog.Enter("WebHeaderCollection::ParseHeadersStrict(): size:" + size.ToString() + ", unparsed:" + unparsed.ToString() + " buffer:[" + Encoding.ASCII.GetString(buffer, unparsed, Math.Min(256, size-unparsed)) + "]");
1568 WebParseErrorCode parseErrorCode = WebParseErrorCode.Generic;
1569 DataParseStatus parseStatus = DataParseStatus.Invalid;
1573 int effectiveSize = maximumResponseHeadersLength <= 0 ? Int32.MaxValue : maximumResponseHeadersLength - totalResponseHeadersLength + i;
1574 DataParseStatus sizeError = DataParseStatus.DataTooBig;
1575 if (size < effectiveSize)
1577 effectiveSize = size;
1578 sizeError = DataParseStatus.NeedMoreData;
1582 if (i >= effectiveSize)
1584 parseStatus = sizeError;
1588 fixed (byte* byteBuffer = buffer)
1592 // If this is CRLF, actually we're done.
1593 if (byteBuffer[i] == '\r')
1595 if (++i == effectiveSize)
1597 parseStatus = sizeError;
1601 if (byteBuffer[i++] == '\n')
1603 totalResponseHeadersLength += i - unparsed;
1605 parseStatus = DataParseStatus.Done;
1609 parseStatus = DataParseStatus.Invalid;
1610 parseErrorCode = WebParseErrorCode.CrLfError;
1614 // Find the header name; only regular characters allowed.
1616 for (; i < effectiveSize && (ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) == RfcChar.Reg; i++);
1617 if (i == effectiveSize)
1619 parseStatus = sizeError;
1622 if (i == iBeginName)
1624 parseStatus = DataParseStatus.Invalid;
1625 parseErrorCode = WebParseErrorCode.InvalidHeaderName;
1630 int iEndName = i - 1;
1631 int crlf = 0; // 1 = cr, 2 = crlf
1632 for (; i < effectiveSize && (ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) != RfcChar.Colon; i++)
1660 parseStatus = DataParseStatus.Invalid;
1661 parseErrorCode = WebParseErrorCode.CrLfError;
1664 if (i == effectiveSize)
1666 parseStatus = sizeError;
1671 parseStatus = DataParseStatus.Invalid;
1672 parseErrorCode = WebParseErrorCode.IncompleteHeaderLine;
1677 if (++i == effectiveSize)
1679 parseStatus = sizeError;
1683 // Read the value. crlf = 3 means in the whitespace after a CRLF
1684 int iBeginValue = -1;
1686 StringBuilder valueAccumulator = null;
1687 for (; i < effectiveSize && ((ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) == RfcChar.WS || crlf != 2); i++)
1729 if (iBeginValue != -1)
1731 string s = HeaderEncoding.GetString(byteBuffer + iBeginValue, iEndValue - iBeginValue + 1);
1732 if (valueAccumulator == null)
1734 valueAccumulator = new StringBuilder(s, s.Length * 5);
1738 valueAccumulator.Append(" ");
1739 valueAccumulator.Append(s);
1744 if (iBeginValue == -1)
1751 parseStatus = DataParseStatus.Invalid;
1752 parseErrorCode = WebParseErrorCode.CrLfError;
1755 if (i == effectiveSize)
1757 parseStatus = sizeError;
1762 string sValue = iBeginValue == -1 ? "" : HeaderEncoding.GetString(byteBuffer + iBeginValue, iEndValue - iBeginValue + 1);
1763 if (valueAccumulator != null)
1765 if (sValue.Length != 0)
1767 valueAccumulator.Append(" ");
1768 valueAccumulator.Append(sValue);
1770 sValue = valueAccumulator.ToString();
1773 // Make the name. See if it's a common header first.
1774 string sName = null;
1775 int headerNameLength = iEndName - iBeginName + 1;
1776 if (m_CommonHeaders != null)
1778 int iHeader = s_CommonHeaderHints[byteBuffer[iBeginName] & 0x1f];
1783 string s = s_CommonHeaderNames[iHeader++];
1785 // Not found if we get to a shorter header or one with a different first character.
1786 if (s.Length < headerNameLength || CaseInsensitiveAscii.AsciiToLower[byteBuffer[iBeginName]] != CaseInsensitiveAscii.AsciiToLower[s[0]])
1789 // Keep looking if the common header is too long.
1790 if (s.Length > headerNameLength)
1794 byte* pBuffer = byteBuffer + iBeginName + 1;
1795 for (j = 1; j < s.Length; j++)
1797 // Avoid the case-insensitive compare in the common case where they match.
1798 if (*(pBuffer++) != s[j] && CaseInsensitiveAscii.AsciiToLower[*(pBuffer - 1)] != CaseInsensitiveAscii.AsciiToLower[s[j]])
1803 // Set it to the appropriate index.
1804 m_NumCommonHeaders++;
1806 if (m_CommonHeaders[iHeader] == null)
1808 m_CommonHeaders[iHeader] = sValue;
1812 // Don't currently handle combining multiple header instances in the common header case.
1813 // Nothing to do but punt them all to the NameValueCollection.
1814 NormalizeCommonHeaders();
1815 AddInternalNotCommon(s, sValue);
1825 // If it wasn't a common header, add it to the hash.
1828 sName = HeaderEncoding.GetString(byteBuffer + iBeginName, headerNameLength);
1829 AddInternalNotCommon(sName, sValue);
1832 totalResponseHeadersLength += i - unparsed;
1838 GlobalLog.Leave("WebHeaderCollection::ParseHeadersStrict() returning parseStatus:" + parseStatus.ToString());
1840 if (parseStatus == DataParseStatus.Invalid) {
1841 parseError.Section = WebParseErrorSection.ResponseHeader;
1842 parseError.Code = parseErrorCode;
1849 // Keeping this version for backwards compatibility (mostly with reflection). Remove some day, along with the interface
1850 // explicit reimplementation.
1853 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase", Justification = "System.dll is still using pre-v4 security model and needs this demand")]
1854 [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter, SerializationFormatter=true)]
1855 void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext)
1857 GetObjectData(serializationInfo, streamingContext);
1860 // Override Get() to check the common headers.
1861 public override string Get(string name)
1863 // In this case, need to make sure name doesn't have any Unicode since it's being used as an index into tables.
1864 if (m_CommonHeaders != null && name != null && name.Length > 0 && name[0] < 256)
1866 int iHeader = s_CommonHeaderHints[name[0] & 0x1f];
1871 string s = s_CommonHeaderNames[iHeader++];
1873 // Not found if we get to a shorter header or one with a different first character.
1874 if (s.Length < name.Length || CaseInsensitiveAscii.AsciiToLower[name[0]] != CaseInsensitiveAscii.AsciiToLower[s[0]])
1877 // Keep looking if the common header is too long.
1878 if (s.Length > name.Length)
1882 for (j = 1; j < s.Length; j++)
1884 // Avoid the case-insensitive compare in the common case where they match.
1885 if (name[j] != s[j] && (name[j] > 255 || CaseInsensitiveAscii.AsciiToLower[name[j]] != CaseInsensitiveAscii.AsciiToLower[s[j]]))
1890 // Get the appropriate header.
1891 return m_CommonHeaders[iHeader - 1];
1897 // Fall back to normal lookup.
1898 if (m_InnerCollection == null)
1900 return m_InnerCollection.Get(name);
1905 // Additional overrides required to fully orphan the base implementation.
1907 public override IEnumerator GetEnumerator()
1909 NormalizeCommonHeaders();
1910 return new NameObjectKeysEnumerator(InnerCollection);
1913 public override int Count
1917 return (m_InnerCollection == null ? 0 : m_InnerCollection.Count) + m_NumCommonHeaders;
1921 public override KeysCollection Keys
1925 NormalizeCommonHeaders();
1926 return InnerCollection.Keys;
1930 internal override bool InternalHasKeys()
1932 NormalizeCommonHeaders();
1933 if (m_InnerCollection == null)
1935 return m_InnerCollection.HasKeys();
1938 public override string Get(int index)
1940 NormalizeCommonHeaders();
1941 return InnerCollection.Get(index);
1944 public override string[] GetValues(int index)
1946 NormalizeCommonHeaders();
1947 return InnerCollection.GetValues(index);
1950 public override string GetKey(int index)
1952 NormalizeCommonHeaders();
1953 return InnerCollection.GetKey(index);
1956 public override string[] AllKeys
1960 NormalizeCommonHeaders();
1961 return InnerCollection.AllKeys;
1965 public override void Clear()
1967 m_CommonHeaders = null;
1968 m_NumCommonHeaders = 0;
1969 InvalidateCachedArrays();
1970 if (m_InnerCollection != null)
1971 m_InnerCollection.Clear();
1973 } // class WebHeaderCollection
1976 internal class CaseInsensitiveAscii : IEqualityComparer, IComparer{
1977 // ASCII char ToLower table
1978 internal static readonly CaseInsensitiveAscii StaticInstance = new CaseInsensitiveAscii();
1979 internal static readonly byte[] AsciiToLower = new byte[] {
1980 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
1981 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
1982 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
1983 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
1984 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
1985 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
1986 60, 61, 62, 63, 64, 97, 98, 99,100,101, // 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
1987 102,103,104,105,106,107,108,109,110,111, // 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
1988 112,113,114,115,116,117,118,119,120,121, // 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
1989 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, // 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
1990 100,101,102,103,104,105,106,107,108,109,
1991 110,111,112,113,114,115,116,117,118,119,
1992 120,121,122,123,124,125,126,127,128,129,
1993 130,131,132,133,134,135,136,137,138,139,
1994 140,141,142,143,144,145,146,147,148,149,
1995 150,151,152,153,154,155,156,157,158,159,
1996 160,161,162,163,164,165,166,167,168,169,
1997 170,171,172,173,174,175,176,177,178,179,
1998 180,181,182,183,184,185,186,187,188,189,
1999 190,191,192,193,194,195,196,197,198,199,
2000 200,201,202,203,204,205,206,207,208,209,
2001 210,211,212,213,214,215,216,217,218,219,
2002 220,221,222,223,224,225,226,227,228,229,
2003 230,231,232,233,234,235,236,237,238,239,
2004 240,241,242,243,244,245,246,247,248,249,
2005 250,251,252,253,254,255
2008 // ASCII string case insensitive hash function
2009 public int GetHashCode(object myObject) {
2010 string myString = myObject as string;
2011 if (myObject == null) {
2014 int myHashCode = myString.Length;
2015 if (myHashCode == 0) {
2018 myHashCode ^= AsciiToLower[(byte)myString[0]]<<24 ^ AsciiToLower[(byte)myString[myHashCode-1]]<<16;
2022 // ASCII string case insensitive comparer
2023 public int Compare(object firstObject, object secondObject) {
2024 string firstString = firstObject as string;
2025 string secondString = secondObject as string;
2026 if (firstString==null) {
2027 return secondString == null ? 0 : -1;
2029 if (secondString == null) {
2032 int result = firstString.Length - secondString.Length;
2033 int comparisons = result > 0 ? secondString.Length : firstString.Length;
2034 int difference, index = 0;
2035 while ( index < comparisons ) {
2036 difference = (int)(AsciiToLower[ firstString[index] ] - AsciiToLower[ secondString[index] ]);
2037 if ( difference != 0 ) {
2038 result = difference;
2046 // ASCII string case insensitive hash function
2047 int FastGetHashCode(string myString) {
2048 int myHashCode = myString.Length;
2049 if (myHashCode!=0) {
2050 myHashCode ^= AsciiToLower[(byte)myString[0]]<<24 ^ AsciiToLower[(byte)myString[myHashCode-1]]<<16;
2055 // ASCII string case insensitive comparer
2056 public new bool Equals(object firstObject, object secondObject) {
2057 string firstString = firstObject as string;
2058 string secondString = secondObject as string;
2059 if (firstString==null) {
2060 return secondString==null;
2062 if (secondString!=null) {
2063 int index = firstString.Length;
2064 if (index==secondString.Length) {
2065 if (FastGetHashCode(firstString)==FastGetHashCode(secondString)) {
2066 int comparisons = firstString.Length;
2069 if (AsciiToLower[firstString[index]]!=AsciiToLower[secondString[index]]) {
2081 internal class HostHeaderString {
2083 private bool m_Converted;
2084 private string m_String;
2085 private byte[] m_Bytes;
2087 internal HostHeaderString() {
2091 internal HostHeaderString(string s) {
2095 private void Init(string s) {
2097 m_Converted = false;
2101 private void Convert() {
2102 if (m_String != null && !m_Converted) {
2103 m_Bytes = Encoding.Default.GetBytes(m_String);
2104 string copy = Encoding.Default.GetString(m_Bytes);
2105 if (!(string.Compare(m_String, copy, StringComparison.Ordinal) == 0)) {
2106 m_Bytes = Encoding.UTF8.GetBytes(m_String);
2111 internal string String {
2112 get { return m_String; }
2118 internal int ByteCount {
2121 return m_Bytes.Length;
2125 internal byte[] Bytes {
2132 internal void Copy(byte[] destBytes, int destByteIndex) {
2134 Array.Copy(m_Bytes, 0, destBytes, destByteIndex, m_Bytes.Length);