feat: add output escaping helpers
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BattleForge\Http;
|
||||
|
||||
final class Escape
|
||||
{
|
||||
public static function html(mixed $value): string
|
||||
{
|
||||
if ($value === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
public static function attr(mixed $value): string
|
||||
{
|
||||
return self::html($value);
|
||||
}
|
||||
|
||||
public static function url(string $value): string
|
||||
{
|
||||
return rawurlencode($value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BattleForge\Tests\Unit\Http;
|
||||
|
||||
use BattleForge\Http\Escape;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class EscapeTest extends TestCase
|
||||
{
|
||||
#[DataProvider('htmlProvider')]
|
||||
public function testItEscapesHtmlContext(mixed $input, string $expected): void
|
||||
{
|
||||
self::assertSame($expected, Escape::html($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string, array{mixed, string}>
|
||||
*/
|
||||
public static function htmlProvider(): iterable
|
||||
{
|
||||
yield 'plain' => ['hello', 'hello'];
|
||||
yield 'less-than' => ['<script>', '<script>'];
|
||||
yield 'ampersand' => ['a & b', 'a & b'];
|
||||
yield 'double-quote' => ['she said "hi"', 'she said "hi"'];
|
||||
yield 'single-quote' => ["it's", 'it's'];
|
||||
yield 'invalid-utf8-substituted' => ["a\xC0\x80b", "a\xEF\xBF\xBD\xEF\xBF\xBDb"];
|
||||
yield 'integer' => [42, '42'];
|
||||
yield 'null' => [null, ''];
|
||||
}
|
||||
|
||||
#[DataProvider('attrProvider')]
|
||||
public function testItEscapesAttributeContext(mixed $input, string $expected): void
|
||||
{
|
||||
self::assertSame($expected, Escape::attr($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string, array{mixed, string}>
|
||||
*/
|
||||
public static function attrProvider(): iterable
|
||||
{
|
||||
yield 'tag-breakout' => ['" onclick="', '" onclick="'];
|
||||
yield 'apostrophe' => ["' onclick='", '' onclick=''];
|
||||
}
|
||||
|
||||
#[DataProvider('urlProvider')]
|
||||
public function testItEncodesUrls(string $input, string $expected): void
|
||||
{
|
||||
self::assertSame($expected, Escape::url($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string, array{string, string}>
|
||||
*/
|
||||
public static function urlProvider(): iterable
|
||||
{
|
||||
yield 'space' => ['hello world', 'hello%20world'];
|
||||
yield 'slash' => ['a/b', 'a%2Fb'];
|
||||
yield 'unicode' => ['héllo', 'h%C3%A9llo'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user