mod common;
use std::fs;
use mini_static::Server;
use tempfile::TempDir;
fn root_with_dotfiles() -> TempDir {
let root = TempDir::new().unwrap();
fs::write(root.path().join(".env"), b"API_KEY=super-secret").unwrap();
fs::create_dir(root.path().join(".git")).unwrap();
fs::write(root.path().join(".git/config"), b"[remote origin]").unwrap();
fs::create_dir(root.path().join(".well-known")).unwrap();
fs::write(root.path().join(".well-known/security.txt"), b"Contact: x").unwrap();
fs::write(root.path().join(".well-known/.hidden"), b"still secret").unwrap();
fs::write(root.path().join("real.html"), b"<html>Real</html>").unwrap();
root
}
#[tokio::test]
async fn dotfiles_are_denied_by_default() {
let root = root_with_dotfiles();
let server = Server::new(root.path()).unwrap();
for path in ["/.env", "/.git/config", "/.git/"] {
let response = common::get(&server, path).await;
assert_eq!(response.status().as_u16(), 404, "{path} should not serve");
let body = common::body_bytes(response).await;
assert!(
!String::from_utf8_lossy(&body).contains("super-secret"),
"{path} leaked file contents"
);
}
}
#[tokio::test]
async fn an_existing_dotfile_is_indistinguishable_from_a_missing_one() {
let root = root_with_dotfiles();
let server = Server::new(root.path()).unwrap();
let present = common::get(&server, "/.env").await;
let absent = common::get(&server, "/.nonexistent-file").await;
assert_eq!(present.status(), absent.status());
assert_eq!(
common::body_bytes(present).await,
common::body_bytes(absent).await
);
}
#[tokio::test]
async fn well_known_is_served_despite_its_leading_dot() {
let root = root_with_dotfiles();
let server = Server::new(root.path()).unwrap();
let response = common::get(&server, "/.well-known/security.txt").await;
assert_eq!(response.status().as_u16(), 200);
assert_eq!(
common::body_bytes(response).await.as_ref(),
b"Contact: x",
"the .well-known exception must serve real content"
);
}
#[tokio::test]
async fn a_dotfile_nested_under_well_known_is_still_denied() {
let root = root_with_dotfiles();
let server = Server::new(root.path()).unwrap();
let response = common::get(&server, "/.well-known/.hidden").await;
assert_eq!(response.status().as_u16(), 404);
}
#[tokio::test]
async fn a_same_directory_reference_is_not_a_hidden_segment() {
let root = root_with_dotfiles();
let server = Server::new(root.path()).unwrap();
let response = common::get(&server, "/./real.html").await;
assert_eq!(response.status().as_u16(), 200);
assert_eq!(
common::body_bytes(response).await.as_ref(),
b"<html>Real</html>"
);
}
#[tokio::test]
async fn ordinary_files_are_unaffected() {
let root = root_with_dotfiles();
let server = Server::new(root.path()).unwrap();
let response = common::get(&server, "/real.html").await;
assert_eq!(response.status().as_u16(), 200);
}
#[tokio::test]
async fn a_percent_encoded_dot_does_not_bypass_the_check() {
let root = root_with_dotfiles();
let server = Server::new(root.path()).unwrap();
for path in ["/%2Eenv", "/%2e%67it/config"] {
assert_eq!(
common::get(&server, path).await.status().as_u16(),
404,
"{path} bypassed the hidden-file check"
);
}
}
#[tokio::test]
async fn with_hidden_files_opts_back_in() {
let root = root_with_dotfiles();
let server = Server::new(root.path()).unwrap().with_hidden_files();
let response = common::get(&server, "/.env").await;
assert_eq!(response.status().as_u16(), 200);
assert_eq!(
common::body_bytes(response).await.as_ref(),
b"API_KEY=super-secret",
"with_hidden_files must restore the old behavior in full"
);
}