diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index e37b8aa120aaf5..e891746feacab8 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -4015,11 +4015,9 @@ private unsafe int CheckAuthorityHelper(char* pString, int idx, int length, if (hasUnicode) { // Normalize any other host or do idn - string user = new string(pString, startInput, end - startInput); - try { - newHost += user.Normalize(NormalizationForm.FormC); + newHost = UriHelper.NormalizeAndConcat(newHost, new ReadOnlySpan(pString + startInput, end - startInput)); } catch (ArgumentException) { @@ -4061,10 +4059,9 @@ private unsafe int CheckAuthorityHelper(char* pString, int idx, int length, if (hasUnicode) { // Normalize any other host - string user = new string(pString, startOtherHost, end - startOtherHost); try { - newHost += user.Normalize(NormalizationForm.FormC); + newHost = UriHelper.NormalizeAndConcat(newHost, new ReadOnlySpan(pString + startOtherHost, end - startOtherHost)); } catch (ArgumentException) { @@ -4095,10 +4092,16 @@ private static unsafe void CheckAuthorityHelperHandleDnsIri(char* pString, int s if (hasUnicode) { - string temp = UriHelper.StripBidiControlCharacters(new ReadOnlySpan(pString + start, end - start)); + ReadOnlySpan host = new ReadOnlySpan(pString + start, end - start); + + if (UriHelper.StripBidiControlCharacters(host, out string? stripped)) + { + host = stripped; + } + try { - newHost += temp.Normalize(NormalizationForm.FormC); + newHost = UriHelper.NormalizeAndConcat(newHost, host); } catch (ArgumentException) { diff --git a/src/libraries/System.Private.Uri/src/System/UriHelper.cs b/src/libraries/System.Private.Uri/src/System/UriHelper.cs index 56ccaa31ef0053..ad83c35b15bb3f 100644 --- a/src/libraries/System.Private.Uri/src/System/UriHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/UriHelper.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Text; @@ -19,6 +20,21 @@ public static string SpanToLowerInvariantString(ReadOnlySpan span) }); } + public static string NormalizeAndConcat(string? start, ReadOnlySpan toNormalize) + { + var vsb = new ValueStringBuilder(stackalloc char[Uri.StackallocThreshold]); + + int charsWritten; + while (!toNormalize.TryNormalize(vsb.RawChars, out charsWritten, NormalizationForm.FormC)) + { + vsb.EnsureCapacity(vsb.Capacity + 1); + } + + string result = string.Concat(start, vsb.RawChars.Slice(0, charsWritten)); + vsb.Dispose(); + return result; + } + // http://host/Path/Path/File?Query is the base of // - http://host/Path/Path/File/ ... (those "File" words may be different in semantic but anyway) // - http://host/Path/Path/#Fragment @@ -591,10 +607,20 @@ internal static bool IsBidiControlCharacter(char ch) => char.IsBetween(ch, '\u200E', '\u202E') && !char.IsBetween(ch, '\u2010', '\u2029'); // Strip Bidirectional control characters from this string - internal static string StripBidiControlCharacters(ReadOnlySpan strToClean, string? backingString = null) + public static string StripBidiControlCharacters(ReadOnlySpan strToClean, string? backingString = null) { Debug.Assert(backingString is null || strToClean.Length == backingString.Length); + if (StripBidiControlCharacters(strToClean, out string? stripped)) + { + return stripped; + } + + return backingString ?? strToClean.ToString(); + } + + public static bool StripBidiControlCharacters(ReadOnlySpan strToClean, [NotNullWhen(true)] out string? stripped) + { int charsToRemove = 0; int indexOfPossibleCharToRemove = strToClean.IndexOfAnyInRange('\u200E', '\u202E'); @@ -613,10 +639,11 @@ internal static string StripBidiControlCharacters(ReadOnlySpan strToClean, if (charsToRemove == 0) { // Hot path - return backingString ?? new string(strToClean); + stripped = null; + return false; } - return string.Create(strToClean.Length - charsToRemove, strToClean, static (buffer, strToClean) => + stripped = string.Create(strToClean.Length - charsToRemove, strToClean, static (buffer, strToClean) => { int destIndex = 0; foreach (char c in strToClean) @@ -628,6 +655,7 @@ internal static string StripBidiControlCharacters(ReadOnlySpan strToClean, } Debug.Assert(buffer.Length == destIndex); }); + return true; } } }