PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / trunk
Defender Security – Malware Scanner, Login Security & Firewall vtrunk
6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 3.1.1 All 140 releases
defender-security / framework / base / class-file.php

class-file.php in Defender Security – Malware Scanner, Login Security & Firewall trunk, at framework/base/class-file.php

506 lines 13.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Base file class.
4 *
5 * @package Calotes\Base
6 */
7
8 namespace Calotes\Base;
9
10 use FilesystemIterator;
11 use RecursiveIteratorIterator;
12 use RecursiveDirectoryIterator;
13 use RecursiveCallbackFilterIterator;
14
15 /**
16 * Represents a file object and provides methods for retrieving directory tree.
17 */
18 class File {
19
20 public const ENGINE_SPL = 'spl', ENGINE_SCANDIR = 'scan_dir', ENGINE_OPENDIR = 'open_dir';
21
22 /**
23 * Engine use to create a dir tree.
24 *
25 * @var string
26 */
27 public $engine = '';
28
29 /**
30 * Absolute path to a folder need to create a dir tree.
31 *
32 * @var string
33 */
34 public $path = '';
35
36 /**
37 * Is the result including file?
38 *
39 * @var bool
40 */
41 public $include_file = true;
42
43 /**
44 * Is the result include dir?
45 *
46 * @var bool
47 */
48 public $include_dir = true;
49
50 /**
51 * Whether to include hidden files/directories.
52 *
53 * @var bool
54 */
55 public $include_hidden = false;
56
57 /**
58 * This is where to define the rules for exclude files out of the result:
59 * 'ext'=>array('jpg','gif') file extension you don't want to appear in the result,
60 * 'path'=>array('/tmp/file1.txt','/tmp/file2') absolute path to files,
61 * 'dir'=>array('/tmp/','/dir/') absolute path to the directory you don't want to include files,
62 * 'filename'=>array('abc*') file name you don't want to include, can be regex.
63 *
64 * @var array
65 */
66 public $exclude = array();
67
68 /**
69 * This is where to define the rules for include files:
70 * 'ext'=>array('jpg','gif') file extension you don't want to appear in the result,
71 * 'path'=>array('/tmp/file1.txt','/tmp/file2') absolute path to files,
72 * 'dir'=>array('/tmp/','/dir/') absolute path to the directory you don't want to include files,
73 * 'filename'=>array('abc*') file name you don't want to include, can be regex.
74 * Please note that if $include is provided, the $exclude will get ignored.
75 *
76 * @var array
77 */
78 public $include = array();
79
80 /**
81 * Does this search recursive?
82 *
83 * @var bool
84 */
85 public $is_recursive = true;
86
87 /**
88 * If provided, only search file smaller than this.
89 *
90 * @var bool
91 */
92 public $max_filesize = false;
93
94 /**
95 * Constructor for the File class.
96 *
97 * @param string $path The path to the directory.
98 * @param bool $include_file Whether to include files in the directory.
99 * @param bool $include_dir Whether to include directories in the directory.
100 * @param array $include_rules The rules for including files/directories.
101 * @param array $exclude_rules The rules for excluding files/directories.
102 * @param bool $is_recursive Whether to recursively search the directory.
103 * @param bool $include_hidden Whether to include hidden files/directories.
104 * @param bool $max_filesize The maximum filesize to include.
105 */
106 public function __construct(
107 $path,
108 $include_file = true,
109 $include_dir = false,
110 $include_rules = array(),
111 $exclude_rules = array(),
112 $is_recursive = true,
113 $include_hidden = false,
114 $max_filesize = false
115 ) {
116 $this->path = $path;
117 $this->include_file = $include_file;
118 $this->include_dir = $include_dir;
119 $this->include = $include_rules;
120 $this->exclude = $exclude_rules;
121 $this->is_recursive = $is_recursive;
122 $this->engine = self::ENGINE_SCANDIR;
123 $this->include_hidden = $include_hidden;
124 $this->max_filesize = $max_filesize;
125 }
126
127 /**
128 * Retrieves the directory tree based on the specified engine.
129 *
130 * @return array The directory tree.
131 */
132 public function get_dir_tree() {
133 $result = array();
134 if ( ! is_dir( $this->path ) ) {
135 return $result;
136 }
137
138 if ( self::ENGINE_SPL === $this->engine ) {
139 $result = $this->get_dir_tree_by_spl();
140 } elseif ( self::ENGINE_SCANDIR === $this->engine ) {
141 $result = $this->get_dir_tree_by_scandir();
142 } elseif ( self::ENGINE_OPENDIR === $this->engine ) {
143 $result = $this->get_dir_tree_by_open_dir();
144 }
145
146 return $result;
147 }
148
149 /**
150 * Create a dir tree by SPL library.
151 *
152 * @return array
153 */
154 private function get_dir_tree_by_spl() {
155 $path = $this->path;
156 $data = array();
157 if ( $this->is_recursive ) {
158 $directory_flag = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO
159 | FilesystemIterator::UNIX_PATHS | FilesystemIterator::SKIP_DOTS;
160 $directory = new RecursiveDirectoryIterator( $path, $directory_flag );
161
162 if ( array() !== $this->include || array() !== $this->exclude ) {
163 $directory = new RecursiveCallbackFilterIterator(
164 $directory,
165 array(
166 &$this,
167 'filter_directory',
168 )
169 );
170 }
171 $tree = new RecursiveIteratorIterator( $directory, RecursiveIteratorIterator::SELF_FIRST );
172 if ( true !== $this->is_recursive ) {
173 $tree->setMaxDepth( $this->is_recursive );
174 }
175 } else {
176 $tree = new FilesystemIterator( $path );
177 }
178
179 foreach ( $tree as $file ) {
180 $real_path = $file->getRealPath();
181
182 $is_hidden = explode( DIRECTORY_SEPARATOR . '.', $real_path );
183 if ( count( $is_hidden ) > 1 && false === $this->include_hidden ) {
184 continue;
185 }
186 if ( false === $this->is_recursive ) {
187 // Have to filter this, for un recursive.
188 if ( array() !== $this->include || array() !== $this->exclude ) {
189 if ( false === $this->filter_directory( $real_path ) ) {
190 continue;
191 }
192 }
193 }
194
195 if ( false === $this->include_file && $file->isFile() ) {
196 continue;
197 }
198
199 if ( false === $this->include_dir && $file->isDir() ) {
200 continue;
201 }
202
203 if ( $file->isFile() && is_numeric( $this->max_filesize ) ) {
204 // Convert max to bytes.
205 $max_size = $this->max_filesize * ( pow( 1024, 2 ) );
206 if ( $file->getSize() > $max_size ) {
207 continue;
208 }
209 }
210
211 $data[] = $real_path;
212 }
213
214 return $data;
215 }
216
217 /**
218 * Retrieves the directory tree using the scandir() function.
219 *
220 * @param string|null $path The path of the directory to retrieve the tree from. If null, it uses the path
221 * property of the object.
222 *
223 * @return array The array containing the directory tree.
224 */
225 private function get_dir_tree_by_scandir( $path = null ) {
226 if ( is_null( $path ) ) {
227 $path = $this->path;
228 }
229 $path = rtrim( $path, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
230 $rfiles = scandir( $path );
231 $data = array();
232
233 foreach ( $rfiles as $rfile ) {
234 if ( '.' === $rfile || '..' === $rfile ) {
235 continue;
236 }
237 if ( '.' === substr( pathinfo( $rfile, PATHINFO_BASENAME ), 0, 1 )
238 && false === $this->include_hidden ) {
239 // Hidden files, move on.
240 continue;
241 }
242
243 $real_path = $path . $rfile;
244
245 $type = filetype( $real_path );
246 if ( 'dir' === $type ) {
247 $real_path .= DIRECTORY_SEPARATOR;
248 }
249
250 if ( ( array() !== $this->include || array() !== $this->exclude ) && ( false === $this->filter_directory(
251 $real_path,
252 $type
253 ) ) ) {
254 continue;
255 }
256
257 if ( 'file' === $type && true === $this->include_file ) {
258 if ( is_numeric( $this->max_filesize ) ) {
259 $max_size = $this->max_filesize * ( pow( 1024, 2 ) );
260 if ( filesize( $real_path ) > $max_size ) {
261 continue;
262 } else {
263 $data[] = $real_path;
264 }
265 } else {
266 $data[] = $real_path;
267 }
268 }
269
270 if ( 'dir' === $type ) {
271 if ( $this->include_dir ) {
272 $data[] = $real_path;
273 }
274 if ( $this->is_recursive ) {
275 $tdata = $this->get_dir_tree_by_scandir( $real_path );
276 $data = array_merge( $data, $tdata );
277 }
278 }
279 }
280
281 return $data;
282 }
283
284 /**
285 * Query files on path using opendir().
286 *
287 * @param mixed $path The path of the directory to retrieve the tree from.
288 *
289 * @return array
290 * @since 1.0.5
291 */
292 private function get_dir_tree_by_open_dir( $path = null ): array {
293 if ( is_null( $path ) ) {
294 $path = $this->path;
295 }
296 $path = rtrim( $path, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
297 $data = array();
298 $dh = opendir( $path );
299 if ( $dh ) {
300 $file = readdir( $dh );
301 while ( false !== $file ) {
302 if ( '.' === $file || '..' === $file ) {
303 continue;
304 }
305 $real_path = $path . $file;
306 if ( '.' === substr( pathinfo( $real_path, PATHINFO_BASENAME ), 0, 1 ) ) {
307 // Hidden files, move on.
308 continue;
309 }
310
311 if ( ( array() !== $this->include || array() !== $this->exclude ) && ( false === $this->filter_directory( $real_path ) ) ) {
312 continue;
313 }
314
315 $type = filetype( $real_path );
316
317 if ( 'file' === $type && true === $this->include_file ) {
318 if ( is_numeric( $this->max_filesize ) ) {
319 $max_size = $this->max_filesize * ( pow( 1024, 2 ) );
320 if ( filesize( $real_path ) > $max_size ) {
321 continue;
322 } else {
323 $data[] = $real_path;
324 }
325 } else {
326 $data[] = $real_path;
327 }
328 }
329
330 if ( 'dir' === $type ) {
331 if ( $this->include_dir ) {
332 $data[] = $real_path;
333 }
334 if ( $this->is_recursive ) {
335 $tdata = $this->get_dir_tree_by_open_dir( $real_path );
336 $data = array_merge( $data, $tdata );
337 }
338 }
339 $file = readdir( $dh );
340 }
341 closedir( $dh );
342 }
343
344 return $data;
345 }
346
347 /**
348 * Filter for recursive directory tree.
349 *
350 * @param mixed $current The path of the directory.
351 * @param mixed $filetype The type of the file.
352 *
353 * @return bool|void
354 */
355 public function filter_directory( $current, $filetype = null ) {
356 if ( array() !== $this->include ) {
357 return $this->filter_include( $current, $filetype );
358 } elseif ( array() !== $this->exclude ) {
359 return $this->filter_exclude( $current, $filetype );
360 }
361 }
362
363 /**
364 * Filter directories based on inclusion rules.
365 *
366 * @param string $path The path of the directory to be filtered.
367 * @param string|null $filetype The type of the file to be filtered. Default is null.
368 *
369 * @return bool Returns true if the directory passes the inclusion rules, false otherwise.
370 */
371 private function filter_include( $path, $filetype = null ) {
372 $include = $this->include;
373 $exclude = $this->exclude;
374 $applied = 0;
375 $dir_include = isset( $include['dir'] ) ? $include['dir'] : array();
376 $dir_exclude = isset( $exclude['dir'] ) ? $exclude['dir'] : array();
377
378 if ( ! is_null( $filetype ) ) {
379 $type = $filetype;
380 } else {
381 $type = filetype( $path );
382 }
383
384 if ( is_array( $dir_include ) && count( $dir_include ) ) {
385 if ( is_array( $dir_exclude ) ) {
386 foreach ( $dir_exclude as $dir ) {
387 if ( 0 === strpos( $path, $dir ) ) {
388 // Exclude matched, we won't list this. Move to next loop.
389 continue;
390 }
391 }
392 }
393
394 foreach ( $dir_include as $dir ) {
395 if ( 0 === strpos( $path, $dir ) ) {
396 return true;
397 }
398 }
399 ++$applied;
400 }
401
402 // Next extension.
403 $ext_include = isset( $include['ext'] ) ? $include['ext'] : array();
404
405 if ( is_array( $ext_include ) && count( $ext_include ) && 'file' === $type ) {
406 // We use foreach() and strcasecmp() instead of regex cause it faster.
407 foreach ( $ext_include as $ext ) {
408 if ( strcasecmp( pathinfo( $path, PATHINFO_EXTENSION ), $ext ) === 0 ) {
409 // Match.
410 return true;
411 }
412 }
413 ++$applied;
414 }
415
416 // Now filename.
417 $filename_include = isset( $include['filename'] ) ? $include['filename'] : array();
418 if ( is_array( $filename_include ) && count( $filename_include ) && 'file' === $type ) {
419 foreach ( $filename_include as $filename ) {
420 $pattern = '/' . preg_quote( $filename, '/' ) . '/';
421 if ( preg_match( $pattern, pathinfo( $path, PATHINFO_BASENAME ) ) ) {
422 return true;
423 }
424 }
425 ++$applied;
426 }
427
428 // Now abs path.
429 $path_include = isset( $include['path'] ) ? $include['path'] : array();
430 if ( is_array( $path_include ) && count( $path_include ) && 'file' === $type ) {
431 foreach ( $path_include as $p ) {
432 if ( strcmp( $p, $path ) === 0 ) {
433 return true;
434 }
435 }
436 ++$applied;
437 }
438
439 if ( 0 === $applied ) {
440 return true;
441 }
442
443 return false;
444 }
445
446 /**
447 * Filter out excluded files or directories based on different criteria.
448 *
449 * @param string $path The path of the file or directory to be checked.
450 * @param string|null $filetype The type of the file or directory. If null, it will be determined using the
451 * `filetype` function.
452 *
453 * @return bool Returns true if the file or directory should be included, false otherwise.
454 */
455 private function filter_exclude( $path, $filetype = null ): bool {
456 $exclude = $this->exclude;
457 // First filer dir, or file inside dir.
458 if ( ! is_null( $filetype ) ) {
459 $type = $filetype;
460 } else {
461 $type = filetype( $path );
462 }
463 $dir_exclude = isset( $exclude['dir'] ) ? $exclude['dir'] : array();
464 if ( is_array( $dir_exclude ) && count( $dir_exclude ) ) {
465 foreach ( $dir_exclude as $dir ) {
466 if ( strpos( $path, $dir ) === 0 ) {
467 return false;
468 }
469 }
470 }
471
472 // Next extension.
473 $ext_exclude = isset( $exclude['ext'] ) ? $exclude['ext'] : array();
474 if ( is_array( $ext_exclude ) && count( $ext_exclude ) && 'file' === $type ) {
475 // We use foreach() and strcasecmp() instead of regex cause it faster.
476 foreach ( $ext_exclude as $ext ) {
477 if ( strcasecmp( pathinfo( $path, PATHINFO_EXTENSION ), $ext ) === 0 ) {
478 // Match.
479 return false;
480 }
481 }
482 }
483 // Now filename.
484 $filename_exclude = isset( $exclude['filename'] ) ? $exclude['filename'] : array();
485 if ( is_array( $filename_exclude ) && count( $filename_exclude ) && 'file' === $type ) {
486 foreach ( $filename_exclude as $filename ) {
487 if ( preg_match( '/' . $filename . '/', pathinfo( $path, PATHINFO_BASENAME ) ) ) {
488 return false;
489 }
490 }
491 }
492
493 // Now abs path.
494 $path_exclude = isset( $exclude['path'] ) ? $exclude['path'] : array();
495 if ( is_array( $path_exclude ) && count( $path_exclude ) && 'file' === $type ) {
496 foreach ( $path_exclude as $p ) {
497 if ( strcmp( $p, $path ) === 0 ) {
498 return false;
499 }
500 }
501 }
502
503 return true;
504 }
505 }
506