55 lines
1.7 KiB
PHP
55 lines
1.7 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Malware;
|
|
|
|
final class ArchiveLimitInspector
|
|
{
|
|
public function inspect(string $path): ?string
|
|
{
|
|
$stream = @fopen($path, 'rb');
|
|
if (! is_resource($stream)) {
|
|
return 'archive_inspection_failed';
|
|
}
|
|
$signature = fread($stream, 4);
|
|
fclose($stream);
|
|
|
|
if (! is_string($signature) || ! in_array($signature, ["PK\x03\x04", "PK\x05\x06", "PK\x07\x08"], true)) {
|
|
return null;
|
|
}
|
|
|
|
if (! class_exists(\ZipArchive::class)) {
|
|
return 'archive_inspector_unavailable';
|
|
}
|
|
|
|
$archive = new \ZipArchive;
|
|
if ($archive->open($path, \ZipArchive::RDONLY) !== true) {
|
|
return 'archive_inspection_failed';
|
|
}
|
|
|
|
try {
|
|
$maxEntries = max(1, (int) config('services.malware.max_archive_entries', 1000));
|
|
$maxExpandedBytes = max(1, (int) config('services.malware.max_decompressed_bytes', 104857600));
|
|
if ($archive->numFiles > $maxEntries) {
|
|
return 'archive_entry_limit_exceeded';
|
|
}
|
|
|
|
$expandedBytes = 0;
|
|
for ($index = 0; $index < $archive->numFiles; $index++) {
|
|
$entry = $archive->statIndex($index);
|
|
if (! is_array($entry) || ! isset($entry['size']) || ! is_int($entry['size']) || $entry['size'] < 0) {
|
|
return 'archive_inspection_failed';
|
|
}
|
|
|
|
if ($entry['size'] > $maxExpandedBytes - $expandedBytes) {
|
|
return 'archive_decompression_limit';
|
|
}
|
|
$expandedBytes += $entry['size'];
|
|
}
|
|
|
|
return null;
|
|
} finally {
|
|
$archive->close();
|
|
}
|
|
}
|
|
}
|