2018-12-18 09:05:17 +00:00
|
|
|
|
/*!
|
|
|
|
|
[![Build Status](https://travis-ci.org/lawliet89/rocket_cors.svg)](https://travis-ci.org/lawliet89/rocket_cors)
|
|
|
|
|
[![Repository](https://img.shields.io/github/tag/lawliet89/rocket_cors.svg)](https://github.com/lawliet89/rocket_cors)
|
|
|
|
|
[![Crates.io](https://img.shields.io/crates/v/rocket_cors.svg)](https://crates.io/crates/rocket_cors)
|
|
|
|
|
|
|
|
|
|
- Documentation: [master branch](https://lawliet89.github.io/rocket_cors) | [stable](https://docs.rs/rocket_cors)
|
|
|
|
|
|
|
|
|
|
Cross-origin resource sharing (CORS) for [Rocket](https://rocket.rs/) applications
|
|
|
|
|
|
|
|
|
|
## Requirements
|
|
|
|
|
|
|
|
|
|
- Nightly Rust
|
|
|
|
|
- Rocket >= 0.4
|
|
|
|
|
|
|
|
|
|
If you are using Rocket 0.3, use the `0.3.0` version of this crate.
|
|
|
|
|
|
|
|
|
|
### Nightly Rust
|
|
|
|
|
|
|
|
|
|
Rocket requires nightly Rust. You should probably install Rust with
|
|
|
|
|
[rustup](https://www.rustup.rs/), then override the code directory to use nightly instead of
|
|
|
|
|
stable. See
|
|
|
|
|
[installation instructions](https://rocket.rs/guide/getting-started/#installing-rust).
|
|
|
|
|
|
|
|
|
|
In particular, `rocket_cors` is currently targetted for the latest `nightly`. Older nightlies
|
|
|
|
|
might work, but they are subject to the minimum that Rocket sets.
|
|
|
|
|
|
|
|
|
|
## Installation
|
|
|
|
|
|
|
|
|
|
Add the following to Cargo.toml:
|
|
|
|
|
|
|
|
|
|
```toml
|
2019-03-12 07:05:40 +00:00
|
|
|
|
rocket_cors = "0.5.0"
|
2018-12-18 09:05:17 +00:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
To use the latest `master` branch, for example:
|
|
|
|
|
|
|
|
|
|
```toml
|
|
|
|
|
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", branch = "master" }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Features
|
|
|
|
|
|
|
|
|
|
By default, a `serialization` feature is enabled in this crate that allows you to (de)serialize
|
2018-12-19 00:29:26 +00:00
|
|
|
|
the [`CorsOptions`] struct that is described below. If you would like to disable this, simply
|
|
|
|
|
change your `Cargo.toml` to:
|
2018-12-18 09:05:17 +00:00
|
|
|
|
|
|
|
|
|
```toml
|
2019-03-12 07:05:40 +00:00
|
|
|
|
rocket_cors = { version = "0.5.0", default-features = false }
|
2018-12-18 09:05:17 +00:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Usage
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
Before you can add CORS responses to your application, you need to create a [`CorsOptions`]
|
|
|
|
|
struct that will hold the settings. Then, you need to create a [`Cors`] struct using
|
|
|
|
|
[`CorsOptions::to_cors`] which will validate and optimise the settings for Rocket to use.
|
2018-12-18 09:05:17 +00:00
|
|
|
|
|
|
|
|
|
Each of the examples can be run off the repository via `cargo run --example xxx` where `xxx` is
|
|
|
|
|
|
|
|
|
|
- `fairing`
|
|
|
|
|
- `guard`
|
|
|
|
|
- `manual`
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
### `CorsOptions` Struct
|
2018-12-18 09:05:17 +00:00
|
|
|
|
|
2019-03-12 07:05:40 +00:00
|
|
|
|
The [`CorsOptions`] struct contains the settings for CORS requests to be validated
|
2018-12-18 09:05:17 +00:00
|
|
|
|
and for responses to be generated. Defaults are defined for every field in the struct, and
|
2019-03-12 07:05:40 +00:00
|
|
|
|
are documented on the [`CorsOptions`] page. You can also deserialize
|
2018-12-18 09:05:17 +00:00
|
|
|
|
the struct from some format like JSON, YAML or TOML when the default `serialization` feature
|
|
|
|
|
is enabled.
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
### `Cors` Struct
|
|
|
|
|
|
|
|
|
|
The [`Cors`] struct is what will be used with Rocket. After creating or deserializing a
|
|
|
|
|
[`CorsOptions`] struct, use [`CorsOptions::to_cors`] to create a [`Cors`] struct.
|
|
|
|
|
|
2018-12-18 09:05:17 +00:00
|
|
|
|
### Three modes of operation
|
|
|
|
|
|
|
|
|
|
You can add CORS to your routes via one of three ways, in descending order of ease and in
|
|
|
|
|
ascending order of flexibility.
|
|
|
|
|
|
|
|
|
|
- Fairing (should only used exclusively)
|
|
|
|
|
- Request Guard
|
|
|
|
|
- Truly Manual
|
|
|
|
|
|
|
|
|
|
Unfortunately, you cannot mix and match Fairing with any other of the methods, due to the
|
|
|
|
|
limitation of Rocket's fairing API. That is, the checks for Fairing will always happen first,
|
|
|
|
|
and if they fail, the route is never executed and so your guard or manual checks will never
|
|
|
|
|
get executed.
|
|
|
|
|
|
|
|
|
|
You can, however, mix and match guards and manual checks.
|
|
|
|
|
|
|
|
|
|
In summary:
|
|
|
|
|
|
|
|
|
|
| | Fairing | Request Guard | Manual |
|
|
|
|
|
|:---------------------------------------:|:-------:|:-------------:|:------:|
|
|
|
|
|
| Must apply to all routes | ✔ | ✗ | ✗ |
|
|
|
|
|
| Different settings for different routes | ✗ | ✗ | ✔ |
|
|
|
|
|
| May define custom OPTIONS routes | ✗ | ✔ | ✔ |
|
|
|
|
|
|
|
|
|
|
### Fairing
|
|
|
|
|
|
|
|
|
|
Fairing is the easiest to use and also the most inflexible. You don't have to define `OPTIONS`
|
|
|
|
|
routes for your application, and the checks are done transparently.
|
|
|
|
|
|
|
|
|
|
However, you can only have one set of settings that must apply to all routes. You cannot opt
|
|
|
|
|
any route out of CORS checks.
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
To use this, simply create a [`Cors`] from [`CorsOptions::to_cors`] and then
|
2018-12-18 09:05:17 +00:00
|
|
|
|
[`attach`](https://api.rocket.rs/rocket/struct.Rocket.html#method.attach) it to Rocket.
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
Refer to the [example](https://github.com/lawliet89/rocket_cors/blob/master/examples/fairing.rs).
|
2018-12-18 09:05:17 +00:00
|
|
|
|
|
|
|
|
|
#### Injected Route
|
|
|
|
|
|
|
|
|
|
The fairing implementation will inject a route during attachment to Rocket. This route is used
|
|
|
|
|
to handle errors during CORS validation.
|
|
|
|
|
|
|
|
|
|
This is due to the limitation in Rocket's Fairing
|
|
|
|
|
[lifecycle](https://rocket.rs/guide/fairings/). Ideally, we want to validate the CORS request
|
|
|
|
|
during `on_request`, and if the validation fails, we want to stop the route from even executing
|
|
|
|
|
to
|
|
|
|
|
|
|
|
|
|
1) prevent side effects
|
|
|
|
|
1) prevent resource usage from unnecessary computation
|
|
|
|
|
|
|
|
|
|
The only way to do this is to hijack the request and route it to our own injected route to
|
|
|
|
|
handle errors. Rocket does not allow Fairings to stop the processing of a route.
|
|
|
|
|
|
|
|
|
|
You can configure the behaviour of the injected route through a couple of fields in the
|
2018-12-19 00:29:26 +00:00
|
|
|
|
[`CorsOptions`].
|
2018-12-18 09:05:17 +00:00
|
|
|
|
|
|
|
|
|
### Request Guard
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
one set of CORS settings and you have to mount additional routes to catch and process OPTIONS
|
|
|
|
|
requests. The `OPTIONS` routes are used for CORS preflight checks.
|
|
|
|
|
|
|
|
|
|
You will have to do the following:
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
- Create a [`Cors`] from [`CorsOptions`] and during Rocket's ignite, add the struct to
|
2018-12-18 09:05:17 +00:00
|
|
|
|
Rocket's [managed state](https://rocket.rs/guide/state/#managed-state).
|
|
|
|
|
- For all the routes that you want to enforce CORS on, you can mount either some
|
|
|
|
|
[catch all route](catch_all_options_routes) or define your own route for the OPTIONS
|
|
|
|
|
verb.
|
|
|
|
|
- Then in all the routes you want to enforce CORS on, add a
|
|
|
|
|
[Request Guard](https://rocket.rs/guide/requests/#request-guards) for the
|
|
|
|
|
[`Guard`](Guard) struct in the route arguments. You should not wrap this in an
|
|
|
|
|
`Option` or `Result` because the guard will let non-CORS requests through and will take over
|
|
|
|
|
error handling in case of errors.
|
|
|
|
|
- In your routes, to add CORS headers to your responses, use the appropriate functions on the
|
|
|
|
|
[`Guard`](Guard) for a `Response` or a `Responder`.
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
Refer to the [example](https://github.com/lawliet89/rocket_cors/blob/master/examples/guard.rs).
|
2018-12-18 09:05:17 +00:00
|
|
|
|
|
|
|
|
|
## Truly Manual
|
|
|
|
|
|
|
|
|
|
This mode is the most difficult to use but offers the most amount of flexibility.
|
|
|
|
|
You might have to understand how the library works internally to know how to use this mode.
|
|
|
|
|
In exchange, you can selectively choose which routes to offer CORS protection to, and you
|
|
|
|
|
can mix and match CORS settings for the routes. You can combine usage of this mode with
|
|
|
|
|
"guard" to offer a mix of ease of use and flexibility.
|
|
|
|
|
|
|
|
|
|
You really do not need to use this unless you have a truly ad-hoc need to respond to CORS
|
|
|
|
|
differently in a route. For example, you have a `ping` endpoint that allows all origins but
|
|
|
|
|
the rest of your routes do not.
|
|
|
|
|
|
|
|
|
|
### Handler
|
|
|
|
|
|
|
|
|
|
This mode requires that you pass in a closure that will be lazily evaluated once a CORS request
|
|
|
|
|
has been validated. If validation fails, the closure will not be run. You should put any code
|
|
|
|
|
that has any side effects or with an appreciable computation cost inside this handler.
|
|
|
|
|
|
|
|
|
|
### Steps to perform:
|
2018-12-19 00:29:26 +00:00
|
|
|
|
- You will first need to have a [`Cors`] struct ready. This struct can be borrowed with a lifetime
|
2018-12-18 09:05:17 +00:00
|
|
|
|
at least as long as `'r` which is the lifetime of a Rocket request. `'static` works too.
|
|
|
|
|
In this case, you might as well use the `Guard` method above and place the `Cors` struct in
|
|
|
|
|
Rocket's [state](https://rocket.rs/guide/state/).
|
2018-12-19 00:29:26 +00:00
|
|
|
|
Alternatively, you can create a [`Cors`] struct directly in the route.
|
2018-12-18 09:05:17 +00:00
|
|
|
|
- Your routes _might_ need to have a `'r` lifetime and return `impl Responder<'r>`. See below.
|
2018-12-19 00:29:26 +00:00
|
|
|
|
- Using the [`Cors`] struct, use either the
|
|
|
|
|
[`Cors::respond_owned`] or
|
|
|
|
|
[`Cors::respond_borrowed`] function and pass in a handler
|
2018-12-18 09:05:17 +00:00
|
|
|
|
that will be executed once CORS validation is successful.
|
2018-12-19 00:29:26 +00:00
|
|
|
|
- Your handler will be passed a [`Guard`] which you will have to use to
|
2018-12-18 09:05:17 +00:00
|
|
|
|
add CORS headers into your own response.
|
|
|
|
|
- You will have to manually define your own `OPTIONS` routes.
|
|
|
|
|
|
|
|
|
|
### Notes about route lifetime
|
|
|
|
|
You might have to specify a `'r` lifetime in your routes and then return `impl Responder<'r>`.
|
|
|
|
|
If you are not sure what to do, you can try to leave the lifetime out and then add it in
|
|
|
|
|
when the compiler complains.
|
|
|
|
|
|
|
|
|
|
Generally, you will need to manually annotate the lifetime for the following cases where
|
|
|
|
|
the compiler is unable to [elide](https://doc.rust-lang.org/beta/nomicon/lifetime-elision.html)
|
|
|
|
|
the lifetime:
|
|
|
|
|
|
|
|
|
|
- Your function arguments do not borrow anything.
|
|
|
|
|
- Your function arguments borrow from more than one lifetime.
|
|
|
|
|
- Your function arguments borrow from a lifetime that is shorter than the `'r` lifetime
|
|
|
|
|
required.
|
|
|
|
|
|
|
|
|
|
You can see examples when the lifetime annotation is required (or not) in `examples/manual.rs`.
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
See the [example](https://github.com/lawliet89/rocket_cors/blob/master/examples/manual.rs).
|
2018-12-18 09:05:17 +00:00
|
|
|
|
|
|
|
|
|
## Mixing Guard and Manual
|
|
|
|
|
|
|
|
|
|
You can mix `Guard` and `Truly Manual` modes together for your application. For example, your
|
|
|
|
|
application might restrict the Origins that can access it, except for one `ping` route that
|
|
|
|
|
allows all access.
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
See the [example](https://github.com/lawliet89/rocket_cors/blob/master/examples/guard.rs).
|
2018-12-18 09:05:17 +00:00
|
|
|
|
|
|
|
|
|
## Reference
|
|
|
|
|
- [Fetch CORS Specification](https://fetch.spec.whatwg.org/#cors-protocol)
|
|
|
|
|
- [Supplanted W3C CORS Specification](https://www.w3.org/TR/cors/)
|
|
|
|
|
- [Resource Advice](https://w3c.github.io/webappsec-cors-for-developers/#resources)
|
|
|
|
|
*/
|
2017-07-13 07:37:15 +00:00
|
|
|
|
|
2018-07-18 05:26:33 +00:00
|
|
|
|
#![deny(
|
|
|
|
|
const_err,
|
|
|
|
|
dead_code,
|
|
|
|
|
deprecated,
|
|
|
|
|
exceeding_bitshifts,
|
|
|
|
|
improper_ctypes,
|
|
|
|
|
missing_docs,
|
|
|
|
|
mutable_transmutes,
|
|
|
|
|
no_mangle_const_items,
|
|
|
|
|
non_camel_case_types,
|
|
|
|
|
non_shorthand_field_patterns,
|
|
|
|
|
non_upper_case_globals,
|
|
|
|
|
overflowing_literals,
|
|
|
|
|
path_statements,
|
|
|
|
|
plugin_as_library,
|
|
|
|
|
stable_features,
|
|
|
|
|
trivial_casts,
|
|
|
|
|
trivial_numeric_casts,
|
|
|
|
|
unconditional_recursion,
|
|
|
|
|
unknown_crate_types,
|
|
|
|
|
unreachable_code,
|
|
|
|
|
unused_allocation,
|
|
|
|
|
unused_assignments,
|
|
|
|
|
unused_attributes,
|
|
|
|
|
unused_comparisons,
|
|
|
|
|
unused_extern_crates,
|
|
|
|
|
unused_features,
|
|
|
|
|
unused_imports,
|
|
|
|
|
unused_import_braces,
|
|
|
|
|
unused_qualifications,
|
|
|
|
|
unused_must_use,
|
|
|
|
|
unused_mut,
|
|
|
|
|
unused_parens,
|
|
|
|
|
unused_results,
|
|
|
|
|
unused_unsafe,
|
|
|
|
|
variant_size_differences,
|
|
|
|
|
warnings,
|
|
|
|
|
while_true
|
|
|
|
|
)]
|
|
|
|
|
#![allow(
|
|
|
|
|
legacy_directory_ownership,
|
|
|
|
|
missing_copy_implementations,
|
|
|
|
|
missing_debug_implementations,
|
|
|
|
|
unknown_lints,
|
|
|
|
|
unsafe_code,
|
|
|
|
|
intra_doc_link_resolution_failure
|
|
|
|
|
)]
|
2017-07-13 07:37:15 +00:00
|
|
|
|
#![doc(test(attr(allow(unused_variables), deny(warnings))))]
|
|
|
|
|
|
2017-07-15 03:18:37 +00:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
#[macro_use]
|
|
|
|
|
mod test_macros;
|
2017-07-17 09:36:41 +00:00
|
|
|
|
mod fairing;
|
2017-07-15 03:18:37 +00:00
|
|
|
|
|
|
|
|
|
pub mod headers;
|
|
|
|
|
|
2017-07-24 05:11:10 +00:00
|
|
|
|
use std::borrow::Cow;
|
2019-03-12 01:58:51 +00:00
|
|
|
|
use std::collections::HashSet;
|
2017-07-13 07:37:15 +00:00
|
|
|
|
use std::error;
|
|
|
|
|
use std::fmt;
|
2017-07-14 17:38:13 +00:00
|
|
|
|
use std::marker::PhantomData;
|
2017-07-13 07:37:15 +00:00
|
|
|
|
use std::ops::Deref;
|
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
2018-11-21 04:17:40 +00:00
|
|
|
|
use ::log::{error, info, log};
|
2019-03-12 07:05:40 +00:00
|
|
|
|
use regex::RegexSet;
|
2017-07-17 08:22:45 +00:00
|
|
|
|
use rocket::http::{self, Status};
|
2018-02-14 05:22:43 +00:00
|
|
|
|
use rocket::request::{FromRequest, Request};
|
2017-07-14 17:38:13 +00:00
|
|
|
|
use rocket::response;
|
2018-10-31 02:59:01 +00:00
|
|
|
|
use rocket::{error_, info_, log_, Outcome, State};
|
|
|
|
|
#[cfg(feature = "serialization")]
|
|
|
|
|
use serde_derive::{Deserialize, Serialize};
|
2017-07-13 07:37:15 +00:00
|
|
|
|
|
2018-10-31 02:30:58 +00:00
|
|
|
|
use crate::headers::{
|
2018-07-18 05:26:33 +00:00
|
|
|
|
AccessControlRequestHeaders, AccessControlRequestMethod, HeaderFieldName, HeaderFieldNamesSet,
|
2019-03-12 01:58:51 +00:00
|
|
|
|
Origin,
|
2018-07-18 05:26:33 +00:00
|
|
|
|
};
|
2017-07-13 07:37:15 +00:00
|
|
|
|
|
2017-07-14 17:38:13 +00:00
|
|
|
|
/// Errors during operations
|
|
|
|
|
///
|
|
|
|
|
/// This enum implements `rocket::response::Responder` which will return an appropriate status code
|
|
|
|
|
/// while printing out the error in the console.
|
|
|
|
|
/// Because these errors are usually the result of an error while trying to respond to a CORS
|
|
|
|
|
/// request, CORS headers cannot be added to the response and your applications requesting CORS
|
|
|
|
|
/// will not be able to see the status code.
|
2017-07-13 07:37:15 +00:00
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub enum Error {
|
|
|
|
|
/// The HTTP request header `Origin` is required but was not provided
|
|
|
|
|
MissingOrigin,
|
|
|
|
|
/// The HTTP request header `Origin` could not be parsed correctly.
|
|
|
|
|
BadOrigin(url::ParseError),
|
2019-03-12 07:05:40 +00:00
|
|
|
|
/// The configured Allowed Origin is opaque and cannot be parsed.
|
|
|
|
|
OpaqueAllowedOrigin(String),
|
2017-07-13 07:37:15 +00:00
|
|
|
|
/// The request header `Access-Control-Request-Method` is required but is missing
|
|
|
|
|
MissingRequestMethod,
|
|
|
|
|
/// The request header `Access-Control-Request-Method` has an invalid value
|
2018-10-31 02:25:10 +00:00
|
|
|
|
BadRequestMethod,
|
2017-07-13 07:37:15 +00:00
|
|
|
|
/// The request header `Access-Control-Request-Headers` is required but is missing.
|
|
|
|
|
MissingRequestHeaders,
|
|
|
|
|
/// Origin is not allowed to make this request
|
2019-03-12 07:05:40 +00:00
|
|
|
|
OriginNotAllowed(String),
|
2017-07-13 07:37:15 +00:00
|
|
|
|
/// Requested method is not allowed
|
2018-12-12 21:53:44 +00:00
|
|
|
|
MethodNotAllowed(String),
|
2019-03-12 07:05:40 +00:00
|
|
|
|
/// A regular expression compilation error
|
|
|
|
|
RegexError(regex::Error),
|
2017-07-13 07:37:15 +00:00
|
|
|
|
/// One or more headers requested are not allowed
|
|
|
|
|
HeadersNotAllowed,
|
2017-07-14 03:03:45 +00:00
|
|
|
|
/// Credentials are allowed, but the Origin is set to "*". This is not allowed by W3C
|
|
|
|
|
///
|
2017-07-15 02:54:26 +00:00
|
|
|
|
/// This is a misconfiguration. Check the docuemntation for `Cors`.
|
2017-07-14 03:03:45 +00:00
|
|
|
|
CredentialsWithWildcardOrigin,
|
2017-07-17 06:28:54 +00:00
|
|
|
|
/// A CORS Request Guard was used, but no CORS Options was available in Rocket's state
|
|
|
|
|
///
|
|
|
|
|
/// This is a misconfiguration. Use `Rocket::manage` to add a CORS options to managed state.
|
|
|
|
|
MissingCorsInRocketState,
|
2017-07-19 01:51:31 +00:00
|
|
|
|
/// The `on_response` handler of Fairing could not find the injected header from the Request.
|
|
|
|
|
/// Either some other fairing has removed it, or this is a bug.
|
|
|
|
|
MissingInjectedHeader,
|
2017-07-17 06:28:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Error {
|
|
|
|
|
fn status(&self) -> Status {
|
|
|
|
|
match *self {
|
2018-02-14 05:22:43 +00:00
|
|
|
|
Error::MissingOrigin
|
2018-12-12 21:53:44 +00:00
|
|
|
|
| Error::OriginNotAllowed(_)
|
|
|
|
|
| Error::MethodNotAllowed(_)
|
2018-02-14 05:22:43 +00:00
|
|
|
|
| Error::HeadersNotAllowed => Status::Forbidden,
|
|
|
|
|
Error::CredentialsWithWildcardOrigin
|
|
|
|
|
| Error::MissingCorsInRocketState
|
2018-11-01 06:44:28 +00:00
|
|
|
|
| Error::MissingInjectedHeader => Status::InternalServerError,
|
2017-07-17 06:28:54 +00:00
|
|
|
|
_ => Status::BadRequest,
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-12 21:53:44 +00:00
|
|
|
|
impl fmt::Display for Error {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
Error::MissingOrigin => write!(f, "The request header `Origin` is required but is missing"),
|
|
|
|
|
Error::BadOrigin(_) => write!(f, "The request header `Origin` contains an invalid URL"),
|
|
|
|
|
Error::MissingRequestMethod => { write!(f,
|
2017-07-13 07:37:15 +00:00
|
|
|
|
"The request header `Access-Control-Request-Method` \
|
2018-12-12 21:53:44 +00:00
|
|
|
|
is required but is missing")
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
2018-12-12 21:53:44 +00:00
|
|
|
|
Error::BadRequestMethod => { write!(f,
|
|
|
|
|
"The request header `Access-Control-Request-Method` has an invalid value")
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
2018-12-12 21:53:44 +00:00
|
|
|
|
Error::MissingRequestHeaders => { write!(f,
|
2017-07-13 07:37:15 +00:00
|
|
|
|
"The request header `Access-Control-Request-Headers` \
|
2018-12-12 21:53:44 +00:00
|
|
|
|
is required but is missing")
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
2019-03-12 07:05:40 +00:00
|
|
|
|
Error::OriginNotAllowed(origin) => write!(f, "Origin '{}' is not allowed to request", origin),
|
2018-12-12 21:53:44 +00:00
|
|
|
|
Error::MethodNotAllowed(method) => write!(f, "Method '{}' is not allowed", &method),
|
|
|
|
|
Error::HeadersNotAllowed => write!(f, "Headers are not allowed"),
|
|
|
|
|
Error::CredentialsWithWildcardOrigin => { write!(f,
|
2017-07-14 03:03:45 +00:00
|
|
|
|
"Credentials are allowed, but the Origin is set to \"*\". \
|
2018-12-12 21:53:44 +00:00
|
|
|
|
This is not allowed by W3C")
|
2017-07-14 03:03:45 +00:00
|
|
|
|
}
|
2018-12-12 21:53:44 +00:00
|
|
|
|
Error::MissingCorsInRocketState => { write!(f,
|
|
|
|
|
"A CORS Request Guard was used, but no CORS Options was available in Rocket's state")
|
2017-07-17 06:28:54 +00:00
|
|
|
|
}
|
2018-12-12 21:53:44 +00:00
|
|
|
|
Error::MissingInjectedHeader => write!(f,
|
2017-07-19 01:51:31 +00:00
|
|
|
|
"The `on_response` handler of Fairing could not find the injected header from the \
|
2019-03-12 07:05:40 +00:00
|
|
|
|
Request. Either some other fairing has removed it, or this is a bug."),
|
|
|
|
|
Error::OpaqueAllowedOrigin(ref origin) => write!(f, "The configured Origin '{}' not have a parsable Origin. Use a regex instead.", origin),
|
|
|
|
|
Error::RegexError(ref e) => write!(f, "{}", e),
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-12 21:53:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl error::Error for Error {
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn cause(&self) -> Option<&dyn error::Error> {
|
2017-07-13 07:37:15 +00:00
|
|
|
|
match *self {
|
|
|
|
|
Error::BadOrigin(ref e) => Some(e),
|
|
|
|
|
_ => Some(self),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-14 17:38:13 +00:00
|
|
|
|
impl<'r> response::Responder<'r> for Error {
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn respond_to(self, _: &Request<'_>) -> Result<response::Response<'r>, Status> {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
error_!("CORS Error: {}", self);
|
|
|
|
|
Err(self.status())
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-12 01:58:51 +00:00
|
|
|
|
impl From<url::ParseError> for Error {
|
|
|
|
|
fn from(error: url::ParseError) -> Self {
|
|
|
|
|
Error::BadOrigin(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-12 07:05:40 +00:00
|
|
|
|
impl From<regex::Error> for Error {
|
|
|
|
|
fn from(error: regex::Error) -> Self {
|
|
|
|
|
Error::RegexError(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-14 03:03:45 +00:00
|
|
|
|
/// An enum signifying that some of type T is allowed, or `All` (everything is allowed).
|
2017-07-15 03:03:24 +00:00
|
|
|
|
///
|
|
|
|
|
/// `Default` is implemented for this enum and is `All`.
|
2017-07-19 04:25:56 +00:00
|
|
|
|
///
|
|
|
|
|
/// This enum is serialized and deserialized
|
|
|
|
|
/// ["Externally tagged"](https://serde.rs/enum-representations.html)
|
2017-09-05 06:11:28 +00:00
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
|
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
|
2017-07-14 03:03:45 +00:00
|
|
|
|
pub enum AllOrSome<T> {
|
|
|
|
|
/// Everything is allowed. Usually equivalent to the "*" value.
|
2017-07-13 07:37:15 +00:00
|
|
|
|
All,
|
2017-07-14 03:03:45 +00:00
|
|
|
|
/// Only some of `T` is allowed
|
|
|
|
|
Some(T),
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-14 03:03:45 +00:00
|
|
|
|
impl<T> Default for AllOrSome<T> {
|
2017-07-13 07:37:15 +00:00
|
|
|
|
fn default() -> Self {
|
2017-07-14 03:03:45 +00:00
|
|
|
|
AllOrSome::All
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 08:22:45 +00:00
|
|
|
|
/// A wrapper type around `rocket::http::Method` to support serialization and deserialization
|
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
|
|
|
|
pub struct Method(http::Method);
|
|
|
|
|
|
|
|
|
|
impl FromStr for Method {
|
2018-10-31 02:25:10 +00:00
|
|
|
|
type Err = ();
|
2017-07-17 08:22:45 +00:00
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
let method = http::Method::from_str(s)?;
|
|
|
|
|
Ok(Method(method))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Deref for Method {
|
|
|
|
|
type Target = http::Method;
|
|
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
&self.0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<http::Method> for Method {
|
|
|
|
|
fn from(method: http::Method) -> Self {
|
|
|
|
|
Method(method)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for Method {
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2017-07-17 08:22:45 +00:00
|
|
|
|
fmt::Display::fmt(&self.0, f)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-05 06:11:28 +00:00
|
|
|
|
#[cfg(feature = "serialization")]
|
|
|
|
|
mod method_serde {
|
|
|
|
|
use std::fmt;
|
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
use serde::{self, Deserialize, Serialize};
|
2017-09-05 06:11:28 +00:00
|
|
|
|
|
2018-10-31 02:30:58 +00:00
|
|
|
|
use crate::Method;
|
2017-09-05 06:11:28 +00:00
|
|
|
|
|
|
|
|
|
impl Serialize for Method {
|
|
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
|
where
|
|
|
|
|
S: serde::Serializer,
|
|
|
|
|
{
|
|
|
|
|
serializer.serialize_str(self.as_str())
|
|
|
|
|
}
|
2017-07-17 08:22:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-05 06:11:28 +00:00
|
|
|
|
impl<'de> Deserialize<'de> for Method {
|
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Method, D::Error>
|
|
|
|
|
where
|
|
|
|
|
D: serde::Deserializer<'de>,
|
|
|
|
|
{
|
|
|
|
|
use serde::de::{self, Visitor};
|
2017-07-17 08:22:45 +00:00
|
|
|
|
|
2017-09-05 06:11:28 +00:00
|
|
|
|
struct MethodVisitor;
|
|
|
|
|
impl<'de> Visitor<'de> for MethodVisitor {
|
|
|
|
|
type Value = Method;
|
2017-07-17 08:22:45 +00:00
|
|
|
|
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2017-09-05 06:11:28 +00:00
|
|
|
|
formatter.write_str("a string containing a HTTP Verb")
|
|
|
|
|
}
|
2017-07-17 08:22:45 +00:00
|
|
|
|
|
2017-09-05 06:11:28 +00:00
|
|
|
|
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
|
|
|
|
where
|
|
|
|
|
E: de::Error,
|
|
|
|
|
{
|
|
|
|
|
match Self::Value::from_str(s) {
|
|
|
|
|
Ok(value) => Ok(value),
|
|
|
|
|
Err(e) => Err(de::Error::custom(format!("{:?}", e))),
|
|
|
|
|
}
|
2017-07-17 08:22:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-05 06:11:28 +00:00
|
|
|
|
deserializer.deserialize_string(MethodVisitor)
|
|
|
|
|
}
|
2017-07-17 08:22:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-19 04:25:56 +00:00
|
|
|
|
/// A list of allowed origins. Either Some origins are allowed, or all origins are allowed.
|
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// use rocket_cors::AllowedOrigins;
|
|
|
|
|
///
|
|
|
|
|
/// let all_origins = AllowedOrigins::all();
|
2019-03-12 07:05:40 +00:00
|
|
|
|
/// let some_origins = AllowedOrigins::some_exact(&["https://www.acme.com"]);
|
|
|
|
|
/// let null_origins = AllowedOrigins::some_null();
|
2017-07-19 04:25:56 +00:00
|
|
|
|
/// ```
|
2019-03-12 07:05:40 +00:00
|
|
|
|
pub type AllowedOrigins = AllOrSome<Origins>;
|
2017-07-19 04:25:56 +00:00
|
|
|
|
|
|
|
|
|
impl AllowedOrigins {
|
|
|
|
|
/// Allows some origins
|
|
|
|
|
///
|
2019-03-12 01:58:51 +00:00
|
|
|
|
/// Validation is not performed at this stage, but at a later stage.
|
2019-03-12 07:05:40 +00:00
|
|
|
|
pub fn some<S1: AsRef<str>, S2: AsRef<str>>(exact: &[S1], regex: &[S2]) -> Self {
|
|
|
|
|
AllOrSome::Some(Origins {
|
|
|
|
|
exact: Some(exact.iter().map(|s| s.as_ref().to_string()).collect()),
|
|
|
|
|
regex: Some(regex.iter().map(|s| s.as_ref().to_string()).collect()),
|
|
|
|
|
..Default::default()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Allows some _exact_ origins
|
|
|
|
|
///
|
|
|
|
|
/// Validation is not performed at this stage, but at a later stage.
|
|
|
|
|
pub fn some_exact<S: AsRef<str>>(exact: &[S]) -> Self {
|
|
|
|
|
AllOrSome::Some(Origins {
|
|
|
|
|
exact: Some(exact.iter().map(|s| s.as_ref().to_string()).collect()),
|
|
|
|
|
..Default::default()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Allow some __regex__ origins
|
|
|
|
|
pub fn some_regex<S: AsRef<str>>(regex: &[S]) -> Self {
|
|
|
|
|
AllOrSome::Some(Origins {
|
|
|
|
|
regex: Some(regex.iter().map(|s| s.as_ref().to_string()).collect()),
|
|
|
|
|
..Default::default()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Allow some `null` origins
|
|
|
|
|
pub fn some_null() -> Self {
|
|
|
|
|
AllOrSome::Some(Origins {
|
|
|
|
|
allow_null: true,
|
|
|
|
|
..Default::default()
|
|
|
|
|
})
|
2017-07-19 04:25:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Allows all origins
|
|
|
|
|
pub fn all() -> Self {
|
|
|
|
|
AllOrSome::All
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-12 07:05:40 +00:00
|
|
|
|
/// Origins that are allowed to make CORS requests.
|
|
|
|
|
///
|
|
|
|
|
/// An origin is defined according to the defined
|
|
|
|
|
/// [syntax](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin).
|
|
|
|
|
///
|
|
|
|
|
/// Origins can be specified as an exact match or using regex.
|
|
|
|
|
///
|
|
|
|
|
/// These Origins are specified as logical `ORs`. That is, if any of the origins match, the entire
|
|
|
|
|
/// request is considered to be valid.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Default)]
|
|
|
|
|
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
|
|
|
|
|
#[cfg_attr(feature = "serialization", serde(default))]
|
|
|
|
|
pub struct Origins {
|
|
|
|
|
/// Whether null origins are accepted
|
|
|
|
|
#[cfg_attr(feature = "serialization", serde(default))]
|
|
|
|
|
pub allow_null: bool,
|
|
|
|
|
/// Origins that must be matched exactly as provided.
|
|
|
|
|
///
|
|
|
|
|
/// These __must__ be valid URL strings that will be parsed and validated when
|
|
|
|
|
/// creating [`Cors`].
|
|
|
|
|
#[cfg_attr(feature = "serialization", serde(default))]
|
|
|
|
|
pub exact: Option<HashSet<String>>,
|
|
|
|
|
/// Origins that will be matched via __any__ regex in this list.
|
|
|
|
|
///
|
|
|
|
|
/// These __must__ be valid Regex that will be parsed and validated when creating [`Cors`].
|
|
|
|
|
///
|
|
|
|
|
/// The regex will be matched according to the
|
|
|
|
|
/// [ASCII serialization](https://html.spec.whatwg.org/multipage/#ascii-serialisation-of-an-origin)
|
|
|
|
|
/// of the incoming Origin.
|
|
|
|
|
///
|
|
|
|
|
/// For more information on the syntax of Regex in Rust, see the
|
|
|
|
|
/// [documentation](https://docs.rs/regex).
|
|
|
|
|
#[cfg_attr(feature = "serialization", serde(default))]
|
|
|
|
|
pub regex: Option<HashSet<String>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parsed set of configured allowed origins
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub(crate) struct ParsedAllowedOrigins {
|
|
|
|
|
pub allow_null: bool,
|
|
|
|
|
pub exact: HashSet<url::Origin>,
|
|
|
|
|
pub regex: Option<RegexSet>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ParsedAllowedOrigins {
|
|
|
|
|
fn parse(origins: &Origins) -> Result<Self, Error> {
|
|
|
|
|
let exact: Result<HashSet<url::Origin>, Error> = match &origins.exact {
|
|
|
|
|
Some(exact) => exact.iter().map(|url| to_origin(url.as_str())).collect(),
|
|
|
|
|
None => Ok(Default::default()),
|
|
|
|
|
};
|
|
|
|
|
let exact = exact?;
|
|
|
|
|
|
|
|
|
|
// Let's check if any of them is Opaque
|
|
|
|
|
exact.iter().try_for_each(|url| {
|
|
|
|
|
if !url.is_tuple() {
|
|
|
|
|
Err(Error::OpaqueAllowedOrigin(url.ascii_serialization()))
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
let regex = match &origins.regex {
|
|
|
|
|
None => None,
|
|
|
|
|
Some(ref regex) => Some(RegexSet::new(regex)?),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
|
allow_null: origins.allow_null,
|
|
|
|
|
exact,
|
|
|
|
|
regex,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn verify(&self, origin: &Origin) -> bool {
|
|
|
|
|
info_!("Verifying origin: {}", origin);
|
|
|
|
|
match origin {
|
|
|
|
|
Origin::Null => {
|
|
|
|
|
info_!("Origin is null. Allowing? {}", self.allow_null);
|
|
|
|
|
self.allow_null
|
|
|
|
|
}
|
|
|
|
|
Origin::Parsed(ref parsed) => {
|
|
|
|
|
// Verify by exact, then regex
|
|
|
|
|
if self.exact.get(parsed).is_some() {
|
|
|
|
|
info_!("Origin has an exact match");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if let Some(regex_set) = &self.regex {
|
|
|
|
|
let regex_match = regex_set.is_match(&parsed.ascii_serialization());
|
|
|
|
|
info_!("Origin has a regex match? {}", regex_match);
|
|
|
|
|
return regex_match;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info!("Origin does not match anything");
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-19 04:25:56 +00:00
|
|
|
|
/// A list of allowed methods
|
|
|
|
|
///
|
|
|
|
|
/// The [list](https://api.rocket.rs/rocket/http/enum.Method.html)
|
|
|
|
|
/// of methods is whatever is supported by Rocket.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// use std::str::FromStr;
|
|
|
|
|
/// use rocket_cors::AllowedMethods;
|
|
|
|
|
///
|
|
|
|
|
/// let allowed_methods: AllowedMethods = ["Get", "Post", "Delete"]
|
|
|
|
|
/// .iter()
|
|
|
|
|
/// .map(|s| FromStr::from_str(s).unwrap())
|
|
|
|
|
/// .collect();
|
|
|
|
|
/// ```
|
|
|
|
|
pub type AllowedMethods = HashSet<Method>;
|
|
|
|
|
|
|
|
|
|
/// A list of allowed headers
|
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// use rocket_cors::AllowedHeaders;
|
|
|
|
|
///
|
|
|
|
|
/// let all_headers = AllowedHeaders::all();
|
|
|
|
|
/// let some_headers = AllowedHeaders::some(&["Authorization", "Accept"]);
|
|
|
|
|
/// ```
|
|
|
|
|
pub type AllowedHeaders = AllOrSome<HashSet<HeaderFieldName>>;
|
|
|
|
|
|
|
|
|
|
impl AllowedHeaders {
|
|
|
|
|
/// Allow some headers
|
|
|
|
|
pub fn some(headers: &[&str]) -> Self {
|
|
|
|
|
AllOrSome::Some(headers.iter().map(|s| s.to_string().into()).collect())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Allows all headers
|
|
|
|
|
pub fn all() -> Self {
|
|
|
|
|
AllOrSome::All
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
/// Configuration options for CORS request handling.
|
2017-07-15 03:03:24 +00:00
|
|
|
|
///
|
2017-07-14 17:38:13 +00:00
|
|
|
|
/// You create a new copy of this struct by defining the configurations in the fields below.
|
2017-09-05 06:11:28 +00:00
|
|
|
|
/// This struct can also be deserialized by serde with the `serialization` feature which is
|
|
|
|
|
/// enabled by default.
|
2017-07-14 03:03:45 +00:00
|
|
|
|
///
|
|
|
|
|
/// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) is implemented for this
|
|
|
|
|
/// struct. The default for each field is described in the docuementation for the field.
|
2017-07-19 04:25:56 +00:00
|
|
|
|
///
|
2018-12-19 00:29:26 +00:00
|
|
|
|
/// Before you can use this with Rocket, you will need to call the [`CorsOptions::to_cors`] method.
|
|
|
|
|
///
|
2017-07-19 04:25:56 +00:00
|
|
|
|
/// # Examples
|
|
|
|
|
///
|
|
|
|
|
/// You can run an example from the repository to demonstrate the JSON serialization with
|
|
|
|
|
/// `cargo run --example json`.
|
|
|
|
|
///
|
|
|
|
|
/// ## Pure default
|
|
|
|
|
/// ```rust
|
2018-12-19 00:29:26 +00:00
|
|
|
|
/// let default = rocket_cors::CorsOptions::default();
|
2017-07-19 04:25:56 +00:00
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// ## JSON Examples
|
|
|
|
|
/// ### Default
|
|
|
|
|
///
|
|
|
|
|
/// ```json
|
|
|
|
|
/// {
|
|
|
|
|
/// "allowed_origins": "All",
|
|
|
|
|
/// "allowed_methods": [
|
|
|
|
|
/// "POST",
|
|
|
|
|
/// "PATCH",
|
|
|
|
|
/// "PUT",
|
|
|
|
|
/// "DELETE",
|
|
|
|
|
/// "HEAD",
|
|
|
|
|
/// "OPTIONS",
|
|
|
|
|
/// "GET"
|
|
|
|
|
/// ],
|
|
|
|
|
/// "allowed_headers": "All",
|
|
|
|
|
/// "allow_credentials": false,
|
|
|
|
|
/// "expose_headers": [],
|
|
|
|
|
/// "max_age": null,
|
|
|
|
|
/// "send_wildcard": false,
|
2018-02-14 06:21:50 +00:00
|
|
|
|
/// "fairing_route_base": "/cors",
|
|
|
|
|
/// "fairing_route_rank": 0
|
2017-07-19 04:25:56 +00:00
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
/// ### Defined
|
|
|
|
|
/// ```json
|
|
|
|
|
/// {
|
|
|
|
|
/// "allowed_origins": {
|
2019-03-12 07:05:40 +00:00
|
|
|
|
/// "Some": {
|
|
|
|
|
/// "exact": ["https://www.acme.com"],
|
|
|
|
|
/// "regex": ["^https://www.example-[A-z0-9]*.com$"]
|
|
|
|
|
/// }
|
2017-07-19 04:25:56 +00:00
|
|
|
|
/// },
|
|
|
|
|
/// "allowed_methods": [
|
|
|
|
|
/// "POST",
|
|
|
|
|
/// "DELETE",
|
|
|
|
|
/// "GET"
|
|
|
|
|
/// ],
|
|
|
|
|
/// "allowed_headers": {
|
|
|
|
|
/// "Some": [
|
|
|
|
|
/// "Accept",
|
|
|
|
|
/// "Authorization"
|
|
|
|
|
/// ]
|
|
|
|
|
/// },
|
|
|
|
|
/// "allow_credentials": true,
|
|
|
|
|
/// "expose_headers": [
|
|
|
|
|
/// "Content-Type",
|
|
|
|
|
/// "X-Custom"
|
|
|
|
|
/// ],
|
|
|
|
|
/// "max_age": 42,
|
|
|
|
|
/// "send_wildcard": false,
|
|
|
|
|
/// "fairing_route_base": "/mycors"
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
2017-09-05 06:11:28 +00:00
|
|
|
|
#[derive(Eq, PartialEq, Clone, Debug)]
|
|
|
|
|
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
|
2018-12-19 00:29:26 +00:00
|
|
|
|
pub struct CorsOptions {
|
2017-07-13 07:37:15 +00:00
|
|
|
|
/// Origins that are allowed to make requests.
|
|
|
|
|
/// Will be verified against the `Origin` request header.
|
2017-07-14 03:03:45 +00:00
|
|
|
|
///
|
|
|
|
|
/// When `All` is set, and `send_wildcard` is set, "*" will be sent in
|
|
|
|
|
/// 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.
|
|
|
|
|
///
|
|
|
|
|
/// When `Some` is set, the client's `Origin` request header will be checked in a
|
|
|
|
|
/// case-sensitive manner.
|
|
|
|
|
///
|
|
|
|
|
/// This is the `list of origins` in the
|
|
|
|
|
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
|
|
|
|
///
|
2017-07-15 03:03:24 +00:00
|
|
|
|
/// Defaults to `All`.
|
2018-07-19 02:03:03 +00:00
|
|
|
|
///
|
2017-09-05 06:11:28 +00:00
|
|
|
|
#[cfg_attr(feature = "serialization", serde(default))]
|
2017-07-19 04:25:56 +00:00
|
|
|
|
pub allowed_origins: AllowedOrigins,
|
2017-07-14 03:03:45 +00:00
|
|
|
|
/// The list of methods which the allowed origins are allowed to access for
|
|
|
|
|
/// non-simple requests.
|
|
|
|
|
///
|
|
|
|
|
/// 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]`
|
2018-07-18 05:26:33 +00:00
|
|
|
|
#[cfg_attr(
|
|
|
|
|
feature = "serialization",
|
2018-12-19 00:29:26 +00:00
|
|
|
|
serde(default = "CorsOptions::default_allowed_methods")
|
2018-07-18 05:26:33 +00:00
|
|
|
|
)]
|
2017-07-19 04:25:56 +00:00
|
|
|
|
pub allowed_methods: AllowedMethods,
|
2017-07-14 03:03:45 +00:00
|
|
|
|
/// The list of header field names which can be used when this resource is accessed by allowed
|
|
|
|
|
/// origins.
|
|
|
|
|
///
|
|
|
|
|
/// 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.
|
|
|
|
|
///
|
|
|
|
|
/// This is the `list of headers` in the
|
|
|
|
|
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
|
|
|
|
///
|
|
|
|
|
/// Defaults to `All`.
|
2017-09-05 06:11:28 +00:00
|
|
|
|
#[cfg_attr(feature = "serialization", serde(default))]
|
2019-03-12 01:58:51 +00:00
|
|
|
|
pub allowed_headers: AllowedHeaders,
|
2017-07-14 03:03:45 +00:00
|
|
|
|
/// Allows users to make authenticated requests.
|
|
|
|
|
/// If true, injects the `Access-Control-Allow-Credentials` header in responses.
|
|
|
|
|
/// This allows cookies and credentials to be submitted across domains.
|
|
|
|
|
///
|
|
|
|
|
/// This **CANNOT** be used in conjunction with `allowed_origins` set to `All` and
|
|
|
|
|
/// `send_wildcard` set to `true`. Depending on the mode of usage, this will either result
|
|
|
|
|
/// in an `Error::CredentialsWithWildcardOrigin` error during Rocket launch or runtime.
|
|
|
|
|
///
|
|
|
|
|
/// Defaults to `false`.
|
2017-09-05 06:11:28 +00:00
|
|
|
|
#[cfg_attr(feature = "serialization", serde(default))]
|
2017-07-13 07:37:15 +00:00
|
|
|
|
pub allow_credentials: bool,
|
2017-07-14 03:03:45 +00:00
|
|
|
|
/// The list of headers which are safe to expose to the API of a CORS API specification.
|
|
|
|
|
/// This corresponds to the `Access-Control-Expose-Headers` responde header.
|
|
|
|
|
///
|
|
|
|
|
/// 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.
|
2017-09-05 06:11:28 +00:00
|
|
|
|
#[cfg_attr(feature = "serialization", serde(default))]
|
2017-07-13 07:37:15 +00:00
|
|
|
|
pub expose_headers: HashSet<String>,
|
2017-07-14 03:03:45 +00:00
|
|
|
|
/// The 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).
|
2017-09-05 06:11:28 +00:00
|
|
|
|
#[cfg_attr(feature = "serialization", serde(default))]
|
2017-07-13 07:37:15 +00:00
|
|
|
|
pub max_age: Option<usize>,
|
2017-07-14 03:03:45 +00:00
|
|
|
|
/// If true, and the `allowed_origins` parameter is `All`, a wildcard
|
|
|
|
|
/// `Access-Control-Allow-Origin` response header is sent, rather than the request’s
|
|
|
|
|
/// `Origin` header.
|
|
|
|
|
///
|
|
|
|
|
/// This is the `supports credentials flag` in the
|
|
|
|
|
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
|
|
|
|
///
|
|
|
|
|
/// 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 Rocket launch or runtime.
|
|
|
|
|
///
|
|
|
|
|
/// Defaults to `false`.
|
2017-09-05 06:11:28 +00:00
|
|
|
|
#[cfg_attr(feature = "serialization", serde(default))]
|
2017-07-14 03:03:45 +00:00
|
|
|
|
pub send_wildcard: bool,
|
2018-02-14 06:21:50 +00:00
|
|
|
|
/// When used as Fairing, Cors will need to redirect failed CORS checks to a custom route
|
|
|
|
|
/// mounted by the fairing. Specify the base of the route so that it doesn't clash with any
|
2017-07-17 06:28:54 +00:00
|
|
|
|
/// of your existing routes.
|
|
|
|
|
///
|
|
|
|
|
/// Defaults to "/cors"
|
2018-07-18 05:26:33 +00:00
|
|
|
|
#[cfg_attr(
|
|
|
|
|
feature = "serialization",
|
2018-12-19 00:29:26 +00:00
|
|
|
|
serde(default = "CorsOptions::default_fairing_route_base")
|
2018-07-18 05:26:33 +00:00
|
|
|
|
)]
|
2017-07-17 06:28:54 +00:00
|
|
|
|
pub fairing_route_base: String,
|
2018-02-14 06:21:50 +00:00
|
|
|
|
/// When used as Fairing, Cors will need to redirect failed CORS checks to a custom route
|
|
|
|
|
/// mounted by the fairing. Specify the rank of the route so that it doesn't clash with any
|
|
|
|
|
/// of your existing routes. Remember that a higher ranked route has lower priority.
|
|
|
|
|
///
|
|
|
|
|
/// Defaults to 0
|
2018-07-18 05:26:33 +00:00
|
|
|
|
#[cfg_attr(
|
|
|
|
|
feature = "serialization",
|
2018-12-19 00:29:26 +00:00
|
|
|
|
serde(default = "CorsOptions::default_fairing_route_rank")
|
2018-07-18 05:26:33 +00:00
|
|
|
|
)]
|
2018-02-14 06:21:50 +00:00
|
|
|
|
pub fairing_route_rank: isize,
|
2017-07-14 03:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
impl Default for CorsOptions {
|
2017-07-14 03:03:45 +00:00
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
allowed_origins: Default::default(),
|
|
|
|
|
allowed_methods: Self::default_allowed_methods(),
|
|
|
|
|
allowed_headers: Default::default(),
|
|
|
|
|
allow_credentials: Default::default(),
|
|
|
|
|
expose_headers: Default::default(),
|
|
|
|
|
max_age: Default::default(),
|
|
|
|
|
send_wildcard: Default::default(),
|
2017-07-17 06:28:54 +00:00
|
|
|
|
fairing_route_base: Self::default_fairing_route_base(),
|
2018-02-14 06:21:50 +00:00
|
|
|
|
fairing_route_rank: Self::default_fairing_route_rank(),
|
2017-07-14 03:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
impl CorsOptions {
|
2017-07-14 03:03:45 +00:00
|
|
|
|
fn default_allowed_methods() -> HashSet<Method> {
|
2017-07-17 08:22:45 +00:00
|
|
|
|
use rocket::http::Method;
|
|
|
|
|
|
2017-07-14 03:03:45 +00:00
|
|
|
|
vec![
|
|
|
|
|
Method::Get,
|
|
|
|
|
Method::Head,
|
|
|
|
|
Method::Post,
|
|
|
|
|
Method::Options,
|
|
|
|
|
Method::Put,
|
|
|
|
|
Method::Patch,
|
|
|
|
|
Method::Delete,
|
2018-10-31 02:30:10 +00:00
|
|
|
|
]
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(From::from)
|
|
|
|
|
.collect()
|
2017-07-14 03:03:45 +00:00
|
|
|
|
}
|
2017-07-13 07:37:15 +00:00
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
fn default_fairing_route_base() -> String {
|
|
|
|
|
"/cors".to_string()
|
2017-07-14 17:38:13 +00:00
|
|
|
|
}
|
2017-07-13 07:37:15 +00:00
|
|
|
|
|
2018-02-14 06:21:50 +00:00
|
|
|
|
fn default_fairing_route_rank() -> isize {
|
|
|
|
|
0
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-12 01:58:51 +00:00
|
|
|
|
/// Validates if any of the settings are disallowed, incorrect, or illegal
|
2017-07-17 06:28:54 +00:00
|
|
|
|
pub fn validate(&self) -> Result<(), Error> {
|
|
|
|
|
if self.allowed_origins.is_all() && self.send_wildcard && self.allow_credentials {
|
|
|
|
|
Err(Error::CredentialsWithWildcardOrigin)?;
|
2017-07-14 17:38:13 +00:00
|
|
|
|
}
|
2017-07-13 07:37:15 +00:00
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
Ok(())
|
2017-07-14 17:38:13 +00:00
|
|
|
|
}
|
2017-07-24 05:11:10 +00:00
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
/// Creates a [`Cors`] struct that can be used to respond to requests or as a Rocket Fairing
|
|
|
|
|
pub fn to_cors(&self) -> Result<Cors, Error> {
|
|
|
|
|
Cors::from_options(self)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Response generator and [Fairing](https://rocket.rs/guide/fairings/) for CORS
|
|
|
|
|
///
|
|
|
|
|
/// This struct can be as Fairing or in an ad-hoc manner to generate CORS response. See the
|
|
|
|
|
/// documentation at the [crate root](index.html) for usage information.
|
|
|
|
|
///
|
|
|
|
|
/// This struct can be created by using [`CorsOptions::to_cors`] or [`Cors::from_options`].
|
2019-03-12 07:05:40 +00:00
|
|
|
|
#[derive(Clone, Debug)]
|
2019-03-12 01:58:51 +00:00
|
|
|
|
pub struct Cors {
|
2019-03-12 07:05:40 +00:00
|
|
|
|
pub(crate) allowed_origins: AllOrSome<ParsedAllowedOrigins>,
|
2019-03-12 01:58:51 +00:00
|
|
|
|
pub(crate) allowed_methods: AllowedMethods,
|
|
|
|
|
pub(crate) allowed_headers: AllOrSome<HashSet<HeaderFieldName>>,
|
|
|
|
|
pub(crate) allow_credentials: bool,
|
|
|
|
|
pub(crate) expose_headers: HashSet<String>,
|
|
|
|
|
pub(crate) max_age: Option<usize>,
|
|
|
|
|
pub(crate) send_wildcard: bool,
|
|
|
|
|
pub(crate) fairing_route_base: String,
|
|
|
|
|
pub(crate) fairing_route_rank: isize,
|
2018-12-19 00:29:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Cors {
|
|
|
|
|
/// Create a `Cors` struct from a [`CorsOptions`]
|
|
|
|
|
pub fn from_options(options: &CorsOptions) -> Result<Self, Error> {
|
|
|
|
|
options.validate()?;
|
2019-03-12 01:58:51 +00:00
|
|
|
|
|
|
|
|
|
let allowed_origins = parse_origins(&options.allowed_origins)?;
|
|
|
|
|
|
|
|
|
|
Ok(Cors {
|
|
|
|
|
allowed_origins,
|
|
|
|
|
allowed_methods: options.allowed_methods.clone(),
|
|
|
|
|
allowed_headers: options.allowed_headers.clone(),
|
|
|
|
|
allow_credentials: options.allow_credentials,
|
|
|
|
|
expose_headers: options.expose_headers.clone(),
|
|
|
|
|
max_age: options.max_age,
|
|
|
|
|
send_wildcard: options.send_wildcard,
|
|
|
|
|
fairing_route_base: options.fairing_route_base.clone(),
|
|
|
|
|
fairing_route_rank: options.fairing_route_rank,
|
|
|
|
|
})
|
2018-12-19 00:29:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-24 05:11:10 +00:00
|
|
|
|
/// Manually respond to a request with CORS checks and headers using an Owned `Cors`.
|
|
|
|
|
///
|
|
|
|
|
/// Use this variant when your `Cors` struct will not live at least as long as the whole `'r`
|
|
|
|
|
/// lifetime of the request.
|
|
|
|
|
///
|
|
|
|
|
/// After the CORS checks are done, the passed in handler closure will be run to generate a
|
|
|
|
|
/// final response. You will have to merge your response with the `Guard` that you have been
|
|
|
|
|
/// passed in to include the CORS headers.
|
|
|
|
|
///
|
|
|
|
|
/// See the documentation at the [crate root](index.html) for usage information.
|
|
|
|
|
pub fn respond_owned<'r, F, R>(self, handler: F) -> Result<ManualResponder<'r, F, R>, Error>
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce(Guard<'r>) -> R + 'r,
|
|
|
|
|
R: response::Responder<'r>,
|
|
|
|
|
{
|
|
|
|
|
Ok(ManualResponder::new(Cow::Owned(self), handler))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Manually respond to a request with CORS checks and headers using a borrowed `Cors`.
|
|
|
|
|
///
|
|
|
|
|
/// Use this variant when your `Cors` struct will live at least as long as the whole `'r`
|
|
|
|
|
/// lifetime of the request. If you are getting your `Cors` from Rocket's state, you will have
|
|
|
|
|
/// to use the [`inner` function](https://api.rocket.rs/rocket/struct.State.html#method.inner)
|
|
|
|
|
/// to get a longer borrowed lifetime.
|
|
|
|
|
///
|
|
|
|
|
/// After the CORS checks are done, the passed in handler closure will be run to generate a
|
|
|
|
|
/// final response. You will have to merge your response with the `Guard` that you have been
|
|
|
|
|
/// passed in to include the CORS headers.
|
|
|
|
|
///
|
|
|
|
|
/// See the documentation at the [crate root](index.html) for usage information.
|
|
|
|
|
pub fn respond_borrowed<'r, F, R>(
|
|
|
|
|
&'r self,
|
|
|
|
|
handler: F,
|
|
|
|
|
) -> Result<ManualResponder<'r, F, R>, Error>
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce(Guard<'r>) -> R + 'r,
|
|
|
|
|
R: response::Responder<'r>,
|
|
|
|
|
{
|
|
|
|
|
Ok(ManualResponder::new(Cow::Borrowed(self), handler))
|
|
|
|
|
}
|
2017-07-14 17:38:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A CORS Response which provides the following CORS headers:
|
2017-07-14 05:29:54 +00:00
|
|
|
|
///
|
|
|
|
|
/// - `Access-Control-Allow-Origin`
|
|
|
|
|
/// - `Access-Control-Expose-Headers`
|
|
|
|
|
/// - `Access-Control-Max-Age`
|
|
|
|
|
/// - `Access-Control-Allow-Credentials`
|
|
|
|
|
/// - `Access-Control-Allow-Methods`
|
|
|
|
|
/// - `Access-Control-Allow-Headers`
|
2018-02-14 07:50:39 +00:00
|
|
|
|
///
|
|
|
|
|
/// The following headers will be merged:
|
2017-07-14 05:29:54 +00:00
|
|
|
|
/// - `Vary`
|
2017-07-18 05:11:30 +00:00
|
|
|
|
///
|
|
|
|
|
/// You can get this struct by using `Cors::validate_request` in an ad-hoc manner.
|
2017-07-17 10:09:07 +00:00
|
|
|
|
#[derive(Eq, PartialEq, Debug)]
|
2017-11-06 02:57:25 +00:00
|
|
|
|
pub(crate) struct Response {
|
2019-03-12 07:05:40 +00:00
|
|
|
|
allow_origin: Option<AllOrSome<String>>,
|
2017-07-13 07:37:15 +00:00
|
|
|
|
allow_methods: HashSet<Method>,
|
|
|
|
|
allow_headers: HeaderFieldNamesSet,
|
|
|
|
|
allow_credentials: bool,
|
|
|
|
|
expose_headers: HeaderFieldNamesSet,
|
|
|
|
|
max_age: Option<usize>,
|
2017-07-14 03:03:45 +00:00
|
|
|
|
vary_origin: bool,
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-14 17:38:13 +00:00
|
|
|
|
impl Response {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
/// Create an empty `Response`
|
2017-07-14 17:38:13 +00:00
|
|
|
|
fn new() -> Self {
|
2017-07-13 07:37:15 +00:00
|
|
|
|
Self {
|
2017-07-14 03:03:45 +00:00
|
|
|
|
allow_origin: None,
|
2017-07-13 07:37:15 +00:00
|
|
|
|
allow_headers: HashSet::new(),
|
|
|
|
|
allow_methods: HashSet::new(),
|
|
|
|
|
allow_credentials: false,
|
|
|
|
|
expose_headers: HashSet::new(),
|
|
|
|
|
max_age: None,
|
2017-07-14 03:03:45 +00:00
|
|
|
|
vary_origin: false,
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-14 03:03:45 +00:00
|
|
|
|
|
|
|
|
|
/// Consumes the `Response` and return an altered response with origin and `vary_origin` set
|
2019-03-12 07:05:40 +00:00
|
|
|
|
fn origin(mut self, origin: &str, vary_origin: bool) -> Self {
|
|
|
|
|
self.allow_origin = Some(AllOrSome::Some(origin.to_string()));
|
2017-07-14 03:03:45 +00:00
|
|
|
|
self.vary_origin = vary_origin;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Consumes the `Response` and return an altered response with origin set to "*"
|
2017-07-14 07:35:44 +00:00
|
|
|
|
fn any(mut self) -> Self {
|
|
|
|
|
self.allow_origin = Some(AllOrSome::All);
|
|
|
|
|
self
|
2017-07-14 03:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
/// Consumes the Response and set credentials
|
|
|
|
|
fn credentials(mut self, value: bool) -> Self {
|
2017-07-13 07:37:15 +00:00
|
|
|
|
self.allow_credentials = value;
|
2017-07-17 06:28:54 +00:00
|
|
|
|
self
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Consumes the CORS, set expose_headers to
|
|
|
|
|
/// passed headers and returns changed CORS
|
2017-07-14 03:03:45 +00:00
|
|
|
|
fn exposed_headers(mut self, headers: &[&str]) -> Self {
|
2018-12-19 00:51:09 +00:00
|
|
|
|
self.expose_headers = headers.iter().map(|s| s.to_string().into()).collect();
|
2017-07-13 07:37:15 +00:00
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Consumes the CORS, set max_age to
|
|
|
|
|
/// passed value and returns changed CORS
|
2017-07-14 03:03:45 +00:00
|
|
|
|
fn max_age(mut self, value: Option<usize>) -> Self {
|
2017-07-13 07:37:15 +00:00
|
|
|
|
self.max_age = value;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Consumes the CORS, set allow_methods to
|
|
|
|
|
/// passed methods and returns changed CORS
|
2017-07-14 07:35:44 +00:00
|
|
|
|
fn methods(mut self, methods: &HashSet<Method>) -> Self {
|
|
|
|
|
self.allow_methods = methods.clone();
|
2017-07-13 07:37:15 +00:00
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Consumes the CORS, set allow_headers to
|
|
|
|
|
/// passed headers and returns changed CORS
|
|
|
|
|
fn headers(mut self, headers: &[&str]) -> Self {
|
2018-12-19 00:51:09 +00:00
|
|
|
|
self.allow_headers = headers.iter().map(|s| s.to_string().into()).collect();
|
2017-07-13 07:37:15 +00:00
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
/// Consumes the `Response` and return a `Responder` that wraps a
|
|
|
|
|
/// provided `rocket:response::Responder` with CORS headers
|
|
|
|
|
pub fn responder<'r, R: response::Responder<'r>>(self, responder: R) -> Responder<'r, R> {
|
|
|
|
|
Responder::new(responder, self)
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
/// Merge a `rocket::Response` with this CORS response. This is usually used in the final step
|
|
|
|
|
/// of a route to return a value for the route.
|
2017-07-14 17:38:13 +00:00
|
|
|
|
///
|
|
|
|
|
/// This will overwrite any existing CORS headers
|
2017-07-17 06:28:54 +00:00
|
|
|
|
pub fn response<'r>(&self, base: response::Response<'r>) -> response::Response<'r> {
|
2017-07-14 17:38:13 +00:00
|
|
|
|
let mut response = response::Response::build_from(base).finalize();
|
2017-07-17 06:28:54 +00:00
|
|
|
|
self.merge(&mut response);
|
|
|
|
|
response
|
|
|
|
|
}
|
2017-07-14 03:03:45 +00:00
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
/// Merge CORS headers with an existing `rocket::Response`.
|
|
|
|
|
///
|
|
|
|
|
/// This will overwrite any existing CORS headers
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn merge(&self, response: &mut response::Response<'_>) {
|
2017-07-14 17:38:13 +00:00
|
|
|
|
// TODO: We should be able to remove this
|
2017-07-14 03:03:45 +00:00
|
|
|
|
let origin = match self.allow_origin {
|
|
|
|
|
None => {
|
|
|
|
|
// This is not a CORS response
|
2017-07-17 06:28:54 +00:00
|
|
|
|
return;
|
2017-07-14 03:03:45 +00:00
|
|
|
|
}
|
2017-07-14 05:29:54 +00:00
|
|
|
|
Some(ref origin) => origin,
|
2017-07-14 03:03:45 +00:00
|
|
|
|
};
|
|
|
|
|
|
2017-07-14 05:29:54 +00:00
|
|
|
|
let origin = match *origin {
|
|
|
|
|
AllOrSome::All => "*".to_string(),
|
2019-03-12 07:05:40 +00:00
|
|
|
|
AllOrSome::Some(ref origin) => origin.to_string(),
|
2017-07-14 03:03:45 +00:00
|
|
|
|
};
|
|
|
|
|
|
2017-09-05 05:19:22 +00:00
|
|
|
|
let _ = response.set_raw_header("Access-Control-Allow-Origin", origin);
|
2017-07-13 07:37:15 +00:00
|
|
|
|
|
|
|
|
|
if self.allow_credentials {
|
2017-09-05 05:19:22 +00:00
|
|
|
|
let _ = response.set_raw_header("Access-Control-Allow-Credentials", "true");
|
2017-07-14 17:38:13 +00:00
|
|
|
|
} else {
|
|
|
|
|
response.remove_header("Access-Control-Allow-Credentials");
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !self.expose_headers.is_empty() {
|
2018-07-18 05:26:33 +00:00
|
|
|
|
let headers: Vec<String> = self
|
|
|
|
|
.expose_headers
|
2017-07-14 05:29:54 +00:00
|
|
|
|
.iter()
|
2017-07-13 07:37:15 +00:00
|
|
|
|
.map(|s| s.deref().to_string())
|
|
|
|
|
.collect();
|
|
|
|
|
let headers = headers.join(", ");
|
|
|
|
|
|
2017-09-05 05:19:22 +00:00
|
|
|
|
let _ = response.set_raw_header("Access-Control-Expose-Headers", headers);
|
2017-07-14 17:38:13 +00:00
|
|
|
|
} else {
|
|
|
|
|
response.remove_header("Access-Control-Expose-Headers");
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !self.allow_headers.is_empty() {
|
2018-07-18 05:26:33 +00:00
|
|
|
|
let headers: Vec<String> = self
|
|
|
|
|
.allow_headers
|
2017-07-14 05:29:54 +00:00
|
|
|
|
.iter()
|
2017-07-13 07:37:15 +00:00
|
|
|
|
.map(|s| s.deref().to_string())
|
|
|
|
|
.collect();
|
|
|
|
|
let headers = headers.join(", ");
|
|
|
|
|
|
2017-09-05 05:19:22 +00:00
|
|
|
|
let _ = response.set_raw_header("Access-Control-Allow-Headers", headers);
|
2017-07-14 17:38:13 +00:00
|
|
|
|
} else {
|
|
|
|
|
response.remove_header("Access-Control-Allow-Headers");
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !self.allow_methods.is_empty() {
|
2017-07-14 05:29:54 +00:00
|
|
|
|
let methods: Vec<_> = self.allow_methods.iter().map(|m| m.as_str()).collect();
|
2017-07-13 07:37:15 +00:00
|
|
|
|
let methods = methods.join(", ");
|
|
|
|
|
|
2017-09-05 05:19:22 +00:00
|
|
|
|
let _ = response.set_raw_header("Access-Control-Allow-Methods", methods);
|
2017-07-14 17:38:13 +00:00
|
|
|
|
} else {
|
|
|
|
|
response.remove_header("Access-Control-Allow-Methods");
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.max_age.is_some() {
|
|
|
|
|
let max_age = self.max_age.unwrap();
|
2017-09-05 05:19:22 +00:00
|
|
|
|
let _ = response.set_raw_header("Access-Control-Max-Age", max_age.to_string());
|
2017-07-14 17:38:13 +00:00
|
|
|
|
} else {
|
|
|
|
|
response.remove_header("Access-Control-Max-Age");
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-14 03:03:45 +00:00
|
|
|
|
if self.vary_origin {
|
2018-02-14 07:50:39 +00:00
|
|
|
|
response.adjoin_raw_header("Vary", "Origin");
|
2017-07-14 05:29:54 +00:00
|
|
|
|
}
|
2017-07-17 06:28:54 +00:00
|
|
|
|
}
|
2017-07-14 05:29:54 +00:00
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
/// Validate and create a new CORS Response from a request and settings
|
2017-07-17 11:19:51 +00:00
|
|
|
|
pub fn validate_and_build<'a, 'r>(
|
2017-07-17 06:28:54 +00:00
|
|
|
|
options: &'a Cors,
|
|
|
|
|
request: &'a Request<'r>,
|
|
|
|
|
) -> Result<Self, Error> {
|
2017-07-17 11:19:51 +00:00
|
|
|
|
validate_and_build(options, request)
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
/// A [request guard](https://rocket.rs/guide/requests/#request-guards) to check CORS headers
|
2017-07-18 10:22:20 +00:00
|
|
|
|
/// before a route is run. Will not execute the route if checks fail.
|
2017-07-17 06:28:54 +00:00
|
|
|
|
///
|
2017-07-18 10:22:20 +00:00
|
|
|
|
/// See the documentation at the [crate root](index.html) for usage information.
|
|
|
|
|
///
|
|
|
|
|
/// You should not wrap this in an
|
|
|
|
|
/// `Option` or `Result` because the guard will let non-CORS requests through and will take over
|
|
|
|
|
/// error handling in case of errors.
|
2017-07-18 05:11:30 +00:00
|
|
|
|
/// In essence, this is just a wrapper around `Response` with a `'r` borrowed lifetime so users
|
|
|
|
|
/// don't have to keep specifying the lifetimes in their routes
|
2017-07-17 06:28:54 +00:00
|
|
|
|
pub struct Guard<'r> {
|
|
|
|
|
response: Response,
|
|
|
|
|
marker: PhantomData<&'r Response>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'r> Guard<'r> {
|
|
|
|
|
fn new(response: Response) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
response,
|
|
|
|
|
marker: PhantomData,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Consumes the Guard and return a `Responder` that wraps a
|
|
|
|
|
/// provided `rocket:response::Responder` with CORS headers
|
|
|
|
|
pub fn responder<R: response::Responder<'r>>(self, responder: R) -> Responder<'r, R> {
|
|
|
|
|
self.response.responder(responder)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Merge a `rocket::Response` with this CORS Guard. This is usually used in the final step
|
|
|
|
|
/// of a route to return a value for the route.
|
|
|
|
|
///
|
|
|
|
|
/// This will overwrite any existing CORS headers
|
|
|
|
|
pub fn response(&self, base: response::Response<'r>) -> response::Response<'r> {
|
|
|
|
|
self.response.response(base)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, 'r> FromRequest<'a, 'r> for Guard<'r> {
|
|
|
|
|
type Error = Error;
|
|
|
|
|
|
|
|
|
|
fn from_request(request: &'a Request<'r>) -> rocket::request::Outcome<Self, Self::Error> {
|
2018-11-21 04:17:40 +00:00
|
|
|
|
let options = match request.guard::<State<'_, Cors>>() {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
Outcome::Success(options) => options,
|
|
|
|
|
_ => {
|
|
|
|
|
let error = Error::MissingCorsInRocketState;
|
|
|
|
|
return Outcome::Failure((error.status(), error));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-07-17 11:19:51 +00:00
|
|
|
|
match Response::validate_and_build(&options, request) {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
Ok(response) => Outcome::Success(Self::new(response)),
|
|
|
|
|
Err(error) => Outcome::Failure((error.status(), error)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A [`Responder`](https://rocket.rs/guide/responses/#responder) which will simply wraps another
|
|
|
|
|
/// `Responder` with CORS headers.
|
|
|
|
|
///
|
|
|
|
|
/// The following CORS headers will be overwritten:
|
|
|
|
|
///
|
|
|
|
|
/// - `Access-Control-Allow-Origin`
|
|
|
|
|
/// - `Access-Control-Expose-Headers`
|
|
|
|
|
/// - `Access-Control-Max-Age`
|
|
|
|
|
/// - `Access-Control-Allow-Credentials`
|
|
|
|
|
/// - `Access-Control-Allow-Methods`
|
|
|
|
|
/// - `Access-Control-Allow-Headers`
|
2018-02-14 07:50:39 +00:00
|
|
|
|
///
|
|
|
|
|
/// The following headers will be merged:
|
2017-07-17 06:28:54 +00:00
|
|
|
|
/// - `Vary`
|
2017-07-18 10:22:20 +00:00
|
|
|
|
///
|
|
|
|
|
/// See the documentation at the [crate root](index.html) for usage information.
|
2017-07-17 06:28:54 +00:00
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct Responder<'r, R> {
|
|
|
|
|
responder: R,
|
|
|
|
|
cors_response: Response,
|
2018-11-21 04:17:40 +00:00
|
|
|
|
marker: PhantomData<dyn response::Responder<'r>>,
|
2017-07-17 06:28:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'r, R: response::Responder<'r>> Responder<'r, R> {
|
|
|
|
|
fn new(responder: R, cors_response: Response) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
responder,
|
|
|
|
|
cors_response,
|
|
|
|
|
marker: PhantomData,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Respond to a request
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn respond(self, request: &Request<'_>) -> response::Result<'r> {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
let mut response = self.responder.respond_to(request)?; // handle status errors?
|
|
|
|
|
self.cors_response.merge(&mut response);
|
|
|
|
|
Ok(response)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'r, R: response::Responder<'r>> response::Responder<'r> for Responder<'r, R> {
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn respond_to(self, request: &Request<'_>) -> response::Result<'r> {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
self.respond(request)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-24 05:11:10 +00:00
|
|
|
|
/// A Manual Responder used in the "truly manual" mode of operation.
|
|
|
|
|
///
|
|
|
|
|
/// See the documentation at the [crate root](index.html) for usage information.
|
|
|
|
|
pub struct ManualResponder<'r, F, R> {
|
|
|
|
|
options: Cow<'r, Cors>,
|
|
|
|
|
handler: F,
|
|
|
|
|
marker: PhantomData<R>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'r, F, R> ManualResponder<'r, F, R>
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce(Guard<'r>) -> R + 'r,
|
|
|
|
|
R: response::Responder<'r>,
|
|
|
|
|
{
|
|
|
|
|
/// Create a new manual responder by passing in either a borrowed or owned `Cors` option.
|
|
|
|
|
///
|
|
|
|
|
/// A borrowed `Cors` option must live for the entirety of the `'r` lifetime which is the
|
|
|
|
|
/// lifetime of the entire Rocket request.
|
|
|
|
|
fn new(options: Cow<'r, Cors>, handler: F) -> Self {
|
|
|
|
|
let marker = PhantomData;
|
|
|
|
|
Self {
|
|
|
|
|
options,
|
|
|
|
|
handler,
|
|
|
|
|
marker,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn build_guard(&self, request: &Request<'_>) -> Result<Guard<'r>, Error> {
|
2017-07-24 05:11:10 +00:00
|
|
|
|
let response = Response::validate_and_build(&self.options, request)?;
|
|
|
|
|
Ok(Guard::new(response))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'r, F, R> response::Responder<'r> for ManualResponder<'r, F, R>
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce(Guard<'r>) -> R + 'r,
|
|
|
|
|
R: response::Responder<'r>,
|
|
|
|
|
{
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn respond_to(self, request: &Request<'_>) -> response::Result<'r> {
|
2017-07-24 05:11:10 +00:00
|
|
|
|
let guard = match self.build_guard(request) {
|
|
|
|
|
Ok(guard) => guard,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
error_!("CORS error: {}", err);
|
|
|
|
|
return Err(err.status());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
(self.handler)(guard).respond_to(request)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 11:19:51 +00:00
|
|
|
|
/// Result of CORS validation.
|
|
|
|
|
///
|
|
|
|
|
/// The variants hold enough information to build a response to the validation result
|
2017-07-18 05:11:30 +00:00
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
2017-07-17 11:19:51 +00:00
|
|
|
|
enum ValidationResult {
|
|
|
|
|
/// Not a CORS request
|
|
|
|
|
None,
|
|
|
|
|
/// Successful preflight request
|
|
|
|
|
Preflight {
|
2019-03-12 07:05:40 +00:00
|
|
|
|
origin: String,
|
2017-07-17 11:19:51 +00:00
|
|
|
|
headers: Option<AccessControlRequestHeaders>,
|
|
|
|
|
},
|
|
|
|
|
/// Successful actual request
|
2019-03-12 07:05:40 +00:00
|
|
|
|
Request { origin: String },
|
2019-03-12 01:58:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-12 07:05:40 +00:00
|
|
|
|
/// Convert a str to a URL Origin
|
2019-03-12 01:58:51 +00:00
|
|
|
|
fn to_origin<S: AsRef<str>>(origin: S) -> Result<url::Origin, Error> {
|
|
|
|
|
Ok(url::Url::parse(origin.as_ref())?.origin())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse and process allowed origins
|
2019-03-12 07:05:40 +00:00
|
|
|
|
fn parse_origins(origins: &AllowedOrigins) -> Result<AllOrSome<ParsedAllowedOrigins>, Error> {
|
2019-03-12 01:58:51 +00:00
|
|
|
|
match origins {
|
|
|
|
|
AllOrSome::All => Ok(AllOrSome::All),
|
2019-03-12 07:05:40 +00:00
|
|
|
|
AllOrSome::Some(origins) => {
|
|
|
|
|
let parsed = ParsedAllowedOrigins::parse(origins)?;
|
|
|
|
|
Ok(AllOrSome::Some(parsed))
|
2019-03-12 01:58:51 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-17 11:19:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
/// Validates a request for CORS and returns a CORS Response
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn validate_and_build(options: &Cors, request: &Request<'_>) -> Result<Response, Error> {
|
2017-07-17 11:19:51 +00:00
|
|
|
|
let result = validate(options, request)?;
|
2017-07-17 06:28:54 +00:00
|
|
|
|
|
2017-07-17 11:19:51 +00:00
|
|
|
|
Ok(match result {
|
|
|
|
|
ValidationResult::None => Response::new(),
|
|
|
|
|
ValidationResult::Preflight { origin, headers } => {
|
2017-07-26 06:37:02 +00:00
|
|
|
|
preflight_response(options, &origin, headers.as_ref())
|
2017-07-17 11:19:51 +00:00
|
|
|
|
}
|
2017-07-26 06:37:02 +00:00
|
|
|
|
ValidationResult::Request { origin } => actual_request_response(options, &origin),
|
2017-07-17 11:19:51 +00:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Validate a CORS request
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn validate(options: &Cors, request: &Request<'_>) -> Result<ValidationResult, Error> {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
// 1. If the Origin header is not present terminate this set of steps.
|
|
|
|
|
// The request is outside the scope of this specification.
|
|
|
|
|
let origin = origin(request)?;
|
|
|
|
|
let origin = match origin {
|
|
|
|
|
None => {
|
|
|
|
|
// Not a CORS request
|
2017-07-17 11:19:51 +00:00
|
|
|
|
return Ok(ValidationResult::None);
|
2017-07-17 06:28:54 +00:00
|
|
|
|
}
|
|
|
|
|
Some(origin) => origin,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Check if the request verb is an OPTION or something else
|
2017-07-17 11:19:51 +00:00
|
|
|
|
match request.method() {
|
2017-07-17 08:22:45 +00:00
|
|
|
|
http::Method::Options => {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
let method = request_method(request)?;
|
|
|
|
|
let headers = request_headers(request)?;
|
2017-07-17 11:19:51 +00:00
|
|
|
|
preflight_validate(options, &origin, &method, &headers)?;
|
2019-03-12 01:58:51 +00:00
|
|
|
|
Ok(ValidationResult::Preflight {
|
2019-03-12 07:05:40 +00:00
|
|
|
|
origin: origin.to_string(),
|
2019-03-12 01:58:51 +00:00
|
|
|
|
headers,
|
|
|
|
|
})
|
2017-07-17 06:28:54 +00:00
|
|
|
|
}
|
2017-07-17 11:19:51 +00:00
|
|
|
|
_ => {
|
|
|
|
|
actual_request_validate(options, &origin)?;
|
2019-03-12 01:58:51 +00:00
|
|
|
|
Ok(ValidationResult::Request {
|
2019-03-12 07:05:40 +00:00
|
|
|
|
origin: origin.to_string(),
|
2019-03-12 01:58:51 +00:00
|
|
|
|
})
|
2017-07-17 11:19:51 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-17 06:28:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Consumes the responder and based on the provided list of allowed origins,
|
|
|
|
|
/// check if the requested origin is allowed.
|
|
|
|
|
/// Useful for pre-flight and during requests
|
|
|
|
|
fn validate_origin(
|
2019-03-12 07:05:40 +00:00
|
|
|
|
origin: &Origin,
|
|
|
|
|
allowed_origins: &AllOrSome<ParsedAllowedOrigins>,
|
2017-07-17 06:28:54 +00:00
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
|
match *allowed_origins {
|
|
|
|
|
// Always matching is acceptable since the list of origins can be unbounded.
|
|
|
|
|
AllOrSome::All => Ok(()),
|
2019-03-12 07:05:40 +00:00
|
|
|
|
AllOrSome::Some(ref allowed_origins) => {
|
|
|
|
|
if allowed_origins.verify(origin) {
|
|
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
Err(Error::OriginNotAllowed(origin.to_string()))
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-17 06:28:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Validate allowed methods
|
|
|
|
|
fn validate_allowed_method(
|
|
|
|
|
method: &AccessControlRequestMethod,
|
2019-03-12 01:58:51 +00:00
|
|
|
|
allowed_methods: &AllowedMethods,
|
2017-07-17 06:28:54 +00:00
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
|
let &AccessControlRequestMethod(ref request_method) = method;
|
|
|
|
|
if !allowed_methods.iter().any(|m| m == request_method) {
|
2018-12-12 21:53:44 +00:00
|
|
|
|
Err(Error::MethodNotAllowed(method.0.to_string()))?
|
2017-07-17 06:28:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Subset to route? Or just the method requested for?
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Validate allowed headers
|
|
|
|
|
fn validate_allowed_headers(
|
|
|
|
|
headers: &AccessControlRequestHeaders,
|
2019-03-12 01:58:51 +00:00
|
|
|
|
allowed_headers: &AllowedHeaders,
|
2017-07-17 06:28:54 +00:00
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
|
let &AccessControlRequestHeaders(ref headers) = headers;
|
|
|
|
|
|
|
|
|
|
match *allowed_headers {
|
|
|
|
|
AllOrSome::All => Ok(()),
|
|
|
|
|
AllOrSome::Some(ref allowed_headers) => {
|
|
|
|
|
if !headers.is_empty() && !headers.is_subset(allowed_headers) {
|
|
|
|
|
Err(Error::HeadersNotAllowed)?
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the `Origin` request header from the request
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn origin(request: &Request<'_>) -> Result<Option<Origin>, Error> {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
match Origin::from_request(request) {
|
|
|
|
|
Outcome::Forward(()) => Ok(None),
|
|
|
|
|
Outcome::Success(origin) => Ok(Some(origin)),
|
|
|
|
|
Outcome::Failure((_, err)) => Err(err),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the `Access-Control-Request-Method` request header from the request
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn request_method(request: &Request<'_>) -> Result<Option<AccessControlRequestMethod>, Error> {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
match AccessControlRequestMethod::from_request(request) {
|
|
|
|
|
Outcome::Forward(()) => Ok(None),
|
|
|
|
|
Outcome::Success(method) => Ok(Some(method)),
|
|
|
|
|
Outcome::Failure((_, err)) => Err(err),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the `Access-Control-Request-Headers` request header from the request
|
2018-11-21 04:17:40 +00:00
|
|
|
|
fn request_headers(request: &Request<'_>) -> Result<Option<AccessControlRequestHeaders>, Error> {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
match AccessControlRequestHeaders::from_request(request) {
|
|
|
|
|
Outcome::Forward(()) => Ok(None),
|
|
|
|
|
Outcome::Success(geaders) => Ok(Some(geaders)),
|
|
|
|
|
Outcome::Failure((_, err)) => Err(err),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 11:19:51 +00:00
|
|
|
|
/// Do pre-flight validation checks
|
2017-07-17 06:28:54 +00:00
|
|
|
|
///
|
|
|
|
|
/// This implementation references the
|
2018-02-14 08:27:50 +00:00
|
|
|
|
/// [W3C recommendation](https://www.w3.org/TR/cors/#resource-preflight-requests)
|
|
|
|
|
/// and [Fetch specification](https://fetch.spec.whatwg.org/#cors-preflight-fetch)
|
2017-07-17 11:19:51 +00:00
|
|
|
|
fn preflight_validate(
|
2017-07-17 06:28:54 +00:00
|
|
|
|
options: &Cors,
|
2019-03-12 07:05:40 +00:00
|
|
|
|
origin: &Origin,
|
2017-07-17 11:19:51 +00:00
|
|
|
|
method: &Option<AccessControlRequestMethod>,
|
|
|
|
|
headers: &Option<AccessControlRequestHeaders>,
|
|
|
|
|
) -> Result<(), Error> {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
// Note: All header parse failures are dealt with in the `FromRequest` trait implementation
|
|
|
|
|
|
|
|
|
|
// 2. If the value of the Origin header is not a case-sensitive match for any of the values
|
|
|
|
|
// in list of origins do not set any additional headers and terminate this set of steps.
|
2017-07-26 06:37:02 +00:00
|
|
|
|
validate_origin(origin, &options.allowed_origins)?;
|
2017-07-17 06:28:54 +00:00
|
|
|
|
|
|
|
|
|
// 3. Let `method` be the value as result of parsing the Access-Control-Request-Method
|
|
|
|
|
// header.
|
|
|
|
|
// If there is no Access-Control-Request-Method header or if parsing failed,
|
|
|
|
|
// do not set any additional headers and terminate this set of steps.
|
|
|
|
|
// The request is outside the scope of this specification.
|
|
|
|
|
|
2017-07-17 11:19:51 +00:00
|
|
|
|
let method = method.as_ref().ok_or_else(|| Error::MissingRequestMethod)?;
|
2017-07-17 06:28:54 +00:00
|
|
|
|
|
|
|
|
|
// 4. Let header field-names be the values as result of parsing the
|
|
|
|
|
// Access-Control-Request-Headers headers.
|
|
|
|
|
// If there are no Access-Control-Request-Headers headers
|
|
|
|
|
// let header field-names be the empty list.
|
|
|
|
|
// If parsing failed do not set any additional headers and terminate this set of steps.
|
|
|
|
|
// The request is outside the scope of this specification.
|
|
|
|
|
|
|
|
|
|
// 5. If method is not a case-sensitive match for any of the values in list of methods
|
|
|
|
|
// do not set any additional headers and terminate this set of steps.
|
|
|
|
|
|
2017-07-17 11:19:51 +00:00
|
|
|
|
validate_allowed_method(method, &options.allowed_methods)?;
|
2017-07-17 06:28:54 +00:00
|
|
|
|
|
|
|
|
|
// 6. If any of the header field-names is not a ASCII case-insensitive match for any of the
|
|
|
|
|
// values in list of headers do not set any additional headers and terminate this set of
|
|
|
|
|
// steps.
|
|
|
|
|
|
2017-07-26 06:37:02 +00:00
|
|
|
|
if let Some(ref headers) = *headers {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
validate_allowed_headers(headers, &options.allowed_headers)?;
|
2017-07-17 11:19:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Build a response for pre-flight checks
|
|
|
|
|
///
|
|
|
|
|
/// This implementation references the
|
2018-02-14 08:27:50 +00:00
|
|
|
|
/// [W3C recommendation](https://www.w3.org/TR/cors/#resource-preflight-requests)
|
|
|
|
|
/// and [Fetch specification](https://fetch.spec.whatwg.org/#cors-preflight-fetch).
|
2017-07-17 11:19:51 +00:00
|
|
|
|
fn preflight_response(
|
|
|
|
|
options: &Cors,
|
2019-03-12 07:05:40 +00:00
|
|
|
|
origin: &str,
|
2017-07-26 06:37:02 +00:00
|
|
|
|
headers: Option<&AccessControlRequestHeaders>,
|
2017-07-17 11:19:51 +00:00
|
|
|
|
) -> Response {
|
|
|
|
|
let response = Response::new();
|
2017-07-17 06:28:54 +00:00
|
|
|
|
|
|
|
|
|
// 7. If the resource supports credentials add a single Access-Control-Allow-Origin header,
|
|
|
|
|
// with the value of the Origin header as value, and add a
|
|
|
|
|
// single Access-Control-Allow-Credentials header with the case-sensitive string "true" as
|
|
|
|
|
// value.
|
|
|
|
|
// Otherwise, add a single Access-Control-Allow-Origin header,
|
|
|
|
|
// with either the value of the Origin header or the string "*" as value.
|
|
|
|
|
// Note: The string "*" cannot be used for a resource that supports credentials.
|
|
|
|
|
|
|
|
|
|
// Validation has been done in options.validate
|
2017-07-17 11:19:51 +00:00
|
|
|
|
let response = match options.allowed_origins {
|
|
|
|
|
AllOrSome::All => {
|
|
|
|
|
if options.send_wildcard {
|
|
|
|
|
response.any()
|
|
|
|
|
} else {
|
2017-07-26 06:37:02 +00:00
|
|
|
|
response.origin(origin, true)
|
2017-07-17 11:19:51 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-26 06:37:02 +00:00
|
|
|
|
AllOrSome::Some(_) => response.origin(origin, false),
|
2017-07-17 11:19:51 +00:00
|
|
|
|
};
|
2017-07-17 06:28:54 +00:00
|
|
|
|
let response = response.credentials(options.allow_credentials);
|
|
|
|
|
|
|
|
|
|
// 8. Optionally add a single Access-Control-Max-Age header
|
|
|
|
|
// with as value the amount of seconds the user agent is allowed to cache the result of the
|
|
|
|
|
// request.
|
|
|
|
|
let response = response.max_age(options.max_age);
|
|
|
|
|
|
|
|
|
|
// 9. If method is a simple method this step may be skipped.
|
|
|
|
|
// Add one or more Access-Control-Allow-Methods headers consisting of
|
|
|
|
|
// (a subset of) the list of methods.
|
|
|
|
|
// If a method is a simple method it does not need to be listed, but this is not prohibited.
|
|
|
|
|
// Since the list of methods can be unbounded,
|
|
|
|
|
// simply returning the method indicated by Access-Control-Request-Method
|
|
|
|
|
// (if supported) can be enough.
|
|
|
|
|
|
2017-07-17 11:19:51 +00:00
|
|
|
|
let response = response.methods(&options.allowed_methods);
|
2017-07-17 06:28:54 +00:00
|
|
|
|
|
|
|
|
|
// 10. If each of the header field-names is a simple header and none is Content-Type,
|
|
|
|
|
// this step may be skipped.
|
|
|
|
|
// Add one or more Access-Control-Allow-Headers headers consisting of (a subset of)
|
|
|
|
|
// the list of headers.
|
|
|
|
|
// If a header field name is a simple header and is not Content-Type,
|
|
|
|
|
// it is not required to be listed. Content-Type is to be listed as only a
|
|
|
|
|
// subset of its values makes it qualify as simple header.
|
|
|
|
|
// Since the list of headers can be unbounded, simply returning supported headers
|
|
|
|
|
// from Access-Control-Allow-Headers can be enough.
|
|
|
|
|
|
2017-07-17 11:19:51 +00:00
|
|
|
|
// We do not do anything special with simple headers
|
2017-07-26 06:37:02 +00:00
|
|
|
|
if let Some(headers) = headers {
|
2017-07-17 11:19:51 +00:00
|
|
|
|
let &AccessControlRequestHeaders(ref headers) = headers;
|
|
|
|
|
response.headers(
|
|
|
|
|
headers
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|s| &**s.deref())
|
|
|
|
|
.collect::<Vec<&str>>()
|
|
|
|
|
.as_slice(),
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
response
|
2017-07-26 06:37:02 +00:00
|
|
|
|
}
|
2017-07-17 06:28:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 11:19:51 +00:00
|
|
|
|
/// Do checks for an actual request
|
|
|
|
|
///
|
|
|
|
|
/// This implementation references the
|
2018-02-14 08:27:50 +00:00
|
|
|
|
/// [W3C recommendation](https://www.w3.org/TR/cors/#resource-requests)
|
|
|
|
|
/// and [Fetch specification](https://fetch.spec.whatwg.org/#cors-preflight-fetch).
|
2019-03-12 07:05:40 +00:00
|
|
|
|
fn actual_request_validate(options: &Cors, origin: &Origin) -> Result<(), Error> {
|
2017-07-17 06:28:54 +00:00
|
|
|
|
// Note: All header parse failures are dealt with in the `FromRequest` trait implementation
|
|
|
|
|
|
|
|
|
|
// 2. If the value of the Origin header is not a case-sensitive match for any of the values
|
|
|
|
|
// in list of origins, do not set any additional headers and terminate this set of steps.
|
|
|
|
|
// Always matching is acceptable since the list of origins can be unbounded.
|
|
|
|
|
|
2017-07-26 06:37:02 +00:00
|
|
|
|
validate_origin(origin, &options.allowed_origins)?;
|
2017-07-17 11:19:51 +00:00
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Build the response for an actual request
|
|
|
|
|
///
|
|
|
|
|
/// This implementation references the
|
2018-02-14 08:27:50 +00:00
|
|
|
|
/// [W3C recommendation](https://www.w3.org/TR/cors/#resource-requests)
|
|
|
|
|
/// and [Fetch specification](https://fetch.spec.whatwg.org/#cors-preflight-fetch)
|
2019-03-12 07:05:40 +00:00
|
|
|
|
fn actual_request_response(options: &Cors, origin: &str) -> Response {
|
2017-07-17 11:19:51 +00:00
|
|
|
|
let response = Response::new();
|
|
|
|
|
|
|
|
|
|
// 3. If the resource supports credentials add a single Access-Control-Allow-Origin header,
|
|
|
|
|
// with the value of the Origin header as value, and add a
|
|
|
|
|
// single Access-Control-Allow-Credentials header with the case-sensitive string "true" as
|
|
|
|
|
// value.
|
|
|
|
|
// Otherwise, add a single Access-Control-Allow-Origin header,
|
|
|
|
|
// with either the value of the Origin header or the string "*" as value.
|
|
|
|
|
// Note: The string "*" cannot be used for a resource that supports credentials.
|
|
|
|
|
|
|
|
|
|
// Validation has been done in options.validate
|
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
let response = match options.allowed_origins {
|
|
|
|
|
AllOrSome::All => {
|
|
|
|
|
if options.send_wildcard {
|
|
|
|
|
response.any()
|
|
|
|
|
} else {
|
2017-07-26 06:37:02 +00:00
|
|
|
|
response.origin(origin, true)
|
2017-07-17 06:28:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-26 06:37:02 +00:00
|
|
|
|
AllOrSome::Some(_) => response.origin(origin, false),
|
2017-07-17 06:28:54 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let response = response.credentials(options.allow_credentials);
|
|
|
|
|
|
|
|
|
|
// 4. If the list of exposed headers is not empty add one or more
|
|
|
|
|
// Access-Control-Expose-Headers headers, with as values the header field names given in
|
|
|
|
|
// the list of exposed headers.
|
|
|
|
|
// By not adding the appropriate headers resource can also clear the preflight result cache
|
|
|
|
|
// of all entries where origin is a case-sensitive match for the value of the Origin header
|
|
|
|
|
// and url is a case-sensitive match for the URL of the resource.
|
|
|
|
|
|
2017-07-26 06:37:02 +00:00
|
|
|
|
response.exposed_headers(
|
2017-07-17 06:28:54 +00:00
|
|
|
|
options
|
|
|
|
|
.expose_headers
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|s| &**s)
|
|
|
|
|
.collect::<Vec<&str>>()
|
|
|
|
|
.as_slice(),
|
2017-07-26 06:37:02 +00:00
|
|
|
|
)
|
2017-07-17 06:28:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-24 07:45:53 +00:00
|
|
|
|
/// 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,
|
|
|
|
|
"/",
|
2018-02-14 05:22:43 +00:00
|
|
|
|
catch_all_options_route_handler,
|
2017-07-24 07:45:53 +00:00
|
|
|
|
),
|
|
|
|
|
rocket::Route::ranked(
|
|
|
|
|
isize::max_value(),
|
|
|
|
|
http::Method::Options,
|
|
|
|
|
"/<catch_all_options_route..>",
|
2018-02-14 05:22:43 +00:00
|
|
|
|
catch_all_options_route_handler,
|
2017-07-24 07:45:53 +00:00
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Handler for the "catch all options route"
|
|
|
|
|
fn catch_all_options_route_handler<'r>(
|
2018-11-21 04:17:40 +00:00
|
|
|
|
request: &'r Request<'_>,
|
2017-07-24 07:45:53 +00:00
|
|
|
|
_: rocket::Data,
|
|
|
|
|
) -> rocket::handler::Outcome<'r> {
|
2018-11-21 04:17:40 +00:00
|
|
|
|
let guard: Guard<'_> = match request.guard() {
|
2017-07-24 07:45:53 +00:00
|
|
|
|
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(()))
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-13 07:37:15 +00:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use std::str::FromStr;
|
2017-07-18 05:11:30 +00:00
|
|
|
|
|
|
|
|
|
use rocket::http::Header;
|
2018-07-18 05:26:33 +00:00
|
|
|
|
use rocket::local::Client;
|
2017-09-05 06:11:28 +00:00
|
|
|
|
#[cfg(feature = "serialization")]
|
2017-07-17 08:22:45 +00:00
|
|
|
|
use serde_json;
|
2017-07-18 05:11:30 +00:00
|
|
|
|
|
2017-07-13 07:37:15 +00:00
|
|
|
|
use super::*;
|
2018-10-31 02:30:58 +00:00
|
|
|
|
use crate::http::Method;
|
2017-07-13 07:37:15 +00:00
|
|
|
|
|
2019-03-12 07:05:40 +00:00
|
|
|
|
fn to_parsed_origin<S: AsRef<str>>(origin: S) -> Result<Origin, Error> {
|
|
|
|
|
Origin::from_str(origin.as_ref())
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
fn make_cors_options() -> CorsOptions {
|
2019-03-12 07:05:40 +00:00
|
|
|
|
let allowed_origins = AllowedOrigins::some_exact(&["https://www.acme.com"]);
|
2017-07-14 07:35:44 +00:00
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
CorsOptions {
|
2018-12-19 00:51:09 +00:00
|
|
|
|
allowed_origins,
|
2017-07-17 08:22:45 +00:00
|
|
|
|
allowed_methods: vec![http::Method::Get]
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(From::from)
|
|
|
|
|
.collect(),
|
2017-07-19 04:25:56 +00:00
|
|
|
|
allowed_headers: AllowedHeaders::some(&[&"Authorization", "Accept"]),
|
2017-07-17 06:28:54 +00:00
|
|
|
|
allow_credentials: true,
|
2017-07-18 05:11:30 +00:00
|
|
|
|
expose_headers: ["Content-Type", "X-Custom"]
|
2019-03-12 07:05:40 +00:00
|
|
|
|
.iter()
|
|
|
|
|
.map(|s| s.to_string())
|
2017-07-18 05:11:30 +00:00
|
|
|
|
.collect(),
|
2017-07-17 06:28:54 +00:00
|
|
|
|
..Default::default()
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-14 07:35:44 +00:00
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
fn make_invalid_options() -> CorsOptions {
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let mut cors = make_cors_options();
|
|
|
|
|
cors.allow_credentials = true;
|
|
|
|
|
cors.allowed_origins = AllOrSome::All;
|
|
|
|
|
cors.send_wildcard = true;
|
|
|
|
|
cors
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Make a client with no routes for unit testing
|
|
|
|
|
fn make_client() -> Client {
|
|
|
|
|
let rocket = rocket::ignite();
|
|
|
|
|
Client::new(rocket).expect("valid rocket instance")
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-12 01:58:51 +00:00
|
|
|
|
// `to_origin` tests
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn origin_is_parsed_properly() {
|
|
|
|
|
let url = "https://foo.bar.xyz";
|
|
|
|
|
let parsed = not_err!(Origin::from_str(url));
|
|
|
|
|
assert_eq!(parsed.ascii_serialization(), url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn origin_parsing_strips_paths() {
|
|
|
|
|
// this should never really be sent by a compliant user agent
|
|
|
|
|
let url = "https://foo.bar.xyz/path/somewhere";
|
|
|
|
|
let parsed = not_err!(Origin::from_str(url));
|
|
|
|
|
let expected = "https://foo.bar.xyz";
|
|
|
|
|
assert_eq!(parsed.ascii_serialization(), expected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "BadOrigin")]
|
|
|
|
|
fn origin_parsing_disallows_invalid_origins() {
|
|
|
|
|
let url = "invalid_url";
|
|
|
|
|
let _ = Origin::from_str(url).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn origin_parses_opaque_origins() {
|
|
|
|
|
let url = "blob://foobar";
|
|
|
|
|
let parsed = not_err!(Origin::from_str(url));
|
|
|
|
|
|
|
|
|
|
assert!(!parsed.is_tuple());
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 08:22:45 +00:00
|
|
|
|
// CORS options test
|
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn cors_is_validated() {
|
|
|
|
|
assert!(make_cors_options().validate().is_ok())
|
|
|
|
|
}
|
2017-07-14 07:35:44 +00:00
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "CredentialsWithWildcardOrigin")]
|
|
|
|
|
fn cors_validates_illegal_allow_credentials() {
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let cors = make_invalid_options();
|
2017-07-14 07:35:44 +00:00
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
cors.validate().unwrap();
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 08:22:45 +00:00
|
|
|
|
/// Check that the the default deserialization matches the one returned by `Default::default`
|
2017-09-05 06:11:28 +00:00
|
|
|
|
#[cfg(feature = "serialization")]
|
2017-07-17 08:22:45 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn cors_default_deserialization_is_correct() {
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let deserialized: CorsOptions = serde_json::from_str("{}").expect("To not fail");
|
|
|
|
|
assert_eq!(deserialized, CorsOptions::default());
|
2019-03-12 07:05:40 +00:00
|
|
|
|
|
|
|
|
|
let expected_json = r#"
|
|
|
|
|
{
|
|
|
|
|
"allowed_origins": "All",
|
|
|
|
|
"allowed_methods": [
|
|
|
|
|
"POST",
|
|
|
|
|
"PATCH",
|
|
|
|
|
"PUT",
|
|
|
|
|
"DELETE",
|
|
|
|
|
"HEAD",
|
|
|
|
|
"OPTIONS",
|
|
|
|
|
"GET"
|
|
|
|
|
],
|
|
|
|
|
"allowed_headers": "All",
|
|
|
|
|
"allow_credentials": false,
|
|
|
|
|
"expose_headers": [],
|
|
|
|
|
"max_age": null,
|
|
|
|
|
"send_wildcard": false,
|
|
|
|
|
"fairing_route_base": "/cors",
|
|
|
|
|
"fairing_route_rank": 0
|
|
|
|
|
}
|
|
|
|
|
"#;
|
|
|
|
|
let actual: CorsOptions = serde_json::from_str(expected_json).expect("to not fail");
|
|
|
|
|
assert_eq!(actual, CorsOptions::default());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Checks that the example provided can actually be deserialized
|
|
|
|
|
#[cfg(feature = "serialization")]
|
|
|
|
|
#[test]
|
|
|
|
|
fn cors_options_example_can_be_deserialized() {
|
|
|
|
|
let json = r#"{
|
|
|
|
|
"allowed_origins": {
|
|
|
|
|
"Some": {
|
|
|
|
|
"exact": ["https://www.acme.com"],
|
|
|
|
|
"regex": ["^https://www.example-[A-z0-9]*.com$"]
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"allowed_methods": [
|
|
|
|
|
"POST",
|
|
|
|
|
"DELETE",
|
|
|
|
|
"GET"
|
|
|
|
|
],
|
|
|
|
|
"allowed_headers": {
|
|
|
|
|
"Some": [
|
|
|
|
|
"Accept",
|
|
|
|
|
"Authorization"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"allow_credentials": true,
|
|
|
|
|
"expose_headers": [
|
|
|
|
|
"Content-Type",
|
|
|
|
|
"X-Custom"
|
|
|
|
|
],
|
|
|
|
|
"max_age": 42,
|
|
|
|
|
"send_wildcard": false,
|
|
|
|
|
"fairing_route_base": "/mycors"
|
|
|
|
|
}"#;
|
|
|
|
|
let _: CorsOptions = serde_json::from_str(json).expect("to not fail");
|
2017-07-17 08:22:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
// The following tests check validation
|
|
|
|
|
|
2017-07-13 07:37:15 +00:00
|
|
|
|
#[test]
|
2017-07-17 06:28:54 +00:00
|
|
|
|
fn validate_origin_allows_all_origins() {
|
2017-07-14 07:35:44 +00:00
|
|
|
|
let url = "https://www.example.com";
|
2019-03-12 07:05:40 +00:00
|
|
|
|
let origin = not_err!(to_parsed_origin(&url));
|
2017-07-14 07:35:44 +00:00
|
|
|
|
let allowed_origins = AllOrSome::All;
|
2017-07-13 07:37:15 +00:00
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
not_err!(validate_origin(&origin, &allowed_origins));
|
2017-07-14 07:35:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2018-02-14 08:27:50 +00:00
|
|
|
|
fn validate_origin_allows_origin() {
|
2017-07-14 07:35:44 +00:00
|
|
|
|
let url = "https://www.example.com";
|
2019-03-12 07:05:40 +00:00
|
|
|
|
let origin = not_err!(to_parsed_origin(&url));
|
|
|
|
|
let allowed_origins = not_err!(parse_origins(&AllowedOrigins::some_exact(&[
|
2019-03-12 01:58:51 +00:00
|
|
|
|
"https://www.example.com"
|
|
|
|
|
])));
|
2017-07-13 07:37:15 +00:00
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
not_err!(validate_origin(&origin, &allowed_origins));
|
2017-07-14 07:35:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-12 01:58:51 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn validate_origin_handles_punycode_properly() {
|
|
|
|
|
// Test a variety of scenarios where the Origin and settings are in punycode, or not
|
|
|
|
|
let cases = vec![
|
|
|
|
|
("https://аpple.com", "https://аpple.com"),
|
|
|
|
|
("https://аpple.com", "https://xn--pple-43d.com"),
|
|
|
|
|
("https://xn--pple-43d.com", "https://аpple.com"),
|
|
|
|
|
("https://xn--pple-43d.com", "https://xn--pple-43d.com"),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (url, allowed_origin) in cases {
|
2019-03-12 07:05:40 +00:00
|
|
|
|
let origin = not_err!(to_parsed_origin(&url));
|
|
|
|
|
let allowed_origins = not_err!(parse_origins(&AllowedOrigins::some_exact(&[
|
|
|
|
|
allowed_origin
|
|
|
|
|
])));
|
2019-03-12 01:58:51 +00:00
|
|
|
|
|
|
|
|
|
not_err!(validate_origin(&origin, &allowed_origins));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-12 07:05:40 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn validate_origin_validates_regex() {
|
|
|
|
|
let url = "https://www.example-something.com";
|
|
|
|
|
let origin = not_err!(to_parsed_origin(&url));
|
|
|
|
|
let allowed_origins = not_err!(parse_origins(&AllowedOrigins::some_regex(&[
|
|
|
|
|
"^https://www.example-[A-z0-9]+.com$"
|
|
|
|
|
])));
|
|
|
|
|
|
|
|
|
|
not_err!(validate_origin(&origin, &allowed_origins));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn validate_origin_validates_mixed_settings() {
|
|
|
|
|
let allowed_origins = not_err!(parse_origins(&AllowedOrigins::some(
|
|
|
|
|
&["https://www.acme.com"],
|
|
|
|
|
&["^https://www.example-[A-z0-9]+.com$"]
|
|
|
|
|
)));
|
|
|
|
|
|
|
|
|
|
let url = "https://www.example-something123.com";
|
|
|
|
|
let origin = not_err!(to_parsed_origin(&url));
|
|
|
|
|
not_err!(validate_origin(&origin, &allowed_origins));
|
|
|
|
|
|
|
|
|
|
let url = "https://www.acme.com";
|
|
|
|
|
let origin = not_err!(to_parsed_origin(&url));
|
|
|
|
|
not_err!(validate_origin(&origin, &allowed_origins));
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-14 07:35:44 +00:00
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "OriginNotAllowed")]
|
2018-02-14 08:27:50 +00:00
|
|
|
|
fn validate_origin_rejects_invalid_origin() {
|
2017-07-14 07:35:44 +00:00
|
|
|
|
let url = "https://www.acme.com";
|
2019-03-12 07:05:40 +00:00
|
|
|
|
let origin = not_err!(to_parsed_origin(&url));
|
|
|
|
|
let allowed_origins = not_err!(parse_origins(&AllowedOrigins::some_exact(&[
|
2019-03-12 01:58:51 +00:00
|
|
|
|
"https://www.example.com"
|
|
|
|
|
])));
|
2017-07-14 07:35:44 +00:00
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
validate_origin(&origin, &allowed_origins).unwrap();
|
2017-07-14 07:35:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-14 08:27:50 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn response_sets_allow_origin_without_vary_correctly() {
|
|
|
|
|
let response = Response::new();
|
2019-03-12 07:05:40 +00:00
|
|
|
|
let response = response.origin("https://www.example.com", false);
|
2018-02-14 08:27:50 +00:00
|
|
|
|
|
|
|
|
|
// Build response and check built response header
|
|
|
|
|
let expected_header = vec!["https://www.example.com"];
|
|
|
|
|
let response = response.response(response::Response::new());
|
2018-07-18 05:26:33 +00:00
|
|
|
|
let actual_header: Vec<_> = response
|
|
|
|
|
.headers()
|
|
|
|
|
.get("Access-Control-Allow-Origin")
|
|
|
|
|
.collect();
|
2018-02-14 08:27:50 +00:00
|
|
|
|
assert_eq!(expected_header, actual_header);
|
|
|
|
|
|
|
|
|
|
assert!(response.headers().get("Vary").next().is_none());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn response_sets_allow_origin_with_vary_correctly() {
|
|
|
|
|
let response = Response::new();
|
2019-03-12 07:05:40 +00:00
|
|
|
|
let response = response.origin("https://www.example.com", true);
|
2018-02-14 08:27:50 +00:00
|
|
|
|
|
|
|
|
|
// Build response and check built response header
|
|
|
|
|
let expected_header = vec!["https://www.example.com"];
|
|
|
|
|
let response = response.response(response::Response::new());
|
2018-07-18 05:26:33 +00:00
|
|
|
|
let actual_header: Vec<_> = response
|
|
|
|
|
.headers()
|
|
|
|
|
.get("Access-Control-Allow-Origin")
|
|
|
|
|
.collect();
|
2018-02-14 08:27:50 +00:00
|
|
|
|
assert_eq!(expected_header, actual_header);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn response_sets_any_origin_correctly() {
|
|
|
|
|
let response = Response::new();
|
|
|
|
|
let response = response.any();
|
|
|
|
|
|
|
|
|
|
// Build response and check built response header
|
|
|
|
|
let expected_header = vec!["*"];
|
|
|
|
|
let response = response.response(response::Response::new());
|
2018-07-18 05:26:33 +00:00
|
|
|
|
let actual_header: Vec<_> = response
|
|
|
|
|
.headers()
|
|
|
|
|
.get("Access-Control-Allow-Origin")
|
|
|
|
|
.collect();
|
2018-02-14 08:27:50 +00:00
|
|
|
|
assert_eq!(expected_header, actual_header);
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-14 07:35:44 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn response_sets_exposed_headers_correctly() {
|
|
|
|
|
let headers = vec!["Bar", "Baz", "Foo"];
|
2017-07-14 17:38:13 +00:00
|
|
|
|
let response = Response::new();
|
2019-03-12 07:05:40 +00:00
|
|
|
|
let response = response.origin("https://www.example.com", false);
|
2017-07-14 07:35:44 +00:00
|
|
|
|
let response = response.exposed_headers(&headers);
|
|
|
|
|
|
|
|
|
|
// Build response and check built response header
|
2017-07-17 06:28:54 +00:00
|
|
|
|
let response = response.response(response::Response::new());
|
2017-07-14 07:35:44 +00:00
|
|
|
|
let actual_header: Vec<_> = response
|
|
|
|
|
.headers()
|
|
|
|
|
.get("Access-Control-Expose-Headers")
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
assert_eq!(1, actual_header.len());
|
|
|
|
|
let mut actual_headers: Vec<String> = actual_header[0]
|
|
|
|
|
.split(',')
|
|
|
|
|
.map(|header| header.trim().to_string())
|
|
|
|
|
.collect();
|
|
|
|
|
actual_headers.sort();
|
|
|
|
|
assert_eq!(headers, actual_headers);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn response_sets_max_age_correctly() {
|
2017-07-14 17:38:13 +00:00
|
|
|
|
let response = Response::new();
|
2019-03-12 07:05:40 +00:00
|
|
|
|
let response = response.origin("https://www.example.com", false);
|
2017-07-14 07:35:44 +00:00
|
|
|
|
|
|
|
|
|
let response = response.max_age(Some(42));
|
|
|
|
|
|
|
|
|
|
// Build response and check built response header
|
|
|
|
|
let expected_header = vec!["42"];
|
2017-07-17 06:28:54 +00:00
|
|
|
|
let response = response.response(response::Response::new());
|
2017-07-14 07:35:44 +00:00
|
|
|
|
let actual_header: Vec<_> = response.headers().get("Access-Control-Max-Age").collect();
|
|
|
|
|
assert_eq!(expected_header, actual_header);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn response_does_not_set_max_age_when_none() {
|
2017-07-14 17:38:13 +00:00
|
|
|
|
let response = Response::new();
|
2019-03-12 07:05:40 +00:00
|
|
|
|
let response = response.origin("https://www.example.com", false);
|
2017-07-14 07:35:44 +00:00
|
|
|
|
|
|
|
|
|
let response = response.max_age(None);
|
|
|
|
|
|
|
|
|
|
// Build response and check built response header
|
2017-07-17 06:28:54 +00:00
|
|
|
|
let response = response.response(response::Response::new());
|
2018-10-31 02:30:10 +00:00
|
|
|
|
assert!(response
|
|
|
|
|
.headers()
|
|
|
|
|
.get("Access-Control-Max-Age")
|
|
|
|
|
.next()
|
|
|
|
|
.is_none())
|
2017-07-14 07:35:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2017-07-17 06:28:54 +00:00
|
|
|
|
fn allowed_methods_validated_correctly() {
|
|
|
|
|
let allowed_methods = vec![Method::Get, Method::Head, Method::Post]
|
|
|
|
|
.into_iter()
|
2017-07-17 08:22:45 +00:00
|
|
|
|
.map(From::from)
|
2017-07-14 07:35:44 +00:00
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let method = "GET";
|
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
not_err!(validate_allowed_method(
|
|
|
|
|
&FromStr::from_str(method).expect("not to fail"),
|
|
|
|
|
&allowed_methods,
|
|
|
|
|
));
|
2017-07-14 07:35:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "MethodNotAllowed")]
|
2017-07-17 06:28:54 +00:00
|
|
|
|
fn allowed_methods_errors_on_disallowed_method() {
|
|
|
|
|
let allowed_methods = vec![Method::Get, Method::Head, Method::Post]
|
|
|
|
|
.into_iter()
|
2017-07-17 08:22:45 +00:00
|
|
|
|
.map(From::from)
|
2017-07-14 07:35:44 +00:00
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let method = "DELETE";
|
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
validate_allowed_method(
|
|
|
|
|
&FromStr::from_str(method).expect("not to fail"),
|
|
|
|
|
&allowed_methods,
|
2018-10-31 02:30:10 +00:00
|
|
|
|
)
|
|
|
|
|
.unwrap()
|
2017-07-17 06:28:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn all_allowed_headers_are_validated_correctly() {
|
|
|
|
|
let allowed_headers = AllOrSome::All;
|
|
|
|
|
let requested_headers = vec!["Bar", "Foo"];
|
|
|
|
|
|
|
|
|
|
not_err!(validate_allowed_headers(
|
|
|
|
|
&FromStr::from_str(&requested_headers.join(",")).unwrap(),
|
|
|
|
|
&allowed_headers,
|
|
|
|
|
));
|
2017-07-14 07:35:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// `Response::allowed_headers` should check that headers are allowed, and only
|
|
|
|
|
/// echoes back the list that is actually requested for and not the whole list
|
|
|
|
|
#[test]
|
2017-07-17 06:28:54 +00:00
|
|
|
|
fn allowed_headers_are_validated_correctly() {
|
2017-07-14 07:35:44 +00:00
|
|
|
|
let allowed_headers = vec!["Bar", "Baz", "Foo"];
|
|
|
|
|
let requested_headers = vec!["Bar", "Foo"];
|
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
not_err!(validate_allowed_headers(
|
|
|
|
|
&FromStr::from_str(&requested_headers.join(",")).unwrap(),
|
|
|
|
|
&AllOrSome::Some(
|
|
|
|
|
allowed_headers
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|s| FromStr::from_str(*s).unwrap())
|
|
|
|
|
.collect(),
|
|
|
|
|
),
|
|
|
|
|
));
|
2017-07-14 07:35:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "HeadersNotAllowed")]
|
2017-07-17 06:28:54 +00:00
|
|
|
|
fn allowed_headers_errors_on_non_subset() {
|
2017-07-14 07:35:44 +00:00
|
|
|
|
let allowed_headers = vec!["Bar", "Baz", "Foo"];
|
|
|
|
|
let requested_headers = vec!["Bar", "Foo", "Unknown"];
|
|
|
|
|
|
2017-07-17 06:28:54 +00:00
|
|
|
|
validate_allowed_headers(
|
|
|
|
|
&FromStr::from_str(&requested_headers.join(",")).unwrap(),
|
|
|
|
|
&AllOrSome::Some(
|
|
|
|
|
allowed_headers
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|s| FromStr::from_str(*s).unwrap())
|
|
|
|
|
.collect(),
|
|
|
|
|
),
|
2018-10-31 02:30:10 +00:00
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
2017-07-14 07:35:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn response_does_not_build_if_origin_is_not_set() {
|
2017-07-14 17:38:13 +00:00
|
|
|
|
let response = Response::new();
|
2017-07-17 06:28:54 +00:00
|
|
|
|
let response = response.response(response::Response::new());
|
2017-07-14 07:35:44 +00:00
|
|
|
|
|
|
|
|
|
let headers: Vec<_> = response.headers().iter().collect();
|
|
|
|
|
assert_eq!(headers.len(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2017-07-14 17:38:13 +00:00
|
|
|
|
fn response_build_removes_existing_cors_headers_and_keeps_others() {
|
2017-07-14 07:35:44 +00:00
|
|
|
|
use std::io::Cursor;
|
|
|
|
|
|
2017-07-14 17:38:13 +00:00
|
|
|
|
let original = response::Response::build()
|
2017-07-14 07:35:44 +00:00
|
|
|
|
.status(Status::ImATeapot)
|
|
|
|
|
.raw_header("X-Teapot-Make", "Rocket")
|
2017-07-14 17:38:13 +00:00
|
|
|
|
.raw_header("Access-Control-Max-Age", "42")
|
2017-07-14 07:35:44 +00:00
|
|
|
|
.sized_body(Cursor::new("Brewing the best coffee!"))
|
|
|
|
|
.finalize();
|
|
|
|
|
|
2017-07-14 17:38:13 +00:00
|
|
|
|
let response = Response::new();
|
2019-03-12 07:05:40 +00:00
|
|
|
|
let response = response.origin("https://www.example.com", false);
|
2017-07-17 06:28:54 +00:00
|
|
|
|
let response = response.response(original);
|
2017-07-14 07:35:44 +00:00
|
|
|
|
// Check CORS header
|
2017-07-14 17:38:13 +00:00
|
|
|
|
let expected_header = vec!["https://www.example.com"];
|
2017-07-14 07:35:44 +00:00
|
|
|
|
let actual_header: Vec<_> = response
|
|
|
|
|
.headers()
|
|
|
|
|
.get("Access-Control-Allow-Origin")
|
|
|
|
|
.collect();
|
|
|
|
|
assert_eq!(expected_header, actual_header);
|
|
|
|
|
|
|
|
|
|
// Check other header
|
|
|
|
|
let expected_header = vec!["Rocket"];
|
|
|
|
|
let actual_header: Vec<_> = response.headers().get("X-Teapot-Make").collect();
|
|
|
|
|
assert_eq!(expected_header, actual_header);
|
|
|
|
|
|
2017-07-14 17:38:13 +00:00
|
|
|
|
// Check that `Access-Control-Max-Age` is removed
|
2018-10-31 02:30:10 +00:00
|
|
|
|
assert!(response
|
|
|
|
|
.headers()
|
|
|
|
|
.get("Access-Control-Max-Age")
|
|
|
|
|
.next()
|
|
|
|
|
.is_none());
|
2017-07-14 07:35:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-05 06:11:28 +00:00
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
|
2017-07-17 08:22:45 +00:00
|
|
|
|
struct MethodTest {
|
2018-10-31 02:30:58 +00:00
|
|
|
|
method: crate::Method,
|
2017-07-17 08:22:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-05 06:11:28 +00:00
|
|
|
|
#[cfg(feature = "serialization")]
|
2017-07-17 08:22:45 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn method_serde_roundtrip() {
|
2018-02-14 05:22:43 +00:00
|
|
|
|
use serde_test::{assert_tokens, Token};
|
2017-07-17 08:22:45 +00:00
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let test = MethodTest {
|
|
|
|
|
method: From::from(http::Method::Get),
|
|
|
|
|
};
|
2017-07-17 08:22:45 +00:00
|
|
|
|
|
|
|
|
|
assert_tokens(
|
|
|
|
|
&test,
|
|
|
|
|
&[
|
|
|
|
|
Token::Struct {
|
|
|
|
|
name: "MethodTest",
|
|
|
|
|
len: 1,
|
|
|
|
|
},
|
|
|
|
|
Token::Str("method"),
|
|
|
|
|
Token::Str("GET"),
|
|
|
|
|
Token::StructEnd,
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-18 05:11:30 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn preflight_validated_correctly() {
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = make_cors_options().to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let client = make_client();
|
2017-07-14 17:38:13 +00:00
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.acme.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let method_header = Header::from(hyper::header::AccessControlRequestMethod(
|
|
|
|
|
hyper::method::Method::Get,
|
|
|
|
|
));
|
2018-10-31 02:30:10 +00:00
|
|
|
|
let request_headers =
|
|
|
|
|
hyper::header::AccessControlRequestHeaders(vec![
|
|
|
|
|
FromStr::from_str("Authorization").unwrap()
|
|
|
|
|
]);
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request_headers = Header::from(request_headers);
|
|
|
|
|
|
|
|
|
|
let request = client
|
|
|
|
|
.options("/")
|
|
|
|
|
.header(origin_header)
|
|
|
|
|
.header(method_header)
|
|
|
|
|
.header(request_headers);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let result = validate(&cors, request.inner()).expect("to not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let expected_result = ValidationResult::Preflight {
|
2019-03-12 07:05:40 +00:00
|
|
|
|
origin: "https://www.acme.com".to_string(),
|
2017-07-18 05:11:30 +00:00
|
|
|
|
// Checks that only a subset of allowed headers are returned
|
|
|
|
|
// -- i.e. whatever is requested for
|
|
|
|
|
headers: Some(FromStr::from_str("Authorization").unwrap()),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected_result, result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn preflight_validation_allows_all_origin() {
|
|
|
|
|
let mut options = make_cors_options();
|
|
|
|
|
options.allowed_origins = AllOrSome::All;
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = options.to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.example.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let method_header = Header::from(hyper::header::AccessControlRequestMethod(
|
|
|
|
|
hyper::method::Method::Get,
|
|
|
|
|
));
|
2018-10-31 02:30:10 +00:00
|
|
|
|
let request_headers =
|
|
|
|
|
hyper::header::AccessControlRequestHeaders(vec![
|
|
|
|
|
FromStr::from_str("Authorization").unwrap()
|
|
|
|
|
]);
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request_headers = Header::from(request_headers);
|
|
|
|
|
|
|
|
|
|
let request = client
|
|
|
|
|
.options("/")
|
|
|
|
|
.header(origin_header)
|
|
|
|
|
.header(method_header)
|
|
|
|
|
.header(request_headers);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let result = validate(&cors, request.inner()).expect("to not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let expected_result = ValidationResult::Preflight {
|
2019-03-12 07:05:40 +00:00
|
|
|
|
origin: "https://www.example.com".to_string(),
|
2017-07-18 05:11:30 +00:00
|
|
|
|
headers: Some(FromStr::from_str("Authorization").unwrap()),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected_result, result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "OriginNotAllowed")]
|
|
|
|
|
fn preflight_validation_errors_on_invalid_origin() {
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = make_cors_options().to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.example.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let method_header = Header::from(hyper::header::AccessControlRequestMethod(
|
|
|
|
|
hyper::method::Method::Get,
|
|
|
|
|
));
|
2018-10-31 02:30:10 +00:00
|
|
|
|
let request_headers =
|
|
|
|
|
hyper::header::AccessControlRequestHeaders(vec![
|
|
|
|
|
FromStr::from_str("Authorization").unwrap()
|
|
|
|
|
]);
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request_headers = Header::from(request_headers);
|
|
|
|
|
|
|
|
|
|
let request = client
|
|
|
|
|
.options("/")
|
|
|
|
|
.header(origin_header)
|
|
|
|
|
.header(method_header)
|
|
|
|
|
.header(request_headers);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let _ = validate(&cors, request.inner()).unwrap();
|
2017-07-18 05:11:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "MissingRequestMethod")]
|
|
|
|
|
fn preflight_validation_errors_on_missing_request_method() {
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = make_cors_options().to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.acme.com").unwrap());
|
2018-10-31 02:30:10 +00:00
|
|
|
|
let request_headers =
|
|
|
|
|
hyper::header::AccessControlRequestHeaders(vec![
|
|
|
|
|
FromStr::from_str("Authorization").unwrap()
|
|
|
|
|
]);
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request_headers = Header::from(request_headers);
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let request = client
|
|
|
|
|
.options("/")
|
|
|
|
|
.header(origin_header)
|
|
|
|
|
.header(request_headers);
|
2017-07-18 05:11:30 +00:00
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let _ = validate(&cors, request.inner()).unwrap();
|
2017-07-18 05:11:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "MethodNotAllowed")]
|
|
|
|
|
fn preflight_validation_errors_on_disallowed_method() {
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = make_cors_options().to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.acme.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let method_header = Header::from(hyper::header::AccessControlRequestMethod(
|
|
|
|
|
hyper::method::Method::Post,
|
|
|
|
|
));
|
2018-10-31 02:30:10 +00:00
|
|
|
|
let request_headers =
|
|
|
|
|
hyper::header::AccessControlRequestHeaders(vec![
|
|
|
|
|
FromStr::from_str("Authorization").unwrap()
|
|
|
|
|
]);
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request_headers = Header::from(request_headers);
|
|
|
|
|
|
|
|
|
|
let request = client
|
|
|
|
|
.options("/")
|
|
|
|
|
.header(origin_header)
|
|
|
|
|
.header(method_header)
|
|
|
|
|
.header(request_headers);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let _ = validate(&cors, request.inner()).unwrap();
|
2017-07-18 05:11:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "HeadersNotAllowed")]
|
|
|
|
|
fn preflight_validation_errors_on_disallowed_headers() {
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = make_cors_options().to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.acme.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let method_header = Header::from(hyper::header::AccessControlRequestMethod(
|
|
|
|
|
hyper::method::Method::Get,
|
|
|
|
|
));
|
|
|
|
|
let request_headers = hyper::header::AccessControlRequestHeaders(vec![
|
|
|
|
|
FromStr::from_str("Authorization").unwrap(),
|
|
|
|
|
FromStr::from_str("X-NOT-ALLOWED").unwrap(),
|
|
|
|
|
]);
|
|
|
|
|
let request_headers = Header::from(request_headers);
|
|
|
|
|
|
|
|
|
|
let request = client
|
|
|
|
|
.options("/")
|
|
|
|
|
.header(origin_header)
|
|
|
|
|
.header(method_header)
|
|
|
|
|
.header(request_headers);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let _ = validate(&cors, request.inner()).unwrap();
|
2017-07-18 05:11:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn actual_request_validated_correctly() {
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = make_cors_options().to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.acme.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request = client.get("/").header(origin_header);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let result = validate(&cors, request.inner()).expect("to not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let expected_result = ValidationResult::Request {
|
2019-03-12 07:05:40 +00:00
|
|
|
|
origin: "https://www.acme.com".to_string(),
|
2017-07-18 05:11:30 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected_result, result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn actual_request_validation_allows_all_origin() {
|
|
|
|
|
let mut options = make_cors_options();
|
|
|
|
|
options.allowed_origins = AllOrSome::All;
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = options.to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.example.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request = client.get("/").header(origin_header);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let result = validate(&cors, request.inner()).expect("to not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let expected_result = ValidationResult::Request {
|
2019-03-12 07:05:40 +00:00
|
|
|
|
origin: "https://www.example.com".to_string(),
|
2017-07-18 05:11:30 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected_result, result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "OriginNotAllowed")]
|
|
|
|
|
fn actual_request_validation_errors_on_incorrect_origin() {
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = make_cors_options().to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.example.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request = client.get("/").header(origin_header);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let _ = validate(&cors, request.inner()).unwrap();
|
2017-07-18 05:11:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn non_cors_request_return_empty_response() {
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = make_cors_options().to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let client = make_client();
|
|
|
|
|
|
|
|
|
|
let request = client.options("/");
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let response = validate_and_build(&cors, request.inner()).expect("to not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let expected_response = Response::new();
|
|
|
|
|
assert_eq!(expected_response, response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn preflight_validated_and_built_correctly() {
|
|
|
|
|
let options = make_cors_options();
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = options.to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.acme.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let method_header = Header::from(hyper::header::AccessControlRequestMethod(
|
|
|
|
|
hyper::method::Method::Get,
|
|
|
|
|
));
|
2018-10-31 02:30:10 +00:00
|
|
|
|
let request_headers =
|
|
|
|
|
hyper::header::AccessControlRequestHeaders(vec![
|
|
|
|
|
FromStr::from_str("Authorization").unwrap()
|
|
|
|
|
]);
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request_headers = Header::from(request_headers);
|
|
|
|
|
|
|
|
|
|
let request = client
|
|
|
|
|
.options("/")
|
|
|
|
|
.header(origin_header)
|
|
|
|
|
.header(method_header)
|
|
|
|
|
.header(request_headers);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let response = validate_and_build(&cors, request.inner()).expect("to not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
|
|
|
|
|
let expected_response = Response::new()
|
2019-03-12 07:05:40 +00:00
|
|
|
|
.origin("https://www.acme.com", false)
|
2017-07-18 05:11:30 +00:00
|
|
|
|
.headers(&["Authorization"])
|
|
|
|
|
.methods(&options.allowed_methods)
|
|
|
|
|
.credentials(options.allow_credentials)
|
|
|
|
|
.max_age(options.max_age);
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected_response, response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tests that when All origins are allowed and send_wildcard disabled, the vary header is set
|
|
|
|
|
/// in the response and the requested origin is echoed
|
|
|
|
|
#[test]
|
|
|
|
|
fn preflight_all_origins_with_vary() {
|
|
|
|
|
let mut options = make_cors_options();
|
|
|
|
|
options.allowed_origins = AllOrSome::All;
|
|
|
|
|
options.send_wildcard = false;
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = options.to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
|
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.acme.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let method_header = Header::from(hyper::header::AccessControlRequestMethod(
|
|
|
|
|
hyper::method::Method::Get,
|
|
|
|
|
));
|
2018-10-31 02:30:10 +00:00
|
|
|
|
let request_headers =
|
|
|
|
|
hyper::header::AccessControlRequestHeaders(vec![
|
|
|
|
|
FromStr::from_str("Authorization").unwrap()
|
|
|
|
|
]);
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request_headers = Header::from(request_headers);
|
|
|
|
|
|
|
|
|
|
let request = client
|
|
|
|
|
.options("/")
|
|
|
|
|
.header(origin_header)
|
|
|
|
|
.header(method_header)
|
|
|
|
|
.header(request_headers);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let response = validate_and_build(&cors, request.inner()).expect("to not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
|
|
|
|
|
let expected_response = Response::new()
|
2019-03-12 07:05:40 +00:00
|
|
|
|
.origin("https://www.acme.com", true)
|
2017-07-18 05:11:30 +00:00
|
|
|
|
.headers(&["Authorization"])
|
|
|
|
|
.methods(&options.allowed_methods)
|
|
|
|
|
.credentials(options.allow_credentials)
|
|
|
|
|
.max_age(options.max_age);
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected_response, response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tests that when All origins are allowed and send_wildcard enabled, the origin is set to "*"
|
|
|
|
|
#[test]
|
|
|
|
|
fn preflight_all_origins_with_wildcard() {
|
|
|
|
|
let mut options = make_cors_options();
|
|
|
|
|
options.allowed_origins = AllOrSome::All;
|
|
|
|
|
options.send_wildcard = true;
|
|
|
|
|
options.allow_credentials = false;
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = options.to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
|
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.acme.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let method_header = Header::from(hyper::header::AccessControlRequestMethod(
|
|
|
|
|
hyper::method::Method::Get,
|
|
|
|
|
));
|
2018-10-31 02:30:10 +00:00
|
|
|
|
let request_headers =
|
|
|
|
|
hyper::header::AccessControlRequestHeaders(vec![
|
|
|
|
|
FromStr::from_str("Authorization").unwrap()
|
|
|
|
|
]);
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request_headers = Header::from(request_headers);
|
|
|
|
|
|
|
|
|
|
let request = client
|
|
|
|
|
.options("/")
|
|
|
|
|
.header(origin_header)
|
|
|
|
|
.header(method_header)
|
|
|
|
|
.header(request_headers);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let response = validate_and_build(&cors, request.inner()).expect("to not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
|
|
|
|
|
let expected_response = Response::new()
|
|
|
|
|
.any()
|
|
|
|
|
.headers(&["Authorization"])
|
|
|
|
|
.methods(&options.allowed_methods)
|
|
|
|
|
.credentials(options.allow_credentials)
|
|
|
|
|
.max_age(options.max_age);
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected_response, response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn actual_request_validated_and_built_correctly() {
|
|
|
|
|
let options = make_cors_options();
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = options.to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.acme.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request = client.get("/").header(origin_header);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let response = validate_and_build(&cors, request.inner()).expect("to not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let expected_response = Response::new()
|
2019-03-12 07:05:40 +00:00
|
|
|
|
.origin("https://www.acme.com", false)
|
2017-07-18 05:11:30 +00:00
|
|
|
|
.credentials(options.allow_credentials)
|
|
|
|
|
.exposed_headers(&["Content-Type", "X-Custom"]);
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected_response, response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn actual_request_all_origins_with_vary() {
|
|
|
|
|
let mut options = make_cors_options();
|
|
|
|
|
options.allowed_origins = AllOrSome::All;
|
|
|
|
|
options.send_wildcard = false;
|
|
|
|
|
options.allow_credentials = false;
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = options.to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
|
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.acme.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request = client.get("/").header(origin_header);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let response = validate_and_build(&cors, request.inner()).expect("to not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let expected_response = Response::new()
|
2019-03-12 07:05:40 +00:00
|
|
|
|
.origin("https://www.acme.com", true)
|
2017-07-18 05:11:30 +00:00
|
|
|
|
.credentials(options.allow_credentials)
|
|
|
|
|
.exposed_headers(&["Content-Type", "X-Custom"]);
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected_response, response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn actual_request_all_origins_with_wildcard() {
|
|
|
|
|
let mut options = make_cors_options();
|
|
|
|
|
options.allowed_origins = AllOrSome::All;
|
|
|
|
|
options.send_wildcard = true;
|
|
|
|
|
options.allow_credentials = false;
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let cors = options.to_cors().expect("To not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
|
|
|
|
|
let client = make_client();
|
|
|
|
|
|
2018-02-14 05:22:43 +00:00
|
|
|
|
let origin_header =
|
|
|
|
|
Header::from(hyper::header::Origin::from_str("https://www.acme.com").unwrap());
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let request = client.get("/").header(origin_header);
|
|
|
|
|
|
2018-12-19 00:29:26 +00:00
|
|
|
|
let response = validate_and_build(&cors, request.inner()).expect("to not fail");
|
2017-07-18 05:11:30 +00:00
|
|
|
|
let expected_response = Response::new()
|
|
|
|
|
.any()
|
|
|
|
|
.credentials(options.allow_credentials)
|
|
|
|
|
.exposed_headers(&["Content-Type", "X-Custom"]);
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected_response, response);
|
|
|
|
|
}
|
2017-07-13 07:37:15 +00:00
|
|
|
|
}
|