use std::collections::HashSet;
use crate::{Error, http::{Request, Response, Method}};
use url::{Url, Origin};
enum CorsOriginBuilder {
None,
All,
List(HashSet<String>)
}
impl CorsOriginBuilder {
fn build(self) -> Result<CorsOrigin, Error> {
match self {
CorsOriginBuilder::None => Ok(CorsOrigin::None),
CorsOriginBuilder::All => Ok(CorsOrigin::All),
CorsOriginBuilder::List(origins) => {
Ok(CorsOrigin::List(origins.into_iter().map(|origin| {
Url::parse(&origin)
}).collect::<Result<Vec<_>,_>>().map_err(Error::Url)?
.into_iter().map(|url| url.origin()).collect()))
}
}
}
}
enum CorsOrigin {
None,
All,
List(HashSet<Origin>)
}
pub struct CorsBuilder {
origins: CorsOriginBuilder,
max_age: Option<usize>,
methods: Option<HashSet<Method>>,
headers: Option<HashSet<String>>
}
impl CorsBuilder {
pub fn new() -> CorsBuilder {
CorsBuilder {
origins: CorsOriginBuilder::None,
max_age: None,
methods: None,
headers: None
}
}
pub fn origin<A: Into<String>>(mut self, origin: A) -> Self {
let origin: String = origin.into();
match &mut self.origins {
CorsOriginBuilder::All => (),
CorsOriginBuilder::List(origins) => {
if origin == "*" {
self.origins = CorsOriginBuilder::All;
} else {
origins.insert(origin);
}
},
CorsOriginBuilder::None => {
if origin == "*" {
self.origins = CorsOriginBuilder::All;
} else {
self.origins = CorsOriginBuilder::List([origin].into_iter().collect());
}
}
}
self
}
pub fn max_age(mut self, seconds: usize) -> Self {
self.max_age = Some(seconds);
self
}
pub fn allowed_method(mut self, method: Method) -> Self {
self.methods.get_or_insert_with(|| HashSet::new()).insert(method);
self
}
pub fn allowed_header<A: Into<String>>(mut self, header: A) -> Self {
self.headers.get_or_insert_with(|| HashSet::new()).insert(header.into());
self
}
pub fn build(self) -> Result<Cors, Error> {
Ok(Cors {
origins: self.origins.build()?,
max_age: self.max_age,
methods: self.methods,
headers: self.headers
})
}
}
pub struct Cors {
origins: CorsOrigin,
max_age: Option<usize>,
methods: Option<HashSet<Method>>,
headers: Option<HashSet<String>>
}
impl Cors {
pub(crate) fn apply(&self, request: &Request, response: &mut Response) {
let origin_source = request.header.headers.get("Origin").map(|o| o.get(0)).flatten().or_else(||
request.header.headers.get("origin").map(|o| o.get(0)).flatten()
);
let acao = match &self.origins {
CorsOrigin::None => None,
CorsOrigin::All => {
if let Some(origin) = origin_source {
Some(origin.to_string())
} else {
Some("*".to_string())
}
},
CorsOrigin::List(origins) => {
if let Some(origin) = origin_source {
match Url::parse(&origin) {
Ok(url) => {
origins.get(&url.origin()).map(|found_origin| found_origin.ascii_serialization())
},
Err(_e) => {
#[cfg(feature = "full_log")]
log::debug!("{}, when parsing {}", _e, origin);
None
}
}
} else {
#[cfg(feature = "full_log")]
log::debug!("could not find origin header in preflight request");
None
}
}
};
if let Some(acao) = acao {
response.headers.entry("Access-Control-Allow-Origin".to_string()).or_insert_with(|| Vec::new()).push(acao);
if let Some(max_age) = self.max_age {
response.headers.entry("Access-Control-Max-Age".to_string()).or_insert_with(|| Vec::new()).push(format!("{}", max_age));
}
}
}
pub(crate) fn preflight(&self, request: &Request, methods: &HashSet<Method>) -> Response {
let origin_source = request.header.headers.get("Origin").map(|o| o.get(0)).flatten().or_else(||
request.header.headers.get("origin").map(|o| o.get(0)).flatten()
);
let acao = match &self.origins {
CorsOrigin::None => None,
CorsOrigin::All => {
if let Some(origin) = origin_source {
Some(origin.to_string())
} else {
Some("*".to_string())
}
},
CorsOrigin::List(origins) => {
if let Some(origin) = origin_source {
match Url::parse(&origin) {
Ok(url) => {
origins.get(&url.origin()).map(|found_origin| found_origin.ascii_serialization())
},
Err(_e) => {
#[cfg(feature = "full_log")]
log::debug!("{}, when parsing {}", _e, origin);
None
}
}
} else {
#[cfg(feature = "full_log")]
log::debug!("could not find origin header in preflight request");
None
}
}
};
if let Some(acao) = acao {
let mut response = Response::no_content();
let methods = match request.header.headers.get("Access-Control-Request-Method") {
Some(_) => {
if let Some(override_methods) = &self.methods {
override_methods.iter()
} else {
methods.iter()
}.map(|m| m.to_str()).collect::<Vec<_>>().join(", ")
},
None => {
#[cfg(feature = "full_log")]
log::debug!("the Access-Control-Request-Method field was not found");
return Response::forbidden()
}
};
let headers = if let Some(override_headers) = &self.headers {
override_headers.iter().cloned().collect::<Vec<_>>().join(", ")
} else {
match request.header.headers.get("Access-Control-Request-Headers").map(|acrh| acrh.get(0)).flatten() {
Some(headers) => headers.clone(),
None => {
#[cfg(feature = "full_log")]
log::debug!("the Access-Control-Request-Headers field was not found");
return Response::forbidden()
}
}
};
#[cfg(feature = "full_log")]
log::debug!("the preflight request for '{}' is successful, with methods [{}] and headers [{}]", acao, methods, headers);
response = response.header(
"Access-Control-Allow-Origin".to_string(),
acao
);
response = response.header(
"Access-Control-Allow-Methods".to_string(),
methods
);
response = response.header(
"Access-Control-Allow-Headers".to_string(),
headers
);
if let Some(max_age) = self.max_age {
response = response.header(
"Access-Control-Max-Age".to_string(),
format!("{}", max_age)
);
}
response
} else {
Response::forbidden()
}
}
}