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 "listfile.h"
17
18 #include <fnmatch.h>
19 #include <memory>
20 #include <string>
21 #include <string_view>
22 #include <sys/stat.h>
23 #include <thread>
24 #include <tuple>
25
26 #include "file_utils.h"
27 #include "filemgmt_libhilog.h"
28
29 namespace OHOS::FileManagement::ModuleFileIO {
30 using namespace std;
31 using namespace OHOS::FileManagement::LibN;
32
33 thread_local OptionArgs g_optionArgs;
34
CheckSuffix(const vector<string> & suffixs)35 static bool CheckSuffix(const vector<string> &suffixs)
36 {
37 for (string suffix : suffixs) {
38 if (suffix.length() <= 1 || suffix.length() > MAX_SUFFIX_LENGTH) {
39 return false;
40 }
41 if (suffix[0] != '.') {
42 return false;
43 }
44 for (size_t i = 1; i < suffix.length(); i++) {
45 if (!isalnum(suffix[i])) {
46 return false;
47 }
48 }
49 }
50 return true;
51 }
52
GetFileFilterParam(const NVal & argv,FileFilter * filter)53 static bool GetFileFilterParam(const NVal &argv, FileFilter *filter)
54 {
55 bool ret = false;
56 if (argv.HasProp("suffix") && !argv.GetProp("suffix").TypeIs(napi_undefined)) {
57 vector<string> suffixs;
58 tie(ret, suffixs, ignore) = argv.GetProp("suffix").ToStringArray();
59 if (!ret) {
60 HILOGE("Failed to get suffix prop.");
61 return false;
62 }
63 if (!CheckSuffix(suffixs) || suffixs.size() == 0) {
64 HILOGE("Invalid suffix.");
65 return false;
66 }
67 filter->SetSuffix(suffixs);
68 }
69 if (argv.HasProp("displayName") && !argv.GetProp("displayName").TypeIs(napi_undefined)) {
70 vector<string> displayNames;
71 tie(ret, displayNames, ignore) = argv.GetProp("displayName").ToStringArray();
72 if (!ret) {
73 HILOGE("Failed to get displayname prop.");
74 return false;
75 }
76 if (displayNames.size() == 0) {
77 HILOGE("Invalid displayName.");
78 return false;
79 }
80 filter->SetDisplayName(displayNames);
81 }
82 if (argv.HasProp("fileSizeOver") && !argv.GetProp("fileSizeOver").TypeIs(napi_undefined)) {
83 int64_t fileSizeOver = 0;
84 tie(ret, fileSizeOver) = argv.GetProp("fileSizeOver").ToInt64();
85 if (!ret || fileSizeOver < 0) {
86 HILOGE("Failed to get fileSizeOver prop.");
87 return false;
88 }
89 filter->SetFileSizeOver(fileSizeOver);
90 }
91 if (argv.HasProp("lastModifiedAfter") && !argv.GetProp("lastModifiedAfter").TypeIs(napi_undefined)) {
92 double lastModifiedAfter = 0;
93 tie(ret, lastModifiedAfter) = argv.GetProp("lastModifiedAfter").ToDouble();
94 if (!ret || lastModifiedAfter < 0) {
95 HILOGE("Failed to get lastModifiedAfter prop.");
96 return false;
97 }
98 filter->SetLastModifiedAfter(lastModifiedAfter);
99 }
100 return true;
101 }
102
GetOptionParam(const NVal & argv,OptionArgs * optionArgs)103 static bool GetOptionParam(const NVal &argv, OptionArgs *optionArgs)
104 {
105 bool succ = false;
106 if (argv.HasProp("listNum")) {
107 tie(succ, optionArgs->listNum) = argv.GetProp("listNum").ToInt64(0);
108 if (!succ || optionArgs->listNum < 0) {
109 HILOGE("Failed to get listNum prop");
110 return false;
111 }
112 }
113
114 if (argv.HasProp("recursion")) {
115 tie(succ, optionArgs->recursion) = argv.GetProp("recursion").ToBool(false);
116 if (!succ) {
117 HILOGE("Failed to get recursion prop.");
118 return false;
119 }
120 }
121
122 if (argv.HasProp("filter")) {
123 NVal filterProp = argv.GetProp("filter");
124 if (!filterProp.TypeIs(napi_undefined)) {
125 auto ret = GetFileFilterParam(filterProp, &optionArgs->filter);
126 if (!ret) {
127 HILOGE("Failed to get filter prop.");
128 return false;
129 }
130 }
131 }
132 return true;
133 }
134
GetOptionArg(napi_env env,const NFuncArg & funcArg,OptionArgs & optionArgs,const string & path)135 static bool GetOptionArg(napi_env env, const NFuncArg &funcArg, OptionArgs &optionArgs, const string &path)
136 {
137 optionArgs.Clear();
138 optionArgs.path = path;
139 if (funcArg.GetArgc() == NARG_CNT::ONE) {
140 return true;
141 }
142 if (funcArg.GetArgc() >= NARG_CNT::TWO) {
143 auto options = NVal(env, funcArg[NARG_POS::SECOND]);
144 if (options.TypeIs(napi_object)) {
145 return GetOptionParam(options, &optionArgs);
146 } else if (options.TypeIs(napi_undefined) || options.TypeIs(napi_function)) {
147 return true;
148 }
149 }
150 return false;
151 }
152
FilterSuffix(const vector<string> & suffixs,const struct dirent & filename)153 static bool FilterSuffix(const vector<string> &suffixs, const struct dirent &filename)
154 {
155 if (filename.d_type == DT_DIR) {
156 return true;
157 }
158 size_t found = string(filename.d_name).rfind('.');
159 if (found == std::string::npos) {
160 return false;
161 }
162 string suffixStr = string(filename.d_name).substr(found);
163 for (const auto &iter : suffixs) {
164 if (iter == suffixStr) {
165 return true;
166 }
167 }
168 return false;
169 }
170
FilterDisplayname(const vector<string> & displaynames,const struct dirent & filename)171 static bool FilterDisplayname(const vector<string> &displaynames, const struct dirent &filename)
172 {
173 for (const auto &iter : displaynames) {
174 int ret = fnmatch(iter.c_str(), filename.d_name, FNM_PATHNAME | FNM_PERIOD);
175 if (ret == 0) {
176 return true;
177 }
178 }
179 return false;
180 }
181
FilterFilesizeOver(const int64_t fFileSizeOver,const struct dirent & filename)182 static bool FilterFilesizeOver(const int64_t fFileSizeOver, const struct dirent &filename)
183 {
184 if (fFileSizeOver < 0) {
185 return true;
186 }
187 struct stat info;
188 string stPath = (g_optionArgs.path + '/' + string(filename.d_name));
189 int32_t res = stat(stPath.c_str(), &info);
190 if (res != 0) {
191 HILOGE("Failed to stat file.");
192 return false;
193 }
194 if (info.st_size > fFileSizeOver) {
195 return true;
196 }
197 return false;
198 }
199
FilterLastModifyTime(const double lastModifiedAfter,const struct dirent & filename)200 static bool FilterLastModifyTime(const double lastModifiedAfter, const struct dirent &filename)
201 {
202 if (lastModifiedAfter < 0) {
203 return true;
204 }
205 struct stat info;
206 string stPath = g_optionArgs.path + '/' + string(filename.d_name);
207 int32_t res = stat(stPath.c_str(), &info);
208 if (res != 0) {
209 HILOGE("Failed to stat file.");
210 return false;
211 }
212 if (static_cast<double>(info.st_mtime) > lastModifiedAfter) {
213 return true;
214 }
215 return false;
216 }
217
FilterResult(const struct dirent & filename)218 static bool FilterResult(const struct dirent &filename)
219 {
220 vector<string> fSuffixs = g_optionArgs.filter.GetSuffix();
221 if (!FilterSuffix(fSuffixs, filename) && fSuffixs.size() > 0) {
222 return false;
223 }
224 vector<string> fDisplaynames = g_optionArgs.filter.GetDisplayName();
225 if (!FilterDisplayname(fDisplaynames, filename) && fDisplaynames.size() > 0) {
226 return false;
227 }
228 int64_t fFileSizeOver = g_optionArgs.filter.GetFileSizeOver();
229 if (!FilterFilesizeOver(fFileSizeOver, filename)) {
230 return false;
231 }
232 double fLastModifiedAfter = g_optionArgs.filter.GetLastModifiedAfter();
233 if (!FilterLastModifyTime(fLastModifiedAfter, filename)) {
234 return false;
235 }
236 g_optionArgs.countNum++;
237 return true;
238 }
239
FilterFunc(const struct dirent * filename)240 static int32_t FilterFunc(const struct dirent *filename)
241 {
242 if (string_view(filename->d_name) == "." || string_view(filename->d_name) == "..") {
243 return FILTER_DISMATCH;
244 }
245
246 if (g_optionArgs.countNum < g_optionArgs.listNum || g_optionArgs.listNum == 0) {
247 if ((filename->d_type == DT_DIR && g_optionArgs.recursion) || FilterResult(*filename)) {
248 return FILTER_MATCH;
249 }
250 }
251 return FILTER_DISMATCH;
252 }
253
Deleter(struct NameListArg * arg)254 static void Deleter(struct NameListArg *arg)
255 {
256 for (int i = 0; i < arg->direntNum; i++) {
257 free((arg->namelist)[i]);
258 (arg->namelist)[i] = nullptr;
259 }
260 free(arg->namelist);
261 arg->namelist = nullptr;
262 delete arg;
263 arg = nullptr;
264 }
265
FilterFileRes(const string & path,vector<string> & dirents)266 static int FilterFileRes(const string &path, vector<string> &dirents)
267 {
268 unique_ptr<struct NameListArg, decltype(Deleter)*> pNameList = { new (nothrow) struct NameListArg, Deleter };
269 if (!pNameList) {
270 HILOGE("Failed to request heap memory.");
271 return ENOMEM;
272 }
273 int num = scandir(path.c_str(), &(pNameList->namelist), FilterFunc, nullptr);
274 if (num < 0) {
275 HILOGE("Failed to scan dir");
276 return errno;
277 }
278 pNameList->direntNum = num;
279 for (int i = 0; i < num; i++) {
280 dirents.emplace_back(pNameList->namelist[i]->d_name);
281 }
282 return ERRNO_NOERR;
283 }
284
RecursiveFunc(const string & path,vector<string> & dirents)285 static int RecursiveFunc(const string &path, vector<string> &dirents)
286 {
287 unique_ptr<struct NameListArg, decltype(Deleter)*> pNameList = { new (nothrow) struct NameListArg, Deleter };
288 if (!pNameList) {
289 HILOGE("Failed to request heap memory.");
290 return ENOMEM;
291 }
292 int num = scandir(path.c_str(), &(pNameList->namelist), FilterFunc, nullptr);
293 if (num < 0) {
294 HILOGE("Failed to scan dir");
295 return errno;
296 }
297 pNameList->direntNum = num;
298 for (int i = 0; i < num; i++) {
299 if ((*(pNameList->namelist[i])).d_type == DT_REG) {
300 dirents.emplace_back(path + '/' + pNameList->namelist[i]->d_name);
301 } else if ((*(pNameList->namelist[i])).d_type == DT_DIR) {
302 string pathTemp = g_optionArgs.path;
303 g_optionArgs.path += '/' + string((*(pNameList->namelist[i])).d_name);
304 int ret = RecursiveFunc(g_optionArgs.path, dirents);
305 if (ret != ERRNO_NOERR) {
306 return ret;
307 }
308 g_optionArgs.path = pathTemp;
309 }
310 }
311 return ERRNO_NOERR;
312 }
313
DoListFileVector2NV(napi_env env,const string & path,vector<string> & dirents,bool recursion)314 static napi_value DoListFileVector2NV(napi_env env, const string &path, vector<string> &dirents, bool recursion)
315 {
316 if (recursion) {
317 for (size_t i = 0; i < dirents.size(); i++) {
318 dirents[i] = dirents[i].substr(path.length());
319 }
320 }
321 return NVal::CreateArrayString(env, dirents).val_;
322 }
323
Sync(napi_env env,napi_callback_info info)324 napi_value ListFile::Sync(napi_env env, napi_callback_info info)
325 {
326 NFuncArg funcArg(env, info);
327 if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::TWO)) {
328 HILOGE("Number of arguments unmatched");
329 NError(EINVAL).ThrowErr(env);
330 return nullptr;
331 }
332 auto [succPath, path, unused] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8StringPath();
333 if (!succPath) {
334 HILOGE("Invalid path");
335 NError(EINVAL).ThrowErr(env);
336 return nullptr;
337 }
338 if (!GetOptionArg(env, funcArg, g_optionArgs, string(path.get()))) {
339 HILOGE("Invalid options");
340 NError(EINVAL).ThrowErr(env);
341 return nullptr;
342 }
343 vector<string> direntsRes;
344 int ret = 0;
345 ret = g_optionArgs.recursion ? RecursiveFunc(path.get(), direntsRes) : FilterFileRes(path.get(), direntsRes);
346 if (ret) {
347 NError(ret).ThrowErr(env);
348 return nullptr;
349 }
350 auto res = DoListFileVector2NV(env, string(path.get()), direntsRes, g_optionArgs.recursion);
351 g_optionArgs.Clear();
352 return res;
353 }
354
Async(napi_env env,napi_callback_info info)355 napi_value ListFile::Async(napi_env env, napi_callback_info info)
356 {
357 NFuncArg funcArg(env, info);
358 if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::THREE)) {
359 HILOGE("Number of arguments unmatched");
360 NError(EINVAL).ThrowErr(env);
361 return nullptr;
362 }
363
364 auto [succPath, path, unused] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8StringPath();
365 if (!succPath) {
366 HILOGE("Invalid path");
367 NError(EINVAL).ThrowErr(env);
368 return nullptr;
369 }
370
371 OptionArgs optionArgsTmp = {};
372 if (!GetOptionArg(env, funcArg, optionArgsTmp, string(path.get()))) {
373 HILOGE("Invalid options");
374 NError(EINVAL).ThrowErr(env);
375 return nullptr;
376 }
377
378 auto arg = CreateSharedPtr<ListFileArgs>();
379 if (arg == nullptr) {
380 HILOGE("Failed to request heap memory.");
381 NError(ENOMEM).ThrowErr(env);
382 return nullptr;
383 }
384 auto cbExec = [arg, optionArgsTmp]() -> NError {
385 g_optionArgs = optionArgsTmp;
386 int ret = 0;
387 ret = g_optionArgs.recursion ? RecursiveFunc(g_optionArgs.path, arg->dirents) :
388 FilterFileRes(g_optionArgs.path, arg->dirents);
389 g_optionArgs.Clear();
390 return ret ? NError(ret) : NError(ERRNO_NOERR);
391 };
392
393 auto cbCompl = [arg, optionArgsTmp, path = string(path.get())](napi_env env, NError err) -> NVal {
394 if (err) {
395 return { env, err.GetNapiErr(env) };
396 }
397 return { env, DoListFileVector2NV(env, path, arg->dirents, optionArgsTmp.recursion) };
398 };
399
400 NVal thisVar(env, funcArg.GetThisVar());
401
402 if (funcArg.GetArgc() == NARG_CNT::ONE || (funcArg.GetArgc() == NARG_CNT::TWO &&
403 !NVal(env, funcArg[NARG_POS::SECOND]).TypeIs(napi_function))) {
404 return NAsyncWorkPromise(env, thisVar).Schedule(LIST_FILE_PRODUCE_NAME, cbExec, cbCompl).val_;
405 } else {
406 NVal cb(env, funcArg[((funcArg.GetArgc() == NARG_CNT::TWO) ? NARG_POS::SECOND : NARG_POS::THIRD)]);
407 return NAsyncWorkCallback(env, thisVar, cb).Schedule(LIST_FILE_PRODUCE_NAME, cbExec, cbCompl).val_;
408 }
409 }
410 } // namespace OHOS::FileManagement::ModuleFileIO