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