use pdk_core::logger;
use crate::error::CorsError;
use crate::model::request::origins::OriginGroup;
const ACCESS_CONTROL_MAX_AGE: &str = "Access-Control-Max-Age";
const ACCESS_CONTROL_ALLOW_ORIGIN: &str = "Access-Control-Allow-Origin";
const ACCESS_CONTROL_ALLOW_METHODS: &str = "Access-Control-Allow-Methods";
const ACCESS_CONTROL_ALLOW_HEADERS: &str = "Access-Control-Allow-Headers";
const ACCESS_CONTROL_EXPOSE_HEADERS: &str = "Access-Control-Expose-Headers";
const ACCESS_CONTROL_ALLOW_CREDENTIALS: &str = "Access-Control-Allow-Credentials";
const COMMA: &str = ", ";
pub trait CorsHeader {
fn add(&self) -> Result<Option<(String, String)>, CorsError>;
}
pub struct AllowOrigin<'a> {
origin: &'a str,
}
impl<'a> AllowOrigin<'a> {
pub(crate) fn new(origin: &'a str) -> Self {
Self { origin }
}
}
impl CorsHeader for AllowOrigin<'_> {
fn add(&self) -> Result<Option<(String, String)>, CorsError> {
Ok(Some((
ACCESS_CONTROL_ALLOW_ORIGIN.to_string(),
self.origin.to_string(),
)))
}
}
pub struct ExposeHeaders<'a> {
origin_group: &'a OriginGroup<'a>,
}
impl<'a> ExposeHeaders<'a> {
pub(crate) fn new(origin_group: &'a OriginGroup) -> Self {
Self { origin_group }
}
}
impl CorsHeader for ExposeHeaders<'_> {
fn add(&self) -> Result<Option<(String, String)>, CorsError> {
match self.origin_group.exposed_headers() {
None => Ok(None),
Some(headers) => {
logger::debug!("Adding Access-Control-Expose-Headers into the request.");
let result = headers.join(COMMA);
Ok(Some((ACCESS_CONTROL_EXPOSE_HEADERS.to_string(), result)))
}
}
}
}
pub struct MaxAge<'a> {
origin_group: &'a OriginGroup<'a>,
}
impl<'a> MaxAge<'a> {
pub(crate) fn new(origin_group: &'a OriginGroup) -> Self {
Self { origin_group }
}
}
impl CorsHeader for MaxAge<'_> {
fn add(&self) -> Result<Option<(String, String)>, CorsError> {
Ok(Some((
ACCESS_CONTROL_MAX_AGE.to_string(),
self.origin_group.max_age().to_string(),
)))
}
}
pub struct AllowHeaders<'a> {
origin_group: &'a OriginGroup<'a>,
headers: &'a [String],
}
impl<'a> AllowHeaders<'a> {
pub(crate) fn new(origin_group: &'a OriginGroup<'a>, headers: &'a [String]) -> Self {
Self {
origin_group,
headers,
}
}
}
impl CorsHeader for AllowHeaders<'_> {
fn add(&self) -> Result<Option<(String, String)>, CorsError> {
let headers_are_allowed = self.origin_group.headers_are_allowed(self.headers);
if !headers_are_allowed {
logger::debug!("Some of the headers in {:?} are not allowed.", self.headers);
return Err(CorsError::HeaderNotAllowed);
} else {
logger::debug!("All headers are valid: {:?}", self.headers);
if !self.headers.is_empty() {
logger::debug!("Adding headers");
return Ok(Some((
ACCESS_CONTROL_ALLOW_HEADERS.to_string(),
self.headers.join(COMMA),
)));
}
}
Ok(None)
}
}
pub struct AllowMethods<'a> {
origin_group: &'a OriginGroup<'a>,
method: &'a str,
}
impl<'a> AllowMethods<'a> {
pub(crate) fn new(origin_group: &'a OriginGroup, method: &'a str) -> Self {
Self {
origin_group,
method,
}
}
}
impl CorsHeader for AllowMethods<'_> {
fn add(&self) -> Result<Option<(String, String)>, CorsError> {
let method_is_allowed = self.origin_group.method_is_allowed(self.method);
if !method_is_allowed {
logger::debug!("Method {} is not allowed.", self.method);
Err(CorsError::MethodNotAllowed)
} else {
logger::debug!("Method {} is allowed.", self.method);
Ok(Some((
ACCESS_CONTROL_ALLOW_METHODS.to_string(),
self.method.to_uppercase(),
)))
}
}
}
pub struct AllowCredentials {
allows_credentials: bool,
}
impl AllowCredentials {
pub(crate) fn new(allows_credentials: bool) -> Self {
Self { allows_credentials }
}
}
impl CorsHeader for AllowCredentials {
fn add(&self) -> Result<Option<(String, String)>, CorsError> {
match self.allows_credentials {
true => Ok(Some((
ACCESS_CONTROL_ALLOW_CREDENTIALS.to_string(),
"true".to_string(),
))),
false => Ok(None),
}
}
}