• 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 //! Proxy implementation.
15 
16 use core::convert::TryFrom;
17 use std::net::IpAddr;
18 
19 use ylong_http::headers::HeaderValue;
20 use ylong_http::request::uri::{Authority, Scheme, Uri};
21 
22 use crate::error::HttpClientError;
23 use crate::util::base64::encode;
24 use crate::util::normalizer::UriFormatter;
25 use crate::ErrorKind;
26 
27 /// `Proxies` is responsible for managing a list of proxies.
28 #[derive(Clone, Default)]
29 pub(crate) struct Proxies {
30     list: Vec<Proxy>,
31 }
32 
33 impl Proxies {
add_proxy(&mut self, proxy: Proxy)34     pub(crate) fn add_proxy(&mut self, proxy: Proxy) {
35         self.list.push(proxy)
36     }
37 
match_proxy(&self, uri: &Uri) -> Option<&Proxy>38     pub(crate) fn match_proxy(&self, uri: &Uri) -> Option<&Proxy> {
39         self.list.iter().find(|proxy| proxy.is_intercepted(uri))
40     }
41 }
42 
43 /// Proxy is a configuration of client which should manage the destination
44 /// address of request.
45 ///
46 /// A `Proxy` has below rules:
47 ///
48 /// - Manage the uri of destination address.
49 /// - Manage the request content such as headers.
50 /// - Provide no proxy function which the request will not affected by proxy.
51 #[derive(Clone)]
52 pub(crate) struct Proxy {
53     pub(crate) intercept: Intercept,
54     pub(crate) no_proxy: Option<NoProxy>,
55 }
56 
57 impl Proxy {
new(intercept: Intercept) -> Self58     pub(crate) fn new(intercept: Intercept) -> Self {
59         Self {
60             intercept,
61             no_proxy: None,
62         }
63     }
64 
http(uri: &str) -> Result<Self, HttpClientError>65     pub(crate) fn http(uri: &str) -> Result<Self, HttpClientError> {
66         Ok(Proxy::new(Intercept::Http(ProxyInfo::new(uri)?)))
67     }
68 
https(uri: &str) -> Result<Self, HttpClientError>69     pub(crate) fn https(uri: &str) -> Result<Self, HttpClientError> {
70         Ok(Proxy::new(Intercept::Https(ProxyInfo::new(uri)?)))
71     }
72 
all(uri: &str) -> Result<Self, HttpClientError>73     pub(crate) fn all(uri: &str) -> Result<Self, HttpClientError> {
74         Ok(Proxy::new(Intercept::All(ProxyInfo::new(uri)?)))
75     }
76 
basic_auth(&mut self, username: &str, password: &str)77     pub(crate) fn basic_auth(&mut self, username: &str, password: &str) {
78         let auth = encode(format!("{username}:{password}").as_bytes());
79 
80         // All characters in base64 format are valid characters, so we ignore the error.
81         let mut auth = HeaderValue::from_bytes(auth.as_slice()).unwrap();
82         auth.set_sensitive(true);
83 
84         match &mut self.intercept {
85             Intercept::All(info) => info.basic_auth = Some(auth),
86             Intercept::Http(info) => info.basic_auth = Some(auth),
87             Intercept::Https(info) => info.basic_auth = Some(auth),
88         }
89     }
90 
no_proxy(&mut self, no_proxy: &str)91     pub(crate) fn no_proxy(&mut self, no_proxy: &str) {
92         self.no_proxy = NoProxy::from_str(no_proxy);
93     }
94 
via_proxy(&self, uri: &Uri) -> Uri95     pub(crate) fn via_proxy(&self, uri: &Uri) -> Uri {
96         let info = self.intercept.proxy_info();
97 
98         let mut builder = Uri::builder();
99         builder = builder
100             .scheme(info.scheme().clone())
101             .authority(info.authority().clone());
102 
103         if let Some(path) = uri.path() {
104             builder = builder.path(path.clone());
105         }
106 
107         if let Some(query) = uri.query() {
108             builder = builder.query(query.clone());
109         }
110 
111         // Here all parts of builder is accurate.
112         builder.build().unwrap()
113     }
114 
is_intercepted(&self, uri: &Uri) -> bool115     pub(crate) fn is_intercepted(&self, uri: &Uri) -> bool {
116         let no_proxy = self
117             .no_proxy
118             .as_ref()
119             .map(|no_proxy| no_proxy.contain(uri.to_string().as_str()))
120             .unwrap_or(false);
121 
122         match self.intercept {
123             Intercept::All(_) => !no_proxy,
124             Intercept::Http(_) => !no_proxy && *uri.scheme().unwrap() == Scheme::HTTP,
125             Intercept::Https(_) => !no_proxy && *uri.scheme().unwrap() == Scheme::HTTPS,
126         }
127     }
128 }
129 
130 #[derive(Clone)]
131 pub(crate) enum Intercept {
132     All(ProxyInfo),
133     Http(ProxyInfo),
134     Https(ProxyInfo),
135 }
136 
137 impl Intercept {
proxy_info(&self) -> &ProxyInfo138     pub(crate) fn proxy_info(&self) -> &ProxyInfo {
139         match self {
140             Self::All(info) => info,
141             Self::Http(info) => info,
142             Self::Https(info) => info,
143         }
144     }
145 }
146 
147 /// ProxyInfo which contains authentication, scheme and host.
148 #[derive(Clone)]
149 pub(crate) struct ProxyInfo {
150     pub(crate) scheme: Scheme,
151     pub(crate) authority: Authority,
152     pub(crate) basic_auth: Option<HeaderValue>,
153 }
154 
155 impl ProxyInfo {
new(uri: &str) -> Result<Self, HttpClientError>156     pub(crate) fn new(uri: &str) -> Result<Self, HttpClientError> {
157         let mut uri = match Uri::try_from(uri) {
158             Ok(u) => u,
159             Err(e) => {
160                 return Err(HttpClientError::new_with_cause(ErrorKind::Build, Some(e)));
161             }
162         };
163 
164         // Makes sure that all parts of uri exist.
165         UriFormatter::new().format(&mut uri)?;
166         let (scheme, authority, _, _) = uri.into_parts();
167 
168         // `scheme` and `authority` must have values after formatting.
169         Ok(Self {
170             basic_auth: None,
171             scheme: scheme.unwrap(),
172             authority: authority.unwrap(),
173         })
174     }
175 
authority(&self) -> &Authority176     pub(crate) fn authority(&self) -> &Authority {
177         &self.authority
178     }
179 
scheme(&self) -> &Scheme180     pub(crate) fn scheme(&self) -> &Scheme {
181         &self.scheme
182     }
183 }
184 
185 #[derive(Clone)]
186 enum Ip {
187     Address(IpAddr),
188 }
189 
190 #[derive(Clone, Default)]
191 pub(crate) struct NoProxy {
192     ips: Vec<Ip>,
193     domains: Vec<String>,
194 }
195 
196 impl NoProxy {
from_str(no_proxy: &str) -> Option<Self>197     pub(crate) fn from_str(no_proxy: &str) -> Option<Self> {
198         if no_proxy.is_empty() {
199             return None;
200         }
201 
202         let no_proxy_vec = no_proxy.split(',').map(|c| c.trim()).collect::<Vec<&str>>();
203         let mut ip_list = Vec::new();
204         let mut domains_list = Vec::new();
205 
206         for host in no_proxy_vec {
207             match host.parse::<IpAddr>() {
208                 Ok(ip) => ip_list.push(Ip::Address(ip)),
209                 Err(_) => domains_list.push(host.to_string()),
210             }
211         }
212         Some(NoProxy {
213             ips: ip_list,
214             domains: domains_list,
215         })
216     }
217 
contain(&self, proxy_host: &str) -> bool218     pub(crate) fn contain(&self, proxy_host: &str) -> bool {
219         match proxy_host.parse::<IpAddr>() {
220             Ok(ip) => self.contains_ip(ip),
221             Err(_) => self.contains_domain(proxy_host),
222         }
223     }
224 
contains_ip(&self, ip: IpAddr) -> bool225     fn contains_ip(&self, ip: IpAddr) -> bool {
226         for block_ip in self.ips.iter() {
227             match block_ip {
228                 Ip::Address(i) => {
229                     if &ip == i {
230                         return true;
231                     }
232                 }
233             }
234         }
235         false
236     }
237 
contains_domain(&self, domain: &str) -> bool238     fn contains_domain(&self, domain: &str) -> bool {
239         for block_domain in self.domains.iter() {
240             if block_domain == "*"
241                 || block_domain.ends_with(domain)
242                 || block_domain == domain
243                 || block_domain.trim_matches('.') == domain
244             {
245                 return true;
246             } else if domain.ends_with(block_domain) {
247                 // .example.com and www.
248                 if block_domain.starts_with('.')
249                     || domain.as_bytes().get(domain.len() - block_domain.len() - 1) == Some(&b'.')
250                 {
251                     return true;
252                 }
253             }
254         }
255         false
256     }
257 }
258 
259 #[cfg(test)]
260 mod ut_proxy {
261     use ylong_http::request::uri::{Scheme, Uri};
262 
263     use crate::util::proxy::{Proxies, Proxy};
264 
265     /// UT test cases for `Proxies`.
266     ///
267     /// # Brief
268     /// 1. Creates a `Proxies`.
269     /// 2. Adds some `Proxy` to `Proxies`
270     /// 3. Calls `Proxies::match_proxy` with some `Uri`s and get the results.
271     /// 4. Checks if the test result is correct.
272     #[test]
ut_proxies()273     fn ut_proxies() {
274         let mut proxies = Proxies::default();
275         proxies.add_proxy(Proxy::http("http://www.aaa.com").unwrap());
276         proxies.add_proxy(Proxy::https("http://www.bbb.com").unwrap());
277 
278         let uri = Uri::from_bytes(b"http://www.example.com").unwrap();
279         let proxy = proxies.match_proxy(&uri).unwrap();
280         assert!(proxy.no_proxy.is_none());
281         let info = proxy.intercept.proxy_info();
282         assert_eq!(info.scheme, Scheme::HTTP);
283         assert_eq!(info.authority.to_string(), "www.aaa.com:80");
284 
285         let uri = Uri::from_bytes(b"https://www.example.com").unwrap();
286         let matched = proxies.match_proxy(&uri).unwrap();
287         assert!(matched.no_proxy.is_none());
288         let info = matched.intercept.proxy_info();
289         assert_eq!(info.scheme, Scheme::HTTP);
290         assert_eq!(info.authority.to_string(), "www.bbb.com:80");
291 
292         // with no_proxy
293         let mut proxies = Proxies::default();
294         let mut proxy = Proxy::http("http://www.aaa.com").unwrap();
295         proxy.no_proxy("http://no_proxy.aaa.com");
296         proxies.add_proxy(proxy);
297 
298         let uri = Uri::from_bytes(b"http://www.bbb.com").unwrap();
299         let matched = proxies.match_proxy(&uri).unwrap();
300         let info = matched.intercept.proxy_info();
301         assert_eq!(info.scheme, Scheme::HTTP);
302         assert_eq!(info.authority.to_string(), "www.aaa.com:80");
303 
304         let uri = Uri::from_bytes(b"http://no_proxy.aaa.com").unwrap();
305         assert!(proxies.match_proxy(&uri).is_none());
306     }
307 }
308