-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Playground block: Base64 encode attributes to prevent KSES from break…
…ing the values on save (#258) Some WordPress installations are overly eager with their HTML entity encoding and will save, e.g., `<?php` as `<php`. We cannot easily detect this to decode these HTML entities only when needed, so let's just store the attributes using base64 encoding to prevent WordPress from breaking them. This PR encodes the most entity-encoding-susceptible attributes to base64 in the block editor. To avoid an expensive encode/decode operation on each key stroke, the base64 encode operation is debounced. ## Testing instructions * Install the trunk version of this block on a site * Insert it on a page with a code editor, a few files, and a Blueprint * Switch to this branch * Confirm the original block still works in the editor and on the frontend * Update any attribute, save it, refresh the page * Confirm it still works in the editor and on the frontend * Insert a new block, insert a few files in the code editor, update the Blueprint attribute, save it * Confirm it works in the editor and on the frontend
- Loading branch information
Showing
4 changed files
with
211 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/** | ||
* Base64 encoding and decoding functions. | ||
* We cannot just use `btoa` and `atob` because they do not | ||
* support Unicode characters. | ||
*/ | ||
|
||
export const attributesToBase64 = [ | ||
'blueprint', | ||
'blueprintUrl', | ||
'codeEditorErrorLog', | ||
'constants', | ||
'files', | ||
]; | ||
|
||
export function base64EncodeBlockAttributes( | ||
blockAttributes: Record<string, any> | ||
) { | ||
const base64Props: Record<string, string> = {}; | ||
for (const key in blockAttributes) { | ||
if ( | ||
!attributesToBase64.includes(key) || | ||
typeof blockAttributes[key] === 'number' || | ||
typeof blockAttributes[key] === 'boolean' || | ||
typeof blockAttributes[key] === null || | ||
typeof blockAttributes[key] === undefined | ||
) { | ||
base64Props[key] = blockAttributes[key]; | ||
continue; | ||
} | ||
base64Props[key] = stringToBase64( | ||
JSON.stringify(blockAttributes[key]) | ||
); | ||
} | ||
// The "files" attribute is of type array | ||
if ('files' in base64Props) { | ||
base64Props['files'] = [base64Props['files']] as any; | ||
} | ||
return base64Props; | ||
} | ||
|
||
/** | ||
* Turns base64 encoded attributes back into their original form. | ||
* It never throws, bales out early if we can't decode, and always | ||
* returns a valid object. If any attribute cannot be decoded, it | ||
* will be kept in its original form and presumed to have a non-base64 | ||
* value to keep the older version of the block working without | ||
* migrating the attributes. | ||
* | ||
* @param base64Attributes | ||
* @returns | ||
*/ | ||
export function base64DecodeBlockAttributes( | ||
base64Attributes: Record<string, any> | ||
) { | ||
const attributes: Record<string, any> = {}; | ||
for (const key in base64Attributes) { | ||
let valueToDecode = base64Attributes[key]; | ||
// The "files" attribute is of type array | ||
if (key === 'files') { | ||
valueToDecode = valueToDecode[0]; | ||
} | ||
if ( | ||
!attributesToBase64.includes(key) || | ||
!(typeof valueToDecode === 'string') | ||
) { | ||
attributes[key] = base64Attributes[key]; | ||
continue; | ||
} | ||
if (key in base64Attributes) { | ||
try { | ||
attributes[key] = JSON.parse(base64ToString(valueToDecode)); | ||
} catch (error) { | ||
// Ignore errors and keep the base64 encoded string. | ||
attributes[key] = base64Attributes[key]; | ||
} | ||
} | ||
} | ||
return attributes; | ||
} | ||
|
||
export function stringToBase64(string: string) { | ||
return uint8ArrayToBase64(new TextEncoder().encode(string)); | ||
} | ||
|
||
export function base64ToString(base64: string) { | ||
return new TextDecoder().decode(base64ToUint8Array(base64)); | ||
} | ||
|
||
export function uint8ArrayToBase64(bytes: Uint8Array) { | ||
const binary = []; | ||
const len = bytes.byteLength; | ||
for (let i = 0; i < len; i++) { | ||
binary.push(String.fromCharCode(bytes[i])); | ||
} | ||
return window.btoa(binary.join('')); | ||
} | ||
|
||
export function base64ToUint8Array(base64: string) { | ||
const binaryString = window.atob(base64); // This will convert base64 to binary string | ||
const len = binaryString.length; | ||
const bytes = new Uint8Array(len); | ||
for (let i = 0; i < len; i++) { | ||
bytes[i] = binaryString.charCodeAt(i); | ||
} | ||
return bytes; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters