test: add integration tests (#36)
This commit is contained in:
parent
6b01c143d9
commit
471bca86c6
21 changed files with 2590 additions and 47 deletions
1328
Cargo.lock
generated
1328
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
14
Cargo.toml
14
Cargo.toml
|
@ -7,7 +7,6 @@ description = "Duf is a simple file server."
|
|||
license = "MIT OR Apache-2.0"
|
||||
homepage = "https://github.com/sigoden/duf"
|
||||
repository = "https://github.com/sigoden/duf"
|
||||
autotests = false
|
||||
categories = ["command-line-utilities", "web-programming::http-server"]
|
||||
keywords = ["static", "file", "server", "webdav", "cli"]
|
||||
|
||||
|
@ -39,6 +38,19 @@ xml-rs = "0.8"
|
|||
env_logger = { version = "0.9", default-features = false, features = ["humantime"] }
|
||||
log = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2"
|
||||
reqwest = { version = "0.11", features = ["blocking", "multipart", "rustls-tls"], default-features = false }
|
||||
assert_fs = "1"
|
||||
select = "0.5"
|
||||
port_check = "0.1"
|
||||
rstest = "0.13"
|
||||
regex = "1"
|
||||
pretty_assertions = "1.2"
|
||||
url = "2"
|
||||
diqwest = { version = "1", features = ["blocking"] }
|
||||
predicates = "2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
strip = true
|
||||
|
|
|
@ -206,8 +206,8 @@ fn to_addr(ip: &str, port: u16) -> BoxResult<SocketAddr> {
|
|||
// Load public certificate from file.
|
||||
fn load_certs(filename: &str) -> BoxResult<Vec<Certificate>> {
|
||||
// Open certificate file.
|
||||
let certfile =
|
||||
fs::File::open(&filename).map_err(|e| format!("Failed to open {}: {}", &filename, e))?;
|
||||
let certfile = fs::File::open(&filename)
|
||||
.map_err(|e| format!("Failed to access `{}`, {}", &filename, e))?;
|
||||
let mut reader = io::BufReader::new(certfile);
|
||||
|
||||
// Load and return certificate.
|
||||
|
@ -221,8 +221,8 @@ fn load_certs(filename: &str) -> BoxResult<Vec<Certificate>> {
|
|||
// Load private key from file.
|
||||
fn load_private_key(filename: &str) -> BoxResult<PrivateKey> {
|
||||
// Open keyfile.
|
||||
let keyfile =
|
||||
fs::File::open(&filename).map_err(|e| format!("Failed to open {}: {}", &filename, e))?;
|
||||
let keyfile = fs::File::open(&filename)
|
||||
.map_err(|e| format!("Failed to access `{}`, {}", &filename, e))?;
|
||||
let mut reader = io::BufReader::new(keyfile);
|
||||
|
||||
// Load and return a single private key.
|
||||
|
|
110
src/server.rs
110
src/server.rs
|
@ -149,6 +149,13 @@ impl InnerService {
|
|||
}
|
||||
|
||||
let req_path = req.uri().path();
|
||||
let headers = req.headers();
|
||||
let method = req.method().clone();
|
||||
|
||||
if req_path == "/favicon.ico" && method == Method::GET {
|
||||
self.handle_send_favicon(req.headers(), &mut res).await?;
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
let path = match self.extract_path(req_path) {
|
||||
Some(v) => v,
|
||||
|
@ -175,15 +182,6 @@ impl InnerService {
|
|||
status!(res, StatusCode::NOT_FOUND);
|
||||
return Ok(res);
|
||||
}
|
||||
if is_miss && path.ends_with("favicon.ico") {
|
||||
*res.body_mut() = Body::from(FAVICON_ICO);
|
||||
res.headers_mut()
|
||||
.insert("content-type", "image/x-icon".parse().unwrap());
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
let headers = req.headers();
|
||||
let method = req.method().clone();
|
||||
|
||||
match method {
|
||||
Method::GET | Method::HEAD => {
|
||||
|
@ -247,12 +245,30 @@ impl InnerService {
|
|||
status!(res, StatusCode::NOT_FOUND);
|
||||
}
|
||||
}
|
||||
"MKCOL" if allow_upload && is_miss => self.handle_mkcol(path, &mut res).await?,
|
||||
"COPY" if allow_upload && !is_miss => {
|
||||
self.handle_copy(path, headers, &mut res).await?
|
||||
"MKCOL" => {
|
||||
if !allow_upload || !is_miss {
|
||||
status!(res, StatusCode::FORBIDDEN);
|
||||
} else {
|
||||
self.handle_mkcol(path, &mut res).await?;
|
||||
}
|
||||
}
|
||||
"MOVE" if allow_upload && allow_delete && !is_miss => {
|
||||
self.handle_move(path, headers, &mut res).await?
|
||||
"COPY" => {
|
||||
if !allow_upload {
|
||||
status!(res, StatusCode::FORBIDDEN);
|
||||
} else if is_miss {
|
||||
status!(res, StatusCode::NOT_FOUND);
|
||||
} else {
|
||||
self.handle_copy(path, headers, &mut res).await?
|
||||
}
|
||||
}
|
||||
"MOVE" => {
|
||||
if !allow_upload || !allow_delete {
|
||||
status!(res, StatusCode::FORBIDDEN);
|
||||
} else if is_miss {
|
||||
status!(res, StatusCode::NOT_FOUND);
|
||||
} else {
|
||||
self.handle_move(path, headers, &mut res).await?
|
||||
}
|
||||
}
|
||||
"LOCK" => {
|
||||
// Fake lock
|
||||
|
@ -286,7 +302,13 @@ impl InnerService {
|
|||
) -> BoxResult<()> {
|
||||
ensure_path_parent(path).await?;
|
||||
|
||||
let mut file = fs::File::create(&path).await?;
|
||||
let mut file = match fs::File::create(&path).await {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
status!(res, StatusCode::FORBIDDEN);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let body_with_io_error = req
|
||||
.body_mut()
|
||||
|
@ -436,6 +458,25 @@ impl InnerService {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_send_favicon(
|
||||
&self,
|
||||
headers: &HeaderMap<HeaderValue>,
|
||||
res: &mut Response,
|
||||
) -> BoxResult<()> {
|
||||
let path = self.args.path.join("favicon.ico");
|
||||
let meta = fs::metadata(&path).await.ok();
|
||||
let is_file = meta.map(|v| v.is_file()).unwrap_or_default();
|
||||
if is_file {
|
||||
self.handle_send_file(path.as_path(), headers, false, res)
|
||||
.await?;
|
||||
} else {
|
||||
*res.body_mut() = Body::from(FAVICON_ICO);
|
||||
res.headers_mut()
|
||||
.insert("content-type", "image/x-icon".parse().unwrap());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_send_file(
|
||||
&self,
|
||||
path: &Path,
|
||||
|
@ -534,10 +575,10 @@ impl InnerService {
|
|||
return Ok(());
|
||||
}
|
||||
},
|
||||
None => 0,
|
||||
None => 1,
|
||||
};
|
||||
let mut paths = vec![self.to_pathitem(path, &self.args.path).await?.unwrap()];
|
||||
if depth > 0 {
|
||||
if depth != 0 {
|
||||
match self.list_dir(path, &self.args.path).await {
|
||||
Ok(child) => paths.extend(child),
|
||||
Err(_) => {
|
||||
|
@ -588,7 +629,7 @@ impl InnerService {
|
|||
|
||||
let meta = fs::symlink_metadata(path).await?;
|
||||
if meta.is_dir() {
|
||||
status!(res, StatusCode::BAD_REQUEST);
|
||||
status!(res, StatusCode::FORBIDDEN);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -690,7 +731,10 @@ impl InnerService {
|
|||
r#"
|
||||
<title>Files in {}/ - Duf</title>
|
||||
<style>{}</style>
|
||||
<script>var DATA = {}; {}</script>
|
||||
<script>
|
||||
const DATA =
|
||||
{}
|
||||
{}</script>
|
||||
"#,
|
||||
rel_path.display(),
|
||||
INDEX_CSS,
|
||||
|
@ -811,15 +855,9 @@ impl InnerService {
|
|||
PathType::Dir | PathType::SymlinkDir => None,
|
||||
PathType::File | PathType::SymlinkFile => Some(meta.len()),
|
||||
};
|
||||
let base_name = rel_path
|
||||
.file_name()
|
||||
.and_then(|v| v.to_str())
|
||||
.unwrap_or("/")
|
||||
.to_owned();
|
||||
let name = normalize_path(rel_path);
|
||||
Ok(Some(PathItem {
|
||||
path_type,
|
||||
base_name,
|
||||
name,
|
||||
mtime,
|
||||
size,
|
||||
|
@ -839,7 +877,6 @@ struct IndexData {
|
|||
#[derive(Debug, Serialize, Eq, PartialEq, Ord, PartialOrd)]
|
||||
struct PathItem {
|
||||
path_type: PathType,
|
||||
base_name: String,
|
||||
name: String,
|
||||
mtime: u64,
|
||||
size: Option<u64>,
|
||||
|
@ -849,7 +886,7 @@ impl PathItem {
|
|||
pub fn to_dav_xml(&self, prefix: &str) -> String {
|
||||
let mtime = Utc.timestamp_millis(self.mtime as i64).to_rfc2822();
|
||||
let href = encode_uri(&format!("{}{}", prefix, &self.name));
|
||||
let displayname = escape_str_pcdata(&self.base_name);
|
||||
let displayname = escape_str_pcdata(self.base_name());
|
||||
match self.path_type {
|
||||
PathType::Dir | PathType::SymlinkDir => format!(
|
||||
r#"<D:response>
|
||||
|
@ -885,6 +922,12 @@ impl PathItem {
|
|||
),
|
||||
}
|
||||
}
|
||||
fn base_name(&self) -> &str {
|
||||
Path::new(&self.name)
|
||||
.file_name()
|
||||
.and_then(|v| v.to_str())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Eq, PartialEq, Ord, PartialOrd)]
|
||||
|
@ -1016,13 +1059,14 @@ fn print_listening(addr: &SocketAddr, prefix: &str, tls: bool) {
|
|||
let addrs = retrieve_listening_addrs(addr);
|
||||
let protocol = if tls { "https" } else { "http" };
|
||||
if addrs.len() == 1 {
|
||||
eprintln!("Listening on {}://{}{}", protocol, addr, prefix);
|
||||
println!("Listening on {}://{}{}", protocol, addr, prefix);
|
||||
} else {
|
||||
eprintln!("Listening on:");
|
||||
for addr in addrs {
|
||||
eprintln!(" {}://{}{}", protocol, addr, prefix);
|
||||
}
|
||||
eprintln!();
|
||||
let message = addrs
|
||||
.iter()
|
||||
.map(|addr| format!(" {}://{}{}", protocol, addr, prefix))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
println!("Listening on:\n{}\n", message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
61
tests/allow.rs
Normal file
61
tests/allow.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
mod fixtures;
|
||||
mod utils;
|
||||
|
||||
use fixtures::{server, Error, TestServer};
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
fn default_not_allow_upload(server: TestServer) -> Result<(), Error> {
|
||||
let url = format!("{}file1", server.url());
|
||||
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
||||
assert_eq!(resp.status(), 403);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn default_not_allow_delete(server: TestServer) -> Result<(), Error> {
|
||||
let url = format!("{}test.html", server.url());
|
||||
let resp = fetch!(b"DELETE", &url).send()?;
|
||||
assert_eq!(resp.status(), 403);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn default_not_exist_dir(server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(format!("{}404/", server.url()))?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn allow_upload_not_exist_dir(
|
||||
#[with(&["--allow-upload"])] server: TestServer,
|
||||
) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(format!("{}404/", server.url()))?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn allow_upload_no_override(#[with(&["--allow-upload"])] server: TestServer) -> Result<(), Error> {
|
||||
let url = format!("{}index.html", server.url());
|
||||
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
||||
assert_eq!(resp.status(), 403);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn allow_delete_no_override(#[with(&["--allow-delete"])] server: TestServer) -> Result<(), Error> {
|
||||
let url = format!("{}index.html", server.url());
|
||||
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
||||
assert_eq!(resp.status(), 403);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn allow_upload_delete_can_override(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let url = format!("{}index.html", server.url());
|
||||
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
||||
assert_eq!(resp.status(), 201);
|
||||
Ok(())
|
||||
}
|
38
tests/auth.rs
Normal file
38
tests/auth.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
mod fixtures;
|
||||
mod utils;
|
||||
|
||||
use diqwest::blocking::WithDigestAuth;
|
||||
use fixtures::{server, Error, TestServer};
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
fn no_auth(#[with(&["--auth", "user:pass", "-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(server.url())?;
|
||||
assert_eq!(resp.status(), 401);
|
||||
assert!(resp.headers().contains_key("www-authenticate"));
|
||||
let url = format!("{}file1", server.url());
|
||||
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
||||
assert_eq!(resp.status(), 401);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn auth(#[with(&["--auth", "user:pass", "-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let url = format!("{}file1", server.url());
|
||||
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
||||
assert_eq!(resp.status(), 401);
|
||||
let resp = fetch!(b"PUT", &url)
|
||||
.body(b"abc".to_vec())
|
||||
.send_with_digest_auth("user", "pass")?;
|
||||
assert_eq!(resp.status(), 201);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn auth_skip_access(
|
||||
#[with(&["--auth", "user:pass", "--no-auth-access"])] server: TestServer,
|
||||
) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(server.url())?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
Ok(())
|
||||
}
|
80
tests/bind.rs
Normal file
80
tests/bind.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
mod fixtures;
|
||||
|
||||
use fixtures::{port, server, tmpdir, Error, TestServer};
|
||||
|
||||
use assert_cmd::prelude::*;
|
||||
use assert_fs::fixture::TempDir;
|
||||
use regex::Regex;
|
||||
use rstest::rstest;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
#[rstest]
|
||||
#[case(&["-b", "20.205.243.166"])]
|
||||
fn bind_fails(tmpdir: TempDir, port: u16, #[case] args: &[&str]) -> Result<(), Error> {
|
||||
Command::cargo_bin("duf")?
|
||||
.env("RUST_LOG", "false")
|
||||
.arg(tmpdir.path())
|
||||
.arg("-p")
|
||||
.arg(port.to_string())
|
||||
.args(args)
|
||||
.assert()
|
||||
.stderr(predicates::str::contains("creating server listener"))
|
||||
.failure();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn bind_ipv4(server: TestServer) -> Result<(), Error> {
|
||||
assert!(reqwest::blocking::get(format!("http://127.0.0.1:{}", server.port()).as_str()).is_ok());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn bind_ipv6(#[with(&["-b", "::"])] server: TestServer) -> Result<(), Error> {
|
||||
assert_eq!(
|
||||
reqwest::blocking::get(format!("http://127.0.0.1:{}", server.port()).as_str()).is_ok(),
|
||||
!cfg!(windows)
|
||||
);
|
||||
assert!(reqwest::blocking::get(format!("http://[::1]:{}", server.port()).as_str()).is_ok());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(&[] as &[&str])]
|
||||
#[case(&["--path-prefix", "/prefix"])]
|
||||
fn validate_printed_urls(tmpdir: TempDir, port: u16, #[case] args: &[&str]) -> Result<(), Error> {
|
||||
let mut child = Command::cargo_bin("duf")?
|
||||
.env("RUST_LOG", "false")
|
||||
.arg(tmpdir.path())
|
||||
.arg("-p")
|
||||
.arg(port.to_string())
|
||||
.args(args)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
// WARN assumes urls list is terminated by an empty line
|
||||
let url_lines = BufReader::new(child.stdout.take().unwrap())
|
||||
.lines()
|
||||
.map(|line| line.expect("Error reading stdout"))
|
||||
.take_while(|line| !line.is_empty()) /* non-empty lines */
|
||||
.collect::<Vec<_>>();
|
||||
let url_lines = url_lines.join("\n");
|
||||
|
||||
let urls = Regex::new(r"http://[a-zA-Z0-9\.\[\]:/]+")
|
||||
.unwrap()
|
||||
.captures_iter(url_lines.as_str())
|
||||
.map(|caps| caps.get(0).unwrap().as_str())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert!(!urls.is_empty());
|
||||
|
||||
for url in urls {
|
||||
reqwest::blocking::get(url)?.error_for_status()?;
|
||||
}
|
||||
|
||||
child.kill()?;
|
||||
|
||||
Ok(())
|
||||
}
|
37
tests/cors.rs
Normal file
37
tests/cors.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
mod fixtures;
|
||||
mod utils;
|
||||
|
||||
use fixtures::{server, Error, TestServer};
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
fn cors(#[with(&["--cors"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(server.url())?;
|
||||
|
||||
assert_eq!(
|
||||
resp.headers().get("access-control-allow-origin").unwrap(),
|
||||
"*"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers().get("access-control-allow-headers").unwrap(),
|
||||
"range, content-type, accept, origin, www-authenticate"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn cors_options(#[with(&["--cors"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"OPTIONS", server.url()).send()?;
|
||||
|
||||
assert_eq!(
|
||||
resp.headers().get("access-control-allow-origin").unwrap(),
|
||||
"*"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers().get("access-control-allow-headers").unwrap(),
|
||||
"range, content-type, accept, origin, www-authenticate"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
29
tests/data/cert.pem
Normal file
29
tests/data/cert.pem
Normal file
|
@ -0,0 +1,29 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFCTCCAvGgAwIBAgIUcegjikATvwNSIbN43QybKWIcKSMwDQYJKoZIhvcNAQEL
|
||||
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIyMDYxMTA4NTQyMloXDTMyMDYw
|
||||
ODA4NTQyMlowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAg8AMIICCgKCAgEAo2wdMbFPkX7CAF/Y+hVj5bwm4dlxhwW2Z9Ic2RZFC5w2
|
||||
oK2XwyasDBEqDlgv/bN4xObAVlDZ/4/SuTVSDrNB8dtQl7GTWptpbFKJUdNocU88
|
||||
wqd4k/cLZg2aiQqnZKD88w/AxXnYw+F8yU0pFGj9GX0S5at3/V1hrBVxVO8Y99bb
|
||||
gnJA8NMm0Pw2xYZS++ULuzoECk0xbNdtbtPrIuweI5mMvsJvtiw67EIdl3N9Lj5p
|
||||
L4a7X1C0Xk5H4mOcwM0qq3m31HsCW91PMCjU6suo764rx5Jqv0n9HCNxdiSEadCw
|
||||
f+GrmKtFOw3DcGPETg5AJR8H3rG1agKKjI+vRtL/tZ7coFOhZKXdjGvvUFcWcqO+
|
||||
GppHh16pzJDXi2qeD9Cu5b2ayM2uBnfV7Q3FjOeDqD+BCJ0ClaqNmAD9TF2htzdu
|
||||
Inl+G3OJb4cqaYjaF5YmiZISfrimK5eR2I3et5cqnbuDHMKvDfUd9Jgj/2IqPOHJ
|
||||
EguuXSO7WNKfQmlTv7EN/xrD6jiB/M8ADaSxjCqTbtKNyCbJlu2Wy9WlDXwPkNW8
|
||||
g70T4Br4U4Iy3N/0w2lAAhiizdC2jkehSKmWE2nmixGSXxkSOMgXQXDJ9RBtDQfd
|
||||
8ym/ADfyVndUSnHvf9jCH1NPHlFbB7RVSvUHX22Qq63NUvhV32ct+/IyD/qPpl0C
|
||||
AwEAAaNTMFEwHQYDVR0OBBYEFKwSSbPXBIkmzja3/cNJyqhWy96WMB8GA1UdIwQY
|
||||
MBaAFKwSSbPXBIkmzja3/cNJyqhWy96WMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
|
||||
hvcNAQELBQADggIBAHcrdu1nGDN5YvcHXzbBx73AC921fmn5xxzeFRO7af157g5h
|
||||
4zornLMk4Obp+UGkMbWK4K0NAQXKKm5WjcmoOHNRg7TgTE7b1gcVuS4phdwlIqA6
|
||||
eZGg+NWZyeaIJNjdHgWgGoe+S+5Ne1I7sDKiEXrOzITJrDcQgBKFF08kqT6UNY2W
|
||||
q90m+olPtrewAMgWllpxJ90u4qifPcwP+neDZJim9MhVYtHHeFsmyzlS185iasj8
|
||||
sxvp5HDTopmz0tDuiLHvOMKmyf7vapsnbqEGngQi2qV9rBmldyRLnWSe8u/FN31f
|
||||
zhSk1ikSm1cQ/iyL898XexSmTafyaF8ELswdIMHkGZkVQurWeKn3/CEDXokXkpMI
|
||||
4dlCSgM7SU+XtcjtXbR8/pHpcW2ZnBR0la/qIv81aNKkJeUkTcPC8BUv4jI/oT6z
|
||||
LRrvRjMnHJjnADACuutlNRU4/e7h1XuvlXgFHsp63k7GJXouoIwdHjfkErZXsoEX
|
||||
WeS+pPatkT7wbhfgYVwglMRIpgCu++htSRCV/lbSuYzCG6mKtxJyy4eslSjpHNPG
|
||||
wELDKgzsgLtuTyNfP458O9i8x6wf9J6eVaHe3nqgqkOnnmQxEYnsPaFUMWG1/DYi
|
||||
U+mA/VdQrPe3J4Z082sCe4MVmTzWlWCDpNFFQpv51NbWzc/kuIZuJCAwoZD0
|
||||
-----END CERTIFICATE-----
|
3
tests/data/generate_tls_certs.sh
Executable file
3
tests/data/generate_tls_certs.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
openssl req -subj '/CN=localhost' -x509 -newkey rsa:4096 -keyout key_pkcs8.pem -out cert.pem -nodes -days 3650
|
||||
openssl rsa -in key_pkcs8.pem -out key_pkcs1.pem
|
51
tests/data/key_pkcs1.pem
Normal file
51
tests/data/key_pkcs1.pem
Normal file
|
@ -0,0 +1,51 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
52
tests/data/key_pkcs8.pem
Normal file
52
tests/data/key_pkcs8.pem
Normal file
|
@ -0,0 +1,52 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
afHJpnY0yZEqN1PeDaKVOCQAHbD8qQ==
|
||||
-----END PRIVATE KEY-----
|
25
tests/favicon.rs
Normal file
25
tests/favicon.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
mod fixtures;
|
||||
mod utils;
|
||||
|
||||
use fixtures::{server, Error, TestServer};
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
fn default_favicon(server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(format!("{}favicon.ico", server.url()))?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
assert_eq!(resp.headers().get("content-type").unwrap(), "image/x-icon");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn exist_favicon(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let url = format!("{}favicon.ico", server.url());
|
||||
let data = b"abc";
|
||||
let resp = fetch!(b"PUT", &url).body(data.to_vec()).send()?;
|
||||
assert_eq!(resp.status(), 201);
|
||||
let resp = reqwest::blocking::get(url)?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
assert_eq!(resp.bytes()?, data.to_vec());
|
||||
Ok(())
|
||||
}
|
191
tests/fixtures.rs
Normal file
191
tests/fixtures.rs
Normal file
|
@ -0,0 +1,191 @@
|
|||
use assert_cmd::prelude::*;
|
||||
use assert_fs::fixture::TempDir;
|
||||
use assert_fs::prelude::*;
|
||||
use port_check::free_local_port;
|
||||
use reqwest::Url;
|
||||
use rstest::fixture;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub type Error = Box<dyn std::error::Error>;
|
||||
|
||||
/// File names for testing purpose
|
||||
#[allow(dead_code)]
|
||||
pub static FILES: &[&str] = &[
|
||||
"test.txt",
|
||||
"test.html",
|
||||
"index.html",
|
||||
"test.mkv",
|
||||
#[cfg(not(windows))]
|
||||
"test \" \' & < >.csv",
|
||||
"😀.data",
|
||||
"⎙.mp4",
|
||||
"#[]{}()@!$&'`+,;= %20.test",
|
||||
#[cfg(unix)]
|
||||
":?#[]{}<>()@!$&'`|*+,;= %20.test",
|
||||
#[cfg(not(windows))]
|
||||
"foo\\bar.test",
|
||||
];
|
||||
|
||||
/// Directory names for testing purpose
|
||||
#[allow(dead_code)]
|
||||
pub static DIR_NO_INDEX: &str = "dir-no-index/";
|
||||
|
||||
/// Directory names for testing purpose
|
||||
#[allow(dead_code)]
|
||||
pub static DIRECTORIES: &[&str] = &["dira/", "dirb/", "dirc/", DIR_NO_INDEX];
|
||||
|
||||
/// Name of a deeply nested file
|
||||
#[allow(dead_code)]
|
||||
pub static DEEPLY_NESTED_FILE: &str = "very/deeply/nested/test.rs";
|
||||
|
||||
/// Test fixture which creates a temporary directory with a few files and directories inside.
|
||||
/// The directories also contain files.
|
||||
#[fixture]
|
||||
#[allow(dead_code)]
|
||||
pub fn tmpdir() -> TempDir {
|
||||
let tmpdir = assert_fs::TempDir::new().expect("Couldn't create a temp dir for tests");
|
||||
for file in FILES {
|
||||
tmpdir
|
||||
.child(file)
|
||||
.write_str(&format!("This is {}", file))
|
||||
.expect("Couldn't write to file");
|
||||
}
|
||||
for directory in DIRECTORIES {
|
||||
for file in FILES {
|
||||
if *directory == DIR_NO_INDEX {
|
||||
continue;
|
||||
}
|
||||
tmpdir
|
||||
.child(format!("{}{}", directory, file))
|
||||
.write_str(&format!("This is {}{}", directory, file))
|
||||
.expect("Couldn't write to file");
|
||||
}
|
||||
}
|
||||
|
||||
tmpdir
|
||||
.child(&DEEPLY_NESTED_FILE)
|
||||
.write_str("File in a deeply nested directory.")
|
||||
.expect("Couldn't write to file");
|
||||
tmpdir
|
||||
}
|
||||
|
||||
/// Get a free port.
|
||||
#[fixture]
|
||||
#[allow(dead_code)]
|
||||
pub fn port() -> u16 {
|
||||
free_local_port().expect("Couldn't find a free local port")
|
||||
}
|
||||
|
||||
/// Run miniserve as a server; Start with a temporary directory, a free port and some
|
||||
/// optional arguments then wait for a while for the server setup to complete.
|
||||
#[fixture]
|
||||
#[allow(dead_code)]
|
||||
pub fn server<I>(#[default(&[] as &[&str])] args: I) -> TestServer
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: AsRef<std::ffi::OsStr>,
|
||||
{
|
||||
let port = port();
|
||||
let tmpdir = tmpdir();
|
||||
let child = Command::cargo_bin("duf")
|
||||
.expect("Couldn't find test binary")
|
||||
.env("RUST_LOG", "false")
|
||||
.arg(tmpdir.path())
|
||||
.arg("-p")
|
||||
.arg(port.to_string())
|
||||
.args(args.clone())
|
||||
.stdout(Stdio::null())
|
||||
.spawn()
|
||||
.expect("Couldn't run test binary");
|
||||
let is_tls = args
|
||||
.into_iter()
|
||||
.any(|x| x.as_ref().to_str().unwrap().contains("tls"));
|
||||
|
||||
wait_for_port(port);
|
||||
TestServer::new(port, tmpdir, child, is_tls)
|
||||
}
|
||||
|
||||
/// Same as `server()` but ignore stderr
|
||||
#[fixture]
|
||||
#[allow(dead_code)]
|
||||
pub fn server_no_stderr<I>(#[default(&[] as &[&str])] args: I) -> TestServer
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: AsRef<std::ffi::OsStr>,
|
||||
{
|
||||
let port = port();
|
||||
let tmpdir = tmpdir();
|
||||
let child = Command::cargo_bin("duf")
|
||||
.expect("Couldn't find test binary")
|
||||
.env("RUST_LOG", "false")
|
||||
.arg(tmpdir.path())
|
||||
.arg("-p")
|
||||
.arg(port.to_string())
|
||||
.args(args.clone())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.expect("Couldn't run test binary");
|
||||
let is_tls = args
|
||||
.into_iter()
|
||||
.any(|x| x.as_ref().to_str().unwrap().contains("tls"));
|
||||
|
||||
wait_for_port(port);
|
||||
TestServer::new(port, tmpdir, child, is_tls)
|
||||
}
|
||||
|
||||
/// Wait a max of 1s for the port to become available.
|
||||
fn wait_for_port(port: u16) {
|
||||
let start_wait = Instant::now();
|
||||
|
||||
while !port_check::is_port_reachable(format!("localhost:{}", port)) {
|
||||
sleep(Duration::from_millis(100));
|
||||
|
||||
if start_wait.elapsed().as_secs() > 1 {
|
||||
panic!("timeout waiting for port {}", port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct TestServer {
|
||||
port: u16,
|
||||
tmpdir: TempDir,
|
||||
child: Child,
|
||||
is_tls: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl TestServer {
|
||||
pub fn new(port: u16, tmpdir: TempDir, child: Child, is_tls: bool) -> Self {
|
||||
Self {
|
||||
port,
|
||||
tmpdir,
|
||||
child,
|
||||
is_tls,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn url(&self) -> Url {
|
||||
let protocol = if self.is_tls { "https" } else { "http" };
|
||||
Url::parse(&format!("{}://localhost:{}", protocol, self.port)).unwrap()
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &std::path::Path {
|
||||
self.tmpdir.path()
|
||||
}
|
||||
|
||||
pub fn port(&self) -> u16 {
|
||||
self.port
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestServer {
|
||||
fn drop(&mut self) {
|
||||
self.child.kill().expect("Couldn't kill test server");
|
||||
self.child.wait().unwrap();
|
||||
}
|
||||
}
|
184
tests/http.rs
Normal file
184
tests/http.rs
Normal file
|
@ -0,0 +1,184 @@
|
|||
mod fixtures;
|
||||
mod utils;
|
||||
|
||||
use fixtures::{server, Error, TestServer};
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
fn get_dir(server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(server.url())?;
|
||||
assert_index_resp!(resp);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn head_dir(server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"HEAD", server.url()).send()?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
assert_eq!(
|
||||
resp.headers().get("content-type").unwrap(),
|
||||
"text/html; charset=utf-8"
|
||||
);
|
||||
assert_eq!(resp.text()?, "");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn get_dir_404(server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(format!("{}404/", server.url()))?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn head_dir_404(server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"HEAD", format!("{}404/", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn get_dir_zip(server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(format!("{}?zip", server.url()))?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
assert_eq!(
|
||||
resp.headers().get("content-type").unwrap(),
|
||||
"application/zip"
|
||||
);
|
||||
assert!(resp.headers().contains_key("content-disposition"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn head_dir_zip(server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"HEAD", format!("{}?zip", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
assert_eq!(
|
||||
resp.headers().get("content-type").unwrap(),
|
||||
"application/zip"
|
||||
);
|
||||
assert!(resp.headers().contains_key("content-disposition"));
|
||||
assert_eq!(resp.text()?, "");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn get_dir_search(server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(format!("{}?q={}", server.url(), "test.html"))?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let paths = utils::retrive_index_paths(&resp.text()?);
|
||||
assert!(!paths.is_empty());
|
||||
for p in paths {
|
||||
assert!(p.contains(&"test.html"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn head_dir_search(server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"HEAD", format!("{}?q={}", server.url(), "test.html")).send()?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
assert_eq!(
|
||||
resp.headers().get("content-type").unwrap(),
|
||||
"text/html; charset=utf-8"
|
||||
);
|
||||
assert_eq!(resp.text()?, "");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn get_file(server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(format!("{}index.html", server.url()))?;
|
||||
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("etag"));
|
||||
assert!(resp.headers().contains_key("last-modified"));
|
||||
assert!(resp.headers().contains_key("content-length"));
|
||||
assert_eq!(resp.text()?, "This is index.html");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn head_file(server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"HEAD", format!("{}index.html", server.url())).send()?;
|
||||
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("etag"));
|
||||
assert!(resp.headers().contains_key("last-modified"));
|
||||
assert!(resp.headers().contains_key("content-length"));
|
||||
assert_eq!(resp.text()?, "");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn get_file_404(server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(format!("{}404", server.url()))?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn head_file_404(server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"HEAD", format!("{}404", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn options_dir(server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"OPTIONS", format!("{}index.html", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 204);
|
||||
assert_eq!(
|
||||
resp.headers().get("allow").unwrap(),
|
||||
"GET,HEAD,PUT,OPTIONS,DELETE,PROPFIND,COPY,MOVE"
|
||||
);
|
||||
assert_eq!(resp.headers().get("dav").unwrap(), "1");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn put_file(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let url = format!("{}file1", server.url());
|
||||
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
||||
assert_eq!(resp.status(), 201);
|
||||
let resp = reqwest::blocking::get(url)?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn put_file_create_dir(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let url = format!("{}xyz/file1", server.url());
|
||||
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
||||
assert_eq!(resp.status(), 201);
|
||||
let resp = reqwest::blocking::get(url)?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn put_file_conflict_dir(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let url = format!("{}dira", server.url());
|
||||
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
||||
assert_eq!(resp.status(), 403);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn delete_file(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let url = format!("{}test.html", server.url());
|
||||
let resp = fetch!(b"DELETE", &url).send()?;
|
||||
assert_eq!(resp.status(), 204);
|
||||
let resp = reqwest::blocking::get(url)?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn delete_file_404(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"DELETE", format!("{}file1", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
30
tests/path_prefix.rs
Normal file
30
tests/path_prefix.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
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(())
|
||||
}
|
35
tests/render.rs
Normal file
35
tests/render.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
mod fixtures;
|
||||
|
||||
use fixtures::{server, Error, TestServer, DIR_NO_INDEX};
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
fn render_index(#[with(&["--render-index"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(server.url())?;
|
||||
let text = resp.text()?;
|
||||
assert_eq!(text, "This is index.html");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_index_404(#[with(&["--render-index"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(format!("{}/{}", server.url(), DIR_NO_INDEX))?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_spa(#[with(&["--render-spa"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(server.url())?;
|
||||
let text = resp.text()?;
|
||||
assert_eq!(text, "This is index.html");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_spa_no_404(#[with(&["--render-spa"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(format!("{}/{}", server.url(), DIR_NO_INDEX))?;
|
||||
let text = resp.text()?;
|
||||
assert_eq!(text, "This is index.html");
|
||||
Ok(())
|
||||
}
|
46
tests/symlink.rs
Normal file
46
tests/symlink.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
mod fixtures;
|
||||
mod utils;
|
||||
|
||||
use assert_fs::fixture::TempDir;
|
||||
use fixtures::{server, tmpdir, Error, TestServer};
|
||||
use rstest::rstest;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::symlink as symlink_dir;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::symlink_dir;
|
||||
|
||||
#[rstest]
|
||||
fn default_not_allow_symlink(server: TestServer, tmpdir: TempDir) -> Result<(), Error> {
|
||||
// Create symlink directory "foo" to point outside the root
|
||||
let dir = "foo";
|
||||
symlink_dir(tmpdir.path(), server.path().join(dir)).expect("Couldn't create symlink");
|
||||
let resp = reqwest::blocking::get(format!("{}{}", server.url(), dir))?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
let resp = reqwest::blocking::get(format!("{}{}/index.html", server.url(), dir))?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
let resp = reqwest::blocking::get(server.url())?;
|
||||
let paths = utils::retrive_index_paths(&resp.text()?);
|
||||
assert!(!paths.is_empty());
|
||||
assert!(!paths.contains(&format!("{}/", dir)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn allow_symlink(
|
||||
#[with(&["--allow-symlink"])] server: TestServer,
|
||||
tmpdir: TempDir,
|
||||
) -> Result<(), Error> {
|
||||
// Create symlink directory "foo" to point outside the root
|
||||
let dir = "foo";
|
||||
symlink_dir(tmpdir.path(), server.path().join(dir)).expect("Couldn't create symlink");
|
||||
let resp = reqwest::blocking::get(format!("{}{}", server.url(), dir))?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let resp = reqwest::blocking::get(format!("{}{}/index.html", server.url(), dir))?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let resp = reqwest::blocking::get(server.url())?;
|
||||
let paths = utils::retrive_index_paths(&resp.text()?);
|
||||
assert!(!paths.is_empty());
|
||||
assert!(paths.contains(&format!("{}/", dir)));
|
||||
Ok(())
|
||||
}
|
51
tests/tls.rs
Normal file
51
tests/tls.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
mod fixtures;
|
||||
mod utils;
|
||||
|
||||
use assert_cmd::Command;
|
||||
use fixtures::{server, Error, TestServer};
|
||||
use predicates::str::contains;
|
||||
use reqwest::blocking::ClientBuilder;
|
||||
use rstest::rstest;
|
||||
|
||||
/// Can start the server with TLS and receive encrypted responses.
|
||||
#[rstest]
|
||||
#[case(server(&[
|
||||
"--tls-cert", "tests/data/cert.pem",
|
||||
"--tls-key", "tests/data/key_pkcs8.pem",
|
||||
]))]
|
||||
#[case(server(&[
|
||||
"--tls-cert", "tests/data/cert.pem",
|
||||
"--tls-key", "tests/data/key_pkcs1.pem",
|
||||
]))]
|
||||
fn tls_works(#[case] server: TestServer) -> Result<(), Error> {
|
||||
let client = ClientBuilder::new()
|
||||
.danger_accept_invalid_certs(true)
|
||||
.build()?;
|
||||
let resp = client.get(server.url()).send()?.error_for_status()?;
|
||||
assert_index_resp!(resp);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wrong path for cert throws error.
|
||||
#[rstest]
|
||||
fn wrong_path_cert() -> Result<(), Error> {
|
||||
Command::cargo_bin("duf")?
|
||||
.args(&["--tls-cert", "wrong", "--tls-key", "tests/data/key.pem"])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(contains("error: Failed to access `wrong`"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wrong paths for key throws errors.
|
||||
#[rstest]
|
||||
fn wrong_path_key() -> Result<(), Error> {
|
||||
Command::cargo_bin("duf")?
|
||||
.args(&["--tls-cert", "tests/data/cert.pem", "--tls-key", "wrong"])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(contains("error: Failed to access `wrong`"));
|
||||
|
||||
Ok(())
|
||||
}
|
61
tests/utils.rs
Normal file
61
tests/utils.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use serde_json::Value;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_index_resp {
|
||||
($resp:ident) => {
|
||||
assert_index_resp!($resp, self::fixtures::FILES)
|
||||
};
|
||||
($resp:ident, $files:expr) => {
|
||||
assert_eq!($resp.status(), 200);
|
||||
let body = $resp.text()?;
|
||||
let paths = self::utils::retrive_index_paths(&body);
|
||||
assert!(!paths.is_empty());
|
||||
for file in $files {
|
||||
assert!(paths.contains(&file.to_string()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fetch {
|
||||
($method:literal, $url:expr) => {
|
||||
reqwest::blocking::Client::new().request(hyper::Method::from_bytes($method)?, $url)
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn retrive_index_paths(index: &str) -> HashSet<String> {
|
||||
retrive_index_paths_impl(index).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn encode_uri(v: &str) -> String {
|
||||
let parts: Vec<_> = v.split('/').map(urlencoding::encode).collect();
|
||||
parts.join("/")
|
||||
}
|
||||
|
||||
fn retrive_index_paths_impl(index: &str) -> Option<HashSet<String>> {
|
||||
let lines: Vec<&str> = index.lines().collect();
|
||||
let (i, _) = lines
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, v)| v.contains("const DATA"))?;
|
||||
let line = lines.get(i + 1)?;
|
||||
let value: Value = line.parse().ok()?;
|
||||
let paths = value
|
||||
.get("paths")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.flat_map(|v| {
|
||||
let name = v.get("name")?.as_str()?;
|
||||
let path_type = v.get("path_type")?.as_str()?;
|
||||
if path_type.ends_with("Dir") {
|
||||
Some(format!("{}/", name))
|
||||
} else {
|
||||
Some(name.to_owned())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Some(paths)
|
||||
}
|
203
tests/webdav.rs
Normal file
203
tests/webdav.rs
Normal file
|
@ -0,0 +1,203 @@
|
|||
mod fixtures;
|
||||
mod utils;
|
||||
|
||||
use fixtures::{server, Error, TestServer, FILES};
|
||||
use rstest::rstest;
|
||||
use xml::escape::escape_str_pcdata;
|
||||
|
||||
#[rstest]
|
||||
fn propfind_dir(server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"PROPFIND", format!("{}dira", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 207);
|
||||
let body = resp.text()?;
|
||||
assert!(body.contains("<D:href>/dira</D:href>"));
|
||||
assert!(body.contains("<D:displayname>dira</D:displayname>"));
|
||||
for f in FILES {
|
||||
assert!(body.contains(&format!("<D:href>/dira/{}</D:href>", utils::encode_uri(f))));
|
||||
assert!(body.contains(&format!(
|
||||
"<D:displayname>{}</D:displayname>",
|
||||
escape_str_pcdata(f)
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn propfind_dir_depth0(server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"PROPFIND", format!("{}dira", server.url()))
|
||||
.header("depth", "0")
|
||||
.send()?;
|
||||
assert_eq!(resp.status(), 207);
|
||||
let body = resp.text()?;
|
||||
assert!(body.contains("<D:href>/dira</D:href>"));
|
||||
assert!(body.contains("<D:displayname>dira</D:displayname>"));
|
||||
assert_eq!(
|
||||
body.lines()
|
||||
.filter(|v| *v == "<D:status>HTTP/1.1 200 OK</D:status>")
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn propfind_404(server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"PROPFIND", format!("{}404", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn propfind_file(server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"PROPFIND", format!("{}test.html", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 207);
|
||||
let body = resp.text()?;
|
||||
assert!(body.contains("<D:href>/test.html</D:href>"));
|
||||
assert!(body.contains("<D:displayname>test.html</D:displayname>"));
|
||||
assert_eq!(
|
||||
body.lines()
|
||||
.filter(|v| *v == "<D:status>HTTP/1.1 200 OK</D:status>")
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn proppatch_file(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"PROPPATCH", format!("{}test.html", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 207);
|
||||
let body = resp.text()?;
|
||||
assert!(body.contains("<D:href>/test.html</D:href>"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn proppatch_404(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"PROPPATCH", format!("{}404", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn mkcol_dir(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"MKCOL", format!("{}newdir", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 201);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn mkcol_not_allow_upload(server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"MKCOL", format!("{}newdir", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 403);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn copy_file(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let new_url = format!("{}test2.html", server.url());
|
||||
let resp = fetch!(b"COPY", format!("{}test.html", server.url()))
|
||||
.header("Destination", &new_url)
|
||||
.send()?;
|
||||
assert_eq!(resp.status(), 204);
|
||||
let resp = reqwest::blocking::get(new_url)?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn copy_not_allow_upload(server: TestServer) -> Result<(), Error> {
|
||||
let new_url = format!("{}test2.html", server.url());
|
||||
let resp = fetch!(b"COPY", format!("{}test.html", server.url()))
|
||||
.header("Destination", &new_url)
|
||||
.send()?;
|
||||
assert_eq!(resp.status(), 403);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn copy_file_404(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let new_url = format!("{}test2.html", server.url());
|
||||
let resp = fetch!(b"COPY", format!("{}404", server.url()))
|
||||
.header("Destination", &new_url)
|
||||
.send()?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn move_file(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let origin_url = format!("{}test.html", server.url());
|
||||
let new_url = format!("{}test2.html", server.url());
|
||||
let resp = fetch!(b"MOVE", &origin_url)
|
||||
.header("Destination", &new_url)
|
||||
.send()?;
|
||||
assert_eq!(resp.status(), 204);
|
||||
let resp = reqwest::blocking::get(new_url)?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let resp = reqwest::blocking::get(origin_url)?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn move_not_allow_upload(#[with(&["--allow-delete"])] server: TestServer) -> Result<(), Error> {
|
||||
let origin_url = format!("{}test.html", server.url());
|
||||
let new_url = format!("{}test2.html", server.url());
|
||||
let resp = fetch!(b"MOVE", &origin_url)
|
||||
.header("Destination", &new_url)
|
||||
.send()?;
|
||||
assert_eq!(resp.status(), 403);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn move_not_allow_delete(#[with(&["--allow-upload"])] server: TestServer) -> Result<(), Error> {
|
||||
let origin_url = format!("{}test.html", server.url());
|
||||
let new_url = format!("{}test2.html", server.url());
|
||||
let resp = fetch!(b"MOVE", &origin_url)
|
||||
.header("Destination", &new_url)
|
||||
.send()?;
|
||||
assert_eq!(resp.status(), 403);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn move_file_404(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let new_url = format!("{}test2.html", server.url());
|
||||
let resp = fetch!(b"MOVE", format!("{}404", server.url()))
|
||||
.header("Destination", &new_url)
|
||||
.send()?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn lock_file(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"LOCK", format!("{}test.html", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let body = resp.text()?;
|
||||
assert!(body.contains("<D:href>/test.html</D:href>"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn lock_file_404(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"LOCK", format!("{}404", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn unlock_file(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"LOCK", format!("{}test.html", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn unlock_file_404(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = fetch!(b"LOCK", format!("{}404", server.url())).send()?;
|
||||
assert_eq!(resp.status(), 404);
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in a new issue