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