use super::{HttpBody, HttpObjectResponse};
use crate::pipeline::ADMISSION_EXPOSE_HEADERS;
use crate::url_token::{UrlTokenConfig, UrlTokenConfigError};
pub const KEY_PATH: &str = "/.well-known/mkit-url-token-keys.json";
#[derive(Debug, Clone, Default)]
pub struct HttpMountOptions {
pub cors_origins: Vec<String>,
}
pub fn apply_cors(
response: &mut HttpObjectResponse,
origin: Option<&str>,
opts: &HttpMountOptions,
) {
response
.headers
.retain(|(name, _)| !name.to_ascii_lowercase().starts_with("access-control-"));
if opts.cors_origins.is_empty() {
response
.headers
.push(("Access-Control-Allow-Origin", "*".into()));
} else {
let mut vary = Vec::new();
response.headers.retain(|(name, value)| {
if name.eq_ignore_ascii_case("Vary") {
vary.extend(
value
.split(',')
.map(str::trim)
.filter(|v| !v.is_empty())
.map(str::to_owned),
);
false
} else {
true
}
});
if !vary
.iter()
.any(|value| value.eq_ignore_ascii_case("Origin") || value == "*")
{
vary.push("Origin".into());
}
response.headers.push(("Vary", vary.join(", ")));
if let Some(origin) =
origin.filter(|o| opts.cors_origins.iter().any(|allowed| allowed == o))
{
response
.headers
.push(("Access-Control-Allow-Origin", origin.into()));
}
}
response
.headers
.push(("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS".into()));
response.headers.push(("Access-Control-Allow-Headers", "Range, If-None-Match, If-Range, Payment-Authorization, PAYMENT-SIGNATURE, Authorization, Accept-Payment".into()));
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();
for header in ADMISSION_EXPOSE_HEADERS {
if !expose
.split(',')
.any(|existing| existing.trim().eq_ignore_ascii_case(header))
{
expose.push_str(", ");
expose.push_str(header);
}
}
response
.headers
.push(("Access-Control-Expose-Headers", expose));
}
#[must_use]
pub fn key_document(config: &UrlTokenConfig, method: &str) -> HttpObjectResponse {
match method {
"OPTIONS" => HttpObjectResponse::new(204).with_header("Allow", "GET, HEAD, OPTIONS"),
"GET" | "HEAD" => {
let json = config.keys().key_set_json(config.ttl_ms());
let mut response = HttpObjectResponse::new(200)
.with_header("Content-Type", "application/json")
.with_header("Cache-Control", "public, max-age=300")
.with_header("Content-Length", json.len().to_string());
if method == "GET" {
response.body = HttpBody::Bytes(json.into());
}
response
}
_ => HttpObjectResponse::error(405).with_header("Allow", "GET, HEAD, OPTIONS"),
}
}
pub fn check_key_separation(
config: &UrlTokenConfig,
others: &[[u8; 32]],
) -> Result<(), UrlTokenConfigError> {
if config
.keys()
.public_keys()
.any(|public| others.contains(&public))
{
Err(UrlTokenConfigError::Keys)
} else {
Ok(())
}
}