• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2023 Huawei Device Co., Ltd.
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 //     http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 use ylong_http::headers::Headers;
15 use ylong_http::request::method::Method;
16 use ylong_http::request::uri::Uri;
17 use ylong_http::request::Request;
18 use ylong_http::response::status::StatusCode;
19 use ylong_http::response::Response;
20 
21 use crate::error::{ErrorKind, HttpClientError};
22 use crate::util;
23 
24 /// Redirect strategy supports limited times of redirection and no redirect
25 #[derive(Clone, Debug, Eq, PartialEq)]
26 pub struct RedirectStrategy {
27     inner: StrategyKind,
28 }
29 
30 #[derive(Clone, Debug, Eq, PartialEq)]
31 enum StrategyKind {
32     LimitTimes(usize),
33     NoRedirect,
34 }
35 
36 /// Redirect status supports to check response status and next
37 /// redirected uri
38 #[derive(Clone)]
39 pub struct RedirectStatus<'a> {
40     previous_uri: &'a [Uri],
41 }
42 
43 #[derive(Clone, Debug, Eq, PartialEq)]
44 pub(crate) struct Trigger {
45     inner: TriggerKind,
46 }
47 
48 #[derive(Clone, Debug, Eq, PartialEq)]
49 pub(crate) enum TriggerKind {
50     NextLink,
51     Stop,
52 }
53 
54 impl RedirectStrategy {
limited(max: usize) -> Self55     pub(crate) fn limited(max: usize) -> Self {
56         Self {
57             inner: StrategyKind::LimitTimes(max),
58         }
59     }
60 
none() -> Self61     pub(crate) fn none() -> Self {
62         Self {
63             inner: StrategyKind::NoRedirect,
64         }
65     }
66 
redirect(&self, status: RedirectStatus) -> Result<Trigger, HttpClientError>67     pub(crate) fn redirect(&self, status: RedirectStatus) -> Result<Trigger, HttpClientError> {
68         match self.inner {
69             StrategyKind::LimitTimes(max) => {
70                 if status.previous_uri.len() >= max {
71                     Err(HttpClientError::new_with_message(
72                         ErrorKind::Build,
73                         "Over redirect max limit",
74                     ))
75                 } else {
76                     Ok(status.transfer())
77                 }
78             }
79             StrategyKind::NoRedirect => Ok(status.stop()),
80         }
81     }
82 
get_trigger( &self, redirect_status: RedirectStatus, ) -> Result<TriggerKind, HttpClientError>83     pub(crate) fn get_trigger(
84         &self,
85         redirect_status: RedirectStatus,
86     ) -> Result<TriggerKind, HttpClientError> {
87         let trigger = self.redirect(redirect_status)?;
88         Ok(trigger.inner)
89     }
90 }
91 
92 impl Default for RedirectStrategy {
default() -> RedirectStrategy93     fn default() -> RedirectStrategy {
94         RedirectStrategy::limited(10)
95     }
96 }
97 
98 impl<'a> RedirectStatus<'a> {
new(previous_uri: &'a [Uri]) -> Self99     pub(crate) fn new(previous_uri: &'a [Uri]) -> Self {
100         Self { previous_uri }
101     }
102 
transfer(self) -> Trigger103     fn transfer(self) -> Trigger {
104         Trigger {
105             inner: TriggerKind::NextLink,
106         }
107     }
108 
stop(self) -> Trigger109     fn stop(self) -> Trigger {
110         Trigger {
111             inner: TriggerKind::Stop,
112         }
113     }
114 }
115 
116 pub(crate) struct Redirect;
117 
118 impl Redirect {
get_trigger_kind<T, K>( dst_uri: &mut Uri, redirect: &util::Redirect, redirect_list: &[Uri], response: &Response<K>, request: &mut Request<T>, ) -> Result<TriggerKind, HttpClientError>119     pub(crate) fn get_trigger_kind<T, K>(
120         dst_uri: &mut Uri,
121         redirect: &util::Redirect,
122         redirect_list: &[Uri],
123         response: &Response<K>,
124         request: &mut Request<T>,
125     ) -> Result<TriggerKind, HttpClientError> {
126         let location = match response.headers().get("location") {
127             Some(value) => value,
128             None => {
129                 return Err(HttpClientError::new_with_message(
130                     ErrorKind::Redirect,
131                     "No location in response's headers",
132                 ));
133             }
134         };
135 
136         let loc_str = location.to_str().unwrap();
137         let loc_bytes = loc_str.as_str().trim_start_matches('/').as_bytes();
138 
139         let mut loc_uri = Uri::from_bytes(loc_bytes)
140             .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Redirect, Some(e)))?;
141         if loc_uri.scheme().is_none() || loc_uri.authority().is_none() {
142             // request uri is existed, so can use unwrap directly
143             let origin_scheme = request
144                 .uri()
145                 .scheme()
146                 .ok_or_else(|| {
147                     HttpClientError::new_with_message(
148                         ErrorKind::Connect,
149                         "No uri scheme in request",
150                     )
151                 })?
152                 .as_str();
153             let auth = request
154                 .uri()
155                 .authority()
156                 .ok_or_else(|| {
157                     HttpClientError::new_with_message(
158                         ErrorKind::Connect,
159                         "No uri authority in request",
160                     )
161                 })?
162                 .to_str();
163             let origin_auth = auth.as_str();
164             loc_uri = Uri::builder()
165                 .scheme(origin_scheme)
166                 .authority(origin_auth)
167                 // loc_uri is existed, so can use unwrap directly
168                 .path(
169                     loc_uri
170                         .path()
171                         .ok_or_else(|| {
172                             HttpClientError::new_with_message(
173                                 ErrorKind::Connect,
174                                 "No loc_uri path in location",
175                             )
176                         })?
177                         .as_str(),
178                 )
179                 .query(
180                     loc_uri
181                         .query()
182                         .ok_or_else(|| {
183                             HttpClientError::new_with_message(
184                                 ErrorKind::Connect,
185                                 "No loc_uri query in location",
186                             )
187                         })?
188                         .as_str(),
189                 )
190                 .build()
191                 .unwrap();
192         }
193 
194         let redirect_status = RedirectStatus::new(redirect_list);
195         let trigger = redirect
196             .redirect_strategy()
197             .unwrap()
198             .get_trigger(redirect_status)?;
199 
200         match trigger {
201             TriggerKind::NextLink => {
202                 Self::remove_sensitive_headers(request.headers_mut(), &loc_uri, redirect_list);
203                 *dst_uri = loc_uri.clone();
204                 *request.uri_mut() = loc_uri;
205                 Ok(TriggerKind::NextLink)
206             }
207             TriggerKind::Stop => Ok(TriggerKind::Stop),
208         }
209     }
210 
remove_sensitive_headers(headers: &mut Headers, next: &Uri, previous: &[Uri])211     fn remove_sensitive_headers(headers: &mut Headers, next: &Uri, previous: &[Uri]) {
212         if let Some(previous) = previous.last() {
213             // TODO: Check this logic.
214             let cross_host = next.authority().unwrap() != previous.authority().unwrap();
215             if cross_host {
216                 let _ = headers.remove("authorization");
217                 let _ = headers.remove("cookie");
218                 let _ = headers.remove("cookie2");
219                 let _ = headers.remove("proxy_authorization");
220                 let _ = headers.remove("www_authenticate");
221             }
222         }
223     }
224 
check_redirect<T>(status_code: StatusCode, request: &mut Request<T>) -> bool225     pub(crate) fn check_redirect<T>(status_code: StatusCode, request: &mut Request<T>) -> bool {
226         match status_code {
227             StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => {
228                 Self::update_header_and_method(request);
229                 true
230             }
231             StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => true,
232             _ => false,
233         }
234     }
235 
update_header_and_method<T>(request: &mut Request<T>)236     fn update_header_and_method<T>(request: &mut Request<T>) {
237         for header_name in [
238             "transfer_encoding",
239             "content_encoding",
240             "content_type",
241             "content_length",
242             "content_language",
243             "content_location",
244             "digest",
245             "last_modified",
246         ] {
247             let _ = request.headers_mut().remove(header_name);
248         }
249         let method = request.method_mut();
250         match *method {
251             Method::GET | Method::HEAD => {}
252             _ => {
253                 *method = Method::GET;
254             }
255         }
256     }
257 }
258 
259 #[cfg(test)]
260 mod ut_redirect {
261     use ylong_http::h1::ResponseDecoder;
262     use ylong_http::request::uri::Uri;
263     use ylong_http::request::Request;
264     use ylong_http::response::status::StatusCode;
265     use ylong_http::response::Response;
266 
267     use crate::redirect::Redirect;
268     use crate::util::config::Redirect as setting_redirect;
269     use crate::util::redirect::{RedirectStatus, RedirectStrategy, TriggerKind};
270     /// UT test cases for `Redirect::check_redirect`.
271     ///
272     /// # Brief
273     /// 1. Creates a `request` by calling `request::new`.
274     /// 2. Uses `redirect::check_redirect` to check whether is redirected.
275     /// 3. Checks if the result is true.
276     #[test]
ut_check_redirect()277     fn ut_check_redirect() {
278         let mut request = Request::new("this is a body");
279         let code = StatusCode::MOVED_PERMANENTLY;
280         let res = Redirect::check_redirect(code, &mut request);
281         assert!(res);
282     }
283     /// UT test cases for `Redirect::get_trigger_kind`.
284     ///
285     /// # Brief
286     /// 1. Creates a `redirect` by calling `setting_redirect::default`.
287     /// 2. Uses `Redirect::get_trigger_kind` to get redirected trigger kind.
288     /// 3. Checks if the results are correct.
289     #[test]
ut_get_trigger_kind()290     fn ut_get_trigger_kind() {
291         let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nLocation: \t http://example3.com:80/foo?a=1 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\n".as_bytes();
292         let mut decoder = ResponseDecoder::new();
293         let result = decoder.decode(response_str).unwrap().unwrap();
294         let response = Response::from_raw_parts(result.0, result.1);
295         let mut request = Request::new("this is a body");
296         let request_uri = request.uri_mut();
297         *request_uri = Uri::from_bytes(b"http://example1.com:80/foo?a=1").unwrap();
298         let mut uri = Uri::default();
299         let redirect = setting_redirect::default();
300         let redirect_list: Vec<Uri> = vec![];
301         let res = Redirect::get_trigger_kind(
302             &mut uri,
303             &redirect,
304             &redirect_list,
305             &response,
306             &mut request,
307         );
308         assert!(res.is_ok());
309     }
310     /// UT test cases for `Redirect::get_trigger_kind` err branch.
311     ///
312     /// # Brief
313     /// 1. Creates a `redirect` by calling `setting_redirect::default`.
314     /// 2. Uses `Redirect::get_trigger_kind` to get redirected trigger kind.
315     /// 3. Checks if the results are error.
316     #[test]
ut_get_trigger_kind_err()317     fn ut_get_trigger_kind_err() {
318         let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nLocation: \t example3.com:80 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\n".as_bytes();
319         let mut decoder = ResponseDecoder::new();
320         let result = decoder.decode(response_str).unwrap().unwrap();
321         let response = Response::from_raw_parts(result.0, result.1);
322         let mut request = Request::new("this is a body");
323         let request_uri = request.uri_mut();
324         *request_uri = Uri::from_bytes(b"http://example1.com:80").unwrap();
325         let mut uri = Uri::default();
326         let redirect = setting_redirect::default();
327         let redirect_list: Vec<Uri> = vec![];
328         let res = Redirect::get_trigger_kind(
329             &mut uri,
330             &redirect,
331             &redirect_list,
332             &response,
333             &mut request,
334         );
335         assert!(res.is_err());
336     }
337 
338     /// UT test cases for `RedirectStrategy::default`.
339     ///
340     /// # Brief
341     /// 1. Creates a `RedirectStrategy` by calling `RedirectStrategy::default`.
342     /// 2. Uses `RedirectStrategy::get_trigger` to get redirected uri.
343     /// 3. Checks if the results are correct.
344     #[test]
ut_redirect_default()345     fn ut_redirect_default() {
346         let strategy = RedirectStrategy::default();
347         let next = Uri::from_bytes(b"http://example.com").unwrap();
348         let previous = (0..9)
349             .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap())
350             .collect::<Vec<_>>();
351 
352         let redirect_uri = match strategy
353             .get_trigger(RedirectStatus::new(&previous))
354             .unwrap()
355         {
356             TriggerKind::NextLink => next.to_string(),
357             TriggerKind::Stop => previous.get(9).unwrap().to_string(),
358         };
359         assert_eq!(redirect_uri, "http://example.com".to_string());
360     }
361 
362     /// UT test cases for `RedirectStrategy::limited`.
363     ///
364     /// # Brief
365     /// 1. Creates a `RedirectStrategy` by calling `RedirectStrategy::limited`.
366     /// 2. Sets redirect times which is over max limitation times.
367     /// 3. Uses `RedirectStrategy::get_trigger` to get redirected uri.
368     /// 4. Checks if the results are err.
369     #[test]
ut_redirect_over_redirect_max()370     fn ut_redirect_over_redirect_max() {
371         let strategy = RedirectStrategy::limited(10);
372         let previous = (0..10)
373             .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap())
374             .collect::<Vec<_>>();
375 
376         if let Ok(other) = strategy.get_trigger(RedirectStatus::new(&previous)) {
377             panic!("unexpected {:?}", other);
378         }
379     }
380 
381     /// UT test cases for `RedirectStrategy::none`.
382     ///
383     /// # Brief
384     /// 1. Creates a `RedirectStrategy` by calling `RedirectStrategy::none`.
385     /// 2. Uses `RedirectStrategy::get_trigger` but get origin uri.
386     /// 3. Checks if the results are correct.
387     #[test]
ut_no_redirect()388     fn ut_no_redirect() {
389         let strategy = RedirectStrategy::none();
390         let next = Uri::from_bytes(b"http://example.com").unwrap();
391         let previous = (0..1)
392             .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap())
393             .collect::<Vec<_>>();
394 
395         let redirect_uri = match strategy
396             .get_trigger(RedirectStatus::new(&previous))
397             .unwrap()
398         {
399             TriggerKind::NextLink => next.to_string(),
400             TriggerKind::Stop => previous.get(0).unwrap().to_string(),
401         };
402         assert_eq!(redirect_uri, "http://example0.com".to_string());
403     }
404 }
405