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