diff --git a/includes/functions-html.php b/includes/functions-html.php index 5717a3dc7..8a6de56f6 100644 --- a/includes/functions-html.php +++ b/includes/functions-html.php @@ -524,8 +524,19 @@ function yourls_die( string $message = '', string $title = '', int $header_code yourls_html_head(); yourls_html_logo(); } - echo yourls_esc_html( yourls_apply_filter( 'die_title', "

$title

" ) ); - echo yourls_esc_html( yourls_apply_filter( 'die_message', "

$message

" ) ); + + $title = yourls_apply_filter( 'die_title', "

$title

" ); + $message = yourls_apply_filter( 'die_message', "

$message

" ); + $allowed = [ + 'strong' => [], + 'em' => [], + 'tt' => [], + 'code' => [], + 'h2' => [], + 'br' => [], + 'p' => ['class' => true], + ]; + echo yourls_esc_html_with_whitelist( $title . $message, $allowed ); // Hook into 'yourls_die' to add more elements or messages to that page yourls_do_action( 'yourls_die' ); if( !yourls_did_action( 'html_footer' ) ) { diff --git a/includes/functions-kses.php b/includes/functions-kses.php index 7a81ffdb8..72dc8f2cb 100644 --- a/includes/functions-kses.php +++ b/includes/functions-kses.php @@ -603,6 +603,13 @@ function yourls_kses_named_entities($matches) { if ( empty($matches[1]) ) return ''; + // KSES globals are normally populated on the 'plugins_loaded' action. This can run + // earlier though (eg yourls_die() on a DB connection error, before plugins load), so + // make sure the allowed entity names are available. + if ( ! is_array($yourls_allowedentitynames) ) { + yourls_kses_init(); + } + $i = $matches[1]; return ( ( ! in_array($i, $yourls_allowedentitynames) ) ? "&$i;" : "&$i;" ); } diff --git a/includes/functions.php b/includes/functions.php index 15890ef8e..a53777540 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -1234,13 +1234,19 @@ function yourls_check_maintenance_mode() { * @since 1.6 * @see yourls_get_protocol() * - * @param string $url URL to be check + * @param string $url URL to be checked * @param array $protocols Optional. Array of protocols, defaults to global $yourls_allowedprotocols * @return bool true if protocol allowed, false otherwise */ -function yourls_is_allowed_protocol( $url, $protocols = [] ) { +function yourls_is_allowed_protocol(string $url, array $protocols = [] ): bool { if ( empty( $protocols ) ) { global $yourls_allowedprotocols; + // KSES globals are normally populated on the 'plugins_loaded' action. This can run + // earlier though (eg yourls_die() on a DB connection error, before plugins load), so + // make sure the allowed protocols are available. + if ( ! is_array( $yourls_allowedprotocols ) ) { + yourls_kses_init(); + } $protocols = $yourls_allowedprotocols; } diff --git a/includes/geo/GeoLite2-Country.mmdb b/includes/geo/GeoLite2-Country.mmdb index f4032bc09..657a63563 100644 Binary files a/includes/geo/GeoLite2-Country.mmdb and b/includes/geo/GeoLite2-Country.mmdb differ diff --git a/tests/tests/format/KSESTest.php b/tests/tests/format/KSESTest.php index 11ae86082..014c9e6b2 100644 --- a/tests/tests/format/KSESTest.php +++ b/tests/tests/format/KSESTest.php @@ -43,4 +43,36 @@ function test_sanitize_title() { $this->assertTrue( is_array( yourls_kses_allowed_tags_all() ) ); } + /** + * KSES globals are populated on 'plugins_loaded'. Make sure entity normalization + * (yourls_esc_html) does not blow up if it runs earlier, eg yourls_die() on a DB + * connection error before plugins are loaded + */ + function test_esc_html_works_before_kses_init() { + global $yourls_allowedentitynames; + // Simulate the not-yet-initialized state + $yourls_allowedentitynames = null; + + $escaped = yourls_esc_html( "Unknown database é x 'q'" ); + + // No TypeError, output is escaped, and the global got lazily populated + $this->assertIsString( $escaped ); + $this->assertStringContainsString( '<b>', $escaped ); + $this->assertIsArray( $yourls_allowedentitynames ); + } + + /** + * Same early-call safety for protocol checking, which reads $yourls_allowedprotocols. + */ + function test_is_allowed_protocol_works_before_kses_init() { + global $yourls_allowedprotocols; + // Simulate the not-yet-initialized state + $yourls_allowedprotocols = null; + + // No TypeError on in_array() against a null protocols list + $this->assertTrue( yourls_is_allowed_protocol( 'http://example.com' ) ); + $this->assertFalse( yourls_is_allowed_protocol( 'javascript:alert(1)' ) ); + $this->assertIsArray( $yourls_allowedprotocols ); + } + }