🐛 Fix the backlinks from servers up to the relay

main
_ 2020-11-08 15:53:09 +00:00
parent 345fa64ad0
commit c5691d9d05
10 changed files with 283 additions and 128 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@
/ptth_server.toml
/ptth_relay.toml
/target
/test

View File

@ -10,25 +10,30 @@
display: inline-block;
padding: 10px;
min-width: 50%;
text-decoration: none;
}
.entry_list div:nth-child(odd) {
background-color: #ddd;
}
</style>
<title>{{path}}</title>
<title>{{path}} {{server_name}}</title>
</head>
<body>
<p>{{server_name}}</p>
<p>{{path}}</p>
<div class="entry_list">
<div>
<a class="entry" href="../">../</a>
<a class="entry" href="../">📁 ../</a>
</div>
{{#each entries}}
<div>
<a class="entry" href="{{this.encoded_file_name}}{{this.trailing_slash}}">
{{this.file_name}}{{this.trailing_slash}}
{{this.icon}} {{this.file_name}}{{this.trailing_slash}}
</a>
</div>
{{/each}}

View File

@ -0,0 +1,38 @@
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body {
font-family: sans-serif;
}
.entry {
display: inline-block;
padding: 10px;
min-width: 50%;
text-decoration: none;
}
.entry_list div:nth-child(odd) {
background-color: #ddd;
}
</style>
<title>{{server_name}}</title>
</head>
<body>
<div class="entry_list">
<div>
<a class="entry" href="../">📁 ../</a>
</div>
<div>
<a class="entry" href="files/">
📁 Files
</a>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,33 @@
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body {
font-family: sans-serif;
}
.entry {
display: inline-block;
padding: 20px;
min-width: 50%;
}
.entry_list div:nth-child(odd) {
background-color: #ddd;
}
</style>
<title>PTTH relay</title>
</head>
<body>
<h1>PTTH relay</h1>
<div class="entry_list">
<div>
<a class="entry" href="frontend/servers/">Server list</a>
</div>
</div>
</body>
</html>

View File

@ -23,7 +23,6 @@ use tracing::{
use ptth::{
http_serde::RequestParts,
prefix_match,
server::file_server,
};
@ -46,54 +45,50 @@ fn status_reply <B: Into <Body>> (status: StatusCode, b: B)
async fn handle_all (req: Request <Body>, state: Arc <ServerState <'static>>)
-> Result <Response <Body>, String>
{
debug! ("req.uri () = {:?}", req.uri ());
let path = req.uri ().path ();
//println! ("{}", path);
if let Some (path) = prefix_match (path, "/files") {
let path = path.into ();
let path = path.into ();
let (parts, _) = req.into_parts ();
let (parts, _) = req.into_parts ();
let ptth_req = match RequestParts::from_hyper (parts.method, path, parts.headers) {
Ok (x) => x,
_ => return Ok (status_reply (StatusCode::BAD_REQUEST, "Bad request")),
};
let ptth_req = match RequestParts::from_hyper (parts.method, path, parts.headers) {
Ok (x) => x,
_ => return Ok (status_reply (StatusCode::BAD_REQUEST, "Bad request")),
};
let default_root = PathBuf::from ("./");
let file_server_root: &std::path::Path = state.config.file_server_root
.as_ref ()
.unwrap_or (&default_root);
let default_root = PathBuf::from ("./");
let file_server_root: &std::path::Path = state.config.file_server_root
.as_ref ()
.unwrap_or (&default_root);
let ptth_resp = file_server::serve_all (
&state.handlebars,
file_server_root,
ptth_req.method,
&ptth_req.uri,
&ptth_req.headers,
None
).await;
let ptth_resp = file_server::serve_all (
&state.handlebars,
file_server_root,
ptth_req.method,
&ptth_req.uri,
&ptth_req.headers,
None
).await;
let mut resp = Response::builder ()
.status (StatusCode::from (ptth_resp.parts.status_code));
let mut resp = Response::builder ()
.status (StatusCode::from (ptth_resp.parts.status_code));
use std::str::FromStr;
use std::str::FromStr;
for (k, v) in ptth_resp.parts.headers.into_iter () {
resp = resp.header (hyper::header::HeaderName::from_str (&k).unwrap (), v);
}
let body = ptth_resp.body
.map (Body::wrap_stream)
.unwrap_or_else (Body::empty)
;
let resp = resp.body (body).unwrap ();
Ok (resp)
}
else {
Ok (status_reply (StatusCode::NOT_FOUND, "404 Not Found\n"))
for (k, v) in ptth_resp.parts.headers.into_iter () {
resp = resp.header (hyper::header::HeaderName::from_str (&k).unwrap (), v);
}
let body = ptth_resp.body
.map (Body::wrap_stream)
.unwrap_or_else (Body::empty)
;
let resp = resp.body (body).unwrap ();
Ok (resp)
}
#[derive (Deserialize)]

View File

@ -158,11 +158,17 @@ impl Response {
}
pub fn body_bytes (&mut self, b: Vec <u8>) -> &mut Self {
use std::convert::TryInto;
self.content_length = b.len ().try_into ().ok ();
self.header ("content-length".to_string (), b.len ().to_string ().into_bytes ());
let (mut tx, rx) = tokio::sync::mpsc::channel (1);
tokio::spawn (async move {
tx.send (Ok (b)).await.unwrap ();
});
self.body = Some (rx);
self
}
}

View File

@ -523,6 +523,10 @@ async fn handle_all (req: Request <Body>, state: Arc <RelayState>)
error_reply (StatusCode::BAD_REQUEST, "Bad URI format")
}
}
else if path == "/" {
let s = state.handlebars.render ("relay_root", &()).unwrap ();
ok_reply (s)
}
else if path == "/frontend/relay_up_check" {
error_reply (StatusCode::OK, "Relay is up")
}
@ -539,6 +543,7 @@ pub fn load_templates ()
for (k, v) in vec! [
("relay_server_list", "relay_server_list.html"),
("relay_root", "relay_root.html"),
].into_iter () {
handlebars.register_template_file (k, format! ("ptth_handlebars/{}", v))?;
}

View File

@ -10,8 +10,10 @@ use std::{
};
use handlebars::Handlebars;
use serde::Serialize;
use tokio::{
fs::{
DirEntry,
File,
read_dir,
ReadDir,
@ -28,7 +30,43 @@ use tracing::{
use regex::Regex;
use crate::http_serde;
use crate::{
http_serde,
prefix_match,
};
#[derive (Serialize)]
struct ServerInfo <'a> {
server_name: &'a str,
}
#[derive (Serialize)]
struct TemplateDirEntry {
icon: &'static str,
trailing_slash: &'static str,
// Unfortunately file_name will allocate as long as some platforms
// (Windows!) aren't UTF-8. Cause I don't want to write separate code
// for such a small problem.
file_name: String,
// This could be a Cow with file_name if no encoding was done but
// it's simpler to allocate.
encoded_file_name: String,
error: bool,
}
#[derive (Serialize)]
struct TemplateDirPage <'a> {
#[serde (flatten)]
server_info: ServerInfo <'a>,
path: Cow <'a, str>,
entries: Vec <TemplateDirEntry>,
}
fn parse_range_header (range_str: &str) -> (Option <u64>, Option <u64>) {
use lazy_static::*;
@ -52,47 +90,60 @@ fn parse_range_header (range_str: &str) -> (Option <u64>, Option <u64>) {
(start, end)
}
use serde::Serialize;
use tokio::fs::DirEntry;
// This could probably be done with borrows, if I owned the
// tokio::fs::DirEntry instead of consuming it
#[derive (Serialize)]
struct TemplateDirEntry {
trailing_slash: &'static str,
file_name: String,
encoded_file_name: String,
error: bool,
}
async fn read_dir_entry (entry: DirEntry) -> TemplateDirEntry
{
let trailing_slash = match entry.file_type ().await {
Ok (t) => if t.is_dir () {
"/"
}
else {
""
},
Err (_) => "",
};
let file_name = match entry.file_name ().into_string () {
Ok (x) => x,
Err (_) => return TemplateDirEntry {
icon: "⚠️",
trailing_slash: "",
file_name: "".into (),
file_name: "File / directory name is not UTF-8".into (),
encoded_file_name: "".into (),
error: true,
},
};
let (trailing_slash, icon) = match entry.file_type ().await {
Ok (t) => if t.is_dir () {
("/", "📁")
}
else {
let icon = if file_name.ends_with (".mp4") {
"🎞️"
}
else if file_name.ends_with (".avi") {
"🎞️"
}
else if file_name.ends_with (".mkv") {
"🎞️"
}
else if file_name.ends_with (".jpg") {
"📷"
}
else if file_name.ends_with (".jpeg") {
"📷"
}
else if file_name.ends_with (".png") {
"📷"
}
else if file_name.ends_with (".bmp") {
"📷"
}
else {
"📄"
};
("", icon)
},
Err (_) => ("", "⚠️"),
};
use percent_encoding::*;
let encoded_file_name = utf8_percent_encode (&file_name, CONTROLS).to_string ();
TemplateDirEntry {
icon,
trailing_slash: &trailing_slash,
file_name,
encoded_file_name,
@ -100,6 +151,25 @@ async fn read_dir_entry (entry: DirEntry) -> TemplateDirEntry
}
}
async fn serve_root (
handlebars: &Handlebars <'static>,
) -> http_serde::Response
{
let server_info = ServerInfo {
server_name: "PTTH file server",
};
let s = handlebars.render ("file_server_root", &server_info).unwrap ();
let body = s.into_bytes ();
let mut resp = http_serde::Response::default ();
resp
.header ("content-type".to_string (), "text/html".to_string ().into_bytes ())
.body_bytes (body)
;
resp
}
use std::borrow::Cow;
#[instrument (level = "debug", skip (handlebars, dir))]
@ -109,6 +179,10 @@ async fn serve_dir (
mut dir: ReadDir
) -> http_serde::Response
{
let server_info = ServerInfo {
server_name: "PTTH file server",
};
let mut entries = vec! [];
while let Ok (Some (entry)) = dir.next_entry ().await {
@ -117,23 +191,16 @@ async fn serve_dir (
entries.sort_unstable_by (|a, b| a.file_name.partial_cmp (&b.file_name).unwrap ());
#[derive (Serialize)]
struct TemplateDirPage <'a> {
path: Cow <'a, str>,
entries: Vec <TemplateDirEntry>,
}
let s = handlebars.render ("file_server_dir", &TemplateDirPage {
path,
entries,
server_info,
}).unwrap ();
let body = s.into_bytes ();
let mut resp = http_serde::Response::default ();
resp.content_length = Some (body.len ().try_into ().unwrap ());
resp
.header ("content-type".to_string (), "text/html".to_string ().into_bytes ())
.header ("content-length".to_string (), body.len ().to_string ().into_bytes ())
.body_bytes (body)
;
resp
@ -237,8 +304,8 @@ async fn serve_error (
-> http_serde::Response
{
let mut resp = http_serde::Response::default ();
resp.status_code (status_code)
.body_bytes (msg.into_bytes ());
resp.status_code (status_code);
resp.body_bytes (msg.into_bytes ());
resp
}
@ -254,31 +321,19 @@ pub async fn serve_all (
-> http_serde::Response
{
info! ("Client requested {}", uri);
let mut range_start = None;
let mut range_end = None;
if let Some (v) = headers.get ("range") {
let v = std::str::from_utf8 (v).unwrap ();
let (start, end) = parse_range_header (v);
range_start = start;
range_end = end;
}
let should_send_body = match &method {
http_serde::Method::Get => true,
http_serde::Method::Head => false,
m => {
debug! ("Unsupported method {:?}", m);
return serve_error (http_serde::StatusCode::MethodNotAllowed, "Unsupported method".into ()).await;
}
};
use percent_encoding::*;
let uri = match prefix_match (uri, "/files/") {
Some (x) => x,
None => {
return serve_root (handlebars).await;
},
};
// TODO: There is totally a dir traversal attack in here somewhere
let encoded_path = &uri [1..];
let encoded_path = &uri [..];
let path_s = percent_decode (encoded_path.as_bytes ()).decode_utf8 ().unwrap ();
let path = Path::new (&*path_s);
@ -294,7 +349,10 @@ pub async fn serve_all (
}
}
if let Ok (dir) = read_dir (&full_path).await {
if uri == "/" {
serve_root (handlebars).await
}
else if let Ok (dir) = read_dir (&full_path).await {
serve_dir (
handlebars,
full_path.to_string_lossy (),
@ -302,6 +360,26 @@ pub async fn serve_all (
).await
}
else if let Ok (file) = File::open (&full_path).await {
let mut range_start = None;
let mut range_end = None;
if let Some (v) = headers.get ("range") {
let v = std::str::from_utf8 (v).unwrap ();
let (start, end) = parse_range_header (v);
range_start = start;
range_end = end;
}
let should_send_body = match &method {
http_serde::Method::Get => true,
http_serde::Method::Head => false,
m => {
debug! ("Unsupported method {:?}", m);
return serve_error (http_serde::StatusCode::MethodNotAllowed, "Unsupported method".into ()).await;
}
};
serve_file (
file,
should_send_body,
@ -322,6 +400,7 @@ pub fn load_templates ()
for (k, v) in vec! [
("file_server_dir", "file_server_dir.html"),
("file_server_root", "file_server_root.html"),
].into_iter () {
handlebars.register_template_file (k, format! ("ptth_handlebars/{}", v))?;
}

View File

@ -66,25 +66,19 @@ async fn handle_req_resp <'a> (
debug! ("Handling request {}", req_id);
let response = if let Some (uri) = prefix_match (&parts.uri, "/files") {
let default_root = PathBuf::from ("./");
let file_server_root: &std::path::Path = state.config.file_server_root
.as_ref ()
.unwrap_or (&default_root);
let default_root = PathBuf::from ("./");
let file_server_root: &std::path::Path = state.config.file_server_root
.as_ref ()
.unwrap_or (&default_root);
file_server::serve_all (
&state.handlebars,
file_server_root,
parts.method,
uri,
&parts.headers,
state.hidden_path.as_ref ().map (|p| p.as_path ())
).await
}
else {
debug! ("404 not found");
status_reply (http_serde::StatusCode::NotFound, "404 Not Found")
};
let response = file_server::serve_all (
&state.handlebars,
file_server_root,
parts.method,
&parts.uri,
&parts.headers,
state.hidden_path.as_ref ().map (|p| p.as_path ())
).await;
let mut resp_req = state.client
.post (&format! ("{}/http_response/{}", state.config.relay_url, req_id))

View File

@ -1,8 +1,7 @@
- Not working behind Nginx (Works okay behind Caddy)
- Reduce idle memory use?
- Folder icons in dir list
- ".." from server to server list is broken
- Package templates into exe for release
- Redirect to add trailing slashes
- Add file size in directory listing
- Allow spaces in server names