@@ -244,6 +244,7 @@ impl<T> Status<T> {
244
244
#[ derive( Clone , Debug , Default ) ]
245
245
pub struct ParserConfig {
246
246
allow_spaces_after_header_name_in_responses : bool ,
247
+ allow_obsolete_multiline_headers_in_responses : bool ,
247
248
}
248
249
249
250
impl ParserConfig {
@@ -256,6 +257,42 @@ impl ParserConfig {
256
257
self
257
258
}
258
259
260
+ /// Sets whether obsolete multiline headers should be allowed.
261
+ ///
262
+ /// This is an obsolete part of HTTP/1. Use at your own risk. If you are
263
+ /// building an HTTP library, the newlines (`\r` and `\n`) should be
264
+ /// replaced by spaces before handing the header value to the user.
265
+ ///
266
+ /// # Example
267
+ ///
268
+ /// ```rust
269
+ /// let buf = b"HTTP/1.1 200 OK\r\nFolded-Header: hello\r\n there \r\n\r\n";
270
+ /// let mut headers = [httparse::EMPTY_HEADER; 16];
271
+ /// let mut response = httparse::Response::new(&mut headers);
272
+ ///
273
+ /// let res = httparse::ParserConfig::default()
274
+ /// .allow_obsolete_multiline_headers_in_responses(true)
275
+ /// .parse_response(&mut response, buf);
276
+ ///
277
+ /// assert_eq!(res, Ok(httparse::Status::Complete(buf.len())));
278
+ ///
279
+ /// assert_eq!(response.headers.len(), 1);
280
+ /// assert_eq!(response.headers[0].name, "Folded-Header");
281
+ /// assert_eq!(response.headers[0].value, b"hello\r\n there");
282
+ /// ```
283
+ pub fn allow_obsolete_multiline_headers_in_responses (
284
+ & mut self ,
285
+ value : bool ,
286
+ ) -> & mut Self {
287
+ self . allow_obsolete_multiline_headers_in_responses = value;
288
+ self
289
+ }
290
+
291
+ /// Whether obsolete multiline headers should be allowed.
292
+ pub fn obsolete_multiline_headers_in_responses_are_allowed ( & self ) -> bool {
293
+ self . allow_obsolete_multiline_headers_in_responses
294
+ }
295
+
259
296
/// Parses a response with the given config.
260
297
pub fn parse_response < ' headers , ' buf > (
261
298
& self ,
@@ -815,8 +852,7 @@ fn parse_headers_iter_uninit<'a, 'b>(
815
852
816
853
let mut b;
817
854
818
- ' value: loop {
819
-
855
+ let value_slice = ' value: loop {
820
856
// eat white space between colon and value
821
857
' whitespace_after_colon: loop {
822
858
b = next ! ( bytes) ;
@@ -826,73 +862,131 @@ fn parse_headers_iter_uninit<'a, 'b>(
826
862
continue ' whitespace_after_colon;
827
863
} else {
828
864
if !is_header_value_token ( b) {
829
- break ' value;
865
+ if b == b'\r' {
866
+ expect ! ( bytes. next( ) == b'\n' => Err ( Error :: HeaderValue ) ) ;
867
+ } else if b != b'\n' {
868
+ return Err ( Error :: HeaderValue ) ;
869
+ }
870
+
871
+ if config. allow_obsolete_multiline_headers_in_responses {
872
+ match bytes. peek ( ) {
873
+ None => {
874
+ // Next byte may be a space, in which case that header
875
+ // is using obsolete line folding, so we may have more
876
+ // whitespace to skip after colon.
877
+ return Ok ( Status :: Partial ) ;
878
+ }
879
+ Some ( b' ' ) | Some ( b'\t' ) => {
880
+ // The space will be consumed next iteration.
881
+ continue ' whitespace_after_colon;
882
+ }
883
+ _ => {
884
+ // There is another byte after the end of the line,
885
+ // but it's not whitespace, so it's probably another
886
+ // header or the final line return. This header is thus
887
+ // empty.
888
+ } ,
889
+ }
890
+ }
891
+
892
+ count += bytes. pos ( ) ;
893
+ let whitespace_slice = bytes. slice ( ) ;
894
+
895
+ // This produces an empty slice that points to the beginning
896
+ // of the whitespace.
897
+ break ' value & whitespace_slice[ 0 ..0 ] ;
830
898
}
831
899
break ' whitespace_after_colon;
832
900
}
833
901
}
834
902
835
- // parse value till EOL
836
-
837
- simd:: match_header_value_vectored ( bytes) ;
903
+ ' value_lines: loop {
904
+ // parse value till EOL
905
+
906
+ simd:: match_header_value_vectored ( bytes) ;
907
+
908
+ ' value_line: loop {
909
+ if let Some ( mut bytes8) = bytes. next_8 ( ) {
910
+ macro_rules! check {
911
+ ( $bytes: ident, $i: ident) => ( {
912
+ b = $bytes. $i( ) ;
913
+ if !is_header_value_token( b) {
914
+ break ' value_line;
915
+ }
916
+ } ) ;
917
+ ( $bytes: ident) => ( {
918
+ check!( $bytes, _0) ;
919
+ check!( $bytes, _1) ;
920
+ check!( $bytes, _2) ;
921
+ check!( $bytes, _3) ;
922
+ check!( $bytes, _4) ;
923
+ check!( $bytes, _5) ;
924
+ check!( $bytes, _6) ;
925
+ check!( $bytes, _7) ;
926
+ } )
927
+ }
928
+
929
+ check ! ( bytes8) ;
930
+
931
+ continue ' value_line;
932
+ }
838
933
839
- macro_rules! check {
840
- ( $bytes: ident, $i: ident) => ( {
841
- b = $bytes. $i( ) ;
934
+ b = next ! ( bytes) ;
842
935
if !is_header_value_token ( b) {
843
- break ' value ;
936
+ break ' value_line ;
844
937
}
845
- } ) ;
846
- ( $bytes: ident) => ( {
847
- check!( $bytes, _0) ;
848
- check!( $bytes, _1) ;
849
- check!( $bytes, _2) ;
850
- check!( $bytes, _3) ;
851
- check!( $bytes, _4) ;
852
- check!( $bytes, _5) ;
853
- check!( $bytes, _6) ;
854
- check!( $bytes, _7) ;
855
- } )
856
- }
857
- while let Some ( mut bytes8) = bytes. next_8 ( ) {
858
- check ! ( bytes8) ;
859
- }
860
- loop {
861
- b = next ! ( bytes) ;
862
- if !is_header_value_token ( b) {
863
- break ' value;
864
938
}
865
- }
866
- }
867
939
868
- //found_ctl
869
- let value_slice : & [ u8 ] = if b == b'\r' {
870
- expect ! ( bytes. next( ) == b'\n' => Err ( Error :: HeaderValue ) ) ;
871
- count += bytes. pos ( ) ;
872
- // having just check that `\r\n` exists, it's safe to skip those 2 bytes
873
- unsafe {
874
- bytes. slice_skip ( 2 )
875
- }
876
- } else if b == b'\n' {
877
- count += bytes. pos ( ) ;
878
- // having just check that `\r\n` exists, it's safe to skip 1 byte
879
- unsafe {
880
- bytes. slice_skip ( 1 )
940
+ //found_ctl
941
+ let skip = if b == b'\r' {
942
+ expect ! ( bytes. next( ) == b'\n' => Err ( Error :: HeaderValue ) ) ;
943
+ 2
944
+ } else if b == b'\n' {
945
+ 1
946
+ } else {
947
+ return Err ( Error :: HeaderValue ) ;
948
+ } ;
949
+
950
+ if config. allow_obsolete_multiline_headers_in_responses {
951
+ match bytes. peek ( ) {
952
+ None => {
953
+ // Next byte may be a space, in which case that header
954
+ // may be using line folding, so we need more data.
955
+ return Ok ( Status :: Partial ) ;
956
+ }
957
+ Some ( b' ' ) | Some ( b'\t' ) => {
958
+ // The space will be consumed next iteration.
959
+ continue ' value_lines;
960
+ }
961
+ _ => {
962
+ // There is another byte after the end of the line,
963
+ // but it's not a space, so it's probably another
964
+ // header or the final line return. We are thus done
965
+ // with this current header.
966
+ } ,
967
+ }
968
+ }
969
+
970
+ count += bytes. pos ( ) ;
971
+ // having just checked that a newline exists, it's safe to skip it.
972
+ unsafe {
973
+ break ' value bytes. slice_skip ( skip) ;
974
+ }
881
975
}
882
- } else {
883
- return Err ( Error :: HeaderValue ) ;
884
976
} ;
885
977
886
- let header_value: & [ u8 ] ;
887
978
// trim trailing whitespace in the header
888
- if let Some ( last_visible) = value_slice. iter ( ) . rposition ( |b| * b != b' ' && * b != b'\t' ) {
979
+ let header_value = if let Some ( last_visible) = value_slice
980
+ . iter ( )
981
+ . rposition ( |b| * b != b' ' && * b != b'\t' && * b != b'\r' && * b != b'\n' )
982
+ {
889
983
// There is at least one non-whitespace character.
890
- header_value = & value_slice[ 0 ..last_visible+1 ] ;
984
+ & value_slice[ 0 ..last_visible+1 ]
891
985
} else {
892
986
// There is no non-whitespace character. This can only happen when value_slice is
893
987
// empty.
894
- header_value = value_slice;
895
- }
988
+ value_slice
989
+ } ;
896
990
897
991
* uninit_header = MaybeUninit :: new ( Header {
898
992
name : header_name,
@@ -1402,6 +1496,122 @@ mod tests {
1402
1496
assert_eq ! ( result, Err ( :: Error :: HeaderName ) ) ;
1403
1497
}
1404
1498
1499
+ static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START : & ' static [ u8 ] =
1500
+ b"HTTP/1.1 200 OK\r \n Line-Folded-Header: \r \n \r \n hello there\r \n \r \n " ;
1501
+
1502
+ #[ test]
1503
+ fn test_forbid_response_with_obsolete_line_folding_at_start ( ) {
1504
+ let mut headers = [ EMPTY_HEADER ; 1 ] ;
1505
+ let mut response = Response :: new ( & mut headers[ ..] ) ;
1506
+ let result = response. parse ( RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START ) ;
1507
+
1508
+ assert_eq ! ( result, Err ( :: Error :: HeaderName ) ) ;
1509
+ }
1510
+
1511
+ #[ test]
1512
+ fn test_allow_response_with_obsolete_line_folding_at_start ( ) {
1513
+ let mut headers = [ EMPTY_HEADER ; 1 ] ;
1514
+ let mut response = Response :: new ( & mut headers[ ..] ) ;
1515
+ let result = :: ParserConfig :: default ( )
1516
+ . allow_obsolete_multiline_headers_in_responses ( true )
1517
+ . parse_response ( & mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START ) ;
1518
+
1519
+ assert_eq ! ( result, Ok ( Status :: Complete ( RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_START . len( ) ) ) ) ;
1520
+ assert_eq ! ( response. version. unwrap( ) , 1 ) ;
1521
+ assert_eq ! ( response. code. unwrap( ) , 200 ) ;
1522
+ assert_eq ! ( response. reason. unwrap( ) , "OK" ) ;
1523
+ assert_eq ! ( response. headers. len( ) , 1 ) ;
1524
+ assert_eq ! ( response. headers[ 0 ] . name, "Line-Folded-Header" ) ;
1525
+ assert_eq ! ( response. headers[ 0 ] . value, & b"hello there" [ ..] ) ;
1526
+ }
1527
+
1528
+ static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END : & ' static [ u8 ] =
1529
+ b"HTTP/1.1 200 OK\r \n Line-Folded-Header: hello there\r \n \r \n \r \n \r \n " ;
1530
+
1531
+ #[ test]
1532
+ fn test_forbid_response_with_obsolete_line_folding_at_end ( ) {
1533
+ let mut headers = [ EMPTY_HEADER ; 1 ] ;
1534
+ let mut response = Response :: new ( & mut headers[ ..] ) ;
1535
+ let result = response. parse ( RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END ) ;
1536
+
1537
+ assert_eq ! ( result, Err ( :: Error :: HeaderName ) ) ;
1538
+ }
1539
+
1540
+ #[ test]
1541
+ fn test_allow_response_with_obsolete_line_folding_at_end ( ) {
1542
+ let mut headers = [ EMPTY_HEADER ; 1 ] ;
1543
+ let mut response = Response :: new ( & mut headers[ ..] ) ;
1544
+ let result = :: ParserConfig :: default ( )
1545
+ . allow_obsolete_multiline_headers_in_responses ( true )
1546
+ . parse_response ( & mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END ) ;
1547
+
1548
+ assert_eq ! ( result, Ok ( Status :: Complete ( RESPONSE_WITH_OBSOLETE_LINE_FOLDING_AT_END . len( ) ) ) ) ;
1549
+ assert_eq ! ( response. version. unwrap( ) , 1 ) ;
1550
+ assert_eq ! ( response. code. unwrap( ) , 200 ) ;
1551
+ assert_eq ! ( response. reason. unwrap( ) , "OK" ) ;
1552
+ assert_eq ! ( response. headers. len( ) , 1 ) ;
1553
+ assert_eq ! ( response. headers[ 0 ] . name, "Line-Folded-Header" ) ;
1554
+ assert_eq ! ( response. headers[ 0 ] . value, & b"hello there" [ ..] ) ;
1555
+ }
1556
+
1557
+ static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE : & ' static [ u8 ] =
1558
+ b"HTTP/1.1 200 OK\r \n Line-Folded-Header: hello \r \n \r \n there\r \n \r \n " ;
1559
+
1560
+ #[ test]
1561
+ fn test_forbid_response_with_obsolete_line_folding_in_middle ( ) {
1562
+ let mut headers = [ EMPTY_HEADER ; 1 ] ;
1563
+ let mut response = Response :: new ( & mut headers[ ..] ) ;
1564
+ let result = response. parse ( RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE ) ;
1565
+
1566
+ assert_eq ! ( result, Err ( :: Error :: HeaderName ) ) ;
1567
+ }
1568
+
1569
+ #[ test]
1570
+ fn test_allow_response_with_obsolete_line_folding_in_middle ( ) {
1571
+ let mut headers = [ EMPTY_HEADER ; 1 ] ;
1572
+ let mut response = Response :: new ( & mut headers[ ..] ) ;
1573
+ let result = :: ParserConfig :: default ( )
1574
+ . allow_obsolete_multiline_headers_in_responses ( true )
1575
+ . parse_response ( & mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE ) ;
1576
+
1577
+ assert_eq ! ( result, Ok ( Status :: Complete ( RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_MIDDLE . len( ) ) ) ) ;
1578
+ assert_eq ! ( response. version. unwrap( ) , 1 ) ;
1579
+ assert_eq ! ( response. code. unwrap( ) , 200 ) ;
1580
+ assert_eq ! ( response. reason. unwrap( ) , "OK" ) ;
1581
+ assert_eq ! ( response. headers. len( ) , 1 ) ;
1582
+ assert_eq ! ( response. headers[ 0 ] . name, "Line-Folded-Header" ) ;
1583
+ assert_eq ! ( response. headers[ 0 ] . value, & b"hello \r \n \r \n there" [ ..] ) ;
1584
+ }
1585
+
1586
+ static RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER : & ' static [ u8 ] =
1587
+ b"HTTP/1.1 200 OK\r \n Line-Folded-Header: \r \n \r \n \r \n \r \n " ;
1588
+
1589
+ #[ test]
1590
+ fn test_forbid_response_with_obsolete_line_folding_in_empty_header ( ) {
1591
+ let mut headers = [ EMPTY_HEADER ; 1 ] ;
1592
+ let mut response = Response :: new ( & mut headers[ ..] ) ;
1593
+ let result = response. parse ( RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER ) ;
1594
+
1595
+ assert_eq ! ( result, Err ( :: Error :: HeaderName ) ) ;
1596
+ }
1597
+
1598
+ #[ test]
1599
+ fn test_allow_response_with_obsolete_line_folding_in_empty_header ( ) {
1600
+ let mut headers = [ EMPTY_HEADER ; 1 ] ;
1601
+ let mut response = Response :: new ( & mut headers[ ..] ) ;
1602
+ let result = :: ParserConfig :: default ( )
1603
+ . allow_obsolete_multiline_headers_in_responses ( true )
1604
+ . parse_response ( & mut response, RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER ) ;
1605
+
1606
+ assert_eq ! ( result, Ok ( Status :: Complete ( RESPONSE_WITH_OBSOLETE_LINE_FOLDING_IN_EMPTY_HEADER . len( ) ) ) ) ;
1607
+ assert_eq ! ( response. version. unwrap( ) , 1 ) ;
1608
+ assert_eq ! ( response. code. unwrap( ) , 200 ) ;
1609
+ assert_eq ! ( response. reason. unwrap( ) , "OK" ) ;
1610
+ assert_eq ! ( response. headers. len( ) , 1 ) ;
1611
+ assert_eq ! ( response. headers[ 0 ] . name, "Line-Folded-Header" ) ;
1612
+ assert_eq ! ( response. headers[ 0 ] . value, & b"" [ ..] ) ;
1613
+ }
1614
+
1405
1615
#[ test]
1406
1616
fn test_chunk_size ( ) {
1407
1617
assert_eq ! ( parse_chunk_size( b"0\r \n " ) , Ok ( Status :: Complete ( ( 3 , 0 ) ) ) ) ;
0 commit comments