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 <securec.h>
21 #include <string>
22 #include <string_view>
23 #include <sys/stat.h>
24 #include <thread>
25 #include <tuple>
26
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,OHOS::DistributedFS::FileFilter * filter)53 static bool GetFileFilterParam(const NVal &argv, OHOS::DistributedFS::FileFilter *filter)
54 {
55 bool ret = false;
56 if (argv.HasProp("suffix")) {
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")) {
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")) {
83 int64_t fileSizeOver;
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")) {
92 double lastModifiedAfter;
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();
108 if (!succ) {
109 HILOGE("Failed to get listNum prop.");
110 return false;
111 } else if (optionArgs->listNum < 0) {
112 HILOGE("Invalid listNum.");
113 return false;
114 }
115 }
116
117 if (argv.HasProp("recursion")) {
118 tie(succ, optionArgs->recursion) = argv.GetProp("recursion").ToBool();
119 if (!succ) {
120 HILOGE("Failed to get recursion prop.");
121 return false;
122 }
123 }
124
125 if (argv.HasProp("filter")) {
126 NVal(filterProp) = argv.GetProp("filter");
127 auto ret = GetFileFilterParam(NVal(filterProp), &optionArgs->filter);
128 if (!ret) {
129 HILOGE("Failed to get filter prop.");
130 return false;
131 }
132 }
133 return true;
134 }
135
GetOptionArg(napi_env env,const NFuncArg & funcArg,OptionArgs & optionArgs,const string & path)136 static bool GetOptionArg(napi_env env, const NFuncArg &funcArg, OptionArgs &optionArgs, const string &path)
137 {
138 auto options = NVal(env, funcArg[NARG_POS::SECOND]);
139 if (funcArg.GetArgc() >= NARG_CNT::TWO && options.TypeIs(napi_object)) {
140 if (!GetOptionParam(options, &optionArgs)) {
141 HILOGE("Invalid options");
142 return false;
143 }
144 } else if (funcArg.GetArgc() >= NARG_CNT::TWO && !options.TypeIs(napi_function)) {
145 HILOGE("Invalid options");
146 return false;
147 }
148 optionArgs.path = path;
149 return true;
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 struct stat info;
184 string stPath = (g_optionArgs.path + '/' + string(filename.d_name));
185 int32_t res = stat(stPath.c_str(), &info);
186 if (res != 0) {
187 HILOGE("Failed to stat file.");
188 return false;
189 }
190 if (fFileSizeOver > info.st_size) {
191 return false;
192 }
193 return true;
194 }
195
FilterLastModifyTime(const double lastModifiedAfter,const struct dirent & filename)196 static bool FilterLastModifyTime(const double lastModifiedAfter, const struct dirent& filename)
197 {
198 struct stat info;
199 string stPath = g_optionArgs.path + '/' + string(filename.d_name);
200 int32_t res = stat(stPath.c_str(), &info);
201 if (res != 0) {
202 HILOGE("Failed to stat file.");
203 return false;
204 }
205 if (lastModifiedAfter > time(&info.st_mtime)) {
206 return false;
207 }
208 return true;
209 }
210
FilterResult(const struct dirent & filename)211 static bool FilterResult(const struct dirent &filename)
212 {
213 vector<string> fSuffixs = g_optionArgs.filter.GetSuffix();
214 if (!FilterSuffix(fSuffixs, filename) && fSuffixs.size() > 0) {
215 return false;
216 }
217 vector<string> fDisplaynames = g_optionArgs.filter.GetDisplayName();
218 if (!FilterDisplayname(fDisplaynames, filename) && fDisplaynames.size() > 0) {
219 return false;
220 }
221 int64_t fFileSizeOver = g_optionArgs.filter.GetFileSizeOver();
222 if (!FilterFilesizeOver(fFileSizeOver, filename) && fFileSizeOver > 0) {
223 return false;
224 }
225 double fLastModifiedAfter = g_optionArgs.filter.GetLastModifiedAfter();
226 if (!FilterLastModifyTime(fLastModifiedAfter, filename) && fLastModifiedAfter > 0) {
227 return false;
228 }
229 g_optionArgs.countNum++;
230 return true;
231 }
232
FilterFunc(const struct dirent * filename)233 static int32_t FilterFunc(const struct dirent *filename)
234 {
235 if (string_view(filename->d_name) == "." || string_view(filename->d_name) == "..") {
236 return FILTER_DISMATCH;
237 }
238
239 if (g_optionArgs.countNum < g_optionArgs.listNum || g_optionArgs.listNum == 0) {
240 if ((filename->d_type == DT_DIR && g_optionArgs.recursion) || FilterResult(*filename)) {
241 return FILTER_MATCH;
242 }
243 }
244 return FILTER_DISMATCH;
245 }
246
FilterFileRes(const string & path)247 static vector<struct dirent> FilterFileRes(const string &path)
248 {
249 struct dirent** namelist;
250 int num = scandir(path.c_str(), &(namelist), FilterFunc, alphasort);
251 vector<struct dirent> dirents;
252 for (int i = 0; i < num; i++) {
253 struct dirent tmpDirent;
254 if (EOK == memcpy_s(&tmpDirent, sizeof(dirent), namelist[i], namelist[i]->d_reclen)) {
255 dirents.emplace_back(tmpDirent);
256 }
257 free(namelist[i]);
258 }
259 free(namelist);
260 return dirents;
261 }
262
RecursiveFunc(const string & path,vector<struct dirent> & dirents)263 static void RecursiveFunc(const string &path, vector<struct dirent> &dirents)
264 {
265 struct dirent** namelist;
266 int num = scandir(path.c_str(), &(namelist), FilterFunc, alphasort);
267 for (int i = 0; i < num; i++) {
268 if ((*namelist[i]).d_type == DT_REG) {
269 struct dirent tmpDirent;
270 if (EOK == memcpy_s(&tmpDirent, sizeof(dirent), namelist[i], namelist[i]->d_reclen)) {
271 dirents.emplace_back(tmpDirent);
272 }
273 } else if ((*(namelist)[i]).d_type == DT_DIR) {
274 string pathTemp = g_optionArgs.path;
275 g_optionArgs.path += '/' + string((*namelist[i]).d_name);
276 RecursiveFunc(g_optionArgs.path, dirents);
277 g_optionArgs.path = pathTemp;
278 }
279 free(namelist[i]);
280 }
281 free(namelist);
282 }
283
DoListFileVector2NV(napi_env env,vector<dirent> & dirents)284 static napi_value DoListFileVector2NV(napi_env env, vector<dirent> &dirents)
285 {
286 vector<string> strs;
287 for (size_t i = 0; i < dirents.size(); i++) {
288 strs.emplace_back(dirents[i].d_name);
289 }
290 return NVal::CreateArrayString(env, strs).val_;
291 }
292
DoListFileCompile(napi_env env,NError err,shared_ptr<ListFileArgs> arg)293 static NVal DoListFileCompile(napi_env env, NError err, shared_ptr<ListFileArgs> arg)
294 {
295 if (err) {
296 return { env, err.GetNapiErr(env) };
297 } else {
298 return { env, DoListFileVector2NV(env, arg->dirents) };
299 }
300 }
301
Sync(napi_env env,napi_callback_info info)302 napi_value ListFile::Sync(napi_env env, napi_callback_info info)
303 {
304 NFuncArg funcArg(env, info);
305 if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::TWO)) {
306 HILOGE("Number of arguments unmatched");
307 NError(EINVAL).ThrowErr(env);
308 return nullptr;
309 }
310 auto [succPath, path, unused] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
311 if (!succPath) {
312 HILOGE("Invalid path");
313 NError(EINVAL).ThrowErr(env);
314 return nullptr;
315 }
316 if (!GetOptionArg(env, funcArg, g_optionArgs, string(path.get()))) {
317 NError(EINVAL).ThrowErr(env);
318 return nullptr;
319 }
320 vector<struct dirent> direntsRes;
321 if (g_optionArgs.recursion) {
322 RecursiveFunc(path.get(), direntsRes);
323 } else {
324 direntsRes = FilterFileRes(path.get());
325 }
326 g_optionArgs.Clear();
327 return DoListFileVector2NV(env, direntsRes);
328 }
329
Async(napi_env env,napi_callback_info info)330 napi_value ListFile::Async(napi_env env, napi_callback_info info)
331 {
332 NFuncArg funcArg(env, info);
333 if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::THREE)) {
334 HILOGE("Number of arguments unmatched");
335 NError(EINVAL).ThrowErr(env);
336 return nullptr;
337 }
338
339 auto [succPath, path, unused] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
340 if (!succPath) {
341 HILOGE("Invalid path");
342 NError(EINVAL).ThrowErr(env);
343 return nullptr;
344 }
345
346 OptionArgs optionArgsTmp = {};
347 if (!GetOptionArg(env, funcArg, optionArgsTmp, string(path.get()))) {
348 NError(EINVAL).ThrowErr(env);
349 return nullptr;
350 }
351
352 auto arg = make_shared<ListFileArgs>();
353 if (!arg) {
354 HILOGE("Failed to request heap memory.");
355 NError(ENOMEM).ThrowErr(env);
356 return nullptr;
357 }
358
359 auto cbExec = [arg, optionArgsTmp]() -> NError {
360 g_optionArgs = optionArgsTmp;
361 if (g_optionArgs.recursion) {
362 RecursiveFunc(g_optionArgs.path, arg->dirents);
363 } else {
364 arg->dirents = FilterFileRes(g_optionArgs.path);
365 }
366 g_optionArgs.Clear();
367 return NError(ERRNO_NOERR);
368 };
369
370 auto cbCompl = [arg](napi_env env, NError err) -> NVal {
371 if (err) {
372 return { env, err.GetNapiErr(env) };
373 }
374 return DoListFileCompile(env, err, arg);
375 };
376
377 NVal thisVar(env, funcArg.GetThisVar());
378
379 if (funcArg.GetArgc() == NARG_CNT::ONE || (funcArg.GetArgc() == NARG_CNT::TWO &&
380 NVal(env, funcArg[NARG_POS::SECOND]).TypeIs(napi_object))) {
381 return NAsyncWorkPromise(env, thisVar).Schedule(LIST_FILE_PRODUCE_NAME, cbExec, cbCompl).val_;
382 } else {
383 int32_t cbIdx = (funcArg.GetArgc() == NARG_CNT::TWO) ? NARG_POS::SECOND : NARG_POS::THIRD;
384 NVal cb(env, funcArg[cbIdx]);
385 return NAsyncWorkCallback(env, thisVar, cb).Schedule(LIST_FILE_PRODUCE_NAME, cbExec, cbCompl).val_;
386 }
387 }
388 } // namespace OHOS::FileManagement::ModuleFileIO