@@ -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
@@ -291,12 +310,26 @@ impl App {
291310 ) -> Result < Response < BoxBody < Bytes , hyper:: Error > > , Error > {
292311 let ( parts, body) = req. into_parts ( ) ;
293312 let headers = Headers ( & parts. headers ) ;
294- let query_string = parts. uri . query ( ) . unwrap_or ( "" ) ;
295- let body = body
296- . collect ( )
313+
314+ let expected_length = headers
315+ . 0
316+ . get ( "Content-Length" )
317+ . and_then ( |val| val. to_str ( ) . ok ( ) )
318+ . and_then ( |s| s. parse :: < usize > ( ) . ok ( ) )
319+ . unwrap_or ( MAX_CONTENT_LENGTH ) ;
320+
321+ if expected_length > MAX_CONTENT_LENGTH {
322+ log:: error!( "Error: Content length exceeds max allowed" ) ;
323+ return Err ( Implementation ( ImplementationError :: from (
324+ anyhow ! ( "Content length too large: {expected_length}" ) . into_boxed_dyn_error ( ) ,
325+ ) ) ) ;
326+ }
327+
328+ let body = read_limited_body ( body. into_data_stream ( ) , expected_length)
297329 . await
298- . map_err ( |e| Error :: Implementation ( ImplementationError :: new ( e) ) ) ?
299- . to_bytes ( ) ;
330+ . map_err ( |e| Implementation ( ImplementationError :: from ( e. into_boxed_dyn_error ( ) ) ) ) ?;
331+
332+ let query_string = parts. uri . query ( ) . unwrap_or ( "" ) ;
300333 let proposal = UncheckedOriginalPayload :: from_request ( & body, query_string, headers) ?;
301334
302335 let payjoin_proposal = self . process_v1_proposal ( proposal) ?;
0 commit comments