diff --git a/Plugin/ImageContentPolyglotValidator.php b/Plugin/ImageContentPolyglotValidator.php new file mode 100644 index 0000000..7f417fe --- /dev/null +++ b/Plugin/ImageContentPolyglotValidator.php @@ -0,0 +1,58 @@ +getBase64EncodedData(); + if ($base64 === '') { + return $result; + } + + $decoded = base64_decode($base64, true); + if ($decoded === false) { + throw new InputException(new Phrase('Invalid image payload encoding.')); + } + + $haystack = strtolower($decoded); + foreach (self::DANGEROUS_MARKERS as $marker) { + if (strpos($haystack, strtolower($marker)) !== false) { + throw new InputException( + new Phrase('The uploaded image content is not allowed.') + ); + } + } + + return $result; + } +} diff --git a/Test/Unit/Plugin/ImageContentPolyglotValidatorTest.php b/Test/Unit/Plugin/ImageContentPolyglotValidatorTest.php new file mode 100644 index 0000000..3bbfa6b --- /dev/null +++ b/Test/Unit/Plugin/ImageContentPolyglotValidatorTest.php @@ -0,0 +1,88 @@ +model = new ImageContentPolyglotValidator(); + $this->subjectMock = $this->createMock(ImageContentValidator::class); + $this->imageContentMock = $this->createMock(ImageContentInterface::class); + } + + public function testValidGifPasses(): void + { + $validGif = base64_encode('GIF89a' . str_repeat("\x00", 10)); + $this->imageContentMock->method('getBase64EncodedData')->willReturn($validGif); + + $result = $this->model->afterIsValid($this->subjectMock, true, $this->imageContentMock); + $this->assertTrue($result); + } + + public function testPhpContentMarkerIsRejected(): void + { + $payload = 'imageContentMock->method('getBase64EncodedData')->willReturn(base64_encode($payload)); + + $this->expectException(InputException::class); + $this->expectExceptionMessage('The uploaded image content is not allowed.'); + + $this->model->afterIsValid($this->subjectMock, true, $this->imageContentMock); + } + + public function testGifPhpPolyglotIsRejected(): void + { + $payload = 'GIF89a;'; + $this->imageContentMock->method('getBase64EncodedData')->willReturn(base64_encode($payload)); + + $this->expectException(InputException::class); + $this->expectExceptionMessage('The uploaded image content is not allowed.'); + + $this->model->afterIsValid($this->subjectMock, true, $this->imageContentMock); + } + + public function testEmptyDataPasses(): void + { + $this->imageContentMock->method('getBase64EncodedData')->willReturn(''); + + $result = $this->model->afterIsValid($this->subjectMock, true, $this->imageContentMock); + $this->assertTrue($result); + } + + public function testFalseCoreValidationResultIsPreserved(): void + { + $this->imageContentMock->expects($this->never())->method('getBase64EncodedData'); + + $result = $this->model->afterIsValid($this->subjectMock, false, $this->imageContentMock); + $this->assertFalse($result); + } + + public function testInvalidEncodingThrowsException(): void + { + // "!!!" is not valid base64 with strict check + $this->imageContentMock->method('getBase64EncodedData')->willReturn('!!!'); + + $this->expectException(InputException::class); + $this->expectExceptionMessage('Invalid image payload encoding.'); + + $this->model->afterIsValid($this->subjectMock, true, $this->imageContentMock); + } +} diff --git a/Test/bootstrap.php b/Test/bootstrap.php new file mode 100644 index 0000000..7f630cb --- /dev/null +++ b/Test/bootstrap.php @@ -0,0 +1,36 @@ + - + + - + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..1ad5079 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,12 @@ + + + + + Test/Unit + + +