Skip to content

Commit 28a8168

Browse files
committed
StringUtilities
1 parent 75a1911 commit 28a8168

File tree

3 files changed

+50
-109
lines changed

3 files changed

+50
-109
lines changed

src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ internal static partial class HttpUtilities
2828
private const ulong _http11VersionLong = 3543824036068086856; // GetAsciiStringAsLong("HTTP/1.1"); const results in better codegen
2929

3030
private static readonly UTF8EncodingSealed DefaultRequestHeaderEncoding = new UTF8EncodingSealed();
31-
private static readonly SpanAction<char, IntPtr> _getHeaderName = GetHeaderName;
32-
private static readonly SpanAction<char, IntPtr> _getAsciiStringNonNullCharacters = GetAsciiStringNonNullCharacters;
31+
private static readonly SpanAction<char, IntPtr> s_getHeaderName = GetHeaderName;
3332

3433
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3534
private static void SetKnownMethod(ulong mask, ulong knownMethodUlong, HttpMethod knownMethod, int length)
@@ -86,15 +85,15 @@ public static unsafe string GetHeaderName(this ReadOnlySpan<byte> span)
8685

8786
fixed (byte* source = &MemoryMarshal.GetReference(span))
8887
{
89-
return string.Create(span.Length, new IntPtr(source), _getHeaderName);
88+
return string.Create(span.Length, new IntPtr(source), s_getHeaderName);
9089
}
9190
}
9291

9392
private static unsafe void GetHeaderName(Span<char> buffer, IntPtr state)
9493
{
9594
fixed (char* output = &MemoryMarshal.GetReference(buffer))
9695
{
97-
// This version if AsciiUtilities returns null if there are any null (0 byte) characters
96+
// This version of AsciiUtilities returns null if there are any null (0 byte) characters
9897
// in the string
9998
if (!StringUtilities.TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length))
10099
{
@@ -104,38 +103,11 @@ private static unsafe void GetHeaderName(Span<char> buffer, IntPtr state)
104103
}
105104

106105
public static string GetAsciiStringNonNullCharacters(this Span<byte> span)
107-
=> GetAsciiStringNonNullCharacters((ReadOnlySpan<byte>)span);
108-
109-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
110-
public static unsafe string GetAsciiStringNonNullCharacters(this ReadOnlySpan<byte> span)
111-
{
112-
if (span.IsEmpty)
113-
{
114-
return string.Empty;
115-
}
116-
117-
fixed (byte* source = &MemoryMarshal.GetReference(span))
118-
{
119-
return string.Create(span.Length, new IntPtr(source), _getAsciiStringNonNullCharacters);
120-
}
121-
}
106+
=> StringUtilities.GetAsciiStringNonNullCharacters(span);
122107

123108
public static string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlySpan<byte> span)
124109
=> StringUtilities.GetAsciiOrUTF8StringNonNullCharacters(span, DefaultRequestHeaderEncoding);
125110

