Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions packages/blocks/src/api/serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,19 +281,21 @@ export function getCommentAttributes( blockType, attributes ) {
export function serializeAttributes( attributes ) {
return (
JSON.stringify( attributes )
// Replace escaped `\` characters with the unicode escape sequence.
.replaceAll( '\\\\', '\\u005c' )
Comment on lines +284 to +285
Copy link
Member Author

@sirreal sirreal Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmsnell I'd love to get your opinion on this (and WordPress/wordpress-develop#9558). Initially, I was worried about block invalidation, but if the block was serialized (validly, in a way that can be deserialized) I think there's actually some flexibility because the attribute values are compared not their string representation.

Initially, I maintained the same escaping only addressing the problem of incorrect replacement of syntactic JSON " string delimiter. That approach yielded a very complex regular expression with negative lookbehind and placeholder replacement:

* - "\"" -> "\u0022"
* - "\\" -> "\\"
* - "\\\"" -> "\\\u0022"
*
* See https://developer.wordpress.org/reference/functions/wp_kses_stripslashes/
*/
.replace( /(?<!\\)(\\\\)*\\"/g, '$1\\u0022' )

Instead, I decided to replace escaped backslashes first (\\ to \u005c) and later replace escaped doubled quotes (\" to \u0022). This can be achieved with simple string replacements and no regular expressions.

A simpler approach is possible in PHP to fix the \" replacement by using JSON_HEX_QUOT flag when encoding. However, the updated approach to replace escaped backslashes is easy to match. See WordPress/wordpress-develop#9558.

This is from a long time ago, but feedback is welcome if you have any @aduth (via #6619).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will give some thought to it, but it may not come before next week. I have a lot of catch-up from WCUS

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the block was serialized (validly, in a way that can be deserialized) I think there's actually some flexibility because the attribute values are compared not their string representation.

This should be accurate. There is even more flexibility than that too, because some block attributes are normalized before comparison for validation (e.g. CSS class names, extra HTML attributes, whitespace).


// Don't break HTML comments.
.replace( /--/g, '\\u002d\\u002d' )
.replaceAll( '--', '\\u002d\\u002d' )

// Don't break non-standard-compliant tools.
.replace( /</g, '\\u003c' )
.replace( />/g, '\\u003e' )
.replace( /&/g, '\\u0026' )

// Bypass server stripslashes behavior which would unescape stringify's
// escaping of quotation mark.
//
// See: https://developer.wordpress.org/reference/functions/wp_kses_stripslashes/
.replace( /\\"/g, '\\u0022' )
.replaceAll( '<', '\\u003c' )
.replaceAll( '>', '\\u003e' )
.replaceAll( '&', '\\u0026' )

// Replace escaped quotes (`\"`) to prevent problems with wp_kses_stripsplashes.
// This simple replacement is safe because `\\` has already been replaced.
// `\"` is not a JSON string quote like `"\\"`.
.replaceAll( '\\"', '\\u0022' )
);
}

Expand Down
12 changes: 12 additions & 0 deletions packages/blocks/src/api/test/serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,18 @@ describe( 'block serializer', () => {
'{"a":"\\u0022 and \\u0022"}'
);
} );

it( 'should handle backslash and quote combinations', () => {
const orig = {
bs: '\\',
bsQuote: '\\"',
bsQuoteBs: '\\"\\',
};
expect( JSON.parse( serializeAttributes( orig ) ) ).toEqual( orig );
expect( serializeAttributes( orig ) ).toBe(
'{"bs":"\\u005c","bsQuote":"\\u005c\\u0022","bsQuoteBs":"\\u005c\\u0022\\u005c"}'
);
} );
} );

describe( 'getCommentDelimitedContent()', () => {
Expand Down
Loading