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