Skip to content

Commit 334a4b4

Browse files
authored
Remove unsafe code from WinHttpHandler TryGetHeaderName helper (#114484)
1 parent 0f60b6d commit 334a4b4

File tree

5 files changed

+40
-177
lines changed

5 files changed

+40
-177
lines changed

src/libraries/Common/src/System/Net/HttpKnownHeaderNames.TryGetHeaderName.cs

Lines changed: 28 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -11,45 +11,6 @@ internal static partial class HttpKnownHeaderNames
1111
private const string Gzip = "gzip";
1212
private const string Deflate = "deflate";
1313

14-
/// <summary>
15-
/// Gets a known header name string from a matching char[] array segment, using a case-sensitive
16-
/// ordinal comparison. Used to avoid allocating new strings for known header names.
17-
/// </summary>
18-
public static bool TryGetHeaderName(char[] array, int startIndex, int length, [NotNullWhen(true)] out string? name)
19-
{
20-
CharArrayHelpers.DebugAssertArrayInputs(array, startIndex, length);
21-
22-
return TryGetHeaderName(
23-
array, startIndex, length,
24-
static (arr, index) => arr[index],
25-
static (known, arr, start, len) => known.AsSpan().SequenceEqual(arr.AsSpan(start, len)),
26-
out name);
27-
}
28-
29-
/// <summary>
30-
/// Gets a known header name string from a matching IntPtr buffer, using a case-sensitive
31-
/// ordinal comparison. Used to avoid allocating new strings for known header names.
32-
/// </summary>
33-
public static unsafe bool TryGetHeaderName(IntPtr buffer, int length, out string? name)
34-
{
35-
Debug.Assert(length >= 0);
36-
37-
if (buffer == IntPtr.Zero)
38-
{
39-
name = null;
40-
return false;
41-
}
42-
43-
// We always pass 0 for the startIndex, as buffer should already point to the start.
44-
const int startIndex = 0;
45-
46-
return TryGetHeaderName(
47-
buffer, startIndex, length,
48-
static (buf, index) => (char)((byte*)buf)[index],
49-
static (known, buf, start, len) => EqualsOrdinal(known, buf, len),
50-
out name);
51-
}
52-
5314
public static string GetHeaderValue(string name, ReadOnlySpan<char> value)
5415
{
5516
Debug.Assert(name != null);
@@ -80,18 +41,12 @@ public static string GetHeaderValue(string name, ReadOnlySpan<char> value)
8041
return value.ToString();
8142
}
8243

83-
private static bool TryGetHeaderName<T>(
84-
T key, int startIndex, int length,
85-
Func<T, int, char> charAt,
86-
Func<string, T, int, int, bool> equals,
87-
[NotNullWhen(true)] out string? name)
44+
/// <summary>
45+
/// Gets a known header name string from a matching span segment, using a case-sensitive
46+
/// ordinal comparison. Used to avoid allocating new strings for known header names.
47+
/// </summary>
48+
public static bool TryGetHeaderName(ReadOnlySpan<char> nameSpan, [NotNullWhen(true)] out string? name)
8849
{
89-
Debug.Assert(key != null);
90-
Debug.Assert(startIndex >= 0);
91-
Debug.Assert(length >= 0);
92-
Debug.Assert(charAt != null);
93-
Debug.Assert(equals != null);
94-
9550
// When adding a new constant, add it to HttpKnownHeaderNames.cs as well.
9651

9752
// The lookup works as follows: first switch on the length of the passed-in key.
@@ -112,13 +67,13 @@ private static bool TryGetHeaderName<T>(
11267

11368
string potentialHeader;
11469

115-
switch (length)
70+
switch (nameSpan.Length)
11671
{
11772
case 2:
11873
potentialHeader = TE; goto TryMatch; // TE
11974

12075
case 3:
121-
switch (charAt(key, startIndex))
76+
switch (nameSpan[0])
12277
{
12378
case 'A': potentialHeader = Age; goto TryMatch; // [A]ge
12479
case 'P': potentialHeader = P3P; goto TryMatch; // [P]3P
@@ -128,7 +83,7 @@ private static bool TryGetHeaderName<T>(
12883
break;
12984

13085
case 4:
131-
switch (charAt(key, startIndex))
86+
switch (nameSpan[0])
13287
{
13388
case 'D': potentialHeader = Date; goto TryMatch; // [D]ate
13489
case 'E': potentialHeader = ETag; goto TryMatch; // [E]Tag
@@ -140,15 +95,15 @@ private static bool TryGetHeaderName<T>(
14095
break;
14196

14297
case 5:
143-
switch (charAt(key, startIndex))
98+
switch (nameSpan[0])
14499
{
145100
case 'A': potentialHeader = Allow; goto TryMatch; // [A]llow
146101
case 'R': potentialHeader = Range; goto TryMatch; // [R]ange
147102
}
148103
break;
149104

150105
case 6:
151-
switch (charAt(key, startIndex))
106+
switch (nameSpan[0])
152107
{
153108
case 'A': potentialHeader = Accept; goto TryMatch; // [A]ccept
154109
case 'C': potentialHeader = Cookie; goto TryMatch; // [C]ookie
@@ -160,7 +115,7 @@ private static bool TryGetHeaderName<T>(
160115
break;
161116

162117
case 7:
163-
switch (charAt(key, startIndex))
118+
switch (nameSpan[0])
164119
{
165120
case 'A': potentialHeader = AltSvc; goto TryMatch; // [A]lt-Svc
166121
case 'C': potentialHeader = Cookie2; goto TryMatch; // [C]ookie2
@@ -173,7 +128,7 @@ private static bool TryGetHeaderName<T>(
173128
break;
174129

175130
case 8:
176-
switch (charAt(key, startIndex + 3))
131+
switch (nameSpan[3])
177132
{
178133
case 'M': potentialHeader = IfMatch; goto TryMatch; // If-[M]atch
179134
case 'R': potentialHeader = IfRange; goto TryMatch; // If-[R]ange
@@ -182,7 +137,7 @@ private static bool TryGetHeaderName<T>(
182137
break;
183138

184139
case 10:
185-
switch (charAt(key, startIndex))
140+
switch (nameSpan[0])
186141
{
187142
case 'C': potentialHeader = Connection; goto TryMatch; // [C]onnection
188143
case 'K': potentialHeader = KeepAlive; goto TryMatch; // [K]eep-Alive
@@ -192,7 +147,7 @@ private static bool TryGetHeaderName<T>(
192147
break;
193148

194149
case 11:
195-
switch (charAt(key, startIndex))
150+
switch (nameSpan[0])
196151
{
197152
case 'C': potentialHeader = ContentMD5; goto TryMatch; // [C]ontent-MD5
198153
case 'R': potentialHeader = RetryAfter; goto TryMatch; // [R]etry-After
@@ -201,7 +156,7 @@ private static bool TryGetHeaderName<T>(
201156
break;
202157

203158
case 12:
204-
switch (charAt(key, startIndex + 2))
159+
switch (nameSpan[2])
205160
{
206161
case 'c': potentialHeader = AcceptPatch; goto TryMatch; // Ac[c]ept-Patch
207162
case 'n': potentialHeader = ContentType; goto TryMatch; // Co[n]tent-Type
@@ -213,7 +168,7 @@ private static bool TryGetHeaderName<T>(
213168
break;
214169

215170
case 13:
216-
switch (charAt(key, startIndex + 6))
171+
switch (nameSpan[6])
217172
{
218173
case '-': potentialHeader = AcceptRanges; goto TryMatch; // Accept[-]Ranges
219174
case 'i': potentialHeader = Authorization; goto TryMatch; // Author[i]zation
@@ -225,15 +180,15 @@ private static bool TryGetHeaderName<T>(
225180
break;
226181

227182
case 14:
228-
switch (charAt(key, startIndex))
183+
switch (nameSpan[0])
229184
{
230185
case 'A': potentialHeader = AcceptCharset; goto TryMatch; // [A]ccept-Charset
231186
case 'C': potentialHeader = ContentLength; goto TryMatch; // [C]ontent-Length
232187
}
233188
break;
234189

235190
case 15:
236-
switch (charAt(key, startIndex + 7))
191+
switch (nameSpan[7])
237192
{
238193
case '-': potentialHeader = XFrameOptions; goto TryMatch; // X-Frame[-]Options
239194
case 'm': potentialHeader = XUACompatible; goto TryMatch; // X-UA-Co[m]patible
@@ -244,7 +199,7 @@ private static bool TryGetHeaderName<T>(
244199
break;
245200

246201
case 16:
247-
switch (charAt(key, startIndex + 11))
202+
switch (nameSpan[11])
248203
{
249204
case 'o': potentialHeader = ContentEncoding; goto TryMatch; // Content-Enc[o]ding
250205
case 'g': potentialHeader = ContentLanguage; goto TryMatch; // Content-Lan[g]uage
@@ -256,7 +211,7 @@ private static bool TryGetHeaderName<T>(
256211
break;
257212

258213
case 17:
259-
switch (charAt(key, startIndex))
214+
switch (nameSpan[0])
260215
{
261216
case 'I': potentialHeader = IfModifiedSince; goto TryMatch; // [I]f-Modified-Since
262217
case 'S': potentialHeader = SecWebSocketKey; goto TryMatch; // [S]ec-WebSocket-Key
@@ -265,15 +220,15 @@ private static bool TryGetHeaderName<T>(
265220
break;
266221

267222
case 18:
268-
switch (charAt(key, startIndex))
223+
switch (nameSpan[0])
269224
{
270225
case 'P': potentialHeader = ProxyAuthenticate; goto TryMatch; // [P]roxy-Authenticate
271226
case 'X': potentialHeader = XContentDuration; goto TryMatch; // [X]-Content-Duration
272227
}
273228
break;
274229

275230
case 19:
276-
switch (charAt(key, startIndex))
231+
switch (nameSpan[0])
277232
{
278233
case 'C': potentialHeader = ContentDisposition; goto TryMatch; // [C]ontent-Disposition
279234
case 'I': potentialHeader = IfUnmodifiedSince; goto TryMatch; // [I]f-Unmodified-Since
@@ -288,7 +243,7 @@ private static bool TryGetHeaderName<T>(
288243
potentialHeader = SecWebSocketVersion; goto TryMatch; // Sec-WebSocket-Version
289244

290245
case 22:
291-
switch (charAt(key, startIndex))
246+
switch (nameSpan[0])
292247
{
293248
case 'A': potentialHeader = AccessControlMaxAge; goto TryMatch; // [A]ccess-Control-Max-Age
294249
case 'S': potentialHeader = SecWebSocketProtocol; goto TryMatch; // [S]ec-WebSocket-Protocol
@@ -303,7 +258,7 @@ private static bool TryGetHeaderName<T>(
303258
potentialHeader = SecWebSocketExtensions; goto TryMatch; // Sec-WebSocket-Extensions
304259

305260
case 25:
306-
switch (charAt(key, startIndex))
261+
switch (nameSpan[0])
307262
{
308263
case 'S': potentialHeader = StrictTransportSecurity; goto TryMatch; // [S]trict-Transport-Security
309264
case 'U': potentialHeader = UpgradeInsecureRequests; goto TryMatch; // [U]pgrade-Insecure-Requests
@@ -314,7 +269,7 @@ private static bool TryGetHeaderName<T>(
314269
potentialHeader = AccessControlAllowOrigin; goto TryMatch; // Access-Control-Allow-Origin
315270

316271
case 28:
317-
switch (charAt(key, startIndex + 21))
272+
switch (nameSpan[21])
318273
{
319274
case 'H': potentialHeader = AccessControlAllowHeaders; goto TryMatch; // Access-Control-Allow-[H]eaders
320275
case 'M': potentialHeader = AccessControlAllowMethods; goto TryMatch; // Access-Control-Allow-[M]ethods
@@ -331,57 +286,17 @@ private static bool TryGetHeaderName<T>(
331286
name = null;
332287
return false;
333288

334-
TryMatch:
289+
TryMatch:
335290
Debug.Assert(potentialHeader != null);
336-
return TryMatch(potentialHeader, key, startIndex, length, equals, out name);
337-
}
338-
339-
/// <summary>
340-
/// Returns true if <paramref name="known"/> matches the <paramref name="key"/> char[] array segment,
341-
/// using an ordinal comparison.
342-
/// </summary>
343-
private static bool TryMatch<T>(string known, T key, int startIndex, int length, Func<string, T, int, int, bool> equals, [NotNullWhen(true)] out string? name)
344-
{
345-
Debug.Assert(known != null);
346-
Debug.Assert(known.Length > 0);
347-
Debug.Assert(startIndex >= 0);
348-
Debug.Assert(length > 0);
349-
Debug.Assert(equals != null);
350-
351-
// The lengths should be equal because this method is only called
352-
// from within a "switch (length) { ... }".
353-
Debug.Assert(known.Length == length);
354291

355-
if (equals(known, key, startIndex, length))
292+
if (nameSpan.SequenceEqual(potentialHeader.AsSpan()))
356293
{
357-
name = known;
294+
name = potentialHeader;
358295
return true;
359296
}
360297

361298
name = null;
362299
return false;
363300
}
364-
365-
private static unsafe bool EqualsOrdinal(string left, IntPtr right, int rightLength)
366-
{
367-
Debug.Assert(left != null);
368-
Debug.Assert(right != IntPtr.Zero);
369-
Debug.Assert(rightLength > 0);
370-
371-
// At this point the lengths have already been determined to be equal.
372-
Debug.Assert(left.Length == rightLength);
373-
374-
byte* pRight = (byte*)right;
375-
376-
for (int i = 0; i < left.Length; i++)
377-
{
378-
if (left[i] != pRight[i])
379-
{
380-
return false;
381-
}
382-
}
383-
384-
return true;
385-
}
386301
}
387302
}

0 commit comments

Comments
 (0)