From 22d109f414792115fc251f4588c5b5fce6a71e06 Mon Sep 17 00:00:00 2001 From: Shing Him Ng Date: Sun, 1 Jun 2025 12:30:23 -0500 Subject: [PATCH 1/2] Return error for duplicate concrete params --- src/de.rs | 12 ++++++++++++ src/lib.rs | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/de.rs b/src/de.rs index c5f3c48..6b4afd7 100644 --- a/src/de.rs +++ b/src/de.rs @@ -60,14 +60,23 @@ impl<'a, T: DeserializeParams<'a>> Uri<'a, bitcoin::address::NetworkUnchecked, T let value = ¶m[(pos + 1)..]; match key { "amount" => { + if amount.is_some() { + return Err(Error::Uri(UriError(UriErrorInner::DuplicateParameter(key.to_owned())))); + } let parsed_amount = bitcoin::Amount::from_str_in(value, Denomination::Bitcoin).map_err(Error::uri)?; amount = Some(parsed_amount); }, "label" => { + if label.is_some() { + return Err(Error::Uri(UriError(UriErrorInner::DuplicateParameter(key.to_owned())))); + } let label_decoder = Param::decode(value).map_err(Error::percent_decode_static("label"))?; label = Some(label_decoder); }, "message" => { + if message.is_some() { + return Err(Error::Uri(UriError(UriErrorInner::DuplicateParameter(key.to_owned())))); + } let message_decoder = Param::decode(value).map_err(Error::percent_decode_static("message"))?; message = Some(message_decoder); }, @@ -240,6 +249,7 @@ enum UriErrorInner { InvalidScheme, Address(AddressError), Amount(ParseAmountError), + DuplicateParameter(String), UnknownRequiredParameter(String), PercentDecode { parameter: Cow<'static, str>, @@ -267,6 +277,7 @@ impl fmt::Display for UriError { UriErrorInner::InvalidScheme => write!(f, "the URI has invalid scheme"), UriErrorInner::Address(_) => write!(f, "the address is invalid"), UriErrorInner::Amount(_) => write!(f, "the amount is invalid"), + UriErrorInner::DuplicateParameter(parameter) => write!(f, "the URI contains a duplicate parameter '{}'", parameter), UriErrorInner::UnknownRequiredParameter(parameter) => write!(f, "the URI contains unknown required parameter '{}'", parameter), #[cfg(feature = "std")] UriErrorInner::PercentDecode { parameter, error: _ } => write!(f, "can not percent-decode parameter {}", parameter), @@ -286,6 +297,7 @@ impl std::error::Error for UriError { UriErrorInner::InvalidScheme => None, UriErrorInner::Address(error) => Some(error), UriErrorInner::Amount(error) => Some(error), + UriErrorInner::DuplicateParameter(_) => None, UriErrorInner::UnknownRequiredParameter(_) => None, UriErrorInner::PercentDecode { parameter: _, error } => Some(error), UriErrorInner::MissingEquals(_) => None, diff --git a/src/lib.rs b/src/lib.rs index c47a0ec..a63c3b7 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -488,4 +488,19 @@ mod tests { let uri = input.parse::>(); assert!(uri.is_err()); } + + #[test] + fn duplicate_params() { + let duplicate_label = "bitcoin:1andreas3batLhQa2FawWjeyjCqyBzypd?label=foo&label=bar"; + let uri = duplicate_label.parse::>(); + assert!(uri.is_err()); + + let duplicate_amount = "bitcoin:1andreas3batLhQa2FawWjeyjCqyBzypd?amount=123&amount=456"; + let uri = duplicate_amount.parse::>(); + assert!(uri.is_err()); + + let duplicate_message = "bitcoin:1andreas3batLhQa2FawWjeyjCqyBzypd?message=foo&message=bar"; + let uri = duplicate_message.parse::>(); + assert!(uri.is_err()); + } } From 1c4a1e5850497969adca848b1e131e30a9799927 Mon Sep 17 00:00:00 2001 From: Shing Him Ng Date: Thu, 5 Jun 2025 09:32:47 -0500 Subject: [PATCH 2/2] Run linter --- src/de.rs | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/de.rs b/src/de.rs index 6b4afd7..9a44b77 100644 --- a/src/de.rs +++ b/src/de.rs @@ -25,7 +25,7 @@ impl<'a, T: DeserializeParams<'a>> Uri<'a, bitcoin::address::NetworkUnchecked, T return Err(Error::Uri(UriError(UriErrorInner::TooShort))); } - if !string.get(..SCHEME.len()).map_or(false, |s| s.eq_ignore_ascii_case(SCHEME)) { + if !string.get(..SCHEME.len()).is_some_and(|s| s.eq_ignore_ascii_case(SCHEME)) { return Err(Error::Uri(UriError(UriErrorInner::InvalidScheme))); } diff --git a/src/lib.rs b/src/lib.rs index a63c3b7..7a37b7e 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ //! //! * Rust-idiomatic: uses strong types, standard traits and other things //! * Compliant: implements all requirements of BIP21, including protections to not forget about -//! `req-`. (But see features.) +//! `req-`. (But see features.) //! * Flexible: enables parsing/serializing additional arguments not defined by BIP21 //! * Performant: uses zero-copy deserialization and lazy evaluation wherever possible. //!