1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#![warn(
    missing_debug_implementations,
    missing_docs,
    rust_2018_idioms,
    unreachable_pub
)]

//! `cashweb-payments` is a library providing structures and utilities related to
//! the [`BIP70: Payment Protocol`] and a [`Wallet`] structure to allow receiving
//! payments.
//!
//! [`Wallet`]: wallet::Wallet
//! [`BIP70: Payment Protocol`]: https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki

pub mod wallet;

use bytes::Buf;
use http::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE};
use prost::{DecodeError, Message};
use thiserror::Error;

#[allow(missing_docs)]
pub mod bip70 {
    //! This module contains structures related to the [`BIP70: Payment Protocol`]
    //!
    //! [`BIP70: Payment Protocol`]: https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki

    include!(concat!(env!("OUT_DIR"), "/bip70.rs"));
}

use bip70::Payment;

/// Error associated with payment preprocessing.
#[derive(Debug, Error)]
pub enum PreprocessingError {
    /// Missing the `application/bitcoincash-paymentack` header.
    #[error("missing accept header")]
    MissingAcceptHeader,
    /// Missing the `application/bitcoincash-payment` header.
    #[error("invalid content-type")]
    MissingContentTypeHeader,
    /// Failed to decode the `Payment` protobuf.
    #[error("payment decoding failure: {0}")]
    PaymentDecode(DecodeError),
}

/// Validates and parses the BIP70 payment.
pub async fn preprocess_payment<B: Buf>(
    headers: HeaderMap,
    body: B,
) -> Result<Payment, PreprocessingError> {
    // Bitcoin Cash Headers
    let bch_content_type_value = HeaderValue::from_static("application/bitcoincash-payment");
    let bch_accept_value = HeaderValue::from_static("application/bitcoincash-paymentack");

    // Check for content-type header
    if !headers
        .get_all(CONTENT_TYPE)
        .iter()
        .any(|header_val| header_val == bch_content_type_value)
    {
        return Err(PreprocessingError::MissingContentTypeHeader);
    }

    // Check for accept header
    if !headers
        .get_all(ACCEPT)
        .iter()
        .any(|header_val| header_val == bch_accept_value)
    {
        return Err(PreprocessingError::MissingAcceptHeader);
    }

    // Read and parse payment proto
    let payment = bip70::Payment::decode(body).map_err(PreprocessingError::PaymentDecode)?;

    Ok(payment)
}