use super::*;
fn response() -> Response {
Response::builder().body(Body::empty()).unwrap()
}
#[test]
fn default_directive_is_self() {
let mut response = response();
insert_frame_ancestors(&mut response, DEFAULT_FRAME_ANCESTORS);
assert_eq!(
response.headers().get(&CSP).unwrap(),
"frame-ancestors 'self'"
);
}
#[test]
fn an_existing_policy_wins() {
let mut response = response();
response
.headers_mut()
.insert(CSP, header::HeaderValue::from_static("default-src 'none'"));
insert_frame_ancestors(&mut response, "'self'");
assert_eq!(
response.headers().get(&CSP).unwrap(),
"default-src 'none'",
"an app policy must not be overwritten (or duplicated)"
);
}
#[test]
fn an_invalid_directive_is_dropped_not_panicked() {
let mut response = response();
insert_frame_ancestors(&mut response, "'self'\r\nX-Evil: 1");
assert!(response.headers().get(&CSP).is_none());
}
fn file_response(content_type: Option<&str>) -> Response {
let mut response = response();
if let Some(content_type) = content_type {
response.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_str(content_type).unwrap(),
);
}
response
}
fn policy(response: &Response) -> &str {
response.headers().get(&CSP).unwrap().to_str().unwrap()
}
fn disposition(response: &Response) -> Option<&str> {
response
.headers()
.get(header::CONTENT_DISPOSITION)
.map(|value| value.to_str().unwrap())
}
#[test]
fn the_allow_list_renders_inline() {
for content_type in [
"image/png",
"image/jpeg",
"image/gif",
"image/webp",
"image/avif",
"video/mp4",
"video/webm",
"audio/mpeg",
"audio/ogg",
"audio/wav",
"text/plain",
"TEXT/PLAIN; charset=utf-8",
] {
let mut response = file_response(Some(content_type));
harden_served_file(&mut response);
assert_eq!(
response
.headers()
.get(header::X_CONTENT_TYPE_OPTIONS)
.unwrap(),
"nosniff",
"{content_type} must not be sniffed"
);
assert!(
policy(&response).contains("sandbox"),
"{content_type} must carry the sandbox policy"
);
assert_eq!(
disposition(&response),
None,
"{content_type} renders inline"
);
}
}
#[test]
fn everything_else_downloads() {
for content_type in [
"image/svg+xml",
"text/html; charset=utf-8",
"application/pdf",
] {
let mut response = file_response(Some(content_type));
harden_served_file(&mut response);
assert_eq!(
disposition(&response),
Some("attachment"),
"{content_type} must download"
);
assert!(
policy(&response).contains("sandbox"),
"{content_type} must carry the sandbox policy"
);
}
}
#[test]
fn a_missing_content_type_downloads() {
let mut response = file_response(None);
harden_served_file(&mut response);
assert_eq!(disposition(&response), Some("attachment"));
}
#[test]
fn an_existing_disposition_is_kept_only_when_it_downloads() {
let mut response = file_response(Some("text/html"));
response.headers_mut().insert(
header::CONTENT_DISPOSITION,
header::HeaderValue::from_static("attachment; filename=\"report.html\""),
);
harden_served_file(&mut response);
assert_eq!(
disposition(&response),
Some("attachment; filename=\"report.html\""),
"the directory route's own filename survives"
);
let mut response = file_response(Some("text/html"));
response.headers_mut().insert(
header::CONTENT_DISPOSITION,
header::HeaderValue::from_static("inline"),
);
harden_served_file(&mut response);
assert_eq!(
disposition(&response),
Some("attachment"),
"an inline disposition is replaced, never left to render"
);
}
#[test]
fn a_not_modified_response_carries_the_policy_and_no_disposition() {
let mut response = file_response(None);
*response.status_mut() = StatusCode::NOT_MODIFIED;
harden_served_file(&mut response);
assert_eq!(
response
.headers()
.get(header::X_CONTENT_TYPE_OPTIONS)
.unwrap(),
"nosniff"
);
assert!(policy(&response).contains("sandbox"));
assert_eq!(disposition(&response), None);
}
#[test]
fn an_existing_policy_is_overwritten() {
let mut response = file_response(Some("text/html"));
response
.headers_mut()
.insert(CSP, header::HeaderValue::from_static("script-src *"));
harden_served_file(&mut response);
assert_eq!(
policy(&response),
"default-src 'none'; img-src 'self'; media-src 'self'; \
style-src 'unsafe-inline'; sandbox; frame-ancestors 'self'"
);
}