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#[ derive( Clone ) ]
3638pub struct SenderBuilder < ' a > {
@@ -273,9 +275,15 @@ impl V1Context {
273275 self ,
274276 response : & mut impl std:: io:: Read ,
275277 ) -> Result < Psbt , ResponseError > {
276- let mut res_str = String :: new ( ) ;
277- response. read_to_string ( & mut res_str) . map_err ( InternalValidationError :: Io ) ?;
278- let proposal = Psbt :: from_str ( & res_str) . map_err ( |_| ResponseError :: parse ( & res_str) ) ?;
278+ let mut buf_reader = BufReader :: with_capacity ( MAX_CONTENT_LENGTH + 1 , response) ;
279+ let buffer = buf_reader. fill_buf ( ) . map_err ( InternalValidationError :: Io ) ?;
280+
281+ if buffer. len ( ) > MAX_CONTENT_LENGTH {
282+ return Err ( ResponseError :: from ( InternalValidationError :: ContentTooLarge ) ) ;
283+ }
284+
285+ let res_str = std:: str:: from_utf8 ( buffer) . map_err ( |_| InternalValidationError :: Parse ) ?;
286+ let proposal = Psbt :: from_str ( res_str) . map_err ( |_| ResponseError :: parse ( res_str) ) ?;
279287 self . psbt_context . process_proposal ( proposal) . map_err ( Into :: into)
280288 }
281289}
@@ -285,7 +293,7 @@ mod test {
285293 use bitcoin:: FeeRate ;
286294 use payjoin_test_utils:: { BoxError , PARSED_ORIGINAL_PSBT } ;
287295
288- use super :: SenderBuilder ;
296+ use super :: * ;
289297 use crate :: error_codes:: ErrorCode ;
290298 use crate :: send:: error:: { ResponseError , WellKnownError } ;
291299 use crate :: send:: test:: create_psbt_context;
@@ -294,6 +302,12 @@ mod test {
294302 const PJ_URI : & str =
295303 "bitcoin:2N47mmrWXsNBvQR6k78hWJoTji57zXwNcU7?amount=0.02&pjos=0&pj=HTTPS://EXAMPLE.COM/" ;
296304
305+ /// From the BIP-174 test vector
306+ const INVALID_PSBT : & str = "AgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAA==" ;
307+
308+ /// From the BIP-78 test vector
309+ const PJ_PROPOSAL_PSBT : & str = "cHNidP8BAJwCAAAAAo8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////jye60aAl3JgZdaIERvjkeh72VYZuTGH/ps2I4l0IO4MBAAAAAP7///8CJpW4BQAAAAAXqRQd6EnwadJ0FQ46/q6NcutaawlEMIcACT0AAAAAABepFHdAltvPSGdDwi9DR+m0af6+i2d6h9MAAAAAAQEgqBvXBQAAAAAXqRTeTh6QYcpZE1sDWtXm1HmQRUNU0IcAAQEggIQeAAAAAAAXqRTI8sv5ymFHLIjkZNRrNXSEXZHY1YcBBxcWABRfgGZV5ZJMkgTC1RvlOU9L+e2iEAEIawJHMEQCIGe7e0DfJaVPRYEKWxddL2Pr0G37BoKz0lyNa02O2/tWAiB7ZVgBoF4s8MHocYWWmo4Q1cyV2wl7MX0azlqa8NBENAEhAmXWPPW0G3yE3HajBOb7gO7iKzHSmZ0o0w0iONowcV+tAAAA" ;
310+
297311 fn create_v1_context ( ) -> super :: V1Context {
298312 let psbt_context = create_psbt_context ( ) . expect ( "failed to create context" ) ;
299313 super :: V1Context { psbt_context }
@@ -341,4 +355,55 @@ mod test {
341355 _ => panic ! ( "Expected unrecognized JSON error" ) ,
342356 }
343357 }
358+
359+ #[ test]
360+ fn process_response_valid ( ) {
361+ let mut cursor = std:: io:: Cursor :: new ( PJ_PROPOSAL_PSBT . as_bytes ( ) ) ;
362+
363+ let ctx = create_v1_context ( ) ;
364+ let response = ctx. process_response ( & mut cursor) ;
365+ assert ! ( response. is_ok( ) )
366+ }
367+
368+ #[ test]
369+ fn process_response_invalid_psbt ( ) {
370+ let mut cursor = std:: io:: Cursor :: new ( INVALID_PSBT . as_bytes ( ) ) ;
371+
372+ let ctx = create_v1_context ( ) ;
373+ let response = ctx. process_response ( & mut cursor) ;
374+ match response {
375+ Ok ( _) => panic ! ( "Invalid PSBT should have caused an error" ) ,
376+ Err ( error) => match error {
377+ ResponseError :: Validation ( e) => {
378+ assert_eq ! (
379+ e. to_string( ) ,
380+ ValidationError :: from( InternalValidationError :: Parse ) . to_string( )
381+ ) ;
382+ }
383+ _ => panic ! ( "Unexpected error type" ) ,
384+ } ,
385+ }
386+ }
387+
388+ #[ test]
389+ fn process_response_invalid_utf8 ( ) {
390+ // In UTF-8, 0xF0 represents the start of a 4-byte sequence, so 0xF0 by itself is invalid
391+ let invalid_utf8 = [ 0xF0 ] ;
392+ let mut cursor = std:: io:: Cursor :: new ( invalid_utf8) ;
393+
394+ let ctx = create_v1_context ( ) ;
395+ let response = ctx. process_response ( & mut cursor) ;
396+ match response {
397+ Ok ( _) => panic ! ( "Invalid UTF-8 should have caused an error" ) ,
398+ Err ( error) => match error {
399+ ResponseError :: Validation ( e) => {
400+ assert_eq ! (
401+ e. to_string( ) ,
402+ ValidationError :: from( InternalValidationError :: Parse ) . to_string( )
403+ ) ;
404+ }
405+ _ => panic ! ( "Unexpected error type" ) ,
406+ } ,
407+ }
408+ }
344409}
0 commit comments