Skip to content

Commit

Permalink
Rich text: fix format deregistration (#31518)
Browse files Browse the repository at this point in the history
  • Loading branch information
ellatrix authored and desrosj committed Jul 13, 2021
1 parent 8271874 commit dcaf012
Show file tree
Hide file tree
Showing 18 changed files with 381 additions and 48 deletions.
12 changes: 6 additions & 6 deletions packages/block-editor/src/components/block-compare/block-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
* WordPress dependencies
*/
import { Button } from '@wordpress/components';
import { RawHTML } from '@wordpress/element';
import { safeHTML } from '@wordpress/dom';

const BlockView = ( {
export default function BlockView( {
title,
rawContent,
renderedContent,
action,
actionText,
className,
} ) => {
} ) {
return (
<div className={ className }>
<div className="block-editor-block-compare__content">
Expand All @@ -23,7 +25,7 @@ const BlockView = ( {
</div>

<div className="block-editor-block-compare__preview edit-post-visual-editor">
{ renderedContent }
<RawHTML>{ safeHTML( renderedContent ) }</RawHTML>
</div>
</div>

Expand All @@ -34,6 +36,4 @@ const BlockView = ( {
</div>
</div>
);
};

export default BlockView;
}
25 changes: 6 additions & 19 deletions packages/block-editor/src/components/block-compare/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { diffChars } from 'diff/lib/diff/character';
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { getSaveContent, getSaveElement } from '@wordpress/blocks';
import { getSaveContent } from '@wordpress/blocks';

/**
* Internal dependencies
Expand Down Expand Up @@ -50,25 +50,12 @@ function BlockCompare( {
const newContent = newBlocks.map( ( item ) =>
getSaveContent( item.name, item.attributes, item.innerBlocks )
);
const renderedContent = newBlocks.map( ( item ) =>
getSaveElement( item.name, item.attributes, item.innerBlocks )
);

return {
rawContent: newContent.join( '' ),
renderedContent,
};
return newContent.join( '' );
}

const original = {
rawContent: block.originalContent,
renderedContent: getSaveElement( block.name, block.attributes ),
};
const converted = getConvertedContent( convertor( block ) );
const difference = getDifference(
original.rawContent,
converted.rawContent
);
const difference = getDifference( block.originalContent, converted );

return (
<div className="block-editor-block-compare__wrapper">
Expand All @@ -77,8 +64,8 @@ function BlockCompare( {
className="block-editor-block-compare__current"
action={ onKeep }
actionText={ __( 'Convert to HTML' ) }
rawContent={ original.rawContent }
renderedContent={ original.renderedContent }
rawContent={ block.originalContent }
renderedContent={ block.originalContent }
/>

<BlockView
Expand All @@ -87,7 +74,7 @@ function BlockCompare( {
action={ onConvert }
actionText={ convertButtonText }
rawContent={ difference }
renderedContent={ converted.renderedContent }
renderedContent={ converted }
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ exports[`BlockView should match snapshot 1`] = `
<div
className="block-editor-block-compare__preview edit-post-visual-editor"
>
render
<RawHTML>
render
</RawHTML>
</div>
</div>
<div
Expand Down
14 changes: 11 additions & 3 deletions packages/block-editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ import { omit } from 'lodash';
/**
* WordPress dependencies
*/
import { createContext, useMemo, useCallback } from '@wordpress/element';
import {
createContext,
useMemo,
useCallback,
RawHTML,
} from '@wordpress/element';
import {
getBlockType,
getSaveElement,
getSaveContent,
isUnmodifiedDefaultBlock,
hasBlockSupport,
} from '@wordpress/blocks';
import { withFilters } from '@wordpress/components';
import { withDispatch, withSelect, useDispatch } from '@wordpress/data';
import { compose, pure, ifCondition } from '@wordpress/compose';
import { safeHTML } from '@wordpress/dom';

/**
* Internal dependencies
Expand Down Expand Up @@ -134,10 +140,12 @@ function BlockListBlock( {
let block;

if ( ! isValid ) {
const saveContent = getSaveContent( blockType, attributes );

block = (
<Block className="has-warning">
<BlockInvalidWarning clientId={ clientId } />
<div>{ getSaveElement( blockType, attributes ) }</div>
<RawHTML>{ safeHTML( saveContent ) }</RawHTML>
</Block>
);
} else if ( mode === 'html' ) {
Expand Down
9 changes: 9 additions & 0 deletions packages/block-editor/src/components/rich-text/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,12 @@ figcaption.block-editor-rich-text__editable [data-rich-text-placeholder]::before
}
}
}

[data-rich-text-script] {
display: inline;

&::before {
content: "</>";
background: rgb(255, 255, 0);
}
}
21 changes: 13 additions & 8 deletions packages/block-library/src/buttons/transforms.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* WordPress dependencies
*/
import { createBlock } from '@wordpress/blocks';
import { __unstableCreateElement as createElement } from '@wordpress/rich-text';

/**
* Internal dependencies
Expand Down Expand Up @@ -37,12 +38,14 @@ const transforms = {
{},
// Loop the selected buttons
buttons.map( ( attributes ) => {
const element = createElement(
document,
attributes.content
);
// Remove any HTML tags
const div = document.createElement( 'div' );
div.innerHTML = attributes.content;
const text = div.innerText || '';
const text = element.innerText || '';
// Get first url
const link = div.querySelector( 'a' );
const link = element.querySelector( 'a' );
const url = link?.getAttribute( 'href' );
// Create singular button in the buttons block
return createBlock( 'core/button', {
Expand All @@ -53,10 +56,12 @@ const transforms = {
),
isMatch: ( paragraphs ) => {
return paragraphs.every( ( attributes ) => {
const div = document.createElement( 'div' );
div.innerHTML = attributes.content;
const text = div.innerText || '';
const links = div.querySelectorAll( 'a' );
const element = createElement(
document,
attributes.content
);
const text = element.innerText || '';
const links = element.querySelectorAll( 'a' );
return text.length <= 30 && links.length <= 1;
} );
},
Expand Down
3 changes: 2 additions & 1 deletion packages/block-library/src/missing/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
useBlockProps,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { safeHTML } from '@wordpress/dom';

function MissingBlockWarning( { attributes, convertToHTML } ) {
const { originalName, originalUndelimitedContent } = attributes;
Expand Down Expand Up @@ -45,7 +46,7 @@ function MissingBlockWarning( { attributes, convertToHTML } ) {
return (
<div { ...useBlockProps( { className: 'has-warning' } ) }>
<Warning actions={ actions }>{ messageHTML }</Warning>
<RawHTML>{ originalUndelimitedContent }</RawHTML>
<RawHTML>{ safeHTML( originalUndelimitedContent ) }</RawHTML>
</div>
);
}
Expand Down
12 changes: 12 additions & 0 deletions packages/dom/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,18 @@ _Returns_
- `Element`: The new node.
<a name="safeHTML" href="#safeHTML">#</a> **safeHTML**
Strips scripts and on\* attributes from HTML.
_Parameters_
- _html_ `string`: HTML to sanitize.
_Returns_
- `string`: The sanitized HTML.
<a name="unwrap" href="#unwrap">#</a> **unwrap**
Unwrap the given node. This means any child nodes are moved to the parent.
Expand Down
1 change: 1 addition & 0 deletions packages/dom/src/dom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export { default as __unstableStripHTML } from './strip-html';
export { default as isEmpty } from './is-empty';
export { default as removeInvalidHTML } from './remove-invalid-html';
export { default as isRTL } from './is-rtl';
export { default as safeHTML } from './safe-html';
38 changes: 38 additions & 0 deletions packages/dom/src/dom/safe-html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Internal dependencies
*/
import remove from './remove';

/**
* Strips scripts and on* attributes from HTML.
*
* @param {string} html HTML to sanitize.
*
* @return {string} The sanitized HTML.
*/
export default function safeHTML( html ) {
const { body } = document.implementation.createHTMLDocument( '' );
body.innerHTML = html;
const elements = body.getElementsByTagName( '*' );
let elementIndex = elements.length;

while ( elementIndex-- ) {
const element = elements[ elementIndex ];

if ( element.tagName === 'SCRIPT' ) {
remove( element );
} else {
let attributeIndex = element.attributes.length;

while ( attributeIndex-- ) {
const { name: key } = element.attributes[ attributeIndex ];

if ( key.startsWith( 'on' ) ) {
element.removeAttribute( key );
}
}
}
}

return body.innerHTML;
}
31 changes: 31 additions & 0 deletions packages/dom/src/dom/test/safe-html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Internal dependencies
*/
import safeHTML from '../safe-html';

describe( 'safeHTML', () => {
it( 'should strip on* attributes', () => {
const input = '<img src="" onerror="alert(\'1\')" onload="">';
const output = '<img src="">';
expect( safeHTML( input ) ).toBe( output );
} );

it( 'should strip on* attributes with spacing', () => {
const input = '<img src="" onerror = "alert(\'1\')" onload = "">';
const output = '<img src="">';
expect( safeHTML( input ) ).toBe( output );
} );

it( 'should strip nested on* attributes', () => {
const input =
'<p><strong><img src="" onerror="alert(\'1\')"></strong></p>';
const output = '<p><strong><img src=""></strong></p>';
expect( safeHTML( input ) ).toBe( output );
} );

it( 'should strip script tags', () => {
const input = '<script>alert("1")</script><script></script>';
const output = '';
expect( safeHTML( input ) ).toBe( output );
} );
} );
44 changes: 44 additions & 0 deletions packages/e2e-tests/specs/editor/blocks/missing.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* WordPress dependencies
*/
import { createNewPost, setPostContent } from '@wordpress/e2e-test-utils';

describe( 'missing block', () => {
beforeEach( async () => {
await createNewPost();
} );

it( 'should strip potentially malicious on* attributes', async () => {
let hasAlert = false;

page.on( 'dialog', () => {
hasAlert = true;
} );

await setPostContent(
`<!-- wp:non-existing-block-here --><img src onerror=alert(1)>`
);

// Give the browser time to show the alert.
await page.evaluate( () => new Promise( window.requestIdleCallback ) );

expect( hasAlert ).toBe( false );
} );

it( 'hould strip potentially malicious script tags', async () => {
let hasAlert = false;

page.on( 'dialog', () => {
hasAlert = true;
} );

await setPostContent(
`<!-- wp:non-existing-block-here --><script>alert("EVIL");</script>`
);

// Give the browser time to show the alert.
await page.evaluate( () => new Promise( window.requestIdleCallback ) );

expect( hasAlert ).toBe( false );
} );
} );
Loading

0 comments on commit dcaf012

Please sign in to comment.