@@ -15,14 +15,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
15
15
public class HttpParser < TRequestHandler > : IHttpParser < TRequestHandler > where TRequestHandler : IHttpHeadersHandler , IHttpRequestLineHandler
16
16
{
17
17
private readonly bool _showErrorDetails ;
18
+ private readonly bool _enableLineFeedTerminator ;
18
19
19
- public HttpParser ( ) : this ( showErrorDetails : true )
20
+ public HttpParser ( ) : this ( showErrorDetails : true , enableLineFeedTerminator : true )
20
21
{
21
22
}
22
23
23
- public HttpParser ( bool showErrorDetails )
24
+ public HttpParser ( bool showErrorDetails ) : this ( showErrorDetails , enableLineFeedTerminator : false )
25
+ {
26
+ }
27
+
28
+ internal HttpParser ( bool showErrorDetails , bool enableLineFeedTerminator )
24
29
{
25
30
_showErrorDetails = showErrorDetails ;
31
+ _enableLineFeedTerminator = enableLineFeedTerminator ;
26
32
}
27
33
28
34
// byte types don't have a data type annotation so we pre-cast them; to avoid in-place casts
@@ -115,11 +121,35 @@ private void ParseRequestLine(TRequestHandler handler, ReadOnlySpan<byte> reques
115
121
// Consume space
116
122
offset ++ ;
117
123
118
- // Version + CR is 9 bytes which should take us to .Length
119
- // LF should have been dropped prior to method call
120
- if ( ( uint ) offset + 9 != ( uint ) requestLine . Length || requestLine [ offset + sizeof ( ulong ) ] != ByteCR )
124
+ if ( ! _enableLineFeedTerminator )
121
125
{
122
- RejectRequestLine ( requestLine ) ;
126
+ // Version + CR is 9 bytes which should take us to .Length
127
+ // LF should have been dropped prior to method call
128
+ if ( ( uint ) offset + 9 != ( uint ) requestLine . Length || requestLine [ offset + 8 ] != ByteCR )
129
+ {
130
+ RejectRequestLine ( requestLine ) ;
131
+ }
132
+ }
133
+ else
134
+ {
135
+ // LF should have been dropped prior to method call
136
+ // If offset + 8 is .Length then requestLine is valid since it mean LF was the next char
137
+ if ( ( uint ) offset + 8 != ( uint ) requestLine . Length )
138
+ {
139
+ // Version + CR is 9 bytes which should take us to .Length
140
+ if ( ( uint ) offset + 9 != ( uint ) requestLine . Length || requestLine [ offset + 8 ] != ByteCR )
141
+ {
142
+ RejectRequestLine ( requestLine ) ;
143
+ }
144
+ }
145
+ else
146
+ {
147
+ // e.g., GET / HTTP1.\r\n
148
+ if ( requestLine [ offset + 7 ] == ByteCR )
149
+ {
150
+ RejectRequestLine ( requestLine ) ;
151
+ }
152
+ }
123
153
}
124
154
125
155
// Version
@@ -201,44 +231,59 @@ public bool ParseHeaders(TRequestHandler handler, ref SequenceReader<byte> reade
201
231
// the bounds check for the next lookup of span[length]
202
232
if ( ( uint ) length < ( uint ) span . Length )
203
233
{
204
- // Early memory read to hide latency
205
- var expectedCR = span [ length ] ;
206
- // Correctly has a CR, move to next
207
- length ++ ;
234
+ var headerSpan = span [ ..length ] ;
208
235
209
- if ( expectedCR != ByteCR )
236
+ if ( length < 4 )
210
237
{
211
- // Sequence needs to be CRLF not LF first.
212
- RejectRequestHeader ( span [ .. length ] ) ;
238
+ // Less than min possible headerSpan of 3 bytes a:b
239
+ RejectRequestHeader ( headerSpan ) ;
213
240
}
241
+
242
+ // Early memory read to hide latency
243
+ var lineTerminator = span [ length ] ;
244
+ // Correctly has a CR/LF, move to next
245
+ length ++ ;
214
246
215
- if ( ( uint ) length < ( uint ) span . Length )
247
+ if ( lineTerminator == ByteLF )
216
248
{
217
- // Early memory read to hide latency
218
- var expectedLF = span [ length ] ;
219
- // Correctly has a LF, move to next
220
- length ++ ;
221
-
222
- if ( expectedLF != ByteLF ||
223
- length < 5 ||
224
- // Exclude the CRLF from the headerLine and parse the header name:value pair
225
- ! TryTakeSingleHeader ( handler , span [ ..( length - 2 ) ] ) )
249
+ if ( ! _enableLineFeedTerminator )
226
250
{
227
- // Sequence needs to be CRLF and not contain an inner CR not part of terminator.
228
- // Less than min possible headerSpan of 5 bytes a:b\r\n
229
- // Not parsable as a valid name:value header pair.
251
+ // Sequence needs to be CRLF not LF first.
230
252
RejectRequestHeader ( span [ ..length ] ) ;
231
253
}
232
-
233
- // Read the header successfully, skip the reader forward past the headerSpan.
234
- span = span . Slice ( length ) ;
235
- reader . Advance ( length ) ;
236
254
}
237
255
else
238
256
{
239
- // No enough data, set length to 0.
240
- length = 0 ;
257
+ if ( ( uint ) length < ( uint ) span . Length )
258
+ {
259
+ // Early memory read to hide latency
260
+ var expectedLF = span [ length ] ;
261
+ // Correctly has a LF, move to next
262
+ length ++ ;
263
+
264
+ if ( expectedLF != ByteLF )
265
+ {
266
+ // Sequence needs to be CRLF not LF first.
267
+ RejectRequestHeader ( span [ ..length ] ) ;
268
+ }
269
+ }
270
+ else
271
+ {
272
+ // No enough data, set length to 0.
273
+ length = 0 ;
274
+ }
275
+ }
276
+
277
+ if ( length != 0 && ! TryTakeSingleHeader ( handler , headerSpan ) )
278
+ {
279
+ // Sequence needs to be CRLF and not contain an inner CR not part of terminator.
280
+ // Not parsable as a valid name:value header pair.
281
+ RejectRequestHeader ( span [ ..length ] ) ;
241
282
}
283
+
284
+ // Read the header successfully, skip the reader forward past the headerSpan.
285
+ span = span [ length ..] ;
286
+ reader . Advance ( length ) ;
242
287
}
243
288
}
244
289
@@ -287,14 +332,17 @@ private int ParseMultiSpanHeader(TRequestHandler handler, ref SequenceReader<byt
287
332
if ( currentSlice . Slice ( reader . Position , lineEndPosition . Value ) . Length == currentSlice . Length - 1 )
288
333
{
289
334
// No enough data, so CRLF can't currently be there.
290
- // However, we need to check the found char is CR and not LF
335
+ // However, we need to check the found char is CR and not LF (unless quirk mode)
291
336
292
337
// Advance 1 to include CR/LF in lineEnd
293
338
lineEnd = currentSlice . GetPosition ( 1 , lineEndPosition . Value ) ;
294
339
headerSpan = currentSlice . Slice ( reader . Position , lineEnd ) . ToSpan ( ) ;
295
340
if ( headerSpan [ ^ 1 ] != ByteCR )
296
341
{
297
- RejectRequestHeader ( headerSpan ) ;
342
+ if ( ! _enableLineFeedTerminator || headerSpan [ ^ 1 ] != ByteLF )
343
+ {
344
+ RejectRequestHeader ( headerSpan ) ;
345
+ }
298
346
}
299
347
return - 1 ;
300
348
}
@@ -305,14 +353,35 @@ private int ParseMultiSpanHeader(TRequestHandler handler, ref SequenceReader<byt
305
353
306
354
if ( headerSpan . Length < 5 )
307
355
{
308
- // Less than min possible headerSpan is 5 bytes a:b\r\n
309
- RejectRequestHeader ( headerSpan ) ;
356
+ if ( ! _enableLineFeedTerminator || headerSpan . Length < 4 )
357
+ {
358
+ // Less than min possible headerSpan is 4 bytes a:b\n or 5 bytes a:b\r\n
359
+ RejectRequestHeader ( headerSpan ) ;
360
+ }
310
361
}
311
362
312
363
if ( headerSpan [ ^ 2 ] != ByteCR )
313
364
{
314
- // Sequence needs to be CRLF not LF first.
315
- RejectRequestHeader ( headerSpan [ ..^ 1 ] ) ;
365
+ if ( ! _enableLineFeedTerminator )
366
+ {
367
+ // Sequence needs to be CRLF.
368
+ RejectRequestHeader ( headerSpan [ ..^ 1 ] ) ;
369
+ }
370
+ else if ( headerSpan [ ^ 2 ] != ByteLF )
371
+ {
372
+ RejectRequestHeader ( headerSpan [ ..^ 1 ] ) ;
373
+ }
374
+ else
375
+ {
376
+ // Line ends with LF and quirk mode is enabled, try to parse the header
377
+ if ( ! TryTakeSingleHeader ( handler , headerSpan [ ..^ 1 ] ) )
378
+ {
379
+ // Not parsable as a valid name:value header pair.
380
+ RejectRequestHeader ( headerSpan ) ;
381
+ }
382
+
383
+ return headerSpan . Length ;
384
+ }
316
385
}
317
386
318
387
if ( headerSpan [ ^ 1 ] != ByteLF ||
0 commit comments