Skip to content

Commit 89ae2bd

Browse files
committed
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 WordPress/gutenberg@10453ab. Developed in #9558. Props jonsurrell, westonruter, mamaduka, dmsnell, shailu25. Fixes #63917. git-svn-id: https://develop.svn.wordpress.org/trunk@60708 602fd350-edb4-49c9-b593-d223f7449a82
1 parent dde9c80 commit 89ae2bd

File tree

2 files changed

+97
-23
lines changed

2 files changed

+97
-23
lines changed

src/wp-includes/blocks.php

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,14 +1612,18 @@ function make_after_block_visitor( $hooked_blocks, $context, $callback = 'insert
16121612
*/
16131613
function 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

16251629
/**

tests/phpunit/tests/blocks/serialize.php

Lines changed: 85 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,37 @@
1010
* @group blocks
1111
*/
1212
class 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
/**
1539
* @dataProvider data_serialize_identity_from_parsed
1640
*
1741
* @param string $original Original block markup.
42+
*
43+
* @ticket 63917
1844
*/
1945
public function test_serialize_identity_from_parsed( $original ) {
2046
$blocks = parse_blocks( $original );
@@ -24,28 +50,72 @@ public function test_serialize_identity_from_parsed( $original ) {
2450
$this->assertSame( $original, $actual );
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 /-->' ),
55+
'Void block' =>
56+
array( '<!-- wp:void /-->' ),
57+
58+
'Freeform content ($block_name = null)' =>
59+
array( 'Example.' ),
3160

32-
// Freeform content ($block_name = null).
33-
array( 'Example.' ),
61+
'Block with content' =>
62+
array( '<!-- wp:content -->Example.<!-- /wp:content -->' ),
3463

35-
// Block with content.
36-
array( '<!-- wp:content -->Example.<!-- /wp:content -->' ),
64+
'Block with attributes' =>
65+
array( '<!-- wp:attributes {"key":"value"} /-->' ),
3766

38-
// Block with attributes.
39-
array( '<!-- wp:attributes {"key":"value"} /-->' ),
67+
'Block with inner blocks' =>
68+
array( "<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->" ),
4069

41-
// Block with inner blocks.
42-
array( "<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->" ),
70+
'Block with attribute values that may conflict with HTML comment' =>
71+
array( '<!-- wp:attributes {"key":"\\u002d\\u002d\\u003c\\u003e\\u0026\\u0022"} /-->' ),
4372

44-
// Block with attribute values that may conflict with HTML comment.
45-
array( '<!-- wp:attributes {"key":"\\u002d\\u002d\\u003c\\u003e\\u0026\\u0022"} /-->' ),
73+
'Block with attribute values that should not be escaped' =>
74+
array( '<!-- wp:attributes {"key":"€1.00 / 3 for €2.00"} /-->' ),
4675

47-
// Block with attribute values that should not be escaped.
48-
array( '<!-- wp:attributes {"key":"€1.00 / 3 for €2.00"} /-->' ),
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
}
51121

0 commit comments

Comments
 (0)