• 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 "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