@@ -259,6 +259,7 @@ pub struct ParserConfig {
259
259
allow_obsolete_multiline_headers_in_responses : bool ,
260
260
allow_multiple_spaces_in_request_line_delimiters : bool ,
261
261
allow_multiple_spaces_in_response_status_delimiters : bool ,
262
+ allow_space_before_first_header_name : bool ,
262
263
ignore_invalid_headers_in_responses : bool ,
263
264
ignore_invalid_headers_in_requests : bool ,
264
265
}
@@ -356,6 +357,39 @@ impl ParserConfig {
356
357
self . allow_obsolete_multiline_headers_in_responses
357
358
}
358
359
360
+ /// Sets whether white space before the first header is allowed
361
+ ///
362
+ /// This is not allowed by spec but some browsers ignore it. So this an option for
363
+ /// compatibility.
364
+ /// See https://github.com/curl/curl/issues/11605 for reference
365
+ /// # Example
366
+ ///
367
+ /// ```rust
368
+ /// let buf = b"HTTP/1.1 200 OK\r\n Space-Before-Header: hello there\r\n\r\n";
369
+ /// let mut headers = [httparse::EMPTY_HEADER; 1];
370
+ /// let mut response = httparse::Response::new(&mut headers[..]);
371
+ /// let result = httparse::ParserConfig::default()
372
+ /// .allow_space_before_first_header_name(true)
373
+ /// .parse_response(&mut response, buf);
374
+
375
+ /// assert_eq!(result, Ok(httparse::Status::Complete(buf.len())));
376
+ /// assert_eq!(response.version.unwrap(), 1);
377
+ /// assert_eq!(response.code.unwrap(), 200);
378
+ /// assert_eq!(response.reason.unwrap(), "OK");
379
+ /// assert_eq!(response.headers.len(), 1);
380
+ /// assert_eq!(response.headers[0].name, "Space-Before-Header");
381
+ /// assert_eq!(response.headers[0].value, &b"hello there"[..]);
382
+ /// ```
383
+ pub fn allow_space_before_first_header_name ( & mut self , value : bool ) -> & mut Self {
384
+ self . allow_space_before_first_header_name = value;
385
+ self
386
+ }
387
+
388
+ /// Whether white space before first header is allowed or not
389
+ pub fn space_before_first_header_name_are_allowed ( & self ) -> bool {
390
+ self . allow_space_before_first_header_name
391
+ }
392
+
359
393
/// Parses a request with the given config.
360
394
pub fn parse_request < ' buf > (
361
395
& self ,
@@ -519,6 +553,7 @@ impl<'h, 'b> Request<'h, 'b> {
519
553
& HeaderParserConfig {
520
554
allow_spaces_after_header_name: false ,
521
555
allow_obsolete_multiline_headers: false ,
556
+ allow_space_before_first_header_name: config. allow_space_before_first_header_name,
522
557
ignore_invalid_headers: config. ignore_invalid_headers_in_requests
523
558
} ,
524
559
) ) ;
@@ -716,6 +751,7 @@ impl<'h, 'b> Response<'h, 'b> {
716
751
& HeaderParserConfig {
717
752
allow_spaces_after_header_name: config. allow_spaces_after_header_name_in_responses,
718
753
allow_obsolete_multiline_headers: config. allow_obsolete_multiline_headers_in_responses,
754
+ allow_space_before_first_header_name: config. allow_space_before_first_header_name,
719
755
ignore_invalid_headers: config. ignore_invalid_headers_in_responses
720
756
}
721
757
) ) ;
@@ -1001,6 +1037,7 @@ unsafe fn assume_init_slice<T>(s: &mut [MaybeUninit<T>]) -> &mut [T] {
1001
1037
struct HeaderParserConfig {
1002
1038
allow_spaces_after_header_name : bool ,
1003
1039
allow_obsolete_multiline_headers : bool ,
1040
+ allow_space_before_first_header_name : bool ,
1004
1041
ignore_invalid_headers : bool ,
1005
1042
}
1006
1043
@@ -1120,7 +1157,23 @@ fn parse_headers_iter_uninit<'a>(
1120
1157
break ;
1121
1158
}
1122
1159
if !is_header_name_token ( b) {
1123
- handle_invalid_char ! ( bytes, b, HeaderName ) ;
1160
+ if config. allow_space_before_first_header_name
1161
+ && autoshrink. num_headers == 0
1162
+ && ( b == b' ' || b == b'\t' )
1163
+ {
1164
+ //advance past white space and then try parsing header again
1165
+ while let Some ( peek) = bytes. peek ( ) {
1166
+ if peek == b' ' || peek == b'\t' {
1167
+ next ! ( bytes) ;
1168
+ } else {
1169
+ break ;
1170
+ }
1171
+ }
1172
+ bytes. slice ( ) ;
1173
+ continue ' headers;
1174
+ } else {
1175
+ handle_invalid_char ! ( bytes, b, HeaderName ) ;
1176
+ }
1124
1177
}
1125
1178
1126
1179
#[ allow( clippy:: never_loop) ]
@@ -2498,4 +2551,38 @@ mod tests {
2498
2551
assert ! ( REQUEST . as_ptr( ) <= method. as_ptr( ) ) ;
2499
2552
assert ! ( method. as_ptr( ) <= buf_end) ;
2500
2553
}
2554
+
2555
+ static RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER : & [ u8 ] =
2556
+ b"HTTP/1.1 200 OK\r \n Space-Before-Header: hello there\r \n \r \n " ;
2557
+
2558
+ #[ test]
2559
+ fn test_forbid_response_with_space_before_first_header ( ) {
2560
+ let mut headers = [ EMPTY_HEADER ; 1 ] ;
2561
+ let mut response = Response :: new ( & mut headers[ ..] ) ;
2562
+ let result = response. parse ( RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER ) ;
2563
+
2564
+ assert_eq ! ( result, Err ( crate :: Error :: HeaderName ) ) ;
2565
+ }
2566
+
2567
+ #[ test]
2568
+ fn test_allow_response_response_with_space_before_first_header ( ) {
2569
+ let mut headers = [ EMPTY_HEADER ; 1 ] ;
2570
+ let mut response = Response :: new ( & mut headers[ ..] ) ;
2571
+ let result = crate :: ParserConfig :: default ( )
2572
+ . allow_space_before_first_header_name ( true )
2573
+ . parse_response ( & mut response, RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER ) ;
2574
+
2575
+ assert_eq ! (
2576
+ result,
2577
+ Ok ( Status :: Complete (
2578
+ RESPONSE_WITH_SPACE_BEFORE_FIRST_HEADER . len( )
2579
+ ) )
2580
+ ) ;
2581
+ assert_eq ! ( response. version. unwrap( ) , 1 ) ;
2582
+ assert_eq ! ( response. code. unwrap( ) , 200 ) ;
2583
+ assert_eq ! ( response. reason. unwrap( ) , "OK" ) ;
2584
+ assert_eq ! ( response. headers. len( ) , 1 ) ;
2585
+ assert_eq ! ( response. headers[ 0 ] . name, "Space-Before-Header" ) ;
2586
+ assert_eq ! ( response. headers[ 0 ] . value, & b"hello there" [ ..] ) ;
2587
+ }
2501
2588
}
0 commit comments