240 lines
7.5 KiB
PHP
240 lines
7.5 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Shared;
|
|
|
|
use App\Shared\Html\HtmlPurifierFactory;
|
|
use App\Shared\Html\HtmlSanitizer;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Tests unitaires pour HtmlSanitizer.
|
|
*
|
|
* Vérifie que HTMLPurifier supprime bien les contenus dangereux
|
|
* (XSS, balises non autorisées, schémas URI interdits) et conserve
|
|
* les balises légitimes produites par l'éditeur Trumbowyg.
|
|
*
|
|
* Ces tests utilisent une vraie instance HTMLPurifier (pas de mock)
|
|
* car c'est le comportement de purification lui-même qui est testé.
|
|
*/
|
|
final class HtmlSanitizerTest extends TestCase
|
|
{
|
|
private HtmlSanitizer $sanitizer;
|
|
|
|
/**
|
|
* Crée une instance réelle de HtmlSanitizer avant chaque test.
|
|
*/
|
|
protected function setUp(): void
|
|
{
|
|
$purifier = HtmlPurifierFactory::create(sys_get_temp_dir() . '/htmlpurifier_tests');
|
|
$this->sanitizer = new HtmlSanitizer($purifier);
|
|
}
|
|
|
|
|
|
// ── Balises autorisées ─────────────────────────────────────────
|
|
|
|
/**
|
|
* Les balises de texte courantes doivent être conservées.
|
|
*/
|
|
public function testTextTagsPreserved(): void
|
|
{
|
|
$html = '<p>Un <strong>texte</strong> avec <em>emphase</em> et <u>soulignement</u>.</p>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringContainsString('<strong>texte</strong>', $result);
|
|
$this->assertStringContainsString('<em>emphase</em>', $result);
|
|
$this->assertStringContainsString('<u>soulignement</u>', $result);
|
|
}
|
|
|
|
/**
|
|
* Les titres h1 à h6 doivent être conservés.
|
|
*/
|
|
public function testHeadingsPreserved(): void
|
|
{
|
|
$html = '<h1>Titre 1</h1><h2>Titre 2</h2><h3>Titre 3</h3>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringContainsString('<h1>', $result);
|
|
$this->assertStringContainsString('<h2>', $result);
|
|
$this->assertStringContainsString('<h3>', $result);
|
|
}
|
|
|
|
/**
|
|
* Les listes ordonnées et non ordonnées doivent être conservées.
|
|
*/
|
|
public function testListsPreserved(): void
|
|
{
|
|
$html = '<ul><li>Item 1</li><li>Item 2</li></ul><ol><li>A</li></ol>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringContainsString('<ul>', $result);
|
|
$this->assertStringContainsString('<ol>', $result);
|
|
$this->assertStringContainsString('<li>', $result);
|
|
}
|
|
|
|
/**
|
|
* Les liens avec href http/https doivent être conservés.
|
|
*/
|
|
public function testHttpLinksPreserved(): void
|
|
{
|
|
$html = '<a href="https://example.com">Lien</a>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringContainsString('href="https://example.com"', $result);
|
|
$this->assertStringContainsString('Lien', $result);
|
|
}
|
|
|
|
/**
|
|
* Les images avec src doivent être conservées.
|
|
*/
|
|
public function testImagesPreserved(): void
|
|
{
|
|
$html = '<img src="https://example.com/image.jpg" alt="Description">';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringContainsString('<img', $result);
|
|
$this->assertStringContainsString('src="https://example.com/image.jpg"', $result);
|
|
}
|
|
|
|
/**
|
|
* Les blocs de code doivent être conservés.
|
|
*/
|
|
public function testPreTagPreserved(): void
|
|
{
|
|
$html = '<pre>code ici</pre>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringContainsString('<pre>', $result);
|
|
}
|
|
|
|
|
|
// ── Balises et attributs dangereux — suppression XSS ───────────
|
|
|
|
/**
|
|
* Les balises <script> doivent être supprimées.
|
|
*/
|
|
public function testScriptTagRemoved(): void
|
|
{
|
|
$html = '<p>Texte</p><script>alert("xss")</script>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringNotContainsString('<script>', $result);
|
|
$this->assertStringNotContainsString('alert(', $result);
|
|
}
|
|
|
|
/**
|
|
* Les attributs onclick et autres handlers JavaScript doivent être supprimés.
|
|
*/
|
|
public function testJavascriptAttributesRemoved(): void
|
|
{
|
|
$html = '<p onclick="alert(1)" onmouseover="evil()">Texte</p>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringNotContainsString('onclick', $result);
|
|
$this->assertStringNotContainsString('onmouseover', $result);
|
|
}
|
|
|
|
/**
|
|
* Les liens javascript: doivent être supprimés.
|
|
*/
|
|
public function testJavascriptLinkRemoved(): void
|
|
{
|
|
$html = '<a href="javascript:alert(1)">Cliquez</a>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringNotContainsString('javascript:', $result);
|
|
}
|
|
|
|
/**
|
|
* Les liens data: doivent être supprimés.
|
|
*/
|
|
public function testDataLinkRemoved(): void
|
|
{
|
|
$html = '<a href="data:text/html,<script>alert(1)</script>">XSS</a>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringNotContainsString('data:', $result);
|
|
}
|
|
|
|
/**
|
|
* La balise <iframe> doit être supprimée.
|
|
*/
|
|
public function testIframeTagRemoved(): void
|
|
{
|
|
$html = '<iframe src="https://evil.com"></iframe>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringNotContainsString('<iframe', $result);
|
|
}
|
|
|
|
/**
|
|
* La balise <object> doit être supprimée.
|
|
*/
|
|
public function testObjectTagRemoved(): void
|
|
{
|
|
$html = '<object data="malware.swf"></object>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringNotContainsString('<object', $result);
|
|
}
|
|
|
|
/**
|
|
* La balise <form> doit être supprimée.
|
|
*/
|
|
public function testFormTagRemoved(): void
|
|
{
|
|
$html = '<form action="/steal"><input type="password"></form>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringNotContainsString('<form', $result);
|
|
$this->assertStringNotContainsString('<input', $result);
|
|
}
|
|
|
|
|
|
// ── Cas limites ────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Une chaîne vide doit retourner une chaîne vide (ou quasi-vide).
|
|
*/
|
|
public function testEmptyStringReturnsEmptyOrBlank(): void
|
|
{
|
|
$result = $this->sanitizer->sanitize('');
|
|
|
|
$this->assertSame('', trim($result));
|
|
}
|
|
|
|
/**
|
|
* Du texte brut sans balises doit être conservé.
|
|
*/
|
|
public function testPlainTextWithoutTags(): void
|
|
{
|
|
$html = 'Bonjour le monde';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringContainsString('Bonjour le monde', $result);
|
|
}
|
|
|
|
/**
|
|
* Les attributs CSS text-align doivent être conservés.
|
|
*/
|
|
public function testStyleTextAlignAttributePreserved(): void
|
|
{
|
|
$html = '<p style="text-align: center;">Centré</p>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringContainsString('text-align', $result);
|
|
}
|
|
|
|
/**
|
|
* Les propriétés CSS autres que text-align doivent être supprimées.
|
|
*/
|
|
public function testOtherCssPropertiesRemoved(): void
|
|
{
|
|
$html = '<p style="color: red; background: url(evil.php);">Texte</p>';
|
|
$result = $this->sanitizer->sanitize($html);
|
|
|
|
$this->assertStringNotContainsString('color', $result);
|
|
$this->assertStringNotContainsString('background', $result);
|
|
}
|
|
}
|