Skip to content

Commit 2db2821

Browse files
authored
Remove unsafe code from Http KnownHeaders (#114757)
1 parent ac8e79a commit 2db2821

File tree

1 file changed

+36
-72
lines changed

1 file changed

+36
-72
lines changed

src/libraries/System.Net.Http/src/System/Net/Http/Headers/KnownHeaders.cs

+36-72
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
using System.Net.Http.HPack;
55
using System.Net.Http.QPack;
6-
using System.Runtime.InteropServices;
6+
using System.Numerics;
7+
using System.Runtime.CompilerServices;
78
using System.Text;
89

910
namespace System.Net.Http.Headers
@@ -114,63 +115,29 @@ internal static class KnownHeaders
114115
private static AltSvcHeaderParser? GetAltSvcHeaderParser() => AltSvcHeaderParser.Parser;
115116
#endif
116117

117-
// Helper interface for making GetCandidate generic over strings, utf8, etc
118-
private interface IHeaderNameAccessor
119-
{
120-
int Length { get; }
121-
char this[int index] { get; }
122-
}
123-
124-
private readonly struct StringAccessor : IHeaderNameAccessor
125-
{
126-
private readonly string _string;
127-
128-
public StringAccessor(string s)
129-
{
130-
_string = s;
131-
}
132-
133-
public int Length => _string.Length;
134-
public char this[int index] => _string[index];
135-
}
136-
137-
// Can't use Span here as it's unsupported.
138-
private readonly unsafe struct BytePtrAccessor : IHeaderNameAccessor
139-
{
140-
private readonly byte* _p;
141-
private readonly int _length;
142-
143-
public BytePtrAccessor(byte* p, int length)
144-
{
145-
_p = p;
146-
_length = length;
147-
}
148-
149-
public int Length => _length;
150-
public char this[int index] => (char)_p[index];
151-
}
152-
153118
/// <summary>
154119
/// Find possible known header match via lookup on length and a distinguishing char for that length.
155120
/// </summary>
156121
/// <remarks>
157122
/// Matching is case-insensitive. Because of this, we do not preserve the case of the original header,
158123
/// whether from the wire or from the user explicitly setting a known header using a header name string.
159124
/// </remarks>
160-
private static KnownHeader? GetCandidate<T>(T key)
161-
where T : struct, IHeaderNameAccessor // Enforce struct for performance
125+
private static KnownHeader? GetCandidate<T>(ReadOnlySpan<T> key)
126+
where T : struct, INumberBase<T>
162127
{
163128
// Lookup is performed by first switching on the header name's length, and then switching
164129
// on the most unique position in that length's string.
165130

166-
int length = key.Length;
167-
switch (length)
131+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
132+
static int GetLower(T value) => int.CreateTruncating(value) | 0x20;
133+
134+
switch (key.Length)
168135
{
169136
case 2:
170137
return TE; // TE
171138

172139
case 3:
173-
switch (key[0] | 0x20)
140+
switch (GetLower(key[0]))
174141
{
175142
case 'a': return Age; // [A]ge
176143
case 'p': return P3P; // [P]3P
@@ -180,7 +147,7 @@ public BytePtrAccessor(byte* p, int length)
180147
break;
181148

182149
case 4:
183-
switch (key[0] | 0x20)
150+
switch (GetLower(key[0]))
184151
{
185152
case 'd': return Date; // [D]ate
186153
case 'e': return ETag; // [E]Tag
@@ -192,15 +159,15 @@ public BytePtrAccessor(byte* p, int length)
192159
break;
193160

194161
case 5:
195-
switch (key[0] | 0x20)
162+
switch (GetLower(key[0]))
196163
{
197164
case 'a': return Allow; // [A]llow
198165
case 'r': return Range; // [R]ange
199166
}
200167
break;
201168

202169
case 6:
203-
switch (key[0] | 0x20)
170+
switch (GetLower(key[0]))
204171
{
205172
case 'a': return Accept; // [A]ccept
206173
case 'c': return Cookie; // [C]ookie
@@ -212,14 +179,14 @@ public BytePtrAccessor(byte* p, int length)
212179
break;
213180

214181
case 7:
215-
switch (key[0] | 0x20)
182+
switch (GetLower(key[0]))
216183
{
217184
case ':': return PseudoStatus; // [:]status
218185
case 'a': return AltSvc; // [A]lt-Svc
219186
case 'c': return Cookie2; // [C]ookie2
220187
case 'e': return Expires; // [E]xpires
221188
case 'r':
222-
switch (key[3] | 0x20)
189+
switch (GetLower(key[3]))
223190
{
224191
case 'e': return Referer; // [R]ef[e]rer
225192
case 'r': return Refresh; // [R]ef[r]esh
@@ -233,7 +200,7 @@ public BytePtrAccessor(byte* p, int length)
233200
break;
234201

235202
case 8:
236-
switch (key[3] | 0x20)
203+
switch (GetLower(key[3]))
237204
{
238205
case '-': return AltUsed; // Alt[-]Used
239206
case 'a': return Location; // Loc[a]tion
@@ -246,7 +213,7 @@ public BytePtrAccessor(byte* p, int length)
246213
return ExpectCT; // Expect-CT
247214

248215
case 10:
249-
switch (key[0] | 0x20)
216+
switch (GetLower(key[0]))
250217
{
251218
case 'c': return Connection; // [C]onnection
252219
case 'k': return KeepAlive; // [K]eep-Alive
@@ -256,7 +223,7 @@ public BytePtrAccessor(byte* p, int length)
256223
break;
257224

258225
case 11:
259-
switch (key[0] | 0x20)
226+
switch (GetLower(key[0]))
260227
{
261228
case 'c': return ContentMD5; // [C]ontent-MD5
262229
case 'g': return GrpcStatus; // [g]rpc-status
@@ -266,7 +233,7 @@ public BytePtrAccessor(byte* p, int length)
266233
break;
267234

268235
case 12:
269-
switch (key[5] | 0x20)
236+
switch (GetLower(key[5]))
270237
{
271238
case 'd': return XMSEdgeRef; // X-MSE[d]ge-Ref
272239
case 'e': return XPoweredBy; // X-Pow[e]red-By
@@ -279,12 +246,12 @@ public BytePtrAccessor(byte* p, int length)
279246
break;
280247

281248
case 13:
282-
switch (key[12] | 0x20)
249+
switch (GetLower(key[12]))
283250
{
284251
case 'd': return LastModified; // Last-Modifie[d]
285252
case 'e': return ContentRange; // Content-Rang[e]
286253
case 'g':
287-
switch (key[0] | 0x20)
254+
switch (GetLower(key[0]))
288255
{
289256
case 's': return ServerTiming; // [S]erver-Timin[g]
290257
case 'g': return GrpcEncoding; // [g]rpc-encodin[g]
@@ -299,15 +266,15 @@ public BytePtrAccessor(byte* p, int length)
299266
break;
300267

301268
case 14:
302-
switch (key[0] | 0x20)
269+
switch (GetLower(key[0]))
303270
{
304271
case 'a': return AcceptCharset; // [A]ccept-Charset
305272
case 'c': return ContentLength; // [C]ontent-Length
306273
}
307274
break;
308275

309276
case 15:
310-
switch (key[7] | 0x20)
277+
switch (GetLower(key[7]))
311278
{
312279
case '-': return XFrameOptions; // X-Frame[-]Options
313280
case 'e': return AcceptEncoding; // Accept-[E]ncoding
@@ -319,11 +286,11 @@ public BytePtrAccessor(byte* p, int length)
319286
break;
320287

321288
case 16:
322-
switch (key[11] | 0x20)
289+
switch (GetLower(key[11]))
323290
{
324291
case 'a': return ContentLocation; // Content-Loc[a]tion
325292
case 'c':
326-
switch (key[0] | 0x20)
293+
switch (GetLower(key[0]))
327294
{
328295
case 'p': return ProxyConnection; // [P]roxy-Conne[c]tion
329296
case 'x': return XXssProtection; // [X]-XSS-Prote[c]tion
@@ -337,7 +304,7 @@ public BytePtrAccessor(byte* p, int length)
337304
break;
338305

339306
case 17:
340-
switch (key[0] | 0x20)
307+
switch (GetLower(key[0]))
341308
{
342309
case 'i': return IfModifiedSince; // [I]f-Modified-Since
343310
case 's': return SecWebSocketKey; // [S]ec-WebSocket-Key
@@ -346,15 +313,15 @@ public BytePtrAccessor(byte* p, int length)
346313
break;
347314

348315
case 18:
349-
switch (key[0] | 0x20)
316+
switch (GetLower(key[0]))
350317
{
351318
case 'p': return ProxyAuthenticate; // [P]roxy-Authenticate
352319
case 'x': return XContentDuration; // [X]-Content-Duration
353320
}
354321
break;
355322

356323
case 19:
357-
switch (key[0] | 0x20)
324+
switch (GetLower(key[0]))
358325
{
359326
case 'c': return ContentDisposition; // [C]ontent-Disposition
360327
case 'i': return IfUnmodifiedSince; // [I]f-Unmodified-Since
@@ -369,7 +336,7 @@ public BytePtrAccessor(byte* p, int length)
369336
return SecWebSocketVersion; // Sec-WebSocket-Version
370337

371338
case 22:
372-
switch (key[0] | 0x20)
339+
switch (GetLower(key[0]))
373340
{
374341
case 'a': return AccessControlMaxAge; // [A]ccess-Control-Max-Age
375342
case 's': return SecWebSocketProtocol; // [S]ec-WebSocket-Protocol
@@ -384,7 +351,7 @@ public BytePtrAccessor(byte* p, int length)
384351
return SecWebSocketExtensions; // Sec-WebSocket-Extensions
385352

386353
case 25:
387-
switch (key[0] | 0x20)
354+
switch (GetLower(key[0]))
388355
{
389356
case 's': return StrictTransportSecurity; // [S]trict-Transport-Security
390357
case 'u': return UpgradeInsecureRequests; // [U]pgrade-Insecure-Requests
@@ -395,7 +362,7 @@ public BytePtrAccessor(byte* p, int length)
395362
return AccessControlAllowOrigin; // Access-Control-Allow-Origin
396363

397364
case 28:
398-
switch (key[21] | 0x20)
365+
switch (GetLower(key[21]))
399366
{
400367
case 'h': return AccessControlAllowHeaders; // Access-Control-Allow-[H]eaders
401368
case 'm': return AccessControlAllowMethods; // Access-Control-Allow-[M]ethods
@@ -412,9 +379,9 @@ public BytePtrAccessor(byte* p, int length)
412379
return null;
413380
}
414381

415-
internal static KnownHeader? TryGetKnownHeader(string name)
382+
public static KnownHeader? TryGetKnownHeader(string name)
416383
{
417-
KnownHeader? candidate = GetCandidate(new StringAccessor(name));
384+
KnownHeader? candidate = GetCandidate<char>(name);
418385
if (candidate != null && StringComparer.OrdinalIgnoreCase.Equals(name, candidate.Name))
419386
{
420387
return candidate;
@@ -423,15 +390,12 @@ public BytePtrAccessor(byte* p, int length)
423390
return null;
424391
}
425392

426-
internal static unsafe KnownHeader? TryGetKnownHeader(ReadOnlySpan<byte> name)
393+
public static KnownHeader? TryGetKnownHeader(ReadOnlySpan<byte> name)
427394
{
428-
fixed (byte* p = &MemoryMarshal.GetReference(name))
395+
KnownHeader? candidate = GetCandidate(name);
396+
if (candidate != null && Ascii.EqualsIgnoreCase(name, candidate.Name))
429397
{
430-
KnownHeader? candidate = GetCandidate(new BytePtrAccessor(p, name.Length));
431-
if (candidate != null && Ascii.EqualsIgnoreCase(name, candidate.Name))
432-
{
433-
return candidate;
434-
}
398+
return candidate;
435399
}
436400

437401
return null;

0 commit comments

Comments
 (0)