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