mirror of
https://github.com/Frumph/comic-easel.git
synced 2026-08-25 11:29:39 -04:00
87c3d09bb8
Passing ENT_QUOTES alone to htmlspecialchars() replaces PHP 8.1's default flag set rather than adding to it, dropping ENT_SUBSTITUTE -- so a single invalid UTF-8 byte made the call return an empty string and the whole field rendered blank. Pass ENT_SUBSTITUTE explicitly, and fall back to UTF-8 when blog_charset is empty or unrecognised, which produces the same empty result. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
96 lines
3.3 KiB
PHP
96 lines
3.3 KiB
PHP
<?php
|
|
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
|
|
/**
|
|
* ceo_escape_stored_text() — functions/library.php
|
|
*
|
|
* The shared escaper for comic meta rendered as HTML text. Two things about it are easy to
|
|
* get wrong and neither is visible on inspection, which is why they are pinned here.
|
|
*/
|
|
class EscapeStoredTextTest extends CE_TestCase {
|
|
|
|
protected function setUp(): void {
|
|
parent::setUp();
|
|
self::loadPluginFile( 'functions/library.php' );
|
|
}
|
|
|
|
/**
|
|
* A single invalid UTF-8 byte must not blank the value.
|
|
*
|
|
* From PHP 8.1 the default flag set for htmlspecialchars() is
|
|
* ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401. Passing ENT_QUOTES alone replaces that
|
|
* default instead of adding to it, dropping ENT_SUBSTITUTE — and without it a malformed
|
|
* byte makes htmlspecialchars() return an empty string. A transcript with one stray byte
|
|
* would render as nothing at all.
|
|
*/
|
|
public function testInvalidUtf8DoesNotBlankTheValue() {
|
|
$out = ceo_escape_stored_text( "Panel one\x80 and two" );
|
|
$this->assertNotSame( '', $out );
|
|
$this->assertStringContainsString( 'Panel one', $out );
|
|
$this->assertStringContainsString( 'and two', $out );
|
|
}
|
|
|
|
/** An empty or unrecognised blog_charset must not blank the value either. */
|
|
#[DataProvider( 'badCharsetProvider' )]
|
|
public function testUnusableBlogCharsetDoesNotBlankTheValue( $charset ) {
|
|
CE_Test_State::$options['blog_charset'] = $charset;
|
|
$this->assertStringContainsString( 'Panel one', ceo_escape_stored_text( 'Panel one' ) );
|
|
}
|
|
|
|
public static function badCharsetProvider() {
|
|
return array(
|
|
'empty' => array( '' ),
|
|
'false' => array( false ),
|
|
'null' => array( null ),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Rendering must be stable across saves: a value stored the way the meta boxes store it
|
|
* must come back as exactly those bytes. See the header of functions/library.php for why
|
|
* esc_html() cannot be used here.
|
|
*/
|
|
#[DataProvider( 'authoredTextProvider' )]
|
|
public function testRenderingIsStableAcrossSaves( $typed ) {
|
|
$stored = esc_textarea( $typed );
|
|
$this->assertSame( $stored, ceo_escape_stored_text( $stored ) );
|
|
}
|
|
|
|
public static function authoredTextProvider() {
|
|
return array(
|
|
'markup shown as text' => array( '<b>bold</b>' ),
|
|
'an entity typed literally' => array( '<b>' ),
|
|
'a bare ampersand' => array( 'Tom & Jerry' ),
|
|
'an ampersand already escaped' => array( 'Tom & Jerry' ),
|
|
'quotes' => array( 'she said "hi"' ),
|
|
'an apostrophe' => array( "it's fine" ),
|
|
);
|
|
}
|
|
|
|
/** Raw markup written past the meta boxes must come out inert. */
|
|
public function testRawMarkupIsNeutralised() {
|
|
$out = ceo_escape_stored_text( '<script>alert(1)</script>' );
|
|
$this->assertStringNotContainsString( '<script', $out );
|
|
$this->assertSame( '<script>alert(1)</script>', $out );
|
|
}
|
|
|
|
public function testAttributeBreakingCharactersAreEscaped() {
|
|
$this->assertSame( '"', ceo_escape_stored_text( '"' ) );
|
|
$this->assertSame( ''', ceo_escape_stored_text( "'" ) );
|
|
}
|
|
|
|
#[DataProvider( 'nonStringProvider' )]
|
|
public function testNonStringInputIsHandled( $input, $expected ) {
|
|
$this->assertSame( $expected, ceo_escape_stored_text( $input ) );
|
|
}
|
|
|
|
public static function nonStringProvider() {
|
|
return array(
|
|
array( null, '' ),
|
|
array( false, '' ),
|
|
array( 123, '123' ),
|
|
);
|
|
}
|
|
}
|