1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-25 00:12:59 +01:00
actix-extras/src/middleware/cors.rs

1161 lines
39 KiB
Rust
Raw Normal View History

2018-01-10 07:33:51 +01:00
//! Cross-origin resource sharing (CORS) for Actix applications
2018-01-10 08:55:42 +01:00
//!
//! CORS middleware could be used with application and with resource.
//! First you need to construct CORS middleware instance.
//!
//! To construct a cors:
//!
//! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building.
//! 2. Use any of the builder methods to set fields in the backend.
2018-04-19 04:05:24 +02:00
//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the
//! constructed backend.
2018-01-10 08:55:42 +01:00
//!
2018-03-31 09:16:55 +02:00
//! Cors middleware could be used as parameter for `App::middleware()` or
2018-04-10 06:39:32 +02:00
//! `ResourceHandler::middleware()` methods. But you have to use
//! `Cors::for_app()` method to support *preflight* OPTIONS request.
2018-01-11 20:14:18 +01:00
//!
2018-01-10 08:55:42 +01:00
//!
//! # Example
//!
//! ```rust
//! # extern crate actix_web;
2018-04-10 06:39:32 +02:00
//! use actix_web::middleware::cors::Cors;
2018-06-01 18:37:14 +02:00
//! use actix_web::{http, App, HttpRequest, HttpResponse};
2018-01-10 08:55:42 +01:00
//!
//! fn index(mut req: HttpRequest) -> &'static str {
2018-06-01 18:37:14 +02:00
//! "Hello world"
2018-01-10 08:55:42 +01:00
//! }
//!
//! fn main() {
2018-06-01 18:37:14 +02:00
//! let app = App::new().configure(|app| {
//! Cors::for_app(app) // <- Construct CORS middleware builder
2018-04-10 06:39:32 +02:00
//! .allowed_origin("https://www.rust-lang.org/")
//! .allowed_methods(vec!["GET", "POST"])
//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
//! .allowed_header(http::header::CONTENT_TYPE)
//! .max_age(3600)
//! .resource("/index.html", |r| {
//! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
//! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
//! })
2018-06-01 18:37:14 +02:00
//! .register()
//! });
2018-01-10 08:55:42 +01:00
//! }
//! ```
2018-04-14 01:02:01 +02:00
//! In this example custom *CORS* middleware get registered for "/index.html"
//! endpoint.
2018-01-10 08:55:42 +01:00
//!
//! Cors middleware automatically handle *OPTIONS* preflight request.
2018-01-10 07:33:51 +01:00
use std::collections::HashSet;
2018-01-10 22:41:33 +01:00
use std::iter::FromIterator;
use std::rc::Rc;
2018-01-10 07:33:51 +01:00
2018-01-10 22:41:33 +01:00
use http::header::{self, HeaderName, HeaderValue};
2018-04-14 01:02:01 +02:00
use http::{self, HttpTryFrom, Method, StatusCode, Uri};
2018-01-10 07:33:51 +01:00
use application::App;
2018-04-14 01:02:01 +02:00
use error::{ResponseError, Result};
use httpmessage::HttpMessage;
2018-01-10 07:33:51 +01:00
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
2018-01-10 22:41:33 +01:00
use middleware::{Middleware, Response, Started};
2018-04-14 01:02:01 +02:00
use resource::ResourceHandler;
2018-01-10 07:33:51 +01:00
/// A set of errors that can occur during processing CORS
#[derive(Debug, Fail)]
2018-01-10 23:20:00 +01:00
pub enum CorsError {
2018-01-10 07:33:51 +01:00
/// The HTTP request header `Origin` is required but was not provided
2018-04-29 07:55:47 +02:00
#[fail(
display = "The HTTP request header `Origin` is required but was not provided"
)]
2018-01-10 07:33:51 +01:00
MissingOrigin,
/// The HTTP request header `Origin` could not be parsed correctly.
2018-04-14 01:02:01 +02:00
#[fail(display = "The HTTP request header `Origin` could not be parsed correctly.")]
2018-01-10 07:33:51 +01:00
BadOrigin,
2018-04-14 01:02:01 +02:00
/// The request header `Access-Control-Request-Method` is required but is
/// missing
2018-04-29 07:55:47 +02:00
#[fail(
display = "The request header `Access-Control-Request-Method` is required but is missing"
)]
2018-01-10 07:33:51 +01:00
MissingRequestMethod,
/// The request header `Access-Control-Request-Method` has an invalid value
2018-04-29 07:55:47 +02:00
#[fail(
display = "The request header `Access-Control-Request-Method` has an invalid value"
)]
2018-01-10 07:33:51 +01:00
BadRequestMethod,
2018-04-14 01:02:01 +02:00
/// The request header `Access-Control-Request-Headers` has an invalid
/// value
2018-04-29 07:55:47 +02:00
#[fail(
display = "The request header `Access-Control-Request-Headers` has an invalid value"
)]
2018-01-10 08:55:42 +01:00
BadRequestHeaders,
2018-04-14 01:02:01 +02:00
/// The request header `Access-Control-Request-Headers` is required but is
/// missing.
2018-04-29 07:55:47 +02:00
#[fail(
display = "The request header `Access-Control-Request-Headers` is required but is
missing"
)]
2018-01-10 07:33:51 +01:00
MissingRequestHeaders,
/// Origin is not allowed to make this request
2018-04-14 01:02:01 +02:00
#[fail(display = "Origin is not allowed to make this request")]
2018-01-10 07:33:51 +01:00
OriginNotAllowed,
/// Requested method is not allowed
2018-04-14 01:02:01 +02:00
#[fail(display = "Requested method is not allowed")]
2018-01-10 07:33:51 +01:00
MethodNotAllowed,
/// One or more headers requested are not allowed
2018-04-14 01:02:01 +02:00
#[fail(display = "One or more headers requested are not allowed")]
2018-01-10 07:33:51 +01:00
HeadersNotAllowed,
2018-01-10 22:41:33 +01:00
}
2018-01-10 23:20:00 +01:00
impl ResponseError for CorsError {
2018-01-10 07:33:51 +01:00
fn error_response(&self) -> HttpResponse {
HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self))
2018-01-10 07:33:51 +01:00
}
}
2018-04-14 01:02:01 +02:00
/// An enum signifying that some of type T is allowed, or `All` (everything is
/// allowed).
2018-01-10 07:33:51 +01:00
///
/// `Default` is implemented for this enum and is `All`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AllOrSome<T> {
/// Everything is allowed. Usually equivalent to the "*" value.
All,
/// Only some of `T` is allowed
Some(T),
}
impl<T> Default for AllOrSome<T> {
fn default() -> Self {
AllOrSome::All
}
}
impl<T> AllOrSome<T> {
/// Returns whether this is an `All` variant
pub fn is_all(&self) -> bool {
match *self {
AllOrSome::All => true,
AllOrSome::Some(_) => false,
}
}
/// Returns whether this is a `Some` variant
pub fn is_some(&self) -> bool {
!self.is_all()
}
2018-01-10 08:55:42 +01:00
/// Returns &T
pub fn as_ref(&self) -> Option<&T> {
match *self {
AllOrSome::All => None,
AllOrSome::Some(ref t) => Some(t),
}
}
2018-01-10 07:33:51 +01:00
}
/// `Middleware` for Cross-origin resource sharing support
///
/// The Cors struct contains the settings for CORS requests to be validated and
/// for responses to be generated.
#[derive(Clone)]
2018-01-10 07:33:51 +01:00
pub struct Cors {
inner: Rc<Inner>,
}
struct Inner {
2018-01-10 07:33:51 +01:00
methods: HashSet<Method>,
2018-01-12 05:11:34 +01:00
origins: AllOrSome<HashSet<String>>,
2018-01-10 22:41:33 +01:00
origins_str: Option<HeaderValue>,
2018-01-10 07:33:51 +01:00
headers: AllOrSome<HashSet<HeaderName>>,
2018-01-10 22:41:33 +01:00
expose_hdrs: Option<String>,
2018-01-10 07:33:51 +01:00
max_age: Option<usize>,
2018-01-10 22:41:33 +01:00
preflight: bool,
send_wildcard: bool,
supports_credentials: bool,
vary_header: bool,
}
impl Default for Cors {
fn default() -> Cors {
let inner = Inner {
2018-01-10 23:20:00 +01:00
origins: AllOrSome::default(),
2018-01-10 22:41:33 +01:00
origins_str: None,
methods: HashSet::from_iter(
2018-04-14 01:02:01 +02:00
vec![
Method::GET,
Method::HEAD,
Method::POST,
Method::OPTIONS,
Method::PUT,
Method::PATCH,
Method::DELETE,
].into_iter(),
),
2018-01-10 22:41:33 +01:00
headers: AllOrSome::All,
expose_hdrs: None,
max_age: None,
preflight: true,
send_wildcard: false,
supports_credentials: false,
vary_header: true,
};
2018-04-14 01:02:01 +02:00
Cors {
inner: Rc::new(inner),
}
2018-01-10 22:41:33 +01:00
}
2018-01-10 07:33:51 +01:00
}
impl Cors {
/// Build a new CORS middleware instance
pub fn build() -> CorsBuilder<()> {
2018-01-10 07:33:51 +01:00
CorsBuilder {
cors: Some(Inner {
2018-01-10 07:33:51 +01:00
origins: AllOrSome::All,
2018-01-10 22:41:33 +01:00
origins_str: None,
2018-01-10 07:33:51 +01:00
methods: HashSet::new(),
headers: AllOrSome::All,
2018-01-10 22:41:33 +01:00
expose_hdrs: None,
2018-01-10 07:33:51 +01:00
max_age: None,
2018-01-10 22:41:33 +01:00
preflight: true,
send_wildcard: false,
supports_credentials: false,
vary_header: true,
2018-01-10 07:33:51 +01:00
}),
methods: false,
error: None,
2018-01-10 22:41:33 +01:00
expose_hdrs: HashSet::new(),
resources: Vec::new(),
app: None,
}
}
/// Create CorsBuilder for a specified application.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::cors::Cors;
2018-06-01 18:37:14 +02:00
/// use actix_web::{http, App, HttpResponse};
///
/// fn main() {
2018-06-01 18:37:14 +02:00
/// let app = App::new().configure(
/// |app| {
/// Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/")
/// .resource("/resource", |r| { // register resource
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// })
2018-06-01 18:37:14 +02:00
/// .register()
/// }, // construct CORS and return application instance
/// );
/// }
/// ```
pub fn for_app<S: 'static>(app: App<S>) -> CorsBuilder<S> {
CorsBuilder {
cors: Some(Inner {
origins: AllOrSome::All,
origins_str: None,
methods: HashSet::new(),
headers: AllOrSome::All,
expose_hdrs: None,
max_age: None,
preflight: true,
send_wildcard: false,
supports_credentials: false,
vary_header: true,
}),
methods: false,
error: None,
expose_hdrs: HashSet::new(),
resources: Vec::new(),
app: Some(app),
2018-01-10 07:33:51 +01:00
}
}
2018-01-11 20:14:18 +01:00
/// This method register cors middleware with resource and
/// adds route for *OPTIONS* preflight requests.
///
2018-04-14 01:02:01 +02:00
/// It is possible to register *Cors* middleware with
/// `ResourceHandler::middleware()` method, but in that case *Cors*
/// middleware wont be able to handle *OPTIONS* requests.
pub fn register<S: 'static>(self, resource: &mut ResourceHandler<S>) {
2018-05-17 21:20:20 +02:00
resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok());
2018-01-11 20:14:18 +01:00
resource.middleware(self);
}
2018-01-10 23:20:00 +01:00
fn validate_origin<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CorsError> {
2018-01-10 07:33:51 +01:00
if let Some(hdr) = req.headers().get(header::ORIGIN) {
if let Ok(origin) = hdr.to_str() {
return match self.inner.origins {
2018-01-12 05:11:34 +01:00
AllOrSome::All => Ok(()),
2018-04-14 01:02:01 +02:00
AllOrSome::Some(ref allowed_origins) => allowed_origins
.get(origin)
.and_then(|_| Some(()))
.ok_or_else(|| CorsError::OriginNotAllowed),
2018-01-12 05:11:34 +01:00
};
2018-01-10 07:33:51 +01:00
}
2018-01-10 23:20:00 +01:00
Err(CorsError::BadOrigin)
2018-01-10 07:33:51 +01:00
} else {
return match self.inner.origins {
2018-01-10 23:56:45 +01:00
AllOrSome::All => Ok(()),
2018-04-14 01:02:01 +02:00
_ => Err(CorsError::MissingOrigin),
};
2018-01-10 07:33:51 +01:00
}
}
2018-04-14 01:02:01 +02:00
fn validate_allowed_method<S>(
2018-04-29 07:55:47 +02:00
&self, req: &mut HttpRequest<S>,
2018-04-14 01:02:01 +02:00
) -> Result<(), CorsError> {
2018-05-17 21:20:20 +02:00
if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
2018-01-10 07:33:51 +01:00
if let Ok(meth) = hdr.to_str() {
if let Ok(method) = Method::try_from(meth) {
2018-05-17 21:20:20 +02:00
return self
.inner
2018-04-14 01:02:01 +02:00
.methods
.get(&method)
2018-01-10 07:33:51 +01:00
.and_then(|_| Some(()))
2018-01-10 23:20:00 +01:00
.ok_or_else(|| CorsError::MethodNotAllowed);
2018-01-10 07:33:51 +01:00
}
}
2018-01-10 23:20:00 +01:00
Err(CorsError::BadRequestMethod)
2018-01-10 07:33:51 +01:00
} else {
2018-01-10 23:20:00 +01:00
Err(CorsError::MissingRequestMethod)
2018-01-10 07:33:51 +01:00
}
}
2018-01-10 08:55:42 +01:00
2018-04-14 01:02:01 +02:00
fn validate_allowed_headers<S>(
2018-04-29 07:55:47 +02:00
&self, req: &mut HttpRequest<S>,
2018-04-14 01:02:01 +02:00
) -> Result<(), CorsError> {
match self.inner.headers {
2018-01-12 05:11:34 +01:00
AllOrSome::All => Ok(()),
AllOrSome::Some(ref allowed_headers) => {
2018-05-17 21:20:20 +02:00
if let Some(hdr) =
req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
2018-04-14 01:02:01 +02:00
{
2018-01-12 05:11:34 +01:00
if let Ok(headers) = hdr.to_str() {
2018-01-10 08:55:42 +01:00
let mut hdrs = HashSet::new();
for hdr in headers.split(',') {
match HeaderName::try_from(hdr.trim()) {
Ok(hdr) => hdrs.insert(hdr),
2018-04-14 01:02:01 +02:00
Err(_) => return Err(CorsError::BadRequestHeaders),
2018-01-10 08:55:42 +01:00
};
}
if !hdrs.is_empty() && !hdrs.is_subset(allowed_headers) {
2018-04-14 01:02:01 +02:00
return Err(CorsError::HeadersNotAllowed);
2018-01-10 08:55:42 +01:00
}
2018-04-14 01:02:01 +02:00
return Ok(());
2018-01-10 08:55:42 +01:00
}
2018-01-12 05:11:34 +01:00
Err(CorsError::BadRequestHeaders)
} else {
Err(CorsError::MissingRequestHeaders)
2018-01-10 08:55:42 +01:00
}
}
}
}
2018-01-10 07:33:51 +01:00
}
impl<S> Middleware<S> for Cors {
2018-06-21 19:06:23 +02:00
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
if self.inner.preflight && Method::OPTIONS == *req.method() {
2018-01-10 07:48:35 +01:00
self.validate_origin(req)?;
self.validate_allowed_method(req)?;
2018-01-10 08:55:42 +01:00
self.validate_allowed_headers(req)?;
// allowed headers
let headers = if let Some(headers) = self.inner.headers.as_ref() {
2018-04-14 01:02:01 +02:00
Some(
HeaderValue::try_from(
&headers
.iter()
.fold(String::new(), |s, v| s + "," + v.as_str())
.as_str()[1..],
).unwrap(),
)
2018-05-17 21:20:20 +02:00
} else if let Some(hdr) =
req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
2018-04-14 01:02:01 +02:00
{
Some(hdr.clone())
} else {
None
};
2018-01-10 08:55:42 +01:00
Ok(Started::Response(
HttpResponse::Ok()
.if_some(self.inner.max_age.as_ref(), |max_age, resp| {
2018-01-10 22:41:33 +01:00
let _ = resp.header(
2018-04-14 01:02:01 +02:00
header::ACCESS_CONTROL_MAX_AGE,
format!("{}", max_age).as_str(),
);
})
.if_some(headers, |headers, resp| {
2018-04-14 01:02:01 +02:00
let _ =
resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers);
})
.if_true(self.inner.origins.is_all(), |resp| {
if self.inner.send_wildcard {
2018-01-10 22:41:33 +01:00
resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
} else {
let origin = req.headers().get(header::ORIGIN).unwrap();
resp.header(
2018-04-14 01:02:01 +02:00
header::ACCESS_CONTROL_ALLOW_ORIGIN,
origin.clone(),
);
2018-01-10 22:41:33 +01:00
}
})
.if_true(self.inner.origins.is_some(), |resp| {
2018-01-10 22:41:33 +01:00
resp.header(
header::ACCESS_CONTROL_ALLOW_ORIGIN,
2018-04-14 01:02:01 +02:00
self.inner.origins_str.as_ref().unwrap().clone(),
);
2018-01-10 22:41:33 +01:00
})
.if_true(self.inner.supports_credentials, |resp| {
2018-01-10 22:41:33 +01:00
resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
})
2018-01-10 08:55:42 +01:00
.header(
header::ACCESS_CONTROL_ALLOW_METHODS,
2018-05-17 21:20:20 +02:00
&self
.inner
2018-04-14 01:02:01 +02:00
.methods
.iter()
.fold(String::new(), |s, v| s + "," + v.as_str())
.as_str()[1..],
)
.finish(),
))
2018-01-10 08:55:42 +01:00
} else {
// Only check requests with a origin header.
if req.headers().contains_key(header::ORIGIN) {
self.validate_origin(req)?;
}
2018-01-10 22:41:33 +01:00
2018-01-10 08:55:42 +01:00
Ok(Started::Done)
2018-01-10 07:33:51 +01:00
}
}
2018-04-14 01:02:01 +02:00
fn response(
2018-06-21 19:06:23 +02:00
&self, req: &mut HttpRequest<S>, mut resp: HttpResponse,
2018-04-14 01:02:01 +02:00
) -> Result<Response> {
match self.inner.origins {
2018-01-10 22:41:33 +01:00
AllOrSome::All => {
if self.inner.send_wildcard {
2018-01-10 22:41:33 +01:00
resp.headers_mut().insert(
2018-04-14 01:02:01 +02:00
header::ACCESS_CONTROL_ALLOW_ORIGIN,
HeaderValue::from_static("*"),
);
} else if let Some(origin) = req.headers().get(header::ORIGIN) {
2018-04-14 01:02:01 +02:00
resp.headers_mut()
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
2018-01-10 22:41:33 +01:00
}
}
AllOrSome::Some(_) => {
resp.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_ORIGIN,
2018-04-14 01:02:01 +02:00
self.inner.origins_str.as_ref().unwrap().clone(),
);
2018-01-10 22:41:33 +01:00
}
}
if let Some(ref expose) = self.inner.expose_hdrs {
2018-01-10 22:41:33 +01:00
resp.headers_mut().insert(
header::ACCESS_CONTROL_EXPOSE_HEADERS,
2018-04-14 01:02:01 +02:00
HeaderValue::try_from(expose.as_str()).unwrap(),
);
2018-01-10 22:41:33 +01:00
}
if self.inner.supports_credentials {
2018-01-10 22:41:33 +01:00
resp.headers_mut().insert(
2018-04-14 01:02:01 +02:00
header::ACCESS_CONTROL_ALLOW_CREDENTIALS,
HeaderValue::from_static("true"),
);
2018-01-10 22:41:33 +01:00
}
if self.inner.vary_header {
2018-01-10 22:41:33 +01:00
let value = if let Some(hdr) = resp.headers_mut().get(header::VARY) {
let mut val: Vec<u8> = Vec::with_capacity(hdr.as_bytes().len() + 8);
val.extend(hdr.as_bytes());
val.extend(b", Origin");
HeaderValue::try_from(&val[..]).unwrap()
} else {
HeaderValue::from_static("Origin")
};
resp.headers_mut().insert(header::VARY, value);
}
2018-01-10 07:48:35 +01:00
Ok(Response::Done(resp))
2018-01-10 07:33:51 +01:00
}
}
2018-04-14 01:02:01 +02:00
/// Structure that follows the builder pattern for building `Cors` middleware
/// structs.
2018-01-10 07:33:51 +01:00
///
/// To construct a cors:
///
/// 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building.
/// 2. Use any of the builder methods to set fields in the backend.
2018-04-14 01:02:01 +02:00
/// 3. Call [finish](struct.Cors.html#method.finish) to retrieve the
/// constructed backend.
2018-01-10 07:33:51 +01:00
///
/// # Example
///
/// ```rust
/// # extern crate http;
/// # extern crate actix_web;
/// use actix_web::middleware::cors;
2018-06-01 18:37:14 +02:00
/// use http::header;
2018-01-10 07:33:51 +01:00
///
/// # fn main() {
/// let cors = cors::Cors::build()
/// .allowed_origin("https://www.rust-lang.org/")
/// .allowed_methods(vec!["GET", "POST"])
/// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
/// .allowed_header(header::CONTENT_TYPE)
/// .max_age(3600)
/// .finish();
2018-01-10 07:33:51 +01:00
/// # }
/// ```
2018-04-14 01:02:01 +02:00
pub struct CorsBuilder<S = ()> {
cors: Option<Inner>,
2018-01-10 07:33:51 +01:00
methods: bool,
error: Option<http::Error>,
2018-01-10 22:41:33 +01:00
expose_hdrs: HashSet<HeaderName>,
resources: Vec<(String, ResourceHandler<S>)>,
app: Option<App<S>>,
2018-01-10 07:33:51 +01:00
}
2018-04-14 01:02:01 +02:00
fn cors<'a>(
2018-04-29 07:55:47 +02:00
parts: &'a mut Option<Inner>, err: &Option<http::Error>,
2018-04-14 01:02:01 +02:00
) -> Option<&'a mut Inner> {
2018-01-10 07:33:51 +01:00
if err.is_some() {
2018-04-14 01:02:01 +02:00
return None;
2018-01-10 07:33:51 +01:00
}
parts.as_mut()
}
impl<S: 'static> CorsBuilder<S> {
2018-01-10 07:33:51 +01:00
/// Add an origin that are allowed to make requests.
/// Will be verified against the `Origin` request header.
///
/// When `All` is set, and `send_wildcard` is set, "*" will be sent in
2018-04-14 01:02:01 +02:00
/// the `Access-Control-Allow-Origin` response header. Otherwise, the
/// client's `Origin` request header will be echoed back in the
/// `Access-Control-Allow-Origin` response header.
2018-01-10 07:33:51 +01:00
///
2018-04-14 01:02:01 +02:00
/// When `Some` is set, the client's `Origin` request header will be
/// checked in a case-sensitive manner.
2018-01-10 07:33:51 +01:00
///
/// This is the `list of origins` in the
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
///
/// Defaults to `All`.
///
/// Builder panics if supplied origin is not valid uri.
pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder<S> {
2018-01-10 07:33:51 +01:00
if let Some(cors) = cors(&mut self.cors, &self.error) {
match Uri::try_from(origin) {
2018-01-12 05:11:34 +01:00
Ok(_) => {
2018-01-10 07:33:51 +01:00
if cors.origins.is_all() {
cors.origins = AllOrSome::Some(HashSet::new());
}
if let AllOrSome::Some(ref mut origins) = cors.origins {
2018-01-12 05:11:34 +01:00
origins.insert(origin.to_owned());
2018-01-10 07:33:51 +01:00
}
}
Err(e) => {
self.error = Some(e.into());
}
}
}
self
}
2018-04-14 01:02:01 +02:00
/// Set a list of methods which the allowed origins are allowed to access
/// for requests.
2018-01-10 07:33:51 +01:00
///
/// This is the `list of methods` in the
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
///
/// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]`
pub fn allowed_methods<U, M>(&mut self, methods: U) -> &mut CorsBuilder<S>
2018-04-14 01:02:01 +02:00
where
U: IntoIterator<Item = M>,
Method: HttpTryFrom<M>,
2018-01-10 07:33:51 +01:00
{
self.methods = true;
if let Some(cors) = cors(&mut self.cors, &self.error) {
for m in methods {
2018-01-10 07:33:51 +01:00
match Method::try_from(m) {
Ok(method) => {
cors.methods.insert(method);
2018-04-14 01:02:01 +02:00
}
2018-01-10 07:33:51 +01:00
Err(e) => {
self.error = Some(e.into());
2018-04-14 01:02:01 +02:00
break;
2018-01-10 07:33:51 +01:00
}
}
2018-04-14 01:02:01 +02:00
}
2018-01-10 07:33:51 +01:00
}
self
}
/// Set an allowed header
pub fn allowed_header<H>(&mut self, header: H) -> &mut CorsBuilder<S>
2018-04-14 01:02:01 +02:00
where
HeaderName: HttpTryFrom<H>,
2018-01-10 07:33:51 +01:00
{
if let Some(cors) = cors(&mut self.cors, &self.error) {
match HeaderName::try_from(header) {
Ok(method) => {
if cors.headers.is_all() {
cors.headers = AllOrSome::Some(HashSet::new());
}
if let AllOrSome::Some(ref mut headers) = cors.headers {
headers.insert(method);
}
}
Err(e) => self.error = Some(e.into()),
}
}
self
}
/// Set a list of header field names which can be used when
/// this resource is accessed by allowed origins.
///
2018-04-14 01:02:01 +02:00
/// If `All` is set, whatever is requested by the client in
/// `Access-Control-Request-Headers` will be echoed back in the
/// `Access-Control-Allow-Headers` header.
2018-01-10 07:33:51 +01:00
///
/// This is the `list of headers` in the
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
///
/// Defaults to `All`.
pub fn allowed_headers<U, H>(&mut self, headers: U) -> &mut CorsBuilder<S>
2018-04-14 01:02:01 +02:00
where
U: IntoIterator<Item = H>,
HeaderName: HttpTryFrom<H>,
2018-01-10 07:33:51 +01:00
{
if let Some(cors) = cors(&mut self.cors, &self.error) {
for h in headers {
2018-01-10 07:33:51 +01:00
match HeaderName::try_from(h) {
Ok(method) => {
if cors.headers.is_all() {
cors.headers = AllOrSome::Some(HashSet::new());
}
if let AllOrSome::Some(ref mut headers) = cors.headers {
headers.insert(method);
}
}
Err(e) => {
self.error = Some(e.into());
2018-04-14 01:02:01 +02:00
break;
2018-01-10 07:33:51 +01:00
}
}
2018-04-14 01:02:01 +02:00
}
2018-01-10 07:33:51 +01:00
}
self
}
2018-04-14 01:02:01 +02:00
/// Set a list of headers which are safe to expose to the API of a CORS API
/// specification. This corresponds to the
/// `Access-Control-Expose-Headers` response header.
2018-01-10 22:41:33 +01:00
///
/// This is the `list of exposed headers` in the
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
///
/// This defaults to an empty set.
pub fn expose_headers<U, H>(&mut self, headers: U) -> &mut CorsBuilder<S>
2018-04-14 01:02:01 +02:00
where
U: IntoIterator<Item = H>,
HeaderName: HttpTryFrom<H>,
2018-01-10 22:41:33 +01:00
{
for h in headers {
match HeaderName::try_from(h) {
Ok(method) => {
self.expose_hdrs.insert(method);
2018-04-14 01:02:01 +02:00
}
2018-01-10 22:41:33 +01:00
Err(e) => {
self.error = Some(e.into());
2018-04-14 01:02:01 +02:00
break;
2018-01-10 22:41:33 +01:00
}
}
}
self
}
2018-01-10 07:33:51 +01:00
/// Set a maximum time for which this CORS request maybe cached.
/// This value is set as the `Access-Control-Max-Age` header.
///
/// This defaults to `None` (unset).
pub fn max_age(&mut self, max_age: usize) -> &mut CorsBuilder<S> {
2018-01-10 07:33:51 +01:00
if let Some(cors) = cors(&mut self.cors, &self.error) {
cors.max_age = Some(max_age)
}
self
}
2018-01-10 08:55:42 +01:00
/// Set a wildcard origins
///
2018-04-14 01:02:01 +02:00
/// If send wildcard is set and the `allowed_origins` parameter is `All`, a
/// wildcard `Access-Control-Allow-Origin` response header is sent,
/// rather than the requests `Origin` header.
2018-01-10 08:55:42 +01:00
///
/// This is the `supports credentials flag` in the
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
///
2018-04-14 01:02:01 +02:00
/// This **CANNOT** be used in conjunction with `allowed_origins` set to
/// `All` and `allow_credentials` set to `true`. Depending on the mode
/// of usage, this will either result in an `Error::
/// CredentialsWithWildcardOrigin` error during actix launch or runtime.
2018-01-10 08:55:42 +01:00
///
/// Defaults to `false`.
pub fn send_wildcard(&mut self) -> &mut CorsBuilder<S> {
2018-01-10 08:55:42 +01:00
if let Some(cors) = cors(&mut self.cors, &self.error) {
2018-01-10 22:41:33 +01:00
cors.send_wildcard = true
}
self
}
/// Allows users to make authenticated requests
///
2018-04-14 01:02:01 +02:00
/// If true, injects the `Access-Control-Allow-Credentials` header in
/// responses. This allows cookies and credentials to be submitted
/// across domains.
2018-01-10 22:41:33 +01:00
///
2018-04-14 01:02:01 +02:00
/// This option cannot be used in conjunction with an `allowed_origin` set
/// to `All` and `send_wildcards` set to `true`.
2018-01-10 22:41:33 +01:00
///
/// Defaults to `false`.
///
/// Builder panics if credentials are allowed, but the Origin is set to "*".
/// This is not allowed by W3C
pub fn supports_credentials(&mut self) -> &mut CorsBuilder<S> {
2018-01-10 22:41:33 +01:00
if let Some(cors) = cors(&mut self.cors, &self.error) {
cors.supports_credentials = true
2018-01-10 08:55:42 +01:00
}
self
}
2018-01-10 22:41:33 +01:00
/// Disable `Vary` header support.
///
/// When enabled the header `Vary: Origin` will be returned as per the W3
/// implementation guidelines.
///
/// Setting this header when the `Access-Control-Allow-Origin` is
/// dynamically generated (e.g. when there is more than one allowed
/// origin, and an Origin than '*' is returned) informs CDNs and other
/// caches that the CORS headers are dynamic, and cannot be cached.
///
/// By default `vary` header support is enabled.
pub fn disable_vary_header(&mut self) -> &mut CorsBuilder<S> {
2018-01-10 22:41:33 +01:00
if let Some(cors) = cors(&mut self.cors, &self.error) {
cors.vary_header = false
}
self
}
/// Disable *preflight* request support.
///
/// When enabled cors middleware automatically handles *OPTIONS* request.
/// This is useful application level middleware.
///
/// By default *preflight* support is enabled.
pub fn disable_preflight(&mut self) -> &mut CorsBuilder<S> {
2018-01-10 22:41:33 +01:00
if let Some(cors) = cors(&mut self.cors, &self.error) {
cors.preflight = false
}
self
}
/// Configure resource for a specific path.
///
/// This is similar to a `App::resource()` method. Except, cors middleware
/// get registered for the resource.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::cors::Cors;
2018-06-01 18:37:14 +02:00
/// use actix_web::{http, App, HttpResponse};
///
/// fn main() {
2018-06-01 18:37:14 +02:00
/// let app = App::new().configure(
/// |app| {
/// Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/")
/// .allowed_methods(vec!["GET", "POST"])
/// .allowed_header(http::header::CONTENT_TYPE)
/// .max_age(3600)
/// .resource("/resource1", |r| { // register resource
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// })
/// .resource("/resource2", |r| { // register another resource
/// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed());
/// })
2018-06-01 18:37:14 +02:00
/// .register()
/// }, // construct CORS and return application instance
/// );
/// }
/// ```
pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut CorsBuilder<S>
2018-04-14 01:02:01 +02:00
where
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static,
{
// add resource handler
let mut handler = ResourceHandler::default();
f(&mut handler);
self.resources.push((path.to_owned(), handler));
self
}
fn construct(&mut self) -> Cors {
2018-01-10 07:33:51 +01:00
if !self.methods {
2018-04-14 01:02:01 +02:00
self.allowed_methods(vec![
Method::GET,
Method::HEAD,
Method::POST,
Method::OPTIONS,
Method::PUT,
Method::PATCH,
Method::DELETE,
]);
2018-01-10 07:33:51 +01:00
}
if let Some(e) = self.error.take() {
panic!("{}", e);
2018-01-10 22:41:33 +01:00
}
let mut cors = self.cors.take().expect("cannot reuse CorsBuilder");
if cors.supports_credentials && cors.send_wildcard && cors.origins.is_all() {
panic!("Credentials are allowed, but the Origin is set to \"*\"");
2018-01-10 22:41:33 +01:00
}
if let AllOrSome::Some(ref origins) = cors.origins {
2018-04-29 18:09:08 +02:00
let s = origins
.iter()
.fold(String::new(), |s, v| s + &v.to_string());
2018-01-10 22:41:33 +01:00
cors.origins_str = Some(HeaderValue::try_from(s.as_str()).unwrap());
}
if !self.expose_hdrs.is_empty() {
cors.expose_hdrs = Some(
2018-04-29 18:09:08 +02:00
self.expose_hdrs
.iter()
.fold(String::new(), |s, v| s + v.as_str())[1..]
2018-04-14 01:02:01 +02:00
.to_owned(),
);
}
Cors {
inner: Rc::new(cors),
2018-01-10 07:33:51 +01:00
}
2018-01-10 22:41:33 +01:00
}
/// Finishes building and returns the built `Cors` instance.
///
/// This method panics in case of any configuration error.
pub fn finish(&mut self) -> Cors {
if !self.resources.is_empty() {
2018-04-14 01:02:01 +02:00
panic!(
"CorsBuilder::resource() was used,
to construct CORS `.register(app)` method should be used"
);
}
self.construct()
}
2018-04-14 01:02:01 +02:00
/// Finishes building Cors middleware and register middleware for
/// application
///
/// This method panics in case of any configuration error or if non of
/// resources are registered.
pub fn register(&mut self) -> App<S> {
if self.resources.is_empty() {
panic!("No resources are registered.");
}
let cors = self.construct();
2018-05-17 21:20:20 +02:00
let mut app = self
.app
2018-04-14 01:02:01 +02:00
.take()
.expect("CorsBuilder has to be constructed with Cors::for_app(app)");
// register resources
for (path, mut resource) in self.resources.drain(..) {
cors.clone().register(&mut resource);
app.register_resource(&path, resource);
}
app
}
2018-01-10 22:41:33 +01:00
}
#[cfg(test)]
mod tests {
use super::*;
2018-04-10 06:29:57 +02:00
use test::{self, TestRequest};
2018-01-10 22:41:33 +01:00
impl Started {
fn is_done(&self) -> bool {
match *self {
Started::Done => true,
_ => false,
}
}
fn response(self) -> HttpResponse {
match self {
Started::Response(resp) => resp,
_ => panic!(),
}
}
}
2018-01-10 23:56:45 +01:00
impl Response {
fn response(self) -> HttpResponse {
match self {
Response::Done(resp) => resp,
_ => panic!(),
}
}
}
2018-01-10 22:41:33 +01:00
#[test]
#[should_panic(expected = "Credentials are allowed, but the Origin is set to")]
2018-01-10 22:41:33 +01:00
fn cors_validates_illegal_allow_credentials() {
2018-04-29 18:09:08 +02:00
Cors::build()
.supports_credentials()
.send_wildcard()
.finish();
2018-01-10 22:41:33 +01:00
}
#[test]
#[should_panic(expected = "No resources are registered")]
fn no_resource() {
2018-04-29 18:09:08 +02:00
Cors::build()
.supports_credentials()
.send_wildcard()
.register();
}
#[test]
#[should_panic(expected = "Cors::for_app(app)")]
fn no_resource2() {
2018-04-29 18:09:08 +02:00
Cors::build()
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.register();
}
2018-01-10 22:41:33 +01:00
#[test]
fn validate_origin_allows_all_origins() {
2018-06-21 19:06:23 +02:00
let cors = Cors::default();
2018-04-14 01:02:01 +02:00
let mut req =
TestRequest::with_header("Origin", "https://www.example.com").finish();
2018-01-10 22:41:33 +01:00
assert!(cors.start(&mut req).ok().unwrap().is_done())
}
#[test]
fn test_preflight() {
let mut cors = Cors::build()
.send_wildcard()
.max_age(3600)
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
.allowed_header(header::CONTENT_TYPE)
.finish();
2018-01-10 22:41:33 +01:00
2018-04-14 01:02:01 +02:00
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
2018-01-10 22:41:33 +01:00
.method(Method::OPTIONS)
.finish();
assert!(cors.start(&mut req).is_err());
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "put")
.method(Method::OPTIONS)
.finish();
assert!(cors.start(&mut req).is_err());
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
2018-04-29 18:09:08 +02:00
.header(
header::ACCESS_CONTROL_REQUEST_HEADERS,
"AUTHORIZATION,ACCEPT",
)
2018-01-10 22:41:33 +01:00
.method(Method::OPTIONS)
.finish();
let resp = cors.start(&mut req).unwrap().response();
assert_eq!(
&b"*"[..],
2018-04-29 18:09:08 +02:00
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
2018-04-14 01:02:01 +02:00
);
2018-01-10 22:41:33 +01:00
assert_eq!(
&b"3600"[..],
2018-04-29 18:09:08 +02:00
resp.headers()
.get(header::ACCESS_CONTROL_MAX_AGE)
.unwrap()
.as_bytes()
2018-04-14 01:02:01 +02:00
);
2018-01-10 22:41:33 +01:00
//assert_eq!(
// &b"authorization,accept,content-type"[..],
2018-04-14 01:02:01 +02:00
// resp.headers().get(header::ACCESS_CONTROL_ALLOW_HEADERS).unwrap().
// as_bytes()); assert_eq!(
2018-01-10 22:41:33 +01:00
// &b"POST,GET,OPTIONS"[..],
2018-04-14 01:02:01 +02:00
// resp.headers().get(header::ACCESS_CONTROL_ALLOW_METHODS).unwrap().
// as_bytes());
2018-01-10 07:33:51 +01:00
Rc::get_mut(&mut cors.inner).unwrap().preflight = false;
2018-01-10 22:41:33 +01:00
assert!(cors.start(&mut req).unwrap().is_done());
2018-01-10 07:33:51 +01:00
}
2018-01-10 23:20:00 +01:00
// #[test]
// #[should_panic(expected = "MissingOrigin")]
// fn test_validate_missing_origin() {
2018-06-21 19:06:23 +02:00
// let cors = Cors::build()
// .allowed_origin("https://www.example.com")
// .finish();
// let mut req = HttpRequest::default();
// cors.start(&mut req).unwrap();
// }
2018-01-10 23:56:45 +01:00
#[test]
#[should_panic(expected = "OriginNotAllowed")]
fn test_validate_not_allowed_origin() {
2018-06-21 19:06:23 +02:00
let cors = Cors::build()
2018-04-29 18:09:08 +02:00
.allowed_origin("https://www.example.com")
.finish();
2018-01-10 23:20:00 +01:00
2018-01-10 23:21:48 +01:00
let mut req = TestRequest::with_header("Origin", "https://www.unknown.com")
2018-01-10 23:20:00 +01:00
.method(Method::GET)
.finish();
2018-01-10 23:56:45 +01:00
cors.start(&mut req).unwrap();
}
2018-01-10 23:20:00 +01:00
2018-01-10 23:56:45 +01:00
#[test]
fn test_validate_origin() {
2018-06-21 19:06:23 +02:00
let cors = Cors::build()
2018-04-29 18:09:08 +02:00
.allowed_origin("https://www.example.com")
.finish();
2018-01-10 23:21:48 +01:00
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::GET)
.finish();
assert!(cors.start(&mut req).unwrap().is_done());
2018-01-10 23:20:00 +01:00
}
2018-01-10 23:56:45 +01:00
#[test]
fn test_no_origin_response() {
2018-06-21 19:06:23 +02:00
let cors = Cors::build().finish();
let mut req = TestRequest::default().method(Method::GET).finish();
let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&mut req, resp).unwrap().response();
2018-04-29 18:09:08 +02:00
assert!(
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.is_none()
);
2018-04-14 01:02:01 +02:00
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.finish();
let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!(
&b"https://www.example.com"[..],
2018-04-29 18:09:08 +02:00
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
2018-04-14 01:02:01 +02:00
);
}
2018-01-10 23:56:45 +01:00
#[test]
fn test_response() {
2018-06-21 19:06:23 +02:00
let cors = Cors::build()
2018-01-10 23:56:45 +01:00
.send_wildcard()
2018-01-11 00:28:33 +01:00
.disable_preflight()
2018-01-10 23:56:45 +01:00
.max_age(3600)
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
.allowed_header(header::CONTENT_TYPE)
.finish();
2018-01-10 23:56:45 +01:00
2018-04-14 01:02:01 +02:00
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
2018-01-10 23:56:45 +01:00
.method(Method::OPTIONS)
.finish();
let resp: HttpResponse = HttpResponse::Ok().into();
2018-01-10 23:56:45 +01:00
let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!(
&b"*"[..],
2018-04-29 18:09:08 +02:00
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
);
assert_eq!(
&b"Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes()
2018-04-14 01:02:01 +02:00
);
2018-01-10 23:56:45 +01:00
2018-05-17 21:20:20 +02:00
let resp: HttpResponse =
HttpResponse::Ok().header(header::VARY, "Accept").finish();
2018-01-10 23:56:45 +01:00
let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!(
&b"Accept, Origin"[..],
2018-04-14 01:02:01 +02:00
resp.headers().get(header::VARY).unwrap().as_bytes()
);
2018-01-10 23:56:45 +01:00
2018-06-21 19:06:23 +02:00
let cors = Cors::build()
2018-01-11 00:28:33 +01:00
.disable_vary_header()
2018-01-10 23:56:45 +01:00
.allowed_origin("https://www.example.com")
.finish();
let resp: HttpResponse = HttpResponse::Ok().into();
2018-01-10 23:56:45 +01:00
let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!(
2018-01-12 05:20:50 +01:00
&b"https://www.example.com"[..],
2018-04-29 18:09:08 +02:00
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
2018-04-14 01:02:01 +02:00
);
2018-01-10 23:56:45 +01:00
}
#[test]
fn cors_resource() {
2018-04-14 01:02:01 +02:00
let mut srv = test::TestServer::with_factory(|| {
App::new().configure(|app| {
Cors::for_app(app)
.allowed_origin("https://www.example.com")
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.register()
})
});
2018-04-10 06:29:57 +02:00
let request = srv
.get()
.uri(srv.url("/test"))
.header("ORIGIN", "https://www.example2.com")
.finish()
.unwrap();
2018-04-10 06:29:57 +02:00
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
2018-05-17 21:20:20 +02:00
let request = srv
.get()
2018-04-14 01:02:01 +02:00
.uri(srv.url("/test"))
.header("ORIGIN", "https://www.example.com")
.finish()
.unwrap();
2018-04-10 06:29:57 +02:00
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
2018-01-10 07:33:51 +01:00
}