Skip to content

Commit

Permalink
Specify escaped characters more conservatively
Browse files Browse the repository at this point in the history
'#'' is not in the set qchar indirectly defined in BIP 21, and therefore
should be escaped.

[BIP 21](https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki#abnf-grammar):

>     labelparam     = "label=" *qchar
>     messageparam   = "message=" *qchar
>     otherparam     = qchar *qchar [ "=" *qchar ]
...
> Here, "qchar" corresponds to valid characters of an RFC 3986 URI query
> component, excluding the "=" and "&" characters, which this BIP takes
> as separators.

[RFC 3986 § 3.4](https://www.rfc-editor.org/rfc/rfc3986#section-3.4):

> The query component is indicated by the first question mark ("?")
> character and terminated by a number sign ("#") character or by the
> end of the URI.

[RFC 3986 Appendix A](https://www.rfc-editor.org/rfc/rfc3986#appendix-A):

>     pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
>     query         = *( pchar / "/" / "?" )
...
>     pct-encoded   = "%" HEXDIG HEXDIG
>     unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
...
>     sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
>                   / "*" / "+" / "," / ";" / "="
  • Loading branch information
nothingmuch committed Dec 2, 2024
1 parent eb32638 commit 0585806
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 3 deletions.
17 changes: 15 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,11 +432,24 @@ mod tests {

#[test]
fn label_with_rfc3986_param_separator() {
let input = "bitcoin:1andreas3batLhQa2FawWjeyjCqyBzypd?label=foo%26bar%20%3D%20baz%3F";
let input = "bitcoin:1andreas3batLhQa2FawWjeyjCqyBzypd?label=foo%26bar%20%3D%20baz/blah?;:@";
let uri = input.parse::<Uri<'_, _>>().unwrap().require_network(bitcoin::Network::Bitcoin).unwrap();
let label: Cow<'_, str> = uri.label.clone().unwrap().try_into().unwrap();
assert_eq!(uri.address.to_string(), "1andreas3batLhQa2FawWjeyjCqyBzypd");
assert_eq!(label, "foo&bar = baz?");
assert_eq!(label, "foo&bar = baz/blah?;:@");
assert!(uri.amount.is_none());
assert!(uri.message.is_none());

assert_eq!(uri.to_string(), input);
}

#[test]
fn label_with_rfc3986_fragment_separator() {
let input = "bitcoin:1andreas3batLhQa2FawWjeyjCqyBzypd?label=foo%23bar";
let uri = input.parse::<Uri<'_, _>>().unwrap().require_network(bitcoin::Network::Bitcoin).unwrap();
let label: Cow<'_, str> = uri.label.clone().unwrap().try_into().unwrap();
assert_eq!(uri.address.to_string(), "1andreas3batLhQa2FawWjeyjCqyBzypd");
assert_eq!(label, "foo#bar");
assert!(uri.amount.is_none());
assert!(uri.message.is_none());

Expand Down
56 changes: 55 additions & 1 deletion src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,61 @@ impl<'a, W: fmt::Write> fmt::Write for EqSignChecker<'a, W> {
}

/// Set of characters that will be percent-encoded
const ASCII_SET: percent_encoding_rfc3986::AsciiSet = percent_encoding_rfc3986::CONTROLS.add(b'&').add(b'?').add(b' ').add(b'=');
///
/// This contains anything not in `query` (i.e. ``gen-delim` from the quoted
/// definitions`) as per RFC 3986, as well as '&' and '=' as per BIP 21.
///
/// [BIP 21](https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki#abnf-grammar):
///
/// > ```text
/// > labelparam = "label=" *qchar
/// > messageparam = "message=" *qchar
/// > otherparam = qchar *qchar [ "=" *qchar ]
/// > ```
/// ...
/// > Here, "qchar" corresponds to valid characters of an RFC 3986 URI > query
/// component, excluding the "=" and "&" characters, which this BIP > takes as
/// separators.
///
/// [RFC 3986 Appendix A](https://www.rfc-editor.org/rfc/rfc3986#appendix-A):
///
/// > ```text
/// > pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
/// > query = *( pchar / "/" / "?" )
/// > ```
/// ...
/// > ```text
/// > pct-encoded = "%" HEXDIG HEXDIG
/// > unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
/// > ```
/// ...
/// > ```text
/// > sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/// > / "*" / "+" / "," / ";" / "="
/// > ```
const ASCII_SET: percent_encoding_rfc3986::AsciiSet = percent_encoding_rfc3986::NON_ALPHANUMERIC
// allow non-alphanumeric characters from `unreserved`
.remove(b'-')
.remove(b'.')
.remove(b'_')
.remove(b'~')
// allow non-alphanumeric characters from `sub-delims` excluding bip-21
// separators ("&", and "=")
.remove(b'!')
.remove(b'$')
.remove(b'\'')
.remove(b'(')
.remove(b')')
.remove(b'*')
.remove(b'+')
.remove(b',')
.remove(b';')
// allow pchar extra chars
.remove(b':')
.remove(b'@')
// allow query extra chars
.remove(b'/')
.remove(b'?');

/// Percent-encodes writes.
struct WriterEncoder<W: fmt::Write>(W);
Expand Down

0 comments on commit 0585806

Please sign in to comment.