@@ -14,7 +14,7 @@ use std::sync::Arc;
1414use http_body_util:: { BodyExt , Limited } ;
1515use hyper:: body:: Incoming ;
1616use hyper:: service:: Service ;
17- use hyper:: { Request , Response } ;
17+ use hyper:: { HeaderMap , Request , Response } ;
1818use ldk_node:: bitcoin:: hashes:: hmac:: { Hmac , HmacEngine } ;
1919use ldk_node:: bitcoin:: hashes:: { sha256, Hash , HashEngine } ;
2020use ldk_node:: Node ;
@@ -256,7 +256,11 @@ impl Service<Request<Incoming>> for NodeService {
256256 let shutdown_rx = self . shutdown_rx . clone ( ) ;
257257 let ( request_parts, request_body) = req. into_parts ( ) ;
258258 let future: Self :: Future = Box :: pin ( async move {
259- let body_bytes = match read_request_body ( request_body) . await {
259+ let content_length = match request_content_length ( & request_parts. headers ) {
260+ Ok ( content_length) => content_length,
261+ Err ( status) => return Ok ( grpc_error_response ( status) ) ,
262+ } ;
263+ let body_bytes = match read_request_body ( request_body, content_length) . await {
260264 Ok ( bytes) => bytes,
261265 Err ( status) => return Ok ( grpc_error_response ( status) ) ,
262266 } ;
@@ -499,7 +503,39 @@ async fn handle_grpc_unary<
499503 }
500504}
501505
502- async fn read_request_body ( body : Incoming ) -> Result < bytes:: Bytes , GrpcStatus > {
506+ fn request_content_length ( headers : & HeaderMap ) -> Result < Option < u64 > , GrpcStatus > {
507+ let Some ( content_length) = headers. get ( "content-length" ) else {
508+ return Ok ( None ) ;
509+ } ;
510+ let len = content_length. to_str ( ) . ok ( ) . and_then ( |value| value. parse :: < u64 > ( ) . ok ( ) ) . ok_or_else (
511+ || GrpcStatus :: new ( GRPC_STATUS_INVALID_ARGUMENT , "Invalid content-length header" ) ,
512+ ) ?;
513+ if len > MAX_BODY_SIZE as u64 {
514+ return Err ( GrpcStatus :: new (
515+ GRPC_STATUS_INVALID_ARGUMENT ,
516+ "Request body too large or failed to read" ,
517+ ) ) ;
518+ }
519+ Ok ( Some ( len) )
520+ }
521+
522+ fn validate_request_body_len (
523+ content_length : Option < u64 > , actual_len : usize ,
524+ ) -> Result < ( ) , GrpcStatus > {
525+ if let Some ( expected_len) = content_length {
526+ if expected_len != actual_len as u64 {
527+ return Err ( GrpcStatus :: new (
528+ GRPC_STATUS_INVALID_ARGUMENT ,
529+ "Request body length does not match content-length" ,
530+ ) ) ;
531+ }
532+ }
533+ Ok ( ( ) )
534+ }
535+
536+ async fn read_request_body (
537+ body : Incoming , content_length : Option < u64 > ,
538+ ) -> Result < bytes:: Bytes , GrpcStatus > {
503539 let limited_body = Limited :: new ( body, MAX_BODY_SIZE ) ;
504540 let bytes = match limited_body. collect ( ) . await {
505541 Ok ( collected) => collected. to_bytes ( ) ,
@@ -510,6 +546,7 @@ async fn read_request_body(body: Incoming) -> Result<bytes::Bytes, GrpcStatus> {
510546 ) ) ;
511547 } ,
512548 } ;
549+ validate_request_body_len ( content_length, bytes. len ( ) ) ?;
513550 Ok ( bytes)
514551}
515552
@@ -606,4 +643,55 @@ mod tests {
606643 assert ! ( result. is_err( ) ) ;
607644 assert_eq ! ( result. unwrap_err( ) . error_code, LdkServerErrorCode :: AuthError ) ;
608645 }
646+
647+ #[ test]
648+ fn test_request_content_length_missing ( ) {
649+ let headers = HeaderMap :: new ( ) ;
650+ assert_eq ! ( request_content_length( & headers) . unwrap( ) , None ) ;
651+ }
652+
653+ #[ test]
654+ fn test_request_content_length_parses_value ( ) {
655+ let mut headers = HeaderMap :: new ( ) ;
656+ headers. insert ( "content-length" , "42" . parse ( ) . unwrap ( ) ) ;
657+
658+ assert_eq ! ( request_content_length( & headers) . unwrap( ) , Some ( 42 ) ) ;
659+ }
660+
661+ #[ test]
662+ fn test_request_content_length_rejects_invalid_value ( ) {
663+ let mut headers = HeaderMap :: new ( ) ;
664+ headers. insert ( "content-length" , "not-a-number" . parse ( ) . unwrap ( ) ) ;
665+
666+ let err = request_content_length ( & headers) . unwrap_err ( ) ;
667+ assert_eq ! ( err. code, GRPC_STATUS_INVALID_ARGUMENT ) ;
668+ assert_eq ! ( err. message, "Invalid content-length header" ) ;
669+ }
670+
671+ #[ test]
672+ fn test_request_content_length_rejects_oversized_value ( ) {
673+ let mut headers = HeaderMap :: new ( ) ;
674+ headers. insert ( "content-length" , ( MAX_BODY_SIZE as u64 + 1 ) . to_string ( ) . parse ( ) . unwrap ( ) ) ;
675+
676+ let err = request_content_length ( & headers) . unwrap_err ( ) ;
677+ assert_eq ! ( err. code, GRPC_STATUS_INVALID_ARGUMENT ) ;
678+ assert_eq ! ( err. message, "Request body too large or failed to read" ) ;
679+ }
680+
681+ #[ test]
682+ fn test_validate_request_body_len_allows_matching_length ( ) {
683+ assert ! ( validate_request_body_len( Some ( 5 ) , 5 ) . is_ok( ) ) ;
684+ }
685+
686+ #[ test]
687+ fn test_validate_request_body_len_allows_missing_length ( ) {
688+ assert ! ( validate_request_body_len( None , 5 ) . is_ok( ) ) ;
689+ }
690+
691+ #[ test]
692+ fn test_validate_request_body_len_rejects_mismatch ( ) {
693+ let err = validate_request_body_len ( Some ( 6 ) , 5 ) . unwrap_err ( ) ;
694+ assert_eq ! ( err. code, GRPC_STATUS_INVALID_ARGUMENT ) ;
695+ assert_eq ! ( err. message, "Request body length does not match content-length" ) ;
696+ }
609697}
0 commit comments