File: Internal\Http\HttpResponseHeaders.cs
Web Access
Project: src\src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj (Microsoft.AspNetCore.Server.Kestrel.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
 
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
 
internal sealed partial class HttpResponseHeaders : HttpHeaders
{
    // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
    private static ReadOnlySpan<byte> CrLf => "\r\n"u8;
    private static ReadOnlySpan<byte> ColonSpace => ": "u8;
 
    public Func<string, Encoding?> EncodingSelector { get; set; }
 
    public HttpResponseHeaders(Func<string, Encoding?>? encodingSelector = null)
    {
        EncodingSelector = encodingSelector ?? KestrelServerOptions.DefaultHeaderEncodingSelector;
    }
 
    public Enumerator GetEnumerator()
    {
        return new Enumerator(this);
    }
 
    protected override IEnumerator<KeyValuePair<string, StringValues>> GetEnumeratorFast()
    {
        return GetEnumerator();
    }
 
    internal void CopyTo(ref BufferWriter<PipeWriter> buffer)
    {
        CopyToFast(ref buffer);
 
        var extraHeaders = MaybeUnknown;
        // Only reserve stack space for the enumerators if there are extra headers
        if (extraHeaders != null && extraHeaders.Count > 0)
        {
            var encodingSelector = EncodingSelector;
            if (ReferenceEquals(encodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector))
            {
                CopyExtraHeaders(ref buffer, extraHeaders);
            }
            else
            {
                CopyExtraHeadersCustomEncoding(ref buffer, extraHeaders, encodingSelector);
            }
        }
 
        static void CopyExtraHeaders(ref BufferWriter<PipeWriter> buffer, Dictionary<string, StringValues> headers)
        {
            foreach (var kv in headers)
            {
                foreach (var value in kv.Value)
                {
                    if (value != null)
                    {
                        buffer.Write(CrLf);
                        buffer.WriteAscii(kv.Key);
                        buffer.Write(ColonSpace);
                        buffer.WriteAscii(value);
                    }
                }
            }
        }
 
        static void CopyExtraHeadersCustomEncoding(ref BufferWriter<PipeWriter> buffer, Dictionary<string, StringValues> headers,
            Func<string, Encoding?> encodingSelector)
        {
            foreach (var kv in headers)
            {
                var encoding = encodingSelector(kv.Key);
                foreach (var value in kv.Value)
                {
                    if (value != null)
                    {
                        buffer.Write(CrLf);
                        buffer.WriteAscii(kv.Key);
                        buffer.Write(ColonSpace);
 
                        if (encoding is null)
                        {
                            buffer.WriteAscii(value);
                        }
                        else
                        {
                            buffer.WriteEncoded(value, encoding);
                        }
                    }
                }
            }
        }
    }
 
    private static long ParseContentLength(string value)
    {
        if (!HeaderUtilities.TryParseNonNegativeInt64(value, out var parsed))
        {
            ThrowInvalidContentLengthException(value);
        }
 
        return parsed;
    }
 
    [DoesNotReturn]
    private static void ThrowInvalidContentLengthException(string value)
    {
        throw new InvalidOperationException(CoreStrings.FormatInvalidContentLength_InvalidNumber(value));
    }
 
    [DoesNotReturn]
    private static void ThrowInvalidHeaderBits()
    {
        throw new InvalidOperationException("Invalid Header Bits");
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    private void SetValueUnknown(string key, StringValues value)
    {
        ValidateHeaderNameCharacters(key);
        Unknown[GetInternedHeaderName(key)] = value;
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    private bool AddValueUnknown(string key, StringValues value)
    {
        ValidateHeaderNameCharacters(key);
        Unknown.Add(GetInternedHeaderName(key), value);
        // Return true, above will throw and exit for false
        return true;
    }
 
    public partial struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
    {
        private readonly HttpResponseHeaders _collection;
        private long _currentBits;
        private int _next;
        private KeyValuePair<string, StringValues> _current;
        private KnownHeaderType _currentKnownType;
        private readonly bool _hasUnknown;
        private Dictionary<string, StringValues>.Enumerator _unknownEnumerator;
 
        internal Enumerator(HttpResponseHeaders collection)
        {
            _collection = collection;
            _currentBits = collection._bits;
            _next = GetNext(_currentBits, collection.ContentLength.HasValue);
            _current = default;
            _currentKnownType = default;
            _hasUnknown = collection.MaybeUnknown != null;
            _unknownEnumerator = _hasUnknown
                ? collection.MaybeUnknown!.GetEnumerator()
                : default;
        }
 
        public readonly KeyValuePair<string, StringValues> Current => _current;
 
        internal readonly KnownHeaderType CurrentKnownType => _currentKnownType;
 
        readonly object IEnumerator.Current => _current;
 
        public readonly void Dispose()
        {
        }
 
        public void Reset()
        {
            _currentBits = _collection._bits;
            _next = GetNext(_currentBits, _collection.ContentLength.HasValue);
        }
    }
}