1use std::borrow::Cow;
13use crate::client_hint::ClientHint;
14use crate::cors::Cors;
15use crate::ext::date_time_ext::DateTimeExt;
16use crate::ext::string_ext::StringExt;
17use crate::range::Range;
18use crate::request::Request;
19use crate::symbol::SYMBOL;
20
21#[cfg(test)]
22mod tests;
23
24pub(crate) mod sf;
25
26pub mod content_disposition;
27
28#[cfg(test)]
29mod example;
30
31#[derive(PartialEq, Eq, Clone, Debug)]
43pub struct Header {
44 pub name: Cow<'static, str>,
46 pub value: Cow<'static, str>,
48}
49
50struct FixedHeaders {
52 before_date: Vec<Header>,
53 after_date: Vec<Header>,
54}
55
56impl Header {
57
58 pub fn new(name: impl Into<Cow<'static, str>>, value: impl Into<Cow<'static, str>>) -> Header {
67 Header { name: name.into(), value: value.into() }
68 }
69
70 pub(crate) fn name_from(name: &str) -> Cow<'static, str> {
75 const COMMON: &[&str] = &[
76 "Host", "host", "User-Agent", "user-agent", "Accept", "accept",
77 "Accept-Encoding", "accept-encoding", "Accept-Language", "accept-language",
78 "Connection", "connection", "Content-Type", "content-type",
79 "Content-Length", "content-length", "Date", "date", "Server", "server",
80 "Last-Modified", "last-modified", "ETag", "etag", "Accept-Ranges", "accept-ranges",
81 "Cache-Control", "cache-control", "Cookie", "cookie", "Set-Cookie", "set-cookie",
82 "Referer", "referer", "Origin", "origin", "Authorization", "authorization",
83 "If-None-Match", "if-none-match", "If-Modified-Since", "if-modified-since",
84 "Transfer-Encoding", "transfer-encoding", "Keep-Alive", "keep-alive",
85 "Vary", "vary", "Upgrade-Insecure-Requests", "upgrade-insecure-requests",
86 "X-Forwarded-For", "x-forwarded-for", "Via", "via",
87 ];
88 match COMMON.iter().find(|common| **common == name) {
89 Some(common) => Cow::Borrowed(common),
90 None => Cow::Owned(name.to_string()),
91 }
92 }
93
94 pub fn as_string(&self) -> String {
95 let formatted = format!("{}: {}", self.name, self.value);
96 formatted
97 }
98
99 pub const _ACCESS_CONTROL_REQUEST_METHOD: &'static str = "Access-Control-Request-Method";
100 pub const _ACCESS_CONTROL_REQUEST_HEADERS: &'static str = "Access-Control-Request-Headers";
101 pub const _ACCESS_CONTROL_ALLOW_ORIGIN: &'static str = "Access-Control-Allow-Origin";
102 pub const _ACCESS_CONTROL_ALLOW_METHODS: &'static str = "Access-Control-Allow-Methods";
103 pub const _ACCESS_CONTROL_ALLOW_HEADERS: &'static str = "Access-Control-Allow-Headers";
104 pub const _ACCESS_CONTROL_ALLOW_CREDENTIALS: &'static str = "Access-Control-Allow-Credentials";
105 pub const _ACCESS_CONTROL_MAX_AGE: &'static str = "Access-Control-Max-Age";
106 pub const _ACCESS_CONTROL_EXPOSE_HEADERS: &'static str = "Access-Control-Expose-Headers";
107
108 pub const _ACCEPT: &'static str = "Accept";
109 pub const _ACCEPT_RANGES: &'static str = "Accept-Ranges";
110 pub const _ACCEPT_CH: &'static str = "Accept-CH";
111 pub const _ACCEPT_ENCODING: &'static str = "Accept-Encoding";
112 pub const _ACCEPT_LANGUAGE: &'static str = "Accept-Language";
113 pub const _ACCEPT_PATCH: &'static str = "Accept-Patch";
114 pub const _ACCEPT_POST: &'static str = "Accept-Post";
115 pub const _AGE: &'static str = "Age";
116 pub const _ALLOW: &'static str = "Allow";
117 pub const _ALT_SVC: &'static str = "Alt-Svc";
118 pub const _AUTHORIZATION: &'static str = "Authorization";
119 pub const _CACHE_CONTROL: &'static str = "Cache-Control";
120 pub const _CONNECTION: &'static str = "Connection";
121 pub const _CLEAR_SITE_DATA: &'static str = "Clear-Site-Data";
122 pub const _CONTENT_TYPE: &'static str = "Content-Type";
123 pub const _CONTENT_LENGTH: &'static str = "Content-Length";
124 pub const _CONTENT_RANGE: &'static str = "Content-Range";
125 pub const _CONTENT_DISPOSITION: &'static str = "Content-Disposition";
126 pub const _CONTENT_ENCODING: &'static str = "Content-Encoding";
127 pub const _CONTENT_LANGUAGE: &'static str = "Content-Language";
128 pub const _CONTENT_LOCATION: &'static str = "Content-Location";
129 pub const _CONTENT_SECURITY_POLICY: &'static str = "Content-Security-Policy";
130 pub const _CONTENT_SECURITY_POLICY_REPORT_ONLY: &'static str = "Content-Security-Policy-Report-Only";
131 pub const _COOKIE: &'static str = "Cookie";
132 pub const _CROSS_ORIGIN_EMBEDDER_POLICY: &'static str = "Cross-Origin-Embedder-Policy";
133 pub const _CROSS_ORIGIN_OPENER_POLICY: &'static str = "Cross-Origin-Opener-Policy";
134 pub const _CROSS_ORIGIN_RESOURCE_POLICY: &'static str = "Cross-Origin-Resource-Policy";
135 pub const _DATE: &'static str = "Date";
136 pub const _DATE_UNIX_EPOCH_NANOS: &'static str = "Date-Unix-Epoch-Nanos";
137 pub const _DEVICE_MEMORY: &'static str = "Device-Memory";
138 pub const _DIGEST: &'static str = "Digest";
139 pub const _DOWNLINK: &'static str = "Downlink";
140 pub const _EARLY_DATA: &'static str = "Early-Data";
141 pub const _ECT: &'static str = "ECT";
142 pub const _ETAG: &'static str = "ETag";
143 pub const _EXPECT: &'static str = "Expect";
144 pub const _EXPIRES: &'static str = "Expires";
145 pub const _FEATURE_POLICY: &'static str = "Feature-Policy";
146 pub const _PERMISSIONS_POLICY: &'static str = "Permissions-Policy";
147 pub const _FORWARDED: &'static str = "Forwarded";
148 pub const _FROM: &'static str = "From";
149 pub const _HOST: &'static str = "Host";
150 pub const _IF_MATCH: &'static str = "If-Match";
151 pub const _IF_MODIFIED_SINCE: &'static str = "If-Modified-Since";
152 pub const _IF_NONE_MATCH: &'static str = "If-None-Match";
153 pub const _IF_RANGE: &'static str = "If-Range";
154 pub const _IF_UNMODIFIED_SINCE: &'static str = "If-Unmodified-Since";
155 pub const _LAST_MODIFIED: &'static str = "Last-Modified";
156 pub const _LAST_MODIFIED_UNIX_EPOCH_NANOS: &'static str = "Last-Modified-Unix-Epoch-Nanos";
157 pub const _LINK: &'static str = "Link";
158 pub const _LOCATION: &'static str = "Location";
159 pub const _MAX_FORWARDS: &'static str = "Max-Forwards";
160 pub const _NEL: &'static str = "NEL";
161 pub const _ORIGIN: &'static str = "Origin";
162 pub const _PROXY_AUTHENTICATE: &'static str = "Proxy-Authenticate";
163 pub const _PROXY_AUTHORIZATION: &'static str = "Proxy-Authorization";
164 pub const _RANGE: &'static str = "Range";
165 pub const _REFERER: &'static str = "Referer";
166 pub const _REFERRER_POLICY: &'static str = "Referrer-Policy";
167 pub const _RETRY_AFTER: &'static str = "Retry-After";
168 pub const _RTT: &'static str = "RTT";
169 pub const _SAVE_DATA: &'static str = "Save-Data";
170 pub const _SEC_CH_UA: &'static str = "Sec-CH-UA";
171 pub const _SEC_CH_UA_ARCH: &'static str = "Sec-CH-UA-Arch";
172 pub const _SEC_CH_UA_BITNESS: &'static str = "Sec-CH-UA-Bitness";
173 pub const _SEC_CH_UA_FULL_VERSION_LIST: &'static str = "Sec-CH-UA-Full-Version-List";
174 pub const _SEC_CH_UA_MOBILE: &'static str = "Sec-CH-UA-Mobile";
175 pub const _SEC_CH_UA_MODEL: &'static str = "Sec-CH-UA-Model";
176 pub const _SEC_CH_UA_PLATFORM: &'static str = "Sec-CH-UA-Platform";
177 pub const _SEC_CH_UA_PLATFORM_VERSION: &'static str = "Sec-CH-UA-Platform-Version";
178 pub const _SEC_FETCH_DEST: &'static str = "Sec-Fetch-Dest";
179 pub const _SEC_FETCH_MODE: &'static str = "Sec-Fetch-Mode";
180 pub const _SEC_FETCH_SITE: &'static str = "Sec-Fetch-Site";
181 pub const _SEC_FETCH_USER: &'static str = "Sec-Fetch-User";
182 pub const _SEC_GPC: &'static str = "Sec-GPC";
183 pub const _SERVER: &'static str = "Server";
184 pub const _SERVER_TIMING: &'static str = "Server-Timing";
185 pub const _SERVICE_WORKER_NAVIGATION_PRELOAD: &'static str = "Service-Worker-Navigation-Preload";
186 pub const _SET_COOKIE: &'static str = "Set-Cookie";
187 pub const _SOURCE_MAP: &'static str = "SourceMap";
188 pub const _STRICT_TRANSPORT_SECURITY: &'static str = "Strict-Transport-Security";
189 pub const _TE: &'static str = "TE";
190 pub const _TIMING_ALLOW_ORIGIN: &'static str = "Timing-Allow-Origin";
191 pub const _TRAILER: &'static str = "Trailer";
192 pub const _TRANSFER_ENCODING: &'static str = "Transfer-Encoding";
193 pub const _UPGRADE: &'static str = "Upgrade";
194 pub const _UPGRADE_INSECURE_REQUESTS: &'static str = "Upgrade-Insecure-Requests";
195 pub const _USER_AGENT: &'static str = "User-Agent";
196 pub const _VARY: &'static str = "Vary";
197 pub const _VIA: &'static str = "Via";
198 pub const _WANT_DIGEST: &'static str = "Want-Digest";
199 pub const _WWW_AUTHENTICATE: &'static str = "WWW-Authenticate";
200 pub const _X_CONTENT_TYPE_OPTIONS: &'static str = "X-Content-Type-Options";
201 pub const _X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF: &'static str = "nosniff";
202 pub const _X_FRAME_OPTIONS: &'static str = "X-Frame-Options";
203 pub const _X_FRAME_OPTIONS_VALUE_DENY: &'static str = "DENY";
204 pub const _X_FRAME_OPTIONS_VALUE_SAME_ORIGIN: &'static str = "SAMEORIGIN";
205
206 pub const _STRICT_TRANSPORT_SECURITY_VALUE_DEFAULT: &'static str = "max-age=31536000; includeSubDomains";
207 pub const _REFERRER_POLICY_VALUE_DEFAULT: &'static str = "strict-origin-when-cross-origin";
208 pub const _PERMISSIONS_POLICY_VALUE_DEFAULT: &'static str = "geolocation=(), microphone=(), camera=()";
209 pub const _CONTENT_SECURITY_POLICY_VALUE_DEFAULT: &'static str = "default-src 'self'";
210
211
212
213
214 pub const NAME_VALUE_SEPARATOR: &'static str = ": ";
215
216 pub fn single_line(s: &str) -> std::borrow::Cow<'_, str> {
222 if memchr::memchr2(b'\r', b'\n', s.as_bytes()).is_some() {
225 std::borrow::Cow::Owned(s.replace(['\r', '\n'], " "))
226 } else {
227 std::borrow::Cow::Borrowed(s)
228 }
229 }
230
231 pub const _DO_NOT_STORE_CACHE: &'static str = "no-store, no-cache, private, max-age=0, must-revalidate, proxy-revalidate";
232
233
234
235 pub fn get_header_list_with_config(
243 request: &Request,
244 config: &crate::server_config::ServerConfig,
245 ) -> Vec<Header> {
246 let fixed = Header::fixed_headers(&config.csp, config.client_hints, config.security_headers);
251 let mut header_list = Cors::get_headers_from_config(request, config);
252 header_list.reserve(fixed.before_date.len() + 2 + fixed.after_date.len());
253 header_list.extend(fixed.before_date.iter().cloned());
254 header_list.push(Header { name: Header::_DATE.into(), value: crate::http::date::now().into() });
255 if config.nanos_headers {
256 header_list.push(Header::get_date_iso_8601_header());
257 }
258 header_list.extend(fixed.after_date.iter().cloned());
259 header_list
260 }
261
262 fn fixed_headers(csp: &str, client_hints: bool, security_headers: bool) -> std::sync::Arc<FixedHeaders> {
266 use std::sync::{Arc, RwLock};
267 static CACHE: RwLock<Option<(String, bool, bool, Arc<FixedHeaders>)>> = RwLock::new(None);
268 if let Some((cached_csp, cached_hints, cached_security, fixed)) = CACHE.read().unwrap_or_else(|e| e.into_inner()).as_ref() {
269 if cached_csp == csp && *cached_hints == client_hints && *cached_security == security_headers {
270 return Arc::clone(fixed);
271 }
272 }
273 let mut before_date = Vec::with_capacity(6);
276 let vary = if client_hints {
277 before_date.push(ClientHint::get_accept_client_hints_header());
278 before_date.push(ClientHint::get_critical_client_hints_header());
279 [Cors::get_vary_header_value(), ClientHint::get_vary_header_value()].join(", ")
280 } else {
281 Cors::get_vary_header_value()
282 };
283 before_date.push(Header { name: Header::_VARY.into(), value: vary.into() });
284 before_date.push(Header::get_x_content_type_options_header());
285 before_date.push(Header::get_accept_ranges_header());
286 if security_headers {
288 before_date.push(Header::get_x_frame_options_header());
289 }
290 let mut after_date = vec![Header::get_no_cache_header()];
291 if security_headers {
292 after_date.push(Header { name: Header::_REFERRER_POLICY.into(), value: Header::_REFERRER_POLICY_VALUE_DEFAULT.into() });
293 after_date.push(Header { name: Header::_PERMISSIONS_POLICY.into(), value: Header::_PERMISSIONS_POLICY_VALUE_DEFAULT.into() });
294 }
295 if !csp.is_empty() {
296 after_date.push(Header { name: Header::_CONTENT_SECURITY_POLICY.into(), value: csp.to_string().into() });
297 }
298 let before_date = before_date.into_iter().map(Header::interned).collect();
301 let after_date = after_date.into_iter().map(Header::interned).collect();
302 let fixed = Arc::new(FixedHeaders { before_date, after_date });
303 *CACHE.write().unwrap_or_else(|e| e.into_inner()) = Some((csp.to_string(), client_hints, security_headers, Arc::clone(&fixed)));
304 fixed
305 }
306
307 fn interned(self) -> Header {
312 fn intern(s: Cow<'static, str>) -> Cow<'static, str> {
313 use std::collections::HashSet;
314 use std::sync::{Mutex, OnceLock};
315 let owned = match s {
316 Cow::Borrowed(_) => return s,
317 Cow::Owned(owned) => owned,
318 };
319 static INTERNED: OnceLock<Mutex<HashSet<&'static str>>> = OnceLock::new();
320 let mut set = INTERNED.get_or_init(|| Mutex::new(HashSet::new())).lock().unwrap_or_else(|e| e.into_inner());
321 if let Some(existing) = set.get(owned.as_str()) {
322 return Cow::Borrowed(existing);
323 }
324 let leaked: &'static str = Box::leak(owned.into_boxed_str());
325 set.insert(leaked);
326 Cow::Borrowed(leaked)
327 }
328 Header { name: intern(self.name), value: intern(self.value) }
329 }
330
331 pub fn get_header_list(request: &Request) -> Vec<Header> {
339 Self::get_header_list_with_config(request, &crate::server_config::ServerConfig::current())
340 }
341
342
343 pub fn get_x_content_type_options_header() -> Header {
344 Header {
345 name: Header::_X_CONTENT_TYPE_OPTIONS.into(),
346 value: Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF.into(),
347 }
348 }
349
350 pub fn get_accept_ranges_header() -> Header {
351 Header {
352 name: Header::_ACCEPT_RANGES.into(),
353 value: Range::BYTES.into(),
354 }
355 }
356
357 pub fn get_x_frame_options_header() -> Header {
358 Header {
359 name: Header::_X_FRAME_OPTIONS.into(),
360 value: Header::_X_FRAME_OPTIONS_VALUE_SAME_ORIGIN.into(),
361 }
362 }
363
364 pub fn get_date_iso_8601_header() -> Header {
365 Header {
366 name: Header::_DATE_UNIX_EPOCH_NANOS.into(),
367 value: crate::ext::decimal::Decimal::new(DateTimeExt::_now_unix_epoch_nanos()).as_str().to_string().into(),
368 }
369 }
370
371 pub fn get_no_cache_header() -> Header {
372 Header {
373 name: Header::_CACHE_CONTROL.into(),
374 value: Header::_DO_NOT_STORE_CACHE.into(),
375 }
376 }
377
378 pub fn get_hsts_header() -> Header {
379 Header {
380 name: Header::_STRICT_TRANSPORT_SECURITY.into(),
381 value: Header::_STRICT_TRANSPORT_SECURITY_VALUE_DEFAULT.into(),
382 }
383 }
384
385 pub fn parse_header(raw_header: &str) -> Result<Header, String> {
386 let escaped_header = StringExt::filter_ascii_control_characters(raw_header);
387 let escaped_header = StringExt::truncate_new_line_carriage_return(&escaped_header);
388
389 let boxed_split = escaped_header.split_once(SYMBOL.colon);
390 if boxed_split.is_none() {
391 let message = format!("Unable to parse header: {}", escaped_header);
392 return Err(message)
393 }
394
395 let (name, value) = boxed_split.unwrap();
396
397 let header = Header {
398 name: name.trim().to_string().into(),
399 value: value.trim().to_string().into(),
400 };
401
402 Ok(header)
403 }
404
405 pub fn parse(raw_header: &str) -> Result<Header, String> {
406 Header::parse_header(raw_header)
407 }
408
409 pub fn generate(&self) -> String {
410 self.as_string()
411 }
412
413}
414