@@ -60,6 +60,7 @@ const NODES: ReadonlyArray<string> = [
6060 'header_value' ,
6161 'header_value_otherwise' ,
6262 'header_value_lenient' ,
63+ 'header_value_lenient_failed' ,
6364 'header_value_lws' ,
6465 'header_value_te_chunked' ,
6566 'header_value_te_chunked_last' ,
@@ -490,11 +491,27 @@ export class HTTP {
490491 FLAGS . CHUNKED ,
491492 'header_value_te_chunked' ) ;
492493
494+ // Once chunked has been selected, no other encoding is possible in requests
495+ // https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1
496+ const forbidAfterChunkedInRequest = ( otherwise : Node ) => {
497+ return this . load ( 'type' , {
498+ [ TYPE . REQUEST ] : this . testLenientFlags ( LENIENT_FLAGS . TRANSFER_ENCODING , {
499+ 0 : span . headerValue . end ( ) . skipTo (
500+ p . error ( ERROR . INVALID_TRANSFER_ENCODING , 'Invalid `Transfer-Encoding` header value' ) ,
501+ ) ,
502+ } ) . otherwise ( otherwise ) ,
503+ } , otherwise ) ;
504+ } ;
505+
493506 n ( 'header_value_start' )
494507 . otherwise ( this . load ( 'header_state' , {
495508 [ HEADER_STATE . UPGRADE ] : this . setFlag ( FLAGS . UPGRADE , fallback ) ,
496- [ HEADER_STATE . TRANSFER_ENCODING ] : this . setFlag (
497- FLAGS . TRANSFER_ENCODING , toTransferEncoding ) ,
509+ [ HEADER_STATE . TRANSFER_ENCODING ] : this . testFlags (
510+ FLAGS . CHUNKED ,
511+ {
512+ 1 : forbidAfterChunkedInRequest ( this . setFlag ( FLAGS . TRANSFER_ENCODING , toTransferEncoding ) ) ,
513+ } ,
514+ this . setFlag ( FLAGS . TRANSFER_ENCODING , toTransferEncoding ) ) ,
498515 [ HEADER_STATE . CONTENT_LENGTH ] : n ( 'header_value_content_length_once' ) ,
499516 [ HEADER_STATE . CONNECTION ] : n ( 'header_value_connection' ) ,
500517 } , 'header_value' ) ) ;
@@ -516,7 +533,8 @@ export class HTTP {
516533 . peek ( [ '\r' , '\n' ] , this . update ( 'header_state' ,
517534 HEADER_STATE . TRANSFER_ENCODING_CHUNKED ,
518535 'header_value_otherwise' ) )
519- . otherwise ( n ( 'header_value_te_chunked' ) ) ;
536+ . peek ( ',' , forbidAfterChunkedInRequest ( n ( 'header_value_te_chunked' ) ) )
537+ . otherwise ( n ( 'header_value_te_token' ) ) ;
520538
521539 n ( 'header_value_te_token' )
522540 . match ( ',' , n ( 'header_value_te_token_ows' ) )
@@ -601,18 +619,23 @@ export class HTTP {
601619
602620 const checkLenient = this . testLenientFlags ( LENIENT_FLAGS . HEADERS , {
603621 1 : n ( 'header_value_lenient' ) ,
604- } , p . error ( ERROR . INVALID_HEADER_TOKEN , 'Invalid header value char ') ) ;
622+ } , n ( 'header_value_lenient_failed ') ) ;
605623
606624 n ( 'header_value_otherwise' )
607625 . peek ( '\r' , span . headerValue . end ( ) . skipTo ( n ( 'header_value_almost_done' ) ) )
608- . peek ( '\n' , span . headerValue . end ( n ( 'header_value_almost_done' ) ) )
609626 . otherwise ( checkLenient ) ;
610627
611628 n ( 'header_value_lenient' )
612629 . peek ( '\r' , span . headerValue . end ( ) . skipTo ( n ( 'header_value_almost_done' ) ) )
613630 . peek ( '\n' , span . headerValue . end ( n ( 'header_value_almost_done' ) ) )
614631 . skipTo ( n ( 'header_value_lenient' ) ) ;
615632
633+ n ( 'header_value_lenient_failed' )
634+ . peek ( '\n' , span . headerValue . end ( ) . skipTo (
635+ p . error ( ERROR . CR_EXPECTED , 'Missing expected CR after header value' ) ) ,
636+ )
637+ . otherwise ( p . error ( ERROR . INVALID_HEADER_TOKEN , 'Invalid header value char' ) ) ;
638+
616639 n ( 'header_value_almost_done' )
617640 . match ( '\n' , n ( 'header_value_lws' ) )
618641 . otherwise ( p . error ( ERROR . LF_EXPECTED ,
0 commit comments