Skip to content

Commit

Permalink
fix: user script extraction and execution (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
rafegoldberg authored Dec 17, 2020
1 parent cf39f9f commit 3eeeee9
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 59 deletions.
2 changes: 1 addition & 1 deletion __tests__/__snapshots__/magic-block-parser.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ Object {
<br/>
<a class=\\"button\\">Go</a>
</form>",
"scripts": undefined,
"runScripts": undefined,
},
},
"type": "html-block",
Expand Down
33 changes: 33 additions & 0 deletions __tests__/components/HTMLBlock.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const { mount } = require('enzyme');
const React = require('react');
const { renderToString } = require('react-dom/server');

const sanitize = require('../../sanitize.schema');
const HTMLBlock = require('../../components/HTMLBlock')(sanitize);

describe('HTML Block', () => {
global.window = true;
global.mockFn = jest.fn(() => console.log('custom script ran'));

it("doesn't run user scripts by default", () => {
mount(<HTMLBlock html="<script>mockFn()</script>" runScripts={false} />);
expect(global.mockFn).toHaveBeenCalledTimes(0);
});

it('runs user scripts in compat mode', () => {
mount(<HTMLBlock html="<script>mockFn()</script>" runScripts={true} />);
expect(global.mockFn).toHaveBeenCalledTimes(1);
});

it("doesn't run scripts on the server (even in compat mode)", () => {
const html = `
<h1>Hello World</h1>
<script>mockFn()</script>
`;
const elem = <HTMLBlock html={html} runScripts={true} />;
const ssr = renderToString(elem);
expect(elem.props.runScripts).toBe(true);
expect(ssr.indexOf('<script>')).toBeLessThan(0);
expect(ssr.indexOf('<h1>')).toBeGreaterThanOrEqual(0);
});
});
6 changes: 2 additions & 4 deletions __tests__/components.test.js → __tests__/components/test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* eslint-disable no-eval */
const { mount } = require('enzyme');
const React = require('react');
const markdown = require('../index');
const markdown = require('../../index');

const { silenceConsole } = require('./helpers');
const { silenceConsole } = require('../helpers');

describe('Data Replacements', () => {
it('Variables', () => {
Expand Down Expand Up @@ -247,8 +247,6 @@ ${JSON.stringify({

it('Should use h1 tags for magic heading blocks.', () => expect(wrap.find('h1')).toHaveLength(1));

it('Should execute scripts in magic custom HTML blocks.', () => expect(global.eval).toHaveBeenCalledTimes(2));

it('Should allow block-level RDMD compoonents in tables.', () => {
const table = wrap.find('table');
expect(table.find('.CodeTabs')).toHaveLength(1);
Expand Down
30 changes: 15 additions & 15 deletions components/HTMLBlock.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,41 @@
const React = require('react');
const PropTypes = require('prop-types');

/**
* @arg {string} html the HTML from which to extract script tags.
*/
const extractScripts = html => {
if (typeof window === 'undefined' || !html) return () => {};

const regex = /<script\b[^>]*>([\s\S]*?)<\/script>/gim;
const scripts = [...html.matchAll(regex)].map(m => m[1].trim());
const MATCH_SCRIPT_TAGS = /<script\b[^>]*>([\s\S]*?)<\/script>\n?/gim;

return () => scripts.map(js => window.eval(js));
const extractScripts = (html = '') => {
const scripts = [...html.matchAll(MATCH_SCRIPT_TAGS)].map(m => m[1]);
const cleaned = html.replace(MATCH_SCRIPT_TAGS, '');
return [cleaned, () => scripts.map(js => window.eval(js))];
};

class HTMLBlock extends React.Component {
constructor(props) {
super(props);
if ('scripts' in this.props) this.runScripts = extractScripts(this.props.html);
[this.html, this.exec] = extractScripts(this.props.html);
}

componentDidMount() {
if ('scripts' in this.props) this.runScripts();
const { runScripts } = this.props;
if (typeof window !== 'undefined' && (runScripts === '' || runScripts)) this.exec();
}

render() {
const { html: __html } = this.props;
return <div className="rdmd-html" dangerouslySetInnerHTML={{ __html }} />;
return <div className="rdmd-html" dangerouslySetInnerHTML={{ __html: this.html }} />;
}
}

HTMLBlock.defaultProps = {
runScripts: false,
};

HTMLBlock.propTypes = {
html: PropTypes.string,
scripts: PropTypes.any,
runScripts: PropTypes.any,
};

module.exports = sanitize => {
sanitize.tagNames.push('html-block');
sanitize.attributes['html-block'] = ['html', 'scripts'];
sanitize.attributes['html-block'] = ['html', 'runScripts'];
return HTMLBlock;
};
39 changes: 1 addition & 38 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const unified = require('unified');

/* Unified Plugins
*/
const sanitize = require('hast-util-sanitize/lib/github.json');
const sanitize = require('./sanitize.schema');

const generateTOC = require('mdast-util-toc');
const mapNodes = require('unist-util-map');
Expand Down Expand Up @@ -61,43 +61,6 @@ const toPlainText = require('./processor/plugin/plain-text');
// Processor Option Defaults
const options = require('./options.json');

// Sanitization Schema Defaults
sanitize.clobberPrefix = '';

sanitize.tagNames.push('span', 'style');
sanitize.attributes['*'].push('class', 'className', 'align', 'style');

/**
* @todo don't manually whitelist custom component attributes
* within the engine!
* @todo change `link` to `href`
*/
sanitize.attributes['tutorial-tile'] = ['backgroundColor', 'emoji', 'link'];

sanitize.tagNames.push('rdme-pin');

sanitize.tagNames.push('rdme-embed');
sanitize.attributes['rdme-embed'] = [
'url',
'provider',
'html',
'title',
'href',
'iframe',
'width',
'height',
'image',
'favicon',
];

sanitize.attributes.a = ['href', 'title', 'class', 'className', 'download'];

sanitize.tagNames.push('figure');
sanitize.tagNames.push('figcaption');

sanitize.tagNames.push('input'); // allow GitHub-style todo lists
sanitize.ancestors.input = ['li'];

/**
* Normalize Magic Block Raw Text
*/
Expand Down
2 changes: 1 addition & 1 deletion processor/parse/magic-block-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ function tokenize(eat, value) {
hName: 'html-block',
hProperties: {
html: json.html,
scripts: compatibilityMode,
runScripts: compatibilityMode,
},
},
},
Expand Down
40 changes: 40 additions & 0 deletions sanitize.schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const sanitize = require('hast-util-sanitize/lib/github.json');

// Sanitization Schema Defaults
sanitize.clobberPrefix = '';

sanitize.tagNames.push('span', 'style');
sanitize.attributes['*'].push('class', 'className', 'align', 'style');

/**
* @todo don't manually whitelist custom component attributes
* within the engine!
* @todo change `link` to `href`
*/
sanitize.attributes['tutorial-tile'] = ['backgroundColor', 'emoji', 'link'];

sanitize.tagNames.push('rdme-pin');

sanitize.tagNames.push('rdme-embed');
sanitize.attributes['rdme-embed'] = [
'url',
'provider',
'html',
'title',
'href',
'iframe',
'width',
'height',
'image',
'favicon',
];

sanitize.attributes.a = ['href', 'title', 'class', 'className', 'download'];

sanitize.tagNames.push('figure');
sanitize.tagNames.push('figcaption');

sanitize.tagNames.push('input'); // allow GitHub-style todo lists
sanitize.ancestors.input = ['li'];

module.exports = sanitize;

0 comments on commit 3eeeee9

Please sign in to comment.