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 [successCallback = downloadCallback.successCallback, failCallback = downloadCallback.failCallback,
185 instanceId](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 return;
191 }
192 LOGI("Async http task of url [%{private}s] success, the responseCode = %d", request.GetURL().c_str(),
193 response.GetResponseCode());
194 successCallback(std::move(response.GetResult()), true, instanceId);
195 });
196 task->OnCancel([cancelCallback = downloadCallback.cancelCallback, instanceId](
197 const NetStackRequest& request, const NetStackResponse& response) {
198 LOGI("Async Http task of url [%{private}s] cancelled by netStack", request.GetURL().c_str());
199 std::string errorMsg = "Http task of url " + request.GetURL() + " cancelled by netStack";
200 cancelCallback(errorMsg, true, instanceId);
201 });
202 task->OnFail([failCallback = downloadCallback.failCallback, instanceId](
203 const NetStackRequest& request, const NetStackResponse& response, const NetStackError& error) {
204 LOGI("Async http task of url [%{private}s] failed, response code %{public}d, msg from netStack: "
205 "[%{public}s]",
206 request.GetURL().c_str(), response.GetResponseCode(), error.GetErrorMessage().c_str());
207 std::string errorMsg = "Http task of url " + request.GetURL() + " failed, response code " +
208 std::to_string(response.GetResponseCode()) +
209 ", msg from netStack: " + error.GetErrorMessage();
210 failCallback(errorMsg, true, instanceId);
211 });
212 if (downloadCallback.onProgressCallback) {
213 task->OnProgress([onProgressCallback = downloadCallback.onProgressCallback, instanceId](
214 const NetStackRequest& request, u_long dlTotal, u_long dlNow, u_long ulTotal,
215 u_long ulNow) { onProgressCallback(dlTotal, dlNow, true, instanceId); });
216 }
217 AddDownloadTask(url, task, nodeId);
218 auto result = task->Start();
219 LOGI("download src [%{private}s] [%{public}s]", url.c_str(),
220 result ? " successfully" : " failed to download, please check netStack log");
221 return result;
222 }
223
DownloadSync(DownloadCallback && downloadCallback,const std::string & url,int32_t instanceId,int32_t nodeId)224 bool DownloadSync(
225 DownloadCallback&& downloadCallback, const std::string& url, int32_t instanceId, int32_t nodeId) override
226 {
227 LOGI("DownloadSync task of [%{private}s] start", url.c_str());
228 NetStackRequest httpReq;
229 httpReq.SetHeader("Accept", "image/webp,*/*");
230 httpReq.SetURL(url);
231 auto& session = NetStack::HttpClient::HttpSession::GetInstance();
232 auto task = session.CreateTask(httpReq);
233 std::shared_ptr<DownloadCondition> downloadCondition = std::make_shared<DownloadCondition>();
234 if (!task) {
235 return false;
236 }
237 task->OnSuccess([downloadCondition](const NetStackRequest& request, const NetStackResponse& response) {
238 LOGI("Sync http task of url [%{private}s] success, the responseCode = %d", request.GetURL().c_str(),
239 response.GetResponseCode());
240 {
241 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
242 downloadCondition->downloadSuccess = true;
243 downloadCondition->dataOut = std::move(response.GetResult());
244 }
245 downloadCondition->cv.notify_all();
246 });
247 task->OnCancel([downloadCondition](const NetStackRequest& request, const NetStackResponse& response) {
248 LOGI("Sync Http task of url [%{private}s] cancelled", request.GetURL().c_str());
249 {
250 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
251 downloadCondition->errorMsg.append("Http task of url ");
252 downloadCondition->errorMsg.append(request.GetURL());
253 downloadCondition->errorMsg.append(" cancelled by netStack");
254 downloadCondition->downloadSuccess = false;
255 }
256 downloadCondition->cv.notify_all();
257 });
258 task->OnFail([downloadCondition](const NetStackRequest& request, const NetStackResponse& response,
259 const NetStackError& error) { OnFail(downloadCondition, request, response, error); });
260 if (downloadCallback.onProgressCallback) {
261 task->OnProgress([onProgressCallback = downloadCallback.onProgressCallback, instanceId](
262 const NetStackRequest& request, u_long dlTotal, u_long dlNow, u_long ulTotal,
263 u_long ulNow) { onProgressCallback(dlTotal, dlNow, false, instanceId); });
264 }
265 AddDownloadTask(url, task, nodeId);
266 auto result = task->Start();
267 return HandleDownloadResult(result, std::move(downloadCallback), downloadCondition, instanceId, url);
268 }
269
OnFail(std::shared_ptr<DownloadCondition> downloadCondition,const NetStackRequest & request,const NetStackResponse & response,const NetStackError & error)270 static void OnFail(std::shared_ptr<DownloadCondition> downloadCondition, const NetStackRequest& request,
271 const NetStackResponse& response, const NetStackError& error)
272 {
273 LOGI(
274 "Sync Http task of url [%{private}s] failed, response code %{public}d, msg from netStack: [%{public}s]",
275 request.GetURL().c_str(), response.GetResponseCode(), error.GetErrorMessage().c_str());
276 {
277 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
278 downloadCondition->errorMsg.append("Http task of url ");
279 downloadCondition->errorMsg.append(request.GetURL());
280 downloadCondition->errorMsg.append(" failed, response code ");
281 auto responseCode = response.GetResponseCode();
282 downloadCondition->errorMsg.append(std::to_string(responseCode));
283 downloadCondition->errorMsg.append(", msg from netStack: ");
284 downloadCondition->errorMsg.append(error.GetErrorMessage());
285 downloadCondition->downloadSuccess = false;
286 }
287 downloadCondition->cv.notify_all();
288 }
289
RemoveDownloadTask(const std::string & url,int32_t nodeId)290 bool RemoveDownloadTask(const std::string& url, int32_t nodeId) override
291 {
292 std::scoped_lock lock(httpTaskMutex_);
293 auto urlKey = url + std::to_string(nodeId);
294 auto iter = httpTaskMap_.find(urlKey);
295 if (iter != httpTaskMap_.end()) {
296 auto task = iter->second;
297 if (task->GetStatus() == NetStackTaskStatus::RUNNING) {
298 LOGI("AceImage RemoveDownloadTask, url:%{private}s", url.c_str());
299 task->Cancel();
300 }
301 httpTaskMap_.erase(urlKey);
302 return true;
303 }
304 return false;
305 }
306
307 private:
308 struct ProxyInfo {
309 std::string host;
310 int32_t port = 0;
311 std::string exclusions;
312 };
313 std::mutex httpTaskMutex_;
314 std::unordered_map<std::string, std::shared_ptr<NetStackTask>> httpTaskMap_;
315
HandleDownloadResult(bool result,DownloadCallback && downloadCallback,const std::shared_ptr<DownloadCondition> & downloadCondition,int32_t instanceId,const std::string & url)316 bool HandleDownloadResult(bool result, DownloadCallback&& downloadCallback,
317 const std::shared_ptr<DownloadCondition>& downloadCondition, int32_t instanceId, const std::string& url)
318 {
319 if (!result) {
320 return result;
321 }
322 {
323 std::unique_lock<std::mutex> downloadLock(downloadCondition->downloadMutex);
324 // condition_variable is waiting for any of the success, cancel or failed to respond in sync mode
325 downloadCondition->cv.wait_for(
326 downloadLock, std::chrono::milliseconds(MAXIMUM_WAITING_PERIOD), [downloadCondition] {
327 return downloadCondition ? downloadCondition->downloadSuccess.has_value() : false;
328 });
329 }
330 if (!downloadCondition->downloadSuccess.has_value()) {
331 LOGI("Sync Task of netstack with url [%{private}s] maximum waiting period exceed", url.c_str());
332 }
333 if (downloadCondition->downloadSuccess.value_or(false)) {
334 downloadCallback.successCallback(std::move(downloadCondition->dataOut), false, instanceId);
335 } else {
336 downloadCallback.failCallback(downloadCondition->errorMsg, false, instanceId);
337 }
338 return true;
339 }
340
HandleDownloadResult(bool result,const std::shared_ptr<DownloadCondition> & downloadCondition,const std::shared_ptr<DownloadResult> & downloadResult)341 bool HandleDownloadResult(bool result, const std::shared_ptr<DownloadCondition>& downloadCondition,
342 const std::shared_ptr<DownloadResult>& downloadResult)
343 {
344 if (!result) {
345 return result;
346 }
347 {
348 std::unique_lock<std::mutex> downloadLock(downloadCondition->downloadMutex);
349 // condition_variable is waiting for any of the success, cancel or failed to respond in sync mode
350 downloadCondition->cv.wait_for(
351 downloadLock, std::chrono::milliseconds(MAXIMUM_WAITING_PERIOD), [downloadCondition, downloadResult] {
352 return downloadCondition ? downloadResult->downloadSuccess.has_value() : false;
353 });
354 }
355 return true;
356 }
357
AddDownloadTask(const std::string & url,const std::shared_ptr<NetStackTask> & task,int32_t nodeId)358 void AddDownloadTask(const std::string& url, const std::shared_ptr<NetStackTask>& task, int32_t nodeId)
359 {
360 std::scoped_lock lock(httpTaskMutex_);
361 httpTaskMap_.emplace(url + std::to_string(nodeId), task);
362 }
363
Initialize()364 bool Initialize()
365 {
366 if (initialized_) {
367 return true;
368 }
369
370 std::lock_guard<std::mutex> lock(mutex_);
371 if (initialized_) {
372 return true;
373 }
374 if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
375 LOGE("Failed to initialize 'curl'");
376 return false;
377 }
378 initialized_ = true;
379 return true;
380 }
381
OnWritingMemory(void * data,size_t size,size_t memBytes,void * userData)382 static size_t OnWritingMemory(void* data, size_t size, size_t memBytes, void* userData)
383 {
384 // size is always 1, for more details see https://curl.haxx.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
385 auto& dataOut = *static_cast<std::vector<uint8_t>*>(userData);
386 auto chunkData = static_cast<uint8_t*>(data);
387 dataOut.insert(dataOut.end(), chunkData, chunkData + memBytes);
388 return memBytes;
389 }
390
GetProxy(ProxyInfo & proxy)391 static bool GetProxy(ProxyInfo& proxy)
392 {
393 NetManagerStandard::HttpProxy httpProxy;
394 NetManagerStandard::NetConnClient::GetInstance().GetDefaultHttpProxy(httpProxy);
395 proxy.host = httpProxy.GetHost();
396 proxy.port = httpProxy.GetPort();
397
398 auto exclusionList = httpProxy.GetExclusionList();
399 for (auto&& ex : exclusionList) {
400 proxy.exclusions.append(ex);
401 if (ex != exclusionList.back()) {
402 proxy.exclusions.append(",");
403 }
404 }
405 return true;
406 }
407
408 std::mutex mutex_;
409 bool initialized_ = false;
410 bool isCurl_ = false;
411 };
412
OHOS_ACE_CreateDownloadManager()413 extern "C" ACE_FORCE_EXPORT void* OHOS_ACE_CreateDownloadManager()
414 {
415 return new DownloadManagerImpl();
416 }
417 } // namespace OHOS::Ace
418