238 lines
6.8 KiB
Rust
238 lines
6.8 KiB
Rust
#[cfg(feature = "governor")]
|
|
use std::num::NonZero;
|
|
|
|
#[cfg(feature = "cf-worker")]
|
|
#[allow(unsafe_code)]
|
|
mod json {
|
|
use wasm_bindgen::prelude::*;
|
|
#[wasm_bindgen]
|
|
extern "C" {
|
|
#[wasm_bindgen(js_namespace = JSON)]
|
|
pub fn stringify(value: &JsValue) -> String;
|
|
}
|
|
}
|
|
|
|
/// Application configuration
|
|
#[derive(Debug, Clone, serde::Deserialize)]
|
|
pub struct Config {
|
|
/// The listen address
|
|
pub listen: Option<String>,
|
|
|
|
/// Sandbox configuration
|
|
#[serde(default)]
|
|
pub sandbox: SandboxConfig,
|
|
|
|
/// Send Cache-Control headers
|
|
pub enable_cache: bool,
|
|
|
|
/// Index page configuration
|
|
pub index_redirect: IndexConfig,
|
|
|
|
/// Whether to only serve media with a known safe signature
|
|
pub allow_unknown: bool,
|
|
|
|
/// Fetch configuration
|
|
pub fetch: FetchConfig,
|
|
|
|
/// Post-processing configuration
|
|
pub post_process: PostProcessConfig,
|
|
|
|
#[cfg(not(feature = "cf-worker"))]
|
|
/// The maximum number of X-Forwarded-For headers to allow
|
|
pub max_x_forwarded_for: u8,
|
|
|
|
#[cfg(feature = "governor")]
|
|
/// Governor configuration
|
|
pub rate_limit: Vec<RateLimitConfig>,
|
|
}
|
|
|
|
/// Sandbox configuration
|
|
#[derive(Debug, Clone, serde::Deserialize)]
|
|
#[non_exhaustive]
|
|
pub enum SandboxConfig {
|
|
/// No sandboxing
|
|
#[serde(rename = "none")]
|
|
NoSandbox,
|
|
/// AppArmor sandboxing
|
|
#[cfg(feature = "apparmor")]
|
|
#[serde(rename = "apparmor")]
|
|
AppArmor(AppArmorConfig),
|
|
}
|
|
|
|
impl Default for SandboxConfig {
|
|
fn default() -> Self {
|
|
Self::NoSandbox
|
|
}
|
|
}
|
|
|
|
/// AppArmor configuration
|
|
#[cfg(feature = "apparmor")]
|
|
#[derive(Debug, Clone, serde::Deserialize)]
|
|
pub struct AppArmorConfig {
|
|
/// The profile to transition to after initialization is complete
|
|
pub serve: String,
|
|
/// The AppArmor hat to use when processing media
|
|
pub image_hat: String,
|
|
}
|
|
|
|
/// Governor configuration
|
|
#[cfg(feature = "governor")]
|
|
#[derive(Debug, Clone, serde::Deserialize)]
|
|
pub struct RateLimitConfig {
|
|
/// The key to use for rate limiting headers
|
|
pub key: Option<String>,
|
|
/// The rate limit replenish interval in milliseconds
|
|
pub replenish_every: u64,
|
|
/// The rate limit burst size
|
|
pub burst: NonZero<u32>,
|
|
|
|
/// The minimum request duration in milliseconds for this rate limit to apply
|
|
pub min_request_duration: Option<u64>,
|
|
}
|
|
|
|
#[cfg(feature = "cf-worker")]
|
|
#[derive(Debug, thiserror::Error)]
|
|
/// Configuration error
|
|
pub enum CfConfigError {
|
|
#[error("Failed to convert env")]
|
|
/// Failed to convert the environment to a JS object
|
|
EnvConversion,
|
|
#[error("Failed to parse JSON")]
|
|
/// Failed to parse JSON
|
|
JsonParse(#[from] serde_json::Error),
|
|
}
|
|
|
|
#[cfg(feature = "cf-worker")]
|
|
impl Config {
|
|
/// Load the configuration from the Cloudflare Worker environment
|
|
pub fn load_from_cf_env(env: worker::Env) -> Result<Self, CfConfigError> {
|
|
use wasm_bindgen::JsCast;
|
|
let obj = env.dyn_into().map_err(|_| CfConfigError::EnvConversion)?;
|
|
let json = json::stringify(&obj);
|
|
serde_json::from_str(&json).map_err(CfConfigError::JsonParse)
|
|
}
|
|
}
|
|
|
|
impl Default for Config {
|
|
fn default() -> Self {
|
|
Config {
|
|
listen: Some("127.0.0.1:3000".to_string()),
|
|
enable_cache: false,
|
|
sandbox: SandboxConfig::default(),
|
|
fetch: FetchConfig {
|
|
#[cfg(not(feature = "cf-worker"))]
|
|
addr_family: AddrFamilyConfig::Both,
|
|
allow_http: false,
|
|
via: concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")).to_string(),
|
|
user_agent: concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"))
|
|
.to_string(),
|
|
},
|
|
index_redirect: IndexConfig::Message(format!(
|
|
"Welcome to {}",
|
|
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")),
|
|
)),
|
|
allow_unknown: false,
|
|
post_process: PostProcessConfig {
|
|
enable_redirects: false,
|
|
normalization: NormalizationPolicy::Opportunistic,
|
|
allow_svg_passthrough: false,
|
|
},
|
|
#[cfg(not(feature = "cf-worker"))]
|
|
max_x_forwarded_for: 0,
|
|
#[cfg(feature = "governor")]
|
|
rate_limit: vec![RateLimitConfig {
|
|
key: None,
|
|
replenish_every: 2000,
|
|
burst: NonZero::new(32).unwrap(),
|
|
min_request_duration: None,
|
|
}],
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Fetch configuration
|
|
#[derive(Debug, Clone, serde::Deserialize)]
|
|
pub struct FetchConfig {
|
|
/// The address family to use
|
|
#[cfg(not(feature = "cf-worker"))]
|
|
#[serde(default)]
|
|
pub addr_family: AddrFamilyConfig,
|
|
|
|
/// Whether to allow HTTP requests
|
|
pub allow_http: bool,
|
|
/// The via string to use when fetching media
|
|
pub via: String,
|
|
/// The user agent to use when fetching media
|
|
pub user_agent: String,
|
|
}
|
|
|
|
/// Address family configuration
|
|
#[cfg(not(feature = "cf-worker"))]
|
|
#[derive(Debug, Clone, Copy, serde::Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum AddrFamilyConfig {
|
|
/// Prefer IPv4
|
|
V4Only,
|
|
/// Prefer IPv6
|
|
V6Only,
|
|
/// Use both IPv4 and IPv6
|
|
Both,
|
|
}
|
|
|
|
#[cfg(not(feature = "cf-worker"))]
|
|
impl Default for AddrFamilyConfig {
|
|
fn default() -> Self {
|
|
Self::Both
|
|
}
|
|
}
|
|
|
|
/// Post-processing configuration
|
|
#[derive(Debug, Clone, serde::Deserialize)]
|
|
pub struct PostProcessConfig {
|
|
/// Opportunistically redirect to the original URL if the media is not modified
|
|
///
|
|
/// Potentially leaks the user's IP address and other metadata
|
|
pub enable_redirects: bool,
|
|
/// Whether to normalize media files when the request specifically asks for a format
|
|
pub normalization: NormalizationPolicy,
|
|
/// Whether to allow SVG passthrough
|
|
///
|
|
/// This opens up the possibility of SVG-based attacks
|
|
pub allow_svg_passthrough: bool,
|
|
}
|
|
|
|
/// Normalization policy
|
|
#[derive(Copy, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum NormalizationPolicy {
|
|
/// Ignore the requested format and return the original
|
|
Never,
|
|
/// Don't convert the media if we don't have to
|
|
///
|
|
///
|
|
/// This is the default for Cloudflare Workers
|
|
Lazy,
|
|
/// Only return the requested format if other conversions are necessary
|
|
///
|
|
/// This is the default for local environments
|
|
Opportunistic,
|
|
/// Always make an attempt to return the requested format
|
|
Aggressive,
|
|
}
|
|
|
|
impl Default for NormalizationPolicy {
|
|
fn default() -> Self {
|
|
Self::Opportunistic
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, serde::Deserialize)]
|
|
#[serde(untagged)]
|
|
/// Index page configuration
|
|
pub enum IndexConfig {
|
|
/// Redirect to a URL
|
|
#[allow(missing_docs)]
|
|
Redirect { permanent: bool, url: String },
|
|
/// Display a message
|
|
Message(String),
|
|
}
|