@@ -5,7 +5,7 @@ use bitcoin::consensus::encode::Decodable;
55use bitcoin:: consensus:: Encodable ;
66use url:: Url ;
77
8- use super :: error:: { ParseOhttpKeysParamError , ParseReceiverPubkeyParamError } ;
8+ use super :: error:: { ParseExpParamError , ParseOhttpKeysParamError , ParseReceiverPubkeyParamError } ;
99use crate :: hpke:: HpkePublicKey ;
1010use crate :: ohttp:: OhttpKeys ;
1111
@@ -15,7 +15,7 @@ pub(crate) trait UrlExt {
1515 fn set_receiver_pubkey ( & mut self , exp : HpkePublicKey ) ;
1616 fn ohttp ( & self ) -> Result < OhttpKeys , ParseOhttpKeysParamError > ;
1717 fn set_ohttp ( & mut self , ohttp : OhttpKeys ) ;
18- fn exp ( & self ) -> Option < std:: time:: SystemTime > ;
18+ fn exp ( & self ) -> Result < std:: time:: SystemTime , ParseExpParamError > ;
1919 fn set_exp ( & mut self , exp : std:: time:: SystemTime ) ;
2020}
2121
@@ -60,22 +60,24 @@ impl UrlExt for Url {
6060 fn set_ohttp ( & mut self , ohttp : OhttpKeys ) { set_param ( self , "OH1" , & ohttp. to_string ( ) ) }
6161
6262 /// Retrieve the exp parameter from the URL fragment
63- fn exp ( & self ) -> Option < std:: time:: SystemTime > {
64- get_param ( self , "EX1" , | value| {
65- let ( hrp , bytes ) = crate :: bech32 :: nochecksum :: decode ( value ) . ok ( ) ?;
63+ fn exp ( & self ) -> Result < std:: time:: SystemTime , ParseExpParamError > {
64+ let value =
65+ get_param ( self , "EX1" , |v| Some ( v . to_owned ( ) ) ) . ok_or ( ParseExpParamError :: MissingExp ) ?;
6666
67- let ex_hrp: Hrp = Hrp :: parse ( "EX" ) . unwrap ( ) ;
68- if hrp != ex_hrp {
69- return None ;
70- }
67+ let ( hrp, bytes) =
68+ crate :: bech32:: nochecksum:: decode ( & value) . map_err ( ParseExpParamError :: DecodeBech32 ) ?;
69+
70+ let ex_hrp: Hrp = Hrp :: parse ( "EX" ) . unwrap ( ) ;
71+ if hrp != ex_hrp {
72+ return Err ( ParseExpParamError :: InvalidHrp ( hrp) ) ;
73+ }
7174
72- let mut cursor = & bytes[ ..] ;
73- u32:: consensus_decode ( & mut cursor)
74- . map ( |timestamp| {
75- std:: time:: UNIX_EPOCH + std:: time:: Duration :: from_secs ( timestamp as u64 )
76- } )
77- . ok ( )
78- } )
75+ let mut cursor = & bytes[ ..] ;
76+ u32:: consensus_decode ( & mut cursor)
77+ . map ( |timestamp| {
78+ std:: time:: UNIX_EPOCH + std:: time:: Duration :: from_secs ( timestamp as u64 )
79+ } )
80+ . map_err ( |e| ParseExpParamError :: InvalidExp ( e) )
7981 }
8082
8183 /// Set the exp parameter in the URL fragment
@@ -173,7 +175,26 @@ mod tests {
173175 url. set_exp ( exp_time) ;
174176 assert_eq ! ( url. fragment( ) , Some ( "EX1C4UC6ES" ) ) ;
175177
176- assert_eq ! ( url. exp( ) , Some ( exp_time) ) ;
178+ assert_eq ! ( url. exp( ) . unwrap( ) , exp_time) ;
179+ }
180+
181+ #[ test]
182+ fn test_errors_when_parsing_exp ( ) {
183+ let missing_exp_url = Url :: parse ( "http://example.com" ) . unwrap ( ) ;
184+ assert ! ( matches!( missing_exp_url. exp( ) , Err ( ParseExpParamError :: MissingExp ) ) ) ;
185+
186+ let invalid_bech32_exp_url =
187+ Url :: parse ( "http://example.com?pj=https://test-payjoin-url#EX1invalid_bech_32" )
188+ . unwrap ( ) ;
189+ assert ! ( matches!( invalid_bech32_exp_url. exp( ) , Err ( ParseExpParamError :: DecodeBech32 ( _) ) ) ) ;
190+
191+ let invalid_hrp_exp_url =
192+ Url :: parse ( "http://example.com?pj=https://test-payjoin-url#EX1010" ) . unwrap ( ) ;
193+ assert ! ( matches!( invalid_hrp_exp_url. exp( ) , Err ( ParseExpParamError :: InvalidHrp ( _) ) ) ) ;
194+
195+ let invalid_timestamp_exp_url =
196+ Url :: parse ( "http://example.com?pj=https://test-payjoin-url#EX10" ) . unwrap ( ) ;
197+ assert ! ( matches!( invalid_timestamp_exp_url. exp( ) , Err ( ParseExpParamError :: InvalidExp ( _) ) ) )
177198 }
178199
179200 #[ test]
0 commit comments