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.path = path;
138 if (funcArg.GetArgc() == NARG_CNT::ONE) {
139 return true;
140 }
141 if (funcArg.GetArgc() >= NARG_CNT::TWO) {
142 auto options = NVal(env, funcArg[NARG_POS::SECOND]);
143 if (options.TypeIs(napi_object)) {
144 return GetOptionParam(options, &optionArgs);
145 } else if (options.TypeIs(napi_undefined) || options.TypeIs(napi_function)) {
146 return true;
147 }
148 }
149 return false;
150 }
151
FilterSuffix(const vector<string> & suffixs,const struct dirent & filename)152 static bool FilterSuffix(const vector<string> &suffixs, const struct dirent &filename)
153 {
154 if (filename.d_type == DT_DIR) {
155 return true;
156 }
157 size_t found = string(filename.d_name).rfind('.');
158 if (found == std::string::npos) {
159 return false;
160 }
161 string suffixStr = string(filename.d_name).substr(found);
162 for (const auto &iter : suffixs) {
163 if (iter == suffixStr) {
164 return true;
165 }
166 }
167 return false;
168 }
169
FilterDisplayname(const vector<string> & displaynames,const struct dirent & filename)170 static bool FilterDisplayname(const vector<string> &displaynames, const struct dirent &filename)
171 {
172 for (const auto &iter : displaynames) {
173 int ret = fnmatch(iter.c_str(), filename.d_name, FNM_PATHNAME | FNM_PERIOD);
174 if (ret == 0) {
175 return true;
176 }
177 }
178 return false;
179 }
180
FilterFilesizeOver(const int64_t fFileSizeOver,const struct dirent & filename)181 static bool FilterFilesizeOver(const int64_t fFileSizeOver, const struct dirent &filename)
182 {
183 if (fFileSizeOver < 0) {
184 return true;
185 }
186 struct stat info;
187 string stPath = (g_optionArgs.path + '/' + string(filename.d_name));
188 int32_t res = stat(stPath.c_str(), &info);
189 if (res != 0) {
190 HILOGE("Failed to stat file.");
191 return false;
192 }
193 if (info.st_size > fFileSizeOver) {
194 return true;
195 }
196 return false;
197 }
198
FilterLastModifyTime(const double lastModifiedAfter,const struct dirent & filename)199 static bool FilterLastModifyTime(const double lastModifiedAfter, const struct dirent &filename)
200 {
201 if (lastModifiedAfter < 0) {
202 return true;
203 }
204 struct stat info;
205 string stPath = g_optionArgs.path + '/' + string(filename.d_name);
206 int32_t res = stat(stPath.c_str(), &info);
207 if (res != 0) {
208 HILOGE("Failed to stat file.");
209 return false;
210 }
211 if (static_cast<double>(info.st_mtime) > lastModifiedAfter) {
212 return true;
213 }
214 return false;
215 }
216
FilterResult(const struct dirent & filename)217 static bool FilterResult(const struct dirent &filename)
218 {
219 vector<string> fSuffixs = g_optionArgs.filter.GetSuffix();
220 if (!FilterSuffix(fSuffixs, filename) && fSuffixs.size() > 0) {
221 return false;
222 }
223 vector<string> fDisplaynames = g_optionArgs.filter.GetDisplayName();
224 if (!FilterDisplayname(fDisplaynames, filename) && fDisplaynames.size() > 0) {
225 return false;
226 }
227 int64_t fFileSizeOver = g_optionArgs.filter.GetFileSizeOver();
228 if (!FilterFilesizeOver(fFileSizeOver, filename)) {
229 return false;
230 }
231 double fLastModifiedAfter = g_optionArgs.filter.GetLastModifiedAfter();
232 if (!FilterLastModifyTime(fLastModifiedAfter, filename)) {
233 return false;
234 }
235 g_optionArgs.countNum++;
236 return true;
237 }
238
FilterFunc(const struct dirent * filename)239 static int32_t FilterFunc(const struct dirent *filename)
240 {
241 if (string_view(filename->d_name) == "." || string_view(filename->d_name) == "..") {
242 return FILTER_DISMATCH;
243 }
244
245 if (g_optionArgs.countNum < g_optionArgs.listNum || g_optionArgs.listNum == 0) {
246 if ((filename->d_type == DT_DIR && g_optionArgs.recursion) || FilterResult(*filename)) {
247 return FILTER_MATCH;
248 }
249 }
250 return FILTER_DISMATCH;
251 }
252
Deleter(struct NameListArg * arg)253 static void Deleter(struct NameListArg *arg)
254 {
255 for (int i = 0; i < arg->direntNum; i++) {
256 free((arg->namelist)[i]);
257 (arg->namelist)[i] = nullptr;
258 }
259 free(arg->namelist);
260 }
261
FilterFileRes(const string & path,vector<string> & dirents)262 static int FilterFileRes(const string &path, vector<string> &dirents)
263 {
264 unique_ptr<struct NameListArg, decltype(Deleter)*> pNameList = { new (nothrow) struct NameListArg, Deleter };
265 if (!pNameList) {
266 HILOGE("Failed to request heap memory.");
267 return ENOMEM;
268 }
269 int num = scandir(path.c_str(), &(pNameList->namelist), FilterFunc, nullptr);
270 if (num < 0) {
271 HILOGE("Failed to scan dir");
272 return errno;
273 }
274 pNameList->direntNum = num;
275 for (int i = 0; i < num; i++) {
276 dirents.emplace_back(pNameList->namelist[i]->d_name);
277 }
278 return ERRNO_NOERR;
279 }
280
RecursiveFunc(const string & path,vector<string> & dirents)281 static int RecursiveFunc(const string &path, vector<string> &dirents)
282 {
283 unique_ptr<struct NameListArg, decltype(Deleter)*> pNameList = { new (nothrow) struct NameListArg, Deleter };
284 if (!pNameList) {
285 HILOGE("Failed to request heap memory.");
286 return ENOMEM;
287 }
288 int num = scandir(path.c_str(), &(pNameList->namelist), FilterFunc, nullptr);
289 if (num < 0) {
290 HILOGE("Failed to scan dir");
291 return errno;
292 }
293 pNameList->direntNum = num;
294 for (int i = 0; i < num; i++) {
295 if ((*(pNameList->namelist[i])).d_type == DT_REG) {
296 dirents.emplace_back(path + '/' + pNameList->namelist[i]->d_name);
297 } else if ((*(pNameList->namelist[i])).d_type == DT_DIR) {
298 string pathTemp = g_optionArgs.path;
299 g_optionArgs.path += '/' + string((*(pNameList->namelist[i])).d_name);
300 int ret = RecursiveFunc(g_optionArgs.path, dirents);
301 if (ret != ERRNO_NOERR) {
302 return ret;
303 }
304 g_optionArgs.path = pathTemp;
305 }
306 }
307 return ERRNO_NOERR;
308 }
309
DoListFileVector2NV(napi_env env,const string & path,vector<string> & dirents,bool recursion)310 static napi_value DoListFileVector2NV(napi_env env, const string &path, vector<string> &dirents, bool recursion)
311 {
312 if (recursion) {
313 for (size_t i = 0; i < dirents.size(); i++) {
314 dirents[i] = dirents[i].substr(path.length());
315 }
316 }
317 return NVal::CreateArrayString(env, dirents).val_;
318 }
319
Sync(napi_env env,napi_callback_info info)320 napi_value ListFile::Sync(napi_env env, napi_callback_info info)
321 {
322 NFuncArg funcArg(env, info);
323 if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::TWO)) {
324 HILOGE("Number of arguments unmatched");
325 NError(EINVAL).ThrowErr(env);
326 return nullptr;
327 }
328 auto [succPath, path, unused] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
329 if (!succPath) {
330 HILOGE("Invalid path");
331 NError(EINVAL).ThrowErr(env);
332 return nullptr;
333 }
334 if (!GetOptionArg(env, funcArg, g_optionArgs, string(path.get()))) {
335 HILOGE("Invalid options");
336 NError(EINVAL).ThrowErr(env);
337 return nullptr;
338 }
339 vector<string> direntsRes;
340 int ret = 0;
341 ret = g_optionArgs.recursion ? RecursiveFunc(path.get(), direntsRes) : FilterFileRes(path.get(), direntsRes);
342 if (ret) {
343 NError(ret).ThrowErr(env);
344 return nullptr;
345 }
346 auto res = DoListFileVector2NV(env, string(path.get()), direntsRes, g_optionArgs.recursion);
347 g_optionArgs.Clear();
348 return res;
349 }
350
Async(napi_env env,napi_callback_info info)351 napi_value ListFile::Async(napi_env env, napi_callback_info info)
352 {
353 NFuncArg funcArg(env, info);
354 if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::THREE)) {
355 HILOGE("Number of arguments unmatched");
356 NError(EINVAL).ThrowErr(env);
357 return nullptr;
358 }
359
360 auto [succPath, path, unused] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
361 if (!succPath) {
362 HILOGE("Invalid path");
363 NError(EINVAL).ThrowErr(env);
364 return nullptr;
365 }
366
367 OptionArgs optionArgsTmp = {};
368 if (!GetOptionArg(env, funcArg, optionArgsTmp, string(path.get()))) {
369 HILOGE("Invalid options");
370 NError(EINVAL).ThrowErr(env);
371 return nullptr;
372 }
373
374 auto arg = CreateSharedPtr<ListFileArgs>();
375 if (arg == nullptr) {
376 HILOGE("Failed to request heap memory.");
377 NError(ENOMEM).ThrowErr(env);
378 return nullptr;
379 }
380 auto cbExec = [arg, optionArgsTmp]() -> NError {
381 g_optionArgs = optionArgsTmp;
382 int ret = 0;
383 ret = g_optionArgs.recursion ? RecursiveFunc(g_optionArgs.path, arg->dirents) :
384 FilterFileRes(g_optionArgs.path, arg->dirents);
385 g_optionArgs.Clear();
386 return ret ? NError(ret) : NError(ERRNO_NOERR);
387 };
388
389 auto cbCompl = [arg, optionArgsTmp, path = string(path.get())](napi_env env, NError err) -> NVal {
390 if (err) {
391 return { env, err.GetNapiErr(env) };
392 }
393 return { env, DoListFileVector2NV(env, path, arg->dirents, optionArgsTmp.recursion) };
394 };
395
396 NVal thisVar(env, funcArg.GetThisVar());
397
398 if (funcArg.GetArgc() == NARG_CNT::ONE || (funcArg.GetArgc() == NARG_CNT::TWO &&
399 !NVal(env, funcArg[NARG_POS::SECOND]).TypeIs(napi_function))) {
400 return NAsyncWorkPromise(env, thisVar).Schedule(LIST_FILE_PRODUCE_NAME, cbExec, cbCompl).val_;
401 } else {
402 NVal cb(env, funcArg[((funcArg.GetArgc() == NARG_CNT::TWO) ? NARG_POS::SECOND : NARG_POS::THIRD)]);
403 return NAsyncWorkCallback(env, thisVar, cb).Schedule(LIST_FILE_PRODUCE_NAME, cbExec, cbCompl).val_;
404 }
405 }
406 } // namespace OHOS::FileManagement::ModuleFileIO