diff --git a/src/editor/config/config.js b/src/editor/config/config.js index af1d5cc59..71c2fc1bc 100644 --- a/src/editor/config/config.js +++ b/src/editor/config/config.js @@ -83,7 +83,8 @@ export default { showToolbar: 1, // Allow script tag importing - allowScripts: 0, + // @deprecated in favor of `config.parser.optionsHtml.allowScripts` + // allowScripts: 0, // If true render a select of available devices showDevices: 1, diff --git a/src/parser/config/config.js b/src/parser/config/config.js index aa11e24fa..c35ce5ac0 100644 --- a/src/parser/config/config.js +++ b/src/parser/config/config.js @@ -14,8 +14,17 @@ export default { // Here the result will be XMLDocument, which extends Node parserHtml: null, - // DOMParser mime type (default 'text/html') - // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString - // If you use the `text/html` parser, it will fix the invalid syntax automatically - htmlType: null + // Default HTML parser options (used in `parserModule.parseHtml(' tags + allowScripts: false, + + // Allow unsafe HTML attributes (eg. `on*` inline event handlers) + allowUnsafeAttr: false, + }, }; diff --git a/src/parser/model/ParserHtml.js b/src/parser/model/ParserHtml.js index b04c12166..db2131ddd 100644 --- a/src/parser/model/ParserHtml.js +++ b/src/parser/model/ParserHtml.js @@ -1,4 +1,4 @@ -import { each, isString, isFunction } from 'underscore'; +import { each, isString, isFunction, isUndefined, forEach } from 'underscore'; import BrowserParserHtml from './BrowserParserHtml'; export default config => { @@ -35,8 +35,7 @@ export default config => { // so put it under try/catch and let fail silently try { value = - (firstChar == '{' && lastChar == '}') || - (firstChar == '[' && lastChar == ']') + (firstChar == '{' && lastChar == '}') || (firstChar == '[' && lastChar == ']') ? JSON.parse(value) : value; } catch (e) {} @@ -49,7 +48,7 @@ export default config => { return { props, - attrs + attrs, }; }, @@ -69,10 +68,7 @@ export default config => { var decl = decls[i].trim(); if (!decl) continue; var prop = decl.split(':'); - result[prop[0].trim()] = prop - .slice(1) - .join(':') - .trim(); + result[prop[0].trim()] = prop.slice(1).join(':').trim(); } return result; }, @@ -118,8 +114,7 @@ export default config => { // Start with understanding what kind of component it is if (ct) { let obj = ''; - let type = - node.getAttribute && node.getAttribute(`${modelAttrStart}type`); + let type = node.getAttribute && node.getAttribute(`${modelAttrStart}type`); // If the type is already defined, use it if (type) { @@ -177,8 +172,7 @@ export default config => { // so put it under try/catch and let fail silently try { nodeValue = - (firstChar == '{' && lastChar == '}') || - (firstChar == '[' && lastChar == ']') + (firstChar == '{' && lastChar == '}') || (firstChar == '[' && lastChar == ']') ? JSON.parse(nodeValue) : nodeValue; } catch (e) {} @@ -205,12 +199,12 @@ export default config => { !model.type && (model.type = 'text'); model.components = { type: 'textnode', - content: firstChild.nodeValue + content: firstChild.nodeValue, }; } else { model.components = this.parseNode(node, { ...opts, - inSvg: opts.inSvg || model.type === 'svg' + inSvg: opts.inSvg || model.type === 'svg', }); } } @@ -247,10 +241,7 @@ export default config => { const comp = comps[ci]; const cType = comp.type; - if ( - ['text', 'textnode'].indexOf(cType) < 0 && - c.textTags.indexOf(comp.tagName) < 0 - ) { + if (['text', 'textnode'].indexOf(cType) < 0 && c.textTags.indexOf(comp.tagName) < 0) { allTxt = 0; break; } @@ -287,17 +278,29 @@ export default config => { const conf = (em && em.get('Config')) || {}; const res = { html: null, css: null }; const cf = { ...config, ...opts }; - const el = isFunction(cf.parserHtml) - ? cf.parserHtml(str, cf) - : BrowserParserHtml(str, cf); + const options = { + ...config.optionsHtml, + // Support previous `configParser.htmlType` option + htmlType: config.optionsHtml.htmlType || config.htmlType, + ...opts, + }; + const el = isFunction(cf.parserHtml) ? cf.parserHtml(str, options) : BrowserParserHtml(str, options); const scripts = el.querySelectorAll('script'); let i = scripts.length; - // Remove all scripts - if (!conf.allowScripts) { + // Support previous `configMain.allowScripts` option + const allowScripts = !isUndefined(conf.allowScripts) ? conf.allowScripts : options.allowScripts; + + // Remove script tags + if (!allowScripts) { while (i--) scripts[i].parentNode.removeChild(scripts[i]); } + // Remove unsafe attributes + if (!options.allowUnsafeAttr) { + this.__clearUnsafeAttr(el); + } + // Detach style tags and parse them if (parserCss) { const styles = el.querySelectorAll('style'); @@ -315,12 +318,21 @@ export default config => { em && em.trigger(`${event}:root`, { input: str, root: el }); const result = this.parseNode(el); // I have to keep it otherwise it breaks the DomComponents.addComponent (returns always array) - const resHtml = - result.length === 1 && !c.returnArray ? result[0] : result; + const resHtml = result.length === 1 && !c.returnArray ? result[0] : result; res.html = resHtml; em && em.trigger(event, { input: str, output: res }); return res; - } + }, + + __clearUnsafeAttr(node) { + const attrs = node.attributes || []; + const nodes = node.childNodes || []; + forEach(attrs, attr => { + const name = attr.nodeName || ''; + name.indexOf('on') === 0 && node.removeAttribute(name); + }); + forEach(nodes, node => this.__clearUnsafeAttr(node)); + }, }; };