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,9 +279,15 @@ 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}
@@ -289,7 +297,7 @@ mod test {
289297 use bitcoin:: FeeRate ;
290298 use payjoin_test_utils:: { BoxError , PARSED_ORIGINAL_PSBT } ;
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;
@@ -298,6 +306,12 @@ mod test {
298306 const PJ_URI : & str =
299307 "bitcoin:2N47mmrWXsNBvQR6k78hWJoTji57zXwNcU7?amount=0.02&pjos=0&pj=HTTPS://EXAMPLE.COM/" ;
300308
309+ /// From the BIP-174 test vector
310+ const INVALID_PSBT : & str = "AgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAA==" ;
311+
312+ /// From the BIP-78 test vector
313+ const PJ_PROPOSAL_PSBT : & str = "cHNidP8BAJwCAAAAAo8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////jye60aAl3JgZdaIERvjkeh72VYZuTGH/ps2I4l0IO4MBAAAAAP7///8CJpW4BQAAAAAXqRQd6EnwadJ0FQ46/q6NcutaawlEMIcACT0AAAAAABepFHdAltvPSGdDwi9DR+m0af6+i2d6h9MAAAAAAQEgqBvXBQAAAAAXqRTeTh6QYcpZE1sDWtXm1HmQRUNU0IcAAQEggIQeAAAAAAAXqRTI8sv5ymFHLIjkZNRrNXSEXZHY1YcBBxcWABRfgGZV5ZJMkgTC1RvlOU9L+e2iEAEIawJHMEQCIGe7e0DfJaVPRYEKWxddL2Pr0G37BoKz0lyNa02O2/tWAiB7ZVgBoF4s8MHocYWWmo4Q1cyV2wl7MX0azlqa8NBENAEhAmXWPPW0G3yE3HajBOb7gO7iKzHSmZ0o0w0iONowcV+tAAAA" ;
314+
301315 fn create_v1_context ( ) -> super :: V1Context {
302316 let psbt_context = create_psbt_context ( ) . expect ( "failed to create context" ) ;
303317 super :: V1Context { psbt_context }
@@ -345,4 +359,55 @@ mod test {
345359 _ => panic ! ( "Expected unrecognized JSON error" ) ,
346360 }
347361 }
362+
363+ #[ test]
364+ fn process_response_valid ( ) {
365+ let mut cursor = std:: io:: Cursor :: new ( PJ_PROPOSAL_PSBT . as_bytes ( ) ) ;
366+
367+ let ctx = create_v1_context ( ) ;
368+ let response = ctx. process_response ( & mut cursor) ;
369+ assert ! ( response. is_ok( ) )
370+ }
371+
372+ #[ test]
373+ fn process_response_invalid_psbt ( ) {
374+ let mut cursor = std:: io:: Cursor :: new ( INVALID_PSBT . as_bytes ( ) ) ;
375+
376+ let ctx = create_v1_context ( ) ;
377+ let response = ctx. process_response ( & mut cursor) ;
378+ match response {
379+ Ok ( _) => panic ! ( "Invalid PSBT should have caused an error" ) ,
380+ Err ( error) => match error {
381+ ResponseError :: Validation ( e) => {
382+ assert_eq ! (
383+ e. to_string( ) ,
384+ ValidationError :: from( InternalValidationError :: Parse ) . to_string( )
385+ ) ;
386+ }
387+ _ => panic ! ( "Unexpected error type" ) ,
388+ } ,
389+ }
390+ }
391+
392+ #[ test]
393+ fn process_response_invalid_utf8 ( ) {
394+ // In UTF-8, 0xF0 represents the start of a 4-byte sequence, so 0xF0 by itself is invalid
395+ let invalid_utf8 = [ 0xF0 ] ;
396+ let mut cursor = std:: io:: Cursor :: new ( invalid_utf8) ;
397+
398+ let ctx = create_v1_context ( ) ;
399+ let response = ctx. process_response ( & mut cursor) ;
400+ match response {
401+ Ok ( _) => panic ! ( "Invalid UTF-8 should have caused an error" ) ,
402+ Err ( error) => match error {
403+ ResponseError :: Validation ( e) => {
404+ assert_eq ! (
405+ e. to_string( ) ,
406+ ValidationError :: from( InternalValidationError :: Parse ) . to_string( )
407+ ) ;
408+ }
409+ _ => panic ! ( "Unexpected error type" ) ,
410+ } ,
411+ }
412+ }
348413}
0 commit comments