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,14 +293,20 @@ 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 :: send:: error:: { ResponseError , WellKnownError } ;
290298 use crate :: send:: test:: create_psbt_context;
291299 use crate :: { Uri , UriExt } ;
292300
293301 const PJ_URI : & str =
294302 "bitcoin:2N47mmrWXsNBvQR6k78hWJoTji57zXwNcU7?amount=0.02&pjos=0&pj=HTTPS://EXAMPLE.COM/" ;
295303
304+ /// From the BIP-174 test vector
305+ const INVALID_PSBT : & str = "AgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAA==" ;
306+
307+ /// From the BIP-78 test vector
308+ const PJ_PROPOSAL_PSBT : & str = "cHNidP8BAJwCAAAAAo8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////jye60aAl3JgZdaIERvjkeh72VYZuTGH/ps2I4l0IO4MBAAAAAP7///8CJpW4BQAAAAAXqRQd6EnwadJ0FQ46/q6NcutaawlEMIcACT0AAAAAABepFHdAltvPSGdDwi9DR+m0af6+i2d6h9MAAAAAAQEgqBvXBQAAAAAXqRTeTh6QYcpZE1sDWtXm1HmQRUNU0IcAAQEggIQeAAAAAAAXqRTI8sv5ymFHLIjkZNRrNXSEXZHY1YcBBxcWABRfgGZV5ZJMkgTC1RvlOU9L+e2iEAEIawJHMEQCIGe7e0DfJaVPRYEKWxddL2Pr0G37BoKz0lyNa02O2/tWAiB7ZVgBoF4s8MHocYWWmo4Q1cyV2wl7MX0azlqa8NBENAEhAmXWPPW0G3yE3HajBOb7gO7iKzHSmZ0o0w0iONowcV+tAAAA" ;
309+
296310 fn create_v1_context ( ) -> super :: V1Context {
297311 let psbt_context = create_psbt_context ( ) . expect ( "failed to create context" ) ;
298312 super :: V1Context { psbt_context }
@@ -337,4 +351,55 @@ mod test {
337351 _ => panic ! ( "Expected unrecognized JSON error" ) ,
338352 }
339353 }
354+
355+ #[ test]
356+ fn process_response_valid ( ) {
357+ let mut cursor = std:: io:: Cursor :: new ( PJ_PROPOSAL_PSBT . as_bytes ( ) ) ;
358+
359+ let ctx = create_v1_context ( ) ;
360+ let response = ctx. process_response ( & mut cursor) ;
361+ assert ! ( response. is_ok( ) )
362+ }
363+
364+ #[ test]
365+ fn process_response_invalid_psbt ( ) {
366+ let mut cursor = std:: io:: Cursor :: new ( INVALID_PSBT . as_bytes ( ) ) ;
367+
368+ let ctx = create_v1_context ( ) ;
369+ let response = ctx. process_response ( & mut cursor) ;
370+ match response {
371+ Ok ( _) => panic ! ( "Invalid PSBT should have caused an error" ) ,
372+ Err ( error) => match error {
373+ ResponseError :: Validation ( e) => {
374+ assert_eq ! (
375+ e. to_string( ) ,
376+ ValidationError :: from( InternalValidationError :: Parse ) . to_string( )
377+ ) ;
378+ }
379+ _ => panic ! ( "Unexpected error type" ) ,
380+ } ,
381+ }
382+ }
383+
384+ #[ test]
385+ fn process_response_invalid_utf8 ( ) {
386+ // In UTF-8, 0xF0 represents the start of a 4-byte sequence, so 0xF0 by itself is invalid
387+ let invalid_utf8 = [ 0xF0 ] ;
388+ let mut cursor = std:: io:: Cursor :: new ( invalid_utf8) ;
389+
390+ let ctx = create_v1_context ( ) ;
391+ let response = ctx. process_response ( & mut cursor) ;
392+ match response {
393+ Ok ( _) => panic ! ( "Invalid UTF-8 should have caused an error" ) ,
394+ Err ( error) => match error {
395+ ResponseError :: Validation ( e) => {
396+ assert_eq ! (
397+ e. to_string( ) ,
398+ ValidationError :: from( InternalValidationError :: Parse ) . to_string( )
399+ ) ;
400+ }
401+ _ => panic ! ( "Unexpected error type" ) ,
402+ } ,
403+ }
404+ }
340405}
0 commit comments