From 2b5e4f13e556cd97a0677c187523d39557d6857d Mon Sep 17 00:00:00 2001 From: DanGould Date: Thu, 6 Oct 2022 17:36:59 -0400 Subject: [PATCH] Move http server to own module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 志宇 --- src/http.rs | 108 ++++++++++++++++++++++++++++++++++++++ src/main.rs | 147 ++-------------------------------------------------- 2 files changed, 112 insertions(+), 143 deletions(-) create mode 100644 src/http.rs diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 0000000..6ca49be --- /dev/null +++ b/src/http.rs @@ -0,0 +1,108 @@ +use std::net::SocketAddr; + +use bip78::receiver::*; +use hyper::service::{make_service_fn, service_fn}; +use hyper::{Body, Method, Request, Response, Server, StatusCode}; + +use crate::scheduler::{ScheduledPayJoin, Scheduler}; + +#[cfg(not(feature = "test_paths"))] +const STATIC_DIR: &str = "/usr/share/loin/static"; + +#[cfg(feature = "test_paths")] +const STATIC_DIR: &str = "static"; + +/// Serve requests to Schedule and execute PayJoins with given options. +pub async fn serve(sched: Scheduler, bind_addr: SocketAddr) -> Result<(), hyper::Error> { + let new_service = make_service_fn(move |_| { + let sched = sched.clone(); + async move { + let handler = move |req| handle_web_req(sched.clone(), req); + Ok::<_, hyper::Error>(service_fn(handler)) + } + }); + + let server = Server::bind(&bind_addr).serve(new_service); + println!("Listening on: http://{}", bind_addr); + server.await +} + +async fn handle_web_req( + scheduler: Scheduler, + req: Request, +) -> Result, hyper::Error> { + use std::path::Path; + + match (req.method(), req.uri().path()) { + (&Method::GET, "/pj") => { + let index = + std::fs::read(Path::new(STATIC_DIR).join("index.html")).expect("can't open index"); + Ok(Response::new(Body::from(index))) + } + + (&Method::GET, path) if path.starts_with("/pj/static/") => { + let directory_traversal_vulnerable_path = &path[("/pj/static/".len())..]; + let file = + std::fs::read(Path::new(STATIC_DIR).join(directory_traversal_vulnerable_path)) + .expect("can't open static file"); + Ok(Response::new(Body::from(file))) + } + + (&Method::POST, "/pj") => { + dbg!(req.uri().query()); + + let headers = Headers(req.headers().to_owned()); + let query = { + let uri = req.uri(); + if let Some(query) = uri.query() { + Some(&query.to_owned()); + } + None + }; + let body = req.into_body(); + let bytes = hyper::body::to_bytes(body).await?; + dbg!(&bytes); // this is correct by my accounts + let reader = &*bytes; + let original_request = UncheckedProposal::from_request(reader, query, headers).unwrap(); + + let proposal_psbt = scheduler.propose_payjoin(original_request).await.unwrap(); + + Ok(Response::new(Body::from(proposal_psbt))) + } + + (&Method::POST, "/pj/schedule") => { + let bytes = hyper::body::to_bytes(req.into_body()).await?; + let request = + serde_json::from_slice::(&bytes).expect("invalid request"); + + let address = scheduler.schedule_payjoin(&request).await.unwrap(); + let total_amount = request.total_amount(); + + // TODO: Don't hardcode pj endpoint + // * Optional cli flag or ENV for pj endpoint (in the case of port forwarding), otherwise + // we should determine the bip21 string using `api::ServeOptions` + let uri = format!( + "bitcoin:{}?amount={}&pj=https://localhost:3010/pj", + address, + total_amount.to_string_in(bitcoin::Denomination::Bitcoin) + ); + let mut response = Response::new(Body::from(uri)); + response + .headers_mut() + .insert(hyper::header::CONTENT_TYPE, "text/plain".parse().unwrap()); + Ok(response) + } + + // Return the 404 Not Found for other routes. + _ => { + let mut not_found = Response::default(); + *not_found.status_mut() = StatusCode::NOT_FOUND; + Ok(not_found) + } + } +} + +pub(crate) struct Headers(hyper::HeaderMap); +impl bip78::receiver::Headers for Headers { + fn get_header(&self, key: &str) -> Option<&str> { self.0.get(key)?.to_str().ok() } +} diff --git a/src/main.rs b/src/main.rs index 1510166..ee8c0af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,12 @@ mod args; +mod http; mod lnd; pub mod scheduler; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; - -use bip78::receiver::*; -use bitcoin::{Address, Script, TxOut}; -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Method, Request, Response, Server, StatusCode}; -use scheduler::ScheduledPayJoin; +use scheduler::Scheduler; use crate::args::parse_args; use crate::lnd::*; -use crate::scheduler::Scheduler; #[macro_use] extern crate serde_derive; @@ -21,46 +14,6 @@ extern crate configure_me; configure_me::include_config!(); -#[cfg(not(feature = "test_paths"))] -const STATIC_DIR: &str = "/usr/share/loin/static"; - -#[cfg(feature = "test_paths")] -const STATIC_DIR: &str = "static"; - -#[derive(Clone, Default)] -struct PayJoins(Arc>>); - -impl PayJoins { - fn insert(&self, address: &Address, payjoin: ScheduledPayJoin) -> Result<(), ()> { - use std::collections::hash_map::Entry; - - match self.0.lock().expect("payjoins mutex poisoned").entry(address.script_pubkey()) { - Entry::Vacant(place) => { - place.insert(payjoin); - Ok(()) - } - Entry::Occupied(_) => Err(()), - } - } - - fn find<'a>(&self, txouts: &'a mut [TxOut]) -> Option<(&'a mut TxOut, ScheduledPayJoin)> { - let mut payjoins = self.0.lock().expect("payjoins mutex poisoned"); - txouts - .iter_mut() - .find_map(|txout| payjoins.remove(&txout.script_pubkey).map(|payjoin| (txout, payjoin))) - } -} - -#[derive(Clone)] -struct Handler { - client: LndClient, - payjoins: PayJoins, -} - -impl Handler { - async fn new(client: LndClient) -> Self { Self { client, payjoins: Default::default() } } -} - #[tokio::main] async fn main() -> Result<(), Box> { let (config, args) = @@ -83,100 +36,8 @@ async fn main() -> Result<(), Box> { ); } - let addr = ([127, 0, 0, 1], config.bind_port).into(); - - let service = make_service_fn(move |_| { - let sched = scheduler.clone(); // TODO Review this double clone. Wataf is going on here, are we referencing the same scheduler or do we need a container? - async move { - Ok::<_, hyper::Error>(service_fn(move |request| handle_web_req(sched.clone(), request))) - } - }); - - let server = Server::bind(&addr).serve(service); - - println!("Listening on http://{}", addr); - - server.await?; + let bind_addr = ([127, 0, 0, 1], config.bind_port).into(); + http::serve(scheduler, bind_addr).await?; Ok(()) } - -pub(crate) struct Headers(hyper::HeaderMap); -impl bip78::receiver::Headers for Headers { - fn get_header(&self, key: &str) -> Option<&str> { self.0.get(key)?.to_str().ok() } -} - -async fn handle_web_req( - scheduler: Scheduler, - req: Request, -) -> Result, hyper::Error> { - use std::path::Path; - - match (req.method(), req.uri().path()) { - (&Method::GET, "/pj") => { - let index = - std::fs::read(Path::new(STATIC_DIR).join("index.html")).expect("can't open index"); - Ok(Response::new(Body::from(index))) - } - - (&Method::GET, path) if path.starts_with("/pj/static/") => { - let directory_traversal_vulnerable_path = &path[("/pj/static/".len())..]; - let file = - std::fs::read(Path::new(STATIC_DIR).join(directory_traversal_vulnerable_path)) - .expect("can't open static file"); - Ok(Response::new(Body::from(file))) - } - - (&Method::POST, "/pj") => { - dbg!(req.uri().query()); - - let headers = Headers(req.headers().to_owned()); - let query = { - let uri = req.uri(); - if let Some(query) = uri.query() { - Some(&query.to_owned()); - } - None - }; - let body = req.into_body(); - let bytes = hyper::body::to_bytes(body).await?; - dbg!(&bytes); // this is correct by my accounts - let reader = &*bytes; - let original_request = UncheckedProposal::from_request(reader, query, headers).unwrap(); - - let proposal_psbt = scheduler.propose_payjoin(original_request).await.unwrap(); - - Ok(Response::new(Body::from(proposal_psbt))) - } - - (&Method::POST, "/pj/schedule") => { - let bytes = hyper::body::to_bytes(req.into_body()).await?; - let request = - serde_json::from_slice::(&bytes).expect("invalid request"); - - let address = scheduler.schedule_payjoin(&request).await.unwrap(); - let total_amount = request.total_amount(); - - // TODO: Don't hardcode pj endpoint - // * Optional cli flag or ENV for pj endpoint (in the case of port forwarding), otherwise - // we should determine the bip21 string using `api::ServeOptions` - let uri = format!( - "bitcoin:{}?amount={}&pj=https://localhost:3010/pj", - address, - total_amount.to_string_in(bitcoin::Denomination::Bitcoin) - ); - let mut response = Response::new(Body::from(uri)); - response - .headers_mut() - .insert(hyper::header::CONTENT_TYPE, "text/plain".parse().unwrap()); - Ok(response) - } - - // Return the 404 Not Found for other routes. - _ => { - let mut not_found = Response::default(); - *not_found.status_mut() = StatusCode::NOT_FOUND; - Ok(not_found) - } - } -}