warp/filters/host.rs
1//! Host ("authority") filter
2//!
3use crate::filter::{filter_fn_one, Filter, One};
4use crate::reject::{self, Rejection};
5use futures_util::future;
6pub use http::uri::Authority;
7use std::str::FromStr;
8
9/// Creates a `Filter` that requires a specific authority (target server's
10/// host and port) in the request.
11///
12/// Authority is specified either in the `Host` header or in the target URI.
13///
14/// # Example
15///
16/// ```
17/// use warp::Filter;
18///
19/// let multihost =
20/// warp::host::exact("foo.com").map(|| "you've reached foo.com")
21/// .or(warp::host::exact("bar.com").map(|| "you've reached bar.com"));
22/// ```
23pub fn exact(expected: &str) -> impl Filter<Extract = (), Error = Rejection> + Clone {
24 let expected = Authority::from_str(expected).expect("invalid host/authority");
25 optional()
26 .and_then(move |option: Option<Authority>| match option {
27 Some(authority) if authority == expected => future::ok(()),
28 _ => future::err(reject::not_found()),
29 })
30 .untuple_one()
31}
32
33/// Creates a `Filter` that looks for an authority (target server's host
34/// and port) in the request.
35///
36/// Authority is specified either in the `Host` header or in the target URI.
37///
38/// If found, extracts the `Authority`, otherwise continues the request,
39/// extracting `None`.
40///
41/// Rejects with `400 Bad Request` if the `Host` header is malformed or if there
42/// is a mismatch between the `Host` header and the target URI.
43///
44/// # Example
45///
46/// ```
47/// use warp::{Filter, host::Authority};
48///
49/// let host = warp::host::optional()
50/// .map(|authority: Option<Authority>| {
51/// if let Some(a) = authority {
52/// format!("{} is currently not at home", a.host())
53/// } else {
54/// "please state who you're trying to reach".to_owned()
55/// }
56/// });
57/// ```
58pub fn optional() -> impl Filter<Extract = One<Option<Authority>>, Error = Rejection> + Copy {
59 filter_fn_one(move |route| {
60 // The authority can be sent by clients in various ways:
61 //
62 // 1) in the "target URI"
63 // a) serialized in the start line (HTTP/1.1 proxy requests)
64 // b) serialized in `:authority` pseudo-header (HTTP/2 generated - "SHOULD")
65 // 2) in the `Host` header (HTTP/1.1 origin requests, HTTP/2 converted)
66 //
67 // Hyper transparently handles 1a/1b, but not 2, so we must look at both.
68
69 let from_uri = route.uri().authority();
70
71 let name = "host";
72 let from_header = route.headers()
73 .get(name)
74 .map(|value|
75 // Header present, parse it
76 value.to_str().map_err(|_| reject::invalid_header(name))
77 .and_then(|value| Authority::from_str(value).map_err(|_| reject::invalid_header(name)))
78 );
79
80 future::ready(match (from_uri, from_header) {
81 // no authority in the request (HTTP/1.0 or non-conforming)
82 (None, None) => Ok(None),
83
84 // authority specified in either or both matching
85 (Some(a), None) => Ok(Some(a.clone())),
86 (None, Some(Ok(a))) => Ok(Some(a)),
87 (Some(a), Some(Ok(b))) if *a == b => Ok(Some(b)),
88
89 // mismatch
90 (Some(_), Some(Ok(_))) => Err(reject::invalid_header(name)),
91
92 // parse error
93 (_, Some(Err(r))) => Err(r),
94 })
95 })
96}