126-
private static unsafe void GetAsciiStringNonNullCharacters(Span<char> buffer, IntPtr state)
127-
{
128-
fixed (char* output = &MemoryMarshal.GetReference(buffer))
129-
{
130-
// StringUtilities.TryGetAsciiString returns null if there are any null (0 byte) characters
131-
// in the string
132-
if (!StringUtilities.TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length))
133-
{
134-
throw new InvalidOperationException();
135-
}
136-
}
137-
}
138-
139111
public static string GetRequestHeaderString(this ReadOnlySpan<byte> span, string name, Func<string, Encoding?> encodingSelector)
140112
{
141113
if (ReferenceEquals(KestrelServerOptions.DefaultRequestHeaderEncodingSelector, encodingSelector))

src/Shared/Http2cat/Http2Utilities.cs

Lines changed: 1 addition & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -143,64 +143,7 @@ public Http2Utilities(ConnectionContext clientConnectionContext, ILogger logger,
143143

144144
void IHttpHeadersHandler.OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
145145
{
146-
_decodedHeaders[GetAsciiStringNonNullCharacters(name)] = GetAsciiOrUTF8StringNonNullCharacters(value);
147-
}
148-
149-
public unsafe string GetAsciiStringNonNullCharacters(ReadOnlySpan<byte> span)
150-
{
151-
if (span.IsEmpty)
152-
{
153-
return string.Empty;
154-
}
155-
156-
var asciiString = new string('\0', span.Length);
157-
158-
fixed (char* output = asciiString)
159-
fixed (byte* buffer = span)
160-
{
161-
// This version if AsciiUtilities returns null if there are any null (0 byte) characters
162-
// in the string
163-
if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length))
164-
{
165-
throw new InvalidOperationException();
166-
}
167-
}
168-
return asciiString;
169-
}
170-
171-
public unsafe string GetAsciiOrUTF8StringNonNullCharacters(ReadOnlySpan<byte> span)
172-
{
173-
if (span.IsEmpty)
174-
{
175-
return string.Empty;
176-
}
177-
178-
var resultString = new string('\0', span.Length);
179-
180-
fixed (char* output = resultString)
181-
fixed (byte* buffer = span)
182-
{
183-
// This version if AsciiUtilities returns null if there are any null (0 byte) characters
184-
// in the string
185-
if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length))
186-
{
187-
// null characters are considered invalid
188-
if (span.IndexOf((byte)0) != -1)
189-
{
190-
throw new InvalidOperationException();
191-
}
192-
193-
try
194-
{
195-
resultString = HeaderValueEncoding.GetString(buffer, span.Length);
196-
}
197-
catch (DecoderFallbackException)
198-
{
199-
throw new InvalidOperationException();
200-
}
201-
}
202-
}
203-
return resultString;
146+
_decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiOrUTF8StringNonNullCharacters(HeaderValueEncoding);
204147
}
205148

206149
void IHttpHeadersHandler.OnHeadersComplete(bool endStream) { }

