canonicalMime); self::assertSame('png', $result->extension); } finally { unlink($path); } } public function testItRejectsAFileWithTheWrongDeclaredMime(): void { $path = self::TMP_DIR . '/bf-bad-mime-' . bin2hex(random_bytes(4)) . '.png'; self::assertNotFalse(file_put_contents($path, self::validPng(8, 8))); try { $this->expectException(InvalidImageException::class); $this->expectExceptionMessage('Declared MIME does not match file contents'); ImageValidator::validate($path, 'image/jpeg'); } finally { unlink($path); } } public function testItRejectsTextContent(): void { $path = self::TMP_DIR . '/bf-text-' . bin2hex(random_bytes(4)) . '.txt'; self::assertNotFalse(file_put_contents($path, 'this is not an image')); try { $this->expectException(InvalidImageException::class); ImageValidator::validate($path, 'image/png'); } finally { unlink($path); } } public function testItRejectsOversizedFiles(): void { $path = self::TMP_DIR . '/bf-huge-' . bin2hex(random_bytes(4)) . '.png'; // Create a 2 MB + 1 byte file $bytes = str_repeat('A', 2_000_001); self::assertNotFalse(file_put_contents($path, $bytes)); try { $this->expectException(InvalidImageException::class); $this->expectExceptionMessage('exceeds the 2000000 byte limit'); ImageValidator::validate($path, 'image/png'); } finally { unlink($path); } } public function testItRejectsOversizedDimensions(): void { $path = self::TMP_DIR . '/bf-wide-' . bin2hex(random_bytes(4)) . '.png'; self::assertNotFalse(file_put_contents($path, self::validPng(600, 8))); try { $this->expectException(InvalidImageException::class); $this->expectExceptionMessage('exceed the 512 pixel limit'); ImageValidator::validate($path, 'image/png'); } finally { unlink($path); } } public function testItAcceptsJpegWebpAndGif(): void { $jpeg = self::TMP_DIR . '/bf-jpeg-' . bin2hex(random_bytes(4)) . '.jpg'; $webp = self::TMP_DIR . '/bf-webp-' . bin2hex(random_bytes(4)) . '.webp'; $gif = self::TMP_DIR . '/bf-gif-' . bin2hex(random_bytes(4)) . '.gif'; file_put_contents($jpeg, self::validJpeg(8, 8)); file_put_contents($webp, self::validWebp(8, 8)); file_put_contents($gif, self::validGif(8, 8)); try { self::assertSame('jpg', ImageValidator::validate($jpeg, 'image/jpeg')->extension); self::assertSame('webp', ImageValidator::validate($webp, 'image/webp')->extension); self::assertSame('gif', ImageValidator::validate($gif, 'image/gif')->extension); } finally { unlink($jpeg); unlink($webp); unlink($gif); } } /** * Build a minimal valid PNG of the given dimensions, using a pre-baked * 8x8 transparent PNG as the base and trusting `getimagesize` to accept * any correctly-formed PNG. We just need the file to (1) have the PNG * magic and (2) be decodable by GD or `getimagesize`. * * For the oversized-dimensions test we synthesize a 600x8 PNG via * `imagecreatetruecolor` + `imagepng` to force the IHDR to claim those * dimensions. */ private static function validPng(int $width, int $height): string { if ($width === 8 && $height === 8) { return base64_decode( 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAFElEQVR4nGNgYGD4z0AswK' . 'EWBgYGRgYGBkYGRgAAB4nCH2AAAAAElFTkSuQmCC', true, ); } $im = imagecreatetruecolor($width, $height); if ($im === false) { throw new \RuntimeException('Failed to create image.'); } ob_start(); imagepng($im); $bytes = ob_get_clean(); imagedestroy($im); return (string) $bytes; } private static function validJpeg(int $width, int $height): string { $im = imagecreatetruecolor($width, $height); if ($im === false) { throw new \RuntimeException('Failed to create image.'); } ob_start(); imagejpeg($im); $bytes = ob_get_clean(); imagedestroy($im); return (string) $bytes; } private static function validWebp(int $width, int $height): string { $im = imagecreatetruecolor($width, $height); if ($im === false) { throw new \RuntimeException('Failed to create image.'); } ob_start(); imagewebp($im); $bytes = ob_get_clean(); imagedestroy($im); return (string) $bytes; } private static function validGif(int $width, int $height): string { $im = imagecreatetruecolor($width, $height); if ($im === false) { throw new \RuntimeException('Failed to create image.'); } ob_start(); imagegif($im); $bytes = ob_get_clean(); imagedestroy($im); return (string) $bytes; } }