Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Cargo-minimal.lock
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16fc24bc615b9fd63148f59b218ea58a444b55762f8845da910e23aca686398b"
dependencies = [
"thiserror 1.0.63",
"url",
]

[[package]]
Expand Down Expand Up @@ -2782,6 +2781,7 @@ dependencies = [
"bitcoin-hpke",
"bitcoin-ohttp",
"bitcoin_uri",
"form_urlencoded",
"http",
"once_cell",
"payjoin-test-utils",
Expand All @@ -2791,7 +2791,6 @@ dependencies = [
"serde_json",
"tokio",
"tracing",
"url",
"web-time",
]

Expand All @@ -2805,6 +2804,7 @@ dependencies = [
"clap 4.5.46",
"config",
"dirs",
"form_urlencoded",
"http-body-util",
"hyper",
"hyper-util",
Expand All @@ -2822,7 +2822,6 @@ dependencies = [
"tokio-rustls",
"tracing",
"tracing-subscriber",
"url",
]

[[package]]
Expand Down
5 changes: 2 additions & 3 deletions Cargo-recent.lock
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16fc24bc615b9fd63148f59b218ea58a444b55762f8845da910e23aca686398b"
dependencies = [
"thiserror 1.0.63",
"url",
]

[[package]]
Expand Down Expand Up @@ -2782,6 +2781,7 @@ dependencies = [
"bitcoin-hpke",
"bitcoin-ohttp",
"bitcoin_uri",
"form_urlencoded",
"http",
"once_cell",
"payjoin-test-utils",
Expand All @@ -2791,7 +2791,6 @@ dependencies = [
"serde_json",
"tokio",
"tracing",
"url",
"web-time",
]

Expand All @@ -2805,6 +2804,7 @@ dependencies = [
"clap 4.5.46",
"config",
"dirs",
"form_urlencoded",
"http-body-util",
"hyper",
"hyper-util",
Expand All @@ -2822,7 +2822,6 @@ dependencies = [
"tokio-rustls",
"tracing",
"tracing-subscriber",
"url",
]

[[package]]
Expand Down
6 changes: 6 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ name = "uri_deserialize_pjuri"
path = "fuzz_targets/uri/deserialize_pjuri.rs"
doc = false
bench = false

[[bin]]
name = "url_decode_url"
path = "fuzz_targets/url/decode_url.rs"
doc = false
bench = false
58 changes: 58 additions & 0 deletions fuzz/fuzz_targets/url/decode_url.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#![no_main]

use std::str;

use libfuzzer_sys::fuzz_target;
// Adjust this path to wherever your Url module lives in your crate.
use payjoin::Url;

fn do_test(data: &[u8]) {
let Ok(s) = str::from_utf8(data) else { return };

let Ok(mut url) = Url::parse(s) else { return };

let _ = url.scheme();
let _ = url.has_host();
let _ = url.domain();
let _ = url.host_str();
let _ = url.port();
let _ = url.path();
let _ = url.query();
let _ = url.fragment();
let _ = url.as_str();
let _ = url.to_string();
if let Some(segs) = url.path_segments() {
let _ = segs.collect::<Vec<_>>();
}

let raw = url.as_str().to_owned();
if let Ok(reparsed) = Url::parse(&raw) {
assert_eq!(
reparsed.as_str(),
raw,
"round-trip mismatch: first={raw:?} second={:?}",
reparsed.as_str()
);
}

url.set_port(Some(8080));
url.set_port(None);
url.set_fragment(Some("fuzz"));
url.set_fragment(None);
url.set_query(Some("k=v"));
url.set_query(None);
url.query_pairs_mut().append_pair("fuzz_key", "fuzz_val");

if let Some(mut segs) = url.path_segments_mut() {
segs.push("fuzz_segment");
}

let _ = url.join("relative/path");
let _ = url.join("/absolute/path");
let _ = url.join("../dotdot");
let _ = url.join("https://other.example.com/new");
}

fuzz_target!(|data| {
do_test(data);
});
2 changes: 1 addition & 1 deletion payjoin-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ bitcoind-async-client = "0.8.1"
clap = { version = "4.5.45", features = ["derive"] }
config = "0.15.14"
dirs = "6.0.0"
form_urlencoded = { version = "1.2.2" }
http-body-util = { version = "0.1.3", optional = true }
hyper = { version = "1.6.0", features = ["http1", "server"], optional = true }
hyper-util = { version = "0.1.16", optional = true }
Expand All @@ -51,7 +52,6 @@ tokio-rustls = { version = "0.26.2", features = [
], default-features = false, optional = true }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
url = { version = "2.5.4", features = ["serde"] }

[dev-dependencies]
nix = { version = "0.30.1", features = ["aio", "process", "signal"] }
Expand Down
13 changes: 9 additions & 4 deletions payjoin-cli/src/app/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ use anyhow::Result;
use config::builder::DefaultState;
use config::{ConfigError, File, FileFormat};
use payjoin::bitcoin::FeeRate;
use payjoin::Version;
use payjoin::{Url, Version};
use serde::Deserialize;
use url::Url;

use crate::cli::{Cli, Commands};
use crate::db;
Expand Down Expand Up @@ -317,10 +316,16 @@ fn handle_subcommands(config: Builder, cli: &Cli) -> Result<Builder, ConfigError
#[cfg(feature = "v1")]
let config = config
.set_override_option("v1.port", port.map(|p| p.to_string()))?
.set_override_option("v1.pj_endpoint", pj_endpoint.as_ref().map(|s| s.as_str()))?;
.set_override_option(
"v1.pj_endpoint",
pj_endpoint.clone().map(|s| s.to_string()),
)?;
#[cfg(feature = "v2")]
let config = config
.set_override_option("v2.pj_directory", pj_directory.as_ref().map(|s| s.as_str()))?
.set_override_option(
"v2.pj_directory",
pj_directory.clone().map(|s| s.to_string()),
)?
.set_override_option(
"v2.ohttp_keys",
ohttp_keys.as_ref().map(|s| s.to_string_lossy().into_owned()),
Expand Down
6 changes: 2 additions & 4 deletions payjoin-cli/src/app/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,7 @@ impl App {
// If --port 0 is specified, a free port is chosen, so we need to set it
// on the endpoint which must not have a port.
if port == 0 {
endpoint
.set_port(Some(listener.local_addr()?.port()))
.expect("setting port must succeed");
endpoint.set_port(Some(listener.local_addr()?.port()));
}

let pj_uri_string = self.construct_payjoin_uri(amount, endpoint.as_str())?;
Expand Down Expand Up @@ -235,7 +233,7 @@ impl App {
let query_string = req.uri().query().unwrap_or("");
tracing::trace!("{:?}, {query_string:?}", req.method());
let query_params: HashMap<_, _> =
url::form_urlencoded::parse(query_string.as_bytes()).into_owned().collect();
form_urlencoded::parse(query_string.as_bytes()).into_owned().collect();
let amount = query_params.get("amount").map(|amt| {
Amount::from_btc(amt.parse().expect("Failed to parse amount")).unwrap()
});
Expand Down
2 changes: 1 addition & 1 deletion payjoin-cli/src/app/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ impl App {
async fn unwrap_relay_or_else_fetch(
&self,
directory: Option<impl payjoin::IntoUrl>,
) -> Result<url::Url> {
) -> Result<payjoin::Url> {
let directory = directory.map(|url| url.into_url()).transpose()?;
let selected_relay =
self.relay_manager.lock().expect("Lock should not be poisoned").get_selected_relay();
Expand Down
19 changes: 10 additions & 9 deletions payjoin-cli/src/app/v2/ohttp.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
use std::sync::{Arc, Mutex};

use anyhow::{anyhow, Result};
use payjoin::Url;

use super::Config;

#[derive(Debug, Clone)]
pub struct RelayManager {
selected_relay: Option<url::Url>,
failed_relays: Vec<url::Url>,
selected_relay: Option<Url>,
failed_relays: Vec<Url>,
}

impl RelayManager {
pub fn new() -> Self { RelayManager { selected_relay: None, failed_relays: Vec::new() } }

pub fn set_selected_relay(&mut self, relay: url::Url) { self.selected_relay = Some(relay); }
pub fn set_selected_relay(&mut self, relay: Url) { self.selected_relay = Some(relay); }

pub fn get_selected_relay(&self) -> Option<url::Url> { self.selected_relay.clone() }
pub fn get_selected_relay(&self) -> Option<Url> { self.selected_relay.clone() }

pub fn add_failed_relay(&mut self, relay: url::Url) { self.failed_relays.push(relay); }
pub fn add_failed_relay(&mut self, relay: Url) { self.failed_relays.push(relay); }

pub fn get_failed_relays(&self) -> Vec<url::Url> { self.failed_relays.clone() }
pub fn get_failed_relays(&self) -> Vec<Url> { self.failed_relays.clone() }
}

pub(crate) struct ValidatedOhttpKeys {
pub(crate) ohttp_keys: payjoin::OhttpKeys,
pub(crate) relay_url: url::Url,
pub(crate) relay_url: Url,
}

pub(crate) async fn unwrap_ohttp_keys_or_else_fetch(
config: &Config,
directory: Option<url::Url>,
directory: Option<Url>,
relay_manager: Arc<Mutex<RelayManager>>,
) -> Result<ValidatedOhttpKeys> {
if let Some(ohttp_keys) = config.v2()?.ohttp_keys.clone() {
Expand All @@ -48,7 +49,7 @@ pub(crate) async fn unwrap_ohttp_keys_or_else_fetch(

async fn fetch_ohttp_keys(
config: &Config,
directory: Option<url::Url>,
directory: Option<Url>,
relay_manager: Arc<Mutex<RelayManager>>,
) -> Result<ValidatedOhttpKeys> {
use payjoin::bitcoin::secp256k1::rand::prelude::SliceRandom;
Expand Down
14 changes: 9 additions & 5 deletions payjoin-cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::path::PathBuf;
use clap::{value_parser, Parser, Subcommand};
use payjoin::bitcoin::amount::ParseAmountError;
use payjoin::bitcoin::{Amount, FeeRate};
use payjoin::Url;
use serde::Deserialize;
use url::Url;

#[derive(Debug, Clone, Deserialize, Parser)]
pub struct Flags {
Expand Down Expand Up @@ -114,13 +114,13 @@ pub enum Commands {

#[cfg(feature = "v1")]
/// The `pj=` endpoint to receive the payjoin request
#[arg(long = "pj-endpoint", value_parser = value_parser!(Url))]
pj_endpoint: Option<Url>,
#[arg(long = "pj-endpoint", value_parser = parse_boxed_url)]
pj_endpoint: Option<Box<Url>>,

#[cfg(feature = "v2")]
/// The directory to store payjoin requests
#[arg(long = "pj-directory", value_parser = value_parser!(Url))]
pj_directory: Option<Url>,
#[arg(long = "pj-directory", value_parser = parse_boxed_url)]
pj_directory: Option<Box<Url>>,

#[cfg(feature = "v2")]
/// The path to the ohttp keys file
Expand All @@ -144,3 +144,7 @@ pub fn parse_fee_rate_in_sat_per_vb(s: &str) -> Result<FeeRate, std::num::ParseF
let fee_rate_sat_per_kwu = fee_rate_sat_per_vb * 250.0_f32;
Ok(FeeRate::from_sat_per_kwu(fee_rate_sat_per_kwu.ceil() as u64))
}

fn parse_boxed_url(s: &str) -> Result<Box<Url>, String> {
s.parse::<Url>().map(Box::new).map_err(|e| e.to_string())
}
2 changes: 1 addition & 1 deletion payjoin-directory/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ acme = ["tokio-rustls-acme"]

[dependencies]
anyhow = "1.0.99"
bhttp = { version = "0.6.1", features = ["http"] }
bhttp = { version = "0.6.1" }
bitcoin = { version = "0.32.7", features = ["base64", "rand-std"] }
clap = { version = "4.5.45", features = ["derive", "env"] }
config = "0.15.14"
Expand Down
8 changes: 3 additions & 5 deletions payjoin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ default = ["v2"]
#[doc = "Core features for payjoin state machines"]
_core = [
"bitcoin/rand-std",
"dep:http",
"http",
"serde_json",
"url/serde",
"form_urlencoded",
"bitcoin_uri",
"serde",
"bitcoin/serde",
Expand All @@ -43,6 +43,7 @@ _test-utils = []
bhttp = { version = "0.6.1", optional = true }
bitcoin = { version = "0.32.7", features = ["base64"] }
bitcoin_uri = { version = "0.1.0", optional = true }
form_urlencoded = { version = "1.2.2", optional = true }
hpke = { package = "bitcoin-hpke", version = "0.13.0", optional = true }
http = { version = "1.3.1", optional = true }
ohttp = { package = "bitcoin-ohttp", version = "0.6.0", optional = true }
Expand All @@ -53,9 +54,6 @@ rustls = { version = "0.23.31", optional = true, default-features = false, featu
serde = { version = "1.0.219", default-features = false, optional = true }
serde_json = { version = "1.0.142", optional = true }
tracing = "0.1.41"
url = { version = "2.5.4", optional = true, default-features = false, features = [
"serde",
] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
web-time = "1.1.0"
Expand Down
8 changes: 4 additions & 4 deletions payjoin/src/core/into_url.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use url::{ParseError, Url};
use crate::core::{Url, UrlParseError};

#[derive(Debug, PartialEq, Eq)]
pub enum Error {
BadScheme,
ParseError(ParseError),
ParseError(UrlParseError),
}

impl std::fmt::Display for Error {
Expand All @@ -19,8 +19,8 @@ impl std::fmt::Display for Error {

impl std::error::Error for Error {}

impl From<ParseError> for Error {
fn from(err: ParseError) -> Error { Error::ParseError(err) }
impl From<UrlParseError> for Error {
fn from(err: UrlParseError) -> Error { Error::ParseError(err) }
}

type Result<T> = core::result::Result<T, Error>;
Expand Down
Loading
Loading