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