src/Shared/ServerInfrastructure/StringUtilities.cs

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
1717
{
1818
internal static class StringUtilities
1919
{
20-
private static readonly SpanAction<char, IntPtr> s_getAsciiOrUtf8StringNonNullCharacters = GetAsciiStringNonNullCharacters;
21-
22-
private static string GetAsciiOrUTF8StringNonNullCharacters(this Span<byte> span, Encoding defaultEncoding)
23-
=> GetAsciiOrUTF8StringNonNullCharacters((ReadOnlySpan<byte>)span, defaultEncoding);
20+
private static readonly SpanAction<char, IntPtr> s_getAsciiOrUTF8StringNonNullCharacters = GetAsciiStringNonNullCharactersWithMarker;
2421

2522
public static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlySpan<byte> span, Encoding defaultEncoding)
2623
{
@@ -31,7 +28,7 @@ public static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlyS
3128

3229
fixed (byte* source = &MemoryMarshal.GetReference(span))
3330
{
34-
var resultString = string.Create(span.Length, new IntPtr(source), s_getAsciiOrUtf8StringNonNullCharacters);
31+
var resultString = string.Create(span.Length, (IntPtr)source, s_getAsciiOrUTF8StringNonNullCharacters);
3532

3633
// If resultString is marked, perform UTF-8 encoding
3734
if (resultString[0] == '\0')
@@ -56,11 +53,11 @@ public static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this ReadOnlyS
5653
}
5754
}
5855

59-
private static unsafe void GetAsciiStringNonNullCharacters(Span<char> buffer, IntPtr state)
56+
private static unsafe void GetAsciiStringNonNullCharactersWithMarker(Span<char> buffer, IntPtr state)
6057
{
6158
fixed (char* output = &MemoryMarshal.GetReference(buffer))
6259
{
63-
// This version if AsciiUtilities returns false if there are any null ('\0') or non-Ascii
60+
// This version of AsciiUtilities returns false if there are any null ('\0') or non-Ascii
6461
// character (> 127) in the string.
6562
if (!TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length))
6663
{
@@ -70,27 +67,59 @@ private static unsafe void GetAsciiStringNonNullCharacters(Span<char> buffer, In
7067
}
7168
}
7269

70+
private static readonly SpanAction<char, IntPtr> s_getAsciiStringNonNullCharacters = GetAsciiStringNonNullCharacters;
71+
72+
public static unsafe string GetAsciiStringNonNullCharacters(this ReadOnlySpan<byte> span)
73+
{
74+
if (span.IsEmpty)
75+
{
76+
return string.Empty;
77+
}
78+
79+
fixed (byte* source = &MemoryMarshal.GetReference(span))
80+
{
81+
return string.Create(span.Length, (IntPtr)source, s_getAsciiStringNonNullCharacters);
82+
}
83+
}
84+
85+
private static unsafe void GetAsciiStringNonNullCharacters(Span<char> buffer, IntPtr state)
86+
{
87+
fixed (char* output = &MemoryMarshal.GetReference(buffer))
88+
{
89+
// This version of AsciiUtilities returns false if there are any null ('\0') or non-Ascii
90+
// character (> 127) in the string.
91+
if (!TryGetAsciiString((byte*)state.ToPointer(), output, buffer.Length))
92+
{
93+
throw new InvalidOperationException();
94+
}
95+
}
96+
}
97+
98+
private static readonly SpanAction<char, IntPtr> s_getLatin1StringNonNullCharacters = GetLatin1StringNonNullCharacters;
99+
73100
public static unsafe string GetLatin1StringNonNullCharacters(this ReadOnlySpan<byte> span)
74101
{
75102
if (span.IsEmpty)
76103
{
77104
return string.Empty;
78105
}
79106

80-
var resultString = new string('\0', span.Length);
107+
fixed (byte* source = &MemoryMarshal.GetReference(span))
108+
{
109+
return string.Create(span.Length, (IntPtr)source, s_getLatin1StringNonNullCharacters);
110+
}
111+
}
81112

82-
fixed (char* output = resultString)
83-
fixed (byte* buffer = span)
113+
private static unsafe void GetLatin1StringNonNullCharacters(Span<char> buffer, IntPtr state)
114+
{
115+
fixed (char* output = &MemoryMarshal.GetReference(buffer))
84116
{
85-
// This returns false if there are any null (0 byte) characters in the string.
86-
if (!TryGetLatin1String(buffer, output, span.Length))
117+
if (!TryGetLatin1String((byte*)state.ToPointer(), output, buffer.Length))
87118
{
88119
// null characters are considered invalid
89120
throw new InvalidOperationException();
90121
}
91122
}
92-
93-
return resultString;
94123
}
95124

96125
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
@@ -299,7 +328,7 @@ public static unsafe bool TryGetLatin1String(byte* input, char* output, int coun
299328
// If Vector not-accelerated or remaining less than vector size
300329
if (!Vector.IsHardwareAccelerated || input > end - Vector<sbyte>.Count)
301330
{
302-
if (IntPtr.Size == 8) // Use Intrinsic switch for branch elimination
331+
if (Environment.Is64BitProcess) // Use Intrinsic switch for branch elimination
303332
{
304333
// 64-bit: Loop longs by default
305334
while (input <= end - sizeof(long))
@@ -403,10 +432,6 @@ public static bool BytesOrdinalEqualsStringAndAscii(string previousValue, ReadOn
403432
goto NotEqual;
404433
}
405434

406-
// Use IntPtr values rather than int, to avoid unnecessary 32 -> 64 movs on 64-bit.
407-
// Unfortunately this means we also need to cast to byte* for comparisons as IntPtr doesn't
408-
// support operator comparisons (e.g. <=, >, etc).
409-
//
410435
// Note: Pointer comparison is unsigned, so we use the compare pattern (offset + length <= count)
411436
// rather than (offset <= count - length) which we'd do with signed comparison to avoid overflow.
412437
// This isn't problematic as we know the maximum length is max string length (from test above)
@@ -666,6 +691,7 @@ private static bool IsValidHeaderString(string value)
666691
return false;
667692
}
668693
}
694+
669695
private static readonly SpanAction<char, (string? str, char separator, uint number)> s_populateSpanWithHexSuffix = PopulateSpanWithHexSuffix;
670696

671697
/// <summary>

0 commit comments

Comments
 (0)