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 var hinfo = HInfo[name];
253 // Is common header which supports multi value or it's unknown header
254 return hinfo.AllowMultiValues || hinfo.HeaderName == "";
258 #if !FEATURE_PAL || MONO
259 private bool AllowHttpRequestHeader {
261 if (m_Type==WebHeaderCollectionType.Unknown) {
262 m_Type = WebHeaderCollectionType.WebRequest;
264 return m_Type==WebHeaderCollectionType.WebRequest || m_Type==WebHeaderCollectionType.HttpWebRequest || m_Type==WebHeaderCollectionType.HttpListenerRequest;
268 internal bool AllowHttpResponseHeader {
270 if (m_Type==WebHeaderCollectionType.Unknown) {
271 m_Type = WebHeaderCollectionType.WebResponse;
273 return m_Type==WebHeaderCollectionType.WebResponse || m_Type==WebHeaderCollectionType.HttpWebResponse || m_Type==WebHeaderCollectionType.HttpListenerResponse;
277 public string this[HttpRequestHeader header] {
279 if (!AllowHttpRequestHeader) {
280 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
282 return this[UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header)];
285 if (!AllowHttpRequestHeader) {
286 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
288 this[UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header)] = value;
291 public string this[HttpResponseHeader header] {
293 if (!AllowHttpResponseHeader) {
294 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
297 // Some of these can be mapped to Common Headers. Other cases can be added as needed for perf.
298 if (m_CommonHeaders != null)
302 case HttpResponseHeader.ProxyAuthenticate:
303 return m_CommonHeaders[c_ProxyAuthenticate];
305 case HttpResponseHeader.WwwAuthenticate:
306 return m_CommonHeaders[c_WwwAuthenticate];
310 return this[UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header)];
313 if (!AllowHttpResponseHeader) {
314 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
316 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
317 if (value!=null && value.Length>ushort.MaxValue) {
318 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
321 this[UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header)] = value;
325 public void Add(HttpRequestHeader header, string value) {
326 if (!AllowHttpRequestHeader) {
327 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
329 this.Add(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header), value);
332 public void Add(HttpResponseHeader header, string value) {
333 if (!AllowHttpResponseHeader) {
334 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
336 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
337 if (value!=null && value.Length>ushort.MaxValue) {
338 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
341 this.Add(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header), value);
344 public void Set(HttpRequestHeader header, string value) {
345 if (!AllowHttpRequestHeader) {
346 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
348 this.Set(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header), value);
351 public void Set(HttpResponseHeader header, string value) {
352 if (!AllowHttpResponseHeader) {
353 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
355 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
356 if (value!=null && value.Length>ushort.MaxValue) {
357 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
360 this.Set(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header), value);
364 internal void SetInternal(HttpResponseHeader header, string value) {
365 if (!AllowHttpResponseHeader) {
366 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
368 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
369 if (value!=null && value.Length>ushort.MaxValue) {
370 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
373 this.SetInternal(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header), value);
377 public void Remove(HttpRequestHeader header) {
378 if (!AllowHttpRequestHeader) {
379 throw new InvalidOperationException(SR.GetString(SR.net_headers_req));
381 this.Remove(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int)header));
384 public void Remove(HttpResponseHeader header) {
385 if (!AllowHttpResponseHeader) {
386 throw new InvalidOperationException(SR.GetString(SR.net_headers_rsp));
388 this.Remove(UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.ToString((int)header));
390 #endif // !FEATURE_PAL
392 // In general, HttpWebResponse headers aren't modified, so these methods don't support common headers.
395 /// <para>[To be supplied.]</para>
397 protected void AddWithoutValidate(string headerName, string headerValue) {
398 headerName = CheckBadChars(headerName, false);
399 headerValue = CheckBadChars(headerValue, true);
400 GlobalLog.Print("WebHeaderCollection::AddWithoutValidate() calling InnerCollection.Add() key:[" + headerName + "], value:[" + headerValue + "]");
401 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
402 if (headerValue!=null && headerValue.Length>ushort.MaxValue) {
403 throw new ArgumentOutOfRangeException("headerValue", headerValue, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
406 NormalizeCommonHeaders();
407 InvalidateCachedArrays();
408 InnerCollection.Add(headerName, headerValue);
411 internal void SetAddVerified(string name, string value) {
412 if(HInfo[name].AllowMultiValues) {
413 GlobalLog.Print("WebHeaderCollection::SetAddVerified() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
414 NormalizeCommonHeaders();
415 InvalidateCachedArrays();
416 InnerCollection.Add(name, value);
419 GlobalLog.Print("WebHeaderCollection::SetAddVerified() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]");
420 NormalizeCommonHeaders();
421 InvalidateCachedArrays();
422 InnerCollection.Set(name, value);
426 // Below three methods are for fast headers manipulation, bypassing all the checks
427 internal void AddInternal(string name, string value) {
428 GlobalLog.Print("WebHeaderCollection::AddInternal() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
429 NormalizeCommonHeaders();
430 InvalidateCachedArrays();
431 InnerCollection.Add(name, value);
434 internal void ChangeInternal(string name, string value) {
435 GlobalLog.Print("WebHeaderCollection::ChangeInternal() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]");
436 NormalizeCommonHeaders();
437 InvalidateCachedArrays();
438 InnerCollection.Set(name, value);
442 internal void RemoveInternal(string name) {
443 GlobalLog.Print("WebHeaderCollection::RemoveInternal() calling InnerCollection.Remove() key:[" + name + "]");
444 NormalizeCommonHeaders();
445 if (m_InnerCollection != null)
447 InvalidateCachedArrays();
448 m_InnerCollection.Remove(name);
452 internal void CheckUpdate(string name, string value) {
453 value = CheckBadChars(value, true);
454 ChangeInternal(name, value);
457 // 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.
458 private void AddInternalNotCommon(string name, string value)
460 GlobalLog.Print("WebHeaderCollection::AddInternalNotCommon() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
461 InvalidateCachedArrays();
462 InnerCollection.Add(name, value);
466 private static readonly char[] HttpTrimCharacters = new char[]{(char)0x09,(char)0xA,(char)0xB,(char)0xC,(char)0xD,(char)0x20};
469 // CheckBadChars - throws on invalid chars to be not found in header name/value
471 internal static string CheckBadChars(string name, bool isHeaderValue) {
473 if (name == null || name.Length == 0) {
474 // emtpy name is invlaid
475 if (!isHeaderValue) {
476 throw name == null ? new ArgumentNullException("name") :
477 new ArgumentException(SR.GetString(SR.net_emptystringcall, "name"), "name");
485 //Trim spaces from both ends
486 name = name.Trim(HttpTrimCharacters);
488 //First, check for correctly formed multi-line value
489 //Second, check for absenece of CTL characters
491 for(int i = 0; i < name.Length; ++i) {
492 char c = (char) (0x000000ff & (uint) name[i]);
502 // Technically this is bad HTTP. But it would be a breaking change to throw here.
503 // Is there an exploit?
506 else if (c == 127 || (c < ' ' && c != '\t'))
508 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidControlChars), "value");
518 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidCRLFChars), "value");
521 if (c == ' ' || c == '\t')
526 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidCRLFChars), "value");
531 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidCRLFChars), "value");
536 //First, check for absence of separators and spaces
537 if (name.IndexOfAny(ValidationHelper.InvalidParamChars) != -1) {
538 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidHeaderChars), "name");
541 //Second, check for non CTL ASCII-7 characters (32-126)
542 if (ContainsNonAsciiChars(name)) {
543 throw new ArgumentException(SR.GetString(SR.net_WebHeaderInvalidNonAsciiChars), "name");
549 internal static bool IsValidToken(string token) {
550 return (token.Length > 0)
551 && (token.IndexOfAny(ValidationHelper.InvalidParamChars) == -1)
552 && !ContainsNonAsciiChars(token);
555 internal static bool ContainsNonAsciiChars(string token) {
556 for (int i = 0; i < token.Length; ++i) {
557 if ((token[i] < 0x20) || (token[i] > 0x7e)) {
565 // ThrowOnRestrictedHeader - generates an error if the user,
566 // passed in a reserved string as the header name
568 internal void ThrowOnRestrictedHeader(string headerName)
570 if (m_Type == WebHeaderCollectionType.HttpWebRequest)
572 if (HInfo[headerName].IsRequestRestricted)
574 throw new ArgumentException(SR.GetString(SR.net_headerrestrict, headerName), "name");
577 else if (m_Type == WebHeaderCollectionType.HttpListenerResponse)
579 if (HInfo[headerName].IsResponseRestricted)
581 throw new ArgumentException(SR.GetString(SR.net_headerrestrict, headerName), "name");
587 // Our Public METHOD set, most are inherited from NameValueCollection,
588 // not all methods from NameValueCollection are listed, even though usable -
593 // this[name] {set, get}
594 // Remove(name), returns bool
595 // Remove(name), returns void
599 // SplitValue(name, value)
601 // ParseHeaders(char [], ...)
602 // ParseHeaders(byte [], ...)
605 // Add more headers; if "name" already exists it will
606 // add concatenated value
610 // Routine Description:
611 // Adds headers with validation to see if they are "proper" headers.
612 // Will cause header to be concat to existing if already found.
613 // If the header is a special header, listed in RestrictedHeaders object,
614 // then this call will cause an exception indication as such.
616 // name - header-name to add
617 // value - header-value to add, a header is already there, will concat this value
623 /// Adds a new header with the indicated name and value.
626 public override void Add(string name, string value) {
627 name = CheckBadChars(name, false);
628 ThrowOnRestrictedHeader(name);
629 value = CheckBadChars(value, true);
630 GlobalLog.Print("WebHeaderCollection::Add() calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
631 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
632 if (value!=null && value.Length>ushort.MaxValue) {
633 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
636 NormalizeCommonHeaders();
637 InvalidateCachedArrays();
638 InnerCollection.Add(name, value);
643 // Routine Description:
644 // Adds headers with validation to see if they are "proper" headers.
645 // Assumes a combined a "Name: Value" string, and parses the two parts out.
646 // Will cause header to be concat to existing if already found.
647 // If the header is a speical header, listed in RestrictedHeaders object,
648 // then this call will cause an exception indication as such.
650 // header - header name: value pair
656 /// Adds the indicated header.
659 public void Add(string header) {
660 if ( ValidationHelper.IsBlankString(header) ) {
661 throw new ArgumentNullException("header");
663 int colpos = header.IndexOf(':');
664 // check for badly formed header passed in
666 throw new ArgumentException(SR.GetString(SR.net_WebHeaderMissingColon), "header");
668 string name = header.Substring(0, colpos);
669 string value = header.Substring(colpos+1);
670 name = CheckBadChars(name, false);
671 ThrowOnRestrictedHeader(name);
672 value = CheckBadChars(value, true);
673 GlobalLog.Print("WebHeaderCollection::Add(" + header + ") calling InnerCollection.Add() key:[" + name + "], value:[" + value + "]");
674 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
675 if (value!=null && value.Length>ushort.MaxValue) {
676 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
679 NormalizeCommonHeaders();
680 InvalidateCachedArrays();
681 InnerCollection.Add(name, value);
685 // Routine Description:
686 // Sets headers with validation to see if they are "proper" headers.
687 // If the header is a special header, listed in RestrictedHeaders object,
688 // then this call will cause an exception indication as such.
690 // name - header-name to set
691 // value - header-value to set
697 /// Sets the specified header to the specified value.
700 public override void Set(string name, string value) {
701 if (ValidationHelper.IsBlankString(name)) {
702 throw new ArgumentNullException("name");
704 name = CheckBadChars(name, false);
705 ThrowOnRestrictedHeader(name);
706 value = CheckBadChars(value, true);
707 GlobalLog.Print("WebHeaderCollection::Set() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]");
708 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
709 if (value!=null && value.Length>ushort.MaxValue) {
710 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
713 NormalizeCommonHeaders();
714 InvalidateCachedArrays();
715 InnerCollection.Set(name, value);
719 internal void SetInternal(string name, string value) {
720 if (ValidationHelper.IsBlankString(name)) {
721 throw new ArgumentNullException("name");
723 name = CheckBadChars(name, false);
724 value = CheckBadChars(value, true);
725 GlobalLog.Print("WebHeaderCollection::Set() calling InnerCollection.Set() key:[" + name + "], value:[" + value + "]");
726 if (m_Type==WebHeaderCollectionType.HttpListenerResponse) {
727 if (value!=null && value.Length>ushort.MaxValue) {
728 throw new ArgumentOutOfRangeException("value", value, SR.GetString(SR.net_headers_toolong, ushort.MaxValue));
731 NormalizeCommonHeaders();
732 InvalidateCachedArrays();
733 InnerCollection.Set(name, value);
738 // Routine Description:
739 // Removes give header with validation to see if they are "proper" headers.
740 // If the header is a speical header, listed in RestrictedHeaders object,
741 // then this call will cause an exception indication as such.
743 // name - header-name to remove
748 /// <para>Removes the specified header.</para>
750 public override void Remove(string name) {
751 if ( ValidationHelper.IsBlankString(name) ) {
752 throw new ArgumentNullException("name");
754 ThrowOnRestrictedHeader(name);
755 name = CheckBadChars(name, false);
756 GlobalLog.Print("WebHeaderCollection::Remove() calling InnerCollection.Remove() key:[" + name + "]");
757 NormalizeCommonHeaders();
758 if (m_InnerCollection != null)
760 InvalidateCachedArrays();
761 m_InnerCollection.Remove(name);
767 // Routine Description:
768 // This method takes a header name and returns a string array representing
769 // the individual values for that headers. For example, if the headers
770 // contained the line Accept: text/plain, text/html then
771 // GetValues("Accept") would return an array of two strings: "text/plain"
774 // header - Name of the header.
776 // string[] - array of parsed string objects
780 /// Gets an array of header values stored in a
784 public override string[] GetValues(string header) {
785 // This method doesn't work with common headers. Dump common headers into the pool.
786 NormalizeCommonHeaders();
788 // First get the information about the header and the values for
790 HeaderInfo Info = HInfo[header];
791 string[] Values = InnerCollection.GetValues(header);
792 // If we have no information about the header or it doesn't allow
793 // multiple values, just return the values.
794 if (Info == null || Values == null || !Info.AllowMultiValues) {
797 // Here we have a multi value header. We need to go through
798 // each entry in the multi values array, and if an entry itself
799 // has multiple values we'll need to combine those in.
801 // We do some optimazation here, where we try not to copy the
802 // values unless there really is one that have multiple values.
804 ArrayList ValueList = null;
806 for (i = 0; i < Values.Length; i++) {
807 // Parse this value header.
808 TempValues = Info.Parser(Values[i]);
809 // If we don't have an array list yet, see if this
810 // value has multiple values.
811 if (ValueList == null) {
812 // See if it has multiple values.
813 if (TempValues.Length > 1) {
814 // It does, so we need to create an array list that
815 // represents the Values, then trim out this one and
816 // the ones after it that haven't been parsed yet.
817 ValueList = new ArrayList(Values);
818 ValueList.RemoveRange(i, Values.Length - i);
819 ValueList.AddRange(TempValues);
823 // We already have an ArrayList, so just add the values.
824 ValueList.AddRange(TempValues);
827 // See if we have an ArrayList. If we don't, just return the values.
828 // Otherwise convert the ArrayList to a string array and return that.
829 if (ValueList != null) {
830 string[] ReturnArray = new string[ValueList.Count];
831 ValueList.CopyTo(ReturnArray);
839 // Routine Description:
840 // Generates a string representation of the headers, that is ready to be sent except for it being in string format:
841 // the format looks like:
843 // Header-Name: Header-Value\r\n
844 // Header-Name2: Header-Value2\r\n
846 // Header-NameN: Header-ValueN\r\n
849 // Uses the string builder class to Append the elements together.
861 public override string ToString() {
862 string result = GetAsString(this, false, false);
863 GlobalLog.Print("WebHeaderCollection::ToString: \r\n" + result);
867 internal string ToString(bool forTrace)
869 return GetAsString(this, false, true);
874 // if winInetCompat = true then it will not insert spaces after ':'
875 // and it will output "~U" header first
877 internal static string GetAsString(NameValueCollection cc,
882 throw new InvalidOperationException();
884 #endif // FEATURE_PAL
886 if (cc == null || cc.Count == 0) {
889 StringBuilder sb = new StringBuilder(ApproxAveHeaderLineSize*cc.Count);
891 statusLine = cc[string.Empty];
892 if (statusLine != null) {
893 sb.Append(statusLine).Append("\r\n");
895 for (int i = 0; i < cc.Count ; i++) {
896 string key = cc.GetKey(i) as string;
897 string val = cc.Get(i) as string;
901 // Put a condition here that if we are using basic auth,
902 // we shouldn't put the authorization header. Otherwise
903 // the password will get saved in the trace.
908 if (ValidationHelper.IsBlankString(key)) {
918 sb.Append(val).Append("\r\n");
922 return sb.ToString();
927 // Routine Description:
928 // Generates a byte array representation of the headers, that is ready to be sent.
929 // So it Serializes our headers into a byte array suitable for sending over the net.
931 // the format looks like:
933 // Header-Name1: Header-Value1\r\n
934 // Header-Name2: Header-Value2\r\n
936 // Header-NameN: Header-ValueN\r\n
939 // Uses the ToString() method to generate, and then performs conversion.
941 // Performance Note: Why are we not doing a single copy/covert run?
942 // As the code before used to know the size of the output!
943 // Because according to Demitry, its cheaper to copy the headers twice,
944 // then it is to call the UNICODE to ANSI conversion code many times.
948 // byte [] - array of bytes values
956 public byte[] ToByteArray() {
957 // Make sure the buffer is big enough.
958 string tempStr = ToString();
960 // Use the string of headers, convert to Char Array,
961 // then convert to Bytes,
962 // serializing finally into the buffer, along the way.
964 byte[] buffer = HeaderEncoding.GetBytes(tempStr);
969 /// <para>Tests if access to the HTTP header with the provided name is accessible for setting.</para>
971 public static bool IsRestricted(string headerName)
973 return IsRestricted(headerName, false);
976 public static bool IsRestricted(string headerName, bool response)
978 return response ? HInfo[CheckBadChars(headerName, false)].IsResponseRestricted : HInfo[CheckBadChars(headerName, false)].IsRequestRestricted;
984 /// Initializes a new instance of the <see cref='System.Net.WebHeaderCollection'/>
988 public WebHeaderCollection() : base(DBNull.Value)
992 internal WebHeaderCollection(WebHeaderCollectionType type) : base(DBNull.Value)
995 if (type == WebHeaderCollectionType.HttpWebResponse)
996 m_CommonHeaders = new string[s_CommonHeaderNames.Length - 1]; // Minus one for the sentinel.
1000 internal WebHeaderCollection(NameValueCollection cc): base(DBNull.Value)
1002 m_InnerCollection = new NameValueCollection(cc.Count + 2, CaseInsensitiveAscii.StaticInstance);
1004 for (int i = 0; i < len; ++i) {
1005 String key = cc.GetKey(i);
1006 String[] values = cc.GetValues(i);
1007 if (values != null) {
1008 for (int j = 0; j < values.Length; j++) {
1009 InnerCollection.Add(key, values[j]);
1013 InnerCollection.Add(key, null);
1019 // ISerializable constructor
1022 /// <para>[To be supplied.]</para>
1024 protected WebHeaderCollection(SerializationInfo serializationInfo, StreamingContext streamingContext) :
1027 int count = serializationInfo.GetInt32("Count");
1028 m_InnerCollection = new NameValueCollection(count + 2, CaseInsensitiveAscii.StaticInstance);
1029 for (int i = 0; i < count; i++) {
1030 string headerName = serializationInfo.GetString(i.ToString(NumberFormatInfo.InvariantInfo));
1031 string headerValue = serializationInfo.GetString((i+count).ToString(NumberFormatInfo.InvariantInfo));
1032 GlobalLog.Print("WebHeaderCollection::.ctor(ISerializable) calling InnerCollection.Add() key:[" + headerName + "], value:[" + headerValue + "]");
1033 InnerCollection.Add(headerName, headerValue);
1037 public override void OnDeserialization(object sender) {
1044 // ISerializable method
1047 [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)]
1048 public override void GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext) {
1050 // for now disregard streamingContext.
1052 NormalizeCommonHeaders();
1053 serializationInfo.AddValue("Count", Count);
1054 for (int i = 0; i < Count; i++)
1056 serializationInfo.AddValue(i.ToString(NumberFormatInfo.InvariantInfo), GetKey(i));
1057 serializationInfo.AddValue((i + Count).ToString(NumberFormatInfo.InvariantInfo), Get(i));
1062 // we use this static class as a helper class to encode/decode HTTP headers.
1063 // what we need is a 1-1 correspondence between a char in the range U+0000-U+00FF
1064 // and a byte in the range 0x00-0xFF (which is the range that can hit the network).
1065 // The Latin-1 encoding (ISO-88591-1) (GetEncoding(28591)) works for byte[] to string, but is a little slow.
1066 // It doesn't work for string -> byte[] because of best-fit-mapping problems.
1067 internal static class HeaderEncoding
1069 internal static unsafe string GetString(byte[] bytes, int byteIndex, int byteCount)
1071 fixed(byte* pBytes = bytes)
1072 return GetString(pBytes + byteIndex, byteCount);
1075 internal static unsafe string GetString(byte* pBytes, int byteCount)
1080 string s = new String('\0', byteCount);
1082 fixed (char* pStr = s)
1084 char* pString = pStr;
1085 while (byteCount >= 8)
1087 pString[0] = (char) pBytes[0];
1088 pString[1] = (char) pBytes[1];
1089 pString[2] = (char) pBytes[2];
1090 pString[3] = (char) pBytes[3];
1091 pString[4] = (char) pBytes[4];
1092 pString[5] = (char) pBytes[5];
1093 pString[6] = (char) pBytes[6];
1094 pString[7] = (char) pBytes[7];
1099 for (int i = 0; i < byteCount; i++)
1101 pString[i] = (char) pBytes[i];
1108 internal static int GetByteCount(string myString) {
1109 return myString.Length;
1111 internal unsafe static void GetBytes(string myString, int charIndex, int charCount, byte[] bytes, int byteIndex) {
1112 if (myString.Length==0) {
1115 fixed (byte *bufferPointer = bytes) {
1116 byte* newBufferPointer = bufferPointer + byteIndex;
1117 int finalIndex = charIndex + charCount;
1118 while (charIndex<finalIndex) {
1119 *newBufferPointer++ = (byte)myString[charIndex++];
1123 internal unsafe static byte[] GetBytes(string myString) {
1124 byte[] bytes = new byte[myString.Length];
1125 if (myString.Length!=0) {
1126 GetBytes(myString, 0, myString.Length, bytes, 0);
1131 // The normal client header parser just casts bytes to chars (see GetString).
1132 // Check if those bytes were actually utf-8 instead of ASCII.
1133 // If not, just return the input value.
1134 [System.Runtime.CompilerServices.FriendAccessAllowed]
1135 internal static string DecodeUtf8FromString(string input) {
1136 if (string.IsNullOrWhiteSpace(input)) {
1140 bool possibleUtf8 = false;
1141 for (int i = 0; i < input.Length; i++) {
1142 if (input[i] > (char)255) {
1143 return input; // This couldn't have come from the wire, someone assigned it directly.
1145 else if (input[i] > (char)127) {
1146 possibleUtf8 = true;
1151 byte[] rawBytes = new byte[input.Length];
1152 for (int i = 0; i < input.Length; i++) {
1153 if (input[i] > (char)255) {
1154 return input; // This couldn't have come from the wire, someone assigned it directly.
1156 rawBytes[i] = (byte)input[i];
1159 // We don't want '?' replacement characters, just fail.
1160 Encoding decoder = Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback,
1161 DecoderFallback.ExceptionFallback);
1162 return decoder.GetString(rawBytes);
1164 catch (ArgumentException) { } // Not actually Utf-8
1172 // Routine Description:
1174 // This code is optimized for the case in which all the headers fit in the buffer.
1175 // we support multiple re-entrance, but we won't save intermediate
1176 // state, we will just roll back all the parsing done for the current header if we can't
1177 // parse a whole one (including multiline) or decide something else ("invalid data" or "done parsing").
1179 // we're going to cycle through the loop until we
1181 // 1) find an HTTP violation (in this case we return DataParseStatus.Invalid)
1182 // 2) we need more data (in this case we return DataParseStatus.NeedMoreData)
1183 // 3) we found the end of the headers and the beginning of the entity body (in this case we return DataParseStatus.Done)
1188 // buffer - buffer containing the data to be parsed
1189 // size - size of the buffer
1190 // unparsed - offset of data yet to be parsed
1194 // DataParseStatus - status of parsing
1198 // 02/13/2001 rewrote the method from scratch.
1202 // b system.dll!System.Net.WebHeaderCollection::ParseHeaders
1203 internal unsafe DataParseStatus ParseHeaders(
1207 ref int totalResponseHeadersLength,
1208 int maximumResponseHeadersLength,
1209 ref WebParseError parseError) {
1211 fixed (byte * byteBuffer = buffer) {
1215 // quick check in the boundaries (as we use unsafe pointer)
1216 if (buffer.Length < size) {
1217 return DataParseStatus.NeedMoreData;
1220 int headerNameStartOffset = -1;
1221 int headerNameEndOffset = -1;
1222 int headerValueStartOffset = -1;
1223 int headerValueEndOffset = -1;
1224 int numberOfLf = -1;
1225 int index = unparsed;
1227 string headerMultiLineValue;
1231 // we need this because this method is entered multiple times.
1232 int localTotalResponseHeadersLength = totalResponseHeadersLength;
1234 WebParseErrorCode parseErrorCode = WebParseErrorCode.Generic;
1235 DataParseStatus parseStatus = DataParseStatus.Invalid;
1237 GlobalLog.Enter("WebHeaderCollection::ParseHeaders(): ANSI size:" + size.ToString() + ", unparsed:" + unparsed.ToString() + " buffer:[" + Encoding.ASCII.GetString(buffer, unparsed, Math.Min(256, size-unparsed)) + "]");
1241 // according to RFC216 a header can have the following syntax:
1243 // message-header = field-name ":" [ field-value ]
1244 // field-name = token
1245 // field-value = *( field-content | LWS )
1246 // field-content = <the OCTETs making up the field-value and consisting of either *TEXT or combinations of token, separators, and quoted-string>
1247 // TEXT = <any OCTET except CTLs, but including LWS>
1248 // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
1249 // SP = <US-ASCII SP, space (32)>
1250 // HT = <US-ASCII HT, horizontal-tab (9)>
1251 // CR = <US-ASCII CR, carriage return (13)>
1252 // LF = <US-ASCII LF, linefeed (10)>
1253 // LWS = [CR LF] 1*( SP | HT )
1254 // CHAR = <any US-ASCII character (octets 0 - 127)>
1255 // token = 1*<any CHAR except CTLs or separators>
1256 // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
1257 // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
1258 // qdtext = <any TEXT except <">>
1259 // quoted-pair = "\" CHAR
1263 // At each iteration of the following loop we expect to parse a single HTTP header entirely.
1267 // trim leading whitespaces (LWS) just for extra robustness, in fact if there are leading white spaces then:
1268 // 1) it could be that after the status line we might have spaces. handle this.
1269 // 2) this should have been detected to be a multiline header so there'll be no spaces and we'll spend some time here.
1271 headerName = string.Empty;
1272 headerValue = string.Empty;
1273 spaceAfterLf = false;
1274 headerMultiLineValue = null;
1278 // so, restrict this extra trimming only on the first header line
1280 while (index < size) {
1281 ch = (char) byteBuffer[index];
1282 if (ch == ' ' || ch == '\t') {
1284 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1285 parseStatus = DataParseStatus.DataTooBig;
1296 // we reached the end of the buffer. ask for more data.
1298 parseStatus = DataParseStatus.NeedMoreData;
1304 // what we have here is the beginning of a new header
1306 headerNameStartOffset = index;
1308 while (index < size) {
1309 ch = (char) byteBuffer[index];
1310 if (ch != ':' && ch != '\n') {
1313 // if there's an illegal character we should return DataParseStatus.Invalid
1314 // instead we choose to be flexible, try to trim it, but include it in the string
1316 headerNameEndOffset = index;
1319 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1320 parseStatus = DataParseStatus.DataTooBig;
1327 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1328 parseStatus = DataParseStatus.DataTooBig;
1337 // we reached the end of the buffer. ask for more data.
1339 parseStatus = DataParseStatus.NeedMoreData;
1345 // skip all [' ','\t','\r','\n'] characters until HeaderValue starts
1346 // if we didn't find any headers yet, we set numberOfLf to 1
1347 // so that we take the '\n' from the status line into account
1350 numberOfLf = (Count == 0 && headerNameEndOffset < 0) ? 1 : 0;
1351 while (index<size && numberOfLf<2) {
1352 ch = (char) byteBuffer[index];
1356 // In this case, need to check for a space.
1357 if (numberOfLf == 1)
1359 if (index + 1 == size)
1362 // we reached the end of the buffer. ask for more data.
1363 // need to be able to peek after the \n and see if there's some space.
1365 parseStatus = DataParseStatus.NeedMoreData;
1368 spaceAfterLf = (char) byteBuffer[index + 1] == ' ' || (char) byteBuffer[index + 1] == '\t';
1372 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1373 parseStatus = DataParseStatus.DataTooBig;
1381 if (numberOfLf==2 || (numberOfLf==1 && !spaceAfterLf)) {
1383 // if we've counted two '\n' we got at the end of the headers even if we're past the end of the buffer
1384 // if we've counted one '\n' and the first character after that was a ' ' or a '\t'
1385 // no matter if we found a ':' or not, treat this as an empty header name.
1391 // we reached the end of the buffer. ask for more data.
1393 parseStatus = DataParseStatus.NeedMoreData;
1397 headerValueStartOffset = index;
1399 while (index<size) {
1400 ch = (char) byteBuffer[index];
1403 headerValueEndOffset = index;
1406 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1407 parseStatus = DataParseStatus.DataTooBig;
1417 // we reached the end of the buffer. ask for more data.
1419 parseStatus = DataParseStatus.NeedMoreData;
1424 // at this point we found either a '\n' or the end of the headers
1425 // hence we are at the end of the Header Line. 4 options:
1426 // 1) need more data
1427 // 2) if we find two '\n' => end of headers
1428 // 3) if we find one '\n' and a ' ' or a '\t' => multiline header
1429 // 4) if we find one '\n' and a valid char => next header
1432 while (index<size && numberOfLf<2) {
1433 ch = (char) byteBuffer[index];
1434 if (ch =='\r' || ch == '\n') {
1439 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1440 parseStatus = DataParseStatus.DataTooBig;
1448 if (index==size && numberOfLf<2) {
1450 // we reached the end of the buffer but not of the headers. ask for more data.
1452 parseStatus = DataParseStatus.NeedMoreData;
1457 if (headerValueStartOffset>=0 && headerValueStartOffset>headerNameEndOffset && headerValueEndOffset>=headerValueStartOffset) {
1459 // Encoding fastest way to build the UNICODE string off the byte[]
1461 headerValue = HeaderEncoding.GetString(byteBuffer + headerValueStartOffset, headerValueEndOffset - headerValueStartOffset + 1);
1465 // if we got here from the beginning of the for loop, headerMultiLineValue will be null
1466 // otherwise it will contain the headerValue constructed for the multiline header
1467 // add this line as well to it, separated by a single space
1469 headerMultiLineValue = (headerMultiLineValue==null ? headerValue : headerMultiLineValue + " " + headerValue);
1471 if (index < size && numberOfLf == 1) {
1472 ch = (char) byteBuffer[index];
1473 if (ch == ' ' || ch == '\t') {
1475 // since we found only one Lf and the next header line begins with a Lws,
1476 // this is the beginning of a multiline header.
1477 // parse the next line into headerValue later it will be added to headerMultiLineValue
1480 if (maximumResponseHeadersLength>=0 && ++localTotalResponseHeadersLength>=maximumResponseHeadersLength) {
1481 parseStatus = DataParseStatus.DataTooBig;
1488 if (headerNameStartOffset>=0 && headerNameEndOffset>=headerNameStartOffset) {
1490 // Encoding is the fastest way to build the UNICODE string off the byte[]
1492 headerName = HeaderEncoding.GetString(byteBuffer + headerNameStartOffset, headerNameEndOffset - headerNameStartOffset + 1);
1496 // now it's finally safe to add the header if we have a name for it
1498 if (headerName.Length>0) {
1500 // the base clasee will check for pre-existing headerValue and append
1501 // it using commas as indicated in the RFC
1503 GlobalLog.Print("WebHeaderCollection::ParseHeaders() calling AddInternal() key:[" + headerName + "], value:[" + headerMultiLineValue + "]");
1504 AddInternal(headerName, headerMultiLineValue);
1508 // and update unparsed
1510 totalResponseHeadersLength = localTotalResponseHeadersLength;
1513 if (numberOfLf==2) {
1514 parseStatus = DataParseStatus.Done;
1521 GlobalLog.Leave("WebHeaderCollection::ParseHeaders() returning parseStatus:" + parseStatus.ToString());
1522 if (parseStatus == DataParseStatus.Invalid) {
1523 parseError.Section = WebParseErrorSection.ResponseHeader;
1524 parseError.Code = parseErrorCode;
1532 // Alternative parsing that follows RFC2616. Like the above, this trims both sides of the header value and replaces
1533 // folding with a single space.
1535 private enum RfcChar : byte
1547 private static RfcChar[] RfcCharMap = new RfcChar[128]
1549 RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl,
1550 RfcChar.Ctl, RfcChar.WS, RfcChar.LF, RfcChar.Ctl, RfcChar.Ctl, RfcChar.CR, RfcChar.Ctl, RfcChar.Ctl,
1551 RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl,
1552 RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl, RfcChar.Ctl,
1553 RfcChar.WS, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1554 RfcChar.Delim, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim,
1555 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1556 RfcChar.Reg, RfcChar.Reg, RfcChar.Colon, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim, RfcChar.Delim,
1557 RfcChar.Delim, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1558 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, 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.Delim, RfcChar.Delim, RfcChar.Delim, 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.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1563 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Reg,
1564 RfcChar.Reg, RfcChar.Reg, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Delim, RfcChar.Reg, RfcChar.Ctl,
1567 internal unsafe DataParseStatus ParseHeadersStrict(
1571 ref int totalResponseHeadersLength,
1572 int maximumResponseHeadersLength,
1573 ref WebParseError parseError)
1575 GlobalLog.Enter("WebHeaderCollection::ParseHeadersStrict(): size:" + size.ToString() + ", unparsed:" + unparsed.ToString() + " buffer:[" + Encoding.ASCII.GetString(buffer, unparsed, Math.Min(256, size-unparsed)) + "]");
1577 WebParseErrorCode parseErrorCode = WebParseErrorCode.Generic;
1578 DataParseStatus parseStatus = DataParseStatus.Invalid;
1582 int effectiveSize = maximumResponseHeadersLength <= 0 ? Int32.MaxValue : maximumResponseHeadersLength - totalResponseHeadersLength + i;
1583 DataParseStatus sizeError = DataParseStatus.DataTooBig;
1584 if (size < effectiveSize)
1586 effectiveSize = size;
1587 sizeError = DataParseStatus.NeedMoreData;
1591 if (i >= effectiveSize)
1593 parseStatus = sizeError;
1597 fixed (byte* byteBuffer = buffer)
1601 // If this is CRLF, actually we're done.
1602 if (byteBuffer[i] == '\r')
1604 if (++i == effectiveSize)
1606 parseStatus = sizeError;
1610 if (byteBuffer[i++] == '\n')
1612 totalResponseHeadersLength += i - unparsed;
1614 parseStatus = DataParseStatus.Done;
1618 parseStatus = DataParseStatus.Invalid;
1619 parseErrorCode = WebParseErrorCode.CrLfError;
1623 // Find the header name; only regular characters allowed.
1625 for (; i < effectiveSize && (ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) == RfcChar.Reg; i++);
1626 if (i == effectiveSize)
1628 parseStatus = sizeError;
1631 if (i == iBeginName)
1633 parseStatus = DataParseStatus.Invalid;
1634 parseErrorCode = WebParseErrorCode.InvalidHeaderName;
1639 int iEndName = i - 1;
1640 int crlf = 0; // 1 = cr, 2 = crlf
1641 for (; i < effectiveSize && (ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) != RfcChar.Colon; i++)
1669 parseStatus = DataParseStatus.Invalid;
1670 parseErrorCode = WebParseErrorCode.CrLfError;
1673 if (i == effectiveSize)
1675 parseStatus = sizeError;
1680 parseStatus = DataParseStatus.Invalid;
1681 parseErrorCode = WebParseErrorCode.IncompleteHeaderLine;
1686 if (++i == effectiveSize)
1688 parseStatus = sizeError;
1692 // Read the value. crlf = 3 means in the whitespace after a CRLF
1693 int iBeginValue = -1;
1695 StringBuilder valueAccumulator = null;
1696 for (; i < effectiveSize && ((ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) == RfcChar.WS || crlf != 2); i++)
1738 if (iBeginValue != -1)
1740 string s = HeaderEncoding.GetString(byteBuffer + iBeginValue, iEndValue - iBeginValue + 1);
1741 if (valueAccumulator == null)
1743 valueAccumulator = new StringBuilder(s, s.Length * 5);
1747 valueAccumulator.Append(" ");
1748 valueAccumulator.Append(s);
1753 if (iBeginValue == -1)
1760 parseStatus = DataParseStatus.Invalid;
1761 parseErrorCode = WebParseErrorCode.CrLfError;
1764 if (i == effectiveSize)
1766 parseStatus = sizeError;
1771 string sValue = iBeginValue == -1 ? "" : HeaderEncoding.GetString(byteBuffer + iBeginValue, iEndValue - iBeginValue + 1);
1772 if (valueAccumulator != null)
1774 if (sValue.Length != 0)
1776 valueAccumulator.Append(" ");
1777 valueAccumulator.Append(sValue);
1779 sValue = valueAccumulator.ToString();
1782 // Make the name. See if it's a common header first.
1783 string sName = null;
1784 int headerNameLength = iEndName - iBeginName + 1;
1785 if (m_CommonHeaders != null)
1787 int iHeader = s_CommonHeaderHints[byteBuffer[iBeginName] & 0x1f];
1792 string s = s_CommonHeaderNames[iHeader++];
1794 // Not found if we get to a shorter header or one with a different first character.
1795 if (s.Length < headerNameLength || CaseInsensitiveAscii.AsciiToLower[byteBuffer[iBeginName]] != CaseInsensitiveAscii.AsciiToLower[s[0]])
1798 // Keep looking if the common header is too long.
1799 if (s.Length > headerNameLength)
1803 byte* pBuffer = byteBuffer + iBeginName + 1;
1804 for (j = 1; j < s.Length; j++)
1806 // Avoid the case-insensitive compare in the common case where they match.
1807 if (*(pBuffer++) != s[j] && CaseInsensitiveAscii.AsciiToLower[*(pBuffer - 1)] != CaseInsensitiveAscii.AsciiToLower[s[j]])
1812 // Set it to the appropriate index.
1813 m_NumCommonHeaders++;
1815 if (m_CommonHeaders[iHeader] == null)
1817 m_CommonHeaders[iHeader] = sValue;
1821 // Don't currently handle combining multiple header instances in the common header case.
1822 // Nothing to do but punt them all to the NameValueCollection.
1823 NormalizeCommonHeaders();
1824 AddInternalNotCommon(s, sValue);
1834 // If it wasn't a common header, add it to the hash.
1837 sName = HeaderEncoding.GetString(byteBuffer + iBeginName, headerNameLength);
1838 AddInternalNotCommon(sName, sValue);
1841 totalResponseHeadersLength += i - unparsed;
1847 GlobalLog.Leave("WebHeaderCollection::ParseHeadersStrict() returning parseStatus:" + parseStatus.ToString());
1849 if (parseStatus == DataParseStatus.Invalid) {
1850 parseError.Section = WebParseErrorSection.ResponseHeader;
1851 parseError.Code = parseErrorCode;
1858 // Keeping this version for backwards compatibility (mostly with reflection). Remove some day, along with the interface
1859 // explicit reimplementation.
1862 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase", Justification = "System.dll is still using pre-v4 security model and needs this demand")]
1863 [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter, SerializationFormatter=true)]
1864 void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext)
1866 GetObjectData(serializationInfo, streamingContext);
1869 // Override Get() to check the common headers.
1870 public override string Get(string name)
1872 // In this case, need to make sure name doesn't have any Unicode since it's being used as an index into tables.
1873 if (m_CommonHeaders != null && name != null && name.Length > 0 && name[0] < 256)
1875 int iHeader = s_CommonHeaderHints[name[0] & 0x1f];
1880 string s = s_CommonHeaderNames[iHeader++];
1882 // Not found if we get to a shorter header or one with a different first character.
1883 if (s.Length < name.Length || CaseInsensitiveAscii.AsciiToLower[name[0]] != CaseInsensitiveAscii.AsciiToLower[s[0]])
1886 // Keep looking if the common header is too long.
1887 if (s.Length > name.Length)
1891 for (j = 1; j < s.Length; j++)
1893 // Avoid the case-insensitive compare in the common case where they match.
1894 if (name[j] != s[j] && (name[j] > 255 || CaseInsensitiveAscii.AsciiToLower[name[j]] != CaseInsensitiveAscii.AsciiToLower[s[j]]))
1899 // Get the appropriate header.
1900 return m_CommonHeaders[iHeader - 1];
1906 // Fall back to normal lookup.
1907 if (m_InnerCollection == null)
1909 return m_InnerCollection.Get(name);
1914 // Additional overrides required to fully orphan the base implementation.
1916 public override IEnumerator GetEnumerator()
1918 NormalizeCommonHeaders();
1919 return new NameObjectKeysEnumerator(InnerCollection);
1922 public override int Count
1926 return (m_InnerCollection == null ? 0 : m_InnerCollection.Count) + m_NumCommonHeaders;
1930 public override KeysCollection Keys
1934 NormalizeCommonHeaders();
1935 return InnerCollection.Keys;
1939 internal override bool InternalHasKeys()
1941 NormalizeCommonHeaders();
1942 if (m_InnerCollection == null)
1944 return m_InnerCollection.HasKeys();
1947 public override string Get(int index)
1949 NormalizeCommonHeaders();
1950 return InnerCollection.Get(index);
1953 public override string[] GetValues(int index)
1955 NormalizeCommonHeaders();
1956 return InnerCollection.GetValues(index);
1959 public override string GetKey(int index)
1961 NormalizeCommonHeaders();
1962 return InnerCollection.GetKey(index);
1965 public override string[] AllKeys
1969 NormalizeCommonHeaders();
1970 return InnerCollection.AllKeys;
1974 public override void Clear()
1976 m_CommonHeaders = null;
1977 m_NumCommonHeaders = 0;
1978 InvalidateCachedArrays();
1979 if (m_InnerCollection != null)
1980 m_InnerCollection.Clear();
1982 } // class WebHeaderCollection
1985 internal class CaseInsensitiveAscii : IEqualityComparer, IComparer{
1986 // ASCII char ToLower table
1987 internal static readonly CaseInsensitiveAscii StaticInstance = new CaseInsensitiveAscii();
1988 internal static readonly byte[] AsciiToLower = new byte[] {
1989 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
1990 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
1991 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
1992 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
1993 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
1994 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
1995 60, 61, 62, 63, 64, 97, 98, 99,100,101, // 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
1996 102,103,104,105,106,107,108,109,110,111, // 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
1997 112,113,114,115,116,117,118,119,120,121, // 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
1998 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, // 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
1999 100,101,102,103,104,105,106,107,108,109,
2000 110,111,112,113,114,115,116,117,118,119,
2001 120,121,122,123,124,125,126,127,128,129,
2002 130,131,132,133,134,135,136,137,138,139,
2003 140,141,142,143,144,145,146,147,148,149,
2004 150,151,152,153,154,155,156,157,158,159,
2005 160,161,162,163,164,165,166,167,168,169,
2006 170,171,172,173,174,175,176,177,178,179,
2007 180,181,182,183,184,185,186,187,188,189,
2008 190,191,192,193,194,195,196,197,198,199,
2009 200,201,202,203,204,205,206,207,208,209,
2010 210,211,212,213,214,215,216,217,218,219,
2011 220,221,222,223,224,225,226,227,228,229,
2012 230,231,232,233,234,235,236,237,238,239,
2013 240,241,242,243,244,245,246,247,248,249,
2014 250,251,252,253,254,255
2017 // ASCII string case insensitive hash function
2018 public int GetHashCode(object myObject) {
2019 string myString = myObject as string;
2020 if (myObject == null) {
2023 int myHashCode = myString.Length;
2024 if (myHashCode == 0) {
2027 myHashCode ^= AsciiToLower[(byte)myString[0]]<<24 ^ AsciiToLower[(byte)myString[myHashCode-1]]<<16;
2031 // ASCII string case insensitive comparer
2032 public int Compare(object firstObject, object secondObject) {
2033 string firstString = firstObject as string;
2034 string secondString = secondObject as string;
2035 if (firstString==null) {
2036 return secondString == null ? 0 : -1;
2038 if (secondString == null) {
2041 int result = firstString.Length - secondString.Length;
2042 int comparisons = result > 0 ? secondString.Length : firstString.Length;
2043 int difference, index = 0;
2044 while ( index < comparisons ) {
2045 difference = (int)(AsciiToLower[ firstString[index] ] - AsciiToLower[ secondString[index] ]);
2046 if ( difference != 0 ) {
2047 result = difference;
2055 // ASCII string case insensitive hash function
2056 int FastGetHashCode(string myString) {
2057 int myHashCode = myString.Length;
2058 if (myHashCode!=0) {
2059 myHashCode ^= AsciiToLower[(byte)myString[0]]<<24 ^ AsciiToLower[(byte)myString[myHashCode-1]]<<16;
2064 // ASCII string case insensitive comparer
2065 public new bool Equals(object firstObject, object secondObject) {
2066 string firstString = firstObject as string;
2067 string secondString = secondObject as string;
2068 if (firstString==null) {
2069 return secondString==null;
2071 if (secondString!=null) {
2072 int index = firstString.Length;
2073 if (index==secondString.Length) {
2074 if (FastGetHashCode(firstString)==FastGetHashCode(secondString)) {
2075 int comparisons = firstString.Length;
2078 if (AsciiToLower[firstString[index]]!=AsciiToLower[secondString[index]]) {
2090 internal class HostHeaderString {
2092 private bool m_Converted;
2093 private string m_String;
2094 private byte[] m_Bytes;
2096 internal HostHeaderString() {
2100 internal HostHeaderString(string s) {
2104 private void Init(string s) {
2106 m_Converted = false;
2110 private void Convert() {
2111 if (m_String != null && !m_Converted) {
2112 m_Bytes = Encoding.Default.GetBytes(m_String);
2113 string copy = Encoding.Default.GetString(m_Bytes);
2114 if (!(string.Compare(m_String, copy, StringComparison.Ordinal) == 0)) {
2115 m_Bytes = Encoding.UTF8.GetBytes(m_String);
2120 internal string String {
2121 get { return m_String; }
2127 internal int ByteCount {
2130 return m_Bytes.Length;
2134 internal byte[] Bytes {
2141 internal void Copy(byte[] destBytes, int destByteIndex) {
2143 Array.Copy(m_Bytes, 0, destBytes, destByteIndex, m_Bytes.Length);