mkit_server/http_objects/
mount.rs1use super::{HttpBody, HttpObjectResponse};
3use crate::pipeline::ADMISSION_EXPOSE_HEADERS;
4use crate::url_token::{UrlTokenConfig, UrlTokenConfigError};
5
6pub const KEY_PATH: &str = "/.well-known/mkit-url-token-keys.json";
8
9#[derive(Debug, Clone, Default)]
11pub struct HttpMountOptions {
12 pub cors_origins: Vec<String>,
14}
15
16pub fn apply_cors(
18 response: &mut HttpObjectResponse,
19 origin: Option<&str>,
20 opts: &HttpMountOptions,
21) {
22 response
23 .headers
24 .retain(|(name, _)| !name.to_ascii_lowercase().starts_with("access-control-"));
25 if opts.cors_origins.is_empty() {
26 response
27 .headers
28 .push(("Access-Control-Allow-Origin", "*".into()));
29 } else {
30 let mut vary = Vec::new();
31 response.headers.retain(|(name, value)| {
32 if name.eq_ignore_ascii_case("Vary") {
33 vary.extend(
34 value
35 .split(',')
36 .map(str::trim)
37 .filter(|v| !v.is_empty())
38 .map(str::to_owned),
39 );
40 false
41 } else {
42 true
43 }
44 });
45 if !vary
46 .iter()
47 .any(|value| value.eq_ignore_ascii_case("Origin") || value == "*")
48 {
49 vary.push("Origin".into());
50 }
51 response.headers.push(("Vary", vary.join(", ")));
52 if let Some(origin) =
53 origin.filter(|o| opts.cors_origins.iter().any(|allowed| allowed == o))
54 {
55 response
56 .headers
57 .push(("Access-Control-Allow-Origin", origin.into()));
58 }
59 }
60 response
61 .headers
62 .push(("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS".into()));
63 response.headers.push(("Access-Control-Allow-Headers", "Range, If-None-Match, If-Range, Payment-Authorization, PAYMENT-SIGNATURE, Authorization, Accept-Payment".into()));
64 let mut expose = "ETag, Content-Range, Accept-Ranges, Content-Length, X-Mkit-Commit, X-Mkit-Object, X-Mkit-Object-Type, WWW-Authenticate, Payment-Receipt, PAYMENT-REQUIRED, PAYMENT-RESPONSE, Link".to_owned();
65 for header in ADMISSION_EXPOSE_HEADERS {
66 if !expose
67 .split(',')
68 .any(|existing| existing.trim().eq_ignore_ascii_case(header))
69 {
70 expose.push_str(", ");
71 expose.push_str(header);
72 }
73 }
74 response
75 .headers
76 .push(("Access-Control-Expose-Headers", expose));
77}
78
79#[must_use]
81pub fn key_document(config: &UrlTokenConfig, method: &str) -> HttpObjectResponse {
82 match method {
83 "OPTIONS" => HttpObjectResponse::new(204).with_header("Allow", "GET, HEAD, OPTIONS"),
84 "GET" | "HEAD" => {
85 let json = config.keys().key_set_json(config.ttl_ms());
86 let mut response = HttpObjectResponse::new(200)
87 .with_header("Content-Type", "application/json")
88 .with_header("Cache-Control", "public, max-age=300")
89 .with_header("Content-Length", json.len().to_string());
90 if method == "GET" {
91 response.body = HttpBody::Bytes(json.into());
92 }
93 response
94 }
95 _ => HttpObjectResponse::error(405).with_header("Allow", "GET, HEAD, OPTIONS"),
96 }
97}
98
99pub fn check_key_separation(
104 config: &UrlTokenConfig,
105 others: &[[u8; 32]],
106) -> Result<(), UrlTokenConfigError> {
107 if config
108 .keys()
109 .public_keys()
110 .any(|public| others.contains(&public))
111 {
112 Err(UrlTokenConfigError::Keys)
113 } else {
114 Ok(())
115 }
116}