• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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