diff --git a/src/libraries/System.Private.Uri/src/System/IriHelper.cs b/src/libraries/System.Private.Uri/src/System/IriHelper.cs index bb18ff9485c94..5b821405717b0 100644 --- a/src/libraries/System.Private.Uri/src/System/IriHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/IriHelper.cs @@ -109,11 +109,19 @@ internal static unsafe string EscapeUnescapeIri(char* pInput, int start, int end ValueStringBuilder dest = new ValueStringBuilder(size); byte[]? bytes = null; + const int percentEncodingLen = 3; // Escaped UTF-8 will take 3 chars: %AB. + int bufferRemaining = 0; + int next = start; char ch; + bool escape = false; + bool surrogatePair = false; for (; next < end; ++next) { + escape = false; + surrogatePair = false; + if ((ch = pInput[next]) == '%') { if (next + 2 < end) @@ -218,55 +226,31 @@ internal static unsafe string EscapeUnescapeIri(char* pInput, int start, int end { // unicode - bool escape; - bool surrogatePair = false; - - char ch2 = '\0'; + char ch2; if ((char.IsHighSurrogate(ch)) && (next + 1 < end)) { ch2 = pInput[next + 1]; escape = !CheckIriUnicodeRange(ch, ch2, ref surrogatePair, component == UriComponents.Query); - } - else - { - escape = !CheckIriUnicodeRange(ch, component == UriComponents.Query); - } - - if (escape) - { - Span encodedBytes = stackalloc byte[4]; - - Rune rune; - if (surrogatePair) - { - rune = new Rune(ch, ch2); - } - else if (!Rune.TryCreate(ch, out rune)) + if (!escape) { - rune = Rune.ReplacementChar; - } - - int bytesWritten = rune.EncodeToUtf8(encodedBytes); - encodedBytes = encodedBytes.Slice(0, bytesWritten); - - foreach (byte b in encodedBytes) - { - UriHelper.EscapeAsciiChar(b, ref dest); + // copy the two chars + dest.Append(pInput[next++]); + dest.Append(pInput[next]); } } else { - dest.Append(ch); - if (surrogatePair) + if (CheckIriUnicodeRange(ch, component == UriComponents.Query)) { - dest.Append(ch2); + // copy it + dest.Append(pInput[next]); + } + else + { + // escape it + escape = true; } - } - - if (surrogatePair) - { - next++; } } else @@ -274,6 +258,25 @@ internal static unsafe string EscapeUnescapeIri(char* pInput, int start, int end // just copy the character dest.Append(pInput[next]); } + + if (escape) + { + const int MaxNumberOfBytesEncoded = 4; + + byte[] encodedBytes = new byte[MaxNumberOfBytesEncoded]; + fixed (byte* pEncodedBytes = &encodedBytes[0]) + { + int encodedBytesCount = Encoding.UTF8.GetBytes(pInput + next, surrogatePair ? 2 : 1, pEncodedBytes, MaxNumberOfBytesEncoded); + Debug.Assert(encodedBytesCount <= MaxNumberOfBytesEncoded, "UTF8 encoder should not exceed specified byteCount"); + + bufferRemaining -= encodedBytesCount * percentEncodingLen; + + for (int count = 0; count < encodedBytesCount; ++count) + { + UriHelper.EscapeAsciiChar(encodedBytes[count], ref dest); + } + } + } } string result = dest.ToString(); diff --git a/src/libraries/System.Private.Uri/tests/FunctionalTests/EscapeUnescapeIriTests.cs b/src/libraries/System.Private.Uri/tests/FunctionalTests/EscapeUnescapeIriTests.cs deleted file mode 100644 index db095dd3da78d..0000000000000 --- a/src/libraries/System.Private.Uri/tests/FunctionalTests/EscapeUnescapeIriTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Generic; -using Xunit; - -namespace System.PrivateUri.Tests -{ - public class EscapeUnescapeIriTests - { - public static IEnumerable ReplacesStandaloneSurrogatesWithReplacementChar() - { - const string UrlEncodedReplacementChar = "%EF%BF%BD"; - const string HighSurrogate = "\ud83f"; - const string LowSurrogate = "\udffe"; - - yield return new object[] { "a", "a" }; - yield return new object[] { HighSurrogate + LowSurrogate, "%F0%9F%BF%BE" }; - yield return new object[] { HighSurrogate, UrlEncodedReplacementChar }; - yield return new object[] { LowSurrogate, UrlEncodedReplacementChar }; - yield return new object[] { LowSurrogate + HighSurrogate, UrlEncodedReplacementChar + UrlEncodedReplacementChar }; - yield return new object[] { LowSurrogate + LowSurrogate, UrlEncodedReplacementChar + UrlEncodedReplacementChar }; - yield return new object[] { HighSurrogate + HighSurrogate, UrlEncodedReplacementChar + UrlEncodedReplacementChar }; - } - - [Theory] - [MemberData(nameof(ReplacesStandaloneSurrogatesWithReplacementChar))] - public static void ReplacesStandaloneSurrogatesWithReplacementChar(string input, string expected) - { - const string Prefix = "scheme:"; - Uri uri = new Uri(Prefix + input); - string actual = uri.AbsoluteUri.Substring(Prefix.Length); - Assert.Equal(expected, actual); - } - } -} diff --git a/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj b/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj index ca63421c45fca..a568e2d1069f1 100644 --- a/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj +++ b/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj @@ -4,7 +4,6 @@ -