Add catch-all OPTIONS route and update examples/tests (#24)
This commit is contained in:
parent
8eb782a7ca
commit
36ec852a78
|
@ -15,13 +15,6 @@ fn responder(cors: Guard) -> Responder<&str> {
|
||||||
cors.responder("Hello CORS!")
|
cors.responder("Hello CORS!")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// You need to define an OPTIONS route for preflight checks.
|
|
||||||
/// These routes can just return the unit type `()`
|
|
||||||
#[options("/")]
|
|
||||||
fn responder_options(cors: Guard) -> Responder<()> {
|
|
||||||
cors.responder(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Using a `Response` instead of a `Responder`. You generally won't have to do this.
|
/// Using a `Response` instead of a `Responder`. You generally won't have to do this.
|
||||||
#[get("/response")]
|
#[get("/response")]
|
||||||
fn response(cors: Guard) -> Response {
|
fn response(cors: Guard) -> Response {
|
||||||
|
@ -30,12 +23,16 @@ fn response(cors: Guard) -> Response {
|
||||||
cors.response(response)
|
cors.response(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// You need to define an OPTIONS route for preflight checks.
|
/// Manually mount an OPTIONS route for your own handling
|
||||||
/// These routes can just return the unit type `()`
|
#[options("/manual")]
|
||||||
#[options("/response")]
|
fn manual_options(cors: Guard) -> Responder<&str> {
|
||||||
fn response_options(cors: Guard) -> Response {
|
cors.responder("Manual OPTIONS preflight handling")
|
||||||
let response = Response::new();
|
}
|
||||||
cors.response(response)
|
|
||||||
|
/// Manually mount an OPTIONS route for your own handling
|
||||||
|
#[get("/manual")]
|
||||||
|
fn manual(cors: Guard) -> Responder<&str> {
|
||||||
|
cors.responder("Manual OPTIONS preflight handling")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -54,8 +51,12 @@ fn main() {
|
||||||
rocket::ignite()
|
rocket::ignite()
|
||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/",
|
||||||
routes![responder, responder_options, response, response_options],
|
routes![responder, response],
|
||||||
)
|
)
|
||||||
|
// Mount the routes to catch all the OPTIONS pre-flight requests
|
||||||
|
.mount("/", rocket_cors::catch_all_options_routes())
|
||||||
|
// You can also manually mount an OPTIONS route that will be used instead
|
||||||
|
.mount("/", routes![manual, manual_options])
|
||||||
.manage(options)
|
.manage(options)
|
||||||
.launch();
|
.launch();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,15 +18,6 @@ fn borrowed<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// You need to define an OPTIONS route for preflight checks.
|
|
||||||
/// These routes can just return the unit type `()`
|
|
||||||
#[options("/")]
|
|
||||||
fn borrowed_options<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
|
||||||
options.inner().respond_borrowed(
|
|
||||||
|guard| guard.responder(()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Using a `Response` instead of a `Responder`. You generally won't have to do this.
|
/// Using a `Response` instead of a `Responder`. You generally won't have to do this.
|
||||||
#[get("/response")]
|
#[get("/response")]
|
||||||
fn response<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
fn response<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
||||||
|
@ -38,15 +29,6 @@ fn response<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// You need to define an OPTIONS route for preflight checks.
|
|
||||||
/// These routes can just return the unit type `()`
|
|
||||||
#[options("/response")]
|
|
||||||
fn response_options<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
|
||||||
options.inner().respond_borrowed(
|
|
||||||
move |guard| guard.response(Response::new()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create and use an ad-hoc Cors
|
/// Create and use an ad-hoc Cors
|
||||||
#[get("/owned")]
|
#[get("/owned")]
|
||||||
fn owned<'r>() -> impl Responder<'r> {
|
fn owned<'r>() -> impl Responder<'r> {
|
||||||
|
@ -54,7 +36,8 @@ fn owned<'r>() -> impl Responder<'r> {
|
||||||
options.respond_owned(|guard| guard.responder("Hello CORS"))
|
options.respond_owned(|guard| guard.responder("Hello CORS"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// You need to define an OPTIONS route for preflight checks.
|
/// You need to define an OPTIONS route for preflight checks if you want to use `Cors` struct
|
||||||
|
/// that is not in Rocket's managed state.
|
||||||
/// These routes can just return the unit type `()`
|
/// These routes can just return the unit type `()`
|
||||||
#[options("/owned")]
|
#[options("/owned")]
|
||||||
fn owned_options<'r>() -> impl Responder<'r> {
|
fn owned_options<'r>() -> impl Responder<'r> {
|
||||||
|
@ -82,13 +65,12 @@ fn main() {
|
||||||
"/",
|
"/",
|
||||||
routes![
|
routes![
|
||||||
borrowed,
|
borrowed,
|
||||||
borrowed_options,
|
|
||||||
response,
|
response,
|
||||||
response_options,
|
|
||||||
owned,
|
owned,
|
||||||
owned_options,
|
owned_options,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
.mount("/", rocket_cors::catch_all_options_routes()) // mount the catch all routes
|
||||||
.manage(cors_options())
|
.manage(cors_options())
|
||||||
.launch();
|
.launch();
|
||||||
}
|
}
|
||||||
|
|
118
src/lib.rs
118
src/lib.rs
|
@ -76,7 +76,7 @@
|
||||||
//! |:---------------------------------------:|:-------:|:-------------:|:------:|
|
//! |:---------------------------------------:|:-------:|:-------------:|:------:|
|
||||||
//! | Must apply to all routes | ✔ | ✗ | ✗ |
|
//! | Must apply to all routes | ✔ | ✗ | ✗ |
|
||||||
//! | Different settings for different routes | ✗ | ✗ | ✔ |
|
//! | Different settings for different routes | ✗ | ✗ | ✔ |
|
||||||
//! | Must define OPTIONS route | ✗ | ✔ | ✔ |
|
//! | May define custom OPTIONS routes | ✗ | ✔ | ✔ |
|
||||||
//!
|
//!
|
||||||
//! ### Fairing
|
//! ### Fairing
|
||||||
//!
|
//!
|
||||||
|
@ -128,16 +128,16 @@
|
||||||
//!
|
//!
|
||||||
//! Using request guard requires you to sacrifice the convenience of Fairings for being able to
|
//! Using request guard requires you to sacrifice the convenience of Fairings for being able to
|
||||||
//! opt some routes out of CORS checks and enforcement. _BUT_ you are still restricted to only
|
//! opt some routes out of CORS checks and enforcement. _BUT_ you are still restricted to only
|
||||||
//! one set of CORS settings and you will now have to define `OPTIONS` routes for all the routes
|
//! one set of CORS settings and you have to mount additional routes to catch and process OPTIONS
|
||||||
//! you want to have CORS checks on. The `OPTIONS` routes are used for CORS preflight checks.
|
//! requests. The `OPTIONS` routes are used for CORS preflight checks.
|
||||||
//!
|
//!
|
||||||
//! You will have to do the following:
|
//! You will have to do the following:
|
||||||
//!
|
//!
|
||||||
//! - Create a [`Cors` struct](struct.Cors.html) and during Rocket's ignite, add the struct to
|
//! - Create a [`Cors` struct](struct.Cors.html) and during Rocket's ignite, add the struct to
|
||||||
//! Rocket's [managed state](https://rocket.rs/guide/state/#managed-state).
|
//! Rocket's [managed state](https://rocket.rs/guide/state/#managed-state).
|
||||||
//! - For all the routes that you want to enforce CORS on, you have to define a `OPTIONS` route
|
//! - For all the routes that you want to enforce CORS on, you can mount either some
|
||||||
//! for the path. You can use [dynamic segments](https://rocket.rs/guide/requests/#dynamic-segments)
|
//! [catch all route](fn.catch_all_options_routes.html) or define your own route for the OPTIONS
|
||||||
//! to reduce the number of routes you have to define.
|
//! verb.
|
||||||
//! - Then in all the routes you want to enforce CORS on, add a
|
//! - Then in all the routes you want to enforce CORS on, add a
|
||||||
//! [Request Guard](https://rocket.rs/guide/requests/#request-guards) for the
|
//! [Request Guard](https://rocket.rs/guide/requests/#request-guards) for the
|
||||||
//! [`Guard`](struct.Guard.html) struct in the route arguments. You should not wrap this in an
|
//! [`Guard`](struct.Guard.html) struct in the route arguments. You should not wrap this in an
|
||||||
|
@ -146,11 +146,8 @@
|
||||||
//! - In your routes, to add CORS headers to your responses, use the appropriate functions on the
|
//! - In your routes, to add CORS headers to your responses, use the appropriate functions on the
|
||||||
//! [`Guard`](struct.Guard.html) for a `Response` or a `Responder`.
|
//! [`Guard`](struct.Guard.html) for a `Response` or a `Responder`.
|
||||||
//!
|
//!
|
||||||
//! You can mix this with the "manual" checks, but whichever `Response` is the last merged will
|
|
||||||
//! overwrite the previous CORS headers.
|
|
||||||
//!
|
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! #![feature(plugin, custom_derive)]
|
//! #![feature(plugin)]
|
||||||
//! #![plugin(rocket_codegen)]
|
//! #![plugin(rocket_codegen)]
|
||||||
//! extern crate rocket;
|
//! extern crate rocket;
|
||||||
//! extern crate rocket_cors;
|
//! extern crate rocket_cors;
|
||||||
|
@ -167,27 +164,24 @@
|
||||||
//! cors.responder("Hello CORS!")
|
//! cors.responder("Hello CORS!")
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! /// You need to define an OPTIONS route for preflight checks.
|
|
||||||
//! /// These routes can just return the unit type `()`
|
|
||||||
//! #[options("/")]
|
|
||||||
//! fn responder_options(cors: Guard) -> Responder<()> {
|
|
||||||
//! cors.responder(())
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! /// Using a `Response` instead of a `Responder`. You generally won't have to do this.
|
//! /// Using a `Response` instead of a `Responder`. You generally won't have to do this.
|
||||||
//! #[get("/responder")]
|
//! #[get("/response")]
|
||||||
//! fn response(cors: Guard) -> Response {
|
//! fn response(cors: Guard) -> Response {
|
||||||
//! let mut response = Response::new();
|
//! let mut response = Response::new();
|
||||||
//! response.set_sized_body(Cursor::new("Hello CORS!"));
|
//! response.set_sized_body(Cursor::new("Hello CORS!"));
|
||||||
//! cors.response(response)
|
//! cors.response(response)
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! /// You need to define an OPTIONS route for preflight checks.
|
//! /// Manually mount an OPTIONS route for your own handling
|
||||||
//! /// These routes can just return the unit type `()`
|
//! #[options("/manual")]
|
||||||
//! #[options("/responder")]
|
//! fn manual_options(cors: Guard) -> Responder<&str> {
|
||||||
//! fn response_options(cors: Guard) -> Response {
|
//! cors.responder("Manual OPTIONS preflight handling")
|
||||||
//! let response = Response::new();
|
//! }
|
||||||
//! cors.response(response)
|
//!
|
||||||
|
//! /// Manually mount an OPTIONS route for your own handling
|
||||||
|
//! #[get("/manual")]
|
||||||
|
//! fn manual(cors: Guard) -> Responder<&str> {
|
||||||
|
//! cors.responder("Manual OPTIONS preflight handling")
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! fn main() {
|
//! fn main() {
|
||||||
|
@ -204,11 +198,17 @@
|
||||||
//! };
|
//! };
|
||||||
//!
|
//!
|
||||||
//! rocket::ignite()
|
//! rocket::ignite()
|
||||||
//! .mount("/", routes![responder, responder_options, response, response_options])
|
//! .mount(
|
||||||
|
//! "/",
|
||||||
|
//! routes![responder, response],
|
||||||
|
//! )
|
||||||
|
//! // Mount the routes to catch all the OPTIONS pre-flight requests
|
||||||
|
//! .mount("/", rocket_cors::catch_all_options_routes())
|
||||||
|
//! // You can also manually mount an OPTIONS route that will be used instead
|
||||||
|
//! .mount("/", routes![manual, manual_options])
|
||||||
//! .manage(options)
|
//! .manage(options)
|
||||||
//! .launch();
|
//! .launch();
|
||||||
//! }
|
//! }
|
||||||
//!
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Truly Manual
|
//! ## Truly Manual
|
||||||
|
@ -338,15 +338,6 @@
|
||||||
//! )
|
//! )
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! /// You need to define an OPTIONS route for preflight checks.
|
|
||||||
//! /// These routes can just return the unit type `()`
|
|
||||||
//! #[options("/")]
|
|
||||||
//! fn borrowed_options<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
|
||||||
//! options.inner().respond_borrowed(
|
|
||||||
//! |guard| guard.responder(()),
|
|
||||||
//! )
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! /// Using a `Response` instead of a `Responder`. You generally won't have to do this.
|
//! /// Using a `Response` instead of a `Responder`. You generally won't have to do this.
|
||||||
//! #[get("/response")]
|
//! #[get("/response")]
|
||||||
//! fn response<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
//! fn response<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
||||||
|
@ -358,15 +349,6 @@
|
||||||
//! )
|
//! )
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! /// You need to define an OPTIONS route for preflight checks.
|
|
||||||
//! /// These routes can just return the unit type `()`
|
|
||||||
//! #[options("/response")]
|
|
||||||
//! fn response_options<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
|
||||||
//! options.inner().respond_borrowed(
|
|
||||||
//! move |guard| guard.response(Response::new()),
|
|
||||||
//! )
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! fn cors_options() -> Cors {
|
//! fn cors_options() -> Cors {
|
||||||
//! let (allowed_origins, failed_origins) = AllowedOrigins::some(&["https://www.acme.com"]);
|
//! let (allowed_origins, failed_origins) = AllowedOrigins::some(&["https://www.acme.com"]);
|
||||||
//! assert!(failed_origins.is_empty());
|
//! assert!(failed_origins.is_empty());
|
||||||
|
@ -387,11 +369,10 @@
|
||||||
//! "/",
|
//! "/",
|
||||||
//! routes![
|
//! routes![
|
||||||
//! borrowed,
|
//! borrowed,
|
||||||
//! borrowed_options,
|
|
||||||
//! response,
|
//! response,
|
||||||
//! response_options,
|
|
||||||
//! ],
|
//! ],
|
||||||
//! )
|
//! )
|
||||||
|
//! .mount("/", rocket_cors::catch_all_options_routes()) // mount the catch all routes
|
||||||
//! .manage(cors_options())
|
//! .manage(cors_options())
|
||||||
//! .launch();
|
//! .launch();
|
||||||
//! }
|
//! }
|
||||||
|
@ -1725,6 +1706,51 @@ fn actual_request_response(options: &Cors, origin: Origin) -> Response {
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns "catch all" OPTIONS routes that you can mount to catch all OPTIONS request. Only works
|
||||||
|
/// if you have put a `Cors` struct into Rocket's managed state.
|
||||||
|
///
|
||||||
|
/// This route has very high rank (and therefore low priority) of
|
||||||
|
/// [max value](https://doc.rust-lang.org/nightly/std/primitive.isize.html#method.max_value)
|
||||||
|
/// so you can define your own to override this route's behaviour.
|
||||||
|
///
|
||||||
|
/// See the documentation at the [crate root](index.html) for usage information.
|
||||||
|
pub fn catch_all_options_routes() -> Vec<rocket::Route> {
|
||||||
|
vec![
|
||||||
|
rocket::Route::ranked(
|
||||||
|
isize::max_value(),
|
||||||
|
http::Method::Options,
|
||||||
|
"/",
|
||||||
|
catch_all_options_route_handler
|
||||||
|
),
|
||||||
|
rocket::Route::ranked(
|
||||||
|
isize::max_value(),
|
||||||
|
http::Method::Options,
|
||||||
|
"/<catch_all_options_route..>",
|
||||||
|
catch_all_options_route_handler
|
||||||
|
),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler for the "catch all options route"
|
||||||
|
fn catch_all_options_route_handler<'r>(
|
||||||
|
request: &'r Request,
|
||||||
|
_: rocket::Data,
|
||||||
|
) -> rocket::handler::Outcome<'r> {
|
||||||
|
|
||||||
|
let guard: Guard = match request.guard() {
|
||||||
|
Outcome::Success(guard) => guard,
|
||||||
|
Outcome::Failure((status, _)) => return rocket::handler::Outcome::failure(status),
|
||||||
|
Outcome::Forward(()) => unreachable!("Should not be reachable"),
|
||||||
|
};
|
||||||
|
|
||||||
|
info_!(
|
||||||
|
"\"Catch all\" handling of CORS `OPTIONS` preflight for request {}",
|
||||||
|
request
|
||||||
|
);
|
||||||
|
|
||||||
|
rocket::handler::Outcome::from(request, guard.responder(()))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
106
tests/guard.rs
106
tests/guard.rs
|
@ -13,11 +13,6 @@ use rocket::http::Method;
|
||||||
use rocket::http::{Header, Status};
|
use rocket::http::{Header, Status};
|
||||||
use rocket::local::Client;
|
use rocket::local::Client;
|
||||||
|
|
||||||
#[options("/")]
|
|
||||||
fn cors_options(cors: cors::Guard) -> cors::Responder<&str> {
|
|
||||||
cors.responder("")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn cors(cors: cors::Guard) -> cors::Responder<&str> {
|
fn cors(cors: cors::Guard) -> cors::Responder<&str> {
|
||||||
cors.responder("Hello CORS")
|
cors.responder("Hello CORS")
|
||||||
|
@ -28,33 +23,39 @@ fn panicking_route(_cors: cors::Guard) {
|
||||||
panic!("This route will panic");
|
panic!("This route will panic");
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following routes tests that the routes can be compiled with ad-hoc CORS Response/Responders
|
/// Manually specify our own OPTIONS route
|
||||||
|
#[options("/manual")]
|
||||||
|
fn cors_manual_options(cors: cors::Guard) -> cors::Responder<&str> {
|
||||||
|
cors.responder("Manual CORS Preflight")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Manually specify our own OPTIONS route
|
||||||
|
#[get("/manual")]
|
||||||
|
fn cors_manual(cors: cors::Guard) -> cors::Responder<&str> {
|
||||||
|
cors.responder("Hello CORS")
|
||||||
|
}
|
||||||
|
|
||||||
/// Using a `Response` instead of a `Responder`
|
/// Using a `Response` instead of a `Responder`
|
||||||
#[allow(unmounted_route)]
|
#[get("/response")]
|
||||||
#[get("/")]
|
|
||||||
fn response(cors: cors::Guard) -> Response {
|
fn response(cors: cors::Guard) -> Response {
|
||||||
cors.response(Response::new())
|
cors.response(Response::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Responder` with String
|
/// `Responder` with String
|
||||||
#[allow(unmounted_route)]
|
#[get("/responder/string")]
|
||||||
#[get("/")]
|
|
||||||
fn responder_string(cors: cors::Guard) -> cors::Responder<String> {
|
fn responder_string(cors: cors::Guard) -> cors::Responder<String> {
|
||||||
cors.responder("Hello CORS".to_string())
|
cors.responder("Hello CORS".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Responder` with 'static ()
|
/// `Responder` with 'static ()
|
||||||
#[allow(unmounted_route)]
|
#[get("/responder/unit")]
|
||||||
#[get("/")]
|
|
||||||
fn responder_unit(cors: cors::Guard) -> cors::Responder<()> {
|
fn responder_unit(cors: cors::Guard) -> cors::Responder<()> {
|
||||||
cors.responder(())
|
cors.responder(())
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SomeState;
|
struct SomeState;
|
||||||
/// Borrow `SomeState` from Rocket
|
/// Borrow `SomeState` from Rocket
|
||||||
#[allow(unmounted_route)]
|
#[get("/state")]
|
||||||
#[get("/")]
|
|
||||||
fn state<'r>(cors: cors::Guard<'r>, _state: State<'r, SomeState>) -> cors::Responder<'r, &'r str> {
|
fn state<'r>(cors: cors::Guard<'r>, _state: State<'r, SomeState>) -> cors::Responder<'r, &'r str> {
|
||||||
cors.responder("hmm")
|
cors.responder("hmm")
|
||||||
}
|
}
|
||||||
|
@ -74,8 +75,12 @@ fn make_cors_options() -> cors::Cors {
|
||||||
|
|
||||||
fn make_rocket() -> rocket::Rocket {
|
fn make_rocket() -> rocket::Rocket {
|
||||||
rocket::ignite()
|
rocket::ignite()
|
||||||
.mount("/", routes![cors, cors_options, panicking_route])
|
.mount("/", routes![cors, panicking_route])
|
||||||
|
.mount("/", routes![response, responder_string, responder_unit, state])
|
||||||
|
.mount("/", cors::catch_all_options_routes()) // mount the catch all routes
|
||||||
|
.mount("/", routes![cors_manual, cors_manual_options]) // manual OPTIOONS routes
|
||||||
.manage(make_cors_options())
|
.manage(make_cors_options())
|
||||||
|
.manage(SomeState)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -122,8 +127,9 @@ fn smoke_test() {
|
||||||
assert_eq!("https://www.acme.com", origin_header);
|
assert_eq!("https://www.acme.com", origin_header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check the "catch all" OPTIONS route works for `/`
|
||||||
#[test]
|
#[test]
|
||||||
fn cors_options_check() {
|
fn cors_options_catch_all_check() {
|
||||||
let rocket = make_rocket();
|
let rocket = make_rocket();
|
||||||
let client = Client::new(rocket).unwrap();
|
let client = Client::new(rocket).unwrap();
|
||||||
|
|
||||||
|
@ -153,6 +159,39 @@ fn cors_options_check() {
|
||||||
assert_eq!("https://www.acme.com", origin_header);
|
assert_eq!("https://www.acme.com", origin_header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Check the "catch all" OPTIONS route works for other routes
|
||||||
|
#[test]
|
||||||
|
fn cors_options_catch_all_check_other_routes() {
|
||||||
|
let rocket = make_rocket();
|
||||||
|
let client = Client::new(rocket).unwrap();
|
||||||
|
|
||||||
|
let origin_header = Header::from(
|
||||||
|
hyper::header::Origin::from_str("https://www.acme.com").unwrap(),
|
||||||
|
);
|
||||||
|
let method_header = Header::from(hyper::header::AccessControlRequestMethod(
|
||||||
|
hyper::method::Method::Get,
|
||||||
|
));
|
||||||
|
let request_headers = hyper::header::AccessControlRequestHeaders(
|
||||||
|
vec![FromStr::from_str("Authorization").unwrap()],
|
||||||
|
);
|
||||||
|
let request_headers = Header::from(request_headers);
|
||||||
|
let req = client
|
||||||
|
.options("/response/unit")
|
||||||
|
.header(origin_header)
|
||||||
|
.header(method_header)
|
||||||
|
.header(request_headers);
|
||||||
|
|
||||||
|
let response = req.dispatch();
|
||||||
|
assert!(response.status().class().is_success());
|
||||||
|
|
||||||
|
let origin_header = response
|
||||||
|
.headers()
|
||||||
|
.get_one("Access-Control-Allow-Origin")
|
||||||
|
.expect("to exist");
|
||||||
|
assert_eq!("https://www.acme.com", origin_header);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cors_get_check() {
|
fn cors_get_check() {
|
||||||
let rocket = make_rocket();
|
let rocket = make_rocket();
|
||||||
|
@ -360,3 +399,38 @@ fn routes_failing_checks_are_not_executed() {
|
||||||
.is_none()
|
.is_none()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This test ensures that manually mounted CORS OPTIONS routes are used even in the presence of
|
||||||
|
/// a "catch all" route.
|
||||||
|
#[test]
|
||||||
|
fn overridden_options_routes_are_used() {
|
||||||
|
let rocket = make_rocket();
|
||||||
|
let client = Client::new(rocket).unwrap();
|
||||||
|
|
||||||
|
let origin_header = Header::from(
|
||||||
|
hyper::header::Origin::from_str("https://www.acme.com").unwrap(),
|
||||||
|
);
|
||||||
|
let method_header = Header::from(hyper::header::AccessControlRequestMethod(
|
||||||
|
hyper::method::Method::Get,
|
||||||
|
));
|
||||||
|
let request_headers = hyper::header::AccessControlRequestHeaders(
|
||||||
|
vec![FromStr::from_str("Authorization").unwrap()],
|
||||||
|
);
|
||||||
|
let request_headers = Header::from(request_headers);
|
||||||
|
let req = client
|
||||||
|
.options("/manual")
|
||||||
|
.header(origin_header)
|
||||||
|
.header(method_header)
|
||||||
|
.header(request_headers);
|
||||||
|
|
||||||
|
let mut response = req.dispatch();
|
||||||
|
let body_str = response.body().and_then(|body| body.into_string());
|
||||||
|
assert!(response.status().class().is_success());
|
||||||
|
assert_eq!(body_str, Some("Manual CORS Preflight".to_string()));
|
||||||
|
|
||||||
|
let origin_header = response
|
||||||
|
.headers()
|
||||||
|
.get_one("Access-Control-Allow-Origin")
|
||||||
|
.expect("to exist");
|
||||||
|
assert_eq!("https://www.acme.com", origin_header);
|
||||||
|
}
|
||||||
|
|
125
tests/manual.rs
125
tests/manual.rs
|
@ -15,14 +15,6 @@ use rocket::local::Client;
|
||||||
use rocket::response::Responder;
|
use rocket::response::Responder;
|
||||||
use rocket_cors::*;
|
use rocket_cors::*;
|
||||||
|
|
||||||
/// Using a borrowed `Cors`
|
|
||||||
#[options("/")]
|
|
||||||
fn cors_options<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
|
||||||
options.inner().respond_borrowed(
|
|
||||||
|guard| guard.responder(()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Using a borrowed `Cors`
|
/// Using a borrowed `Cors`
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn cors<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
fn cors<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
||||||
|
@ -31,13 +23,6 @@ fn cors<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[options("/panic")]
|
|
||||||
fn panicking_route_options<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
|
||||||
options.inner().respond_borrowed(
|
|
||||||
|guard| guard.responder(()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/panic")]
|
#[get("/panic")]
|
||||||
fn panicking_route<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
fn panicking_route<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
||||||
options.inner().respond_borrowed(|_| -> () {
|
options.inner().respond_borrowed(|_| -> () {
|
||||||
|
@ -45,6 +30,22 @@ fn panicking_route<'r>(options: State<'r, Cors>) -> impl Responder<'r> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Respond with an owned option instead
|
||||||
|
#[options("/owned")]
|
||||||
|
fn owned_options<'r>() -> impl Responder<'r> {
|
||||||
|
let borrow = make_different_cors_options();
|
||||||
|
|
||||||
|
borrow.respond_owned(|guard| guard.responder("Manual CORS Preflight"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Respond with an owned option instead
|
||||||
|
#[get("/owned")]
|
||||||
|
fn owned<'r>() -> impl Responder<'r> {
|
||||||
|
let borrow = make_different_cors_options();
|
||||||
|
|
||||||
|
borrow.respond_owned(|guard| guard.responder("Hello CORS Owned"))
|
||||||
|
}
|
||||||
|
|
||||||
// The following routes tests that the routes can be compiled with manual CORS
|
// The following routes tests that the routes can be compiled with manual CORS
|
||||||
|
|
||||||
/// `Responder` with String
|
/// `Responder` with String
|
||||||
|
@ -68,15 +69,6 @@ fn borrow<'r>(options: State<'r, Cors>, test_state: State<'r, TestState>) -> imp
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Respond with an owned option instead
|
|
||||||
#[allow(unmounted_route)]
|
|
||||||
#[get("/")]
|
|
||||||
fn owned<'r>() -> impl Responder<'r> {
|
|
||||||
let borrow = make_cors_options();
|
|
||||||
|
|
||||||
borrow.respond_owned(|guard| guard.responder("Hello CORS"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_cors_options() -> Cors {
|
fn make_cors_options() -> Cors {
|
||||||
let (allowed_origins, failed_origins) = AllowedOrigins::some(&["https://www.acme.com"]);
|
let (allowed_origins, failed_origins) = AllowedOrigins::some(&["https://www.acme.com"]);
|
||||||
assert!(failed_origins.is_empty());
|
assert!(failed_origins.is_empty());
|
||||||
|
@ -90,14 +82,25 @@ fn make_cors_options() -> Cors {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_different_cors_options() -> Cors {
|
||||||
|
let (allowed_origins, failed_origins) = AllowedOrigins::some(&["https://www.example.com"]);
|
||||||
|
assert!(failed_origins.is_empty());
|
||||||
|
|
||||||
|
Cors {
|
||||||
|
allowed_origins: allowed_origins,
|
||||||
|
allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(),
|
||||||
|
allowed_headers: AllowedHeaders::some(&["Authorization", "Accept"]),
|
||||||
|
allow_credentials: true,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn rocket() -> rocket::Rocket {
|
fn rocket() -> rocket::Rocket {
|
||||||
rocket::ignite()
|
rocket::ignite()
|
||||||
.mount(
|
.mount("/", routes![cors, panicking_route])
|
||||||
"/",
|
.mount("/", routes![owned, owned_options])
|
||||||
routes![cors, cors_options, panicking_route, panicking_route_options],
|
.mount("/", catch_all_options_routes()) // mount the catch all routes
|
||||||
)
|
|
||||||
.manage(make_cors_options())
|
.manage(make_cors_options())
|
||||||
.attach(make_cors_options())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -144,7 +147,7 @@ fn smoke_test() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cors_options_check() {
|
fn cors_options_borrowed_check() {
|
||||||
let client = Client::new(rocket()).unwrap();
|
let client = Client::new(rocket()).unwrap();
|
||||||
|
|
||||||
let origin_header = Header::from(
|
let origin_header = Header::from(
|
||||||
|
@ -174,7 +177,7 @@ fn cors_options_check() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cors_get_check() {
|
fn cors_get_borrowed_check() {
|
||||||
let client = Client::new(rocket()).unwrap();
|
let client = Client::new(rocket()).unwrap();
|
||||||
|
|
||||||
let origin_header = Header::from(
|
let origin_header = Header::from(
|
||||||
|
@ -370,3 +373,63 @@ fn routes_failing_checks_are_not_executed() {
|
||||||
.is_none()
|
.is_none()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Manual OPTIONS routes are called
|
||||||
|
#[test]
|
||||||
|
fn cors_options_owned_check() {
|
||||||
|
let rocket = rocket();
|
||||||
|
let client = Client::new(rocket).unwrap();
|
||||||
|
|
||||||
|
let origin_header = Header::from(
|
||||||
|
hyper::header::Origin::from_str("https://www.example.com").unwrap(),
|
||||||
|
);
|
||||||
|
let method_header = Header::from(hyper::header::AccessControlRequestMethod(
|
||||||
|
hyper::method::Method::Get,
|
||||||
|
));
|
||||||
|
let request_headers = hyper::header::AccessControlRequestHeaders(
|
||||||
|
vec![FromStr::from_str("Authorization").unwrap()],
|
||||||
|
);
|
||||||
|
let request_headers = Header::from(request_headers);
|
||||||
|
let req = client
|
||||||
|
.options("/owned")
|
||||||
|
.header(origin_header)
|
||||||
|
.header(method_header)
|
||||||
|
.header(request_headers);
|
||||||
|
|
||||||
|
let mut response = req.dispatch();
|
||||||
|
let body_str = response.body().and_then(|body| body.into_string());
|
||||||
|
assert!(response.status().class().is_success());
|
||||||
|
assert_eq!(body_str, Some("Manual CORS Preflight".to_string()));
|
||||||
|
|
||||||
|
let origin_header = response
|
||||||
|
.headers()
|
||||||
|
.get_one("Access-Control-Allow-Origin")
|
||||||
|
.expect("to exist");
|
||||||
|
assert_eq!("https://www.example.com", origin_header);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Owned manual response works
|
||||||
|
#[test]
|
||||||
|
fn cors_get_owned_check() {
|
||||||
|
let client = Client::new(rocket()).unwrap();
|
||||||
|
|
||||||
|
let origin_header = Header::from(
|
||||||
|
hyper::header::Origin::from_str("https://www.example.com").unwrap(),
|
||||||
|
);
|
||||||
|
let authorization = Header::new("Authorization", "let me in");
|
||||||
|
let req = client.get("/owned").header(origin_header).header(
|
||||||
|
authorization,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut response = req.dispatch();
|
||||||
|
println!("{:?}", response);
|
||||||
|
assert!(response.status().class().is_success());
|
||||||
|
let body_str = response.body().and_then(|body| body.into_string());
|
||||||
|
assert_eq!(body_str, Some("Hello CORS Owned".to_string()));
|
||||||
|
|
||||||
|
let origin_header = response
|
||||||
|
.headers()
|
||||||
|
.get_one("Access-Control-Allow-Origin")
|
||||||
|
.expect("to exist");
|
||||||
|
assert_eq!("https://www.example.com", origin_header);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue