Skip to content

Commit 1fe5f9e

Browse files
committed
Fix crash when slicing strings by slicing at non-char boundaries
TIL you cannot index into `str`s safely as they panic if the index is not on a `char` bounary. Better, `split_at_checked` was only very recently added so apparently I wasn't the only one who didn't know this.
1 parent 5540126 commit 1fe5f9e

File tree

1 file changed

+9
-14
lines changed

1 file changed

+9
-14
lines changed

src/lib.rs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -524,13 +524,10 @@ fn parse_resolved_instructions(
524524
instructions: &str, network: Network, supports_proof_of_payment_callbacks: bool,
525525
hrn: Option<HumanReadableName>, hrn_proof: Option<Vec<u8>>,
526526
) -> Result<PaymentInstructions, ParseError> {
527-
const BTC_URI_PFX_LEN: usize = "bitcoin:".len();
528-
const LN_URI_PFX_LEN: usize = "lightning:".len();
527+
let (uri_proto, uri_suffix) = split_once(instructions, ':');
529528

530-
if instructions.len() >= BTC_URI_PFX_LEN
531-
&& instructions[..BTC_URI_PFX_LEN].eq_ignore_ascii_case("bitcoin:")
532-
{
533-
let (body, params) = split_once(&instructions[BTC_URI_PFX_LEN..], '?');
529+
if uri_proto.eq_ignore_ascii_case("bitcoin") {
530+
let (body, params) = split_once(uri_suffix.unwrap_or(""), '?');
534531
let mut methods = Vec::new();
535532
let mut description = None;
536533
let mut pop_callback = None;
@@ -546,8 +543,8 @@ fn parse_resolved_instructions(
546543

547544
let mut parse_segwit = |pfx| {
548545
if let Some(address_string) = v {
549-
if address_string.len() < 3
550-
|| !address_string[..3].eq_ignore_ascii_case(pfx)
546+
if address_string.is_char_boundary(3)
547+
&& !address_string[..3].eq_ignore_ascii_case(pfx)
551548
{
552549
// `bc`/`tb` key-values must only include bech32/bech32m strings with
553550
// HRP "bc"/"tb" (i.e. mainnet/testnet Segwit addresses).
@@ -651,7 +648,7 @@ fn parse_resolved_instructions(
651648
let err = "Missing value for a Proof of Payment instruction in a BIP 321 bitcoin: URI";
652649
return Err(ParseError::InvalidInstructions(err));
653650
}
654-
} else if k.len() >= 4 && k[..4].eq_ignore_ascii_case("req-") {
651+
} else if k.is_char_boundary(4) && k[..4].eq_ignore_ascii_case("req-") {
655652
return Err(ParseError::UnknownRequiredParameter);
656653
}
657654
}
@@ -796,13 +793,11 @@ fn parse_resolved_instructions(
796793
}))
797794
}
798795
}
799-
} else if instructions.len() >= LN_URI_PFX_LEN
800-
&& instructions[..LN_URI_PFX_LEN].eq_ignore_ascii_case("lightning:")
801-
{
796+
} else if uri_proto.eq_ignore_ascii_case("lightning") {
802797
// Though there is no specification, lightning: URIs generally only include BOLT 11
803798
// invoices.
804-
let invoice = Bolt11Invoice::from_str(&instructions[LN_URI_PFX_LEN..])
805-
.map_err(ParseError::InvalidBolt11)?;
799+
let invoice =
800+
Bolt11Invoice::from_str(uri_suffix.unwrap_or("")).map_err(ParseError::InvalidBolt11)?;
806801
let ln_amt = invoice.amount_milli_satoshis().map(Amount::from_milli_sats);
807802
let (description, onchain_amt, method_iter) = instructions_from_bolt11(invoice, network)?;
808803
let inner = PaymentInstructionsImpl {

0 commit comments

Comments
 (0)