@@ -23,9 +23,14 @@ use tokio::sync::watch;
2323use super :: config:: Config ;
2424use super :: wallet:: BitcoindWallet ;
2525use super :: App as AppTrait ;
26- use crate :: app:: { handle_interrupt, http_agent} ;
26+ use crate :: app:: { handle_interrupt, http_agent, read_limited_body } ;
2727use crate :: db:: Database ;
2828
29+ /// 4M block size limit with base64 encoding overhead => maximum reasonable size of content-length
30+ /// 4_000_000 * 4 / 3 fits in u32
31+ const MAX_CONTENT_LENGTH : usize = 4_000_000 * 4 / 3 ;
32+
33+ #[ derive( Clone ) ]
2934struct Headers < ' a > ( & ' a hyper:: HeaderMap ) ;
3035impl payjoin:: receive:: v1:: Headers for Headers < ' _ > {
3136 fn get_header ( & self , key : & str ) -> Option < & str > {
@@ -86,8 +91,22 @@ impl AppTrait for App {
8691 "Sent fallback transaction hex: {:#}" ,
8792 payjoin:: bitcoin:: consensus:: encode:: serialize_hex( & fallback_tx)
8893 ) ;
89- let psbt = ctx. process_response ( & response. bytes ( ) . await ?) . map_err ( |e| {
90- tracing:: debug!( "Error processing response: {e:?}" ) ;
94+
95+ let expected_length = response
96+ . headers ( )
97+ . get ( "Content-Length" )
98+ . and_then ( |val| val. to_str ( ) . ok ( ) )
99+ . and_then ( |s| s. parse :: < usize > ( ) . ok ( ) )
100+ . unwrap_or ( MAX_CONTENT_LENGTH ) ;
101+
102+ if expected_length > MAX_CONTENT_LENGTH {
103+ return Err ( anyhow ! ( "Response body is too large: {} bytes" , expected_length) ) ;
104+ }
105+
106+ let body = read_limited_body ( response. bytes_stream ( ) , MAX_CONTENT_LENGTH ) . await ?;
107+
108+ let psbt = ctx. process_response ( & body) . map_err ( |e| {
109+ log:: debug!( "Error processing response: {e:?}" ) ;
91110 anyhow ! ( "Failed to process response {e}" )
92111 } ) ?;
93112
@@ -295,12 +314,26 @@ impl App {
295314 ) -> Result < Response < BoxBody < Bytes , hyper:: Error > > , ReplyableError > {
296315 let ( parts, body) = req. into_parts ( ) ;
297316 let headers = Headers ( & parts. headers ) ;
298- let query_string = parts. uri . query ( ) . unwrap_or ( "" ) ;
299- let body = body
300- . collect ( )
317+
318+ let expected_length = headers
319+ . 0
320+ . get ( "Content-Length" )
321+ . and_then ( |val| val. to_str ( ) . ok ( ) )
322+ . and_then ( |s| s. parse :: < usize > ( ) . ok ( ) )
323+ . unwrap_or ( MAX_CONTENT_LENGTH ) ;
324+
325+ if expected_length > MAX_CONTENT_LENGTH {
326+ log:: error!( "Error: Content length exceeds max allowed" ) ;
327+ return Err ( Implementation ( ImplementationError :: from (
328+ anyhow ! ( "Content length too large: {expected_length}" ) . into_boxed_dyn_error ( ) ,
329+ ) ) ) ;
330+ }
331+
332+ let body = read_limited_body ( body. into_data_stream ( ) , expected_length)
301333 . await
302- . map_err ( |e| Implementation ( ImplementationError :: new ( e) ) ) ?
303- . to_bytes ( ) ;
334+ . map_err ( |e| Implementation ( ImplementationError :: from ( e. into_boxed_dyn_error ( ) ) ) ) ?;
335+
336+ let query_string = parts. uri . query ( ) . unwrap_or ( "" ) ;
304337 let proposal = UncheckedOriginalPayload :: from_request ( & body, query_string, headers) ?;
305338
306339 let payjoin_proposal = self . process_v1_proposal ( proposal) ?;
0 commit comments