Make WordPress Core

Changeset 60708

Timestamp:
09/04/2025 06:30:56 PM (6 weeks ago)
Author:
jonsurrell
Message:

Editor: Use Unicode escape encoding for "\" characters in block attributes.

Corrects an issue with block attribute encoding where JSON strings ending in the \ character would be misencoded and cause block attributes to be lost.

Client-side block serialization was updated with matching logic in https://github.com/WordPress/gutenberg/commit/10453ab3a4e665b8403a0cb466dba64689b4b491.

Developed in https://github.com/WordPress/wordpress-develop/pull/9558.

Props jonsurrell, westonruter, mamaduka, dmsnell, shailu25.
Fixes #63917.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/blocks.php

    r60704 r60708  
    16131613function serialize_block_attributes( $block_attributes ) {
    16141614    $encoded_attributes = wp_json_encode( $block_attributes, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
    1615     $encoded_attributes = preg_replace( '/--/', '\\u002d\\u002d', $encoded_attributes );
    1616     $encoded_attributes = preg_replace( '/</', '\\u003c', $encoded_attributes );
    1617     $encoded_attributes = preg_replace( '/>/', '\\u003e', $encoded_attributes );
    1618     $encoded_attributes = preg_replace( '/&/', '\\u0026', $encoded_attributes );
    1619     // Regex: /\\"/
    1620     $encoded_attributes = preg_replace( '/\\\\"/', '\\u0022', $encoded_attributes );
    1621 
    1622     return $encoded_attributes;
     1615
     1616    return strtr(
     1617        $encoded_attributes,
     1618        array(
     1619            '\\\\' => '\\u005c',
     1620            '--'   => '\\u002d\\u002d',
     1621            '<'    => '\\u003c',
     1622            '>'    => '\\u003e',
     1623            '&'    => '\\u0026',
     1624            '\\"'  => '\\u0022',
     1625        )
     1626    );
    16231627}
    16241628
  • trunk/tests/phpunit/tests/blocks/serialize.php

    r56970 r60708  
    1111 */
    1212class Tests_Blocks_Serialize extends WP_UnitTestCase {
     13    /**
     14     * Ensure there are no issues with special character encoding.
     15     *
     16     * @ticket 63917
     17     */
     18    public function test_attribute_encoding() {
     19        $block = array(
     20            'blockName'    => 'test',
     21            'attrs'        => array(
     22                'lt'         => '<',
     23                'gt'         => '>',
     24                'amp'        => '&',
     25                'bs'         => '\\',
     26                'quot'       => '"',
     27                'bs-bs-quot' => '\\\\"',
     28            ),
     29            'innerBlocks'  => array(),
     30            'innerHTML'    => '',
     31            'innerContent' => array(),
     32        );
     33
     34        $expected = '<!-- wp:test {"lt":"\\u003c","gt":"\\u003e","amp":"\\u0026","bs":"\\u005c","quot":"\\u0022","bs-bs-quot":"\\u005c\\u005c\\u0022"} /-->';
     35        $this->assertSame( $expected, serialize_block( $block ) );
     36    }
    1337
    1438    /**
     
    1640     *
    1741     * @param string $original Original block markup.
     42     *
     43     * @ticket 63917
    1844     */
    1945    public function test_serialize_identity_from_parsed( $original ) {
     
    2551    }
    2652
    27     public function data_serialize_identity_from_parsed() {
     53    public static function data_serialize_identity_from_parsed(): array {
    2854        return array(
    29             // Void block.
    30             array( '<!-- wp:void /-->' ),
    31 
    32             // Freeform content ($block_name = null).
    33             array( 'Example.' ),
    34 
    35             // Block with content.
    36             array( '<!-- wp:content -->Example.<!-- /wp:content -->' ),
    37 
    38             // Block with attributes.
    39             array( '<!-- wp:attributes {"key":"value"} /-->' ),
    40 
    41             // Block with inner blocks.
    42             array( "<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->" ),
    43 
    44             // Block with attribute values that may conflict with HTML comment.
    45             array( '<!-- wp:attributes {"key":"\\u002d\\u002d\\u003c\\u003e\\u0026\\u0022"} /-->' ),
    46 
    47             // Block with attribute values that should not be escaped.
    48             array( '<!-- wp:attributes {"key":"€1.00 / 3 for €2.00"} /-->' ),
     55            'Void block'                                  =>
     56                array( '<!-- wp:void /-->' ),
     57
     58            'Freeform content ($block_name = null)'       =>
     59                array( 'Example.' ),
     60
     61            'Block with content'                          =>
     62                array( '<!-- wp:content -->Example.<!-- /wp:content -->' ),
     63
     64            'Block with attributes'                       =>
     65                array( '<!-- wp:attributes {"key":"value"} /-->' ),
     66
     67            'Block with inner blocks'                     =>
     68                array( "<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->" ),
     69
     70            'Block with attribute values that may conflict with HTML comment' =>
     71                array( '<!-- wp:attributes {"key":"\\u002d\\u002d\\u003c\\u003e\\u0026\\u0022"} /-->' ),
     72
     73            'Block with attribute values that should not be escaped' =>
     74                array( '<!-- wp:attributes {"key":"€1.00 / 3 for €2.00"} /-->' ),
     75
     76            'Backslashes in attributes, Gutenberg #16508' =>
     77                array( '<!-- wp:attributes {"bs":"\\u005c","bsQuote":"\\u005c\\u0022","bsQuoteBs":"\\u005c\\u0022\\u005c"} /-->' ),
     78
     79            'Tricky backslashes'                          =>
     80                array( '<!-- wp:attributes {"bsbsQbsbsbsQ":"\\u005c\\u005c\\u0022\\u005c\\u005c\\u005c\\u005c\\u0022"} /-->' ),
     81        );
     82    }
     83
     84    /**
     85     * The serialization was adjusted to use unicode escapes sequences for escaped `\` and `"`
     86     * characters inside JSON strings.
     87     *
     88     * Ensure that the previous escape form can be parsed compatibly and serialized back to
     89     * the new form.
     90     *
     91     * @see https://github.com/WordPress/wordpress-develop/pull/9558
     92     * @see https://github.com/WordPress/gutenberg/pull/71291
     93     *
     94     * @ticket 63917
     95     *
     96     * @dataProvider data_serialize_compatible_forms
     97     *
     98     * @param string $before Previous serialization form.
     99     * @param string $after  New serialization form.
     100     */
     101    public function test_older_serialization_is_compatible( string $before, string $after ) {
     102        $this->assertNotSame( $before, $after, 'The same serialization should not be provided for before and after.' );
     103        $blocks = parse_blocks( $before );
     104        $actual = serialize_blocks( $blocks );
     105        $this->assertSame( $after, $actual );
     106    }
     107
     108    public static function data_serialize_compatible_forms(): array {
     109        return array(
     110            'Special characters' => array(
     111                '<!-- wp:attributes {"lt":"\\u003c","gt":"\\u003e","amp":"\\u0026","bs":"\\\\","quot":"\\u0022"} /-->',
     112                '<!-- wp:attributes {"lt":"\\u003c","gt":"\\u003e","amp":"\\u0026","bs":"\\u005c","quot":"\\u0022"} /-->',
     113            ),
     114
     115            'Backslashes'        => array(
     116                '<!-- wp:attributes {"bs":"\\\\","bsQuote":"\\\\\\u0022","bsQuoteBs":"\\\\\\u0022\\\\"} /-->',
     117                '<!-- wp:attributes {"bs":"\\u005c","bsQuote":"\\u005c\\u0022","bsQuoteBs":"\\u005c\\u0022\\u005c"} /-->',
     118            ),
    49119        );
    50120    }
Note: See TracChangeset for help on using the changeset viewer.