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