File: System\Net\WebHeaderCollection.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.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.Serialization;
using System.Text;
 
namespace System.Net
{
    internal enum WebHeaderCollectionType : byte
    {
        Unknown,
        WebRequest,
        WebResponse
    }
 
    public class WebHeaderCollection : NameValueCollection, ISerializable
    {
        private const int ApproxAveHeaderLineSize = 30;
        private const int ApproxHighAvgNumHeaders = 16;
        private WebHeaderCollectionType _type;
        private NameValueCollection? _innerCollection;
 
        private static HeaderInfoTable? _headerInfo;
 
        [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        protected WebHeaderCollection(SerializationInfo serializationInfo, StreamingContext streamingContext)
        {
            throw new PlatformNotSupportedException();
        }
 
        private bool AllowHttpRequestHeader
        {
            get
            {
                if (_type == WebHeaderCollectionType.Unknown)
                {
                    _type = WebHeaderCollectionType.WebRequest;
                }
                return _type == WebHeaderCollectionType.WebRequest;
            }
        }
 
        private static HeaderInfoTable HeaderInfo => _headerInfo ??= new HeaderInfoTable();
 
        private NameValueCollection InnerCollection => _innerCollection ??= new NameValueCollection(ApproxHighAvgNumHeaders, StringComparer.OrdinalIgnoreCase);
 
        private bool AllowHttpResponseHeader
        {
            get
            {
                if (_type == WebHeaderCollectionType.Unknown)
                {
                    _type = WebHeaderCollectionType.WebResponse;
                }
                return _type == WebHeaderCollectionType.WebResponse;
            }
        }
 
        public string? this[HttpRequestHeader header]
        {
            get
            {
                if (!AllowHttpRequestHeader)
                {
                    throw new InvalidOperationException(SR.net_headers_req);
                }
                return this[header.GetName()];
            }
            set
            {
                if (!AllowHttpRequestHeader)
                {
                    throw new InvalidOperationException(SR.net_headers_req);
                }
                this[header.GetName()] = value;
            }
        }
 
        public string? this[HttpResponseHeader header]
        {
            get
            {
                if (!AllowHttpResponseHeader)
                {
                    throw new InvalidOperationException(SR.net_headers_rsp);
                }
                return this[header.GetName()];
            }
            set
            {
                if (!AllowHttpResponseHeader)
                {
                    throw new InvalidOperationException(SR.net_headers_rsp);
                }
                this[header.GetName()] = value;
            }
        }
 
#pragma warning disable CS8765 // Nullability of parameter 'name' doesn't match overridden member
        public override void Set(string name, string? value)
#pragma warning restore CS8765
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException(nameof(name));
            }
 
            name = HttpValidationHelpers.CheckBadHeaderNameChars(name);
            value = HttpValidationHelpers.CheckBadHeaderValueChars(value);
            InvalidateCachedArrays();
            InnerCollection.Set(name, value);
        }
 
        public void Set(HttpRequestHeader header, string? value)
        {
            if (!AllowHttpRequestHeader)
            {
                throw new InvalidOperationException(SR.net_headers_req);
            }
            this.Set(header.GetName(), value);
        }
 
        public void Set(HttpResponseHeader header, string? value)
        {
            if (!AllowHttpResponseHeader)
            {
                throw new InvalidOperationException(SR.net_headers_rsp);
            }
            this.Set(header.GetName(), value);
        }
 
        [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public override void GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext)
        {
            throw new PlatformNotSupportedException();
        }
 
