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