diff --git a/.gitignore b/.gitignore
index 690f90c..d4996e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
/ptth_server.toml
/ptth_relay.toml
/target
+/test
diff --git a/ptth_handlebars/file_server_dir.html b/ptth_handlebars/file_server_dir.html
index 675d58f..552fb24 100644
--- a/ptth_handlebars/file_server_dir.html
+++ b/ptth_handlebars/file_server_dir.html
@@ -10,25 +10,30 @@
display: inline-block;
padding: 10px;
min-width: 50%;
+ text-decoration: none;
}
.entry_list div:nth-child(odd) {
background-color: #ddd;
}
-
{{#each entries}}
{{/each}}
diff --git a/ptth_handlebars/file_server_root.html b/ptth_handlebars/file_server_root.html
new file mode 100644
index 0000000..00a88be
--- /dev/null
+++ b/ptth_handlebars/file_server_root.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
{{server_name}}
+
+
+
+
+
+
+
diff --git a/ptth_handlebars/relay_root.html b/ptth_handlebars/relay_root.html
new file mode 100644
index 0000000..79c7d30
--- /dev/null
+++ b/ptth_handlebars/relay_root.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+
PTTH relay
+
+
+
+
PTTH relay
+
+
+
+
+
diff --git a/src/bin/ptth_file_server.rs b/src/bin/ptth_file_server.rs
index 18b1865..2f638bc 100644
--- a/src/bin/ptth_file_server.rs
+++ b/src/bin/ptth_file_server.rs
@@ -23,7 +23,6 @@ use tracing::{
use ptth::{
http_serde::RequestParts,
- prefix_match,
server::file_server,
};
@@ -46,54 +45,50 @@ fn status_reply
> (status: StatusCode, b: B)
async fn handle_all (req: Request , state: Arc >)
-> Result , 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)]
diff --git a/src/http_serde.rs b/src/http_serde.rs
index 2ef2fc5..056ceb4 100644
--- a/src/http_serde.rs
+++ b/src/http_serde.rs
@@ -158,11 +158,17 @@ impl Response {
}
pub fn body_bytes (&mut self, b: Vec ) -> &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
}
}
diff --git a/src/relay/mod.rs b/src/relay/mod.rs
index ef8aee9..6d017fb 100644
--- a/src/relay/mod.rs
+++ b/src/relay/mod.rs
@@ -523,6 +523,10 @@ async fn handle_all (req: Request , state: Arc )
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))?;
}
diff --git a/src/server/file_server.rs b/src/server/file_server.rs
index e25b7e3..35d9ac2 100644
--- a/src/server/file_server.rs
+++ b/src/server/file_server.rs
@@ -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 ,
+}
fn parse_range_header (range_str: &str) -> (Option , Option ) {
use lazy_static::*;
@@ -52,47 +90,60 @@ fn parse_range_header (range_str: &str) -> (Option , Option ) {
(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 ,
- }
-
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))?;
}
diff --git a/src/server/mod.rs b/src/server/mod.rs
index aa3e041..6b9f06b 100644
--- a/src/server/mod.rs
+++ b/src/server/mod.rs
@@ -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))
diff --git a/todo.md b/todo.md
index b62398a..0064c5b 100644
--- a/todo.md
+++ b/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