pub addrs: Vec<IpAddr>,
pub port: u16,
pub path: PathBuf,
+ pub path_is_file: bool,
pub path_prefix: String,
pub uri_prefix: String,
pub auth: AccessControl,
.unwrap_or_else(|| vec!["0.0.0.0", "::"]);
let addrs: Vec<IpAddr> = Args::parse_addrs(&addrs)?;
let path = Args::parse_path(matches.value_of_os("path").unwrap_or_default())?;
+ let path_is_file = path.metadata()?.is_file();
let path_prefix = matches
.value_of("path-prefix")
.map(|v| v.trim_matches('/').to_owned())
addrs,
port,
path,
+ path_is_file,
path_prefix,
uri_prefix,
auth,
let headers = req.headers();
let method = req.method().clone();
- let authorization = headers.get(AUTHORIZATION);
- let guard_type = self.args.auth.guard(req_path, &method, authorization);
-
if req_path == "/favicon.ico" && method == Method::GET {
self.handle_send_favicon(headers, &mut res).await?;
return Ok(res);
}
+ let authorization = headers.get(AUTHORIZATION);
+ let guard_type = self.args.auth.guard(req_path, &method, authorization);
if guard_type.is_reject() {
self.auth_reject(&mut res);
return Ok(res);
}
+ let head_only = method == Method::HEAD;
+
+ if self.args.path_is_file {
+ self.handle_send_file(&self.args.path, headers, head_only, &mut res)
+ .await?;
+ return Ok(res);
+ }
+
let path = match self.extract_path(req_path) {
Some(v) => v,
None => {
match method {
Method::GET | Method::HEAD => {
- let head_only = method == Method::HEAD;
if is_dir {
if render_try_index && query == "zip" {
self.handle_zip_dir(path, head_only, &mut res).await?;
res: &mut Response,
) -> BoxResult<()> {
let (mut writer, reader) = tokio::io::duplex(BUF_SIZE);
- let filename = path
- .file_name()
- .and_then(|v| v.to_str())
- .ok_or_else(|| format!("Failed to get name of `{}`", path.display()))?;
+ let filename = get_file_name(path)?;
res.headers_mut().insert(
CONTENT_DISPOSITION,
HeaderValue::from_str(&format!(
);
}
+ let filename = get_file_name(path)?;
+ res.headers_mut().insert(
+ CONTENT_DISPOSITION,
+ HeaderValue::from_str(&format!("inline; filename=\"{}\"", encode_uri(filename),))
+ .unwrap(),
+ );
+
res.headers_mut().typed_insert(AcceptRanges::bytes());
let size = meta.len();
*res.status_mut() = StatusCode::NO_CONTENT;
}
+fn get_file_name(path: &Path) -> BoxResult<&str> {
+ path.file_name()
+ .and_then(|v| v.to_str())
+ .ok_or_else(|| format!("Failed to get file name of `{}`", path.display()).into())
+}
+
fn set_webdav_headers(res: &mut Response) {
res.headers_mut().insert(
"Allow",
--- /dev/null
+mod fixtures;
+mod utils;
+
+use assert_cmd::prelude::*;
+use assert_fs::fixture::TempDir;
+use fixtures::{port, server, tmpdir, wait_for_port, Error, TestServer};
+use rstest::rstest;
+use std::process::{Command, Stdio};
+
+#[rstest]
+fn path_prefix_index(#[with(&["--path-prefix", "xyz"])] server: TestServer) -> Result<(), Error> {
+ let resp = reqwest::blocking::get(format!("{}{}", server.url(), "xyz"))?;
+ assert_index_resp!(resp);
+ Ok(())
+}
+
+#[rstest]
+fn path_prefix_file(#[with(&["--path-prefix", "xyz"])] server: TestServer) -> Result<(), Error> {
+ let resp = reqwest::blocking::get(format!("{}{}/index.html", server.url(), "xyz"))?;
+ assert_eq!(resp.status(), 200);
+ assert_eq!(resp.text()?, "This is index.html");
+ Ok(())
+}
+
+#[rstest]
+fn path_prefix_propfind(
+ #[with(&["--path-prefix", "xyz"])] server: TestServer,
+) -> Result<(), Error> {
+ let resp = fetch!(b"PROPFIND", format!("{}{}", server.url(), "xyz")).send()?;
+ let text = resp.text()?;
+ assert!(text.contains("<D:href>/xyz/</D:href>"));
+ Ok(())
+}
+
+#[rstest]
+#[case("index.html")]
+fn serve_single_file(tmpdir: TempDir, port: u16, #[case] file: &str) -> Result<(), Error> {
+ let mut child = Command::cargo_bin("duf")?
+ .env("RUST_LOG", "false")
+ .arg(tmpdir.path().join(file))
+ .arg("-p")
+ .arg(port.to_string())
+ .stdout(Stdio::piped())
+ .spawn()?;
+
+ wait_for_port(port);
+
+ let resp = reqwest::blocking::get(format!("http://localhost:{}/index.html", port))?;
+ assert_eq!(resp.text()?, "This is index.html");
+
+ child.kill()?;
+ Ok(())
+}
mod fixtures;
-use fixtures::{port, server, tmpdir, Error, TestServer};
+use fixtures::{port, server, tmpdir, wait_for_port, Error, TestServer};
use assert_cmd::prelude::*;
use assert_fs::fixture::TempDir;
.stdout(Stdio::piped())
.spawn()?;
+ wait_for_port(port);
+
// WARN assumes urls list is terminated by an empty line
let url_lines = BufReader::new(child.stdout.take().unwrap())
.lines()
}
/// Wait a max of 1s for the port to become available.
-fn wait_for_port(port: u16) {
+pub fn wait_for_port(port: u16) {
let start_wait = Instant::now();
while !port_check::is_port_reachable(format!("localhost:{}", port)) {
assert_eq!(resp.status(), 200);
assert_eq!(resp.headers().get("content-type").unwrap(), "text/html");
assert_eq!(resp.headers().get("accept-ranges").unwrap(), "bytes");
+ assert!(resp.headers().contains_key("content-disposition"));
assert!(resp.headers().contains_key("etag"));
assert!(resp.headers().contains_key("last-modified"));
assert!(resp.headers().contains_key("content-length"));
+++ /dev/null
-mod fixtures;
-mod utils;
-
-use fixtures::{server, Error, TestServer};
-use rstest::rstest;
-
-#[rstest]
-fn path_prefix_index(#[with(&["--path-prefix", "xyz"])] server: TestServer) -> Result<(), Error> {
- let resp = reqwest::blocking::get(format!("{}{}", server.url(), "xyz"))?;
- assert_index_resp!(resp);
- Ok(())
-}
-
-#[rstest]
-fn path_prefix_file(#[with(&["--path-prefix", "xyz"])] server: TestServer) -> Result<(), Error> {
- let resp = reqwest::blocking::get(format!("{}{}/index.html", server.url(), "xyz"))?;
- assert_eq!(resp.status(), 200);
- assert_eq!(resp.text()?, "This is index.html");
- Ok(())
-}
-
-#[rstest]
-fn path_prefix_propfind(
- #[with(&["--path-prefix", "xyz"])] server: TestServer,
-) -> Result<(), Error> {
- let resp = fetch!(b"PROPFIND", format!("{}{}", server.url(), "xyz")).send()?;
- let text = resp.text()?;
- assert!(text.contains("<D:href>/xyz/</D:href>"));
- Ok(())
-}