1 /*
2 * Copyright (c) 2023 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 "legacy/request_manager.h"
17
18 #include <cerrno>
19 #include <climits>
20 #include <cstdlib>
21
22 #include "ability.h"
23 #include "legacy/download_task.h"
24 #include "log.h"
25 #include "napi_base_context.h"
26 #include "napi_utils.h"
27 #include "uv.h"
28
29 namespace OHOS::Request::Legacy {
30 std::map<std::string, RequestManager::DownloadDescriptor> RequestManager::downloadDescriptors_;
31 std::mutex RequestManager::lock_;
32 std::atomic<uint32_t> RequestManager::taskId_;
33
IsLegacy(napi_env env,napi_callback_info info)34 bool RequestManager::IsLegacy(napi_env env, napi_callback_info info)
35 {
36 size_t argc = DOWNLOAD_ARGC;
37 napi_value argv[DOWNLOAD_ARGC]{};
38 NAPI_CALL_BASE(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr), false);
39 auto successCb = NapiUtils::GetNamedProperty(env, argv[0], "success");
40 auto failCb = NapiUtils::GetNamedProperty(env, argv[0], "fail");
41 auto completeCb = NapiUtils::GetNamedProperty(env, argv[0], "complete");
42 return successCb || failCb || completeCb;
43 }
44
GetTaskToken()45 std::string RequestManager::GetTaskToken()
46 {
47 uint32_t id = taskId_++;
48 return "Download-Task-" + std::to_string(id);
49 }
50
CallFunctionAsync(napi_env env,napi_ref func,const ArgsGenerator & generator)51 void RequestManager::CallFunctionAsync(napi_env env, napi_ref func, const ArgsGenerator &generator)
52 {
53 uv_loop_s *loop = nullptr;
54 napi_get_uv_event_loop(env, &loop);
55 if (loop == nullptr) {
56 REQUEST_HILOGE("Failed to get uv event loop");
57 return;
58 }
59 auto *work = new (std::nothrow) uv_work_t;
60 if (work == nullptr) {
61 REQUEST_HILOGE("Failed to create uv work");
62 return;
63 }
64 auto *data = new (std::nothrow) CallFunctionData;
65 if (data == nullptr) {
66 REQUEST_HILOGE("Failed to create CallFunctionData");
67 delete work;
68 return;
69 }
70 data->env_ = env;
71 data->func_ = func;
72 data->generator_ = generator;
73 work->data = data;
74
75 uv_queue_work(
76 loop, work, [](uv_work_t *work) {},
77 [](uv_work_t *work, int st) {
78 int argc{};
79 auto data = static_cast<CallFunctionData *>(work->data);
80 napi_handle_scope scope = nullptr;
81 napi_open_handle_scope(data->env_, &scope);
82 napi_value argv[MAX_CB_ARGS]{};
83 napi_ref recv{};
84 data->generator_(data->env_, &recv, argc, argv);
85 napi_value callback{};
86 napi_get_reference_value(data->env_, data->func_, &callback);
87 napi_value thiz{};
88 napi_get_reference_value(data->env_, recv, &thiz);
89 napi_value result{};
90 napi_call_function(data->env_, thiz, callback, argc, argv, &result);
91 napi_delete_reference(data->env_, data->func_);
92 napi_delete_reference(data->env_, recv);
93 napi_close_handle_scope(data->env_, scope);
94 delete work;
95 delete data;
96 });
97 }
98
OnTaskDone(const std::string & token,bool successful,const std::string & errMsg)99 void RequestManager::OnTaskDone(const std::string &token, bool successful, const std::string &errMsg)
100 {
101 REQUEST_HILOGI("token=%{public}s", token.c_str());
102 DownloadDescriptor descriptor{};
103 {
104 std::lock_guard<std::mutex> lockGuard(lock_);
105 auto it = downloadDescriptors_.find(token);
106 if (it == downloadDescriptors_.end()) {
107 return;
108 }
109 descriptor = it->second;
110 downloadDescriptors_.erase(it);
111 }
112
113 if (successful && descriptor.successCb_) {
114 CallFunctionAsync(descriptor.env_, descriptor.successCb_,
115 [descriptor](napi_env env, napi_ref *recv, int &argc, napi_value *argv) {
116 *recv = descriptor.this_;
117 argc = SUCCESS_CB_ARGC;
118 argv[0] = NapiUtils::CreateObject(descriptor.env_);
119 NapiUtils::SetStringPropertyUtf8(descriptor.env_, argv[0], "uri", URI_PREFIX + descriptor.filename_);
120 });
121 }
122 if (!successful && descriptor.failCb_) {
123 CallFunctionAsync(descriptor.env_, descriptor.failCb_,
124 [descriptor, errMsg](napi_env env, napi_ref *recv, int &argc, napi_value *argv) {
125 *recv = descriptor.this_;
126 argc = FAIL_CB_ARGC;
127 argv[0] = NapiUtils::Convert2JSValue(descriptor.env_, errMsg);
128 argv[1] = NapiUtils::Convert2JSValue(descriptor.env_, FAIL_CB_DOWNLOAD_ERROR);
129 });
130 }
131 delete descriptor.task_;
132 }
133
GetFilenameFromUrl(std::string & url)134 std::string RequestManager::GetFilenameFromUrl(std::string &url)
135 {
136 auto pos = url.rfind('/');
137 if (pos != std::string::npos) {
138 return url.substr(pos + 1);
139 }
140 return url;
141 }
142
GetCacheDir(napi_env env)143 std::string RequestManager::GetCacheDir(napi_env env)
144 {
145 auto ability = AbilityRuntime::GetCurrentAbility(env);
146 if (ability == nullptr) {
147 REQUEST_HILOGE("GetCurrentAbility failed.");
148 return {};
149 }
150 auto abilityContext = ability->GetAbilityContext();
151 if (abilityContext == nullptr) {
152 REQUEST_HILOGE("GetAbilityContext failed.");
153 return {};
154 }
155 return abilityContext->GetCacheDir();
156 }
157
ParseHeader(napi_env env,napi_value option)158 std::vector<std::string> RequestManager::ParseHeader(napi_env env, napi_value option)
159 {
160 if (!NapiUtils::HasNamedProperty(env, option, "header")) {
161 REQUEST_HILOGD("no header present");
162 return {};
163 }
164 napi_value header = NapiUtils::GetNamedProperty(env, option, "header");
165 if (NapiUtils::GetValueType(env, header) != napi_object) {
166 REQUEST_HILOGE("header type is not object");
167 return {};
168 }
169 auto names = NapiUtils::GetPropertyNames(env, header);
170 REQUEST_HILOGD("names size=%{public}d", static_cast<int32_t>(names.size()));
171 std::vector<std::string> headerVector;
172 for (const auto &name : names) {
173 auto value = NapiUtils::Convert2String(env, header, name);
174 headerVector.push_back(name + ":" + value);
175 }
176 return headerVector;
177 }
178
ParseOption(napi_env env,napi_value option)179 DownloadTask::DownloadOption RequestManager::ParseOption(napi_env env, napi_value option)
180 {
181 DownloadTask::DownloadOption downloadOption;
182 downloadOption.url_ = NapiUtils::Convert2String(env, option, "url");
183 downloadOption.fileDir_ = GetCacheDir(env);
184
185 downloadOption.filename_ = NapiUtils::Convert2String(env, option, "filename");
186 if (downloadOption.filename_.empty()) {
187 downloadOption.filename_ = GetFilenameFromUrl(downloadOption.url_);
188 int i = 0;
189 auto filename = downloadOption.filename_;
190 while (access((downloadOption.fileDir_ + '/' + filename).c_str(), F_OK) == 0) {
191 i++;
192 filename = downloadOption.filename_ + std::to_string(i);
193 }
194 downloadOption.filename_ = filename;
195 }
196
197 downloadOption.header_ = ParseHeader(env, option);
198
199 return downloadOption;
200 }
201
IsPathValid(const std::string & dir,const std::string & filename)202 bool RequestManager::IsPathValid(const std::string &dir, const std::string &filename)
203 {
204 auto filepath = dir + '/' + filename;
205 auto fileDirectory = filepath.substr(0, filepath.rfind('/'));
206 char resolvedPath[PATH_MAX] = { 0 };
207 if (realpath(fileDirectory.c_str(), resolvedPath) && !strncmp(resolvedPath, dir.c_str(), dir.length())) {
208 return true;
209 }
210 REQUEST_HILOGE("file path is invalid, errno=%{public}d", errno);
211 return false;
212 }
213
HasSameFilename(const std::string & filename)214 bool RequestManager::HasSameFilename(const std::string &filename)
215 {
216 std::lock_guard<std::mutex> lockGuard(lock_);
217 for (const auto &element : downloadDescriptors_) {
218 if (element.second.filename_ == filename) {
219 return true;
220 }
221 }
222 return false;
223 }
224
CallFailCallback(napi_env env,napi_value object,const std::string & msg)225 void RequestManager::CallFailCallback(napi_env env, napi_value object, const std::string &msg)
226 {
227 auto callback = NapiUtils::GetNamedProperty(env, object, "fail");
228 if (callback != nullptr) {
229 REQUEST_HILOGI("call fail of download");
230 napi_value result[FAIL_CB_ARGC]{};
231 result[0] = NapiUtils::Convert2JSValue(env, msg);
232 result[1] = NapiUtils::Convert2JSValue(env, FAIL_CB_DOWNLOAD_ERROR);
233 NapiUtils::CallFunction(env, object, callback, FAIL_CB_ARGC, result);
234 }
235 }
236
CallSuccessCallback(napi_env env,napi_value object,const std::string & token)237 void RequestManager::CallSuccessCallback(napi_env env, napi_value object, const std::string &token)
238 {
239 auto successCb = NapiUtils::GetNamedProperty(env, object, "success");
240 if (successCb != nullptr) {
241 REQUEST_HILOGI("call success of download");
242 auto responseObject = NapiUtils::CreateObject(env);
243 NapiUtils::SetStringPropertyUtf8(env, responseObject, "token", token);
244 NapiUtils::CallFunction(env, object, successCb, 1, &responseObject);
245 }
246 }
247
Download(napi_env env,napi_callback_info info)248 napi_value RequestManager::Download(napi_env env, napi_callback_info info)
249 {
250 size_t argc = DOWNLOAD_ARGC;
251 napi_value argv[DOWNLOAD_ARGC]{};
252 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
253 napi_value res = NapiUtils::GetUndefined(env);
254
255 auto option = ParseOption(env, argv[0]);
256 if (!IsPathValid(option.fileDir_, option.filename_)) {
257 CallFailCallback(env, argv[0], "invalid file name");
258 return res;
259 }
260 if (HasSameFilename(option.filename_)) {
261 CallFailCallback(env, argv[0], "filename conflict");
262 return res;
263 }
264
265 auto token = GetTaskToken();
266 auto *task = new (std::nothrow) DownloadTask(token, option, OnTaskDone);
267 if (task == nullptr) {
268 return res;
269 }
270 DownloadDescriptor descriptor{ task, option.filename_, env };
271 {
272 std::lock_guard<std::mutex> lockGuard(lock_);
273 downloadDescriptors_[token] = descriptor;
274 }
275 CallSuccessCallback(env, argv[0], token);
276 task->Start();
277 return res;
278 }
279
OnDownloadComplete(napi_env env,napi_callback_info info)280 napi_value RequestManager::OnDownloadComplete(napi_env env, napi_callback_info info)
281 {
282 size_t argc = DOWNLOAD_ARGC;
283 napi_value argv[DOWNLOAD_ARGC]{};
284 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
285 napi_value res = NapiUtils::GetUndefined(env);
286
287 auto token = NapiUtils::Convert2String(env, argv[0], "token");
288 {
289 std::lock_guard<std::mutex> lockGuard(lock_);
290 auto it = downloadDescriptors_.find(token);
291 if (it != downloadDescriptors_.end()) {
292 REQUEST_HILOGI("find token=%{public}s", token.c_str());
293 it->second.env_ = env;
294 napi_create_reference(env, argv[0], 1, &it->second.this_);
295 auto callback = NapiUtils::GetNamedProperty(env, argv[0], "success");
296 napi_create_reference(env, callback, 1, &it->second.successCb_);
297 callback = NapiUtils::GetNamedProperty(env, argv[0], "fail");
298 napi_create_reference(env, callback, 1, &it->second.failCb_);
299 return res;
300 }
301 }
302 REQUEST_HILOGE("%{public}s is not exist", token.c_str());
303 auto callback = NapiUtils::GetNamedProperty(env, argv[0], "fail");
304 if (callback != nullptr) {
305 napi_value result[FAIL_CB_ARGC]{};
306 std::string message = "Download task doesn't exist!";
307 result[0] = NapiUtils::Convert2JSValue(env, message);
308 result[1] = NapiUtils::Convert2JSValue(env, FAIL_CB_TASK_NOT_EXIST);
309 NapiUtils::CallFunction(env, argv[0], callback, FAIL_CB_ARGC, result);
310 }
311 return res;
312 }
313 } // namespace OHOS::Request::Legacy