mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-27 17:52:56 +01:00
refactor: move Host guard into own module
This commit is contained in:
parent
c15016dafb
commit
fa106da555
@ -9,7 +9,7 @@
|
|||||||
- Implement `MessageBody` for `&mut B` where `B: MessageBody + Unpin`. [#2868]
|
- Implement `MessageBody` for `&mut B` where `B: MessageBody + Unpin`. [#2868]
|
||||||
- Implement `MessageBody` for `Pin<B>` where `B::Target: MessageBody`. [#2868]
|
- Implement `MessageBody` for `Pin<B>` where `B::Target: MessageBody`. [#2868]
|
||||||
- Automatic h2c detection via new service finalizer `HttpService::tcp_auto_h2c()`. [#2957]
|
- Automatic h2c detection via new service finalizer `HttpService::tcp_auto_h2c()`. [#2957]
|
||||||
- `HeaderMap::retain()` [#2955].
|
- `HeaderMap::retain()`. [#2955]
|
||||||
- Header name constants in `header` module. [#2956] [#2968]
|
- Header name constants in `header` module. [#2956] [#2968]
|
||||||
- `CACHE_STATUS`
|
- `CACHE_STATUS`
|
||||||
- `CDN_CACHE_CONTROL`
|
- `CDN_CACHE_CONTROL`
|
||||||
|
209
actix-web/src/guard/host.rs
Normal file
209
actix-web/src/guard/host.rs
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
use actix_http::{header, uri::Uri, RequestHead};
|
||||||
|
|
||||||
|
use super::{Guard, GuardContext};
|
||||||
|
|
||||||
|
/// Creates a guard that matches requests targetting a specific host.
|
||||||
|
///
|
||||||
|
/// # Matching Host
|
||||||
|
/// This guard will:
|
||||||
|
/// - match against the `Host` header, if present;
|
||||||
|
/// - fall-back to matching against the request target's host, if present;
|
||||||
|
/// - return false if host cannot be determined;
|
||||||
|
///
|
||||||
|
/// # Matching Scheme
|
||||||
|
/// Optionally, this guard can match against the host's scheme. Set the scheme for matching using
|
||||||
|
/// `Host(host).scheme(protocol)`. If the request's scheme cannot be determined, it will not prevent
|
||||||
|
/// the guard from matching successfully.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// The `Host` guard can be used to set up a form of [virtual hosting] within a single app.
|
||||||
|
/// Overlapping scope prefixes are usually discouraged, but when combined with non-overlapping guard
|
||||||
|
/// definitions they become safe to use in this way. Without these host guards, only routes under
|
||||||
|
/// the first-to-be-defined scope would be accessible. You can test this locally using `127.0.0.1`
|
||||||
|
/// and `localhost` as the `Host` guards.
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{web, http::Method, guard, App, HttpResponse};
|
||||||
|
///
|
||||||
|
/// App::new()
|
||||||
|
/// .service(
|
||||||
|
/// web::scope("")
|
||||||
|
/// .guard(guard::Host("www.rust-lang.org"))
|
||||||
|
/// .default_service(web::to(|| async {
|
||||||
|
/// HttpResponse::Ok().body("marketing site")
|
||||||
|
/// })),
|
||||||
|
/// )
|
||||||
|
/// .service(
|
||||||
|
/// web::scope("")
|
||||||
|
/// .guard(guard::Host("play.rust-lang.org"))
|
||||||
|
/// .default_service(web::to(|| async {
|
||||||
|
/// HttpResponse::Ok().body("playground frontend")
|
||||||
|
/// })),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The example below additionally guards on the host URI's scheme. This could allow routing to
|
||||||
|
/// different handlers for `http:` vs `https:` visitors; to redirect, for example.
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{web, guard::Host, HttpResponse};
|
||||||
|
///
|
||||||
|
/// web::scope("/admin")
|
||||||
|
/// .guard(Host("admin.rust-lang.org").scheme("https"))
|
||||||
|
/// .default_service(web::to(|| async {
|
||||||
|
/// HttpResponse::Ok().body("admin connection is secure")
|
||||||
|
/// }));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [virtual hosting]: https://en.wikipedia.org/wiki/Virtual_hosting
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn Host(host: impl AsRef<str>) -> HostGuard {
|
||||||
|
HostGuard {
|
||||||
|
host: host.as_ref().to_string(),
|
||||||
|
scheme: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_host_uri(req: &RequestHead) -> Option<Uri> {
|
||||||
|
req.headers
|
||||||
|
.get(header::HOST)
|
||||||
|
.and_then(|host_value| host_value.to_str().ok())
|
||||||
|
.or_else(|| req.uri.host())
|
||||||
|
.and_then(|host| host.parse().ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct HostGuard {
|
||||||
|
host: String,
|
||||||
|
scheme: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostGuard {
|
||||||
|
/// Set request scheme to match
|
||||||
|
pub fn scheme<H: AsRef<str>>(mut self, scheme: H) -> HostGuard {
|
||||||
|
self.scheme = Some(scheme.as_ref().to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Guard for HostGuard {
|
||||||
|
fn check(&self, ctx: &GuardContext<'_>) -> bool {
|
||||||
|
// parse host URI from header or request target
|
||||||
|
let req_host_uri = match get_host_uri(ctx.head()) {
|
||||||
|
Some(uri) => uri,
|
||||||
|
|
||||||
|
// no match if host cannot be determined
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
match req_host_uri.host() {
|
||||||
|
// fall through to scheme checks
|
||||||
|
Some(uri_host) if self.host == uri_host => {}
|
||||||
|
|
||||||
|
// Either:
|
||||||
|
// - request's host does not match guard's host;
|
||||||
|
// - It was possible that the parsed URI from request target did not contain a host.
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref scheme) = self.scheme {
|
||||||
|
if let Some(ref req_host_uri_scheme) = req_host_uri.scheme_str() {
|
||||||
|
return scheme == req_host_uri_scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: is this the correct behavior?
|
||||||
|
// falls through if scheme cannot be determined
|
||||||
|
}
|
||||||
|
|
||||||
|
// all conditions passed
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::test::TestRequest;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn host_from_header() {
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.insert_header((
|
||||||
|
header::HOST,
|
||||||
|
header::HeaderValue::from_static("www.rust-lang.org"),
|
||||||
|
))
|
||||||
|
.to_srv_request();
|
||||||
|
|
||||||
|
let host = Host("www.rust-lang.org");
|
||||||
|
assert!(host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("www.rust-lang.org").scheme("https");
|
||||||
|
assert!(host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("blog.rust-lang.org");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("blog.rust-lang.org").scheme("https");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("crates.io");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("localhost");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn host_without_header() {
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.uri("www.rust-lang.org")
|
||||||
|
.to_srv_request();
|
||||||
|
|
||||||
|
let host = Host("www.rust-lang.org");
|
||||||
|
assert!(host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("www.rust-lang.org").scheme("https");
|
||||||
|
assert!(host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("blog.rust-lang.org");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("blog.rust-lang.org").scheme("https");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("crates.io");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("localhost");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn host_scheme() {
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.insert_header((
|
||||||
|
header::HOST,
|
||||||
|
header::HeaderValue::from_static("https://www.rust-lang.org"),
|
||||||
|
))
|
||||||
|
.to_srv_request();
|
||||||
|
|
||||||
|
let host = Host("www.rust-lang.org").scheme("https");
|
||||||
|
assert!(host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("www.rust-lang.org");
|
||||||
|
assert!(host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("www.rust-lang.org").scheme("http");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("blog.rust-lang.org");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("blog.rust-lang.org").scheme("https");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("crates.io").scheme("https");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let host = Host("localhost");
|
||||||
|
assert!(!host.check(&req.guard_ctx()));
|
||||||
|
}
|
||||||
|
}
|
@ -52,12 +52,15 @@ use std::{
|
|||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead};
|
use actix_http::{header, Extensions, Method as HttpMethod, RequestHead};
|
||||||
|
|
||||||
use crate::{http::header::Header, service::ServiceRequest, HttpMessage as _};
|
use crate::{http::header::Header, service::ServiceRequest, HttpMessage as _};
|
||||||
|
|
||||||
mod acceptable;
|
mod acceptable;
|
||||||
|
mod host;
|
||||||
|
|
||||||
pub use self::acceptable::Acceptable;
|
pub use self::acceptable::Acceptable;
|
||||||
|
pub use self::host::{Host, HostGuard};
|
||||||
|
|
||||||
/// Provides access to request parts that are useful during routing.
|
/// Provides access to request parts that are useful during routing.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -371,124 +374,6 @@ impl Guard for HeaderGuard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a guard that matches requests targetting a specific host.
|
|
||||||
///
|
|
||||||
/// # Matching Host
|
|
||||||
/// This guard will:
|
|
||||||
/// - match against the `Host` header, if present;
|
|
||||||
/// - fall-back to matching against the request target's host, if present;
|
|
||||||
/// - return false if host cannot be determined;
|
|
||||||
///
|
|
||||||
/// # Matching Scheme
|
|
||||||
/// Optionally, this guard can match against the host's scheme. Set the scheme for matching using
|
|
||||||
/// `Host(host).scheme(protocol)`. If the request's scheme cannot be determined, it will not prevent
|
|
||||||
/// the guard from matching successfully.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// The [module-level documentation](self) has an example of virtual hosting using `Host` guards.
|
|
||||||
///
|
|
||||||
/// The example below additionally guards on the host URI's scheme. This could allow routing to
|
|
||||||
/// different handlers for `http:` vs `https:` visitors; to redirect, for example.
|
|
||||||
/// ```
|
|
||||||
/// use actix_web::{web, guard::Host, HttpResponse};
|
|
||||||
///
|
|
||||||
/// web::scope("/admin")
|
|
||||||
/// .guard(Host("admin.rust-lang.org").scheme("https"))
|
|
||||||
/// .default_service(web::to(|| async {
|
|
||||||
/// HttpResponse::Ok().body("admin connection is secure")
|
|
||||||
/// }));
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// The `Host` guard can be used to set up some form of [virtual hosting] within a single app.
|
|
||||||
/// Overlapping scope prefixes are usually discouraged, but when combined with non-overlapping guard
|
|
||||||
/// definitions they become safe to use in this way. Without these host guards, only routes under
|
|
||||||
/// the first-to-be-defined scope would be accessible. You can test this locally using `127.0.0.1`
|
|
||||||
/// and `localhost` as the `Host` guards.
|
|
||||||
/// ```
|
|
||||||
/// use actix_web::{web, http::Method, guard, App, HttpResponse};
|
|
||||||
///
|
|
||||||
/// App::new()
|
|
||||||
/// .service(
|
|
||||||
/// web::scope("")
|
|
||||||
/// .guard(guard::Host("www.rust-lang.org"))
|
|
||||||
/// .default_service(web::to(|| async {
|
|
||||||
/// HttpResponse::Ok().body("marketing site")
|
|
||||||
/// })),
|
|
||||||
/// )
|
|
||||||
/// .service(
|
|
||||||
/// web::scope("")
|
|
||||||
/// .guard(guard::Host("play.rust-lang.org"))
|
|
||||||
/// .default_service(web::to(|| async {
|
|
||||||
/// HttpResponse::Ok().body("playground frontend")
|
|
||||||
/// })),
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// [virtual hosting]: https://en.wikipedia.org/wiki/Virtual_hosting
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub fn Host(host: impl AsRef<str>) -> HostGuard {
|
|
||||||
HostGuard {
|
|
||||||
host: host.as_ref().to_string(),
|
|
||||||
scheme: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_host_uri(req: &RequestHead) -> Option<Uri> {
|
|
||||||
req.headers
|
|
||||||
.get(header::HOST)
|
|
||||||
.and_then(|host_value| host_value.to_str().ok())
|
|
||||||
.or_else(|| req.uri.host())
|
|
||||||
.and_then(|host| host.parse().ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub struct HostGuard {
|
|
||||||
host: String,
|
|
||||||
scheme: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HostGuard {
|
|
||||||
/// Set request scheme to match
|
|
||||||
pub fn scheme<H: AsRef<str>>(mut self, scheme: H) -> HostGuard {
|
|
||||||
self.scheme = Some(scheme.as_ref().to_string());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Guard for HostGuard {
|
|
||||||
fn check(&self, ctx: &GuardContext<'_>) -> bool {
|
|
||||||
// parse host URI from header or request target
|
|
||||||
let req_host_uri = match get_host_uri(ctx.head()) {
|
|
||||||
Some(uri) => uri,
|
|
||||||
|
|
||||||
// no match if host cannot be determined
|
|
||||||
None => return false,
|
|
||||||
};
|
|
||||||
|
|
||||||
match req_host_uri.host() {
|
|
||||||
// fall through to scheme checks
|
|
||||||
Some(uri_host) if self.host == uri_host => {}
|
|
||||||
|
|
||||||
// Either:
|
|
||||||
// - request's host does not match guard's host;
|
|
||||||
// - It was possible that the parsed URI from request target did not contain a host.
|
|
||||||
_ => return false,
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref scheme) = self.scheme {
|
|
||||||
if let Some(ref req_host_uri_scheme) = req_host_uri.scheme_str() {
|
|
||||||
return scheme == req_host_uri_scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: is this the correct behavior?
|
|
||||||
// falls through if scheme cannot be determined
|
|
||||||
}
|
|
||||||
|
|
||||||
// all conditions passed
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use actix_http::{header, Method};
|
use actix_http::{header, Method};
|
||||||
@ -515,90 +400,6 @@ mod tests {
|
|||||||
assert!(!hdr.check(&req.guard_ctx()));
|
assert!(!hdr.check(&req.guard_ctx()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn host_from_header() {
|
|
||||||
let req = TestRequest::default()
|
|
||||||
.insert_header((
|
|
||||||
header::HOST,
|
|
||||||
header::HeaderValue::from_static("www.rust-lang.org"),
|
|
||||||
))
|
|
||||||
.to_srv_request();
|
|
||||||
|
|
||||||
let host = Host("www.rust-lang.org");
|
|
||||||
assert!(host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("www.rust-lang.org").scheme("https");
|
|
||||||
assert!(host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("blog.rust-lang.org");
|
|
||||||
assert!(!host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("blog.rust-lang.org").scheme("https");
|
|
||||||
assert!(!host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("crates.io");
|
|
||||||
assert!(!host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("localhost");
|
|
||||||
assert!(!host.check(&req.guard_ctx()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn host_without_header() {
|
|
||||||
let req = TestRequest::default()
|
|
||||||
.uri("www.rust-lang.org")
|
|
||||||
.to_srv_request();
|
|
||||||
|
|
||||||
let host = Host("www.rust-lang.org");
|
|
||||||
assert!(host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("www.rust-lang.org").scheme("https");
|
|
||||||
assert!(host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("blog.rust-lang.org");
|
|
||||||
assert!(!host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("blog.rust-lang.org").scheme("https");
|
|
||||||
assert!(!host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("crates.io");
|
|
||||||
assert!(!host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("localhost");
|
|
||||||
assert!(!host.check(&req.guard_ctx()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn host_scheme() {
|
|
||||||
let req = TestRequest::default()
|
|
||||||
.insert_header((
|
|
||||||
header::HOST,
|
|
||||||
header::HeaderValue::from_static("https://www.rust-lang.org"),
|
|
||||||
))
|
|
||||||
.to_srv_request();
|
|
||||||
|
|
||||||
let host = Host("www.rust-lang.org").scheme("https");
|
|
||||||
assert!(host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("www.rust-lang.org");
|
|
||||||
assert!(host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("www.rust-lang.org").scheme("http");
|
|
||||||
assert!(!host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("blog.rust-lang.org");
|
|
||||||
assert!(!host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("blog.rust-lang.org").scheme("https");
|
|
||||||
assert!(!host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("crates.io").scheme("https");
|
|
||||||
assert!(!host.check(&req.guard_ctx()));
|
|
||||||
|
|
||||||
let host = Host("localhost");
|
|
||||||
assert!(!host.check(&req.guard_ctx()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn method_guards() {
|
fn method_guards() {
|
||||||
let get_req = TestRequest::get().to_srv_request();
|
let get_req = TestRequest::get().to_srv_request();
|
||||||
|
@ -76,7 +76,6 @@ impl ConnectionInfo {
|
|||||||
for (name, val) in req
|
for (name, val) in req
|
||||||
.headers
|
.headers
|
||||||
.get_all(&header::FORWARDED)
|
.get_all(&header::FORWARDED)
|
||||||
.into_iter()
|
|
||||||
.filter_map(|hdr| hdr.to_str().ok())
|
.filter_map(|hdr| hdr.to_str().ok())
|
||||||
// "for=1.2.3.4, for=5.6.7.8; scheme=https"
|
// "for=1.2.3.4, for=5.6.7.8; scheme=https"
|
||||||
.flat_map(|val| val.split(';'))
|
.flat_map(|val| val.split(';'))
|
||||||
|
Loading…
Reference in New Issue
Block a user