Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rich text: fix format deregistration #31518

Merged
merged 13 commits into from
Jul 13, 2021
Merged
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