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