Add another layer in the URI to make room for future features

main
_ 2020-11-01 22:07:55 -06:00
parent 13117e4237
commit c5ecf4bc88
7 changed files with 76 additions and 58 deletions

View File

@ -26,7 +26,7 @@
{{#if servers}} {{#if servers}}
{{#each servers}} {{#each servers}}
<div> <div>
<a class="entry" href="{{this.path}}/">{{this.name}}</a> <a class="entry" href="{{this.path}}/files/">{{this.name}}</a>
</div> </div>
{{/each}} {{/each}}
{{else}} {{else}}

View File

@ -20,6 +20,7 @@ use hyper::{
use ptth::{ use ptth::{
http_serde::RequestParts, http_serde::RequestParts,
prefix_match,
server::file_server, server::file_server,
}; };
@ -33,16 +34,6 @@ fn status_reply <B: Into <Body>> (status: StatusCode, b: B)
Response::builder ().status (status).body (b.into ()).unwrap () Response::builder ().status (status).body (b.into ()).unwrap ()
} }
fn prefix_match <'a> (hay: &'a str, needle: &str) -> Option <&'a str>
{
if hay.starts_with (needle) {
Some (&hay [needle.len ()..])
}
else {
None
}
}
async fn handle_all (req: Request <Body>, state: Arc <ServerState <'static>>) async fn handle_all (req: Request <Body>, state: Arc <ServerState <'static>>)
-> Result <Response <Body>, Infallible> -> Result <Response <Body>, Infallible>
{ {
@ -61,7 +52,7 @@ async fn handle_all (req: Request <Body>, state: Arc <ServerState <'static>>)
_ => return Ok (status_reply (StatusCode::BAD_REQUEST, "Bad request")), _ => return Ok (status_reply (StatusCode::BAD_REQUEST, "Bad request")),
}; };
let ptth_resp = file_server::serve_all (state.handlebars.clone (), &root, ptth_req).await; let ptth_resp = file_server::serve_all (&state.handlebars, &root, ptth_req.method, &ptth_req.uri, &ptth_req.headers).await;
let mut resp = Response::builder () let mut resp = Response::builder ()
.status (StatusCode::from (ptth_resp.parts.status_code)); .status (StatusCode::from (ptth_resp.parts.status_code));

View File

@ -13,6 +13,17 @@ pub const PTTH_MAGIC_HEADER: &str = "X-PTTH-2LJYXWC4";
pub mod relay; pub mod relay;
pub mod server; pub mod server;
pub fn prefix_match <'a> (hay: &'a str, needle: &str) -> Option <&'a str>
{
if hay.starts_with (needle) {
Some (&hay [needle.len ()..])
}
else {
None
}
}
#[cfg (test)] #[cfg (test)]
mod tests { mod tests {
use std::{ use std::{
@ -89,7 +100,7 @@ mod tests {
assert_eq! (resp, "Relay is up\n"); assert_eq! (resp, "Relay is up\n");
let resp = client.get (&format! ("{}/servers/{}/COPYING", relay_url, server_name)) let resp = client.get (&format! ("{}/servers/{}/files/COPYING", relay_url, server_name))
.send ().await.unwrap ().bytes ().await.unwrap (); .send ().await.unwrap ().bytes ().await.unwrap ();
if blake3::hash (&resp) != blake3::Hash::from ([ if blake3::hash (&resp) != blake3::Hash::from ([

View File

@ -33,6 +33,7 @@ use tokio::{
use crate::{ use crate::{
http_serde, http_serde,
prefix_match,
}; };
/* /*
@ -291,16 +292,6 @@ async fn handle_http_request (
} }
} }
fn prefix_match <'a> (hay: &'a str, needle: &str) -> Option <&'a str>
{
if hay.starts_with (needle) {
Some (&hay [needle.len ()..])
}
else {
None
}
}
async fn handle_all (req: Request <Body>, state: Arc <RelayState>) async fn handle_all (req: Request <Body>, state: Arc <RelayState>)
-> Result <Response <Body>, Infallible> -> Result <Response <Body>, Infallible>
{ {

View File

@ -2,11 +2,11 @@
use std::{ use std::{
cmp::{min, max}, cmp::{min, max},
collections::*,
convert::{Infallible, TryInto}, convert::{Infallible, TryInto},
error::Error, error::Error,
io::SeekFrom, io::SeekFrom,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc,
}; };
use handlebars::Handlebars; use handlebars::Handlebars;
@ -99,7 +99,7 @@ async fn read_dir_entry (entry: DirEntry) -> TemplateDirEntry
use std::borrow::Cow; use std::borrow::Cow;
async fn serve_dir ( async fn serve_dir (
handlebars: Arc <Handlebars <'static>>, handlebars: &Handlebars <'static>,
path: Cow <'_, str>, path: Cow <'_, str>,
mut dir: ReadDir mut dir: ReadDir
) -> http_serde::Response ) -> http_serde::Response
@ -226,34 +226,33 @@ async fn serve_error (
} }
pub async fn serve_all ( pub async fn serve_all (
handlebars: Arc <Handlebars <'static>>, handlebars: &Handlebars <'static>,
root: &Path, root: &Path,
parts: http_serde::RequestParts method: http_serde::Method,
uri: &str,
headers: &HashMap <String, Vec <u8>>,
) )
-> http_serde::Response -> http_serde::Response
{ {
println! ("Client requested {}", parts.uri); println! ("Client requested {}", uri);
let mut range_start = None; let mut range_start = None;
let mut range_end = None; let mut range_end = None;
for (k, v) in parts.headers.iter () { if let Some (v) = headers.get ("range") {
let v = std::str::from_utf8 (v).unwrap (); let v = std::str::from_utf8 (v).unwrap ();
//println! ("{}: {}", k, v);
if k == "range" { let (start, end) = parse_range_header (v);
let (start, end) = parse_range_header (v); range_start = start;
range_start = start; range_end = end;
range_end = end;
}
} }
let should_send_body = matches! (&parts.method, http_serde::Method::Get); let should_send_body = matches! (&method, http_serde::Method::Get);
use percent_encoding::*; use percent_encoding::*;
// TODO: There is totally a dir traversal attack in here somewhere // TODO: There is totally a dir traversal attack in here somewhere
let encoded_path = &parts.uri [1..]; let encoded_path = &uri [1..];
let path_s = percent_decode (encoded_path.as_bytes ()).decode_utf8 ().unwrap (); let path_s = percent_decode (encoded_path.as_bytes ()).decode_utf8 ().unwrap ();
let path = Path::new (&*path_s); let path = Path::new (&*path_s);

View File

@ -15,14 +15,29 @@ use tokio::{
time::delay_for, time::delay_for,
}; };
use crate::http_serde; use crate::{
http_serde,
prefix_match,
};
pub mod file_server; pub mod file_server;
struct ServerState {
opt: Opt,
handlebars: Handlebars <'static>,
client: Client,
}
fn status_reply (c: http_serde::StatusCode, body: &str) -> http_serde::Response
{
let mut r = http_serde::Response::default ();
r.status_code (c)
.body_bytes (body.as_bytes ().to_vec ());
r
}
async fn handle_req_resp <'a> ( async fn handle_req_resp <'a> (
opt: &'a Opt, state: Arc <ServerState>,
handlebars: Arc <Handlebars <'static>>,
client: Arc <Client>,
req_resp: reqwest::Response req_resp: reqwest::Response
) { ) {
//println! ("Step 1"); //println! ("Step 1");
@ -40,17 +55,26 @@ async fn handle_req_resp <'a> (
}; };
for wrapped_req in wrapped_reqs.into_iter () { for wrapped_req in wrapped_reqs.into_iter () {
let handlebars = handlebars.clone (); let state = state.clone ();
let opt = opt.clone ();
let client = client.clone ();
tokio::spawn (async move { tokio::spawn (async move {
let (req_id, parts) = (wrapped_req.id, wrapped_req.req); let (req_id, parts) = (wrapped_req.id, wrapped_req.req);
let response = file_server::serve_all (handlebars, &opt.file_server_root, parts).await; let response = if let Some (uri) = prefix_match (&parts.uri, "/files") {
file_server::serve_all (
&state.handlebars,
&state.opt.file_server_root,
parts.method,
uri,
&parts.headers
).await
}
else {
status_reply (http_serde::StatusCode::NotFound, "404 Not Found")
};
let mut resp_req = client let mut resp_req = state.client
.post (&format! ("{}/7ZSFUKGV_http_response/{}", opt.relay_url, req_id)) .post (&format! ("{}/7ZSFUKGV_http_response/{}", state.opt.relay_url, req_id))
.header (crate::PTTH_MAGIC_HEADER, base64::encode (rmp_serde::to_vec (&response.parts).unwrap ())); .header (crate::PTTH_MAGIC_HEADER, base64::encode (rmp_serde::to_vec (&response.parts).unwrap ()));
if let Some (body) = response.body { if let Some (body) = response.body {
@ -89,12 +113,16 @@ pub async fn main (config_file: ConfigFile, opt: Opt)
let mut headers = reqwest::header::HeaderMap::new (); let mut headers = reqwest::header::HeaderMap::new ();
headers.insert ("X-ApiKey", config_file.api_key.try_into ().unwrap ()); headers.insert ("X-ApiKey", config_file.api_key.try_into ().unwrap ());
// TODO: (FN46S2M2) Combine these Arcs let client = Client::builder ()
let client = Arc::new (Client::builder ()
.default_headers (headers) .default_headers (headers)
.build ().unwrap ()); .build ().unwrap ();
let opt = Arc::new (opt); let handlebars = file_server::load_templates ()?;
let handlebars = Arc::new (file_server::load_templates ()?);
let state = Arc::new (ServerState {
opt,
handlebars,
client,
});
let mut backoff_delay = 0; let mut backoff_delay = 0;
@ -103,7 +131,7 @@ pub async fn main (config_file: ConfigFile, opt: Opt)
delay_for (Duration::from_millis (backoff_delay)).await; delay_for (Duration::from_millis (backoff_delay)).await;
} }
let req_req = client.get (&format! ("{}/7ZSFUKGV_http_listen/{}", opt.relay_url, config_file.name)); let req_req = state.client.get (&format! ("{}/7ZSFUKGV_http_listen/{}", state.opt.relay_url, config_file.name));
let err_backoff_delay = std::cmp::min (30_000, backoff_delay * 2 + 500); let err_backoff_delay = std::cmp::min (30_000, backoff_delay * 2 + 500);
@ -129,12 +157,10 @@ pub async fn main (config_file: ConfigFile, opt: Opt)
// Spawn another task for each request so we can // Spawn another task for each request so we can
// immediately listen for the next connection // immediately listen for the next connection
let client = client.clone (); let state = state.clone ();
let opt = opt.clone ();
let handlebars = handlebars.clone ();
tokio::spawn (async move { tokio::spawn (async move {
handle_req_resp (&opt, handlebars, client, req_resp).await; handle_req_resp (state, req_resp).await;
}); });
} }
} }

View File

@ -1,4 +1,4 @@
- FN46S2M2 Combine Arcs - Server-side hash?
- Log / audit log? - Log / audit log?
- Prevent directory traversal attacks - Prevent directory traversal attacks