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