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

payjoin / rust-payjoin / 17087788010

20 Aug 2025 03:34AM UTC coverage: 86.492% (-0.02%) from 86.514%
17087788010

Pull #808

github

web-flow
Merge dcd8e2856 into 2a6dbbad6
Pull Request #808: Enforce content-length validation on sender and size limits on payjoin-cli

29 of 35 new or added lines in 3 files covered. (82.86%)

1 existing line in 1 file now uncovered.

7850 of 9076 relevant lines covered (86.49%)

513.83 hits per line

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

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

3
use anyhow::{anyhow, Result};
4
use futures::{Stream, StreamExt};
5
use hyper::body::Bytes;
6
use payjoin::bitcoin::psbt::Psbt;
7
use payjoin::bitcoin::{Amount, FeeRate};
8
use payjoin::{bitcoin, PjUri};
9
use tokio::signal;
10
use tokio::sync::watch;
11

12
pub mod config;
13
pub mod rpc;
14
pub mod wallet;
15
use crate::app::config::Config;
16
use crate::app::wallet::BitcoindWallet;
17

18
#[cfg(feature = "v1")]
19
pub(crate) mod v1;
20
#[cfg(feature = "v2")]
21
pub(crate) mod v2;
22

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

34
    fn create_original_psbt(&self, uri: &PjUri, fee_rate: FeeRate) -> Result<Psbt> {
3✔
35
        let amount = uri.amount.ok_or_else(|| anyhow!("please specify the amount in the Uri"))?;
3✔
36

37
        // wallet_create_funded_psbt requires a HashMap<address: String, Amount>
38
        let mut outputs = HashMap::with_capacity(1);
3✔
39
        outputs.insert(uri.address.to_string(), amount);
3✔
40

41
        self.wallet().create_psbt(outputs, fee_rate, true)
3✔
42
    }
3✔
43

44
    fn process_pj_response(&self, psbt: Psbt) -> Result<bitcoin::Txid> {
3✔
45
        log::debug!("Proposed psbt: {psbt:#?}");
3✔
46

47
        let signed = self.wallet().process_psbt(&psbt)?;
3✔
48
        let tx = self.wallet().finalize_psbt(&signed)?;
3✔
49

50
        let txid = self.wallet().broadcast_tx(&tx)?;
3✔
51

52
        println!("Payjoin sent. TXID: {txid}");
3✔
53
        Ok(txid)
3✔
54
    }
3✔
55
}
56

57
#[cfg(feature = "_danger-local-https")]
58
fn http_agent(config: &Config) -> Result<reqwest::Client> {
9✔
59
    Ok(http_agent_builder(config.root_certificate.as_ref())?.build()?)
9✔
60
}
9✔
61

62
#[cfg(not(feature = "_danger-local-https"))]
63
fn http_agent(_config: &Config) -> Result<reqwest::Client> { Ok(reqwest::Client::new()) }
64

65
#[cfg(feature = "_danger-local-https")]
66
fn http_agent_builder(
9✔
67
    root_cert_path: Option<&std::path::PathBuf>,
9✔
68
) -> Result<reqwest::ClientBuilder> {
9✔
69
    let mut builder = reqwest::ClientBuilder::new().use_rustls_tls();
9✔
70

71
    if let Some(root_cert_path) = root_cert_path {
9✔
72
        let cert_der = std::fs::read(root_cert_path)?;
9✔
73
        builder =
9✔
74
            builder.add_root_certificate(reqwest::tls::Certificate::from_der(cert_der.as_slice())?)
9✔
75
    }
×
76
    Ok(builder)
9✔
77
}
9✔
78

79
async fn handle_interrupt(tx: watch::Sender<()>) {
10✔
80
    if let Err(e) = signal::ctrl_c().await {
10✔
81
        eprintln!("Error setting up Ctrl-C handler: {e}");
×
82
    }
8✔
83
    let _ = tx.send(());
8✔
84
}
8✔
85

86
#[allow(dead_code)]
87
pub async fn read_limited_body<S, E>(mut stream: S, expected_len: usize) -> Result<Vec<u8>>
4✔
88
where
4✔
89
    S: Stream<Item = Result<Bytes, E>> + Unpin,
4✔
90
    E: std::error::Error + Send + Sync + 'static,
4✔
91
{
4✔
92
    let mut body = Vec::with_capacity(expected_len);
4✔
93

94
    while let Some(chunk) = stream.next().await {
8✔
95
        let chunk = chunk.map_err(|e| anyhow!("Error reading body chunk: {}", e))?;
4✔
96
        if body.len() + chunk.len() > expected_len {
4✔
NEW
97
            return Err(anyhow!("Body exceeds expected size of {expected_len} bytes"));
×
98
        }
4✔
99
        body.extend_from_slice(&chunk);
4✔
100
    }
101

102
    Ok(body)
4✔
103
}
4✔
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