ordinary_config/http/
csp.rs1use serde::{Deserialize, Serialize};
6
7#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
8#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
9#[derive(Deserialize, Serialize, Debug, Clone, Default)]
10pub struct HttpCsp {
11 #[serde(skip_serializing_if = "Option::is_none")]
13 #[serde(default)]
14 pub default_src: Option<String>,
15
16 #[serde(skip_serializing_if = "Option::is_none")]
19 #[serde(default)]
20 pub script_src: Option<String>,
21
22 #[serde(skip_serializing_if = "Option::is_none")]
25 #[serde(default)]
26 pub style_src: Option<String>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
32 #[serde(default)]
33 pub font_src: Option<String>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
39 #[serde(default)]
40 pub img_src: Option<String>,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
46 #[serde(default)]
47 pub frame_src: Option<String>,
48
49 #[serde(skip_serializing_if = "Option::is_none")]
51 #[serde(default)]
52 pub include_inline_hashes: Option<bool>,
53}
54
55impl HttpCsp {
56 #[must_use]
57 #[allow(clippy::too_many_lines)]
58 pub fn build_string(
59 &self,
60 base: &Self,
61 inline_style_hashes: Option<Vec<String>>,
62 inline_script_hashes: Option<Vec<String>>,
63 secure: bool,
64 has_wasm: bool,
65 ) -> String {
66 let mut out = String::new();
67
68 let include_inline_hashes = self
69 .include_inline_hashes
70 .unwrap_or(base.include_inline_hashes.unwrap_or(true));
71
72 let default_src = self
73 .default_src
74 .clone()
75 .unwrap_or(base.default_src.clone().unwrap_or("'self'".to_string()));
76
77 if !default_src.is_empty() {
78 out.push_str("default-src ");
79 out.push_str(default_src.as_str());
80 out.push_str("; ");
81 }
82
83 let mut script_src = self
84 .script_src
85 .clone()
86 .unwrap_or(base.script_src.clone().unwrap_or_default());
87
88 if include_inline_hashes
89 && let Some(script_hashes) = inline_script_hashes
90 && !script_hashes.is_empty()
91 {
92 if script_src.is_empty() {
93 script_src.push_str("'self'");
94 if has_wasm {
95 script_src.push_str(" 'wasm-unsafe-eval'");
96 }
97 }
98
99 for hash in script_hashes {
100 script_src.push_str(" '");
101 script_src.push_str(hash.as_str());
102 script_src.push('\'');
103 }
104 }
105
106 if !script_src.is_empty() {
107 out.push_str("script-src ");
108 out.push_str(script_src.as_str());
109 out.push_str("; ");
110 }
111
112 let mut style_src = self
113 .style_src
114 .clone()
115 .unwrap_or(base.style_src.clone().unwrap_or_default());
116
117 if include_inline_hashes
118 && let Some(style_hashes) = inline_style_hashes
119 && !style_hashes.is_empty()
120 {
121 if style_src.is_empty() {
122 style_src.push_str("'self'");
123 }
124
125 for hash in style_hashes {
126 style_src.push_str(" '");
127 style_src.push_str(hash.as_str());
128 style_src.push('\'');
129 }
130 }
131
132 if !style_src.is_empty() {
133 out.push_str("style-src ");
134 out.push_str(style_src.as_str());
135 out.push_str("; ");
136 }
137
138 let font_src = self
139 .font_src
140 .clone()
141 .unwrap_or(base.font_src.clone().unwrap_or_default());
142 if !font_src.is_empty() {
143 out.push_str("font-src ");
144 out.push_str(font_src.as_str());
145 out.push_str("; ");
146 }
147
148 let img_src = self
149 .img_src
150 .clone()
151 .unwrap_or(base.img_src.clone().unwrap_or_default());
152 if !img_src.is_empty() {
153 out.push_str("img-src ");
154 out.push_str(img_src.as_str());
155 out.push_str("; ");
156 }
157
158 let frame_src = self
159 .frame_src
160 .clone()
161 .unwrap_or(base.frame_src.clone().unwrap_or_default());
162 if !frame_src.is_empty() {
163 out.push_str("frame-src ");
164 out.push_str(frame_src.as_str());
165 out.push_str("; ");
166 }
167
168 if secure {
169 out.push_str("upgrade-insecure-requests; ");
170 }
171
172 out.push_str("report-to csp");
173
174 out = out.trim().to_string();
175 out
176 }
177}