1 #![cfg(not(target_arch = "wasm32"))]
2 mod support;
3 use futures_util::stream::StreamExt;
4 use support::*;
5 
6 #[tokio::test]
test_redirect_301_and_302_and_303_changes_post_to_get()7 async fn test_redirect_301_and_302_and_303_changes_post_to_get() {
8     let client = reqwest::Client::new();
9     let codes = [301u16, 302, 303];
10 
11     for &code in codes.iter() {
12         let redirect = server::http(move |req| async move {
13             if req.method() == "POST" {
14                 assert_eq!(req.uri(), &*format!("/{}", code));
15                 http::Response::builder()
16                     .status(code)
17                     .header("location", "/dst")
18                     .header("server", "test-redirect")
19                     .body(Default::default())
20                     .unwrap()
21             } else {
22                 assert_eq!(req.method(), "GET");
23 
24                 http::Response::builder()
25                     .header("server", "test-dst")
26                     .body(Default::default())
27                     .unwrap()
28             }
29         });
30 
31         let url = format!("http://{}/{}", redirect.addr(), code);
32         let dst = format!("http://{}/{}", redirect.addr(), "dst");
33         let res = client.post(&url).send().await.unwrap();
34         assert_eq!(res.url().as_str(), dst);
35         assert_eq!(res.status(), reqwest::StatusCode::OK);
36         assert_eq!(
37             res.headers().get(reqwest::header::SERVER).unwrap(),
38             &"test-dst"
39         );
40     }
41 }
42 
43 #[tokio::test]
test_redirect_307_and_308_tries_to_get_again()44 async fn test_redirect_307_and_308_tries_to_get_again() {
45     let client = reqwest::Client::new();
46     let codes = [307u16, 308];
47     for &code in codes.iter() {
48         let redirect = server::http(move |req| async move {
49             assert_eq!(req.method(), "GET");
50             if req.uri() == &*format!("/{}", code) {
51                 http::Response::builder()
52                     .status(code)
53                     .header("location", "/dst")
54                     .header("server", "test-redirect")
55                     .body(Default::default())
56                     .unwrap()
57             } else {
58                 assert_eq!(req.uri(), "/dst");
59 
60                 http::Response::builder()
61                     .header("server", "test-dst")
62                     .body(Default::default())
63                     .unwrap()
64             }
65         });
66 
67         let url = format!("http://{}/{}", redirect.addr(), code);
68         let dst = format!("http://{}/{}", redirect.addr(), "dst");
69         let res = client.get(&url).send().await.unwrap();
70         assert_eq!(res.url().as_str(), dst);
71         assert_eq!(res.status(), reqwest::StatusCode::OK);
72         assert_eq!(
73             res.headers().get(reqwest::header::SERVER).unwrap(),
74             &"test-dst"
75         );
76     }
77 }
78 
79 #[tokio::test]
test_redirect_307_and_308_tries_to_post_again()80 async fn test_redirect_307_and_308_tries_to_post_again() {
81     let _ = env_logger::try_init();
82     let client = reqwest::Client::new();
83     let codes = [307u16, 308];
84     for &code in codes.iter() {
85         let redirect = server::http(move |mut req| async move {
86             assert_eq!(req.method(), "POST");
87             assert_eq!(req.headers()["content-length"], "5");
88 
89             let data = req.body_mut().next().await.unwrap().unwrap();
90             assert_eq!(&*data, b"Hello");
91 
92             if req.uri() == &*format!("/{}", code) {
93                 http::Response::builder()
94                     .status(code)
95                     .header("location", "/dst")
96                     .header("server", "test-redirect")
97                     .body(Default::default())
98                     .unwrap()
99             } else {
100                 assert_eq!(req.uri(), "/dst");
101 
102                 http::Response::builder()
103                     .header("server", "test-dst")
104                     .body(Default::default())
105                     .unwrap()
106             }
107         });
108 
109         let url = format!("http://{}/{}", redirect.addr(), code);
110         let dst = format!("http://{}/{}", redirect.addr(), "dst");
111         let res = client.post(&url).body("Hello").send().await.unwrap();
112         assert_eq!(res.url().as_str(), dst);
113         assert_eq!(res.status(), reqwest::StatusCode::OK);
114         assert_eq!(
115             res.headers().get(reqwest::header::SERVER).unwrap(),
116             &"test-dst"
117         );
118     }
119 }
120 
121 #[cfg(feature = "blocking")]
122 #[test]
test_redirect_307_does_not_try_if_reader_cannot_reset()123 fn test_redirect_307_does_not_try_if_reader_cannot_reset() {
124     let client = reqwest::blocking::Client::new();
125     let codes = [307u16, 308];
126     for &code in codes.iter() {
127         let redirect = server::http(move |mut req| async move {
128             assert_eq!(req.method(), "POST");
129             assert_eq!(req.uri(), &*format!("/{}", code));
130             assert_eq!(req.headers()["transfer-encoding"], "chunked");
131 
132             let data = req.body_mut().next().await.unwrap().unwrap();
133             assert_eq!(&*data, b"Hello");
134 
135             http::Response::builder()
136                 .status(code)
137                 .header("location", "/dst")
138                 .header("server", "test-redirect")
139                 .body(Default::default())
140                 .unwrap()
141         });
142 
143         let url = format!("http://{}/{}", redirect.addr(), code);
144         let res = client
145             .post(&url)
146             .body(reqwest::blocking::Body::new(&b"Hello"[..]))
147             .send()
148             .unwrap();
149         assert_eq!(res.url().as_str(), url);
150         assert_eq!(res.status(), code);
151     }
152 }
153 
154 #[tokio::test]
test_redirect_removes_sensitive_headers()155 async fn test_redirect_removes_sensitive_headers() {
156     use tokio::sync::watch;
157 
158     let (tx, rx) = watch::channel::<Option<std::net::SocketAddr>>(None);
159 
160     let end_server = server::http(move |req| {
161         let mut rx = rx.clone();
162         async move {
163             assert_eq!(req.headers().get("cookie"), None);
164 
165             rx.changed().await.unwrap();
166             let mid_addr = rx.borrow().unwrap();
167             assert_eq!(
168                 req.headers()["referer"],
169                 format!("http://{}/sensitive", mid_addr)
170             );
171             http::Response::default()
172         }
173     });
174 
175     let end_addr = end_server.addr();
176 
177     let mid_server = server::http(move |req| async move {
178         assert_eq!(req.headers()["cookie"], "foo=bar");
179         http::Response::builder()
180             .status(302)
181             .header("location", format!("http://{}/end", end_addr))
182             .body(Default::default())
183             .unwrap()
184     });
185 
186     tx.send(Some(mid_server.addr())).unwrap();
187 
188     reqwest::Client::builder()
189         .build()
190         .unwrap()
191         .get(&format!("http://{}/sensitive", mid_server.addr()))
192         .header(
193             reqwest::header::COOKIE,
194             reqwest::header::HeaderValue::from_static("foo=bar"),
195         )
196         .send()
197         .await
198         .unwrap();
199 }
200 
201 #[tokio::test]
test_redirect_policy_can_return_errors()202 async fn test_redirect_policy_can_return_errors() {
203     let server = server::http(move |req| async move {
204         assert_eq!(req.uri(), "/loop");
205         http::Response::builder()
206             .status(302)
207             .header("location", "/loop")
208             .body(Default::default())
209             .unwrap()
210     });
211 
212     let url = format!("http://{}/loop", server.addr());
213     let err = reqwest::get(&url).await.unwrap_err();
214     assert!(err.is_redirect());
215 }
216 
217 #[tokio::test]
test_redirect_policy_can_stop_redirects_without_an_error()218 async fn test_redirect_policy_can_stop_redirects_without_an_error() {
219     let server = server::http(move |req| async move {
220         assert_eq!(req.uri(), "/no-redirect");
221         http::Response::builder()
222             .status(302)
223             .header("location", "/dont")
224             .body(Default::default())
225             .unwrap()
226     });
227 
228     let url = format!("http://{}/no-redirect", server.addr());
229 
230     let res = reqwest::Client::builder()
231         .redirect(reqwest::redirect::Policy::none())
232         .build()
233         .unwrap()
234         .get(&url)
235         .send()
236         .await
237         .unwrap();
238 
239     assert_eq!(res.url().as_str(), url);
240     assert_eq!(res.status(), reqwest::StatusCode::FOUND);
241 }
242 
243 #[tokio::test]
test_referer_is_not_set_if_disabled()244 async fn test_referer_is_not_set_if_disabled() {
245     let server = server::http(move |req| async move {
246         if req.uri() == "/no-refer" {
247             http::Response::builder()
248                 .status(302)
249                 .header("location", "/dst")
250                 .body(Default::default())
251                 .unwrap()
252         } else {
253             assert_eq!(req.uri(), "/dst");
254             assert_eq!(req.headers().get("referer"), None);
255 
256             http::Response::default()
257         }
258     });
259 
260     reqwest::Client::builder()
261         .referer(false)
262         .build()
263         .unwrap()
264         .get(&format!("http://{}/no-refer", server.addr()))
265         .send()
266         .await
267         .unwrap();
268 }
269 
270 #[tokio::test]
test_invalid_location_stops_redirect_gh484()271 async fn test_invalid_location_stops_redirect_gh484() {
272     let server = server::http(move |_req| async move {
273         http::Response::builder()
274             .status(302)
275             .header("location", "http://www.yikes{KABOOM}")
276             .body(Default::default())
277             .unwrap()
278     });
279 
280     let url = format!("http://{}/yikes", server.addr());
281 
282     let res = reqwest::get(&url).await.unwrap();
283 
284     assert_eq!(res.url().as_str(), url);
285     assert_eq!(res.status(), reqwest::StatusCode::FOUND);
286 }
287 
288 #[cfg(feature = "cookies")]
289 #[tokio::test]
test_redirect_302_with_set_cookies()290 async fn test_redirect_302_with_set_cookies() {
291     let code = 302;
292     let server = server::http(move |req| async move {
293         if req.uri() == "/302" {
294             http::Response::builder()
295                 .status(302)
296                 .header("location", "/dst")
297                 .header("set-cookie", "key=value")
298                 .body(Default::default())
299                 .unwrap()
300         } else {
301             assert_eq!(req.uri(), "/dst");
302             assert_eq!(req.headers()["cookie"], "key=value");
303             http::Response::default()
304         }
305     });
306 
307     let url = format!("http://{}/{}", server.addr(), code);
308     let dst = format!("http://{}/{}", server.addr(), "dst");
309 
310     let client = reqwest::ClientBuilder::new()
311         .cookie_store(true)
312         .build()
313         .unwrap();
314     let res = client.get(&url).send().await.unwrap();
315 
316     assert_eq!(res.url().as_str(), dst);
317     assert_eq!(res.status(), reqwest::StatusCode::OK);
318 }
319