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