• 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::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