warp/filters/
header.rs

1//! Header Filters
2//!
3//! These filters are used to interact with the Request HTTP headers. Some
4//! of them, like `exact` and `exact_ignore_case`, are just predicates,
5//! they don't extract any values. The `header` filter allows parsing
6//! a type from any header.
7use std::convert::Infallible;
8use std::str::FromStr;
9
10use futures_util::future;
11use headers::{Header, HeaderMapExt};
12use http::header::HeaderValue;
13use http::HeaderMap;
14
15use crate::filter::{filter_fn, filter_fn_one, Filter, One};
16use crate::reject::{self, Rejection};
17
18/// Create a `Filter` that tries to parse the specified header.
19///
20/// This `Filter` will look for a header with supplied name, and try to
21/// parse to a `T`, otherwise rejects the request.
22///
23/// # Example
24///
25/// ```
26/// use std::net::SocketAddr;
27///
28/// // Parse `content-length: 100` as a `u64`
29/// let content_length = warp::header::<u64>("content-length");
30///
31/// // Parse `host: 127.0.0.1:8080` as a `SocketAddr
32/// let local_host = warp::header::<SocketAddr>("host");
33///
34/// // Parse `foo: bar` into a `String`
35/// let foo = warp::header::<String>("foo");
36/// ```
37pub fn header<T: FromStr + Send + 'static>(
38    name: &'static str,
39) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy {
40    filter_fn_one(move |route| {
41        tracing::trace!("header({:?})", name);
42        let route = route
43            .headers()
44            .get(name)
45            .ok_or_else(|| reject::missing_header(name))
46            .and_then(|value| value.to_str().map_err(|_| reject::invalid_header(name)))
47            .and_then(|s| T::from_str(s).map_err(|_| reject::invalid_header(name)));
48        future::ready(route)
49    })
50}
51
52pub(crate) fn header2<T: Header + Send + 'static>(
53) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy {
54    filter_fn_one(move |route| {
55        tracing::trace!("header2({:?})", T::name());
56        let route = route
57            .headers()
58            .typed_get()
59            .ok_or_else(|| reject::invalid_header(T::name().as_str()));
60        future::ready(route)
61    })
62}
63
64/// Create a `Filter` that tries to parse the specified header, if it exists.
65///
66/// If the header does not exist, it yields `None`. Otherwise, it will try to
67/// parse as a `T`, and if it fails, a invalid header rejection is return. If
68/// successful, the filter yields `Some(T)`.
69///
70/// # Example
71///
72/// ```
73/// // Grab the `authorization` header if it exists.
74/// let opt_auth = warp::header::optional::<String>("authorization");
75/// ```
76pub fn optional<T>(
77    name: &'static str,
78) -> impl Filter<Extract = One<Option<T>>, Error = Rejection> + Copy
79where
80    T: FromStr + Send + 'static,
81{
82    filter_fn_one(move |route| {
83        tracing::trace!("optional({:?})", name);
84        let result = route.headers().get(name).map(|value| {
85            value
86                .to_str()
87                .map_err(|_| reject::invalid_header(name))?
88                .parse::<T>()
89                .map_err(|_| reject::invalid_header(name))
90        });
91
92        match result {
93            Some(Ok(t)) => future::ok(Some(t)),
94            Some(Err(e)) => future::err(e),
95            None => future::ok(None),
96        }
97    })
98}
99
100pub(crate) fn optional2<T>() -> impl Filter<Extract = One<Option<T>>, Error = Infallible> + Copy
101where
102    T: Header + Send + 'static,
103{
104    filter_fn_one(move |route| future::ready(Ok(route.headers().typed_get())))
105}
106
107/* TODO
108pub fn exact2<T>(header: T) -> impl FilterClone<Extract=(), Error=Rejection>
109where
110    T: Header + PartialEq + Clone + Send,
111{
112    filter_fn(move |route| {
113        tracing::trace!("exact2({:?})", T::NAME);
114        route.headers()
115            .typed_get::<T>()
116            .and_then(|val| if val == header {
117                Some(())
118            } else {
119                None
120            })
121            .ok_or_else(|| reject::bad_request())
122    })
123}
124*/
125
126/// Create a `Filter` that requires a header to match the value exactly.
127///
128/// This `Filter` will look for a header with supplied name and the exact
129/// value, otherwise rejects the request.
130///
131/// # Example
132///
133/// ```
134/// // Require `dnt: 1` header to be set.
135/// let must_dnt = warp::header::exact("dnt", "1");
136/// ```
137pub fn exact(
138    name: &'static str,
139    value: &'static str,
140) -> impl Filter<Extract = (), Error = Rejection> + Copy {
141    filter_fn(move |route| {
142        tracing::trace!("exact?({:?}, {:?})", name, value);
143        let route = route
144            .headers()
145            .get(name)
146            .ok_or_else(|| reject::missing_header(name))
147            .and_then(|val| {
148                if val == value {
149                    Ok(())
150                } else {
151                    Err(reject::invalid_header(name))
152                }
153            });
154        future::ready(route)
155    })
156}
157
158/// Create a `Filter` that requires a header to match the value exactly.
159///
160/// This `Filter` will look for a header with supplied name and the exact
161/// value, ignoring ASCII case, otherwise rejects the request.
162///
163/// # Example
164///
165/// ```
166/// // Require `connection: keep-alive` header to be set.
167/// let keep_alive = warp::header::exact_ignore_case("connection", "keep-alive");
168/// ```
169pub fn exact_ignore_case(
170    name: &'static str,
171    value: &'static str,
172) -> impl Filter<Extract = (), Error = Rejection> + Copy {
173    filter_fn(move |route| {
174        tracing::trace!("exact_ignore_case({:?}, {:?})", name, value);
175        let route = route
176            .headers()
177            .get(name)
178            .ok_or_else(|| reject::missing_header(name))
179            .and_then(|val| {
180                if val.as_bytes().eq_ignore_ascii_case(value.as_bytes()) {
181                    Ok(())
182                } else {
183                    Err(reject::invalid_header(name))
184                }
185            });
186        future::ready(route)
187    })
188}
189
190/// Create a `Filter` that gets a `HeaderValue` for the name.
191///
192/// # Example
193///
194/// ```
195/// use warp::{Filter, http::header::HeaderValue};
196///
197/// let filter = warp::header::value("x-token")
198///     .map(|value: HeaderValue| {
199///         format!("header value bytes: {:?}", value)
200///     });
201/// ```
202pub fn value(
203    name: &'static str,
204) -> impl Filter<Extract = One<HeaderValue>, Error = Rejection> + Copy {
205    filter_fn_one(move |route| {
206        tracing::trace!("value({:?})", name);
207        let route = route
208            .headers()
209            .get(name)
210            .cloned()
211            .ok_or_else(|| reject::missing_header(name));
212        future::ready(route)
213    })
214}
215
216/// Create a `Filter` that returns a clone of the request's `HeaderMap`.
217///
218/// # Example
219///
220/// ```
221/// use warp::{Filter, http::HeaderMap};
222///
223/// let headers = warp::header::headers_cloned()
224///     .map(|headers: HeaderMap| {
225///         format!("header count: {}", headers.len())
226///     });
227/// ```
228pub fn headers_cloned() -> impl Filter<Extract = One<HeaderMap>, Error = Infallible> + Copy {
229    filter_fn_one(|route| future::ok(route.headers().clone()))
230}