1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-06-02 21:39:26 +00:00
actix-web/src/guard.rs

500 lines
13 KiB
Rust
Raw Normal View History

2019-03-03 20:09:38 +00:00
//! Route match guards.
2019-03-07 23:37:39 +00:00
//!
//! Guards are one of the ways how actix-web router chooses a
//! handler service. In essence it is just a function that accepts a
//! reference to a `RequestHead` instance and returns a boolean.
2019-03-07 23:37:39 +00:00
//! It is possible to add guards to *scopes*, *resources*
//! and *routes*. Actix provide several guards by default, like various
//! http methods, header, etc. To become a guard, type must implement `Guard`
2020-04-21 03:09:35 +00:00
//! trait. Simple functions could be guards as well.
2019-03-07 23:37:39 +00:00
//!
//! Guards can not modify the request object. But it is possible
//! to store extra attributes on a request by using the `Extensions` container.
//! Extensions containers are available via the `RequestHead::extensions()` method.
2019-03-07 23:37:39 +00:00
//!
//! ```rust
//! use actix_web::{web, http, dev, guard, App, HttpResponse};
//!
//! fn main() {
//! App::new().service(web::resource("/index.html").route(
//! web::route()
//! .guard(guard::Post())
2019-03-30 19:13:21 +00:00
//! .guard(guard::fn_guard(|head| head.method == http::Method::GET))
2019-03-07 23:37:39 +00:00
//! .to(|| HttpResponse::MethodNotAllowed()))
//! );
//! }
//! ```
2019-03-02 06:51:32 +00:00
#![allow(non_snake_case)]
2019-12-05 17:35:43 +00:00
use std::convert::TryFrom;
use actix_http::http::{self, header, uri::Uri};
use actix_http::RequestHead;
2019-03-02 06:51:32 +00:00
/// Trait defines resource guards. Guards are used for route selection.
2019-03-03 20:09:38 +00:00
///
/// Guards can not modify the request object. But it is possible
/// to store extra attributes on a request by using the `Extensions` container.
/// Extensions containers are available via the `RequestHead::extensions()` method.
2019-03-03 20:09:38 +00:00
pub trait Guard {
2019-03-02 06:51:32 +00:00
/// Check if request matches predicate
fn check(&self, request: &RequestHead) -> bool;
2019-03-02 06:51:32 +00:00
}
2019-03-31 03:48:00 +00:00
/// Create guard object for supplied function.
2019-03-30 18:33:31 +00:00
///
/// ```rust
/// use actix_web::{guard, web, App, HttpResponse};
///
/// fn main() {
/// App::new().service(web::resource("/index.html").route(
/// web::route()
/// .guard(
2019-03-31 03:48:00 +00:00
/// guard::fn_guard(
/// |req| req.headers()
/// .contains_key("content-type")))
2019-03-30 18:33:31 +00:00
/// .to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
pub fn fn_guard<F>(f: F) -> impl Guard
where
F: Fn(&RequestHead) -> bool,
{
FnGuard(f)
}
struct FnGuard<F: Fn(&RequestHead) -> bool>(F);
2019-03-07 23:37:39 +00:00
2019-03-30 18:33:31 +00:00
impl<F> Guard for FnGuard<F>
2019-03-07 23:37:39 +00:00
where
2019-03-30 18:33:31 +00:00
F: Fn(&RequestHead) -> bool,
2019-03-07 23:37:39 +00:00
{
fn check(&self, head: &RequestHead) -> bool {
2019-03-30 18:33:31 +00:00
(self.0)(head)
2019-03-07 23:37:39 +00:00
}
}
2019-04-02 20:35:01 +00:00
impl<F> Guard for F
where
F: Fn(&RequestHead) -> bool,
{
fn check(&self, head: &RequestHead) -> bool {
(self)(head)
}
}
2019-03-03 20:09:38 +00:00
/// Return guard that matches if any of supplied guards.
2019-03-02 06:51:32 +00:00
///
/// ```rust
2019-03-03 20:09:38 +00:00
/// use actix_web::{web, guard, App, HttpResponse};
2019-03-02 06:51:32 +00:00
///
/// fn main() {
/// App::new().service(web::resource("/index.html").route(
/// web::route()
/// .guard(guard::Any(guard::Get()).or(guard::Post()))
/// .to(|| HttpResponse::MethodNotAllowed()))
/// );
2019-03-02 06:51:32 +00:00
/// }
/// ```
2019-03-03 20:09:38 +00:00
pub fn Any<F: Guard + 'static>(guard: F) -> AnyGuard {
AnyGuard(vec![Box::new(guard)])
2019-03-02 06:51:32 +00:00
}
2020-04-21 03:09:35 +00:00
/// Matches any of supplied guards.
2019-07-17 09:48:37 +00:00
pub struct AnyGuard(Vec<Box<dyn Guard>>);
2019-03-02 06:51:32 +00:00
2019-03-03 20:09:38 +00:00
impl AnyGuard {
/// Add guard to a list of guards to check
pub fn or<F: Guard + 'static>(mut self, guard: F) -> Self {
self.0.push(Box::new(guard));
2019-03-02 06:51:32 +00:00
self
}
}
2019-03-03 20:09:38 +00:00
impl Guard for AnyGuard {
fn check(&self, req: &RequestHead) -> bool {
2019-03-02 06:51:32 +00:00
for p in &self.0 {
if p.check(req) {
return true;
}
}
false
}
}
2019-03-03 20:09:38 +00:00
/// Return guard that matches if all of the supplied guards.
2019-03-02 06:51:32 +00:00
///
2019-03-03 00:24:14 +00:00
/// ```rust
2019-03-03 20:09:38 +00:00
/// use actix_web::{guard, web, App, HttpResponse};
2019-03-02 06:51:32 +00:00
///
/// fn main() {
/// App::new().service(web::resource("/index.html").route(
/// web::route()
2019-03-03 20:09:38 +00:00
/// .guard(
/// guard::All(guard::Get()).and(guard::Header("content-type", "text/plain")))
2019-03-03 00:24:14 +00:00
/// .to(|| HttpResponse::MethodNotAllowed()))
/// );
2019-03-02 06:51:32 +00:00
/// }
/// ```
2019-03-03 20:09:38 +00:00
pub fn All<F: Guard + 'static>(guard: F) -> AllGuard {
AllGuard(vec![Box::new(guard)])
2019-03-02 06:51:32 +00:00
}
2019-03-03 20:09:38 +00:00
/// Matches if all of supplied guards.
2019-07-17 09:48:37 +00:00
pub struct AllGuard(Vec<Box<dyn Guard>>);
2019-03-02 06:51:32 +00:00
2019-03-03 20:09:38 +00:00
impl AllGuard {
/// Add new guard to the list of guards to check
pub fn and<F: Guard + 'static>(mut self, guard: F) -> Self {
self.0.push(Box::new(guard));
2019-03-02 06:51:32 +00:00
self
}
}
2019-03-03 20:09:38 +00:00
impl Guard for AllGuard {
fn check(&self, request: &RequestHead) -> bool {
2019-03-02 06:51:32 +00:00
for p in &self.0 {
if !p.check(request) {
return false;
}
}
true
}
}
2019-03-03 20:09:38 +00:00
/// Return guard that matches if supplied guard does not match.
pub fn Not<F: Guard + 'static>(guard: F) -> NotGuard {
NotGuard(Box::new(guard))
2019-03-02 06:51:32 +00:00
}
#[doc(hidden)]
2019-07-17 09:48:37 +00:00
pub struct NotGuard(Box<dyn Guard>);
2019-03-02 06:51:32 +00:00
2019-03-03 20:09:38 +00:00
impl Guard for NotGuard {
fn check(&self, request: &RequestHead) -> bool {
2019-03-02 06:51:32 +00:00
!self.0.check(request)
}
}
2019-03-03 20:09:38 +00:00
/// Http method guard
2019-03-02 06:51:32 +00:00
#[doc(hidden)]
2019-03-03 20:09:38 +00:00
pub struct MethodGuard(http::Method);
2019-03-02 06:51:32 +00:00
2019-03-03 20:09:38 +00:00
impl Guard for MethodGuard {
fn check(&self, request: &RequestHead) -> bool {
request.method == self.0
2019-03-02 06:51:32 +00:00
}
}
2019-03-03 20:09:38 +00:00
/// Guard to match *GET* http method
pub fn Get() -> MethodGuard {
MethodGuard(http::Method::GET)
2019-03-02 06:51:32 +00:00
}
/// Predicate to match *POST* http method
2019-03-03 20:09:38 +00:00
pub fn Post() -> MethodGuard {
MethodGuard(http::Method::POST)
2019-03-02 06:51:32 +00:00
}
/// Predicate to match *PUT* http method
2019-03-03 20:09:38 +00:00
pub fn Put() -> MethodGuard {
MethodGuard(http::Method::PUT)
2019-03-02 06:51:32 +00:00
}
/// Predicate to match *DELETE* http method
2019-03-03 20:09:38 +00:00
pub fn Delete() -> MethodGuard {
MethodGuard(http::Method::DELETE)
2019-03-02 06:51:32 +00:00
}
/// Predicate to match *HEAD* http method
2019-03-03 20:09:38 +00:00
pub fn Head() -> MethodGuard {
MethodGuard(http::Method::HEAD)
2019-03-02 06:51:32 +00:00
}
/// Predicate to match *OPTIONS* http method
2019-03-03 20:09:38 +00:00
pub fn Options() -> MethodGuard {
MethodGuard(http::Method::OPTIONS)
2019-03-02 06:51:32 +00:00
}
/// Predicate to match *CONNECT* http method
2019-03-03 20:09:38 +00:00
pub fn Connect() -> MethodGuard {
MethodGuard(http::Method::CONNECT)
2019-03-02 06:51:32 +00:00
}
/// Predicate to match *PATCH* http method
2019-03-03 20:09:38 +00:00
pub fn Patch() -> MethodGuard {
MethodGuard(http::Method::PATCH)
2019-03-02 06:51:32 +00:00
}
/// Predicate to match *TRACE* http method
2019-03-03 20:09:38 +00:00
pub fn Trace() -> MethodGuard {
MethodGuard(http::Method::TRACE)
2019-03-02 06:51:32 +00:00
}
/// Predicate to match specified http method
2019-03-03 20:09:38 +00:00
pub fn Method(method: http::Method) -> MethodGuard {
MethodGuard(method)
2019-03-02 06:51:32 +00:00
}
/// Return predicate that matches if request contains specified header and
/// value.
2019-03-03 20:09:38 +00:00
pub fn Header(name: &'static str, value: &'static str) -> HeaderGuard {
HeaderGuard(
2019-03-02 06:51:32 +00:00
header::HeaderName::try_from(name).unwrap(),
header::HeaderValue::from_static(value),
)
}
#[doc(hidden)]
2019-03-03 20:09:38 +00:00
pub struct HeaderGuard(header::HeaderName, header::HeaderValue);
2019-03-02 06:51:32 +00:00
2019-03-03 20:09:38 +00:00
impl Guard for HeaderGuard {
fn check(&self, req: &RequestHead) -> bool {
if let Some(val) = req.headers.get(&self.0) {
2019-03-02 06:51:32 +00:00
return val == self.1;
}
false
}
}
/// Return predicate that matches if request contains specified Host name.
///
2019-12-08 06:31:16 +00:00
/// ```rust
/// use actix_web::{web, guard::Host, App, HttpResponse};
///
/// fn main() {
2019-12-08 06:31:16 +00:00
/// App::new().service(
/// web::resource("/index.html")
/// .guard(Host("www.rust-lang.org"))
2019-12-08 06:31:16 +00:00
/// .to(|| HttpResponse::MethodNotAllowed())
/// );
/// }
/// ```
pub fn Host<H: AsRef<str>>(host: H) -> HostGuard {
HostGuard(host.as_ref().to_string(), None)
}
fn get_host_uri(req: &RequestHead) -> Option<Uri> {
use core::str::FromStr;
2019-11-06 19:20:47 +00:00
req.headers
.get(header::HOST)
.and_then(|host_value| host_value.to_str().ok())
.or_else(|| req.uri.host())
.map(|host: &str| Uri::from_str(host).ok())
.and_then(|host_success| host_success)
}
#[doc(hidden)]
pub struct HostGuard(String, Option<String>);
impl HostGuard {
/// Set request scheme to match
pub fn scheme<H: AsRef<str>>(mut self, scheme: H) -> HostGuard {
self.1 = Some(scheme.as_ref().to_string());
self
}
}
impl Guard for HostGuard {
fn check(&self, req: &RequestHead) -> bool {
let req_host_uri = if let Some(uri) = get_host_uri(req) {
uri
} else {
return false;
};
if let Some(uri_host) = req_host_uri.host() {
if self.0 != uri_host {
return false;
}
} else {
return false;
}
if let Some(ref scheme) = self.1 {
if let Some(ref req_host_uri_scheme) = req_host_uri.scheme_str() {
return scheme == req_host_uri_scheme;
}
}
true
}
}
2019-03-02 06:51:32 +00:00
2019-03-03 00:24:14 +00:00
#[cfg(test)]
mod tests {
use actix_http::http::{header, Method};
2019-03-02 06:51:32 +00:00
2019-03-03 00:24:14 +00:00
use super::*;
2019-03-03 06:03:45 +00:00
use crate::test::TestRequest;
2019-03-02 06:51:32 +00:00
2019-03-03 00:24:14 +00:00
#[test]
fn test_header() {
2021-01-15 02:11:10 +00:00
let req = TestRequest::default()
.insert_header((header::TRANSFER_ENCODING, "chunked"))
.to_http_request();
2019-03-02 06:51:32 +00:00
2019-03-03 00:24:14 +00:00
let pred = Header("transfer-encoding", "chunked");
2019-04-02 20:35:01 +00:00
assert!(pred.check(req.head()));
2019-03-02 06:51:32 +00:00
2019-03-03 00:24:14 +00:00
let pred = Header("transfer-encoding", "other");
2019-04-02 20:35:01 +00:00
assert!(!pred.check(req.head()));
2019-03-02 06:51:32 +00:00
2019-03-03 00:24:14 +00:00
let pred = Header("content-type", "other");
2019-04-02 20:35:01 +00:00
assert!(!pred.check(req.head()));
2019-03-03 00:24:14 +00:00
}
2019-03-02 06:51:32 +00:00
#[test]
fn test_host() {
let req = TestRequest::default()
2021-01-15 02:11:10 +00:00
.insert_header((
header::HOST,
header::HeaderValue::from_static("www.rust-lang.org"),
2021-01-15 02:11:10 +00:00
))
.to_http_request();
let pred = Host("www.rust-lang.org");
assert!(pred.check(req.head()));
let pred = Host("www.rust-lang.org").scheme("https");
assert!(pred.check(req.head()));
let pred = Host("blog.rust-lang.org");
assert!(!pred.check(req.head()));
let pred = Host("blog.rust-lang.org").scheme("https");
assert!(!pred.check(req.head()));
let pred = Host("crates.io");
assert!(!pred.check(req.head()));
let pred = Host("localhost");
assert!(!pred.check(req.head()));
}
#[test]
fn test_host_scheme() {
let req = TestRequest::default()
2021-01-15 02:11:10 +00:00
.insert_header((
header::HOST,
header::HeaderValue::from_static("https://www.rust-lang.org"),
2021-01-15 02:11:10 +00:00
))
.to_http_request();
let pred = Host("www.rust-lang.org").scheme("https");
assert!(pred.check(req.head()));
let pred = Host("www.rust-lang.org");
assert!(pred.check(req.head()));
let pred = Host("www.rust-lang.org").scheme("http");
assert!(!pred.check(req.head()));
let pred = Host("blog.rust-lang.org");
assert!(!pred.check(req.head()));
let pred = Host("blog.rust-lang.org").scheme("https");
assert!(!pred.check(req.head()));
let pred = Host("crates.io").scheme("https");
assert!(!pred.check(req.head()));
let pred = Host("localhost");
assert!(!pred.check(req.head()));
}
2019-03-03 00:24:14 +00:00
#[test]
fn test_host_without_header() {
let req = TestRequest::default()
.uri("www.rust-lang.org")
.to_http_request();
let pred = Host("www.rust-lang.org");
assert!(pred.check(req.head()));
let pred = Host("www.rust-lang.org").scheme("https");
assert!(pred.check(req.head()));
let pred = Host("blog.rust-lang.org");
assert!(!pred.check(req.head()));
let pred = Host("blog.rust-lang.org").scheme("https");
assert!(!pred.check(req.head()));
let pred = Host("crates.io");
assert!(!pred.check(req.head()));
let pred = Host("localhost");
assert!(!pred.check(req.head()));
}
2019-03-03 00:24:14 +00:00
#[test]
fn test_methods() {
let req = TestRequest::default().to_http_request();
2019-03-03 06:03:45 +00:00
let req2 = TestRequest::default()
2019-03-03 00:24:14 +00:00
.method(Method::POST)
.to_http_request();
2019-03-03 00:24:14 +00:00
2019-04-02 20:35:01 +00:00
assert!(Get().check(req.head()));
assert!(!Get().check(req2.head()));
assert!(Post().check(req2.head()));
assert!(!Post().check(req.head()));
2019-03-03 00:24:14 +00:00
let r = TestRequest::default().method(Method::PUT).to_http_request();
2019-04-02 20:35:01 +00:00
assert!(Put().check(r.head()));
assert!(!Put().check(req.head()));
2019-03-03 00:24:14 +00:00
let r = TestRequest::default()
.method(Method::DELETE)
.to_http_request();
2019-04-02 20:35:01 +00:00
assert!(Delete().check(r.head()));
assert!(!Delete().check(req.head()));
2019-03-03 00:24:14 +00:00
let r = TestRequest::default()
.method(Method::HEAD)
.to_http_request();
2019-04-02 20:35:01 +00:00
assert!(Head().check(r.head()));
assert!(!Head().check(req.head()));
2019-03-03 00:24:14 +00:00
let r = TestRequest::default()
.method(Method::OPTIONS)
.to_http_request();
2019-04-02 20:35:01 +00:00
assert!(Options().check(r.head()));
assert!(!Options().check(req.head()));
2019-03-03 00:24:14 +00:00
let r = TestRequest::default()
.method(Method::CONNECT)
.to_http_request();
2019-04-02 20:35:01 +00:00
assert!(Connect().check(r.head()));
assert!(!Connect().check(req.head()));
2019-03-03 00:24:14 +00:00
let r = TestRequest::default()
.method(Method::PATCH)
.to_http_request();
2019-04-02 20:35:01 +00:00
assert!(Patch().check(r.head()));
assert!(!Patch().check(req.head()));
2019-03-03 00:24:14 +00:00
let r = TestRequest::default()
.method(Method::TRACE)
.to_http_request();
2019-04-02 20:35:01 +00:00
assert!(Trace().check(r.head()));
assert!(!Trace().check(req.head()));
2019-03-03 00:24:14 +00:00
}
2019-03-02 06:51:32 +00:00
2019-03-03 00:24:14 +00:00
#[test]
fn test_preds() {
2019-03-04 05:02:01 +00:00
let r = TestRequest::default()
.method(Method::TRACE)
.to_http_request();
2019-03-02 06:51:32 +00:00
2019-04-02 20:35:01 +00:00
assert!(Not(Get()).check(r.head()));
assert!(!Not(Trace()).check(r.head()));
2019-03-02 06:51:32 +00:00
2019-04-02 20:35:01 +00:00
assert!(All(Trace()).and(Trace()).check(r.head()));
assert!(!All(Get()).and(Trace()).check(r.head()));
2019-03-02 06:51:32 +00:00
2019-04-02 20:35:01 +00:00
assert!(Any(Get()).or(Trace()).check(r.head()));
assert!(!Any(Get()).or(Get()).check(r.head()));
2019-03-03 00:24:14 +00:00
}
}