        void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext)
        {
            throw new PlatformNotSupportedException();
        }
 
        public void Remove(HttpRequestHeader header)
        {
            if (!AllowHttpRequestHeader)
            {
                throw new InvalidOperationException(SR.net_headers_req);
            }
            this.Remove(header.GetName());
        }
 
        public void Remove(HttpResponseHeader header)
        {
            if (!AllowHttpResponseHeader)
            {
                throw new InvalidOperationException(SR.net_headers_rsp);
            }
            this.Remove(header.GetName());
        }
 
        public override void OnDeserialization(object? sender)
        {
            // Nop in desktop
        }
 
        public static bool IsRestricted(string headerName)
        {
            return IsRestricted(headerName, false);
        }
 
        public static bool IsRestricted(string headerName, bool response)
        {
            headerName = HttpValidationHelpers.CheckBadHeaderNameChars(headerName);
            return response ? HeaderInfo[headerName].IsResponseRestricted : HeaderInfo[headerName].IsRequestRestricted;
        }
 
        public override string[]? GetValues(int index)
        {
            return InnerCollection.GetValues(index);
        }
 
        // GetValues
        // Routine Description:
        //     This method takes a header name and returns a string array representing
        //     the individual values for that headers. For example, if the headers
        //     contained the line Accept: text/plain, text/html then
        //     GetValues("Accept") would return an array of two strings: "text/plain"
        //     and "text/html".
        // Arguments:
        //     header      - Name of the header.
        // Return Value:
        //     string[] - array of parsed string objects
#pragma warning disable CS8765 // Nullability of parameter 'header' doesn't match overridden member
        public override string[]? GetValues(string header)
#pragma warning restore CS8765
        {
            // First get the information about the header and the values for
            // the header.
            HeaderInfo info = HeaderInfo[header!];
            string[]? values = InnerCollection.GetValues(header);
            // If we have no information about the header or it doesn't allow
            // multiple values, just return the values.
            if (info == null || values == null || !info.AllowMultiValues)
            {
                return values;
            }
            // Here we have a multi value header. We need to go through
            // each entry in the multi values array, and if an entry itself
            // has multiple values we'll need to combine those in.
            //
            // We do some optimazation here, where we try not to copy the
            // values unless there really is one that have multiple values.
            string[] tempValues;
            List<string>? valueList = null;
            for (int i = 0; i < values.Length; i++)
            {
                // Parse this value header.
                tempValues = info.Parser(values[i]);
                // If we don't have an array list yet, see if this
                // value has multiple values.
                if (valueList == null)
                {
                    // If it's not empty, replace valueList.
                    // Because for invalid WebRequest headers, we will return empty
                    // valueList instead of the default NameValueCollection.GetValues().
                    if (tempValues != null)
                    {
                        // It does, so we need to create an array list that
                        // represents the Values, then trim out this one and
                        // the ones after it that haven't been parsed yet.
                        valueList = new List<string>(values);
                        valueList.RemoveRange(i, values.Length - i);
                        valueList.AddRange(tempValues);
                    }
                }
                else
                {
                    // We already have an List, so just add the values.
                    valueList.AddRange(tempValues);
                }
            }
            // See if we have an List. If we don't, just return the values.
            // Otherwise convert the List to a string array and return that.
            if (valueList != null)
            {
                return valueList.ToArray();
            }
            return values;
        }
 
        public override string GetKey(int index)
        {
            return InnerCollection.GetKey(index)!;
        }
 
        public override void Clear()
        {
            InvalidateCachedArrays();
            _innerCollection?.Clear();
        }
 
        public override string? Get(int index)
        {
            if (_innerCollection == null)
            {
                return null;
            }
            return _innerCollection.Get(index);
        }
 
        public override string? Get(string? name)
        {
            if (_innerCollection == null)
            {
                return null;
            }
            return _innerCollection.Get(name);
        }
 
        public void Add(HttpRequestHeader header, string? value)
        {
            if (!AllowHttpRequestHeader)
            {
                throw new InvalidOperationException(SR.net_headers_req);
            }
            this.Add(header.GetName(), value);
        }
 
        public void Add(HttpResponseHeader header, string? value)
        {
            if (!AllowHttpResponseHeader)
            {
                throw new InvalidOperationException(SR.net_headers_rsp);
            }
            this.Add(header.GetName(), value);
        }
 
        public void Add(string header)
        {
            if (string.IsNullOrEmpty(header))
            {
                throw new ArgumentNullException(nameof(header));
            }
            int colpos = header.IndexOf(':');
            // check for badly formed header passed in
            if (colpos < 0)
            {
                throw new ArgumentException(SR.net_WebHeaderMissingColon, nameof(header));
            }
            string name = header.Substring(0, colpos);
            string value = header.Substring(colpos + 1);
            name = HttpValidationHelpers.CheckBadHeaderNameChars(name);
            value = HttpValidationHelpers.CheckBadHeaderValueChars(value);
            InvalidateCachedArrays();
            InnerCollection.Add(name, value);
        }
 
