owasp_headers/
lib.rs

1#![deny(clippy::all, unsafe_code)]
2#![warn(missing_docs)]
3
4//! Modern web browsers may prevent or mitigate security vulnerabilities when they encounter the
5//! [HTTP response headers recommended by OWASP](https://owasp.org/www-project-secure-headers/).
6//!
7//! This crate offers these HTTP headers and their values,
8//! so that they may be more-conveniently used when developing web services in Rust.
9//!
10//! Example:
11//! ```
12//! use std::convert::Infallible;
13//! use http::response::Parts;
14//! use hyper::{self, Request, Response};
15//!
16//! // e.g. https://docs.rs/tower/latest/tower/fn.service_fn.html
17//! async fn handle(_req: Request<()>) -> http::Result<Response<String>> {
18//!     let mut response = Response::new(String::from("hello, world!"));
19//!     let mut headers = response.headers_mut();
20//!     headers.extend(owasp_headers::headers().into_iter());
21//!     Ok(response)
22//! }
23//! ```
24//!
25//! If you are developing a web service using
26//! [`tower::Service`](https://docs.rs/tower/0.5/tower/trait.Service.html),
27//! you may find it even more convenient to apply these headers using
28//! [tower-default-headers](https://crates.io/crates/tower-default-headers).
29
30use http::header::{
31    CACHE_CONTROL, CONTENT_SECURITY_POLICY, HeaderMap, HeaderValue, PRAGMA, REFERRER_POLICY,
32    X_CONTENT_TYPE_OPTIONS, X_FRAME_OPTIONS,
33};
34
35/// produces an owned-collection of headers and their values
36pub fn headers() -> HeaderMap {
37    let mut h = HeaderMap::new();
38    h.reserve(13);
39    h.insert(
40        HTTP_STRICT_TRANSPORT_SECURITY,
41        HeaderValue::from_static(HTTP_STRICT_TRANSPORT_SECURITY_DEFAULT),
42    );
43    h.insert(
44        X_FRAME_OPTIONS,
45        HeaderValue::from_static(X_FRAME_OPTIONS_DEFAULT),
46    );
47    h.insert(
48        X_CONTENT_TYPE_OPTIONS,
49        HeaderValue::from_static(X_CONTENT_TYPE_OPTIONS_DEFAULT),
50    );
51    h.insert(
52        CONTENT_SECURITY_POLICY,
53        HeaderValue::from_static(CONTENT_SECURITY_POLICY_DEFAULT),
54    );
55    h.insert(
56        X_PERMITTED_CROSS_DOMAIN_POLICIES,
57        HeaderValue::from_static(X_PERMITTED_CROSS_DOMAIN_POLICIES_DEFAULT),
58    );
59    h.insert(
60        REFERRER_POLICY,
61        HeaderValue::from_static(REFERRER_POLICY_DEFAULT),
62    );
63    h.insert(
64        CLEAR_SITE_DATA,
65        HeaderValue::from_static(CLEAR_SITE_DATA_DEFAULT),
66    );
67    h.insert(
68        CROSS_ORIGIN_EMBEDDER_POLICY,
69        HeaderValue::from_static(CROSS_ORIGIN_EMBEDDER_POLICY_DEFAULT),
70    );
71    h.insert(
72        CROSS_ORIGIN_OPENER_POLICY,
73        HeaderValue::from_static(SAME_ORIGIN),
74    );
75    h.insert(
76        CROSS_ORIGIN_RESOURCE_POLICY,
77        HeaderValue::from_static(SAME_ORIGIN),
78    );
79    h.insert(
80        PERMISSIONS_POLICY,
81        HeaderValue::from_static(PERMISSIONS_POLICY_DEFAULT),
82    );
83    h.insert(
84        CACHE_CONTROL,
85        HeaderValue::from_static(CACHE_CONTROL_DEFAULT),
86    );
87    h.insert(PRAGMA, HeaderValue::from_static(PRAGMA_DEFAULT));
88    h
89}
90
91const CACHE_CONTROL_DEFAULT: &str = "no-store, max-age=0";
92const CLEAR_SITE_DATA: &str = "clear-site-data";
93const CLEAR_SITE_DATA_DEFAULT: &str = r#""cache","cookies","storage""#;
94const CONTENT_SECURITY_POLICY_DEFAULT: &str = "default-src 'self'; object-src 'none'; child-src 'self'; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content";
95const CROSS_ORIGIN_EMBEDDER_POLICY: &str = "cross-origin-embedder-policy";
96const CROSS_ORIGIN_EMBEDDER_POLICY_DEFAULT: &str = "require-corp";
97const CROSS_ORIGIN_OPENER_POLICY: &str = "cross-origin-opener-policy";
98const CROSS_ORIGIN_RESOURCE_POLICY: &str = "cross-origin-resource-policy";
99const HTTP_STRICT_TRANSPORT_SECURITY: &str = "http-strict-transport-security";
100const HTTP_STRICT_TRANSPORT_SECURITY_DEFAULT: &str = "max-age=31536000 ; includeSubDomains";
101const PERMISSIONS_POLICY: &str = "permissions-policy";
102const PERMISSIONS_POLICY_DEFAULT: &str = "accelerometer=(),autoplay=(),camera=(),display-capture=(),document-domain=(),encrypted-media=(),fullscreen=(),geolocation=(),gyroscope=(),magnetometer=(),microphone=(),midi=(),payment=(),picture-in-picture=(),publickey-credentials-get=(),screen-wake-lock=(),sync-xhr=(self),usb=(),web-share=(),xr-spatial-tracking=()";
103const PRAGMA_DEFAULT: &str = "no-cache";
104const REFERRER_POLICY_DEFAULT: &str = "no-referrer";
105const SAME_ORIGIN: &str = "same-origin";
106const X_CONTENT_TYPE_OPTIONS_DEFAULT: &str = "nosniff";
107const X_FRAME_OPTIONS_DEFAULT: &str = "deny";
108const X_PERMITTED_CROSS_DOMAIN_POLICIES: &str = "x-permitted-cross-domain-policies";
109const X_PERMITTED_CROSS_DOMAIN_POLICIES_DEFAULT: &str = "none";
110
111#[cfg(test)]
112mod tests {
113    use std::{fs::read_to_string, path::Path};
114
115    use super::*;
116
117    #[test]
118    fn headers_returns_headermap_with_expected_len() {
119        let got = headers();
120        assert_eq!(got.len(), 13);
121    }
122
123    #[test]
124    fn headers_returns_headermap_with_expected_contents() {
125        let got = headers();
126        let got_text = format!("{:#?}", got);
127
128        let fixture_path = Path::new("./fixtures/headers.txt");
129        let fixture_data =
130            read_to_string(fixture_path).expect("could not read fixtures/headers.txt");
131
132        assert_eq!(got_text.trim(), fixture_data.trim());
133    }
134}