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
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 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_VERBOSE, 1L);
88 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_ERRORBUFFER, errorStr.data());
89
90 ProxyInfo proxy;
91 if (GetProxy(proxy)) {
92 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_PROXY, proxy.host.c_str());
93 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_PROXYPORT, proxy.port);
94 if (!proxy.exclusions.empty()) {
95 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_NOPROXY, proxy.exclusions.c_str());
96 }
97 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
98 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HTTPPROXYTUNNEL, 1L);
99 }
100
101 #if defined(IOS_PLATFORM) || defined(ANDROID_PLATFORM) || defined(PREVIEW)
102 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_SSL_VERIFYPEER, 0L);
103 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_SSL_VERIFYHOST, 0L);
104 #endif
105
106 CURLcode result = curl_easy_perform(handle.get());
107 if (result != CURLE_OK) {
108 LOGE("Failed to download, url: [%{private}s], [%{public}s]", url.c_str(), curl_easy_strerror(result));
109 if (!errorStr.empty()) {
110 LOGE("Failed to download reason: [%{public}s]", errorStr.c_str());
111 }
112 dataOut.clear();
113 return false;
114 }
115 dataOut.shrink_to_fit();
116 return true;
117 }
118
119 bool Download(const std::string& url, const std::shared_ptr<DownloadResult>& downloadResult) override
120 {
121 NetStackRequest httpReq;
122 httpReq.SetHeader("Accept", "image/webp,*/*");
123 httpReq.SetURL(url);
124 auto& session = NetStack::HttpClient::HttpSession::GetInstance();
125 auto task = session.CreateTask(httpReq);
126 if (!task) {
127 return false;
128 }
129 std::shared_ptr<DownloadCondition> downloadCondition = std::make_shared<DownloadCondition>();
130 task->OnSuccess(
131 [downloadCondition, downloadResult](const NetStackRequest& request, const NetStackResponse& response) {
132 {
133 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
134 downloadResult->downloadSuccess = true;
135 downloadResult->dataOut = std::move(response.GetResult());
136 }
137 downloadCondition->cv.notify_all();
138 });
139 task->OnCancel(
140 [downloadCondition, downloadResult](const NetStackRequest& request, const NetStackResponse& response) {
141 {
142 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
143 downloadResult->errorMsg.append("Http task of url ");
144 downloadResult->errorMsg.append(request.GetURL());
145 downloadResult->errorMsg.append(" cancelled by netStack");
146 downloadResult->downloadSuccess = false;
147 }
148 downloadCondition->cv.notify_all();
149 });
150 task->OnFail([downloadCondition, downloadResult](
151 const NetStackRequest& request, const NetStackResponse& response, const NetStackError& error) {
152 {
153 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
154 downloadResult->errorMsg.append("Http task of url ");
155 downloadResult->errorMsg.append(request.GetURL());
156 downloadResult->errorMsg.append(" failed, response code ");
157 auto responseCode = response.GetResponseCode();
158 downloadResult->errorMsg.append(std::to_string(responseCode));
159 downloadResult->errorMsg.append(", msg from netStack: ");
160 downloadResult->errorMsg.append(error.GetErrorMessage());
161 downloadResult->downloadSuccess = false;
162 }
163 downloadCondition->cv.notify_all();
164 });
165 auto result = task->Start();
166 return HandleDownloadResult(result, downloadCondition, downloadResult);
167 }
168
169 bool DownloadAsync(
170 DownloadCallback&& downloadCallback, const std::string& url, int32_t instanceId, int32_t nodeId) override
171 {
172 NetStackRequest httpReq;
173 httpReq.SetHeader("Accept", "image/webp,*/*");
174 httpReq.SetURL(url);
175 auto& session = NetStack::HttpClient::HttpSession::GetInstance();
176 auto task = session.CreateTask(httpReq);
177 if (!task) {
178 return false;
179 }
180 task->OnSuccess(
181 [successCallback = downloadCallback.successCallback, failCallback = downloadCallback.failCallback,
182 instanceId](const NetStackRequest& request, const NetStackResponse& response) {
183 if (response.GetResponseCode() != NetStack::HttpClient::ResponseCode::OK) {
184 std::string errorMsg = "Http task of url " + request.GetURL() + " failed, response code " +
185 std::to_string(response.GetResponseCode());
186 failCallback(errorMsg, true, instanceId);
187 return;
188 }
189 LOGI("Async http task of url [%{private}s] success, the responseCode = %d", request.GetURL().c_str(),
190 response.GetResponseCode());
191 successCallback(std::move(response.GetResult()), true, instanceId);
192 });
193 task->OnCancel([cancelCallback = downloadCallback.cancelCallback, instanceId](
194 const NetStackRequest& request, const NetStackResponse& response) {
195 LOGI("Async Http task of url [%{private}s] cancelled by netStack", request.GetURL().c_str());
196 std::string errorMsg = "Http task of url " + request.GetURL() + " cancelled by netStack";
197 cancelCallback(errorMsg, true, instanceId);
198 });
199 task->OnFail([failCallback = downloadCallback.failCallback, instanceId](
200 const NetStackRequest& request, const NetStackResponse& response, const NetStackError& error) {
201 LOGI("Async http task of url [%{private}s] failed, response code %{public}d, msg from netStack: "
202 "[%{public}s]",
203 request.GetURL().c_str(), response.GetResponseCode(), error.GetErrorMessage().c_str());
204 std::string errorMsg = "Http task of url " + request.GetURL() + " failed, response code " +
205 std::to_string(response.GetResponseCode()) +
206 ", msg from netStack: " + error.GetErrorMessage();
207 failCallback(errorMsg, true, instanceId);
208 });
209 if (downloadCallback.onProgressCallback) {
210 task->OnProgress([onProgressCallback = downloadCallback.onProgressCallback, instanceId](
211 const NetStackRequest& request, u_long dlTotal, u_long dlNow, u_long ulTotal,
212 u_long ulNow) { onProgressCallback(dlTotal, dlNow, true, instanceId); });
213 }
214 AddDownloadTask(url, task, nodeId);
215 auto result = task->Start();
216 LOGI("download src [%{private}s] [%{public}s]", url.c_str(),
217 result ? " successfully" : " failed to download, please check netStack log");
218 return result;
219 }
220
221 bool DownloadSync(
222 DownloadCallback&& downloadCallback, const std::string& url, int32_t instanceId, int32_t nodeId) override
223 {
224 LOGI("DownloadSync task of [%{private}s] start", url.c_str());
225 NetStackRequest httpReq;
226 httpReq.SetHeader("Accept", "image/webp,*/*");
227 httpReq.SetURL(url);
228 auto& session = NetStack::HttpClient::HttpSession::GetInstance();
229 auto task = session.CreateTask(httpReq);
230 std::shared_ptr<DownloadCondition> downloadCondition = std::make_shared<DownloadCondition>();
231 if (!task) {
232 return false;
233 }
234 task->OnSuccess([downloadCondition](const NetStackRequest& request, const NetStackResponse& response) {
235 LOGI("Sync http task of url [%{private}s] success, the responseCode = %d", request.GetURL().c_str(),
236 response.GetResponseCode());
237 {
238 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
239 downloadCondition->downloadSuccess = true;
240 downloadCondition->dataOut = std::move(response.GetResult());
241 }
242 downloadCondition->cv.notify_all();
243 });
244 task->OnCancel([downloadCondition](const NetStackRequest& request, const NetStackResponse& response) {
245 LOGI("Sync Http task of url [%{private}s] cancelled", request.GetURL().c_str());
246 {
247 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
248 downloadCondition->errorMsg.append("Http task of url ");
249 downloadCondition->errorMsg.append(request.GetURL());
250 downloadCondition->errorMsg.append(" cancelled by netStack");
251 downloadCondition->downloadSuccess = false;
252 }
253 downloadCondition->cv.notify_all();
254 });
255 task->OnFail([downloadCondition](const NetStackRequest& request, const NetStackResponse& response,
256 const NetStackError& error) { OnFail(downloadCondition, request, response, error); });
257 if (downloadCallback.onProgressCallback) {
258 task->OnProgress([onProgressCallback = downloadCallback.onProgressCallback, instanceId](
259 const NetStackRequest& request, u_long dlTotal, u_long dlNow, u_long ulTotal,
260 u_long ulNow) { onProgressCallback(dlTotal, dlNow, false, instanceId); });
261 }
262 AddDownloadTask(url, task, nodeId);
263 auto result = task->Start();
264 return HandleDownloadResult(result, std::move(downloadCallback), downloadCondition, instanceId, url);
265 }
266
OnFail(std::shared_ptr<DownloadCondition> downloadCondition, const NetStackRequest& request, const NetStackResponse& response, const NetStackError& error)267 static void OnFail(std::shared_ptr<DownloadCondition> downloadCondition, const NetStackRequest& request,
268 const NetStackResponse& response, const NetStackError& error)
269 {
270 LOGI("Sync Http task of url [%{private}s] failed, response code %{public}d, msg from netStack: [%{public}s]",
271 request.GetURL().c_str(), response.GetResponseCode(), error.GetErrorMessage().c_str());
272 {
273 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
274 downloadCondition->errorMsg.append("Http task of url ");
275 downloadCondition->errorMsg.append(request.GetURL());
276 downloadCondition->errorMsg.append(" failed, response code ");
277 auto responseCode = response.GetResponseCode();
278 downloadCondition->errorMsg.append(std::to_string(responseCode));
279 downloadCondition->errorMsg.append(", msg from netStack: ");
280 downloadCondition->errorMsg.append(error.GetErrorMessage());
281 downloadCondition->downloadSuccess = false;
282 }
283 downloadCondition->cv.notify_all();
284 }
285
286 bool RemoveDownloadTask(const std::string& url, int32_t nodeId) override
287 {
288 std::scoped_lock lock(httpTaskMutex_);
289 auto urlKey = url + std::to_string(nodeId);
290 auto iter = httpTaskMap_.find(urlKey);
291 if (iter != httpTaskMap_.end()) {
292 auto task = iter->second;
293 if (task->GetStatus() == NetStackTaskStatus::RUNNING) {
294 LOGI("AceImage RemoveDownloadTask, url:%{private}s", url.c_str());
295 task->Cancel();
296 }
297 httpTaskMap_.erase(urlKey);
298 return true;
299 }
300 return false;
301 }
302
303 private:
304 struct ProxyInfo {
305 std::string host;
306 int32_t port = 0;
307 std::string exclusions;
308 };
309 std::mutex httpTaskMutex_;
310 std::unordered_map<std::string, std::shared_ptr<NetStackTask>> httpTaskMap_;
311
HandleDownloadResult(bool result, DownloadCallback&& downloadCallback, const std::shared_ptr<DownloadCondition>& downloadCondition, int32_t instanceId, const std::string& url)312 bool HandleDownloadResult(bool result, DownloadCallback&& downloadCallback,
313 const std::shared_ptr<DownloadCondition>& downloadCondition, int32_t instanceId, const std::string& url)
314 {
315 if (!result) {
316 return result;
317 }
318 {
319 std::unique_lock<std::mutex> downloadLock(downloadCondition->downloadMutex);
320 // condition_variable is waiting for any of the success, cancel or failed to respond in sync mode
321 downloadCondition->cv.wait_for(
322 downloadLock, std::chrono::milliseconds(MAXIMUM_WAITING_PERIOD), [downloadCondition] {
323 return downloadCondition ? downloadCondition->downloadSuccess.has_value() : false;
324 });
325 }
326 if (!downloadCondition->downloadSuccess.has_value()) {
327 LOGI("Sync Task of netstack with url [%{private}s] maximum waiting period exceed", url.c_str());
328 }
329 if (downloadCondition->downloadSuccess.value_or(false)) {
330 downloadCallback.successCallback(std::move(downloadCondition->dataOut), false, instanceId);
331 } else {
332 downloadCallback.failCallback(downloadCondition->errorMsg, false, instanceId);
333 }
334 return true;
335 }
336
HandleDownloadResult(bool result, const std::shared_ptr<DownloadCondition>& downloadCondition, const std::shared_ptr<DownloadResult>& downloadResult)337 bool HandleDownloadResult(bool result, const std::shared_ptr<DownloadCondition>& downloadCondition,
338 const std::shared_ptr<DownloadResult>& downloadResult)
339 {
340 if (!result) {
341 return result;
342 }
343 {
344 std::unique_lock<std::mutex> downloadLock(downloadCondition->downloadMutex);
345 // condition_variable is waiting for any of the success, cancel or failed to respond in sync mode
346 downloadCondition->cv.wait_for(
347 downloadLock, std::chrono::milliseconds(MAXIMUM_WAITING_PERIOD), [downloadCondition, downloadResult] {
348 return downloadCondition ? downloadResult->downloadSuccess.has_value() : false;
349 });
350 }
351 return true;
352 }
353
AddDownloadTask(const std::string& url, const std::shared_ptr<NetStackTask>& task, int32_t nodeId)354 void AddDownloadTask(const std::string& url, const std::shared_ptr<NetStackTask>& task, int32_t nodeId)
355 {
356 std::scoped_lock lock(httpTaskMutex_);
357 httpTaskMap_.emplace(url + std::to_string(nodeId), task);
358 }
359
Initialize()360 bool Initialize()
361 {
362 if (initialized_) {
363 return true;
364 }
365
366 std::lock_guard<std::mutex> lock(mutex_);
367 if (initialized_) {
368 return true;
369 }
370 if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
371 LOGE("Failed to initialize 'curl'");
372 return false;
373 }
374 initialized_ = true;
375 return true;
376 }
377
OnWritingMemory(void* data, size_t size, size_t memBytes, void* userData)378 static size_t OnWritingMemory(void* data, size_t size, size_t memBytes, void* userData)
379 {
380 // size is always 1, for more details see https://curl.haxx.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
381 auto& dataOut = *static_cast<std::vector<uint8_t>*>(userData);
382 auto chunkData = static_cast<uint8_t*>(data);
383 dataOut.insert(dataOut.end(), chunkData, chunkData + memBytes);
384 return memBytes;
385 }
386
GetProxy(ProxyInfo& proxy)387 static bool GetProxy(ProxyInfo& proxy)
388 {
389 NetManagerStandard::HttpProxy httpProxy;
390 NetManagerStandard::NetConnClient::GetInstance().GetDefaultHttpProxy(httpProxy);
391 proxy.host = httpProxy.GetHost();
392 proxy.port = httpProxy.GetPort();
393
394 auto exclusionList = httpProxy.GetExclusionList();
395 for (auto&& ex : exclusionList) {
396 proxy.exclusions.append(ex);
397 if (ex != exclusionList.back()) {
398 proxy.exclusions.append(",");
399 }
400 }
401 return true;
402 }
403
404 std::mutex mutex_;
405 bool initialized_ = false;
406 bool isCurl_ = false;
407 };
408
OHOS_ACE_CreateDownloadManager()409 extern "C" ACE_FORCE_EXPORT void* OHOS_ACE_CreateDownloadManager()
410 {
411 return new DownloadManagerImpl();
412 }
413 } // namespace OHOS::Ace
414