From 1bc3e17bd29afb7dafa6607c13ae4636c87f88f9 Mon Sep 17 00:00:00 2001 From: uzziahlukeka Date: Sat, 22 Mar 2025 11:11:03 +0100 Subject: [PATCH 1/6] issue with sanitize --- composer.lock | 12 +-- src/BlockFactory.php | 2 +- src/Blocks/Block.php | 28 +++--- src/Blocks/Listing.php | 1 + src/Blocks/Paragraph.php | 34 +++++++ tests/Blocks/ParagraphTest.php | 159 +++++++++++++++++++++++++++++++++ 6 files changed, 215 insertions(+), 21 deletions(-) create mode 100644 tests/Blocks/ParagraphTest.php diff --git a/composer.lock b/composer.lock index 970e660..6eeb20b 100755 --- a/composer.lock +++ b/composer.lock @@ -499,16 +499,16 @@ }, { "name": "symfony/html-sanitizer", - "version": "v7.2.2", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/html-sanitizer.git", - "reference": "f6bc679b024e30f27e33815930a5b8b304c79813" + "reference": "91443febe34cfa5e8e00425f892e6316db95bc23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/f6bc679b024e30f27e33815930a5b8b304c79813", - "reference": "f6bc679b024e30f27e33815930a5b8b304c79813", + "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/91443febe34cfa5e8e00425f892e6316db95bc23", + "reference": "91443febe34cfa5e8e00425f892e6316db95bc23", "shasum": "" }, "require": { @@ -548,7 +548,7 @@ "sanitizer" ], "support": { - "source": "https://github.com/symfony/html-sanitizer/tree/v7.2.2" + "source": "https://github.com/symfony/html-sanitizer/tree/v7.2.3" }, "funding": [ { @@ -564,7 +564,7 @@ "type": "tidelift" } ], - "time": "2024-12-30T18:35:15+00:00" + "time": "2025-01-27T11:08:17+00:00" }, { "name": "symfony/polyfill-mbstring", diff --git a/src/BlockFactory.php b/src/BlockFactory.php index b4103a8..0c192fb 100755 --- a/src/BlockFactory.php +++ b/src/BlockFactory.php @@ -102,7 +102,7 @@ private static function getAllowedTags(mixed $block, array $allowedTags): array private static function getSchema(string $schema): array { - $filename = sprintf("%s/schemas/%s.schema.json", dirname(__DIR__), $schema); + $filename = sprintf('%s/schemas/%s.schema.json', dirname(__DIR__), $schema); $content = file_get_contents($filename); Assert::string($content, sprintf('Schema for block type %s not found', $schema)); diff --git a/src/Blocks/Block.php b/src/Blocks/Block.php index 3995f36..3b9ccd9 100755 --- a/src/Blocks/Block.php +++ b/src/Blocks/Block.php @@ -44,18 +44,18 @@ public static function create(array $data, array $allowedTags = []): static ); } -// private function sanitize(array $data, array|string $allowedTags = []): array -// { -// $sanitizer = Sanitizer::create($allowedTags); -// -// foreach ($data as $key => $value) { -// if (is_array($value)) { -// $data[$key] = $this->sanitize($value, $allowedTags); -// } else { -// $data[$key] = $sanitizer->sanitize($value); -// } -// } -// -// return $data; -// } + // private function sanitize(array $data, array|string $allowedTags = []): array + // { + // $sanitizer = Sanitizer::create($allowedTags); + // + // foreach ($data as $key => $value) { + // if (is_array($value)) { + // $data[$key] = $this->sanitize($value, $allowedTags); + // } else { + // $data[$key] = $sanitizer->sanitize($value); + // } + // } + // + // return $data; + // } } diff --git a/src/Blocks/Listing.php b/src/Blocks/Listing.php index fb114f8..ebd2cb2 100755 --- a/src/Blocks/Listing.php +++ b/src/Blocks/Listing.php @@ -14,6 +14,7 @@ namespace Devscast\EditorJs\Blocks; use Devscast\EditorJs\Assert; +use Devscast\EditorJs\Sanitizer; /** * Class Listing. diff --git a/src/Blocks/Paragraph.php b/src/Blocks/Paragraph.php index 9a3ffd6..17dd08d 100755 --- a/src/Blocks/Paragraph.php +++ b/src/Blocks/Paragraph.php @@ -14,6 +14,7 @@ namespace Devscast\EditorJs\Blocks; use Devscast\EditorJs\Assert; +use Devscast\EditorJs\Sanitizer; /** * Class Paragraph. @@ -40,4 +41,37 @@ public function __construct(string $id, string $type, array $data, ?array $tunes parent::__construct($id, $type, $data, $tunes, $rules); } + + public function toHtml(): string + { + $allowedTags = [ + 'a' => ['href', 'title', 'rel'], + 'strong' => [], + 'em' => [], + 'u' => [], + 'br' => [], + 'p' => ['id'], + ]; + $rawHtml = $this->toRawHtml($this->data); + $sanitizedHtml = $this->sanitizeHtml($rawHtml, $allowedTags); + dump('Sanitized HTML:', $sanitizedHtml); + return $sanitizedHtml; + } + + //convertion vers du html brut + private function toRawHtml(array $data) + { + return "

id}\">{$data['text']}

