File: System\Net\Http\Headers\UriHeaderParser.cs
Web Access
Project: src\src\libraries\System.Net.Http\src\System.Net.Http.csproj (System.Net.Http)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
 
namespace System.Net.Http.Headers
{
    // Don't derive from BaseHeaderParser since parsing is delegated to Uri.TryCreate()
    // which will remove leading and trailing whitespace.
    internal sealed class UriHeaderParser : HttpHeaderParser
    {
        private readonly UriKind _uriKind;
 
        internal static readonly UriHeaderParser RelativeOrAbsoluteUriParser =
            new UriHeaderParser(UriKind.RelativeOrAbsolute);
 
        private UriHeaderParser(UriKind uriKind)
            : base(false)
        {
            _uriKind = uriKind;
        }
 
        public override bool TryParseValue([NotNullWhen(true)] string? value, object? storeValue, ref int index, [NotNullWhen(true)] out object? parsedValue)
        {
            parsedValue = null;
 
            // Some headers support empty/null values. This one doesn't.
            if (string.IsNullOrEmpty(value) || (index == value.Length))
            {
                return false;
            }
 
            string uriString = value;
            if (index > 0)
            {
                uriString = value.Substring(index);
            }
 
            if (!Uri.TryCreate(uriString, _uriKind, out Uri? uri))
            {
                // Some servers send the host names in Utf-8.
                uriString = DecodeUtf8FromString(uriString);
 
                if (!Uri.TryCreate(uriString, _uriKind, out uri))
                {
                    return false;
                }
            }
 
            index = value.Length;
            parsedValue = uri;
            return true;
        }
 
        // The normal client header parser just casts bytes to chars (see GetString).
        // Check if those bytes were actually utf-8 instead of ASCII.
        // If not, just return the input value.
        internal static string DecodeUtf8FromString(string input)
        {
            if (!string.IsNullOrWhiteSpace(input))
            {
                int possibleUtf8Pos = input.AsSpan().IndexOfAnyExceptInRange((char)0, (char)127);
                if (possibleUtf8Pos >= 0 &&
                    !input.AsSpan(possibleUtf8Pos).ContainsAnyExceptInRange((char)0, (char)255))
                {
                    Span<byte> rawBytes = input.Length <= 256 ? stackalloc byte[input.Length] : new byte[input.Length];
                    for (int i = 0; i < input.Length; i++)
                    {
                        rawBytes[i] = (byte)input[i];
                    }
 
                    try
                    {
                        // We don't want '?' replacement characters, just fail.
                        Encoding decoder = Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
                        return decoder.GetString(rawBytes);
                    }
                    catch (ArgumentException) { } // Not actually Utf-8
                }
            }
 
            return input;
        }
 
        public override string ToString(object value)
        {
            Debug.Assert(value is Uri);
            Uri uri = (Uri)value;
 
            if (uri.IsAbsoluteUri)
            {
                return uri.AbsoluteUri;
            }
            else
            {
                return uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
            }
        }
    }
}