Create new lowercase error catch for uri endpoint fragments#558
Create new lowercase error catch for uri endpoint fragments#558DanGould merged 1 commit intopayjoin:masterfrom
Conversation
Pull Request Test Coverage Report for Build 13724695073Details
💛 - Coveralls |
payjoin/src/uri/error.rs
Outdated
| write!(f, "Endpoint scheme is not secure (https or onion)") | ||
| } | ||
| LowercaseEndpointFragment => | ||
| write!(f, "Some or all of the endpoint fragement is lowercase"), |
There was a problem hiding this comment.
small typo:
| write!(f, "Some or all of the endpoint fragement is lowercase"), | |
| write!(f, "Some or all of the endpoint fragment is lowercase"), |
payjoin/src/uri/url_ext.rs
Outdated
| let uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=0.01\ | ||
| &pjos=0&pj=HTTPS://EXAMPLE.COM/\ | ||
| %23oh1qypm5jxyns754y4r45qwe336qfx6zr8dqgvqculvztv20tfveydmfqc"; | ||
| assert!(Uri::try_from(uri).is_err(), "{:?}", Uri::try_from(uri).err()); |
There was a problem hiding this comment.
Nit: I ussually advocate to check for exact error variant you are expecting. This case being LowercaseEndpointFragment
There was a problem hiding this comment.
Since this is an internal error, do you have a good method of checking the error without changing it to be a public trait? Or is it ok to string compare on the err message?
There was a problem hiding this comment.
Looks like this comment is relevant to this as well.
#542 (comment)
There was a problem hiding this comment.
I ussually #[derive(PartialEq)] on the error enum and that works fine. Sometimes other error variants can't derive PartialEq easily and that becomes a headache. If it blows up the scope too much then last resort error string checking can work fine
There was a problem hiding this comment.
Ok, I'll try that now
a480aba to
9ff4690
Compare
payjoin/src/uri/mod.rs
Outdated
| if let Some(fragment) = url.fragment() { | ||
| if fragment.chars().any(|c| c.is_lowercase()) { | ||
| return Err(InternalPjParseError::LowercaseEndpointFragment.into()); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Hol' up. This is v2 specific and probably doesn't belong here
|
Upon review, and to stop an error variant from being introduced that would only be used on v2 codepaths, I'm wondering if the It might make sense to introduce I'm more convince that this belongs in url_ext but not necessarily convinced this also touches the fragment parameter extraction API. |
|
I think something like this would make more sense. Whether or not we need to preserve content in BadEndpoint is up for debate. could we just point to the BIP Url spec for details of a proper endpoint format in the error message? // uri/mod.rs
// ...
#[cfg(not(feature = "v2"))]
let url = Url::parse(&endpoint).map_err(|_| InternalPjParseError::BadEndpoint)?;
#[cfg(feature = "v2")]
let url = url_ext::parse_with_fragment(&endpoint).map_err(|_| InternalPjParseError::BadEndpoint)?;error handling omitted for brevity and my ability to respond quickly // uri/url_ext.rs
// ...
/// Parse and validate the fragment parameters from `&pj=` URI parameter URLs
pub fn parse_with_fragment(input: &str) -> Result<Url, ()> {
let url = Url::parse(input).map_err(|_| ())?;
if let Some(fragment) = url.fragment() {
if fragment.chars().any(|c| c.is_lowercase()) {
return Err(());
}
};
Ok(url)
} |
e344ea8 to
a68ebfc
Compare
DanGould
left a comment
There was a problem hiding this comment.
Getting a lot closer. I think there's still some abstraction leakiness and I do my best in review to guide you through how a significant portion of this design can be elided to fix it.
payjoin/src/uri/mod.rs
Outdated
| use std::borrow::Cow; | ||
|
|
||
| use bitcoin::address::NetworkChecked; | ||
| use error::BadEndpointError; |
There was a problem hiding this comment.
Just a note This'd let you use it without a a prefix elsewhere in the module.
| use error::BadEndpointError; | |
| pub(crate) use error::BadEndpointError; |
But I don't actually think you want to do this. I think if you follow my later suggestions, BadEndpointError can stay referenced in the url_ext and error modules only since you don't need to do manual coercion in mod if you just propagate it to an InternalPjParseError::BadEndpoint variant.
payjoin/src/uri/error.rs
Outdated
| #[derive(Debug)] | ||
| pub enum BadEndpointError { | ||
| UrlParse(ParseError), | ||
| FragmentParse(ParseEndpointFragmentError), |
There was a problem hiding this comment.
Rather than import at the top level, I think we want to feature gate this line (and the line in the Display impl) and fully qualify the type, if we even need multiple variants in the FragmentParse variant.
| FragmentParse(ParseEndpointFragmentError), | |
| #[cfg(feature = "v2")] | |
| FragmentParse(super::url_ext::ParseEndpointFragmentError), |
payjoin/src/uri/url_ext.rs
Outdated
| #[cfg(feature = "v2")] | ||
| #[derive(Debug)] | ||
| pub enum ParseEndpointFragmentError { | ||
| LowercaseEndpointFragment, | ||
| } | ||
|
|
||
| #[cfg(feature = "v2")] | ||
| impl std::fmt::Display for ParseEndpointFragmentError { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| use ParseEndpointFragmentError::*; | ||
|
|
||
| match &self { | ||
| LowercaseEndpointFragment => | ||
| write!(f, "Some or all of the endpoint fragment is lowercase"), | ||
| } | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Since this only has one variant, I'm not convinced we need to complicate it. We can probably just use the one BadEndpoint::FragmentParse variant or rename it BadEndpoint::LowercaseEndpointFragment and simplify by deleting this.
payjoin/src/uri/mod.rs
Outdated
| let url = url_ext::parse_with_fragment(&endpoint).map_err(|_| { | ||
| InternalPjParseError::BadEndpoint(BadEndpointError::FragmentParse( | ||
| ParseEndpointFragmentError::LowercaseEndpointFragment, | ||
| )) |
There was a problem hiding this comment.
The BadEndpointError is being discarded, but it conveys useful information that needs to be propagated. parse_with_fragment can fail either with LowercaseEndpointFragment OR with a UrlParse error here. The following would let that propagate:
| let url = url_ext::parse_with_fragment(&endpoint).map_err(|_| { | |
| InternalPjParseError::BadEndpoint(BadEndpointError::FragmentParse( | |
| ParseEndpointFragmentError::LowercaseEndpointFragment, | |
| )) | |
| let url = url_ext::parse_with_fragment(&endpoint).map_err(InternalPjParseError::BadEndpoint) |
9eb8cda to
3a0f17b
Compare
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| match self { | ||
| BadEndpointError::UrlParse(err) => write!(f, "Invalid URL: {:?}", err), | ||
| BadEndpointError::LowercaseFragment => |
There was a problem hiding this comment.
If it's feature gated above we need a gate here in order to build v1 with --no-default-features -- which may be otherwise broken because we're only testing checks with --all-features in CI now, whoops!
| BadEndpointError::LowercaseFragment => | |
| #[cfg(feature = "v2")] | |
| BadEndpointError::LowercaseFragment => |
There was a problem hiding this comment.
don't think this was resolved
There was a problem hiding this comment.
That's weird its not refreshing here,
rust-payjoin/payjoin/src/uri/error.rs
Lines 27 to 28 in b8e9768
There was a problem hiding this comment.
I think I just spammed 2 rebases too quickly for github to make sense of it
payjoin/src/uri/error.rs
Outdated
| MissingEndpoint => write!(f, "Missing payjoin endpoint"), | ||
| NotUtf8 => write!(f, "Endpoint is not valid UTF-8"), | ||
| BadEndpoint => write!(f, "Endpoint is not valid"), | ||
| BadEndpoint(err) => write!(f, "Endpoint is not valid: {:?}", err), |
There was a problem hiding this comment.
nit: prefer e to err here by custom elsewhere in the repo
3a0f17b to
ead292f
Compare
The pj= param in the pjuri contains multiple different parts but there exist some gotchas when trying to use them as the url path can be parsed even when lowercase but the fragment object cannot. This means that it is possible to copy or display a pjuri that will not work if it is simply converted to lowercase. This pr does not create any method to force the fragment to be uppercase as I think it really should be on the implementation to make sure that casing is correct, so I just created the case error catch.
ead292f to
b8e9768
Compare
I noticed when reviewing #558 that some intended feature combinations were broken. In particular tests didn't catch that a `v2` feature gate on an error variant would break if the `impl Display` for that variant wasn't also gated. This led me to notice that `cargo check --no-default-features --features v1` was also failing because of unavailability of `thread_rng`. The first commit addresses the build failure in independent `v1` build, the second creates checks for `payjoin`. close #566
Closes #555
The pj= param in the pjuri contains multiple different parts but there exist some gotchas when trying to use them as the url path can be parsed even when lowercase but the fragment object cannot. This means that it is possible to copy or display a pjuri that will not work if it is simply converted to lowercase.
This pr does not create any method to force the fragment to be uppercase as I think it really should be on the implementation to make sure that casing is correct, so I just created the case error catch.