refactor: improve resolve_path and handle_assets, abandon guard_path (#360)
This commit is contained in:
parent
015713bc6d
commit
920b70abc4
4 changed files with 60 additions and 50 deletions
|
@ -35,7 +35,7 @@ use std::collections::HashMap;
|
||||||
use std::fs::Metadata;
|
use std::fs::Metadata;
|
||||||
use std::io::SeekFrom;
|
use std::io::SeekFrom;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
use std::sync::atomic::{self, AtomicBool};
|
use std::sync::atomic::{self, AtomicBool};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
@ -71,7 +71,7 @@ pub struct Server {
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
pub fn init(args: Args, running: Arc<AtomicBool>) -> Result<Self> {
|
pub fn init(args: Args, running: Arc<AtomicBool>) -> Result<Self> {
|
||||||
let assets_prefix = format!("{}__dufs_v{}_/", args.uri_prefix, env!("CARGO_PKG_VERSION"));
|
let assets_prefix = format!("__dufs_v{}__/", env!("CARGO_PKG_VERSION"));
|
||||||
let single_file_req_paths = if args.path_is_file {
|
let single_file_req_paths = if args.path_is_file {
|
||||||
vec![
|
vec![
|
||||||
args.uri_prefix.to_string(),
|
args.uri_prefix.to_string(),
|
||||||
|
@ -144,23 +144,23 @@ impl Server {
|
||||||
let headers = req.headers();
|
let headers = req.headers();
|
||||||
let method = req.method().clone();
|
let method = req.method().clone();
|
||||||
|
|
||||||
if guard_path(req_path, &mut res) {
|
|
||||||
return Ok(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
if method == Method::GET && self.handle_assets(req_path, headers, &mut res).await? {
|
|
||||||
return Ok(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
let authorization = headers.get(AUTHORIZATION);
|
|
||||||
let relative_path = match self.resolve_path(req_path) {
|
let relative_path = match self.resolve_path(req_path) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => {
|
None => {
|
||||||
status_forbid(&mut res);
|
status_bad_request(&mut res, "Invalid Path");
|
||||||
return Ok(res);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if method == Method::GET
|
||||||
|
&& self
|
||||||
|
.handle_assets(&relative_path, headers, &mut res)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
let authorization = headers.get(AUTHORIZATION);
|
||||||
let guard = self.args.auth.guard(&relative_path, &method, authorization);
|
let guard = self.args.auth.guard(&relative_path, &method, authorization);
|
||||||
|
|
||||||
let (user, access_paths) = match guard {
|
let (user, access_paths) = match guard {
|
||||||
|
@ -302,10 +302,10 @@ impl Server {
|
||||||
}
|
}
|
||||||
} else if is_file {
|
} else if is_file {
|
||||||
if query_params.contains_key("edit") {
|
if query_params.contains_key("edit") {
|
||||||
self.handle_deal_file(path, DataKind::Edit, head_only, user, &mut res)
|
self.handle_edit_file(path, DataKind::Edit, head_only, user, &mut res)
|
||||||
.await?;
|
.await?;
|
||||||
} else if query_params.contains_key("view") {
|
} else if query_params.contains_key("view") {
|
||||||
self.handle_deal_file(path, DataKind::View, head_only, user, &mut res)
|
self.handle_edit_file(path, DataKind::View, head_only, user, &mut res)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
self.handle_send_file(path, headers, head_only, &mut res)
|
self.handle_send_file(path, headers, head_only, &mut res)
|
||||||
|
@ -869,7 +869,7 @@ impl Server {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_deal_file(
|
async fn handle_edit_file(
|
||||||
&self,
|
&self,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
kind: DataKind,
|
kind: DataKind,
|
||||||
|
@ -901,7 +901,10 @@ impl Server {
|
||||||
.typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
|
.typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
|
||||||
let output = self
|
let output = self
|
||||||
.html
|
.html
|
||||||
.replace("__ASSETS_PREFIX__", &self.assets_prefix)
|
.replace(
|
||||||
|
"__ASSETS_PREFIX__",
|
||||||
|
&format!("{}{}", self.args.uri_prefix, self.assets_prefix),
|
||||||
|
)
|
||||||
.replace("__INDEX_DATA__", &serde_json::to_string(&data)?);
|
.replace("__INDEX_DATA__", &serde_json::to_string(&data)?);
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.typed_insert(ContentLength(output.as_bytes().len() as u64));
|
.typed_insert(ContentLength(output.as_bytes().len() as u64));
|
||||||
|
@ -1126,7 +1129,10 @@ impl Server {
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
|
.typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
|
||||||
self.html
|
self.html
|
||||||
.replace("__ASSETS_PREFIX__", &self.assets_prefix)
|
.replace(
|
||||||
|
"__ASSETS_PREFIX__",
|
||||||
|
&format!("{}{}", self.args.uri_prefix, self.assets_prefix),
|
||||||
|
)
|
||||||
.replace("__INDEX_DATA__", &serde_json::to_string(&data)?)
|
.replace("__INDEX_DATA__", &serde_json::to_string(&data)?)
|
||||||
};
|
};
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
|
@ -1168,15 +1174,11 @@ impl Server {
|
||||||
{
|
{
|
||||||
Some(dest) => dest,
|
Some(dest) => dest,
|
||||||
None => {
|
None => {
|
||||||
*res.status_mut() = StatusCode::BAD_REQUEST;
|
status_bad_request(res, "Invalid Destination");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if guard_path(&dest_path, res) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let authorization = headers.get(AUTHORIZATION);
|
let authorization = headers.get(AUTHORIZATION);
|
||||||
let guard = self
|
let guard = self
|
||||||
.args
|
.args
|
||||||
|
@ -1209,13 +1211,30 @@ impl Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_path(&self, path: &str) -> Option<String> {
|
fn resolve_path(&self, path: &str) -> Option<String> {
|
||||||
let path = path.trim_matches('/');
|
|
||||||
let path = decode_uri(path)?;
|
let path = decode_uri(path)?;
|
||||||
let prefix = self.args.path_prefix.as_str();
|
let path = path.trim_matches('/');
|
||||||
if prefix == "/" {
|
let mut parts = vec![];
|
||||||
return Some(path.to_string());
|
for comp in Path::new(path).components() {
|
||||||
|
if let Component::Normal(v) = comp {
|
||||||
|
let v = v.to_string_lossy();
|
||||||
|
if cfg!(windows) {
|
||||||
|
let chars: Vec<char> = v.chars().collect();
|
||||||
|
if chars.len() == 2 && chars[1] == ':' && chars[0].is_ascii_alphabetic() {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
path.strip_prefix(prefix.trim_start_matches('/'))
|
}
|
||||||
|
parts.push(v);
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let new_path = parts.join("/");
|
||||||
|
let path_prefix = self.args.path_prefix.as_str();
|
||||||
|
if path_prefix.is_empty() {
|
||||||
|
return Some(new_path);
|
||||||
|
}
|
||||||
|
new_path
|
||||||
|
.strip_prefix(path_prefix.trim_start_matches('/'))
|
||||||
.map(|v| v.trim_matches('/').to_string())
|
.map(|v| v.trim_matches('/').to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1690,7 +1709,7 @@ fn parse_upload_offset(headers: &HeaderMap<HeaderValue>, size: u64) -> Result<Op
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
let err = || anyhow!("Invalid X-Update-Range header");
|
let err = || anyhow!("Invalid X-Update-Range Header");
|
||||||
let value = value.to_str().map_err(|_| err())?;
|
let value = value.to_str().map_err(|_| err())?;
|
||||||
if value == "append" {
|
if value == "append" {
|
||||||
return Ok(Some(size));
|
return Ok(Some(size));
|
||||||
|
@ -1698,12 +1717,3 @@ fn parse_upload_offset(headers: &HeaderMap<HeaderValue>, size: u64) -> Result<Op
|
||||||
let (start, _) = parse_range(value, size).ok_or_else(err)?;
|
let (start, _) = parse_range(value, size).ok_or_else(err)?;
|
||||||
Ok(Some(start))
|
Ok(Some(start))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn guard_path(path: &str, res: &mut Response) -> bool {
|
|
||||||
let path = Path::new(path);
|
|
||||||
if path.components().any(|v| v.as_os_str() == "..") {
|
|
||||||
status_bad_request(res, "");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,9 +11,9 @@ use std::process::{Command, Stdio};
|
||||||
fn assets(server: TestServer) -> Result<(), Error> {
|
fn assets(server: TestServer) -> Result<(), Error> {
|
||||||
let ver = env!("CARGO_PKG_VERSION");
|
let ver = env!("CARGO_PKG_VERSION");
|
||||||
let resp = reqwest::blocking::get(server.url())?;
|
let resp = reqwest::blocking::get(server.url())?;
|
||||||
let index_js = format!("/__dufs_v{ver}_/index.js");
|
let index_js = format!("/__dufs_v{ver}__/index.js");
|
||||||
let index_css = format!("/__dufs_v{ver}_/index.css");
|
let index_css = format!("/__dufs_v{ver}__/index.css");
|
||||||
let favicon_ico = format!("/__dufs_v{ver}_/favicon.ico");
|
let favicon_ico = format!("/__dufs_v{ver}__/favicon.ico");
|
||||||
let text = resp.text()?;
|
let text = resp.text()?;
|
||||||
println!("{text}");
|
println!("{text}");
|
||||||
assert!(text.contains(&format!(r#"href="{index_css}""#)));
|
assert!(text.contains(&format!(r#"href="{index_css}""#)));
|
||||||
|
@ -25,7 +25,7 @@ fn assets(server: TestServer) -> Result<(), Error> {
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn asset_js(server: TestServer) -> Result<(), Error> {
|
fn asset_js(server: TestServer) -> Result<(), Error> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}__dufs_v{}_/index.js",
|
"{}__dufs_v{}__/index.js",
|
||||||
server.url(),
|
server.url(),
|
||||||
env!("CARGO_PKG_VERSION")
|
env!("CARGO_PKG_VERSION")
|
||||||
);
|
);
|
||||||
|
@ -41,7 +41,7 @@ fn asset_js(server: TestServer) -> Result<(), Error> {
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn asset_css(server: TestServer) -> Result<(), Error> {
|
fn asset_css(server: TestServer) -> Result<(), Error> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}__dufs_v{}_/index.css",
|
"{}__dufs_v{}__/index.css",
|
||||||
server.url(),
|
server.url(),
|
||||||
env!("CARGO_PKG_VERSION")
|
env!("CARGO_PKG_VERSION")
|
||||||
);
|
);
|
||||||
|
@ -57,7 +57,7 @@ fn asset_css(server: TestServer) -> Result<(), Error> {
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn asset_ico(server: TestServer) -> Result<(), Error> {
|
fn asset_ico(server: TestServer) -> Result<(), Error> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}__dufs_v{}_/favicon.ico",
|
"{}__dufs_v{}__/favicon.ico",
|
||||||
server.url(),
|
server.url(),
|
||||||
env!("CARGO_PKG_VERSION")
|
env!("CARGO_PKG_VERSION")
|
||||||
);
|
);
|
||||||
|
@ -71,9 +71,9 @@ fn asset_ico(server: TestServer) -> Result<(), Error> {
|
||||||
fn assets_with_prefix(#[with(&["--path-prefix", "xyz"])] server: TestServer) -> Result<(), Error> {
|
fn assets_with_prefix(#[with(&["--path-prefix", "xyz"])] server: TestServer) -> Result<(), Error> {
|
||||||
let ver = env!("CARGO_PKG_VERSION");
|
let ver = env!("CARGO_PKG_VERSION");
|
||||||
let resp = reqwest::blocking::get(format!("{}xyz/", server.url()))?;
|
let resp = reqwest::blocking::get(format!("{}xyz/", server.url()))?;
|
||||||
let index_js = format!("/xyz/__dufs_v{ver}_/index.js");
|
let index_js = format!("/xyz/__dufs_v{ver}__/index.js");
|
||||||
let index_css = format!("/xyz/__dufs_v{ver}_/index.css");
|
let index_css = format!("/xyz/__dufs_v{ver}__/index.css");
|
||||||
let favicon_ico = format!("/xyz/__dufs_v{ver}_/favicon.ico");
|
let favicon_ico = format!("/xyz/__dufs_v{ver}__/favicon.ico");
|
||||||
let text = resp.text()?;
|
let text = resp.text()?;
|
||||||
assert!(text.contains(&format!(r#"href="{index_css}""#)));
|
assert!(text.contains(&format!(r#"href="{index_css}""#)));
|
||||||
assert!(text.contains(&format!(r#"href="{favicon_ico}""#)));
|
assert!(text.contains(&format!(r#"href="{favicon_ico}""#)));
|
||||||
|
@ -86,7 +86,7 @@ fn asset_js_with_prefix(
|
||||||
#[with(&["--path-prefix", "xyz"])] server: TestServer,
|
#[with(&["--path-prefix", "xyz"])] server: TestServer,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}xyz/__dufs_v{}_/index.js",
|
"{}xyz/__dufs_v{}__/index.js",
|
||||||
server.url(),
|
server.url(),
|
||||||
env!("CARGO_PKG_VERSION")
|
env!("CARGO_PKG_VERSION")
|
||||||
);
|
);
|
||||||
|
@ -115,7 +115,7 @@ fn assets_override(tmpdir: TempDir, port: u16) -> Result<(), Error> {
|
||||||
let url = format!("http://localhost:{port}");
|
let url = format!("http://localhost:{port}");
|
||||||
let resp = reqwest::blocking::get(&url)?;
|
let resp = reqwest::blocking::get(&url)?;
|
||||||
assert!(resp.text()?.starts_with(&format!(
|
assert!(resp.text()?.starts_with(&format!(
|
||||||
"/__dufs_v{}_/index.js;DATA",
|
"/__dufs_v{}__/index.js;DATA",
|
||||||
env!("CARGO_PKG_VERSION")
|
env!("CARGO_PKG_VERSION")
|
||||||
)));
|
)));
|
||||||
let resp = reqwest::blocking::get(&url)?;
|
let resp = reqwest::blocking::get(&url)?;
|
||||||
|
|
|
@ -53,7 +53,7 @@ fn path_prefix_single_file(tmpdir: TempDir, port: u16, #[case] file: &str) -> Re
|
||||||
let resp = reqwest::blocking::get(format!("http://localhost:{port}/xyz/index.html"))?;
|
let resp = reqwest::blocking::get(format!("http://localhost:{port}/xyz/index.html"))?;
|
||||||
assert_eq!(resp.text()?, "This is index.html");
|
assert_eq!(resp.text()?, "This is index.html");
|
||||||
let resp = reqwest::blocking::get(format!("http://localhost:{port}"))?;
|
let resp = reqwest::blocking::get(format!("http://localhost:{port}"))?;
|
||||||
assert_eq!(resp.status(), 403);
|
assert_eq!(resp.status(), 400);
|
||||||
|
|
||||||
child.kill()?;
|
child.kill()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -49,7 +49,7 @@ fn propfind_404(server: TestServer) -> Result<(), Error> {
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn propfind_double_slash(server: TestServer) -> Result<(), Error> {
|
fn propfind_double_slash(server: TestServer) -> Result<(), Error> {
|
||||||
let resp = fetch!(b"PROPFIND", format!("{}/", server.url())).send()?;
|
let resp = fetch!(b"PROPFIND", server.url()).send()?;
|
||||||
assert_eq!(resp.status(), 207);
|
assert_eq!(resp.status(), 207);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue