1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
//! Defines the `AccessControlRequestMethodMatcher`.
use crate::router::non_match::RouteNonMatch;
use crate::router::route::matcher::RouteMatcher;
use crate::state::{FromState, State};
use hyper::header::{HeaderMap, ACCESS_CONTROL_REQUEST_METHOD};
use hyper::{Method, StatusCode};
/// A route matcher that checks whether the value of the `Access-Control-Request-Method` header matches the defined value.
///
/// Usage:
///
/// ```rust
/// # use gotham::{helpers::http::response::create_empty_response,
/// # hyper::{header::ACCESS_CONTROL_ALLOW_METHODS, Method, StatusCode},
/// # router::{builder::*, route::matcher::AccessControlRequestMethodMatcher}
/// # };
/// let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
///
/// # build_simple_router(|route| {
/// // use the matcher for your request
/// route
/// .options("/foo")
/// .extend_route_matcher(matcher)
/// .to(|state| {
/// // we know that this is a CORS preflight for a PUT request
/// let mut res = create_empty_response(&state, StatusCode::NO_CONTENT);
/// res.headers_mut()
/// .insert(ACCESS_CONTROL_ALLOW_METHODS, "PUT".parse().unwrap());
/// (state, res)
/// });
/// # });
/// ```
#[derive(Clone, Debug)]
pub struct AccessControlRequestMethodMatcher {
method: Method,
}
impl AccessControlRequestMethodMatcher {
/// Construct a new matcher that matches if the `Access-Control-Request-Method` header matches `method`.
/// Note that during matching the method is normalized according to the fetch specification, that is,
/// byte-uppercased. This means that when using a custom `method` instead of a predefined one, make sure
/// it is uppercased or this matcher will never succeed.
pub fn new(method: Method) -> Self {
Self { method }
}
}
impl RouteMatcher for AccessControlRequestMethodMatcher {
fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {
// according to the fetch specification, methods should be normalized by byte-uppercase
// https://fetch.spec.whatwg.org/#concept-method
match HeaderMap::borrow_from(state)
.get(ACCESS_CONTROL_REQUEST_METHOD)
.and_then(|value| value.to_str().ok())
.and_then(|str| str.to_ascii_uppercase().parse::<Method>().ok())
{
Some(m) if m == self.method => Ok(()),
_ => Err(RouteNonMatch::new(StatusCode::NOT_FOUND)),
}
}
}
#[cfg(test)]
mod test {
use super::*;
fn with_state<F>(accept: Option<&str>, block: F)
where
F: FnOnce(&mut State),
{
State::with_new(|state| {
let mut headers = HeaderMap::new();
if let Some(acc) = accept {
headers.insert(ACCESS_CONTROL_REQUEST_METHOD, acc.parse().unwrap());
}
state.put(headers);
block(state);
});
}
#[test]
fn no_acrm_header() {
let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
with_state(None, |state| assert!(matcher.is_match(state).is_err()));
}
#[test]
fn correct_acrm_header() {
let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
with_state(Some("PUT"), |state| {
assert!(matcher.is_match(state).is_ok())
});
with_state(Some("put"), |state| {
assert!(matcher.is_match(state).is_ok())
});
}
#[test]
fn incorrect_acrm_header() {
let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
with_state(Some("DELETE"), |state| {
assert!(matcher.is_match(state).is_err())
});
}
}