File: System\Net\Http\WinHttpResponseHeaderReader.cs
Web Access
Project: src\src\runtime\src\libraries\System.Net.Http.WinHttpHandler\src\System.Net.Http.WinHttpHandler.csproj (System.Net.Http.WinHttpHandler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

namespace System.Net.Http
{
    /// <summary>
    /// Used to read header lines of an HTTP response, where each line is separated by "\r\n".
    /// </summary>
    internal struct WinHttpResponseHeaderReader
    {
        private readonly char[] _buffer;
        private readonly int _length;
        private int _position;

        public WinHttpResponseHeaderReader(char[] buffer, int startIndex, int length)
        {
            CharArrayHelpers.DebugAssertArrayInputs(buffer, startIndex, length);

            _buffer = buffer;
            _position = startIndex;
            _length = length;
        }

        /// <summary>
        /// Reads a header line.
        /// Empty header lines are skipped, as are malformed header lines that are missing a colon character.
        /// </summary>
        /// <returns>true if the next header was read successfully, or false if all characters have been read.</returns>
        public bool ReadHeader([NotNullWhen(true)] out string? name, [NotNullWhen(true)] out string? value)
        {
            while (ReadLine(out int startIndex, out int length))
            {
                // Skip empty lines.
                if (length == 0)
                {
                    continue;
                }

                int colonIndex = Array.IndexOf(_buffer, ':', startIndex, length);

                // Skip malformed header lines that are missing the colon character.
                if (colonIndex < 0)
                {
                    continue;
                }

                int nameLength = colonIndex - startIndex;

                ReadOnlySpan<char> nameSpan = _buffer.AsSpan(startIndex, nameLength);

                // If it's a known header name, use the known name instead of allocating a new string.
                if (!HttpKnownHeaderNames.TryGetHeaderName(nameSpan, out name))
                {
                    name = nameSpan.ToString();
                }

                // Normalize header value by trimming whitespace.
                ReadOnlySpan<char> valueSpan = new ReadOnlySpan<char>(_buffer, colonIndex + 1, startIndex + length - colonIndex - 1).Trim();

                value = HttpKnownHeaderNames.GetHeaderValue(name, valueSpan);

                return true;
            }

            name = null;
            value = null;
            return false;
        }

        /// <summary>
        /// Reads lines separated by "\r\n".
        /// </summary>
        /// <returns>true if the next line was read successfully, or false if all characters have been read.</returns>
        public bool ReadLine()
        {
            return ReadLine(out _, out _);
        }

        /// <summary>
        /// Reads lines separated by "\r\n".
        /// </summary>
        /// <param name="startIndex">The start of the line segment.</param>
        /// <param name="length">The length of the line segment.</param>
        /// <returns>true if the next line was read successfully, or false if all characters have been read.</returns>
        private bool ReadLine(out int startIndex, out int length)
        {
            Debug.Assert(_buffer != null);

            int pos = _position;

            int newline = _buffer.AsSpan(pos, _length - pos).IndexOf("\r\n".AsSpan());
            if (newline >= 0)
            {
                startIndex = pos;
                length = newline;
                _position = pos + newline + 2;
                return true;
            }

            if (pos < _length)
            {
                startIndex = pos;
                length = _length - pos;
                _position = _length;
                return true;
            }

            startIndex = 0;
            length = 0;
            return false;
        }
    }
}