#pragma warning disable CS8765 // Nullability of parameter 'name' doesn't match overridden member
        public override void Add(string name, string? value)
#pragma warning restore CS8765
        {
            ArgumentException.ThrowIfNullOrEmpty(name);
 
            name = HttpValidationHelpers.CheckBadHeaderNameChars(name);
            value = HttpValidationHelpers.CheckBadHeaderValueChars(value);
            InvalidateCachedArrays();
            InnerCollection.Add(name, value);
        }
 
        protected void AddWithoutValidate(string headerName, string? headerValue)
        {
            headerName = HttpValidationHelpers.CheckBadHeaderNameChars(headerName);
            headerValue = HttpValidationHelpers.CheckBadHeaderValueChars(headerValue);
            InvalidateCachedArrays();
            InnerCollection.Add(headerName, headerValue);
        }
 
        // Remove -
        // Routine Description:
        //     Removes give header with validation to see if they are "proper" headers.
        //     If the header is a special header, listed in RestrictedHeaders object,
        //     then this call will cause an exception indicating as such.
        // Arguments:
        //     name - header-name to remove
        // Return Value:
        //     None
 
        /// <devdoc>
        ///    <para>Removes the specified header.</para>
        /// </devdoc>
#pragma warning disable CS8765 // Nullability of parameter 'name' doesn't match overridden member
        public override void Remove(string name)
#pragma warning restore CS8765
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException(nameof(name));
            }
            name = HttpValidationHelpers.CheckBadHeaderNameChars(name);
            if (_innerCollection != null)
            {
                InvalidateCachedArrays();
                _innerCollection.Remove(name);
            }
        }
 
        // ToString()  -
        // Routine Description:
        //     Generates a string representation of the headers, that is ready to be sent except for it being in string format:
        //     the format looks like:
        //
        //     Header-Name: Header-Value\r\n
        //     Header-Name2: Header-Value2\r\n
        //     ...
        //     Header-NameN: Header-ValueN\r\n
        //     \r\n
        //
        //     Uses the string builder class to Append the elements together.
        // Arguments:
        //     None.
        // Return Value:
        //     string
        public override string ToString()
        {
            if (Count == 0)
            {
                return "\r\n";
            }
 
            var sb = new StringBuilder(ApproxAveHeaderLineSize * Count);
 
            foreach (string? key in InnerCollection)
            {
                string? val = InnerCollection.Get(key);
                sb.Append(key)
                    .Append(": ")
                    .Append(val)
                    .Append("\r\n");
            }
 
            sb.Append("\r\n");
            return sb.ToString();
        }
 
        public byte[] ToByteArray()
        {
            string tempString = this.ToString();
            return System.Text.Encoding.ASCII.GetBytes(tempString);
        }
 
        public WebHeaderCollection()
        {
        }
 
        public override int Count
        {
            get
            {
                return (_innerCollection == null ? 0 : _innerCollection.Count);
            }
        }
 
        public override KeysCollection Keys
        {
            get
            {
                return InnerCollection.Keys;
            }
        }
 
        public override string[] AllKeys
        {
            get
            {
                return InnerCollection.AllKeys!;
            }
        }
 
        public override IEnumerator GetEnumerator()
        {
            return InnerCollection.Keys.GetEnumerator();
        }
    }
}