junobuild_storage/http/
headers.rs

1use crate::constants::ASSET_ENCODING_NO_COMPRESSION;
2use crate::http::types::HeaderField;
3use crate::types::config::{StorageConfig, StorageConfigIFrame};
4use crate::types::store::{Asset, AssetEncoding, EncodingType};
5use crate::url::matching_urls;
6use hex::encode;
7
8pub fn build_headers(
9    asset: &Asset,
10    encoding: &AssetEncoding,
11    encoding_type: &EncodingType,
12    config: &StorageConfig,
13) -> Vec<HeaderField> {
14    let mut headers = asset.headers.clone();
15
16    // The Accept-Ranges HTTP response header is a marker used by the server to advertise its support for partial requests from the client for file downloads.
17    headers.push(HeaderField(
18        "accept-ranges".to_string(),
19        "bytes".to_string(),
20    ));
21
22    headers.push(HeaderField(
23        "etag".to_string(),
24        format!("\"{}\"", encode(encoding.sha256)),
25    ));
26
27    // Headers for security
28    headers.extend(security_headers());
29
30    // iFrame with default to DENY for security reason
31    if let Some(iframe_header) = iframe_headers(&config.unwrap_iframe()) {
32        headers.push(iframe_header);
33    }
34
35    if encoding_type.clone() != *ASSET_ENCODING_NO_COMPRESSION {
36        headers.push(HeaderField(
37            "Content-Encoding".to_string(),
38            encoding_type.to_string(),
39        ));
40    }
41
42    // Headers build from the configuration
43    let config_headers = build_config_headers(&asset.key.full_path, config);
44    headers.extend(config_headers);
45
46    headers
47}
48
49pub fn build_redirect_headers(location: &str, iframe: &StorageConfigIFrame) -> Vec<HeaderField> {
50    let mut headers = Vec::new();
51
52    // Headers for security
53    headers.extend(security_headers());
54
55    // iFrame with default to none
56    if let Some(iframe_header) = iframe_headers(iframe) {
57        headers.push(iframe_header);
58    }
59
60    headers.push(HeaderField("Location".to_string(), location.to_string()));
61
62    headers
63}
64
65// Source: NNS-dapp
66/// List of recommended security headers as per https://owasp.org/www-satellite-secure-headers/
67/// These headers enable browser security features (like limit access to platform apis and set
68/// iFrame policies, etc.).
69fn security_headers() -> Vec<HeaderField> {
70    vec![
71        HeaderField("X-Content-Type-Options".to_string(), "nosniff".to_string()),
72        HeaderField(
73            "Strict-Transport-Security".to_string(),
74            "max-age=31536000 ; includeSubDomains".to_string(),
75        ),
76        // "Referrer-Policy: no-referrer" would be more strict, but breaks local dev deployment
77        // same-origin is still ok from a security perspective
78        HeaderField("Referrer-Policy".to_string(), "same-origin".to_string()),
79    ]
80}
81
82fn iframe_headers(iframe: &StorageConfigIFrame) -> Option<HeaderField> {
83    match iframe {
84        StorageConfigIFrame::Deny => Some(HeaderField(
85            "X-Frame-Options".to_string(),
86            "DENY".to_string(),
87        )),
88        StorageConfigIFrame::SameOrigin => Some(HeaderField(
89            "X-Frame-Options".to_string(),
90            "SAMEORIGIN".to_string(),
91        )),
92        StorageConfigIFrame::AllowAny => None,
93    }
94}
95
96pub fn build_config_headers(
97    requested_path: &str,
98    StorageConfig {
99        headers: config_headers,
100        ..
101    }: &StorageConfig,
102) -> Vec<HeaderField> {
103    matching_urls(requested_path, config_headers)
104        .iter()
105        .flat_map(|(_, headers)| headers.clone())
106        .collect()
107}