File: System\Net\HeaderInfoTable.cs
Web Access
Project: src\src\libraries\System.Net.WebHeaderCollection\src\System.Net.WebHeaderCollection.csproj (System.Net.WebHeaderCollection)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
 
namespace System.Net
{
    internal sealed class HeaderInfoTable
    {
        private static readonly Func<string, string[]> s_singleParser = value => new[] { value };
        private static readonly Func<string, string[]> s_multiParser = value => ParseValueHelper(value, isSetCookie: false);
        private static readonly Func<string, string[]> s_setCookieParser = value => ParseValueHelper(value, isSetCookie: true);
        private static readonly HeaderInfo s_unknownHeaderInfo = new HeaderInfo(string.Empty, false, false, false, s_singleParser);
        private static readonly Hashtable s_headerHashTable = CreateHeaderHashtable();
 
        private static string[] ParseValueHelper(string value, bool isSetCookie)
        {
            // RFC 6265: (for Set-Cookie header)
            // If the name-value-pair string lacks a %x3D ("=") character, ignore the set-cookie-string entirely.
            if (isSetCookie && (!value.Contains('='))) return Array.Empty<string>();
 
            var tempStringCollection = new List<string>();
 
            bool inquote = false;
            int startIndex = 0;
            int length = 0;
 
            for (int i = 0; i < value.Length; i++)
            {
                if (value[i] == '\"')
                {
                    inquote = !inquote;
                }
                else if ((value[i] == ',') && !inquote)
                {
                    ReadOnlySpan<char> singleValue = value.AsSpan(startIndex, length).Trim();
                    if (!isSetCookie || !IsDuringExpiresAttributeParsing(singleValue))
                    {
                        tempStringCollection.Add(singleValue.ToString());
                        startIndex = i + 1;
                        length = 0;
                        continue;
                    }
                }
                length++;
            }
 
            // Now add the last of the header values to the string table.
            if (startIndex < value.Length && length > 0)
            {
                tempStringCollection.Add(value.AsSpan(startIndex, length).Trim().ToString());
            }
 
            return tempStringCollection.ToArray();
        }
 
        // This method is to check if we are in the middle of parsing the Expires attribute
        // for Set-Cookie header. It needs to check two conditions: 1. If current attribute
        // is Expires. 2. Have we finished parsing it yet. Because the Expires attribute
        // will contain exactly one comma, no comma means we are still parsing it.
        private static bool IsDuringExpiresAttributeParsing(ReadOnlySpan<char> singleValue)
        {
            int semiPos = singleValue.LastIndexOf(';');
            if (semiPos >= 0)
            {
                ReadOnlySpan<char> lastElement = singleValue.Slice(semiPos + 1);
                if (!lastElement.Contains(','))
                {
                    int equalsPos = lastElement.IndexOf('=');
                    if (equalsPos < 0)
                    {
                        equalsPos = lastElement.Length;
                    }
 
                    return lastElement.Slice(0, equalsPos).Trim().Equals("Expires", StringComparison.OrdinalIgnoreCase);
                }
            }
 
            return false;
        }
 
