< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 93
Coverable lines: 93
Total lines: 161
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 54
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
UnescapePercentEncodedUTF8Sequence(...)0%54540%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.Uri/src/System/PercentEncodingHelper.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.Buffers;
 5using System.Diagnostics;
 6using System.Runtime.InteropServices;
 7using System.Text;
 8
 9namespace System
 10{
 11    internal static class PercentEncodingHelper
 12    {
 13        public static int UnescapePercentEncodedUTF8Sequence(scoped ReadOnlySpan<char> input, ref ValueStringBuilder des
 014        {
 15            // As an optimization, this method should only be called after the first character is known to be a part of 
 016            Debug.Assert(input.Length >= 3);
 017            Debug.Assert(input[0] == '%');
 018            Debug.Assert(UriHelper.DecodeHexChars(input[1], input[2]) != Uri.c_DummyChar);
 019            Debug.Assert(UriHelper.DecodeHexChars(input[1], input[2]) >= 128);
 20
 021            uint fourByteBuffer = 0;
 022            int bytesLeftInBuffer = 0;
 23
 024            int totalCharsConsumed = 0;
 025            int charsToCopy = 0;
 026            int bytesConsumed = 0;
 27
 028        RefillBuffer:
 029            int i = totalCharsConsumed + (bytesLeftInBuffer * 3);
 30
 031        ReadByteFromInput:
 032            if ((uint)(i + 2) >= (uint)input.Length || input[i] != '%')
 033                goto NoMoreOrInvalidInput;
 34
 035            uint value = (uint)HexConverter.FromChar(input[i + 1]);
 36
 37            // Check if the first character is 0 to avoid the case where "%0_" passes the "(value - 128) > 127" guard be
 38            // an invalid second character decodes to 0xFF on its own. If we exclude 0, all invalid values will map outs
 039            if (value == 0)
 040                goto NoMoreOrInvalidInput;
 41
 042            value = (value << 4) + (uint)HexConverter.FromChar(input[i + 2]);
 43
 044            if ((value - 128) > 127)
 045                goto NoMoreOrInvalidInput; // Either not Hex or the decoded value is ASCII
 46
 47            // Rotate the buffer and overwrite the last byte
 048            if (BitConverter.IsLittleEndian)
 049            {
 050                fourByteBuffer = (fourByteBuffer >> 8) | (value << 24);
 051            }
 52            else
 053            {
 054                fourByteBuffer = (fourByteBuffer << 8) | value;
 055            }
 56
 057            if (++bytesLeftInBuffer != 4)
 058            {
 059                i += 3;
 060                goto ReadByteFromInput;
 61            }
 62
 063        DecodeRune:
 064            Debug.Assert(totalCharsConsumed % 3 == 0);
 065            Debug.Assert(bytesLeftInBuffer == 2 || bytesLeftInBuffer == 3 || bytesLeftInBuffer == 4);
 066            Debug.Assert((fourByteBuffer & (BitConverter.IsLittleEndian ? 0x00000080 : 0x80000000)) != 0);
 067            Debug.Assert((fourByteBuffer & (BitConverter.IsLittleEndian ? 0x00008000 : 0x00800000)) != 0);
 068            Debug.Assert(bytesLeftInBuffer < 3 || (fourByteBuffer & (BitConverter.IsLittleEndian ? 0x00800000 : 0x000080
 069            Debug.Assert(bytesLeftInBuffer < 4 || (fourByteBuffer & (BitConverter.IsLittleEndian ? 0x80000000 : 0x000000
 70
 071            uint temp = fourByteBuffer; // make a copy so that the *copy* (not the original) is marked address-taken
 72
 073            if (Rune.DecodeFromUtf8(MemoryMarshal.AsBytes(new ReadOnlySpan<uint>(ref temp))[..bytesLeftInBuffer], out Ru
 074            {
 075                Debug.Assert(bytesConsumed >= 2, $"Rune.DecodeFromUtf8 consumed {bytesConsumed} bytes, likely indicating
 76
 077                if (!iriParsing || IriHelper.CheckIriUnicodeRange((uint)rune.Value, isQuery))
 078                {
 079                    if (charsToCopy != 0)
 080                    {
 081                        dest.Append(input.Slice(totalCharsConsumed - charsToCopy, charsToCopy));
 082                        charsToCopy = 0;
 083                    }
 84
 085                    dest.Append(rune);
 086                    goto AfterDecodeRune;
 87                }
 088            }
 89            else
 090            {
 091                Debug.Assert(bytesConsumed > 0, $"Rune.DecodeFromUtf8 consumed {bytesConsumed} bytes when decoding {byte
 092            }
 093            charsToCopy += bytesConsumed * 3;
 94
 095        AfterDecodeRune:
 096            bytesLeftInBuffer -= bytesConsumed;
 097            totalCharsConsumed += bytesConsumed * 3;
 098            goto RefillBuffer;
 99
 0100        NoMoreOrInvalidInput:
 0101            Debug.Assert(bytesLeftInBuffer < 4);
 102
 103            // If we have more than 1 byte left, we try to decode it
 0104            if (bytesLeftInBuffer > 1)
 0105            {
 0106                Debug.Assert(bytesLeftInBuffer == 2 || bytesLeftInBuffer == 3);
 107
 108                // We reach this branch if we don't have 4 valid bytes to consume
 109                // We have to allign the read bytes to the start of fourByteBuffer memory
 110                // We do this by shifting the fourByteBuffer, the shift direction is determined by system endianness
 111
 112                // If we read 3 bytes, we shift by 1; if we read 2, we shift by 2
 113                // (32 - (bytesLeftInBuffer << 3)) calculates this offset:
 114                // bytesLeftInBuffer == 3 => (32 - (3 << 3)) => 32 - 24 => 8 bits
 115                // bytesLeftInBuffer == 2 => (32 - (2 << 3)) => 32 - 16 => 16 bits
 116
 117                // For invalid input we tried to decode in DecodeRune, we may return here if we have more than 1 byte le
 118                // If bytesConsumed is 1, shift by 1 byte
 119                // If bytesConsumed is 2:
 120                // a) We had 4 bytes in the buffer and now only have 2 => Shift by 2 bytes
 121                // b) We read 1 more byte, leaving us with 3 bytes in the buffer => Shift by 1 byte
 122                // The case for bytesConsumed == 2 is handled by the else block as the offsets are the same as for valid
 123
 0124                if (bytesConsumed == 1)
 0125                {
 0126                    if (BitConverter.IsLittleEndian)
 0127                    {
 0128                        fourByteBuffer >>= 8;
 0129                    }
 130                    else
 0131                    {
 0132                        fourByteBuffer <<= 8;
 0133                    }
 0134                }
 135                else
 0136                {
 0137                    if (BitConverter.IsLittleEndian)
 0138                    {
 0139                        fourByteBuffer >>= (32 - (bytesLeftInBuffer << 3));
 0140                    }
 141                    else
 0142                    {
 0143                        fourByteBuffer <<= (32 - (bytesLeftInBuffer << 3));
 0144                    }
 0145                }
 0146                goto DecodeRune;
 147            }
 148
 0149            Debug.Assert(bytesLeftInBuffer == 0 || bytesLeftInBuffer == 1);
 150
 0151            if ((bytesLeftInBuffer | charsToCopy) == 0)
 0152                return totalCharsConsumed;
 153
 0154            bytesLeftInBuffer *= 3;
 155
 0156            dest.Append(input.Slice(totalCharsConsumed - charsToCopy, charsToCopy + bytesLeftInBuffer));
 0157            return totalCharsConsumed + bytesLeftInBuffer;
 0158        }
 159    }
 160}
 161