| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System; |
| | | 5 | | using System.Diagnostics; |
| | | 6 | | using System.Diagnostics.CodeAnalysis; |
| | | 7 | | using System.Text; |
| | | 8 | | |
| | | 9 | | namespace System.Net.Http.Headers |
| | | 10 | | { |
| | | 11 | | // Don't derive from BaseHeaderParser since parsing is delegated to Uri.TryCreate() |
| | | 12 | | // which will remove leading and trailing whitespace. |
| | | 13 | | internal sealed class UriHeaderParser : HttpHeaderParser |
| | | 14 | | { |
| | | 15 | | private readonly UriKind _uriKind; |
| | | 16 | | |
| | 1 | 17 | | internal static readonly UriHeaderParser RelativeOrAbsoluteUriParser = |
| | 1 | 18 | | new UriHeaderParser(UriKind.RelativeOrAbsolute); |
| | | 19 | | |
| | | 20 | | private UriHeaderParser(UriKind uriKind) |
| | 1 | 21 | | : base(false) |
| | 1 | 22 | | { |
| | 1 | 23 | | _uriKind = uriKind; |
| | 1 | 24 | | } |
| | | 25 | | |
| | | 26 | | public override bool TryParseValue([NotNullWhen(true)] string? value, object? storeValue, ref int index, [NotNul |
| | 100 | 27 | | { |
| | 100 | 28 | | parsedValue = null; |
| | | 29 | | |
| | | 30 | | // Some headers support empty/null values. This one doesn't. |
| | 100 | 31 | | if (string.IsNullOrEmpty(value) || (index == value.Length)) |
| | 16 | 32 | | { |
| | 16 | 33 | | return false; |
| | | 34 | | } |
| | | 35 | | |
| | 84 | 36 | | string uriString = value; |
| | 84 | 37 | | if (index > 0) |
| | 0 | 38 | | { |
| | 0 | 39 | | uriString = value.Substring(index); |
| | 0 | 40 | | } |
| | | 41 | | |
| | 84 | 42 | | if (!Uri.TryCreate(uriString, _uriKind, out Uri? uri)) |
| | 74 | 43 | | { |
| | | 44 | | // Some servers send the host names in Utf-8. |
| | 74 | 45 | | uriString = DecodeUtf8FromString(uriString); |
| | | 46 | | |
| | 74 | 47 | | if (!Uri.TryCreate(uriString, _uriKind, out uri)) |
| | 74 | 48 | | { |
| | 74 | 49 | | return false; |
| | | 50 | | } |
| | 0 | 51 | | } |
| | | 52 | | |
| | 10 | 53 | | index = value.Length; |
| | 10 | 54 | | parsedValue = uri; |
| | 10 | 55 | | return true; |
| | 100 | 56 | | } |
| | | 57 | | |
| | | 58 | | // The normal client header parser just casts bytes to chars (see GetString). |
| | | 59 | | // Check if those bytes were actually utf-8 instead of ASCII. |
| | | 60 | | // If not, just return the input value. |
| | | 61 | | internal static string DecodeUtf8FromString(string input) |
| | 74 | 62 | | { |
| | 74 | 63 | | if (!string.IsNullOrWhiteSpace(input)) |
| | 74 | 64 | | { |
| | 74 | 65 | | int possibleUtf8Pos = input.AsSpan().IndexOfAnyExceptInRange((char)0, (char)127); |
| | 74 | 66 | | if (possibleUtf8Pos >= 0 && |
| | 74 | 67 | | !input.AsSpan(possibleUtf8Pos).ContainsAnyExceptInRange((char)0, (char)255)) |
| | 70 | 68 | | { |
| | 70 | 69 | | Span<byte> rawBytes = input.Length <= 256 ? stackalloc byte[input.Length] : new byte[input.Length]; |
| | 14104 | 70 | | for (int i = 0; i < input.Length; i++) |
| | 6982 | 71 | | { |
| | 6982 | 72 | | rawBytes[i] = (byte)input[i]; |
| | 6982 | 73 | | } |
| | | 74 | | |
| | | 75 | | try |
| | 70 | 76 | | { |
| | | 77 | | // We don't want '?' replacement characters, just fail. |
| | 70 | 78 | | Encoding decoder = Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback, DecoderFallb |
| | 70 | 79 | | return decoder.GetString(rawBytes); |
| | | 80 | | } |
| | 144 | 81 | | catch (ArgumentException) { } // Not actually Utf-8 |
| | 48 | 82 | | } |
| | 52 | 83 | | } |
| | | 84 | | |
| | 52 | 85 | | return input; |
| | 74 | 86 | | } |
| | | 87 | | |
| | | 88 | | public override string ToString(object value) |
| | 15 | 89 | | { |
| | 15 | 90 | | Debug.Assert(value is Uri); |
| | 15 | 91 | | Uri uri = (Uri)value; |
| | | 92 | | |
| | 15 | 93 | | if (uri.IsAbsoluteUri) |
| | 9 | 94 | | { |
| | 9 | 95 | | return uri.AbsoluteUri; |
| | | 96 | | } |
| | | 97 | | else |
| | 6 | 98 | | { |
| | 6 | 99 | | return uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); |
| | | 100 | | } |
| | 15 | 101 | | } |
| | | 102 | | } |
| | | 103 | | } |