• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

payjoin / rust-payjoin / 17460319220

04 Sep 2025 10:02AM UTC coverage: 86.033% (+0.09%) from 85.94%
17460319220

Pull #808

github

web-flow
Merge 6a7857972 into e8829f334
Pull Request #808: Enforce content-length validation on sender and size limits on payjoin-cli

72 of 81 new or added lines in 5 files covered. (88.89%)

2 existing lines in 2 files now uncovered.

8248 of 9587 relevant lines covered (86.03%)

487.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.23
/payjoin-cli/src/app/mod.rs
1
use std::collections::HashMap;
2

3
#[cfg(feature = "v1")]
4
use anyhow::anyhow;
5
use anyhow::Result;
6
#[cfg(feature = "v1")]
7
use futures::{Stream, StreamExt};
8
#[cfg(feature = "v1")]
9
use hyper::body::Bytes;
10
use payjoin::bitcoin::psbt::Psbt;
11
use payjoin::bitcoin::{self, Address, Amount, FeeRate};
12
use tokio::signal;
13
use tokio::sync::watch;
14

15
pub mod config;
16
pub mod rpc;
17
pub mod wallet;
18
use crate::app::config::Config;
19
use crate::app::wallet::BitcoindWallet;
20

21
#[cfg(feature = "v1")]
22
pub(crate) mod v1;
23
#[cfg(feature = "v2")]
24
pub(crate) mod v2;
25

26
#[async_trait::async_trait]
27
pub trait App: Send + Sync {
28
    async fn new(config: Config) -> Result<Self>
29
    where
30
        Self: Sized;
31
    fn wallet(&self) -> BitcoindWallet;
32
    async fn send_payjoin(&self, bip21: &str, fee_rate: FeeRate) -> Result<()>;
33
    async fn receive_payjoin(&self, amount: Amount) -> Result<()>;
34
    #[cfg(feature = "v2")]
35
    async fn resume_payjoins(&self) -> Result<()>;
36

37
    fn create_original_psbt(
4✔
38
        &self,
4✔
39
        address: &Address,
4✔
40
        amount: Amount,
4✔
41
        fee_rate: FeeRate,
4✔
42
    ) -> Result<Psbt> {
4✔
43
        // wallet_create_funded_psbt requires a HashMap<address: String, Amount>
44
        let mut outputs = HashMap::with_capacity(1);
4✔
45
        outputs.insert(address.to_string(), amount);
4✔
46

47
        self.wallet().create_psbt(outputs, fee_rate, true)
4✔
48
    }
4✔
49

50
    fn process_pj_response(&self, psbt: Psbt) -> Result<bitcoin::Txid> {
4✔
51
        tracing::debug!("Proposed psbt: {psbt:#?}");
4✔
52

53
        let signed = self.wallet().process_psbt(&psbt)?;
4✔
54
        let tx = self.wallet().finalize_psbt(&signed)?;
4✔
55

56
        let txid = self.wallet().broadcast_tx(&tx)?;
4✔
57

58
        println!("Payjoin sent. TXID: {txid}");
4✔
59
        Ok(txid)
4✔
60
    }
4✔
61
}
62

63
#[cfg(feature = "_manual-tls")]
64
fn http_agent(config: &Config) -> Result<reqwest::Client> {
10✔
65
    Ok(http_agent_builder(config.root_certificate.as_ref())?.build()?)
10✔
66
}
10✔
67

68
#[cfg(not(feature = "_manual-tls"))]
69
fn http_agent(_config: &Config) -> Result<reqwest::Client> { Ok(reqwest::Client::new()) }
70

71
#[cfg(feature = "_manual-tls")]
72
fn http_agent_builder(
10✔
73
    root_cert_path: Option<&std::path::PathBuf>,
10✔
74
) -> Result<reqwest::ClientBuilder> {
10✔
75
    let mut builder = reqwest::ClientBuilder::new().use_rustls_tls();
10✔
76

77
    if let Some(root_cert_path) = root_cert_path {
10✔
78
        let cert_der = std::fs::read(root_cert_path)?;
10✔
79
        builder =
10✔
80
            builder.add_root_certificate(reqwest::tls::Certificate::from_der(cert_der.as_slice())?)
10✔
81
    }
×
82
    Ok(builder)
10✔
83
}
10✔
84

85
async fn handle_interrupt(tx: watch::Sender<()>) {
12✔
86
    if let Err(e) = signal::ctrl_c().await {
12✔
87
        eprintln!("Error setting up Ctrl-C handler: {e}");
×
88
    }
9✔
89
    let _ = tx.send(());
9✔
90
}
9✔
91

92
#[cfg(feature = "v1")]
93
pub async fn read_limited_body<S, E>(mut stream: S, expected_len: usize) -> Result<Vec<u8>>
5✔
94
where
5✔
95
    S: Stream<Item = Result<Bytes, E>> + Unpin,
5✔
96
    E: std::error::Error + Send + Sync + 'static,
5✔
97
{
5✔
98
    let mut body = Vec::with_capacity(expected_len);
5✔
99

100
    while let Some(chunk) = stream.next().await {
10✔
101
        let chunk = chunk.map_err(|e| anyhow!("Error reading body chunk: {}", e))?;
5✔
102
        if body.len() + chunk.len() > expected_len {
5✔
NEW
103
            return Err(anyhow!("Body exceeds expected size of {expected_len} bytes"));
×
104
        }
5✔
105
        body.extend_from_slice(&chunk);
5✔
106
    }
107

108
    Ok(body)
5✔
109
}
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc