uploadDir = sys_get_temp_dir() . '/urlshort-uploads-' . $suffix; $this->tmpDir = sys_get_temp_dir() . '/urlshort-tmp-' . $suffix; mkdir($this->uploadDir, 0775, true); mkdir($this->tmpDir, 0775, true); } protected function tearDown(): void { foreach ([$this->uploadDir, $this->tmpDir] as $dir) { foreach (glob($dir . '/*') ?: [] as $file) { unlink($file); } if (is_dir($dir)) { rmdir($dir); } } } private function store(): LogoStore { return new LogoStore($this->uploadDir, 1024 * 1024); } /** * @return array */ private function upload(string $name, string $contents): array { $path = $this->tmpDir . '/' . bin2hex(random_bytes(4)); file_put_contents($path, $contents); return [ 'name' => $name, 'tmp_name' => $path, 'size' => strlen($contents), 'error' => UPLOAD_ERR_OK, ]; } private function validPng(int $width = 64, int $height = 64): string { $image = imagecreatetruecolor($width, $height); self::assertNotFalse($image); imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, (int) imagecolorallocate($image, 11, 79, 158)); ob_start(); imagepng($image); $data = (string) ob_get_clean(); imagedestroy($image); return $data; } public function testValidPngIsStoredWithGeneratedName(): void { $filename = $this->store()->store($this->upload('mein logo.png', $this->validPng()), 7); self::assertMatchesRegularExpression('/^logo-7-[a-f0-9]{12}\.png$/', $filename); self::assertFileExists($this->uploadDir . '/' . $filename); } public function testCorruptPngIsRejected(): void { // Gueltiger PNG-Header, aber unbrauchbare Bilddaten: getimagesize() // akzeptiert das, GD kann es nicht dekodieren. $corrupt = base64_decode( 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAHUlEQVQoz2NkYPjPQApgYhhVMKpgVMGoglEFpCsAAFXsAQEt4A0oAAAAAElFTkSuQmCC', true, ); self::assertIsString($corrupt); $this->expectException(RuntimeException::class); $this->expectExceptionMessageMatches('/beschaedigt|gelesen/'); $this->store()->store($this->upload('kaputt.png', $corrupt), 1); } public function testOversizedImageIsRejected(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessageMatches('/zu gross/'); $this->store()->store($this->upload('riesig.png', $this->validPng(4200, 10)), 1); } public function testNonImageIsRejected(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessageMatches('/Nur PNG, JPG und SVG/'); $this->store()->store($this->upload('schad.php', 'uploadDir, 100); $this->expectException(RuntimeException::class); $this->expectExceptionMessageMatches('/hoechstens/'); $store->store($this->upload('gross.png', $this->validPng()), 1); } public function testSvgIsStrippedOfScriptsAndHandlers(): void { $svg = '' . '' . '' . 'x' . ''; $filename = $this->store()->store($this->upload('logo.svg', $svg), 3); $stored = (string) file_get_contents($this->uploadDir . '/' . $filename); self::assertStringEndsWith('.svg', $filename); self::assertStringNotContainsString('expectException(RuntimeException::class); $this->store()->store($this->upload('fake.svg', 'nur text, kein svg'), 1); } public function testPathForIgnoresDirectoryTraversal(): void { $store = $this->store(); $store->store($this->upload('logo.png', $this->validPng()), 5); self::assertNull($store->pathFor('../../../etc/passwd')); self::assertNull($store->pathFor('..\\..\\windows\\win.ini')); self::assertNull($store->pathFor(null)); self::assertNull($store->pathFor('')); } public function testDeleteRemovesTheFile(): void { $store = $this->store(); $filename = $store->store($this->upload('logo.png', $this->validPng()), 9); self::assertFileExists($this->uploadDir . '/' . $filename); $store->delete($filename); self::assertFileDoesNotExist($this->uploadDir . '/' . $filename); } public function testIsSvgDetection(): void { $store = $this->store(); self::assertTrue($store->isSvg('logo-1-abc.svg')); self::assertTrue($store->isSvg('LOGO.SVG')); self::assertFalse($store->isSvg('logo-1-abc.png')); self::assertFalse($store->isSvg(null)); } public function testMissingUploadIsReported(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessageMatches('/keine Datei/'); $this->store()->store(['error' => UPLOAD_ERR_NO_FILE], 1); } }