"; + } + + //nettoyage du html + + private function sanitizeHtml(string $html, array|string $allowedTags = []) + { + $sanitizer = Sanitizer::create($allowedTags); + dump('Input HTML:', $html); + dump('Sanitized HTML:', $sanitizer->sanitize($html)); + + return $sanitizer->sanitize($html); + } } diff --git a/tests/Blocks/ParagraphTest.php b/tests/Blocks/ParagraphTest.php new file mode 100644 index 0000000..7f73311 --- /dev/null +++ b/tests/Blocks/ParagraphTest.php @@ -0,0 +1,159 @@ + 'This is a test paragraph', + ]; + $tunes = null; + $rules = []; + + $paragraph = new Paragraph($id, $type, $data, $tunes, $rules); + + $this->assertInstanceOf(Paragraph::class, $paragraph); + } + + public function testValidParagraphWithFootnotesConstruction(): void + { + $id = '1'; + $type = 'paragraph'; + $data = [ + 'text' => 'This is a test paragraph', + ]; + $tunes = [ + 'footnotes' => [], + ]; + $rules = []; + + $paragraph = new Paragraph($id, $type, $data, $tunes, $rules); + + $this->assertInstanceOf(Paragraph::class, $paragraph); + } + + public function testInvalidTypeConstruction(): void + { + $this->expectException(EditorException::class); + $id = '1'; + $type = 'invalid-type'; + $data = [ + 'text' => 'This is a test paragraph', + ]; + $tunes = null; + $rules = []; + + new Paragraph($id, $type, $data, $tunes, $rules); + } + + public function testMissingTextConstruction(): void + { + $this->expectException(EditorException::class); + $id = '1'; + $type = 'paragraph'; + $data = []; + $tunes = null; + $rules = []; + + new Paragraph($id, $type, $data, $tunes, $rules); + } + + public function testMissingFootnotesInTunesConstruction(): void + { + $this->expectException(EditorException::class); + $id = '1'; + $type = 'paragraph'; + $data = [ + 'text' => 'This is a test paragraph', + ]; + $tunes = []; + $rules = []; + + new Paragraph($id, $type, $data, $tunes, $rules); + } + + public function testInvalidFootnotesTypeConstruction(): void + { + $this->expectException(EditorException::class); + $id = '1'; + $type = 'paragraph'; + $data = [ + 'text' => 'This is a test paragraph', + ]; + $tunes = [ + 'footnotes' => 'not-an-array', + ]; + $rules = []; + + new Paragraph($id, $type, $data, $tunes, $rules); + } + + public function testToHtml(): void + { + $id = 'test_id'; + $type = 'paragraph'; + $data = [ + 'text' => 'This is a test paragraph', + ]; + $tunes = null; + $rules = []; + + $paragraph = new Paragraph($id, $type, $data, $tunes, $rules); + $html = $paragraph->toHtml(); + $this->assertNotEmpty($html, 'HTML should not be empty'); + $this->assertStringContainsString('

This is a test paragraph

', $html); + } + + public function testToHtmlWithAllowedTags(): void + { + $id = 'test_id'; + $type = 'paragraph'; + $data = [ + 'text' => 'This is a test paragraph with link', + ]; + $tunes = null; + $rules = []; + + $paragraph = new Paragraph($id, $type, $data, $tunes, $rules); + $html = $paragraph->toHtml(); + + // Debug information + $this->assertNotEmpty($html, 'HTML output should not be empty. Output: ' . var_export($html, true)); + + // Check raw HTML before sanitization (using reflection to access private method) + $reflectionMethod = new \ReflectionMethod(Paragraph::class, 'toRawHtml'); + $rawHtml = $reflectionMethod->invoke($paragraph, $data); + $this->assertNotEmpty($rawHtml, 'Raw HTML should not be empty'); + + // Continue with the original assertions + $this->assertStringContainsString('test', $html); + $this->assertStringContainsString('link', $html); + } + + public function testToHtmlSanitizesDisallowedTags(): void + { + $id = 'test_id'; + $type = 'paragraph'; + $data = [ + 'text' => 'This is a test with ', + ]; + $tunes = null; + $rules = []; + + $paragraph = new Paragraph($id, $type, $data, $tunes, $rules); + $html = $paragraph->toHtml(); + + $this->assertStringNotContainsString(' test with ', - ]; - $tunes = null; - $rules = []; - - $paragraph = new Paragraph($id, $type, $data, $tunes, $rules); - $html = $paragraph->toHtml(); + $data = <<< JSON + { + "time": 1739980616433, + "blocks": [ + { + "id": "mhTl6ghSkV", + "type": "paragraph", + "data": { + "text": "This is a test with " + } + } + ] + } + JSON; + + /** @var Paragraph $paragraph */ + $paragraph = BlockFactory::parse($data, ['paragraph'], self::ALLOWED_TAGS)[0]; + $html = $paragraph->sanitize(); $this->assertStringNotContainsString(' quote", + "caption": "Author ", + "alignment": "left" + } + } + ] + } + JSON; + + /** @var Quote $quote */ + $quote = BlockFactory::parse($data, ['quote'], self::ALLOWED_TAGS)[0]; + $html = $quote->sanitize(); + + $this->assertStringNotContainsString('

This is raw HTML

" + } + } + ] + } + JSON; + + /** @var Raw $raw */ + $raw = BlockFactory::parse($data, ['raw'], self::ALLOWED_TAGS)[0]; + $html = $raw->sanitize(); + + $this->assertStringNotContainsString('Warning", + "message": "This has " + } + } + ] + } + JSON; + + $allowedTags = [ + 'warning' => [ + 'div' => ['id', 'class'], + 'h2' => [], + 'p' => [], + ], + ]; + + /** @var Warning $warning */ + $warning = BlockFactory::parse($data, ['warning'], $allowedTags)[0]; + $html = $warning->sanitize(); + + $this->assertStringNotContainsString('