🐛 Fix the backlinks from servers up to the relay
parent
345fa64ad0
commit
c5691d9d05
|
@ -4,3 +4,4 @@
|
|||
/ptth_server.toml
|
||||
/ptth_relay.toml
|
||||
/target
|
||||
/test
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
{
|
||||
let path = req.uri ().path ();
|
||||
//println! ("{}", path);
|
||||
debug! ("req.uri () = {:?}", req.uri ());
|
||||
|
||||
if let Some (path) = prefix_match (path, "/files") {
|
||||
let path = path.into ();
|
||||
|
||||
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 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 mut resp = Response::builder ()
|
||||
.status (StatusCode::from (ptth_resp.parts.status_code));
|
||||
|
||||
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"))
|
||||
let path = req.uri ().path ();
|
||||
|
||||
let path = path.into ();
|
||||
|
||||
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 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 mut resp = Response::builder ()
|
||||
.status (StatusCode::from (ptth_resp.parts.status_code));
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
#[derive (Deserialize)]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))?;
|
||||
}
|
||||
|
|
|
@ -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))?;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
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 default_root = PathBuf::from ("./");
|
||||
let file_server_root: &std::path::Path = state.config.file_server_root
|
||||
.as_ref ()
|
||||
.unwrap_or (&default_root);
|
||||
|
||||
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))
|
||||
|
|
3
todo.md
3
todo.md
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue