298 lines
10 KiB
PHP
298 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Helpers\StringHelper;
|
|
use App\Models\Post;
|
|
use App\Models\PostTranslation;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
class PostContentXssProtectionTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
/**
|
|
* Test that the sanitizeHtml method properly escapes script tags.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_escapes_script_tags()
|
|
{
|
|
$maliciousContent = '<p>Hello</p><script>alert("XSS")</script><p>World</p>';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($maliciousContent);
|
|
|
|
// Script tags should be completely removed
|
|
$this->assertStringNotContainsString('<script>', $sanitized);
|
|
$this->assertStringNotContainsString('alert("XSS")', $sanitized);
|
|
|
|
// Safe HTML should be preserved
|
|
$this->assertStringContainsString('<p>Hello</p>', $sanitized);
|
|
$this->assertStringContainsString('<p>World</p>', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test that img tag with onerror handler is sanitized.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_sanitizes_img_onerror()
|
|
{
|
|
$maliciousContent = '<p>Check this image</p><img src="x" onerror="alert(document.cookie)" />';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($maliciousContent);
|
|
|
|
// Image should be allowed but without dangerous attributes
|
|
$this->assertStringContainsString('<img', $sanitized);
|
|
$this->assertStringNotContainsString('onerror', $sanitized);
|
|
$this->assertStringNotContainsString('alert(document.cookie)', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test that iframe injection is completely removed.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_removes_iframe()
|
|
{
|
|
$maliciousContent = '<p>Content</p><iframe src="https://evil.com"></iframe><p>More content</p>';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($maliciousContent);
|
|
|
|
// Iframe should be completely removed
|
|
$this->assertStringNotContainsString('<iframe', $sanitized);
|
|
$this->assertStringNotContainsString('evil.com', $sanitized);
|
|
|
|
// Safe content preserved
|
|
$this->assertStringContainsString('<p>Content</p>', $sanitized);
|
|
$this->assertStringContainsString('<p>More content</p>', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test that safe rich text formatting is preserved.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_preserves_safe_formatting()
|
|
{
|
|
$safeContent = '<h1>Title</h1><p>Paragraph with <strong>bold</strong> and <em>italic</em> text.</p><ul><li>Item 1</li><li>Item 2</li></ul><a href="https://example.com">Link</a>';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($safeContent);
|
|
|
|
// All safe formatting should be preserved
|
|
$this->assertStringContainsString('<h1>Title</h1>', $sanitized);
|
|
$this->assertStringContainsString('<strong>bold</strong>', $sanitized);
|
|
$this->assertStringContainsString('<em>italic</em>', $sanitized);
|
|
$this->assertStringContainsString('<ul>', $sanitized);
|
|
$this->assertStringContainsString('<li>Item 1</li>', $sanitized);
|
|
$this->assertStringContainsString('<a href="https://example.com">Link</a>', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test that event handler attributes are removed.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_removes_event_handlers()
|
|
{
|
|
$maliciousContent = '<a href="#" onclick="alert(1)">Click me</a><div onmouseover="steal()">Hover</div>';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($maliciousContent);
|
|
|
|
// Event handlers should be removed
|
|
$this->assertStringNotContainsString('onclick', $sanitized);
|
|
$this->assertStringNotContainsString('onmouseover', $sanitized);
|
|
$this->assertStringNotContainsString('alert(1)', $sanitized);
|
|
$this->assertStringNotContainsString('steal()', $sanitized);
|
|
|
|
// Safe parts should remain (div is allowed with class attribute)
|
|
$this->assertStringContainsString('Click me', $sanitized);
|
|
$this->assertStringContainsString('Hover', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test that data URIs with JavaScript are removed.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_removes_javascript_data_uris()
|
|
{
|
|
$maliciousContent = '<a href="data:text/html,<script>alert(1)</script>">Click</a>';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($maliciousContent);
|
|
|
|
// JavaScript data URIs should be removed
|
|
$this->assertStringNotContainsString('data:text/html', $sanitized);
|
|
$this->assertStringNotContainsString('<script>', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test that style tags with CSS injection are removed.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_removes_style_tags()
|
|
{
|
|
$maliciousContent = '<p>Text</p><style>body { background: url("javascript:alert(1)") }</style>';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($maliciousContent);
|
|
|
|
// Style tags should be removed
|
|
$this->assertStringNotContainsString('<style>', $sanitized);
|
|
$this->assertStringNotContainsString('javascript:alert', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test that object and embed tags are removed.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_removes_object_embed_tags()
|
|
{
|
|
$maliciousContent = '<object data="malicious.swf"></object><embed src="evil.swf">';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($maliciousContent);
|
|
|
|
// Object and embed tags should be removed
|
|
$this->assertStringNotContainsString('<object', $sanitized);
|
|
$this->assertStringNotContainsString('<embed', $sanitized);
|
|
$this->assertStringNotContainsString('malicious.swf', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test that null input returns empty string.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_handles_null_input()
|
|
{
|
|
$sanitized = StringHelper::sanitizeHtml(null);
|
|
|
|
$this->assertEquals('', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test that empty string returns empty string.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_handles_empty_string()
|
|
{
|
|
$sanitized = StringHelper::sanitizeHtml('');
|
|
|
|
$this->assertEquals('', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test that table HTML is preserved for structured content.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_preserves_tables()
|
|
{
|
|
$tableContent = '<table><thead><tr><th>Header</th></tr></thead><tbody><tr><td>Data</td></tr></tbody></table>';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($tableContent);
|
|
|
|
// Table structure should be preserved
|
|
$this->assertStringContainsString('<table>', $sanitized);
|
|
$this->assertStringContainsString('<thead>', $sanitized);
|
|
$this->assertStringContainsString('<th>Header</th>', $sanitized);
|
|
$this->assertStringContainsString('<tbody>', $sanitized);
|
|
$this->assertStringContainsString('<td>Data</td>', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test that images with valid attributes are preserved.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_preserves_safe_images()
|
|
{
|
|
$imageContent = '<img src="https://example.com/image.jpg" alt="Description" width="500" height="300" title="My Image">';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($imageContent);
|
|
|
|
// Valid image attributes should be preserved
|
|
$this->assertStringContainsString('src="https://example.com/image.jpg"', $sanitized);
|
|
$this->assertStringContainsString('alt="Description"', $sanitized);
|
|
$this->assertStringContainsString('width="500"', $sanitized);
|
|
$this->assertStringContainsString('height="300"', $sanitized);
|
|
$this->assertStringContainsString('title="My Image"', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test that links with target="_blank" are preserved.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_preserves_target_blank_links()
|
|
{
|
|
$linkContent = '<a href="https://example.com" target="_blank" title="External Link">Visit</a>';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($linkContent);
|
|
|
|
// Link with target="_blank" should be preserved
|
|
$this->assertStringContainsString('href="https://example.com"', $sanitized);
|
|
$this->assertStringContainsString('target="_blank"', $sanitized);
|
|
$this->assertStringContainsString('title="External Link"', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test mixed safe and unsafe content.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_handles_mixed_content()
|
|
{
|
|
$mixedContent = '<h2>Article Title</h2><p>This is <strong>safe</strong> content.</p><script>alert("unsafe")</script><p>More safe content with <a href="https://example.com">a link</a>.</p>';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($mixedContent);
|
|
|
|
// Safe content preserved
|
|
$this->assertStringContainsString('<h2>Article Title</h2>', $sanitized);
|
|
$this->assertStringContainsString('<strong>safe</strong>', $sanitized);
|
|
$this->assertStringContainsString('<a href="https://example.com">a link</a>', $sanitized);
|
|
|
|
// Unsafe content removed
|
|
$this->assertStringNotContainsString('<script>', $sanitized);
|
|
$this->assertStringNotContainsString('alert("unsafe")', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test that code and pre tags are preserved for technical content.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_preserves_code_blocks()
|
|
{
|
|
$codeContent = '<pre><code>function hello() { return "world"; }</code></pre>';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($codeContent);
|
|
|
|
// Code blocks should be preserved
|
|
$this->assertStringContainsString('<pre>', $sanitized);
|
|
$this->assertStringContainsString('<code>', $sanitized);
|
|
$this->assertStringContainsString('function hello()', $sanitized);
|
|
}
|
|
|
|
/**
|
|
* Test SVG injection attempts are blocked.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function test_post_content_blocks_svg_injection()
|
|
{
|
|
$maliciousContent = '<svg onload="alert(1)"><circle cx="50" cy="50" r="40"/></svg>';
|
|
|
|
$sanitized = StringHelper::sanitizeHtml($maliciousContent);
|
|
|
|
// SVG should be removed (not in allowed tags list)
|
|
$this->assertStringNotContainsString('<svg', $sanitized);
|
|
$this->assertStringNotContainsString('onload', $sanitized);
|
|
$this->assertStringNotContainsString('alert(1)', $sanitized);
|
|
}
|
|
}
|