1 | <?php |
||||
2 | |||||
3 | namespace PhpZip\Util; |
||||
4 | |||||
5 | use PhpZip\Util\Iterator\IgnoreFilesFilterIterator; |
||||
6 | use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator; |
||||
7 | |||||
8 | /** |
||||
9 | * Files util. |
||||
10 | * |
||||
11 | * @author Ne-Lexa [email protected] |
||||
12 | * @license MIT |
||||
13 | * |
||||
14 | * @internal |
||||
15 | */ |
||||
16 | final class FilesUtil |
||||
17 | { |
||||
18 | /** |
||||
19 | * Is empty directory. |
||||
20 | * |
||||
21 | * @param string $dir Directory |
||||
22 | * |
||||
23 | * @return bool |
||||
24 | */ |
||||
25 | 31 | public static function isEmptyDir($dir) |
|||
26 | { |
||||
27 | 31 | if (!is_readable($dir)) { |
|||
28 | return false; |
||||
29 | } |
||||
30 | |||||
31 | 31 | return \count(scandir($dir)) === 2; |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
32 | } |
||||
33 | |||||
34 | /** |
||||
35 | * Remove recursive directory. |
||||
36 | * |
||||
37 | * @param string $dir directory path |
||||
38 | */ |
||||
39 | 44 | public static function removeDir($dir) |
|||
40 | { |
||||
41 | 44 | $files = new \RecursiveIteratorIterator( |
|||
42 | 44 | new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS), |
|||
43 | 44 | \RecursiveIteratorIterator::CHILD_FIRST |
|||
44 | ); |
||||
45 | |||||
46 | /** @var \SplFileInfo $fileInfo */ |
||||
47 | 44 | foreach ($files as $fileInfo) { |
|||
48 | 37 | $function = ($fileInfo->isDir() ? 'rmdir' : 'unlink'); |
|||
49 | 37 | $function($fileInfo->getPathname()); |
|||
50 | } |
||||
51 | 44 | @rmdir($dir); |
|||
0 ignored issues
–
show
It seems like you do not handle an error condition for
rmdir() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||||
52 | 44 | } |
|||
53 | |||||
54 | /** |
||||
55 | * Convert glob pattern to regex pattern. |
||||
56 | * |
||||
57 | * @param string $globPattern |
||||
58 | * |
||||
59 | * @return string |
||||
60 | */ |
||||
61 | 2 | public static function convertGlobToRegEx($globPattern) |
|||
62 | { |
||||
63 | // Remove beginning and ending * globs because they're useless |
||||
64 | 2 | $globPattern = trim($globPattern, '*'); |
|||
65 | 2 | $escaping = false; |
|||
66 | 2 | $inCurrent = 0; |
|||
67 | 2 | $chars = str_split($globPattern); |
|||
68 | 2 | $regexPattern = ''; |
|||
69 | |||||
70 | 2 | foreach ($chars as $currentChar) { |
|||
71 | 2 | switch ($currentChar) { |
|||
72 | 2 | case '*': |
|||
73 | $regexPattern .= ($escaping ? '\\*' : '.*'); |
||||
74 | $escaping = false; |
||||
75 | break; |
||||
76 | |||||
77 | 2 | case '?': |
|||
78 | $regexPattern .= ($escaping ? '\\?' : '.'); |
||||
79 | $escaping = false; |
||||
80 | break; |
||||
81 | |||||
82 | 2 | case '.': |
|||
83 | 2 | case '(': |
|||
84 | 2 | case ')': |
|||
85 | 2 | case '+': |
|||
86 | 2 | case '|': |
|||
87 | 2 | case '^': |
|||
88 | 2 | case '$': |
|||
89 | 2 | case '@': |
|||
90 | 2 | case '%': |
|||
91 | 2 | $regexPattern .= '\\' . $currentChar; |
|||
92 | 2 | $escaping = false; |
|||
93 | 2 | break; |
|||
94 | |||||
95 | 2 | case '\\': |
|||
96 | if ($escaping) { |
||||
97 | $regexPattern .= '\\\\'; |
||||
98 | $escaping = false; |
||||
99 | } else { |
||||
100 | $escaping = true; |
||||
101 | } |
||||
102 | break; |
||||
103 | |||||
104 | 2 | case '{': |
|||
105 | 2 | if ($escaping) { |
|||
106 | $regexPattern .= '\\{'; |
||||
107 | } else { |
||||
108 | 2 | $regexPattern = '('; |
|||
109 | 2 | $inCurrent++; |
|||
110 | } |
||||
111 | 2 | $escaping = false; |
|||
112 | 2 | break; |
|||
113 | |||||
114 | 2 | case '}': |
|||
115 | 2 | if ($inCurrent > 0 && !$escaping) { |
|||
116 | 2 | $regexPattern .= ')'; |
|||
117 | 2 | $inCurrent--; |
|||
118 | } elseif ($escaping) { |
||||
119 | $regexPattern = '\\}'; |
||||
120 | } else { |
||||
121 | $regexPattern = '}'; |
||||
122 | } |
||||
123 | 2 | $escaping = false; |
|||
124 | 2 | break; |
|||
125 | |||||
126 | 2 | case ',': |
|||
127 | 2 | if ($inCurrent > 0 && !$escaping) { |
|||
128 | 2 | $regexPattern .= '|'; |
|||
129 | } elseif ($escaping) { |
||||
130 | $regexPattern .= '\\,'; |
||||
131 | } else { |
||||
132 | $regexPattern = ','; |
||||
133 | } |
||||
134 | 2 | break; |
|||
135 | default: |
||||
136 | 2 | $escaping = false; |
|||
137 | 2 | $regexPattern .= $currentChar; |
|||
138 | } |
||||
139 | } |
||||
140 | |||||
141 | 2 | return $regexPattern; |
|||
142 | } |
||||
143 | |||||
144 | /** |
||||
145 | * Search files. |
||||
146 | * |
||||
147 | * @param string $inputDir |
||||
148 | * @param bool $recursive |
||||
149 | * @param array $ignoreFiles |
||||
150 | * |
||||
151 | * @return array Searched file list |
||||
152 | */ |
||||
153 | public static function fileSearchWithIgnore($inputDir, $recursive = true, array $ignoreFiles = []) |
||||
154 | { |
||||
155 | if ($recursive) { |
||||
156 | $directoryIterator = new \RecursiveDirectoryIterator($inputDir); |
||||
157 | |||||
158 | if (!empty($ignoreFiles)) { |
||||
159 | $directoryIterator = new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles); |
||||
160 | } |
||||
161 | $iterator = new \RecursiveIteratorIterator($directoryIterator); |
||||
162 | } else { |
||||
163 | $directoryIterator = new \DirectoryIterator($inputDir); |
||||
164 | |||||
165 | if (!empty($ignoreFiles)) { |
||||
166 | $directoryIterator = new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles); |
||||
167 | } |
||||
168 | $iterator = new \IteratorIterator($directoryIterator); |
||||
169 | } |
||||
170 | |||||
171 | $fileList = []; |
||||
172 | |||||
173 | foreach ($iterator as $file) { |
||||
174 | if ($file instanceof \SplFileInfo) { |
||||
175 | $fileList[] = $file->getPathname(); |
||||
176 | } |
||||
177 | } |
||||
178 | |||||
179 | return $fileList; |
||||
180 | } |
||||
181 | |||||
182 | /** |
||||
183 | * Search files from glob pattern. |
||||
184 | * |
||||
185 | * @param string $globPattern |
||||
186 | * @param int $flags |
||||
187 | * @param bool $recursive |
||||
188 | * |
||||
189 | * @return array Searched file list |
||||
190 | */ |
||||
191 | 4 | public static function globFileSearch($globPattern, $flags = 0, $recursive = true) |
|||
192 | { |
||||
193 | 4 | $flags = (int) $flags; |
|||
194 | 4 | $recursive = (bool) $recursive; |
|||
195 | 4 | $files = glob($globPattern, $flags); |
|||
196 | |||||
197 | 4 | if (!$recursive) { |
|||
198 | 1 | return $files; |
|||
0 ignored issues
–
show
The expression
return $files could also return false which is incompatible with the documented return type array . Did you maybe forget to handle an error condition?
If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled. ![]() |
|||||
199 | } |
||||
200 | |||||
201 | 3 | foreach (glob(\dirname($globPattern) . \DIRECTORY_SEPARATOR . '*', \GLOB_ONLYDIR | \GLOB_NOSORT) as $dir) { |
|||
202 | // Unpacking the argument via ... is supported starting from php 5.6 only |
||||
203 | /** @noinspection SlowArrayOperationsInLoopInspection */ |
||||
204 | 3 | $files = array_merge($files, self::globFileSearch($dir . \DIRECTORY_SEPARATOR . basename($globPattern), $flags, $recursive)); |
|||
0 ignored issues
–
show
It seems like
$files can also be of type false ; however, parameter $array1 of array_merge() does only seem to accept array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
205 | } |
||||
206 | |||||
207 | 3 | return $files; |
|||
0 ignored issues
–
show
The expression
return $files could also return false which is incompatible with the documented return type array . Did you maybe forget to handle an error condition?
If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled. ![]() |
|||||
208 | } |
||||
209 | |||||
210 | /** |
||||
211 | * Search files from regex pattern. |
||||
212 | * |
||||
213 | * @param string $folder |
||||
214 | * @param string $pattern |
||||
215 | * @param bool $recursive |
||||
216 | * |
||||
217 | * @return array Searched file list |
||||
218 | */ |
||||
219 | 4 | public static function regexFileSearch($folder, $pattern, $recursive = true) |
|||
220 | { |
||||
221 | 4 | if ($recursive) { |
|||
222 | 3 | $directoryIterator = new \RecursiveDirectoryIterator($folder); |
|||
223 | 3 | $iterator = new \RecursiveIteratorIterator($directoryIterator); |
|||
224 | } else { |
||||
225 | 1 | $directoryIterator = new \DirectoryIterator($folder); |
|||
226 | 1 | $iterator = new \IteratorIterator($directoryIterator); |
|||
227 | } |
||||
228 | |||||
229 | 4 | $regexIterator = new \RegexIterator($iterator, $pattern, \RegexIterator::MATCH); |
|||
230 | 4 | $fileList = []; |
|||
231 | |||||
232 | 4 | foreach ($regexIterator as $file) { |
|||
233 | 4 | if ($file instanceof \SplFileInfo) { |
|||
234 | 4 | $fileList[] = $file->getPathname(); |
|||
235 | } |
||||
236 | } |
||||
237 | |||||
238 | 4 | return $fileList; |
|||
239 | } |
||||
240 | |||||
241 | /** |
||||
242 | * Convert bytes to human size. |
||||
243 | * |
||||
244 | * @param int $size Size bytes |
||||
245 | * @param string|null $unit Unit support 'GB', 'MB', 'KB' |
||||
246 | * |
||||
247 | * @return string |
||||
248 | */ |
||||
249 | public static function humanSize($size, $unit = null) |
||||
250 | { |
||||
251 | if (($unit === null && $size >= 1 << 30) || $unit === 'GB') { |
||||
252 | return number_format($size / (1 << 30), 2) . 'GB'; |
||||
253 | } |
||||
254 | |||||
255 | if (($unit === null && $size >= 1 << 20) || $unit === 'MB') { |
||||
256 | return number_format($size / (1 << 20), 2) . 'MB'; |
||||
257 | } |
||||
258 | |||||
259 | if (($unit === null && $size >= 1 << 10) || $unit === 'KB') { |
||||
260 | return number_format($size / (1 << 10), 2) . 'KB'; |
||||
261 | } |
||||
262 | |||||
263 | return number_format($size) . ' bytes'; |
||||
264 | } |
||||
265 | |||||
266 | /** |
||||
267 | * Normalizes zip path. |
||||
268 | * |
||||
269 | * @param string $path Zip path |
||||
270 | * |
||||
271 | * @return string |
||||
272 | */ |
||||
273 | 10 | public static function normalizeZipPath($path) |
|||
274 | { |
||||
275 | 10 | return implode( |
|||
276 | 10 | \DIRECTORY_SEPARATOR, |
|||
277 | array_filter( |
||||
278 | 10 | explode('/', (string) $path), |
|||
279 | 10 | static function ($part) { |
|||
280 | 10 | return $part !== '.' && $part !== '..'; |
|||
281 | 10 | } |
|||
282 | ) |
||||
283 | ); |
||||
284 | } |
||||
285 | |||||
286 | /** |
||||
287 | * Returns whether the file path is an absolute path. |
||||
288 | * |
||||
289 | * @param string $file A file path |
||||
290 | * |
||||
291 | * @return bool |
||||
292 | * |
||||
293 | * @see source symfony filesystem component |
||||
294 | */ |
||||
295 | public static function isAbsolutePath($file) |
||||
296 | { |
||||
297 | return strspn($file, '/\\', 0, 1) |
||||
298 | || ( |
||||
299 | \strlen($file) > 3 && ctype_alpha($file[0]) |
||||
300 | && $file[1] === ':' |
||||
301 | && strspn($file, '/\\', 2, 1) |
||||
302 | ) |
||||
303 | || parse_url($file, \PHP_URL_SCHEME) !== null; |
||||
304 | } |
||||
305 | |||||
306 | /** |
||||
307 | * @param string $target |
||||
308 | * @param string $path |
||||
309 | * @param bool $allowSymlink |
||||
310 | * |
||||
311 | * @return bool |
||||
312 | */ |
||||
313 | 2 | public static function symlink($target, $path, $allowSymlink) |
|||
314 | { |
||||
315 | 2 | if (\DIRECTORY_SEPARATOR === '\\' || !$allowSymlink) { |
|||
316 | 1 | return file_put_contents($path, $target) !== false; |
|||
317 | } |
||||
318 | |||||
319 | 1 | return symlink($target, $path); |
|||
320 | } |
||||
321 | |||||
322 | /** |
||||
323 | * @param string $file |
||||
324 | * |
||||
325 | * @return bool |
||||
326 | */ |
||||
327 | 21 | public static function isBadCompressionFile($file) |
|||
328 | { |
||||
329 | $badCompressFileExt = [ |
||||
330 | 21 | 'dic', |
|||
331 | 'dng', |
||||
332 | 'f4v', |
||||
333 | 'flipchart', |
||||
334 | 'h264', |
||||
335 | 'lrf', |
||||
336 | 'mobi', |
||||
337 | 'mts', |
||||
338 | 'nef', |
||||
339 | 'pspimage', |
||||
340 | ]; |
||||
341 | |||||
342 | 21 | $ext = strtolower(pathinfo($file, \PATHINFO_EXTENSION)); |
|||
343 | |||||
344 | 21 | if (\in_array($ext, $badCompressFileExt, true)) { |
|||
345 | return true; |
||||
346 | } |
||||
347 | |||||
348 | 21 | $mimeType = self::getMimeTypeFromFile($file); |
|||
349 | |||||
350 | 21 | return self::isBadCompressionMimeType($mimeType); |
|||
351 | } |
||||
352 | |||||
353 | /** |
||||
354 | * @param string $mimeType |
||||
355 | * |
||||
356 | * @return bool |
||||
357 | */ |
||||
358 | 27 | public static function isBadCompressionMimeType($mimeType) |
|||
359 | { |
||||
360 | 27 | static $badDeflateCompMimeTypes = [ |
|||
361 | 'application/epub+zip', |
||||
362 | 'application/gzip', |
||||
363 | 'application/vnd.debian.binary-package', |
||||
364 | 'application/vnd.oasis.opendocument.graphics', |
||||
365 | 'application/vnd.oasis.opendocument.presentation', |
||||
366 | 'application/vnd.oasis.opendocument.text', |
||||
367 | 'application/vnd.oasis.opendocument.text-master', |
||||
368 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
||||
369 | 'application/vnd.rn-realmedia', |
||||
370 | 'application/x-7z-compressed', |
||||
371 | 'application/x-arj', |
||||
372 | 'application/x-bzip2', |
||||
373 | 'application/x-hwp', |
||||
374 | 'application/x-lzip', |
||||
375 | 'application/x-lzma', |
||||
376 | 'application/x-ms-reader', |
||||
377 | 'application/x-rar', |
||||
378 | 'application/x-rpm', |
||||
379 | 'application/x-stuffit', |
||||
380 | 'application/x-tar', |
||||
381 | 'application/x-xz', |
||||
382 | 'application/zip', |
||||
383 | 'application/zlib', |
||||
384 | 'audio/flac', |
||||
385 | 'audio/mpeg', |
||||
386 | 'audio/ogg', |
||||
387 | 'audio/vnd.dolby.dd-raw', |
||||
388 | 'audio/webm', |
||||
389 | 'audio/x-ape', |
||||
390 | 'audio/x-hx-aac-adts', |
||||
391 | 'audio/x-m4a', |
||||
392 | 'audio/x-m4a', |
||||
393 | 'audio/x-wav', |
||||
394 | 'image/gif', |
||||
395 | 'image/heic', |
||||
396 | 'image/jp2', |
||||
397 | 'image/jpeg', |
||||
398 | 'image/png', |
||||
399 | 'image/vnd.djvu', |
||||
400 | 'image/webp', |
||||
401 | 'image/x-canon-cr2', |
||||
402 | 'video/ogg', |
||||
403 | 'video/webm', |
||||
404 | 'video/x-matroska', |
||||
405 | 'video/x-ms-asf', |
||||
406 | 'x-epoc/x-sisx-app', |
||||
407 | ]; |
||||
408 | |||||
409 | 27 | if (\in_array($mimeType, $badDeflateCompMimeTypes, true)) { |
|||
410 | return true; |
||||
411 | } |
||||
412 | |||||
413 | 27 | return false; |
|||
414 | } |
||||
415 | |||||
416 | /** |
||||
417 | * @param string $file |
||||
418 | * |
||||
419 | * @return string |
||||
420 | * |
||||
421 | * @noinspection PhpComposerExtensionStubsInspection |
||||
422 | */ |
||||
423 | 21 | public static function getMimeTypeFromFile($file) |
|||
424 | { |
||||
425 | 21 | if (\function_exists('mime_content_type')) { |
|||
426 | 21 | return mime_content_type($file); |
|||
427 | } |
||||
428 | |||||
429 | return 'application/octet-stream'; |
||||
430 | } |
||||
431 | |||||
432 | /** |
||||
433 | * @param string $contents |
||||
434 | * |
||||
435 | * @return string |
||||
436 | * @noinspection PhpComposerExtensionStubsInspection |
||||
437 | */ |
||||
438 | 10 | public static function getMimeTypeFromString($contents) |
|||
439 | { |
||||
440 | 10 | $contents = (string) $contents; |
|||
441 | 10 | $finfo = new \finfo(\FILEINFO_MIME); |
|||
442 | 10 | $mimeType = $finfo->buffer($contents); |
|||
443 | |||||
444 | 10 | if ($mimeType === false) { |
|||
445 | $mimeType = 'application/octet-stream'; |
||||
446 | } |
||||
447 | |||||
448 | 10 | return explode(';', $mimeType)[0]; |
|||
449 | } |
||||
450 | } |
||||
451 |