Skip to content

Commit b287b92

Browse files
authored
Use SearchValues in PathString (#49117)
1 parent fdfb0d2 commit b287b92

File tree

2 files changed

+36
-99
lines changed

2 files changed

+36
-99
lines changed

src/Http/Http.Abstractions/src/Internal/PathStringHelper.cs

Lines changed: 0 additions & 66 deletions
This file was deleted.

src/Http/Http.Abstractions/src/PathString.cs

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Buffers;
45
using System.ComponentModel;
56
using System.Diagnostics;
67
using System.Diagnostics.CodeAnalysis;
@@ -18,6 +19,9 @@ namespace Microsoft.AspNetCore.Http;
1819
[DebuggerDisplay("{Value}")]
1920
public readonly struct PathString : IEquatable<PathString>
2021
{
22+
private static readonly SearchValues<char> s_validPathChars =
23+
SearchValues.Create("!$&'()*+,-./0123456789:;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~");
24+
2125
internal const int StackAllocThreshold = 128;
2226

2327
/// <summary>
@@ -68,27 +72,18 @@ public override string ToString()
6872
/// <returns>The escaped path value</returns>
6973
public string ToUriComponent()
7074
{
71-
if (!HasValue)
72-
{
73-
return string.Empty;
74-
}
75-
7675
var value = Value;
77-
var i = 0;
78-
for (; i < value.Length; i++)
79-
{
80-
if (!PathStringHelper.IsValidPathChar(value[i]) || PathStringHelper.IsPercentEncodedChar(value, i))
81-
{
82-
break;
83-
}
84-
}
8576

86-
if (i < value.Length)
77+
if (string.IsNullOrEmpty(value))
8778
{
88-
return ToEscapedUriComponent(value, i);
79+
return string.Empty;
8980
}
9081

91-
return value;
82+
var indexOfInvalidChar = value.AsSpan().IndexOfAnyExcept(s_validPathChars);
83+
84+
return indexOfInvalidChar < 0
85+
? value
86+
: ToEscapedUriComponent(value, indexOfInvalidChar);
9287
}
9388

9489
private static string ToEscapedUriComponent(string value, int i)
@@ -99,10 +94,10 @@ private static string ToEscapedUriComponent(string value, int i)
9994
var count = i;
10095
var requiresEscaping = false;
10196

102-
while (i < value.Length)
97+
while ((uint)i < (uint)value.Length)
10398
{
104-
var isPercentEncodedChar = PathStringHelper.IsPercentEncodedChar(value, i);
105-
if (PathStringHelper.IsValidPathChar(value[i]) || isPercentEncodedChar)
99+
var isPercentEncodedChar = false;
100+
if (s_validPathChars.Contains(value[i]) || (isPercentEncodedChar = Uri.IsHexEncoding(value, i)))
106101
{
107102
if (requiresEscaping)
108103
{
@@ -122,8 +117,18 @@ private static string ToEscapedUriComponent(string value, int i)
122117
}
123118
else
124119
{
125-
count++;
126-
i++;
120+
// We just saw a character we don't want to escape. It's likely there are more, do a vectorized search.
121+
var charsToSkip = value.AsSpan(i).IndexOfAnyExcept(s_validPathChars);
122+
123+
if (charsToSkip < 0)
124+
{
125+
// Only valid characters remain
126+
count += value.Length - i;
127+
break;
128+
}
129+
130+
count += charsToSkip;
131+
i += charsToSkip;
127132
}
128133
}
129134
else
@@ -150,21 +155,19 @@ private static string ToEscapedUriComponent(string value, int i)
150155
}
151156
else
152157
{
153-
if (count > 0)
154-
{
155-
buffer ??= new StringBuilder(value.Length * 3);
158+
Debug.Assert(count > 0);
159+
Debug.Assert(buffer is not null);
156160

157-
if (requiresEscaping)
158-
{
159-
buffer.Append(Uri.EscapeDataString(value.Substring(start, count)));
160-
}
161-
else
162-
{
163-
buffer.Append(value, start, count);
164-
}
161+
if (requiresEscaping)
162+
{
163+
buffer.Append(Uri.EscapeDataString(value.Substring(start, count)));
164+
}
165+
else
166+
{
167+
buffer.Append(value, start, count);
165168
}
166169

167-
return buffer?.ToString() ?? string.Empty;
170+
return buffer.ToString();
168171
}
169172
}
170173

0 commit comments

Comments
 (0)