mod common;
use common::{body_bytes, request_with_headers};
use hyper::Method;
use mini_static::Server;
use std::fs;
use tempfile::TempDir;
const ORIGINAL: &[u8] = b"body{color:red}\n";
const OUTSIDE: &[u8] = b"TOP SECRET OUTSIDE ROOT\n";
fn root_with_sidecar_link(target: SidecarLink) -> (TempDir, TempDir) {
let outside = TempDir::new().unwrap();
let root = TempDir::new().unwrap();
fs::write(root.path().join("styles.css"), ORIGINAL).unwrap();
let secret = outside.path().join("secret.txt");
fs::write(&secret, OUTSIDE).unwrap();
let inside = root.path().join("shared-assets.br");
fs::write(&inside, b"legitimate-brotli-bytes").unwrap();
let link_target = match target {
SidecarLink::Outside => secret,
SidecarLink::Inside => inside,
};
std::os::unix::fs::symlink(link_target, root.path().join("styles.css.br")).unwrap();
(root, outside)
}
enum SidecarLink {
Outside,
Inside,
}
#[tokio::test]
async fn a_sidecar_symlinked_outside_the_root_is_declined() {
let (root, _outside) = root_with_sidecar_link(SidecarLink::Outside);
let server = Server::new(root.path()).unwrap();
let response = request_with_headers(
&server,
&Method::GET,
"/styles.css",
&[("accept-encoding", "br")],
)
.await;
assert!(
!response.headers().contains_key("content-encoding"),
"an escaping sidecar must be declined, not served: got content-encoding {:?}",
response.headers().get("content-encoding"),
);
let body = body_bytes(response).await;
assert_eq!(
body.as_ref(),
ORIGINAL,
"expected the original file's bytes; serving {} bytes means the root was escaped",
body.len(),
);
assert_ne!(
body.as_ref(),
OUTSIDE,
"the sidecar probe served a file from outside the served root"
);
}
#[tokio::test]
async fn a_sidecar_symlinked_inside_the_root_is_still_served() {
let (root, _outside) = root_with_sidecar_link(SidecarLink::Inside);
let server = Server::new(root.path()).unwrap();
let response = request_with_headers(
&server,
&Method::GET,
"/styles.css",
&[("accept-encoding", "br")],
)
.await;
assert_eq!(
response
.headers()
.get("content-encoding")
.map(|value| value.to_str().unwrap()),
Some("br"),
"an in-root symlinked sidecar resolves inside the root and must be served"
);
let body = body_bytes(response).await;
assert_eq!(body.as_ref(), b"legitimate-brotli-bytes");
}
#[tokio::test]
async fn a_directory_named_like_a_sidecar_is_declined() {
let root = TempDir::new().unwrap();
fs::write(root.path().join("styles.css"), ORIGINAL).unwrap();
fs::create_dir(root.path().join("styles.css.br")).unwrap();
fs::write(
root.path().join("styles.css.br").join("index.html"),
b"not-a-sidecar",
)
.unwrap();
let server = Server::new(root.path()).unwrap();
let response = request_with_headers(
&server,
&Method::GET,
"/styles.css",
&[("accept-encoding", "br")],
)
.await;
assert!(
!response.headers().contains_key("content-encoding"),
"a directory is not a sidecar and must be declined"
);
let body = body_bytes(response).await;
assert_eq!(
body.as_ref(),
ORIGINAL,
"expected the original; the index-retry inside open_verified must not apply here"
);
}
#[tokio::test]
async fn the_same_symlink_requested_directly_is_refused() {
let (root, _outside) = root_with_sidecar_link(SidecarLink::Outside);
let server = Server::new(root.path()).unwrap();
let response = common::get(&server, "/styles.css.br").await;
assert_eq!(
response.status(),
hyper::StatusCode::NOT_FOUND,
"a symlink escaping the root is refused when requested directly"
);
}