1 use std::fmt;
2
3 use header::{
4 HeaderMap,
5 AUTHORIZATION,
6 COOKIE,
7 PROXY_AUTHORIZATION,
8 WWW_AUTHENTICATE,
9
10 };
11 use hyper::StatusCode;
12
13 use Url;
14
15 /// A type that controls the policy on how to handle the following of redirects.
16 ///
17 /// The default value will catch redirect loops, and has a maximum of 10
18 /// redirects it will follow in a chain before returning an error.
19 ///
20 /// - `limited` can be used have the same as the default behavior, but adjust
21 /// the allowed maximum redirect hops in a chain.
22 /// - `none` can be used to disable all redirect behavior.
23 /// - `custom` can be used to create a customized policy.
24 #[derive(Debug)]
25 pub struct RedirectPolicy {
26 inner: Policy,
27 }
28
29 /// A type that holds information on the next request and previous requests
30 /// in redirect chain.
31 #[derive(Debug)]
32 pub struct RedirectAttempt<'a> {
33 status: StatusCode,
34 next: &'a Url,
35 previous: &'a [Url],
36 }
37
38 /// An action to perform when a redirect status code is found.
39 #[derive(Debug)]
40 pub struct RedirectAction {
41 inner: Action,
42 }
43
44 impl RedirectPolicy {
45 /// Create a RedirectPolicy with a maximum number of redirects.
46 ///
47 /// An `Error` will be returned if the max is reached.
limited(max: usize) -> RedirectPolicy48 pub fn limited(max: usize) -> RedirectPolicy {
49 RedirectPolicy {
50 inner: Policy::Limit(max),
51 }
52 }
53
54 /// Create a RedirectPolicy that does not follow any redirect.
none() -> RedirectPolicy55 pub fn none() -> RedirectPolicy {
56 RedirectPolicy {
57 inner: Policy::None,
58 }
59 }
60
61 /// Create a custom RedirectPolicy using the passed function.
62 ///
63 /// # Note
64 ///
65 /// The default RedirectPolicy handles redirect loops and a maximum loop
66 /// chain, but the custom variant does not do that for you automatically.
67 /// The custom policy should have some way of handling those.
68 ///
69 /// Information on the next request and previous requests can be found
70 /// on the [`RedirectAttempt`] argument passed to the closure.
71 ///
72 /// Actions can be conveniently created from methods on the
73 /// [`RedirectAttempt`].
74 ///
75 /// # Example
76 ///
77 /// ```rust
78 /// # use reqwest::{Error, RedirectPolicy};
79 /// #
80 /// # fn run() -> Result<(), Error> {
81 /// let custom = RedirectPolicy::custom(|attempt| {
82 /// if attempt.previous().len() > 5 {
83 /// attempt.too_many_redirects()
84 /// } else if attempt.url().host_str() == Some("example.domain") {
85 /// // prevent redirects to 'example.domain'
86 /// attempt.stop()
87 /// } else {
88 /// attempt.follow()
89 /// }
90 /// });
91 /// let client = reqwest::Client::builder()
92 /// .redirect(custom)
93 /// .build()?;
94 /// # Ok(())
95 /// # }
96 /// ```
97 ///
98 /// [`RedirectAttempt`]: struct.RedirectAttempt.html
custom<T>(policy: T) -> RedirectPolicy where T: Fn(RedirectAttempt) -> RedirectAction + Send + Sync + 'static,99 pub fn custom<T>(policy: T) -> RedirectPolicy
100 where
101 T: Fn(RedirectAttempt) -> RedirectAction + Send + Sync + 'static,
102 {
103 RedirectPolicy {
104 inner: Policy::Custom(Box::new(policy)),
105 }
106 }
107
108 /// Apply this policy to a given [`RedirectAttempt`] to produce a [`RedirectAction`].
109 ///
110 /// # Note
111 ///
112 /// This method can be used together with RedirectPolicy::custom()
113 /// to construct one RedirectPolicy that wraps another.
114 ///
115 /// # Example
116 ///
117 /// ```rust
118 /// # use reqwest::{Error, RedirectPolicy};
119 /// #
120 /// # fn run() -> Result<(), Error> {
121 /// let custom = RedirectPolicy::custom(|attempt| {
122 /// eprintln!("{}, Location: {:?}", attempt.status(), attempt.url());
123 /// RedirectPolicy::default().redirect(attempt)
124 /// });
125 /// # Ok(())
126 /// # }
127 /// ```
redirect(&self, attempt: RedirectAttempt) -> RedirectAction128 pub fn redirect(&self, attempt: RedirectAttempt) -> RedirectAction {
129 match self.inner {
130 Policy::Custom(ref custom) => custom(attempt),
131 Policy::Limit(max) => {
132 if attempt.previous.len() == max {
133 attempt.too_many_redirects()
134 } else if attempt.previous.contains(attempt.next) {
135 attempt.loop_detected()
136 } else {
137 attempt.follow()
138 }
139 }
140 Policy::None => attempt.stop(),
141 }
142 }
143
check( &self, status: StatusCode, next: &Url, previous: &[Url], ) -> Action144 pub(crate) fn check(
145 &self,
146 status: StatusCode,
147 next: &Url,
148 previous: &[Url],
149 ) -> Action {
150 self
151 .redirect(RedirectAttempt {
152 status: status,
153 next: next,
154 previous: previous,
155 })
156 .inner
157 }
158 }
159
160 impl Default for RedirectPolicy {
default() -> RedirectPolicy161 fn default() -> RedirectPolicy {
162 RedirectPolicy::limited(10)
163 }
164 }
165
166 impl<'a> RedirectAttempt<'a> {
167 /// Get the type of redirect.
status(&self) -> StatusCode168 pub fn status(&self) -> StatusCode {
169 self.status
170 }
171
172 /// Get the next URL to redirect to.
url(&self) -> &Url173 pub fn url(&self) -> &Url {
174 self.next
175 }
176
177 /// Get the list of previous URLs that have already been requested in this chain.
previous(&self) -> &[Url]178 pub fn previous(&self) -> &[Url] {
179 self.previous
180 }
181 /// Returns an action meaning reqwest should follow the next URL.
follow(self) -> RedirectAction182 pub fn follow(self) -> RedirectAction {
183 RedirectAction {
184 inner: Action::Follow,
185 }
186 }
187
188 /// Returns an action meaning reqwest should not follow the next URL.
189 ///
190 /// The 30x response will be returned as the `Ok` result.
stop(self) -> RedirectAction191 pub fn stop(self) -> RedirectAction {
192 RedirectAction {
193 inner: Action::Stop,
194 }
195 }
196
197 /// Returns an action meaning there was a loop of redirects found.
198 ///
199 /// An `Error` will be returned for the result of the sent request.
loop_detected(self) -> RedirectAction200 pub fn loop_detected(self) -> RedirectAction {
201 RedirectAction {
202 inner: Action::LoopDetected,
203 }
204 }
205
206 /// Returns an action meaning there was a loop of redirects found.
207 ///
208 /// An `Error` will be returned for the result of the sent request.
too_many_redirects(self) -> RedirectAction209 pub fn too_many_redirects(self) -> RedirectAction {
210 RedirectAction {
211 inner: Action::TooManyRedirects,
212 }
213 }
214 }
215
216 enum Policy {
217 Custom(Box<dyn Fn(RedirectAttempt) -> RedirectAction + Send + Sync + 'static>),
218 Limit(usize),
219 None,
220 }
221
222 impl fmt::Debug for Policy {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result223 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
224 match *self {
225 Policy::Custom(..) => f.pad("Custom"),
226 Policy::Limit(max) => f.debug_tuple("Limit").field(&max).finish(),
227 Policy::None => f.pad("None"),
228 }
229 }
230 }
231
232 // pub(crate)
233
234 #[derive(Debug, PartialEq)]
235 pub(crate) enum Action {
236 Follow,
237 Stop,
238 LoopDetected,
239 TooManyRedirects,
240 }
241
242
remove_sensitive_headers(headers: &mut HeaderMap, next: &Url, previous: &[Url])243 pub(crate) fn remove_sensitive_headers(headers: &mut HeaderMap, next: &Url, previous: &[Url]) {
244 if let Some(previous) = previous.last() {
245 let cross_host = next.host_str() != previous.host_str() ||
246 next.port_or_known_default() != previous.port_or_known_default();
247 if cross_host {
248 headers.remove(AUTHORIZATION);
249 headers.remove(COOKIE);
250 headers.remove("cookie2");
251 headers.remove(PROXY_AUTHORIZATION);
252 headers.remove(WWW_AUTHENTICATE);
253 }
254 }
255 }
256
257 /*
258 This was the desired way of doing it, but ran in to inference issues when
259 using closures, since the arguments received are references (&Url and &[Url]),
260 and the compiler could not infer the lifetimes of those references. That means
261 people would need to annotate the closure's argument types, which is garbase.
262
263 pub trait Redirect {
264 fn redirect(&self, next: &Url, previous: &[Url]) -> ::Result<bool>;
265 }
266
267 impl<F> Redirect for F
268 where F: Fn(&Url, &[Url]) -> ::Result<bool> {
269 fn redirect(&self, next: &Url, previous: &[Url]) -> ::Result<bool> {
270 self(next, previous)
271 }
272 }
273 */
274
275 #[test]
test_redirect_policy_limit()276 fn test_redirect_policy_limit() {
277 let policy = RedirectPolicy::default();
278 let next = Url::parse("http://x.y/z").unwrap();
279 let mut previous = (0..9)
280 .map(|i| Url::parse(&format!("http://a.b/c/{}", i)).unwrap())
281 .collect::<Vec<_>>();
282
283 assert_eq!(
284 policy.check(StatusCode::FOUND, &next, &previous),
285 Action::Follow
286 );
287
288 previous.push(Url::parse("http://a.b.d/e/33").unwrap());
289
290 assert_eq!(
291 policy.check(StatusCode::FOUND, &next, &previous),
292 Action::TooManyRedirects
293 );
294 }
295
296 #[test]
test_redirect_policy_custom()297 fn test_redirect_policy_custom() {
298 let policy = RedirectPolicy::custom(|attempt| {
299 if attempt.url().host_str() == Some("foo") {
300 attempt.stop()
301 } else {
302 attempt.follow()
303 }
304 });
305
306 let next = Url::parse("http://bar/baz").unwrap();
307 assert_eq!(
308 policy.check(StatusCode::FOUND, &next, &[]),
309 Action::Follow
310 );
311
312 let next = Url::parse("http://foo/baz").unwrap();
313 assert_eq!(
314 policy.check(StatusCode::FOUND, &next, &[]),
315 Action::Stop
316 );
317 }
318
319 #[test]
test_remove_sensitive_headers()320 fn test_remove_sensitive_headers() {
321 use hyper::header::{ACCEPT, AUTHORIZATION, COOKIE, HeaderValue};
322
323 let mut headers = HeaderMap::new();
324 headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
325 headers.insert(AUTHORIZATION, HeaderValue::from_static("let me in"));
326 headers.insert(COOKIE, HeaderValue::from_static("foo=bar"));
327
328 let next = Url::parse("http://initial-domain.com/path").unwrap();
329 let mut prev = vec![Url::parse("http://initial-domain.com/new_path").unwrap()];
330 let mut filtered_headers = headers.clone();
331
332 remove_sensitive_headers(&mut headers, &next, &prev);
333 assert_eq!(headers, filtered_headers);
334
335 prev.push(Url::parse("http://new-domain.com/path").unwrap());
336 filtered_headers.remove(AUTHORIZATION);
337 filtered_headers.remove(COOKIE);
338
339 remove_sensitive_headers(&mut headers, &next, &prev);
340 assert_eq!(headers, filtered_headers);
341 }
342