< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 107
Coverable lines: 107
Total lines: 181
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 62
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
CheckIriUnicodeRange(...)0%880%
CheckIriUnicodeRange(...)0%880%
CheckIriUnicodeRange(...)0%16160%
IsInInclusiveRange(...)100%110%
EscapeUnescapeIri(...)0%30300%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.Uri/src/System/IriHelper.cs

#LineLine coverage
 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
 4using System.Diagnostics;
 5using System.Runtime.CompilerServices;
 6using System.Text;
 7
 8namespace System
 9{
 10    internal static class IriHelper
 11    {
 12        //
 13        // Checks if provided non surrogate char lies in iri range
 14        // This method implements the ABNF checks per https://tools.ietf.org/html/rfc3987#section-2.2
 15        //
 16        internal static bool CheckIriUnicodeRange(char unicode, bool isQuery)
 017        {
 018            return IsInInclusiveRange(unicode, '\u00A0', '\uD7FF')
 019                || IsInInclusiveRange(unicode, '\uF900', '\uFDCF')
 020                || IsInInclusiveRange(unicode, '\uFDF0', '\uFFEF')
 021                || (isQuery && IsInInclusiveRange(unicode, '\uE000', '\uF8FF'));
 022        }
 23
 24        //
 25        // Check if highSurr and lowSurr are a surrogate pair then
 26        // it checks if the combined char is in the range
 27        // Takes in isQuery because iri restrictions for query are different
 28        // This method implements the ABNF checks per https://tools.ietf.org/html/rfc3987#section-2.2
 29        //
 30        internal static bool CheckIriUnicodeRange(char highSurr, char lowSurr, out bool isSurrogatePair, bool isQuery)
 031        {
 032            Debug.Assert(char.IsHighSurrogate(highSurr));
 33
 034            if (Rune.TryCreate(highSurr, lowSurr, out Rune rune))
 035            {
 036                isSurrogatePair = true;
 37
 38                // U+xxFFFE..U+xxFFFF is always private use for all planes, so we exclude it.
 39                // U+E0000..U+E0FFF is disallowed per the 'ucschar' definition in the ABNF.
 40                // U+F0000 and above are only allowed for 'iprivate' per the ABNF (isQuery = true).
 41
 042                return ((rune.Value & 0xFFFF) < 0xFFFE)
 043                    && ((uint)(rune.Value - 0xE0000) >= (0xE1000 - 0xE0000))
 044                    && (isQuery || rune.Value < 0xF0000);
 45            }
 46
 047            isSurrogatePair = false;
 048            return false;
 049        }
 50
 51        internal static bool CheckIriUnicodeRange(uint value, bool isQuery)
 052        {
 053            if (value <= 0xFFFF)
 054            {
 055                return IsInInclusiveRange(value, '\u00A0', '\uD7FF')
 056                    || IsInInclusiveRange(value, '\uF900', '\uFDCF')
 057                    || IsInInclusiveRange(value, '\uFDF0', '\uFFEF')
 058                    || (isQuery && IsInInclusiveRange(value, '\uE000', '\uF8FF'));
 59            }
 60            else
 061            {
 062                return ((value & 0xFFFF) < 0xFFFE)
 063                    && !IsInInclusiveRange(value, 0xE0000, 0xE0FFF)
 064                    && (isQuery || value < 0xF0000);
 65            }
 066        }
 67
 68        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 69        public static bool IsInInclusiveRange(uint value, uint min, uint max)
 070            => (value - min) <= (max - min);
 71
 72        // IRI normalization for strings containing characters that are not allowed or
 73        // escaped characters that should be unescaped in the context of the specified Uri component.
 74        public static void EscapeUnescapeIri(ref ValueStringBuilder dest, scoped ReadOnlySpan<char> span, bool isQuery)
 075        {
 076            Span<byte> maxUtf8EncodedSpan = [0, 0, 0, 0];
 77
 078            for (int i = 0; (uint)i < (uint)span.Length; i++)
 079            {
 080                char ch = span[i];
 81
 082                if (ch == '%')
 083                {
 084                    if ((uint)(i + 2) < (uint)span.Length)
 085                    {
 086                        ch = UriHelper.DecodeHexChars(span[i + 1], span[i + 2]);
 87
 88                        // Do not unescape a reserved char
 089                        if (ch == Uri.c_DummyChar || UriHelper.IsNotSafeForUnescape(ch))
 090                        {
 91                            // keep as is
 092                            dest.Append(span[i]);
 093                            dest.Append(span[i + 1]);
 094                            dest.Append(span[i + 2]);
 095                            i += 2;
 096                        }
 097                        else if (ch <= '\x7F')
 098                        {
 99                            // ASCII
 0100                            dest.Append(ch);
 0101                            i += 2;
 0102                        }
 103                        else
 0104                        {
 105                            // possibly utf8 encoded sequence of unicode
 0106                            int charactersRead = PercentEncodingHelper.UnescapePercentEncodedUTF8Sequence(
 0107                                span.Slice(i),
 0108                                ref dest,
 0109                                isQuery,
 0110                                iriParsing: true);
 111
 0112                            Debug.Assert(charactersRead > 0);
 0113                            i += charactersRead - 1; // -1 as i will be incremented in the loop
 0114                        }
 115
 0116                        continue;
 117                    }
 0118                }
 0119                else if (ch > '\x7f')
 0120                {
 121                    // unicode
 122
 123                    bool isInIriUnicodeRange;
 0124                    bool surrogatePair = false;
 125
 0126                    char ch2 = '\0';
 127
 0128                    if (char.IsHighSurrogate(ch) && (uint)(i + 1) < (uint)span.Length)
 0129                    {
 0130                        ch2 = span[i + 1];
 0131                        isInIriUnicodeRange = CheckIriUnicodeRange(ch, ch2, out surrogatePair, isQuery);
 0132                    }
 133                    else
 0134                    {
 0135                        isInIriUnicodeRange = CheckIriUnicodeRange(ch, isQuery);
 0136                    }
 137
 0138                    if (isInIriUnicodeRange)
 0139                    {
 0140                        dest.Append(ch);
 0141                        if (surrogatePair)
 0142                        {
 0143                            dest.Append(ch2);
 0144                        }
 0145                    }
 146                    else
 0147                    {
 148                        Rune rune;
 0149                        if (surrogatePair)
 0150                        {
 0151                            rune = new Rune(ch, ch2);
 0152                        }
 0153                        else if (!Rune.TryCreate(ch, out rune))
 0154                        {
 0155                            rune = Rune.ReplacementChar;
 0156                        }
 157
 0158                        int bytesWritten = rune.EncodeToUtf8(maxUtf8EncodedSpan);
 0159                        ReadOnlySpan<byte> encodedBytes = maxUtf8EncodedSpan.Slice(0, bytesWritten);
 160
 0161                        foreach (byte b in encodedBytes)
 0162                        {
 0163                            UriHelper.PercentEncodeByte(b, ref dest);
 0164                        }
 0165                    }
 166
 0167                    if (surrogatePair)
 0168                    {
 0169                        i++;
 0170                    }
 171
 0172                    continue;
 173                }
 174
 175                // ASCII, just copy the character
 0176                dest.Append(ch);
 0177            }
 0178        }
 179    }
 180}
 181