• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <condition_variable>
17 #include <cstdint>
18 #include <functional>
19 #include <memory>
20 #include <mutex>
21 #include <optional>
22 #include <string>
23 #include <vector>
24 
25 #include "curl/curl.h"
26 #include "hilog/log.h"
27 #include "http_client.h"
28 #include "http_proxy.h"
29 #include "net_conn_client.h"
30 
31 #include "base/network/download_manager.h"
32 
33 #define ACE_FORCE_EXPORT __attribute__((visibility("default")))
34 
35 #define ACE_CURL_EASY_SET_OPTION(handle, opt, data)            \
36     do {                                                       \
37         CURLcode result = curl_easy_setopt(handle, opt, data); \
38         if (result != CURLE_OK) {                              \
39             return false;                                      \
40         }                                                      \
41     } while (0)
42 
43 namespace OHOS::Ace {
44 namespace {
45 constexpr int32_t MAXIMUM_WAITING_PERIOD = 2800;
46 constexpr OHOS::HiviewDFX::HiLogLabel ACE_DOWNLOAD_MANAGER = { LOG_CORE, 0xC0393A, "DownloadManager" };
47 #define LOGI(fmt, ...) \
48     (void)OHOS::HiviewDFX::HiLog::Info(ACE_DOWNLOAD_MANAGER, "[%{public}d]" fmt, __LINE__, ##__VA_ARGS__)
49 #define LOGE(fmt, ...) \
50     (void)OHOS::HiviewDFX::HiLog::Error(ACE_DOWNLOAD_MANAGER, "[%{public}d]" fmt, __LINE__, ##__VA_ARGS__)
51 } // namespace
52 
53 // For sync download tasks, this period may cause image not able to be loaded.
54 // System detects appFreeze after 3s, which has higher priority
55 using NetStackRequest = NetStack::HttpClient::HttpClientRequest;
56 using NetStackResponse = NetStack::HttpClient::HttpClientResponse;
57 using NetStackError = NetStack::HttpClient::HttpClientError;
58 
59 class ACE_FORCE_EXPORT DownloadManagerImpl : public DownloadManager {
60 public:
61     DownloadManagerImpl() = default;
~DownloadManagerImpl()62     ~DownloadManagerImpl()
63     {
64         if (isCurl_) {
65             curl_global_cleanup();
66         }
67     }
68 
Download(const std::string & url,std::vector<uint8_t> & dataOut)69     bool Download(const std::string& url, std::vector<uint8_t>& dataOut) override
70     {
71         // when calling, it is necessary to set it to true and call curl clean up method
72         // during download manager ohos object destruction
73         isCurl_ = true;
74         if (!Initialize()) {
75             return false;
76         }
77 
78         std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> handle(curl_easy_init(), &curl_easy_cleanup);
79         if (!handle) {
80             return false;
81         }
82 
83         dataOut.clear();
84         std::string errorStr;
85         errorStr.reserve(CURL_ERROR_SIZE);
86 
87         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_URL, url.c_str());
88         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEFUNCTION, OnWritingMemory);
89         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEDATA, &dataOut);
90         // Some servers don't like requests that are made without a user-agent field, so we provide one
91         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_USERAGENT, "libcurl-agent/1.0");
92         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_URL, url.c_str());
93 #if !defined(PREVIEW)
94         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_CAINFO, "/etc/ssl/certs/cacert.pem");
95 #endif
96         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_VERBOSE, 1L);
97         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_ERRORBUFFER, errorStr.data());
98 
99         ProxyInfo proxy;
100         if (GetProxy(proxy)) {
101             ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_PROXY, proxy.host.c_str());
102             ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_PROXYPORT, proxy.port);
103             if (!proxy.exclusions.empty()) {
104                 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_NOPROXY, proxy.exclusions.c_str());
105             }
106             ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
107             ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HTTPPROXYTUNNEL, 1L);
108         }
109 
110 #if defined(IOS_PLATFORM) || defined(ANDROID_PLATFORM) || defined(PREVIEW)
111         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_SSL_VERIFYPEER, 0L);
112         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_SSL_VERIFYHOST, 0L);
113 #endif
114 
115         CURLcode result = curl_easy_perform(handle.get());
116         if (result != CURLE_OK) {
117             LOGE("Failed to download, url: [%{private}s], [%{public}s]", url.c_str(), curl_easy_strerror(result));
118             if (!errorStr.empty()) {
119                 LOGE("Failed to download reason: [%{public}s]", errorStr.c_str());
120             }
121             dataOut.clear();
122             return false;
123         }
124         dataOut.shrink_to_fit();
125         return true;
126     }
127 
DownloadAsync(DownloadCallback && downloadCallback,const std::string & url,int32_t instanceId)128     bool DownloadAsync(DownloadCallback&& downloadCallback, const std::string& url, int32_t instanceId) override
129     {
130         NetStackRequest httpReq;
131         httpReq.SetURL(url);
132         auto& session = NetStack::HttpClient::HttpSession::GetInstance();
133         auto task = session.CreateTask(httpReq);
134         task->OnSuccess([successCallback = std::move(downloadCallback.successCallback), instanceId](
135                             const NetStackRequest& request, const NetStackResponse& response) {
136             LOGI("Async http task of url [%{private}s] success", request.GetURL().c_str());
137             successCallback(std::move(response.GetResult()), true, instanceId);
138         });
139         task->OnCancel([cancelCallback = std::move(downloadCallback.cancelCallback), instanceId](
140                            const NetStackRequest& request, const NetStackResponse& response) {
141             LOGI("Async Http task of url [%{private}s] cancelled by netStack", request.GetURL().c_str());
142             std::string errorMsg;
143             errorMsg.append("Http task of url ");
144             errorMsg.append(request.GetURL());
145             errorMsg.append(" cancelled by netStack");
146             cancelCallback(errorMsg, true, instanceId);
147         });
148         task->OnFail([failCallback = std::move(downloadCallback.failCallback), instanceId](
149                          const NetStackRequest& request, const NetStackResponse& response, const NetStackError& error) {
150             LOGI("Async http task of url [%{private}s] failed, response code %{public}d, msg from netStack: "
151                  "[%{public}s]",
152                 request.GetURL().c_str(), response.GetResponseCode(), error.GetErrorMessage().c_str());
153             std::string errorMsg;
154             errorMsg.append("Http task of url ");
155             errorMsg.append(request.GetURL());
156             errorMsg.append(" failed, response code ");
157             auto responseCode = response.GetResponseCode();
158             errorMsg.append(std::to_string(responseCode));
159             errorMsg.append(", msg from netStack: ");
160             errorMsg.append(error.GetErrorMessage());
161             failCallback(errorMsg, true, instanceId);
162         });
163         auto result = task->Start();
164         LOGI("Task of netstack with src [%{private}s] [%{public}s]", url.c_str(),
165             result ? " started on another thread successfully"
166                    : " failed to start on another thread, please check netStack log");
167         return result;
168     }
169 
DownloadSync(DownloadCallback && downloadCallback,const std::string & url,int32_t instanceId)170     bool DownloadSync(DownloadCallback&& downloadCallback, const std::string& url, int32_t instanceId) override
171     {
172         LOGI("DownloadSync task of [%{private}s] start", url.c_str());
173         NetStackRequest httpReq;
174         httpReq.SetURL(url);
175         auto& session = NetStack::HttpClient::HttpSession::GetInstance();
176         auto task = session.CreateTask(httpReq);
177         std::shared_ptr<DownloadCondition> downloadCondition = std::make_shared<DownloadCondition>();
178         task->OnSuccess([downloadCondition](const NetStackRequest& request, const NetStackResponse& response) {
179             LOGI("Sync Http task of url [%{private}s] success", request.GetURL().c_str());
180             {
181                 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
182                 downloadCondition->downloadSuccess = true;
183                 downloadCondition->dataOut = std::move(response.GetResult());
184             }
185             downloadCondition->cv.notify_all();
186         });
187         task->OnCancel([downloadCondition](const NetStackRequest& request, const NetStackResponse& response) {
188             LOGI("Sync Http task of url [%{private}s] cancelled", request.GetURL().c_str());
189             {
190                 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
191                 downloadCondition->errorMsg.append("Http task of url ");
192                 downloadCondition->errorMsg.append(request.GetURL());
193                 downloadCondition->errorMsg.append(" cancelled by netStack");
194                 downloadCondition->downloadSuccess = false;
195             }
196             downloadCondition->cv.notify_all();
197         });
198         task->OnFail([downloadCondition](
199                          const NetStackRequest& request, const NetStackResponse& response, const NetStackError& error) {
200             LOGI(
201                 "Sync Http task of url [%{private}s] failed, response code %{public}d, msg from netStack: [%{public}s]",
202                 request.GetURL().c_str(), response.GetResponseCode(), error.GetErrorMessage().c_str());
203             {
204                 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
205                 downloadCondition->errorMsg.append("Http task of url ");
206                 downloadCondition->errorMsg.append(request.GetURL());
207                 downloadCondition->errorMsg.append(" failed, response code ");
208                 auto responseCode = response.GetResponseCode();
209                 downloadCondition->errorMsg.append(std::to_string(responseCode));
210                 downloadCondition->errorMsg.append(", msg from netStack: ");
211                 downloadCondition->errorMsg.append(error.GetErrorMessage());
212                 downloadCondition->downloadSuccess = false;
213             }
214             downloadCondition->cv.notify_all();
215         });
216         auto result = task->Start();
217         return HandleDownloadResult(result, std::move(downloadCallback), downloadCondition, instanceId, url);
218     }
219 
220 private:
221     struct ProxyInfo {
222         std::string host;
223         int32_t port = 0;
224         std::string exclusions;
225     };
226 
HandleDownloadResult(bool result,DownloadCallback && downloadCallback,const std::shared_ptr<DownloadCondition> & downloadCondition,int32_t instanceId,const std::string & url)227     bool HandleDownloadResult(bool result, DownloadCallback&& downloadCallback,
228         const std::shared_ptr<DownloadCondition>& downloadCondition, int32_t instanceId, const std::string& url)
229     {
230         if (!result) {
231             return result;
232         }
233         {
234             std::unique_lock<std::mutex> downloadLock(downloadCondition->downloadMutex);
235             // condition_variable is waiting for any of the success, cancel or failed to respond in sync mode
236             downloadCondition->cv.wait_for(
237                 downloadLock, std::chrono::milliseconds(MAXIMUM_WAITING_PERIOD), [downloadCondition] {
238                     return downloadCondition ? downloadCondition->downloadSuccess.has_value() : false;
239                 });
240         }
241         if (!downloadCondition->downloadSuccess.has_value()) {
242             LOGI("Sync Task of netstack with url [%{private}s] maximum waiting period exceed", url.c_str());
243         }
244         if (downloadCondition->downloadSuccess.value_or(false)) {
245             downloadCallback.successCallback(std::move(downloadCondition->dataOut), false, instanceId);
246         } else {
247             downloadCallback.failCallback(downloadCondition->errorMsg, false, instanceId);
248         }
249         return true;
250     }
251 
Initialize()252     bool Initialize()
253     {
254         if (initialized_) {
255             return true;
256         }
257 
258         std::lock_guard<std::mutex> lock(mutex_);
259         if (initialized_) {
260             return true;
261         }
262         if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
263             LOGE("Failed to initialize 'curl'");
264             return false;
265         }
266         initialized_ = true;
267         return true;
268     }
269 
OnWritingMemory(void * data,size_t size,size_t memBytes,void * userData)270     static size_t OnWritingMemory(void* data, size_t size, size_t memBytes, void* userData)
271     {
272         // size is always 1, for more details see https://curl.haxx.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
273         auto& dataOut = *static_cast<std::vector<uint8_t>*>(userData);
274         auto chunkData = static_cast<uint8_t*>(data);
275         dataOut.insert(dataOut.end(), chunkData, chunkData + memBytes);
276         return memBytes;
277     }
278 
GetProxy(ProxyInfo & proxy)279     static bool GetProxy(ProxyInfo& proxy)
280     {
281         NetManagerStandard::HttpProxy httpProxy;
282         NetManagerStandard::NetConnClient::GetInstance().GetDefaultHttpProxy(httpProxy);
283         proxy.host = httpProxy.GetHost();
284         proxy.port = httpProxy.GetPort();
285 
286         auto exclusionList = httpProxy.GetExclusionList();
287         for (auto&& ex : exclusionList) {
288             proxy.exclusions.append(ex);
289             if (ex != exclusionList.back()) {
290                 proxy.exclusions.append(",");
291             }
292         }
293         return true;
294     }
295 
296     std::mutex mutex_;
297     bool initialized_ = false;
298     bool isCurl_ = false;
299 };
300 
OHOS_ACE_CreateDownloadManager()301 extern "C" ACE_FORCE_EXPORT void* OHOS_ACE_CreateDownloadManager()
302 {
303     return new DownloadManagerImpl();
304 }
305 } // namespace OHOS::Ace
306