        private static Hashtable CreateHeaderHashtable()
        {
            const int Items = 52;
            var headers = new Hashtable(Items * 2, StringComparer.OrdinalIgnoreCase)
            {
                { HttpKnownHeaderNames.Age,                new HeaderInfo(HttpKnownHeaderNames.Age,                false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.Allow,              new HeaderInfo(HttpKnownHeaderNames.Allow,              false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.Accept,             new HeaderInfo(HttpKnownHeaderNames.Accept,             true,   false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.Authorization,      new HeaderInfo(HttpKnownHeaderNames.Authorization,      false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.AcceptRanges,       new HeaderInfo(HttpKnownHeaderNames.AcceptRanges,       false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.AcceptCharset,      new HeaderInfo(HttpKnownHeaderNames.AcceptCharset,      false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.AcceptEncoding,     new HeaderInfo(HttpKnownHeaderNames.AcceptEncoding,     false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.AcceptLanguage,     new HeaderInfo(HttpKnownHeaderNames.AcceptLanguage,     false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.Cookie,             new HeaderInfo(HttpKnownHeaderNames.Cookie,             false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.Connection,         new HeaderInfo(HttpKnownHeaderNames.Connection,         true,   false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.ContentMD5,         new HeaderInfo(HttpKnownHeaderNames.ContentMD5,         false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.ContentType,        new HeaderInfo(HttpKnownHeaderNames.ContentType,        true,   false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.CacheControl,       new HeaderInfo(HttpKnownHeaderNames.CacheControl,       false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.ContentRange,       new HeaderInfo(HttpKnownHeaderNames.ContentRange,       false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.ContentLength,      new HeaderInfo(HttpKnownHeaderNames.ContentLength,      true,   true,   false,  s_singleParser) },
                { HttpKnownHeaderNames.ContentEncoding,    new HeaderInfo(HttpKnownHeaderNames.ContentEncoding,    false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.ContentLanguage,    new HeaderInfo(HttpKnownHeaderNames.ContentLanguage,    false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.ContentLocation,    new HeaderInfo(HttpKnownHeaderNames.ContentLocation,    false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.Date,               new HeaderInfo(HttpKnownHeaderNames.Date,               true,   false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.ETag,               new HeaderInfo(HttpKnownHeaderNames.ETag,               false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.Expect,             new HeaderInfo(HttpKnownHeaderNames.Expect,             true,   false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.Expires,            new HeaderInfo(HttpKnownHeaderNames.Expires,            false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.From,               new HeaderInfo(HttpKnownHeaderNames.From,               false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.Host,               new HeaderInfo(HttpKnownHeaderNames.Host,               true,   false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.IfMatch,            new HeaderInfo(HttpKnownHeaderNames.IfMatch,            false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.IfRange,            new HeaderInfo(HttpKnownHeaderNames.IfRange,            false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.IfNoneMatch,        new HeaderInfo(HttpKnownHeaderNames.IfNoneMatch,        false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.IfModifiedSince,    new HeaderInfo(HttpKnownHeaderNames.IfModifiedSince,    true,   false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.IfUnmodifiedSince,  new HeaderInfo(HttpKnownHeaderNames.IfUnmodifiedSince,  false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.KeepAlive,          new HeaderInfo(HttpKnownHeaderNames.KeepAlive,          false,  true,   false,  s_singleParser) },
                { HttpKnownHeaderNames.Location,           new HeaderInfo(HttpKnownHeaderNames.Location,           false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.LastModified,       new HeaderInfo(HttpKnownHeaderNames.LastModified,       false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.MaxForwards,        new HeaderInfo(HttpKnownHeaderNames.MaxForwards,        false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.Pragma,             new HeaderInfo(HttpKnownHeaderNames.Pragma,             false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.ProxyAuthenticate,  new HeaderInfo(HttpKnownHeaderNames.ProxyAuthenticate,  false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.ProxyAuthorization, new HeaderInfo(HttpKnownHeaderNames.ProxyAuthorization, false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.ProxyConnection,    new HeaderInfo(HttpKnownHeaderNames.ProxyConnection,    true,   false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.Range,              new HeaderInfo(HttpKnownHeaderNames.Range,              true,   false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.Referer,            new HeaderInfo(HttpKnownHeaderNames.Referer,            true,   false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.RetryAfter,         new HeaderInfo(HttpKnownHeaderNames.RetryAfter,         false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.Server,             new HeaderInfo(HttpKnownHeaderNames.Server,             false,  false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.SetCookie,          new HeaderInfo(HttpKnownHeaderNames.SetCookie,          false,  false,  true,   s_setCookieParser) },
                { HttpKnownHeaderNames.SetCookie2,         new HeaderInfo(HttpKnownHeaderNames.SetCookie2,         false,  false,  true,   s_setCookieParser) },
                { HttpKnownHeaderNames.TE,                 new HeaderInfo(HttpKnownHeaderNames.TE,                 false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.Trailer,            new HeaderInfo(HttpKnownHeaderNames.Trailer,            false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.TransferEncoding,   new HeaderInfo(HttpKnownHeaderNames.TransferEncoding,   true,   true,   true,   s_multiParser) },
                { HttpKnownHeaderNames.Upgrade,            new HeaderInfo(HttpKnownHeaderNames.Upgrade,            false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.UserAgent,          new HeaderInfo(HttpKnownHeaderNames.UserAgent,          true,   false,  false,  s_singleParser) },
                { HttpKnownHeaderNames.Via,                new HeaderInfo(HttpKnownHeaderNames.Via,                false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.Vary,               new HeaderInfo(HttpKnownHeaderNames.Vary,               false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.Warning,            new HeaderInfo(HttpKnownHeaderNames.Warning,            false,  false,  true,   s_multiParser) },
                { HttpKnownHeaderNames.WWWAuthenticate,    new HeaderInfo(HttpKnownHeaderNames.WWWAuthenticate,    false,  true,   true,   s_singleParser) }
            };
            Debug.Assert(headers.Count == Items);
            return headers;
        }
 
        internal HeaderInfo this[string name]
        {
            get
            {
                HeaderInfo? tempHeaderInfo = (HeaderInfo?)s_headerHashTable[name];
                if (tempHeaderInfo == null)
                {
                    return s_unknownHeaderInfo;
                }
                return tempHeaderInfo;
            }
        }
    }
}