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::request::method::Method;
15 use ylong_http::request::uri::Uri;
16 use ylong_http::request::Request;
17 use ylong_http::response::status::StatusCode;
18 use ylong_http::response::Response;
19 
20 use crate::error::{ErrorKind, HttpClientError};
21 
22 #[derive(Debug, Clone, Eq, PartialEq)]
23 pub(crate) struct Redirect {
24     strategy: Strategy,
25 }
26 
27 impl Redirect {
limited(times: usize) -> Self28     pub(crate) fn limited(times: usize) -> Self {
29         Self {
30             strategy: Strategy::LimitTimes(times),
31         }
32     }
33 
none() -> Self34     pub(crate) fn none() -> Self {
35         Self {
36             strategy: Strategy::NoRedirect,
37         }
38     }
39 
redirect<A, B>( &self, request: &mut Request<A>, response: &Response<B>, info: &mut RedirectInfo, ) -> Result<Trigger, HttpClientError>40     pub(crate) fn redirect<A, B>(
41         &self,
42         request: &mut Request<A>,
43         response: &Response<B>,
44         info: &mut RedirectInfo,
45     ) -> Result<Trigger, HttpClientError> {
46         match response.status() {
47             StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => {
48                 for header_name in UPDATED_HEADERS {
49                     let _ = request.headers_mut().remove(header_name);
50                 }
51                 let method = request.method_mut();
52                 match *method {
53                     Method::GET | Method::HEAD => {}
54                     _ => *method = Method::GET,
55                 }
56             }
57             StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => {}
58             _ => return Ok(Trigger::Stop),
59         }
60 
61         info.previous.push(request.uri().clone());
62 
63         let mut location = response
64             .headers()
65             .get("Location")
66             .and_then(|value| value.to_string().ok())
67             .and_then(|str| Uri::try_from(str.as_bytes()).ok())
68             .ok_or(HttpClientError::from_str(
69                 ErrorKind::Redirect,
70                 "Illegal location header in response",
71             ))?;
72 
73         // If `location` doesn't have `scheme` or `authority`, adds scheme and
74         // authority of the origin request to it.
75         if location.scheme().is_none() || location.authority().is_none() {
76             let origin = request.uri();
77             let scheme = origin.scheme().cloned();
78             let authority = origin.authority().cloned();
79             let (_, _, path, query) = location.into_parts();
80             location = Uri::from_raw_parts(scheme, authority, path, query);
81         }
82 
83         let trigger = self.strategy.trigger(info)?;
84         if let Trigger::NextLink = trigger {
85             if let Some(previous) = info.previous.last() {
86                 if location.authority() != previous.authority() {
87                     for header_name in SENSITIVE_HEADERS {
88                         let _ = request.headers_mut().remove(header_name);
89                     }
90                 }
91             }
92             *request.uri_mut() = location;
93         }
94 
95         Ok(trigger)
96     }
97 }
98 
99 impl Default for Redirect {
default() -> Self100     fn default() -> Self {
101         Self::limited(10)
102     }
103 }
104 
105 pub(crate) struct RedirectInfo {
106     previous: Vec<Uri>,
107 }
108 
109 impl RedirectInfo {
new() -> Self110     pub(crate) fn new() -> Self {
111         Self {
112             previous: Vec::new(),
113         }
114     }
115 }
116 
117 #[derive(Debug, Clone, Eq, PartialEq)]
118 enum Strategy {
119     LimitTimes(usize),
120     NoRedirect,
121 }
122 
123 impl Strategy {
trigger(&self, info: &RedirectInfo) -> Result<Trigger, HttpClientError>124     fn trigger(&self, info: &RedirectInfo) -> Result<Trigger, HttpClientError> {
125         match self {
126             Self::LimitTimes(max) => (info.previous.len() < *max)
127                 .then_some(Trigger::NextLink)
128                 .ok_or(HttpClientError::from_str(
129                     ErrorKind::Build,
130                     "Over redirect max limit",
131                 )),
132             Self::NoRedirect => Ok(Trigger::Stop),
133         }
134     }
135 }
136 
137 pub(crate) enum Trigger {
138     NextLink,
139     Stop,
140 }
141 
142 const UPDATED_HEADERS: [&str; 8] = [
143     "transfer-encoding",
144     "content-encoding",
145     "content-type",
146     "content-length",
147     "content-language",
148     "content-location",
149     "digest",
150     "last-modified",
151 ];
152 
153 const SENSITIVE_HEADERS: [&str; 5] = [
154     "authorization",
155     "cookie",
156     "cookie2",
157     "proxy-authorization",
158     "www-authenticate",
159 ];
160