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