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.
|
2019-03-25 21:02:10 +01:00
|
|
|
|
//! Cors middleware could be used as parameter for `App::wrap()`,
|
|
|
|
|
//! `Resource::wrap()` or `Scope::wrap()` methods.
|
2018-01-10 08:55:42 +01:00
|
|
|
|
//!
|
|
|
|
|
//! # Example
|
|
|
|
|
//!
|
|
|
|
|
//! ```rust
|
2018-04-10 06:39:32 +02:00
|
|
|
|
//! use actix_web::middleware::cors::Cors;
|
2019-03-24 05:29:16 +01:00
|
|
|
|
//! use actix_web::{http, web, App, HttpRequest, HttpResponse, HttpServer};
|
2018-01-10 08:55:42 +01:00
|
|
|
|
//!
|
2019-03-24 05:29:16 +01:00
|
|
|
|
//! fn index(req: HttpRequest) -> &'static str {
|
2018-06-01 18:37:14 +02:00
|
|
|
|
//! "Hello world"
|
2018-01-10 08:55:42 +01:00
|
|
|
|
//! }
|
|
|
|
|
//!
|
2019-03-24 05:29:16 +01:00
|
|
|
|
//! fn main() -> std::io::Result<()> {
|
|
|
|
|
//! HttpServer::new(|| App::new()
|
2019-03-25 21:02:10 +01:00
|
|
|
|
//! .wrap(
|
2019-03-24 05:29:16 +01:00
|
|
|
|
//! Cors::new() // <- Construct CORS middleware builder
|
|
|
|
|
//! .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))
|
|
|
|
|
//! .service(
|
|
|
|
|
//! web::resource("/index.html")
|
|
|
|
|
//! .route(web::get().to(index))
|
|
|
|
|
//! .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
|
|
|
|
|
//! ))
|
|
|
|
|
//! .bind("127.0.0.1:8080")?;
|
|
|
|
|
//!
|
|
|
|
|
//! Ok(())
|
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;
|
2018-04-09 23:20:12 +02:00
|
|
|
|
use std::rc::Rc;
|
2018-01-10 07:33:51 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
use actix_service::{IntoTransform, Service, Transform};
|
|
|
|
|
use derive_more::Display;
|
|
|
|
|
use futures::future::{ok, Either, Future, FutureResult};
|
|
|
|
|
use futures::Poll;
|
2018-01-10 07:33:51 +01:00
|
|
|
|
|
2019-03-27 18:38:01 +01:00
|
|
|
|
use crate::dev::RequestHead;
|
2019-03-24 05:29:16 +01:00
|
|
|
|
use crate::error::{ResponseError, Result};
|
|
|
|
|
use crate::http::header::{self, HeaderName, HeaderValue};
|
|
|
|
|
use crate::http::{self, HttpTryFrom, Method, StatusCode, Uri};
|
|
|
|
|
use crate::service::{ServiceRequest, ServiceResponse};
|
|
|
|
|
use crate::{HttpMessage, HttpResponse};
|
2018-01-10 07:33:51 +01:00
|
|
|
|
|
|
|
|
|
/// A set of errors that can occur during processing CORS
|
2019-03-24 05:29:16 +01:00
|
|
|
|
#[derive(Debug, Display)]
|
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
|
2019-03-24 05:29:16 +01:00
|
|
|
|
#[display(
|
|
|
|
|
fmt = "The HTTP request header `Origin` is required but was not provided"
|
2018-04-29 07:55:47 +02:00
|
|
|
|
)]
|
2018-01-10 07:33:51 +01:00
|
|
|
|
MissingOrigin,
|
|
|
|
|
/// The HTTP request header `Origin` could not be parsed correctly.
|
2019-03-24 05:29:16 +01:00
|
|
|
|
#[display(fmt = "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
|
2019-03-24 05:29:16 +01:00
|
|
|
|
#[display(
|
|
|
|
|
fmt = "The request header `Access-Control-Request-Method` is required but is missing"
|
2018-04-29 07:55:47 +02:00
|
|
|
|
)]
|
2018-01-10 07:33:51 +01:00
|
|
|
|
MissingRequestMethod,
|
|
|
|
|
/// The request header `Access-Control-Request-Method` has an invalid value
|
2019-03-24 05:29:16 +01:00
|
|
|
|
#[display(
|
|
|
|
|
fmt = "The request header `Access-Control-Request-Method` has an invalid value"
|
2018-04-29 07:55:47 +02:00
|
|
|
|
)]
|
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
|
2019-03-24 05:29:16 +01:00
|
|
|
|
#[display(
|
|
|
|
|
fmt = "The request header `Access-Control-Request-Headers` has an invalid value"
|
2018-04-29 07:55:47 +02:00
|
|
|
|
)]
|
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.
|
2019-03-24 05:29:16 +01:00
|
|
|
|
#[display(
|
|
|
|
|
fmt = "The request header `Access-Control-Request-Headers` is required but is
|
2018-04-29 07:55:47 +02:00
|
|
|
|
missing"
|
|
|
|
|
)]
|
2018-01-10 07:33:51 +01:00
|
|
|
|
MissingRequestHeaders,
|
|
|
|
|
/// Origin is not allowed to make this request
|
2019-03-24 05:29:16 +01:00
|
|
|
|
#[display(fmt = "Origin is not allowed to make this request")]
|
2018-01-10 07:33:51 +01:00
|
|
|
|
OriginNotAllowed,
|
|
|
|
|
/// Requested method is not allowed
|
2019-03-24 05:29:16 +01:00
|
|
|
|
#[display(fmt = "Requested method is not allowed")]
|
2018-01-10 07:33:51 +01:00
|
|
|
|
MethodNotAllowed,
|
|
|
|
|
/// One or more headers requested are not allowed
|
2019-03-24 05:29:16 +01:00
|
|
|
|
#[display(fmt = "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 {
|
2019-03-24 05:29:16 +01:00
|
|
|
|
HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self).into())
|
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
|
|
|
|
}
|
|
|
|
|
|
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
|
2019-03-24 05:29:16 +01:00
|
|
|
|
/// use actix_web::http::header;
|
2018-01-10 07:33:51 +01:00
|
|
|
|
/// use actix_web::middleware::cors;
|
|
|
|
|
///
|
|
|
|
|
/// # fn main() {
|
2019-03-24 05:29:16 +01:00
|
|
|
|
/// let cors = cors::Cors::new()
|
2018-01-10 07:33:51 +01:00
|
|
|
|
/// .allowed_origin("https://www.rust-lang.org/")
|
|
|
|
|
/// .allowed_methods(vec!["GET", "POST"])
|
|
|
|
|
/// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
|
|
|
|
|
/// .allowed_header(header::CONTENT_TYPE)
|
2019-03-24 05:29:16 +01:00
|
|
|
|
/// .max_age(3600);
|
2018-01-10 07:33:51 +01:00
|
|
|
|
/// # }
|
|
|
|
|
/// ```
|
2019-03-24 05:29:16 +01:00
|
|
|
|
pub struct Cors {
|
2018-04-09 23:20:12 +02:00
|
|
|
|
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>,
|
2018-01-10 07:33:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
impl Cors {
|
|
|
|
|
/// Build a new CORS middleware instance
|
|
|
|
|
pub fn new() -> Cors {
|
|
|
|
|
Cors {
|
|
|
|
|
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(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Build a new CORS default middleware
|
|
|
|
|
pub fn default() -> CorsFactory {
|
|
|
|
|
let inner = Inner {
|
|
|
|
|
origins: AllOrSome::default(),
|
|
|
|
|
origins_str: None,
|
|
|
|
|
methods: HashSet::from_iter(
|
|
|
|
|
vec![
|
|
|
|
|
Method::GET,
|
|
|
|
|
Method::HEAD,
|
|
|
|
|
Method::POST,
|
|
|
|
|
Method::OPTIONS,
|
|
|
|
|
Method::PUT,
|
|
|
|
|
Method::PATCH,
|
|
|
|
|
Method::DELETE,
|
|
|
|
|
]
|
|
|
|
|
.into_iter(),
|
|
|
|
|
),
|
|
|
|
|
headers: AllOrSome::All,
|
|
|
|
|
expose_hdrs: None,
|
|
|
|
|
max_age: None,
|
|
|
|
|
preflight: true,
|
|
|
|
|
send_wildcard: false,
|
|
|
|
|
supports_credentials: false,
|
|
|
|
|
vary_header: true,
|
|
|
|
|
};
|
|
|
|
|
CorsFactory {
|
|
|
|
|
inner: Rc::new(inner),
|
|
|
|
|
}
|
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`.
|
2018-04-09 23:20:12 +02:00
|
|
|
|
///
|
|
|
|
|
/// Builder panics if supplied origin is not valid uri.
|
2019-03-24 05:29:16 +01:00
|
|
|
|
pub fn allowed_origin(mut self, origin: &str) -> Cors {
|
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]`
|
2019-03-24 05:29:16 +01:00
|
|
|
|
pub fn allowed_methods<U, M>(mut self, methods: U) -> Cors
|
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) {
|
2018-01-10 19:12:34 +01:00
|
|
|
|
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
|
2019-03-24 05:29:16 +01:00
|
|
|
|
pub fn allowed_header<H>(mut self, header: H) -> Cors
|
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`.
|
2019-03-24 05:29:16 +01:00
|
|
|
|
pub fn allowed_headers<U, H>(mut self, headers: U) -> Cors
|
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) {
|
2018-01-10 19:12:34 +01:00
|
|
|
|
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.
|
2019-03-24 05:29:16 +01:00
|
|
|
|
pub fn expose_headers<U, H>(mut self, headers: U) -> Cors
|
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).
|
2019-03-24 05:29:16 +01:00
|
|
|
|
pub fn max_age(mut self, max_age: usize) -> Cors {
|
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 request’s `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`.
|
2019-03-24 05:29:16 +01:00
|
|
|
|
pub fn send_wildcard(mut self) -> Cors {
|
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`.
|
2018-04-09 23:20:12 +02:00
|
|
|
|
///
|
|
|
|
|
/// Builder panics if credentials are allowed, but the Origin is set to "*".
|
|
|
|
|
/// This is not allowed by W3C
|
2019-03-24 05:29:16 +01:00
|
|
|
|
pub fn supports_credentials(mut self) -> Cors {
|
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.
|
2019-03-24 05:29:16 +01:00
|
|
|
|
pub fn disable_vary_header(mut self) -> Cors {
|
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.
|
2019-03-24 05:29:16 +01:00
|
|
|
|
pub fn disable_preflight(mut self) -> Cors {
|
2018-01-10 22:41:33 +01:00
|
|
|
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
|
|
|
|
cors.preflight = false
|
|
|
|
|
}
|
|
|
|
|
self
|
|
|
|
|
}
|
2019-03-24 05:29:16 +01:00
|
|
|
|
}
|
2018-01-10 22:41:33 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
fn cors<'a>(
|
|
|
|
|
parts: &'a mut Option<Inner>,
|
|
|
|
|
err: &Option<http::Error>,
|
|
|
|
|
) -> Option<&'a mut Inner> {
|
|
|
|
|
if err.is_some() {
|
|
|
|
|
return None;
|
2018-04-10 06:11:15 +02:00
|
|
|
|
}
|
2019-03-24 05:29:16 +01:00
|
|
|
|
parts.as_mut()
|
|
|
|
|
}
|
2018-04-10 06:11:15 +02:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
impl<S, P, B> IntoTransform<CorsFactory, S> for Cors
|
|
|
|
|
where
|
|
|
|
|
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
|
|
|
|
|
P: 'static,
|
|
|
|
|
B: 'static,
|
|
|
|
|
{
|
|
|
|
|
fn into_transform(self) -> CorsFactory {
|
|
|
|
|
let mut slf = 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,
|
2019-03-24 05:29:16 +01:00
|
|
|
|
])
|
|
|
|
|
} else {
|
|
|
|
|
self
|
|
|
|
|
};
|
2018-01-10 07:33:51 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
if let Some(e) = slf.error.take() {
|
2018-04-09 23:20:12 +02:00
|
|
|
|
panic!("{}", e);
|
2018-01-10 22:41:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let mut cors = slf.cors.take().expect("cannot reuse CorsBuilder");
|
2018-01-10 22:41:33 +01:00
|
|
|
|
|
|
|
|
|
if cors.supports_credentials && cors.send_wildcard && cors.origins.is_all() {
|
2018-04-09 23:20:12 +02:00
|
|
|
|
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()
|
2018-09-21 07:45:22 +02:00
|
|
|
|
.fold(String::new(), |s, v| format!("{}, {}", s, v));
|
|
|
|
|
cors.origins_str = Some(HeaderValue::try_from(&s[2..]).unwrap());
|
2018-01-10 22:41:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
if !slf.expose_hdrs.is_empty() {
|
2018-01-10 22:41:33 +01:00
|
|
|
|
cors.expose_hdrs = Some(
|
2019-03-24 05:29:16 +01:00
|
|
|
|
slf.expose_hdrs
|
2018-08-23 18:48:01 +02:00
|
|
|
|
.iter()
|
2018-08-03 14:03:11 +02:00
|
|
|
|
.fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..]
|
2018-08-23 18:48:01 +02:00
|
|
|
|
.to_owned(),
|
2018-04-14 01:02:01 +02:00
|
|
|
|
);
|
|
|
|
|
}
|
2019-03-24 05:29:16 +01:00
|
|
|
|
|
|
|
|
|
CorsFactory {
|
2018-04-14 01:02:01 +02:00
|
|
|
|
inner: Rc::new(cors),
|
2018-01-10 07:33:51 +01:00
|
|
|
|
}
|
2018-01-10 22:41:33 +01:00
|
|
|
|
}
|
2019-03-24 05:29:16 +01:00
|
|
|
|
}
|
2018-04-10 06:11:15 +02:00
|
|
|
|
|
2019-03-24 05:29:16 +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.
|
|
|
|
|
pub struct CorsFactory {
|
|
|
|
|
inner: Rc<Inner>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<S, P, B> Transform<S> for CorsFactory
|
|
|
|
|
where
|
|
|
|
|
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
|
|
|
|
S::Future: 'static,
|
|
|
|
|
S::Error: 'static,
|
|
|
|
|
P: 'static,
|
|
|
|
|
B: 'static,
|
|
|
|
|
{
|
|
|
|
|
type Request = ServiceRequest<P>;
|
|
|
|
|
type Response = ServiceResponse<B>;
|
|
|
|
|
type Error = S::Error;
|
|
|
|
|
type InitError = ();
|
|
|
|
|
type Transform = CorsMiddleware<S>;
|
|
|
|
|
type Future = FutureResult<Self::Transform, Self::InitError>;
|
|
|
|
|
|
|
|
|
|
fn new_transform(&self, service: S) -> Self::Future {
|
|
|
|
|
ok(CorsMiddleware {
|
|
|
|
|
service,
|
|
|
|
|
inner: self.inner.clone(),
|
|
|
|
|
})
|
2018-04-10 06:11:15 +02:00
|
|
|
|
}
|
2019-03-24 05:29:16 +01:00
|
|
|
|
}
|
2018-04-10 06:11:15 +02:00
|
|
|
|
|
2019-03-24 05:29:16 +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)]
|
|
|
|
|
pub struct CorsMiddleware<S> {
|
|
|
|
|
service: S,
|
|
|
|
|
inner: Rc<Inner>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Inner {
|
|
|
|
|
methods: HashSet<Method>,
|
|
|
|
|
origins: AllOrSome<HashSet<String>>,
|
|
|
|
|
origins_str: Option<HeaderValue>,
|
|
|
|
|
headers: AllOrSome<HashSet<HeaderName>>,
|
|
|
|
|
expose_hdrs: Option<String>,
|
|
|
|
|
max_age: Option<usize>,
|
|
|
|
|
preflight: bool,
|
|
|
|
|
send_wildcard: bool,
|
|
|
|
|
supports_credentials: bool,
|
|
|
|
|
vary_header: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Inner {
|
|
|
|
|
fn validate_origin(&self, req: &RequestHead) -> Result<(), CorsError> {
|
|
|
|
|
if let Some(hdr) = req.headers().get(header::ORIGIN) {
|
|
|
|
|
if let Ok(origin) = hdr.to_str() {
|
|
|
|
|
return match self.origins {
|
|
|
|
|
AllOrSome::All => Ok(()),
|
|
|
|
|
AllOrSome::Some(ref allowed_origins) => allowed_origins
|
|
|
|
|
.get(origin)
|
|
|
|
|
.and_then(|_| Some(()))
|
|
|
|
|
.ok_or_else(|| CorsError::OriginNotAllowed),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
Err(CorsError::BadOrigin)
|
|
|
|
|
} else {
|
|
|
|
|
return match self.origins {
|
|
|
|
|
AllOrSome::All => Ok(()),
|
|
|
|
|
_ => Err(CorsError::MissingOrigin),
|
|
|
|
|
};
|
2018-04-10 06:11:15 +02:00
|
|
|
|
}
|
2019-03-24 05:29:16 +01:00
|
|
|
|
}
|
2018-04-10 06:11:15 +02:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
fn access_control_allow_origin(&self, req: &RequestHead) -> Option<HeaderValue> {
|
|
|
|
|
match self.origins {
|
|
|
|
|
AllOrSome::All => {
|
|
|
|
|
if self.send_wildcard {
|
|
|
|
|
Some(HeaderValue::from_static("*"))
|
|
|
|
|
} else if let Some(origin) = req.headers().get(header::ORIGIN) {
|
|
|
|
|
Some(origin.clone())
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
AllOrSome::Some(ref origins) => {
|
|
|
|
|
if let Some(origin) =
|
|
|
|
|
req.headers()
|
|
|
|
|
.get(header::ORIGIN)
|
|
|
|
|
.filter(|o| match o.to_str() {
|
|
|
|
|
Ok(os) => origins.contains(os),
|
|
|
|
|
_ => false,
|
|
|
|
|
})
|
|
|
|
|
{
|
|
|
|
|
Some(origin.clone())
|
|
|
|
|
} else {
|
|
|
|
|
Some(self.origins_str.as_ref().unwrap().clone())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-10 06:11:15 +02:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
fn validate_allowed_method(&self, req: &RequestHead) -> Result<(), CorsError> {
|
|
|
|
|
if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
|
|
|
|
|
if let Ok(meth) = hdr.to_str() {
|
|
|
|
|
if let Ok(method) = Method::try_from(meth) {
|
|
|
|
|
return self
|
|
|
|
|
.methods
|
|
|
|
|
.get(&method)
|
|
|
|
|
.and_then(|_| Some(()))
|
|
|
|
|
.ok_or_else(|| CorsError::MethodNotAllowed);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(CorsError::BadRequestMethod)
|
|
|
|
|
} else {
|
|
|
|
|
Err(CorsError::MissingRequestMethod)
|
2018-04-10 06:11:15 +02:00
|
|
|
|
}
|
2019-03-24 05:29:16 +01:00
|
|
|
|
}
|
2018-04-10 06:11:15 +02:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
fn validate_allowed_headers(&self, req: &RequestHead) -> Result<(), CorsError> {
|
|
|
|
|
match self.headers {
|
|
|
|
|
AllOrSome::All => Ok(()),
|
|
|
|
|
AllOrSome::Some(ref allowed_headers) => {
|
|
|
|
|
if let Some(hdr) =
|
|
|
|
|
req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
|
|
|
|
|
{
|
|
|
|
|
if let Ok(headers) = hdr.to_str() {
|
|
|
|
|
let mut hdrs = HashSet::new();
|
|
|
|
|
for hdr in headers.split(',') {
|
|
|
|
|
match HeaderName::try_from(hdr.trim()) {
|
|
|
|
|
Ok(hdr) => hdrs.insert(hdr),
|
|
|
|
|
Err(_) => return Err(CorsError::BadRequestHeaders),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !hdrs.is_empty() && !hdrs.is_subset(allowed_headers) {
|
|
|
|
|
return Err(CorsError::HeadersNotAllowed);
|
|
|
|
|
}
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
Err(CorsError::BadRequestHeaders)
|
|
|
|
|
} else {
|
|
|
|
|
Err(CorsError::MissingRequestHeaders)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-10 06:11:15 +02:00
|
|
|
|
}
|
2018-01-10 22:41:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
impl<S, P, B> Service for CorsMiddleware<S>
|
|
|
|
|
where
|
|
|
|
|
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
|
|
|
|
S::Future: 'static,
|
|
|
|
|
S::Error: 'static,
|
|
|
|
|
P: 'static,
|
|
|
|
|
B: 'static,
|
|
|
|
|
{
|
|
|
|
|
type Request = ServiceRequest<P>;
|
|
|
|
|
type Response = ServiceResponse<B>;
|
|
|
|
|
type Error = S::Error;
|
|
|
|
|
type Future = Either<
|
|
|
|
|
FutureResult<Self::Response, Self::Error>,
|
|
|
|
|
Either<S::Future, Box<Future<Item = Self::Response, Error = Self::Error>>>,
|
|
|
|
|
>;
|
|
|
|
|
|
|
|
|
|
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
|
|
|
|
self.service.poll_ready()
|
|
|
|
|
}
|
2018-01-10 22:41:33 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
|
|
|
|
if self.inner.preflight && Method::OPTIONS == *req.method() {
|
|
|
|
|
if let Err(e) = self
|
|
|
|
|
.inner
|
|
|
|
|
.validate_origin(&req)
|
|
|
|
|
.and_then(|_| self.inner.validate_allowed_method(&req))
|
|
|
|
|
.and_then(|_| self.inner.validate_allowed_headers(&req))
|
|
|
|
|
{
|
|
|
|
|
return Either::A(ok(req.error_response(e)));
|
2018-01-10 22:41:33 +01:00
|
|
|
|
}
|
2019-03-24 05:29:16 +01:00
|
|
|
|
|
|
|
|
|
// allowed headers
|
|
|
|
|
let headers = if let Some(headers) = self.inner.headers.as_ref() {
|
|
|
|
|
Some(
|
|
|
|
|
HeaderValue::try_from(
|
|
|
|
|
&headers
|
|
|
|
|
.iter()
|
|
|
|
|
.fold(String::new(), |s, v| s + "," + v.as_str())
|
|
|
|
|
.as_str()[1..],
|
|
|
|
|
)
|
|
|
|
|
.unwrap(),
|
|
|
|
|
)
|
|
|
|
|
} else if let Some(hdr) =
|
|
|
|
|
req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
|
|
|
|
|
{
|
|
|
|
|
Some(hdr.clone())
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let res = HttpResponse::Ok()
|
|
|
|
|
.if_some(self.inner.max_age.as_ref(), |max_age, resp| {
|
|
|
|
|
let _ = resp.header(
|
|
|
|
|
header::ACCESS_CONTROL_MAX_AGE,
|
|
|
|
|
format!("{}", max_age).as_str(),
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
.if_some(headers, |headers, resp| {
|
|
|
|
|
let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers);
|
|
|
|
|
})
|
|
|
|
|
.if_some(
|
|
|
|
|
self.inner.access_control_allow_origin(&req),
|
|
|
|
|
|origin, resp| {
|
|
|
|
|
let _ = resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.if_true(self.inner.supports_credentials, |resp| {
|
|
|
|
|
resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
|
|
|
|
})
|
|
|
|
|
.header(
|
|
|
|
|
header::ACCESS_CONTROL_ALLOW_METHODS,
|
|
|
|
|
&self
|
|
|
|
|
.inner
|
|
|
|
|
.methods
|
|
|
|
|
.iter()
|
|
|
|
|
.fold(String::new(), |s, v| s + "," + v.as_str())
|
|
|
|
|
.as_str()[1..],
|
|
|
|
|
)
|
|
|
|
|
.finish()
|
|
|
|
|
.into_body();
|
|
|
|
|
|
|
|
|
|
Either::A(ok(req.into_response(res)))
|
|
|
|
|
} else if req.headers().contains_key(header::ORIGIN) {
|
|
|
|
|
// Only check requests with a origin header.
|
|
|
|
|
if let Err(e) = self.inner.validate_origin(&req) {
|
|
|
|
|
return Either::A(ok(req.error_response(e)));
|
2018-01-10 22:41:33 +01:00
|
|
|
|
}
|
2019-03-24 05:29:16 +01:00
|
|
|
|
|
|
|
|
|
let inner = self.inner.clone();
|
|
|
|
|
|
|
|
|
|
Either::B(Either::B(Box::new(self.service.call(req).and_then(
|
|
|
|
|
move |mut res| {
|
|
|
|
|
if let Some(origin) =
|
|
|
|
|
inner.access_control_allow_origin(&res.request())
|
|
|
|
|
{
|
|
|
|
|
res.headers_mut()
|
|
|
|
|
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Some(ref expose) = inner.expose_hdrs {
|
|
|
|
|
res.headers_mut().insert(
|
|
|
|
|
header::ACCESS_CONTROL_EXPOSE_HEADERS,
|
|
|
|
|
HeaderValue::try_from(expose.as_str()).unwrap(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if inner.supports_credentials {
|
|
|
|
|
res.headers_mut().insert(
|
|
|
|
|
header::ACCESS_CONTROL_ALLOW_CREDENTIALS,
|
|
|
|
|
HeaderValue::from_static("true"),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if inner.vary_header {
|
|
|
|
|
let value =
|
|
|
|
|
if let Some(hdr) = res.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")
|
|
|
|
|
};
|
|
|
|
|
res.headers_mut().insert(header::VARY, value);
|
|
|
|
|
}
|
|
|
|
|
Ok(res)
|
|
|
|
|
},
|
|
|
|
|
))))
|
|
|
|
|
} else {
|
|
|
|
|
Either::B(Either::A(self.service.call(req)))
|
2018-01-10 22:41:33 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-24 05:29:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use actix_service::{FnService, Transform};
|
|
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
use crate::dev::PayloadStream;
|
|
|
|
|
use crate::test::{self, block_on, TestRequest};
|
|
|
|
|
|
|
|
|
|
impl Cors {
|
|
|
|
|
fn finish<S, P, B>(self, srv: S) -> CorsMiddleware<S>
|
|
|
|
|
where
|
|
|
|
|
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>
|
|
|
|
|
+ 'static,
|
|
|
|
|
S::Future: 'static,
|
|
|
|
|
S::Error: 'static,
|
|
|
|
|
P: 'static,
|
|
|
|
|
B: 'static,
|
|
|
|
|
{
|
|
|
|
|
block_on(
|
|
|
|
|
IntoTransform::<CorsFactory, S>::into_transform(self).new_transform(srv),
|
|
|
|
|
)
|
|
|
|
|
.unwrap()
|
2018-01-10 23:56:45 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-01-10 22:41:33 +01:00
|
|
|
|
|
|
|
|
|
#[test]
|
2018-04-09 23:20:12 +02:00
|
|
|
|
#[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() {
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let _cors = Cors::new()
|
2018-04-29 18:09:08 +02:00
|
|
|
|
.supports_credentials()
|
|
|
|
|
.send_wildcard()
|
2019-03-24 05:29:16 +01:00
|
|
|
|
.finish(test::ok_service());
|
2018-04-10 06:11:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-10 22:41:33 +01:00
|
|
|
|
#[test]
|
|
|
|
|
fn validate_origin_allows_all_origins() {
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let mut cors = Cors::new().finish(test::ok_service());
|
2019-04-01 05:43:00 +02:00
|
|
|
|
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
|
|
|
|
.to_srv_request();
|
2018-01-10 22:41:33 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
|
|
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
2018-01-10 22:41:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_preflight() {
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let mut cors = Cors::new()
|
2018-01-10 22:41:33 +01:00
|
|
|
|
.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)
|
2019-03-24 05:29:16 +01:00
|
|
|
|
.finish(test::ok_service());
|
2018-01-10 22:41:33 +01:00
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
|
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
2018-01-10 22:41:33 +01:00
|
|
|
|
.method(Method::OPTIONS)
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2018-01-10 22:41:33 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
assert!(cors.inner.validate_allowed_method(&req).is_err());
|
|
|
|
|
assert!(cors.inner.validate_allowed_headers(&req).is_err());
|
2019-03-24 06:03:40 +01:00
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
|
|
|
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
2018-01-10 22:41:33 +01:00
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
|
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
2018-01-10 22:41:33 +01:00
|
|
|
|
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "put")
|
|
|
|
|
.method(Method::OPTIONS)
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2018-01-10 22:41:33 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
assert!(cors.inner.validate_allowed_method(&req).is_err());
|
|
|
|
|
assert!(cors.inner.validate_allowed_headers(&req).is_err());
|
2018-01-10 22:41:33 +01:00
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
|
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
2018-01-10 22:41:33 +01:00
|
|
|
|
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
|
2018-04-29 18:09:08 +02:00
|
|
|
|
.header(
|
|
|
|
|
header::ACCESS_CONTROL_REQUEST_HEADERS,
|
|
|
|
|
"AUTHORIZATION,ACCEPT",
|
2019-03-24 05:29:16 +01:00
|
|
|
|
)
|
|
|
|
|
.method(Method::OPTIONS)
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2018-01-10 22:41:33 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
2018-01-10 22:41:33 +01:00
|
|
|
|
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
|
|
|
|
);
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let hdr = resp
|
|
|
|
|
.headers()
|
|
|
|
|
.get(header::ACCESS_CONTROL_ALLOW_HEADERS)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.to_str()
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert!(hdr.contains("authorization"));
|
|
|
|
|
assert!(hdr.contains("accept"));
|
|
|
|
|
assert!(hdr.contains("content-type"));
|
|
|
|
|
|
|
|
|
|
let methods = resp
|
|
|
|
|
.headers()
|
|
|
|
|
.get(header::ACCESS_CONTROL_ALLOW_METHODS)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.to_str()
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert!(methods.contains("POST"));
|
|
|
|
|
assert!(methods.contains("GET"));
|
|
|
|
|
assert!(methods.contains("OPTIONS"));
|
2018-01-10 07:33:51 +01:00
|
|
|
|
|
2018-04-09 23:20:12 +02:00
|
|
|
|
Rc::get_mut(&mut cors.inner).unwrap().preflight = false;
|
2019-03-24 05:29:16 +01:00
|
|
|
|
|
|
|
|
|
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
|
|
|
|
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
|
|
|
|
|
.header(
|
|
|
|
|
header::ACCESS_CONTROL_REQUEST_HEADERS,
|
|
|
|
|
"AUTHORIZATION,ACCEPT",
|
|
|
|
|
)
|
|
|
|
|
.method(Method::OPTIONS)
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2019-03-24 05:29:16 +01:00
|
|
|
|
|
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
|
|
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
2018-01-10 07:33:51 +01:00
|
|
|
|
}
|
2018-01-10 23:20:00 +01:00
|
|
|
|
|
2018-06-05 16:39:47 +02:00
|
|
|
|
// #[test]
|
|
|
|
|
// #[should_panic(expected = "MissingOrigin")]
|
|
|
|
|
// fn test_validate_missing_origin() {
|
2018-06-21 19:06:23 +02:00
|
|
|
|
// let cors = Cors::build()
|
2018-06-05 16:39:47 +02:00
|
|
|
|
// .allowed_origin("https://www.example.com")
|
|
|
|
|
// .finish();
|
|
|
|
|
// let mut req = HttpRequest::default();
|
2018-06-25 06:58:04 +02:00
|
|
|
|
// cors.start(&req).unwrap();
|
2018-06-05 16:39:47 +02:00
|
|
|
|
// }
|
2018-01-10 23:56:45 +01:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "OriginNotAllowed")]
|
|
|
|
|
fn test_validate_not_allowed_origin() {
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let cors = Cors::new()
|
2018-04-29 18:09:08 +02:00
|
|
|
|
.allowed_origin("https://www.example.com")
|
2019-03-24 05:29:16 +01:00
|
|
|
|
.finish(test::ok_service());
|
2018-01-10 23:20:00 +01:00
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
|
let req = TestRequest::with_header("Origin", "https://www.unknown.com")
|
2018-01-10 23:20:00 +01:00
|
|
|
|
.method(Method::GET)
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2019-03-24 05:29:16 +01:00
|
|
|
|
cors.inner.validate_origin(&req).unwrap();
|
|
|
|
|
cors.inner.validate_allowed_method(&req).unwrap();
|
|
|
|
|
cors.inner.validate_allowed_headers(&req).unwrap();
|
2018-01-10 23:56:45 +01:00
|
|
|
|
}
|
2018-01-10 23:20:00 +01:00
|
|
|
|
|
2018-01-10 23:56:45 +01:00
|
|
|
|
#[test]
|
|
|
|
|
fn test_validate_origin() {
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let mut cors = Cors::new()
|
2018-04-29 18:09:08 +02:00
|
|
|
|
.allowed_origin("https://www.example.com")
|
2019-03-24 05:29:16 +01:00
|
|
|
|
.finish(test::ok_service());
|
2018-01-10 23:21:48 +01:00
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
|
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
2018-01-10 23:21:48 +01:00
|
|
|
|
.method(Method::GET)
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2018-01-10 23:21:48 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
|
|
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
2018-01-10 23:20:00 +01:00
|
|
|
|
}
|
2018-01-10 23:56:45 +01:00
|
|
|
|
|
2018-03-10 17:31:20 +01:00
|
|
|
|
#[test]
|
|
|
|
|
fn test_no_origin_response() {
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let mut cors = Cors::new().disable_preflight().finish(test::ok_service());
|
2018-03-10 17:31:20 +01:00
|
|
|
|
|
2019-04-01 05:43:00 +02:00
|
|
|
|
let req = TestRequest::default().method(Method::GET).to_srv_request();
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
|
|
|
|
assert!(resp
|
|
|
|
|
.headers()
|
|
|
|
|
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
|
|
|
|
.is_none());
|
2018-03-10 17:31:20 +01:00
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
|
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
2018-03-10 17:31:20 +01:00
|
|
|
|
.method(Method::OPTIONS)
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
2018-03-10 17:31:20 +01:00
|
|
|
|
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-03-10 17:31:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-10 23:56:45 +01:00
|
|
|
|
#[test]
|
|
|
|
|
fn test_response() {
|
2018-08-03 14:03:11 +02:00
|
|
|
|
let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let mut cors = Cors::new()
|
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])
|
2018-08-03 14:03:11 +02:00
|
|
|
|
.allowed_headers(exposed_headers.clone())
|
|
|
|
|
.expose_headers(exposed_headers.clone())
|
2018-01-10 23:56:45 +01:00
|
|
|
|
.allowed_header(header::CONTENT_TYPE)
|
2019-03-24 05:29:16 +01:00
|
|
|
|
.finish(test::ok_service());
|
2018-01-10 23:56:45 +01:00
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
|
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
2018-01-10 23:56:45 +01:00
|
|
|
|
.method(Method::OPTIONS)
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2018-01-10 23:56:45 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
2018-01-10 23:56:45 +01:00
|
|
|
|
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-08-03 14:03:11 +02:00
|
|
|
|
{
|
2018-08-23 18:48:01 +02:00
|
|
|
|
let headers = resp
|
|
|
|
|
.headers()
|
2018-08-03 14:03:11 +02:00
|
|
|
|
.get(header::ACCESS_CONTROL_EXPOSE_HEADERS)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.to_str()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.split(',')
|
|
|
|
|
.map(|s| s.trim())
|
|
|
|
|
.collect::<Vec<&str>>();
|
|
|
|
|
|
|
|
|
|
for h in exposed_headers {
|
|
|
|
|
assert!(headers.contains(&h.as_str()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
|
|
|
|
|
let mut cors = Cors::new()
|
|
|
|
|
.send_wildcard()
|
|
|
|
|
.disable_preflight()
|
|
|
|
|
.max_age(3600)
|
|
|
|
|
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
|
|
|
|
|
.allowed_headers(exposed_headers.clone())
|
|
|
|
|
.expose_headers(exposed_headers.clone())
|
|
|
|
|
.allowed_header(header::CONTENT_TYPE)
|
|
|
|
|
.finish(FnService::new(move |req: ServiceRequest<PayloadStream>| {
|
|
|
|
|
req.into_response(
|
|
|
|
|
HttpResponse::Ok().header(header::VARY, "Accept").finish(),
|
|
|
|
|
)
|
|
|
|
|
}));
|
|
|
|
|
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
|
|
|
|
.method(Method::OPTIONS)
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
2018-01-10 23:56:45 +01:00
|
|
|
|
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
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let mut cors = Cors::new()
|
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")
|
2018-09-21 07:45:22 +02:00
|
|
|
|
.allowed_origin("https://www.google.com")
|
2019-03-24 05:29:16 +01:00
|
|
|
|
.finish(test::ok_service());
|
|
|
|
|
|
|
|
|
|
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
|
|
|
|
.method(Method::OPTIONS)
|
|
|
|
|
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
2018-09-21 07:45:22 +02:00
|
|
|
|
|
2018-09-27 05:43:54 +02:00
|
|
|
|
let origins_str = resp
|
|
|
|
|
.headers()
|
|
|
|
|
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.to_str()
|
|
|
|
|
.unwrap();
|
2018-09-21 07:45:22 +02:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
assert_eq!("https://www.example.com", origins_str);
|
2018-04-10 06:11:15 +02:00
|
|
|
|
}
|
2018-12-24 19:16:07 +01:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_multiple_origins() {
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let mut cors = Cors::new()
|
2018-12-24 19:16:07 +01:00
|
|
|
|
.allowed_origin("https://example.com")
|
|
|
|
|
.allowed_origin("https://example.org")
|
|
|
|
|
.allowed_methods(vec![Method::GET])
|
2019-03-24 05:29:16 +01:00
|
|
|
|
.finish(test::ok_service());
|
2018-12-24 19:16:07 +01:00
|
|
|
|
|
|
|
|
|
let req = TestRequest::with_header("Origin", "https://example.com")
|
|
|
|
|
.method(Method::GET)
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2018-12-24 19:16:07 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
2018-12-24 19:16:07 +01:00
|
|
|
|
assert_eq!(
|
|
|
|
|
&b"https://example.com"[..],
|
|
|
|
|
resp.headers()
|
|
|
|
|
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.as_bytes()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let req = TestRequest::with_header("Origin", "https://example.org")
|
|
|
|
|
.method(Method::GET)
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2018-12-24 19:16:07 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
2018-12-24 19:16:07 +01:00
|
|
|
|
assert_eq!(
|
|
|
|
|
&b"https://example.org"[..],
|
|
|
|
|
resp.headers()
|
|
|
|
|
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.as_bytes()
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-03-11 05:26:54 +01:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_multiple_origins_preflight() {
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let mut cors = Cors::new()
|
2019-03-11 05:26:54 +01:00
|
|
|
|
.allowed_origin("https://example.com")
|
|
|
|
|
.allowed_origin("https://example.org")
|
|
|
|
|
.allowed_methods(vec![Method::GET])
|
2019-03-24 05:29:16 +01:00
|
|
|
|
.finish(test::ok_service());
|
2019-03-11 05:26:54 +01:00
|
|
|
|
|
|
|
|
|
let req = TestRequest::with_header("Origin", "https://example.com")
|
|
|
|
|
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
|
|
|
|
.method(Method::OPTIONS)
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2019-03-11 05:26:54 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
2019-03-11 05:26:54 +01:00
|
|
|
|
assert_eq!(
|
|
|
|
|
&b"https://example.com"[..],
|
|
|
|
|
resp.headers()
|
|
|
|
|
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.as_bytes()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let req = TestRequest::with_header("Origin", "https://example.org")
|
|
|
|
|
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
|
|
|
|
.method(Method::OPTIONS)
|
2019-04-01 05:43:00 +02:00
|
|
|
|
.to_srv_request();
|
2019-03-11 05:26:54 +01:00
|
|
|
|
|
2019-03-24 05:29:16 +01:00
|
|
|
|
let resp = test::call_success(&mut cors, req);
|
2019-03-11 05:26:54 +01:00
|
|
|
|
assert_eq!(
|
|
|
|
|
&b"https://example.org"[..],
|
|
|
|
|
resp.headers()
|
|
|
|
|
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.as_bytes()
|
|
|
|
|
);
|
|
|
|
|
}
|
2018-01-10 07:33:51 +01:00
|
|
|
|
}
|