maxSizeBytes(); if ($maxBytes > 0 && $size > $maxBytes) { return UploadValidationResult::fail('file_too_large', null, null, null, $size); } $mime = $this->detectMime($path); if ($mime === '' || ! in_array($mime, $this->allowedMimes(), true)) { return UploadValidationResult::fail('mime_not_allowed', null, null, $mime, $size); } $info = @getimagesize($path); if (! $info || empty($info[0]) || empty($info[1])) { return UploadValidationResult::fail('invalid_image', null, null, $mime, $size); } $width = (int) $info[0]; $height = (int) $info[1]; $maxPixels = $this->maxPixels(); if ($maxPixels > 0 && ($width > $maxPixels || $height > $maxPixels)) { return UploadValidationResult::fail('image_too_large', $width, $height, $mime, $size); } $data = @file_get_contents($path); if ($data === false) { return UploadValidationResult::fail('file_unreadable', $width, $height, $mime, $size); } $image = @imagecreatefromstring($data); if ($image === false) { return UploadValidationResult::fail('decode_failed', $width, $height, $mime, $size); } $reencodeOk = $this->reencodeTest($image, $mime); imagedestroy($image); if (! $reencodeOk) { return UploadValidationResult::fail('reencode_failed', $width, $height, $mime, $size); } return UploadValidationResult::ok($width, $height, $mime, $size); } private function maxSizeBytes(): int { return (int) config('uploads.max_size_mb', 0) * 1024 * 1024; } private function maxPixels(): int { return (int) config('uploads.max_pixels', 0); } private function allowedMimes(): array { $allowed = (array) config('uploads.allowed_mimes', []); if ((bool) config('uploads.allow_gif', false)) { $allowed[] = 'image/gif'; } return array_values(array_unique($allowed)); } private function detectMime(string $path): string { $finfo = new \finfo(FILEINFO_MIME_TYPE); $mime = $finfo->file($path); return $mime ? (string) $mime : ''; } private function reencodeTest($image, string $mime): bool { ob_start(); $result = false; switch ($mime) { case 'image/jpeg': $result = function_exists('imagejpeg') ? imagejpeg($image, null, 80) : false; break; case 'image/png': $result = function_exists('imagepng') ? imagepng($image, null, 6) : false; break; case 'image/webp': $result = function_exists('imagewebp') ? imagewebp($image, null, 80) : false; break; case 'image/gif': $result = function_exists('imagegif') ? imagegif($image) : false; break; } $data = ob_get_clean(); return (bool) $result && is_string($data) && $data !== ''; } }