mod common;
use std::fs;
use mini_static::Server;
use tempfile::TempDir;
fn root_with_nested_file() -> TempDir {
let root = TempDir::new().unwrap();
fs::create_dir(root.path().join("admin")).unwrap();
fs::write(root.path().join("admin/config"), b"SECRET").unwrap();
fs::write(root.path().join("plain.txt"), b"ordinary").unwrap();
root
}
#[tokio::test]
async fn the_unencoded_path_still_serves_the_nested_file() {
let root = root_with_nested_file();
let server = Server::new(root.path()).unwrap();
let response = common::get(&server, "/admin/config").await;
assert_eq!(response.status().as_u16(), 200);
assert_eq!(&common::body_bytes(response).await[..], b"SECRET");
}
#[tokio::test]
async fn an_encoded_separator_does_not_reach_a_nested_file() {
let root = root_with_nested_file();
let server = Server::new(root.path()).unwrap();
for path in ["/admin%2Fconfig", "/admin%2fconfig", "/%61dmin%2Fconfig"] {
let response = common::get(&server, path).await;
assert_eq!(
response.status().as_u16(),
404,
"{path} reached a nested file through an encoded separator"
);
}
}
#[tokio::test]
async fn an_encoded_backslash_is_refused() {
let root = root_with_nested_file();
fs::write(root.path().join(r"admin\config"), b"BACKSLASH-NAMED").unwrap();
let server = Server::new(root.path()).unwrap();
let response = common::get(&server, "/admin%5Cconfig").await;
assert_eq!(
response.status().as_u16(),
404,
"a decoded backslash resolved to a file"
);
}
#[tokio::test]
async fn an_encoded_dot_dot_is_still_refused() {
let root = root_with_nested_file();
let server = Server::new(root.path()).unwrap().with_hidden_files();
for path in ["/admin/../plain.txt", "/admin/%2E%2E/plain.txt", "/%2e%2e/plain.txt"] {
let response = common::get(&server, path).await;
assert_eq!(response.status().as_u16(), 404, "{path} was not refused");
}
}
#[tokio::test]
async fn ordinary_percent_encoding_still_resolves() {
let root = TempDir::new().unwrap();
fs::write(root.path().join("a file.txt"), b"spaced").unwrap();
fs::write(root.path().join("café.txt"), b"accented").unwrap();
fs::create_dir(root.path().join("docs")).unwrap();
fs::write(root.path().join("docs/index.html"), b"<html>docs</html>").unwrap();
let server = Server::new(root.path()).unwrap();
for (path, expected) in [
("/a%20file.txt", &b"spaced"[..]),
("/caf%C3%A9.txt", &b"accented"[..]),
("/docs/index.htm%6C", &b"<html>docs</html>"[..]),
] {
let response = common::get(&server, path).await;
assert_eq!(response.status().as_u16(), 200, "{path} should resolve");
assert_eq!(&common::body_bytes(response).await[..], expected, "{path}");
}
}