2121//! [`bitmask-core`](https://github.com/diba-io/bitmask-core) BDK integration. Bring your own
2222//! wallet and http client.
2323
24+ use std:: io:: { BufRead , BufReader } ;
25+
2426use bitcoin:: psbt:: Psbt ;
2527use bitcoin:: { FeeRate , ScriptBuf , Weight } ;
2628use error:: { BuildSenderError , InternalBuildSenderError } ;
@@ -30,7 +32,7 @@ use super::*;
3032pub use crate :: output_substitution:: OutputSubstitution ;
3133use crate :: psbt:: PsbtExt ;
3234use crate :: request:: Request ;
33- pub use crate :: PjUri ;
35+ use crate :: { PjUri , MAX_CONTENT_LENGTH } ;
3436
3537/// A builder to construct the properties of a `Sender`.
3638#[ derive( Clone ) ]
@@ -277,19 +279,25 @@ impl V1Context {
277279 self ,
278280 response : & mut impl std:: io:: Read ,
279281 ) -> Result < Psbt , ResponseError > {
280- let mut res_str = String :: new ( ) ;
281- response. read_to_string ( & mut res_str) . map_err ( InternalValidationError :: Io ) ?;
282- let proposal = Psbt :: from_str ( & res_str) . map_err ( |_| ResponseError :: parse ( & res_str) ) ?;
282+ let mut buf_reader = BufReader :: with_capacity ( MAX_CONTENT_LENGTH + 1 , response) ;
283+ let buffer = buf_reader. fill_buf ( ) . map_err ( InternalValidationError :: Io ) ?;
284+
285+ if buffer. len ( ) > MAX_CONTENT_LENGTH {
286+ return Err ( ResponseError :: from ( InternalValidationError :: ContentTooLarge ) ) ;
287+ }
288+
289+ let res_str = std:: str:: from_utf8 ( buffer) . map_err ( |_| InternalValidationError :: Parse ) ?;
290+ let proposal = Psbt :: from_str ( res_str) . map_err ( |_| ResponseError :: parse ( res_str) ) ?;
283291 self . psbt_context . process_proposal ( proposal) . map_err ( Into :: into)
284292 }
285293}
286294
287295#[ cfg( test) ]
288296mod test {
289297 use bitcoin:: FeeRate ;
290- use payjoin_test_utils:: { BoxError , PARSED_ORIGINAL_PSBT } ;
298+ use payjoin_test_utils:: { BoxError , INVALID_PSBT , PARSED_ORIGINAL_PSBT , PAYJOIN_PROPOSAL } ;
291299
292- use super :: SenderBuilder ;
300+ use super :: * ;
293301 use crate :: error_codes:: ErrorCode ;
294302 use crate :: send:: error:: { ResponseError , WellKnownError } ;
295303 use crate :: send:: test:: create_psbt_context;
@@ -345,4 +353,55 @@ mod test {
345353 _ => panic ! ( "Expected unrecognized JSON error" ) ,
346354 }
347355 }
356+
357+ #[ test]
358+ fn process_response_valid ( ) {
359+ let mut cursor = std:: io:: Cursor :: new ( PAYJOIN_PROPOSAL . as_bytes ( ) ) ;
360+
361+ let ctx = create_v1_context ( ) ;
362+ let response = ctx. process_response ( & mut cursor) ;
363+ assert ! ( response. is_ok( ) )
364+ }
365+
366+ #[ test]
367+ fn process_response_invalid_psbt ( ) {
368+ let mut cursor = std:: io:: Cursor :: new ( INVALID_PSBT . as_bytes ( ) ) ;
369+
370+ let ctx = create_v1_context ( ) ;
371+ let response = ctx. process_response ( & mut cursor) ;
372+ match response {
373+ Ok ( _) => panic ! ( "Invalid PSBT should have caused an error" ) ,
374+ Err ( error) => match error {
375+ ResponseError :: Validation ( e) => {
376+ assert_eq ! (
377+ e. to_string( ) ,
378+ ValidationError :: from( InternalValidationError :: Parse ) . to_string( )
379+ ) ;
380+ }
381+ _ => panic ! ( "Unexpected error type" ) ,
382+ } ,
383+ }
384+ }
385+
386+ #[ test]
387+ fn process_response_invalid_utf8 ( ) {
388+ // In UTF-8, 0xF0 represents the start of a 4-byte sequence, so 0xF0 by itself is invalid
389+ let invalid_utf8 = [ 0xF0 ] ;
390+ let mut cursor = std:: io:: Cursor :: new ( invalid_utf8) ;
391+
392+ let ctx = create_v1_context ( ) ;
393+ let response = ctx. process_response ( & mut cursor) ;
394+ match response {
395+ Ok ( _) => panic ! ( "Invalid UTF-8 should have caused an error" ) ,
396+ Err ( error) => match error {
397+ ResponseError :: Validation ( e) => {
398+ assert_eq ! (
399+ e. to_string( ) ,
400+ ValidationError :: from( InternalValidationError :: Parse ) . to_string( )
401+ ) ;
402+ }
403+ _ => panic ! ( "Unexpected error type" ) ,
404+ } ,
405+ }
406+ }
348407}
0 commit comments