1 /*
2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "hilog/log.h"
17 #include "http_client.h"
18 #include "net_conn_client.h"
19
20 #include "base/network/download_manager.h"
21
22 #define ACE_FORCE_EXPORT __attribute__((visibility("default")))
23
24 #define ACE_CURL_EASY_SET_OPTION(handle, opt, data) \
25 do { \
26 CURLcode result = curl_easy_setopt(handle, opt, data); \
27 if (result != CURLE_OK) { \
28 return false; \
29 } \
30 } while (0)
31
32 namespace OHOS::Ace {
33 namespace {
34 constexpr int32_t MAXIMUM_WAITING_PERIOD = 2800;
35
36 #define PRINT_LOG(level, fmt, ...) \
37 HILOG_IMPL(LOG_CORE, LOG_##level, 0xD00393A, "DownloadManager", "[%{public}d]" fmt, __LINE__, ##__VA_ARGS__) \
38
39 #define LOGE(fmt, ...) PRINT_LOG(ERROR, fmt, ##__VA_ARGS__)
40 #define LOGW(fmt, ...) PRINT_LOG(WARN, fmt, ##__VA_ARGS__)
41 #define LOGI(fmt, ...) PRINT_LOG(INFO, fmt, ##__VA_ARGS__)
42 #define LOGD(fmt, ...) PRINT_LOG(DEBUG, fmt, ##__VA_ARGS__)
43 } // namespace
44
45 // For sync download tasks, this period may cause image not able to be loaded.
46 // System detects appFreeze after 3s, which has higher priority
47 using NetStackRequest = NetStack::HttpClient::HttpClientRequest;
48 using NetStackResponse = NetStack::HttpClient::HttpClientResponse;
49 using NetStackError = NetStack::HttpClient::HttpClientError;
50 using NetStackTask = NetStack::HttpClient::HttpClientTask;
51 using NetStackTaskStatus = NetStack::HttpClient::TaskStatus;
52
53 class ACE_FORCE_EXPORT DownloadManagerImpl : public DownloadManager {
54 public:
55 DownloadManagerImpl() = default;
~DownloadManagerImpl()56 ~DownloadManagerImpl()
57 {
58 if (isCurl_) {
59 curl_global_cleanup();
60 }
61 }
62
Download(const std::string & url,std::vector<uint8_t> & dataOut)63 bool Download(const std::string& url, std::vector<uint8_t>& dataOut) override
64 {
65 // when calling, it is necessary to set it to true and call curl clean up method
66 // during download manager ohos object destruction
67 isCurl_ = true;
68 if (!Initialize()) {
69 return false;
70 }
71
72 std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> handle(curl_easy_init(), &curl_easy_cleanup);
73 if (!handle) {
74 return false;
75 }
76
77 dataOut.clear();
78 std::string errorStr;
79 errorStr.reserve(CURL_ERROR_SIZE);
80
81 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_URL, url.c_str());
82 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEFUNCTION, OnWritingMemory);
83 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEDATA, &dataOut);
84 // Some servers don't like requests that are made without a user-agent field, so we provide one
85 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_USERAGENT, "libcurl-agent/1.0");
86 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_URL, url.c_str());
87 #if !defined(PREVIEW)
88 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_CAINFO, "/etc/ssl/certs/cacert.pem");
89 #endif
90 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_VERBOSE, 1L);
91 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_ERRORBUFFER, errorStr.data());
92
93 ProxyInfo proxy;
94 if (GetProxy(proxy)) {
95 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_PROXY, proxy.host.c_str());
96 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_PROXYPORT, proxy.port);
97 if (!proxy.exclusions.empty()) {
98 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_NOPROXY, proxy.exclusions.c_str());
99 }
100 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
101 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HTTPPROXYTUNNEL, 1L);
102 }
103
104 #if defined(IOS_PLATFORM) || defined(ANDROID_PLATFORM) || defined(PREVIEW)
105 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_SSL_VERIFYPEER, 0L);
106 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_SSL_VERIFYHOST, 0L);
107 #endif
108
109 CURLcode result = curl_easy_perform(handle.get());
110 if (result != CURLE_OK) {
111 LOGE("Failed to download, url: [%{private}s], [%{public}s]", url.c_str(), curl_easy_strerror(result));
112 if (!errorStr.empty()) {
113 LOGE("Failed to download reason: [%{public}s]", errorStr.c_str());
114 }
115 dataOut.clear();
116 return false;
117 }
118 dataOut.shrink_to_fit();
119 return true;
120 }
121
Download(const std::string & url,const std::shared_ptr<DownloadResult> & downloadResult)122 bool Download(const std::string& url, const std::shared_ptr<DownloadResult>& downloadResult) override
123 {
124 NetStackRequest httpReq;
125 httpReq.SetHeader("Accept", "image/webp,*/*");
126 httpReq.SetURL(url);
127 auto& session = NetStack::HttpClient::HttpSession::GetInstance();
128 auto task = session.CreateTask(httpReq);
129 if (!task) {
130 return false;
131 }
132 std::shared_ptr<DownloadCondition> downloadCondition = std::make_shared<DownloadCondition>();
133 task->OnSuccess(
134 [downloadCondition, downloadResult](const NetStackRequest& request, const NetStackResponse& response) {
135 {
136 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
137 downloadResult->downloadSuccess = true;
138 downloadResult->dataOut = std::move(response.GetResult());
139 }
140 downloadCondition->cv.notify_all();
141 });
142 task->OnCancel(
143 [downloadCondition, downloadResult](const NetStackRequest& request, const NetStackResponse& response) {
144 {
145 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
146 downloadResult->errorMsg.append("Http task of url ");
147 downloadResult->errorMsg.append(request.GetURL());
148 downloadResult->errorMsg.append(" cancelled by netStack");
149 downloadResult->downloadSuccess = false;
150 }
151 downloadCondition->cv.notify_all();
152 });
153 task->OnFail([downloadCondition, downloadResult](
154 const NetStackRequest& request, const NetStackResponse& response, const NetStackError& error) {
155 {
156 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
157 downloadResult->errorMsg.append("Http task of url ");
158 downloadResult->errorMsg.append(request.GetURL());
159 downloadResult->errorMsg.append(" failed, response code ");
160 auto responseCode = response.GetResponseCode();
161 downloadResult->errorMsg.append(std::to_string(responseCode));
162 downloadResult->errorMsg.append(", msg from netStack: ");
163 downloadResult->errorMsg.append(error.GetErrorMessage());
164 downloadResult->downloadSuccess = false;
165 }
166 downloadCondition->cv.notify_all();
167 });
168 auto result = task->Start();
169 return HandleDownloadResult(result, downloadCondition, downloadResult);
170 }
171
DownloadAsync(DownloadCallback && downloadCallback,const std::string & url,int32_t instanceId,int32_t nodeId)172 bool DownloadAsync(
173 DownloadCallback&& downloadCallback, const std::string& url, int32_t instanceId, int32_t nodeId) override
174 {
175 NetStackRequest httpReq;
176 httpReq.SetHeader("Accept", "image/webp,*/*");
177 httpReq.SetURL(url);
178 auto& session = NetStack::HttpClient::HttpSession::GetInstance();
179 auto task = session.CreateTask(httpReq);
180 if (!task) {
181 return false;
182 }
183 task->OnSuccess(
184 [this, successCallback = downloadCallback.successCallback, failCallback = downloadCallback.failCallback,
185 instanceId, url, nodeId](const NetStackRequest& request, const NetStackResponse& response) {
186 if (response.GetResponseCode() != NetStack::HttpClient::ResponseCode::OK) {
187 std::string errorMsg = "Http task of url " + request.GetURL() + " failed, response code " +
188 std::to_string(response.GetResponseCode());
189 failCallback(errorMsg, true, instanceId);
190 RemoveDownloadTask(url, nodeId, false);
191 return;
192 }
193 LOGI("Async http task of url [%{private}s] success, the responseCode = %d", request.GetURL().c_str(),
194 response.GetResponseCode());
195 successCallback(std::move(response.GetResult()), true, instanceId);
196 RemoveDownloadTask(url, nodeId, false);
197 });
198 task->OnCancel([this, cancelCallback = downloadCallback.cancelCallback, instanceId, url, nodeId](
199 const NetStackRequest& request, const NetStackResponse& response) {
200 LOGI("Async Http task of url [%{private}s] cancelled by netStack", request.GetURL().c_str());
201 std::string errorMsg = "Http task of url " + request.GetURL() + " cancelled by netStack";
202 cancelCallback(errorMsg, true, instanceId);
203 RemoveDownloadTask(url, nodeId, false);
204 });
205 task->OnFail([this, failCallback = downloadCallback.failCallback, instanceId, url, nodeId](
206 const NetStackRequest& request, const NetStackResponse& response, const NetStackError& error) {
207 LOGI("Async http task of url [%{private}s] failed, response code %{public}d, msg from netStack: "
208 "[%{public}s]",
209 request.GetURL().c_str(), response.GetResponseCode(), error.GetErrorMessage().c_str());
210 std::string errorMsg = "Http task of url " + request.GetURL() + " failed, response code " +
211 std::to_string(response.GetResponseCode()) +
212 ", msg from netStack: " + error.GetErrorMessage();
213 failCallback(errorMsg, true, instanceId);
214 RemoveDownloadTask(url, nodeId, false);
215 });
216 if (downloadCallback.onProgressCallback) {
217 task->OnProgress([onProgressCallback = downloadCallback.onProgressCallback, instanceId](
218 const NetStackRequest& request, u_long dlTotal, u_long dlNow, u_long ulTotal,
219 u_long ulNow) { onProgressCallback(dlTotal, dlNow, true, instanceId); });
220 }
221 AddDownloadTask(url, task, nodeId);
222 auto result = task->Start();
223 LOGI("download src [%{private}s] [%{public}s]", url.c_str(),
224 result ? " successfully" : " failed to download, please check netStack log");
225 return result;
226 }
227
DownloadSync(DownloadCallback && downloadCallback,const std::string & url,int32_t instanceId,int32_t nodeId)228 bool DownloadSync(
229 DownloadCallback&& downloadCallback, const std::string& url, int32_t instanceId, int32_t nodeId) override
230 {
231 LOGI("DownloadSync task of [%{private}s] start", url.c_str());
232 NetStackRequest httpReq;
233 httpReq.SetHeader("Accept", "image/webp,*/*");
234 httpReq.SetURL(url);
235 auto& session = NetStack::HttpClient::HttpSession::GetInstance();
236 auto task = session.CreateTask(httpReq);
237 std::shared_ptr<DownloadCondition> downloadCondition = std::make_shared<DownloadCondition>();
238 if (!task) {
239 return false;
240 }
241 task->OnSuccess(
242 [this, downloadCondition, url, nodeId](const NetStackRequest& request, const NetStackResponse& response) {
243 LOGI("Sync http task of url [%{private}s] success, the responseCode = %d", request.GetURL().c_str(),
244 response.GetResponseCode());
245 {
246 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
247 downloadCondition->downloadSuccess = true;
248 downloadCondition->dataOut = std::move(response.GetResult());
249 }
250 downloadCondition->cv.notify_all();
251 RemoveDownloadTask(url, nodeId, false);
252 });
253 task->OnCancel(
254 [this, downloadCondition, url, nodeId](const NetStackRequest& request, const NetStackResponse& response) {
255 LOGI("Sync Http task of url [%{private}s] cancelled", request.GetURL().c_str());
256 {
257 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
258 downloadCondition->errorMsg.append("Http task of url ");
259 downloadCondition->errorMsg.append(request.GetURL());
260 downloadCondition->errorMsg.append(" cancelled by netStack");
261 downloadCondition->downloadSuccess = false;
262 }
263 downloadCondition->cv.notify_all();
264 RemoveDownloadTask(url, nodeId, false);
265 });
266 task->OnFail([this, downloadCondition, url, nodeId](
267 const NetStackRequest& request, const NetStackResponse& response, const NetStackError& error) {
268 OnFail(downloadCondition, request, response, error);
269 RemoveDownloadTask(url, nodeId, false);
270 });
271 if (downloadCallback.onProgressCallback) {
272 task->OnProgress([onProgressCallback = downloadCallback.onProgressCallback, instanceId](
273 const NetStackRequest& request, u_long dlTotal, u_long dlNow, u_long ulTotal,
274 u_long ulNow) { onProgressCallback(dlTotal, dlNow, false, instanceId); });
275 }
276 AddDownloadTask(url, task, nodeId);
277 auto result = task->Start();
278 return HandleDownloadResult(result, std::move(downloadCallback), downloadCondition, instanceId, url);
279 }
280
OnFail(std::shared_ptr<DownloadCondition> downloadCondition,const NetStackRequest & request,const NetStackResponse & response,const NetStackError & error)281 static void OnFail(std::shared_ptr<DownloadCondition> downloadCondition, const NetStackRequest& request,
282 const NetStackResponse& response, const NetStackError& error)
283 {
284 LOGI(
285 "Sync Http task of url [%{private}s] failed, response code %{public}d, msg from netStack: [%{public}s]",
286 request.GetURL().c_str(), response.GetResponseCode(), error.GetErrorMessage().c_str());
287 {
288 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
289 downloadCondition->errorMsg.append("Http task of url ");
290 downloadCondition->errorMsg.append(request.GetURL());
291 downloadCondition->errorMsg.append(" failed, response code ");
292 auto responseCode = response.GetResponseCode();
293 downloadCondition->errorMsg.append(std::to_string(responseCode));
294 downloadCondition->errorMsg.append(", msg from netStack: ");
295 downloadCondition->errorMsg.append(error.GetErrorMessage());
296 downloadCondition->downloadSuccess = false;
297 }
298 downloadCondition->cv.notify_all();
299 }
300
RemoveDownloadTask(const std::string & url,int32_t nodeId,bool isCancel=true)301 bool RemoveDownloadTask(const std::string& url, int32_t nodeId, bool isCancel = true) override
302 {
303 std::scoped_lock lock(httpTaskMutex_);
304 auto urlKey = url + std::to_string(nodeId);
305 auto iter = httpTaskMap_.find(urlKey);
306 if (iter != httpTaskMap_.end()) {
307 auto task = iter->second;
308 if (task->GetStatus() == NetStackTaskStatus::RUNNING && isCancel) {
309 LOGI("AceImage RemoveDownloadTask, url:%{private}s", url.c_str());
310 task->Cancel();
311 }
312 httpTaskMap_.erase(iter);
313 return true;
314 }
315 return false;
316 }
317
318 private:
319 struct ProxyInfo {
320 std::string host;
321 int32_t port = 0;
322 std::string exclusions;
323 };
324 std::mutex httpTaskMutex_;
325 std::unordered_map<std::string, std::shared_ptr<NetStackTask>> httpTaskMap_;
326
HandleDownloadResult(bool result,DownloadCallback && downloadCallback,const std::shared_ptr<DownloadCondition> & downloadCondition,int32_t instanceId,const std::string & url)327 bool HandleDownloadResult(bool result, DownloadCallback&& downloadCallback,
328 const std::shared_ptr<DownloadCondition>& downloadCondition, int32_t instanceId, const std::string& url)
329 {
330 if (!result) {
331 return result;
332 }
333 {
334 std::unique_lock<std::mutex> downloadLock(downloadCondition->downloadMutex);
335 // condition_variable is waiting for any of the success, cancel or failed to respond in sync mode
336 downloadCondition->cv.wait_for(
337 downloadLock, std::chrono::milliseconds(MAXIMUM_WAITING_PERIOD), [downloadCondition] {
338 return downloadCondition ? downloadCondition->downloadSuccess.has_value() : false;
339 });
340 }
341 if (!downloadCondition->downloadSuccess.has_value()) {
342 LOGI("Sync Task of netstack with url [%{private}s] maximum waiting period exceed", url.c_str());
343 }
344 if (downloadCondition->downloadSuccess.value_or(false)) {
345 downloadCallback.successCallback(std::move(downloadCondition->dataOut), false, instanceId);
346 } else {
347 downloadCallback.failCallback(downloadCondition->errorMsg, false, instanceId);
348 }
349 return true;
350 }
351
HandleDownloadResult(bool result,const std::shared_ptr<DownloadCondition> & downloadCondition,const std::shared_ptr<DownloadResult> & downloadResult)352 bool HandleDownloadResult(bool result, const std::shared_ptr<DownloadCondition>& downloadCondition,
353 const std::shared_ptr<DownloadResult>& downloadResult)
354 {
355 if (!result) {
356 return result;
357 }
358 {
359 std::unique_lock<std::mutex> downloadLock(downloadCondition->downloadMutex);
360 // condition_variable is waiting for any of the success, cancel or failed to respond in sync mode
361 downloadCondition->cv.wait_for(
362 downloadLock, std::chrono::milliseconds(MAXIMUM_WAITING_PERIOD), [downloadCondition, downloadResult] {
363 return downloadCondition ? downloadResult->downloadSuccess.has_value() : false;
364 });
365 }
366 return true;
367 }
368
AddDownloadTask(const std::string & url,const std::shared_ptr<NetStackTask> & task,int32_t nodeId)369 void AddDownloadTask(const std::string& url, const std::shared_ptr<NetStackTask>& task, int32_t nodeId)
370 {
371 std::scoped_lock lock(httpTaskMutex_);
372 httpTaskMap_.emplace(url + std::to_string(nodeId), task);
373 }
374
Initialize()375 bool Initialize()
376 {
377 if (initialized_) {
378 return true;
379 }
380
381 std::lock_guard<std::mutex> lock(mutex_);
382 if (initialized_) {
383 return true;
384 }
385 if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
386 LOGE("Failed to initialize 'curl'");
387 return false;
388 }
389 initialized_ = true;
390 return true;
391 }
392
OnWritingMemory(void * data,size_t size,size_t memBytes,void * userData)393 static size_t OnWritingMemory(void* data, size_t size, size_t memBytes, void* userData)
394 {
395 // size is always 1, for more details see https://curl.haxx.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
396 auto& dataOut = *static_cast<std::vector<uint8_t>*>(userData);
397 auto chunkData = static_cast<uint8_t*>(data);
398 dataOut.insert(dataOut.end(), chunkData, chunkData + memBytes);
399 return memBytes;
400 }
401
GetProxy(ProxyInfo & proxy)402 static bool GetProxy(ProxyInfo& proxy)
403 {
404 NetManagerStandard::HttpProxy httpProxy;
405 NetManagerStandard::NetConnClient::GetInstance().GetDefaultHttpProxy(httpProxy);
406 proxy.host = httpProxy.GetHost();
407 proxy.port = httpProxy.GetPort();
408
409 auto exclusionList = httpProxy.GetExclusionList();
410 for (auto&& ex : exclusionList) {
411 proxy.exclusions.append(ex);
412 if (ex != exclusionList.back()) {
413 proxy.exclusions.append(",");
414 }
415 }
416 return true;
417 }
418
419 std::mutex mutex_;
420 bool initialized_ = false;
421 bool isCurl_ = false;
422 };
423
OHOS_ACE_CreateDownloadManager()424 extern "C" ACE_FORCE_EXPORT void* OHOS_ACE_CreateDownloadManager()
425 {
426 return new DownloadManagerImpl();
427 }
428 } // namespace OHOS::Ace
429