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 std::error::Error;
15 
16 use ylong_http_client::async_impl::{Client, Interceptor, Request};
17 use ylong_http_client::{
18     Certificate, HttpClientError, Proxy, PubKeyPins, Redirect, Timeout, TlsVersion,
19 };
20 
21 cfg_oh! {
22     use crate::manage::SystemConfig;
23     use crate::utils::url_policy::check_url_domain;
24 }
25 
26 use crate::task::config::{Action, TaskConfig};
27 use crate::task::files::{convert_bundle_name, convert_path};
28 
29 const CONNECT_TIMEOUT: u64 = 60;
30 const SECONDS_IN_ONE_WEEK: u64 = 7 * 24 * 60 * 60;
31 
build_client( config: &TaskConfig, #[cfg(feature = "oh")] mut system: SystemConfig, ) -> Result<Client, Box<dyn Error + Send + Sync>>32 pub(crate) fn build_client(
33     config: &TaskConfig,
34     #[cfg(feature = "oh")] mut system: SystemConfig,
35 ) -> Result<Client, Box<dyn Error + Send + Sync>> {
36     let mut client = Client::builder()
37         .connect_timeout(Timeout::from_secs(CONNECT_TIMEOUT))
38         .request_timeout(Timeout::from_secs(SECONDS_IN_ONE_WEEK))
39         .min_tls_version(TlsVersion::TLS_1_2);
40 
41     client = client.sockets_owner(config.common_data.uid as u32, config.common_data.uid as u32);
42     // Set redirect strategy.
43     if config.common_data.redirect {
44         client = client.redirect(Redirect::limited(usize::MAX));
45     } else {
46         client = client.redirect(Redirect::none());
47     }
48 
49     // Set HTTP proxy.
50     #[cfg(feature = "oh")]
51     if let Some(proxy) = build_task_proxy(config)? {
52         client = client.proxy(proxy);
53     } else if let Some(proxy) = build_system_proxy(&system)? {
54         client = client.proxy(proxy);
55     }
56 
57     // HTTP url that contains redirects also require a certificate when
58     // redirected to HTTPS.
59 
60     // Set system certs.
61     #[cfg(feature = "oh")]
62     if let Some(certs) = system.certs.take() {
63         for cert in certs.into_iter() {
64             client = client.add_root_certificate(cert)
65         }
66     }
67 
68     // Set task certs.
69     let certificates = build_task_certs(config)?;
70     for cert in certificates.into_iter() {
71         client = client.add_root_certificate(cert)
72     }
73 
74     // Set task certificate pinned_key.
75     if let Some(pinned_key) = build_task_certificate_pins(config)? {
76         client = client.add_public_key_pins(pinned_key);
77     }
78 
79     const ATOMIC_SERVICE: u32 = 1;
80     if config.bundle_type == ATOMIC_SERVICE {
81         let domain_type = action_to_domain_type(config.common_data.action);
82         info!(
83             "ApiPolicy Domain check, tid {}, bundle {}, domain_type {}, url {}",
84             config.common_data.task_id, &config.bundle, &domain_type, &config.url
85         );
86         #[cfg(feature = "oh")]
87         if let Some(is_accessed) = check_url_domain(&config.bundle, &domain_type, &config.url) {
88             if !is_accessed {
89                 error!(
90                     "Intercept request by domain check, tid {}, bundle {}, domain_type {}, url {}",
91                     config.common_data.task_id, &config.bundle, &domain_type, &config.url
92                 );
93                 return Err(Box::new(HttpClientError::other(
94                     "Intercept request by domain check",
95                 )));
96             }
97         } else {
98             info!(
99                 "Intercept request by domain check, tid {}, domain_type {}, url {}",
100                 config.common_data.task_id, &domain_type, &config.url
101             );
102         }
103 
104         #[cfg(feature = "oh")]
105         {
106             let interceptors = DomainInterceptor::new(config.bundle.clone(), domain_type);
107             client = client.interceptor(interceptors);
108         }
109 
110         info!(
111             "add interceptor domain check, tid {}",
112             config.common_data.task_id
113         );
114     }
115 
116     // Build client.
117     Ok(cvt_res_error!(
118         client.build().map_err(Box::new),
119         "Build client failed",
120     ))
121 }
122 
build_task_proxy(config: &TaskConfig) -> Result<Option<Proxy>, Box<dyn Error + Send + Sync>>123 fn build_task_proxy(config: &TaskConfig) -> Result<Option<Proxy>, Box<dyn Error + Send + Sync>> {
124     if config.proxy.is_empty() {
125         return Ok(None);
126     }
127 
128     Ok(Some(cvt_res_error!(
129         Proxy::all(&config.proxy).build().map_err(Box::new),
130         "Create task proxy failed",
131     )))
132 }
133 
build_task_certificate_pins( config: &TaskConfig, ) -> Result<Option<PubKeyPins>, Box<dyn Error + Send + Sync>>134 fn build_task_certificate_pins(
135     config: &TaskConfig,
136 ) -> Result<Option<PubKeyPins>, Box<dyn Error + Send + Sync>> {
137     if config.certificate_pins.is_empty() {
138         return Ok(None);
139     }
140 
141     Ok(Some(cvt_res_error!(
142         PubKeyPins::builder()
143             .add(&config.url, &config.certificate_pins)
144             .build()
145             .map_err(Box::new),
146         "Create task certificate pinned_key failed",
147     )))
148 }
149 
150 #[cfg(feature = "oh")]
build_system_proxy( system: &SystemConfig, ) -> Result<Option<Proxy>, Box<dyn Error + Send + Sync>>151 fn build_system_proxy(
152     system: &SystemConfig,
153 ) -> Result<Option<Proxy>, Box<dyn Error + Send + Sync>> {
154     let proxy_host = &system.proxy_host;
155 
156     if proxy_host.is_empty() {
157         return Ok(None);
158     }
159 
160     let proxy_port = &system.proxy_port;
161     let proxy_url = match proxy_port.is_empty() {
162         true => proxy_host.clone(),
163         false => format!("{}:{}", proxy_host, proxy_port),
164     };
165     let no_proxy = &system.proxy_exlist;
166     Ok(Some(cvt_res_error!(
167         Proxy::all(&proxy_url)
168             .no_proxy(no_proxy)
169             .build()
170             .map_err(Box::new),
171         "Create system proxy failed",
172     )))
173 }
174 
build_task_certs(config: &TaskConfig) -> Result<Vec<Certificate>, Box<dyn Error + Send + Sync>>175 fn build_task_certs(config: &TaskConfig) -> Result<Vec<Certificate>, Box<dyn Error + Send + Sync>> {
176     let uid = config.common_data.uid;
177     let paths = config.certs_path.as_slice();
178     let bundle_name = convert_bundle_name(config);
179 
180     let mut certs = Vec::new();
181     for (idx, path) in paths.iter().enumerate() {
182         let path = convert_path(uid, &bundle_name, path);
183         let cert = cvt_res_error!(
184             Certificate::from_path(&path).map_err(Box::new),
185             "Parse task cert failed - idx: {}, path: {}",
186             idx,
187             path,
188         );
189         certs.push(cert);
190     }
191     Ok(certs)
192 }
193 
action_to_domain_type(action: Action) -> String194 fn action_to_domain_type(action: Action) -> String {
195     match action {
196         Action::Download => "download".to_string(),
197         Action::Upload => "upload".to_string(),
198         Action::Any => "".to_string(),
199         _ => unreachable!(),
200     }
201 }
202 
203 struct DomainInterceptor {
204     app_id: String,
205     domain_type: String,
206 }
207 
208 impl DomainInterceptor {
new(app_id: String, domain_type: String) -> Self209     fn new(app_id: String, domain_type: String) -> Self {
210         DomainInterceptor {
211             app_id,
212             domain_type,
213         }
214     }
215 }
216 
217 #[cfg(feature = "oh")]
218 impl Interceptor for DomainInterceptor {
219     /// Intercepts the redirect request.
intercept_redirect_request(&self, request: &Request) -> Result<(), HttpClientError>220     fn intercept_redirect_request(&self, request: &Request) -> Result<(), HttpClientError> {
221         let url = &request.uri().to_string();
222         info!(
223             "ApiPolicy Domain check redirect, bundle {}, domain_type {}, url {}",
224             &self.app_id, &self.domain_type, &url
225         );
226         match check_url_domain(&self.app_id, &self.domain_type, url).unwrap_or(true) {
227             true => Ok(()),
228             false => Err(HttpClientError::other(
229                 "Intercept redirect request by domain check",
230             )),
231         }
232     }
233 }
234