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