0.34.6_repo_cleanup

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-22 09:31:15 +02:00
1 parent ce1a90397c
commit 133d2f0f91
273 files changed
+5969 -6926

No files matched your search

+115
View File
@@ -0,0 +1,115 @@
use anyhow::{ensure, Result};
use http::{HeaderMap, HeaderValue};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct SecurityHeaders {
pub hsts: Hsts,
pub nosniff: bool,
pub frame_options: FrameOptions,
pub referrer_policy: Option<String>,
pub content_security_policy: Option<String>,
pub permissions_policy: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct Hsts {
pub enabled: bool,
pub max_age_seconds: u64,
pub include_subdomains: bool,
pub preload: bool,
}
impl Default for Hsts {
fn default() -> Self {
Self {
enabled: false,
max_age_seconds: 31_536_000,
include_subdomains: false,
preload: false,
}
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FrameOptions {
#[default]
Preserve,
Deny,
Sameorigin,
}
#[derive(Debug, Clone, Default)]
pub struct CompiledSecurity {
hsts: Option<HeaderValue>,
headers: Vec<(&'static str, HeaderValue)>,
}
impl SecurityHeaders {
pub fn compile(&self) -> Result<CompiledSecurity> {
ensure!(
self.hsts.max_age_seconds <= 63_072_000,
"HSTS max_age_seconds must be 0..63072000"
);
ensure!(
!self.hsts.preload
|| (self.hsts.enabled
&& self.hsts.include_subdomains
&& self.hsts.max_age_seconds >= 31_536_000),
"HSTS preload requires enabled, include_subdomains and at least one year"
);
let mut result = CompiledSecurity::default();
if self.hsts.enabled {
let mut value = format!("max-age={}", self.hsts.max_age_seconds);
if self.hsts.include_subdomains {
value.push_str("; includeSubDomains");
}
if self.hsts.preload {
value.push_str("; preload");
}
result.hsts = Some(value.parse()?);
}
if self.nosniff {
result.headers.push((
"x-content-type-options",
HeaderValue::from_static("nosniff"),
));
}
let frame = match self.frame_options {
FrameOptions::Preserve => None,
FrameOptions::Deny => Some("DENY"),
FrameOptions::Sameorigin => Some("SAMEORIGIN"),
};
if let Some(value) = frame {
result
.headers
.push(("x-frame-options", HeaderValue::from_static(value)));
}
for (name, value) in [
("referrer-policy", &self.referrer_policy),
("content-security-policy", &self.content_security_policy),
("permissions-policy", &self.permissions_policy),
] {
if let Some(value) = value {
ensure!(
!value.is_empty() && value.len() <= 8192,
"security header must contain 1..8192 bytes"
);
result.headers.push((name, value.parse()?));
}
}
Ok(result)
}
}
impl CompiledSecurity {
pub fn apply(&self, headers: &mut HeaderMap, tls: bool) {
for (name, value) in &self.headers {
headers.insert(*name, value.clone());
}
if !tls {
headers.remove("strict-transport-security");
} else if let Some(value) = &self.hsts {
headers.insert("strict-transport-security", value.clone());
}
}
}
#[cfg(test)]
include!("tests.rs");