Skip to content
Clone
02d8172
196 lines 6.3 KB Rust
use std::future::{Ready, ready};
use std::rc::Rc;
use std::sync::Arc;
use std::task::{Context, Poll};
use actix_web::body::{BoxBody, EitherBody, MessageBody};
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::http::Method;
use actix_web::http::header::{self, HeaderMap, HeaderValue};
use actix_web::{Error, HttpResponse};
use config::web::WebConfig;
use futures_util::future::LocalBoxFuture;
#[derive(Debug, Clone)]
pub struct Cors {
policy: Arc<CorsPolicy>,
}
impl Cors {
pub fn new(web: &WebConfig) -> Self {
Self {
policy: Arc::new(CorsPolicy::new(web)),
}
}
}
impl<S, B> Transform<S, ServiceRequest> for Cors
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
B: MessageBody + 'static,
{
type Response = ServiceResponse<EitherBody<B>>;
type Error = Error;
type Transform = CorsMiddleware<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(CorsMiddleware {
service: Rc::new(service),
policy: Arc::clone(&self.policy),
}))
}
}
pub struct CorsMiddleware<S> {
service: Rc<S>,
policy: Arc<CorsPolicy>,
}
impl<S, B> Service<ServiceRequest> for CorsMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
B: MessageBody + 'static,
{
type Response = ServiceResponse<EitherBody<B>>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&self, context: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(context)
}
fn call(&self, request: ServiceRequest) -> Self::Future {
let service = Rc::clone(&self.service);
let policy = Arc::clone(&self.policy);
Box::pin(async move {
let Some(origin) = allowed_origin(&request, &policy) else {
return service
.call(request)
.await
.map(|response| response.map_into_left_body());
};
if is_preflight(&request) {
let mut response = HttpResponse::NoContent().finish();
policy.write_preflight(response.headers_mut(), &origin);
let response = response.map_body(|_, body: BoxBody| EitherBody::<B>::right(body));
return Ok(ServiceResponse::new(request.into_parts().0, response));
}
let mut response = service.call(request).await?.map_into_left_body();
policy.write_headers(response.headers_mut(), &origin);
Ok(response)
})
}
}
fn web_allows_value(allow_any: &bool, allow_origins: &[String], origin: &str) -> bool {
*allow_any
|| allow_origins
.iter()
.any(|allowed| allowed.eq_ignore_ascii_case(origin))
}
#[derive(Debug, Clone)]
struct CorsPolicy {
allow_any_origin: bool,
allow_origins: Vec<String>,
allow_methods: HeaderValue,
allow_headers: HeaderValue,
expose_headers: Option<HeaderValue>,
allow_credentials: bool,
max_age: HeaderValue,
}
impl CorsPolicy {
fn new(web: &WebConfig) -> Self {
let origins = web.allow_origins();
let allow_any_origin = origins.iter().any(|origin| origin == "*");
let allow_origins = origins.into_iter().filter(|origin| origin != "*").collect();
Self {
allow_any_origin,
allow_origins,
allow_methods: joined(&web.allow_methods()),
allow_headers: joined(&web.allow_headers()),
expose_headers: optional(&web.expose_headers()),
allow_credentials: web.cors_allow_credentials && !allow_any_origin,
max_age: header_value(&web.cors_max_age_secs.to_string()),
}
}
fn allows(&self, origin: &str) -> bool {
web_allows_value(&self.allow_any_origin, &self.allow_origins, origin)
}
fn write_preflight(&self, headers: &mut HeaderMap, origin: &str) {
self.write_origin(headers, origin);
headers.insert(
header::ACCESS_CONTROL_ALLOW_METHODS,
self.allow_methods.clone(),
);
headers.insert(
header::ACCESS_CONTROL_ALLOW_HEADERS,
self.allow_headers.clone(),
);
headers.insert(header::ACCESS_CONTROL_MAX_AGE, self.max_age.clone());
}
fn write_headers(&self, headers: &mut HeaderMap, origin: &str) {
self.write_origin(headers, origin);
if let Some(expose) = &self.expose_headers {
headers.insert(header::ACCESS_CONTROL_EXPOSE_HEADERS, expose.clone());
}
}
fn write_origin(&self, headers: &mut HeaderMap, origin: &str) {
if headers.contains_key(header::ACCESS_CONTROL_ALLOW_ORIGIN) {
return;
}
if self.allow_any_origin {
headers.insert(
header::ACCESS_CONTROL_ALLOW_ORIGIN,
HeaderValue::from_static("*"),
);
return;
}
if let Ok(value) = HeaderValue::from_str(origin) {
headers.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, value);
}
headers.append(header::VARY, HeaderValue::from_static("origin"));
if self.allow_credentials {
headers.insert(
header::ACCESS_CONTROL_ALLOW_CREDENTIALS,
HeaderValue::from_static("true"),
);
}
}
}
fn allowed_origin(request: &ServiceRequest, policy: &CorsPolicy) -> Option<String> {
let origin = request
.headers()
.get(header::ORIGIN)
.and_then(|value| value.to_str().ok())
.map(str::trim)
.filter(|origin| !origin.is_empty())?;
policy.allows(origin).then(|| origin.to_owned())
}
fn is_preflight(request: &ServiceRequest) -> bool {
request.method() == Method::OPTIONS
&& request
.headers()
.contains_key(header::ACCESS_CONTROL_REQUEST_METHOD)
}
fn joined(values: &[String]) -> HeaderValue {
header_value(&values.join(", "))
}
fn header_value(raw: &str) -> HeaderValue {
HeaderValue::from_str(raw).unwrap_or_else(|_| HeaderValue::from_static(""))
}
fn optional(values: &[String]) -> Option<HeaderValue> {
(!values.is_empty()).then(|| joined(values))
}