From 3924449df3a4b54a1029db682fa13a889d1ac97e Mon Sep 17 00:00:00 2001 From: Kevin Dees Date: Thu, 21 Nov 2024 12:56:08 -0500 Subject: [PATCH] migrate Sanitize to Hygiene --- README.md | 1 - composer.json | 4 +- src/Engines/ViewRenderEngine.php | 1 - src/FilterBasicTags.php | 82 -------------------- src/FilterTags.php | 77 ------------------- src/SanitizeHtml.php | 78 ------------------- tests/TestVista.php | 103 +------------------------- tests/views/test.php | 2 +- tests/views/with-layout-and-title.php | 3 +- 9 files changed, 7 insertions(+), 344 deletions(-) delete mode 100644 src/FilterBasicTags.php delete mode 100644 src/FilterTags.php delete mode 100644 src/SanitizeHtml.php diff --git a/README.md b/README.md index 24310c0..408f985 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ Vista is only five files and very capable: - **Partial Rendering**: Modularize your views with include and section methods. - **Scoped Data Passing**: Pass variables to views with isolated scopes for security and clarity. - **Extensible**: Works seamlessly with other PHP frameworks or custom solutions. -- **Sanitize**: Sanitize raw HTML, attributes, and JSON. ## Installation diff --git a/composer.json b/composer.json index 46f08b6..86ef56b 100644 --- a/composer.json +++ b/composer.json @@ -4,9 +4,7 @@ "license": "MIT", "require": { "php": ">=8.4", - "ext-mbstring": "*", - "ext-dom": "*", - "ext-libxml": "*" + "ext-mbstring": "*" }, "require-dev": { "phpunit/phpunit": "^11" diff --git a/src/Engines/ViewRenderEngine.php b/src/Engines/ViewRenderEngine.php index dd1cf98..fb5fa77 100644 --- a/src/Engines/ViewRenderEngine.php +++ b/src/Engines/ViewRenderEngine.php @@ -2,7 +2,6 @@ namespace Nullai\Vista\Engines; -use Nullai\Vista\SanitizeHtml; use Nullai\Vista\View; class ViewRenderEngine implements \Stringable diff --git a/src/FilterBasicTags.php b/src/FilterBasicTags.php deleted file mode 100644 index d160bea..0000000 --- a/src/FilterBasicTags.php +++ /dev/null @@ -1,82 +0,0 @@ - ['href', 'target', 'rel'], // Links - 'p' => [], // Paragraph - 'br' => [], // Line breaks - 'strong' => [], // Bold text - 'b' => [], // Bold text (alternative) - 'em' => [], // Italics - 'i' => [], // Italics (alternative) - 'u' => [], // Underlined text - 'span' => [], // Inline container - 'div' => [], // Block container - - // Headings - 'h1' => [], // Heading level 1 - 'h2' => [], // Heading level 2 - 'h3' => [], // Heading level 3 - 'h4' => [], // Heading level 4 - 'h5' => [], // Heading level 5 - 'h6' => [], // Heading level 6 - - // Lists - 'ul' => [], // Unordered list - 'ol' => [], // Ordered list - 'li' => [], // List items - - // Images and media - 'img' => ['src', 'alt', 'title', 'width', 'height'], // Images - 'figure' => [], // Figure container - 'figcaption' => [], // Figure caption - 'video' => ['src', 'controls', 'autoplay', 'loop', 'muted', 'poster', ], // Video - 'audio' => ['src', 'controls', 'autoplay', 'loop', 'muted', ], // Audio - - // Table container and structure - 'table' => ['summary', 'border', 'cellspacing', 'cellpadding'], // Table container - 'caption' => [], // Table caption - - // Table groupings - 'thead' => [], // Table head - 'tbody' => [], // Table body - 'tfoot' => [], // Table footer - - // Rows and cells - 'tr' => [], // Table rows - 'th' => ['scope', 'colspan', 'rowspan', 'abbr', 'align', 'valign', 'width'], // Table headers - 'td' => ['colspan', 'rowspan', 'headers', 'align', 'valign', 'width'], // Table data cells - - // Colgroup and col elements - 'colgroup' => ['span'], // Column group - 'col' => ['span', 'width'], // Column definition - - // Semantic HTML5 elements - 'section' => [], // Section - 'article' => [], // Article - 'aside' => [], // Aside - 'nav' => [], // Navigation - 'header' => [], // Header - 'footer' => [], // Footer - 'main' => [], // Main content area - - // Inline and block containers - 'code' => [], // Inline code - 'pre' => [], // Preformatted text - 'blockquote' => ['cite', ], // Block quotes - - // Modern interactive elements - 'details' => [], // Details element for collapsible content - 'summary' => [], // Summary element for collapsible content - 'mark' => [], // Highlighted text - 'time' => ['datetime', ], // Time element - 'progress' => ['value', 'max', ], // Progress bar - 'meter' => ['value', 'min', 'max', 'low', 'high', 'optimum', ], // Gauge - ]; -} \ No newline at end of file diff --git a/src/FilterTags.php b/src/FilterTags.php deleted file mode 100644 index 0e97c02..0000000 --- a/src/FilterTags.php +++ /dev/null @@ -1,77 +0,0 @@ -original = [ - 'tags' => $this->tags, - 'globalAttributes' => $this->globalAttributes, - ]; - } - - public function addGlobalAttributes(array $attributes) : static - { - $this->globalAttributes = array_unique(array_merge($this->globalAttributes, $attributes)); - - return $this; - } - - public function removeGlobalAttributes(array $attributes) : static - { - $this->globalAttributes = array_diff($this->globalAttributes, $attributes); - - return $this; - } - - public function resetGlobalAttributes() : static - { - $this->globalAttributes = $this->original['globalAttributes']; - - return $this; - } - - public function add($tag, $attributes) : static - { - if(isset($this->tags[$tag])) { - $this->tags[$tag] = array_merge($this->tags[$tag], $attributes); - } - else { - $this->tags[$tag] = $attributes; - } - - return $this; - } - - public function remove($tag) : static - { - unset($this->tags[$tag]); - - return $this; - } - - public function resetTags() : static - { - $this->globalAttributes = $this->original['tags']; - - return $this; - } - - public function __toString() : string - { - $list = []; - $g = $this->globalAttributes; - - foreach($this->tags as $tag => $attributes) { - $list[] = $tag . (count($attributes) > 0 || !empty($g) ? ':' . implode('|', array_unique(array_merge($attributes, $g))) : ''); - } - - return implode(',', $list); - } -} \ No newline at end of file diff --git a/src/SanitizeHtml.php b/src/SanitizeHtml.php deleted file mode 100644 index ae348e0..0000000 --- a/src/SanitizeHtml.php +++ /dev/null @@ -1,78 +0,0 @@ -querySelectorAll($selector ?? '*') as $node) { - if (in_array($node->localName, $allowedTagsList) !== $allow) { - $node->parentNode->removeChild($node); - continue; - } - - foreach (iterator_to_array($node->attributes) as $attribute) { - if (!$attribute->localName || in_array($attribute->localName, $tags[$node->localName] ?? []) !== $allow) { - $node->removeAttribute($attribute->localName); - } - } - } - - return $dom->saveHTML(); - } - - public static function escHtml($html, $flags = ENT_NOQUOTES) : string - { - return htmlspecialchars($html, $flags, static::ENCODING); - } - - public static function escAttr($html, $flags = ENT_QUOTES) : string - { - return htmlspecialchars($html, $flags, static::ENCODING); - } - - /** - * Escape data for JSON encoding. - * - * @param mixed $data The data to be encoded. - * @param int $flags Optional. Bitmask consisting of JSON encode options. - * @return string The JSON encoded string. - */ - public static function escJson(mixed $data, int $flags = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) : string - { - return json_encode($data, $flags); - } -} \ No newline at end of file diff --git a/tests/TestVista.php b/tests/TestVista.php index 97d6488..f48caf3 100644 --- a/tests/TestVista.php +++ b/tests/TestVista.php @@ -43,14 +43,14 @@ public function testViewContent() { $view = new View('test'); - $this->assertStringContainsString('test file &', $view->content()); + $this->assertStringContainsString('test file &', $view->content()); } public function testViewContentRelativeLookup() { $view = new View(':test'); - $this->assertStringContainsString('test file &', $view->content()); + $this->assertStringContainsString('test file &', $view->content()); } public function testViewEngineClass() @@ -106,7 +106,7 @@ public function testViewEngineLayoutWithTitleAndJsonEscape() $this->assertStringStartsWith('assertStringContainsString('test title', $content); $this->assertStringContainsString('short tag', $content); - $this->assertStringContainsString('console.log({"site":"\u003CMy Site\u003E"});', $content); + $this->assertStringContainsString('console.log(\'test\');', $content); $this->assertStringNotContainsString('test file &', $content); $this->assertStringEndsWith('html>', $content); } @@ -116,101 +116,6 @@ public function testViewEngineRelativeIncludeNestWithGlobalAndLocalVars() $view = new View('nest.level-two', ['content' => 'nested']); $content = $view->content(); - $this->assertEquals('nested3test file &', $content); - } - - public function testViewEngineSanitizeAttributes() - { - $content = \Nullai\Vista\SanitizeHtml::escAttr('<&">'); - $this->assertEquals('<&">', $content); - } - - public function testViewEngineSanitizeHtml() - { - $content = \Nullai\Vista\SanitizeHtml::escHtml('<&">'); - $this->assertEquals('<&">', $content); - } - - public function testViewEngineSanitizeJson() - { - $content = \Nullai\Vista\SanitizeHtml::escJson(['site' => ' " & Site> & " >>']); - $this->assertEquals('{"site":"\u003CMy \u003Ca\u003E \u0022 \u0026 Site\u003E \u0026 \u0022 \u003E\u003E"}', $content); - } - - public function testViewEngineAllowTags() - { - $content = \Nullai\Vista\SanitizeHtml::allowTags( - "\">Link", - ['a' => []] - ); - $this->assertEquals('Link', $content); - } - - public function testViewEngineAllowTagsWithAttributes() - { - $content = \Nullai\Vista\SanitizeHtml::allowTags( - "Link", - 'a:href|style,br,p,ol,ul,figure:src' - ); - $this->assertEquals('Link', $content); - - $content = \Nullai\Vista\SanitizeHtml::allowTags( - "Link
", - 'a:href|style,br' - ); - $this->assertEquals('Link
', $content); - } - - public function testViewEngineAllowTagsWithTextareaAttributes() - { - $content = \Nullai\Vista\SanitizeHtml::allowTags( - '', - 'textarea' - ); - $this->assertEquals('', $content); - - $content = \Nullai\Vista\SanitizeHtml::allowTags( - '', - 'textarea:value' - ); - $this->assertEquals('', $content); - } - - public function testViewEngineFilterTagsClass() - { - $content = \Nullai\Vista\SanitizeHtml::allowTags( - '', - new FilterBasicTags()->add('textarea', []) - ); - $this->assertEquals('', $content); - - $content = \Nullai\Vista\SanitizeHtml::allowTags( - '', - new FilterBasicTags() - ); - $this->assertEquals('', $content); - - $content = \Nullai\Vista\SanitizeHtml::allowTags( - '', - new FilterBasicTags() - ); - $this->assertEquals('', $content); - - $content = \Nullai\Vista\SanitizeHtml::allowTags( - html: '', - tags: new FilterBasicTags(), - allow: false - ); - $this->assertEquals('', $content); - } - - public function testViewEngineFilterTagsClassBlacklist() - { - $content = \Nullai\Vista\SanitizeHtml::allowTags( - html: '', - tags: 'iframe,script', - allow: false - ); - $this->assertEquals('', $content); + $this->assertEquals('nested3test file &', $content); } } diff --git a/tests/views/test.php b/tests/views/test.php index 8e22daf..494efaf 100644 --- a/tests/views/test.php +++ b/tests/views/test.php @@ -1 +1 @@ -section('scripts'); ?> end(); ?>