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}