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