diff --git a/app/javascript/elements/html_editor.js b/app/javascript/elements/html_editor.js index 8c9a2d2c..52c1c4c6 100644 --- a/app/javascript/elements/html_editor.js +++ b/app/javascript/elements/html_editor.js @@ -106,11 +106,29 @@ img.ProseMirror-separator { } `) +const DROP_ATTRS = [ + 'srcdoc', 'xlink:href', 'srcset', 'action', 'formaction', 'poster', + 'background', 'data', 'cite', 'ping', 'longdesc', 'manifest', 'profile' +] + +const SAFE_URL_REGEXP = /^(?:https?:\/\/|data:image\/|blob:|mailto:|tel:|\{)/i + +function isSafeAttr (name, value) { + const lowerName = name.toLowerCase() + + if (lowerName.startsWith('on') || DROP_ATTRS.includes(lowerName)) return false + if ((lowerName === 'href' || lowerName === 'src') && !SAFE_URL_REGEXP.test(value.trim())) return false + + return true +} + function collectDomAttrs (dom) { const attrs = {} for (let i = 0; i < dom.attributes.length; i++) { - attrs[dom.attributes[i].name] = dom.attributes[i].value + const { name, value } = dom.attributes[i] + + if (isSafeAttr(name, value)) attrs[name] = value } return { htmlAttrs: attrs } diff --git a/app/javascript/template_builder/dynamic_editor.js b/app/javascript/template_builder/dynamic_editor.js index d83062a4..580d47ad 100644 --- a/app/javascript/template_builder/dynamic_editor.js +++ b/app/javascript/template_builder/dynamic_editor.js @@ -98,11 +98,29 @@ dynamic-variable { overflow-wrap: anywhere; }`) +const DROP_ATTRS = [ + 'srcdoc', 'xlink:href', 'srcset', 'action', 'formaction', 'poster', + 'background', 'data', 'cite', 'ping', 'longdesc', 'manifest', 'profile' +] + +const SAFE_URL_REGEXP = /^(?:https?:\/\/|data:image\/|blob:|mailto:|tel:|\{)/i + +function isSafeAttr (name, value) { + const lowerName = name.toLowerCase() + + if (lowerName.startsWith('on') || DROP_ATTRS.includes(lowerName)) return false + if ((lowerName === 'href' || lowerName === 'src') && !SAFE_URL_REGEXP.test(value.trim())) return false + + return true +} + function collectDomAttrs (dom) { const attrs = {} for (let i = 0; i < dom.attributes.length; i++) { - attrs[dom.attributes[i].name] = dom.attributes[i].value + const { name, value } = dom.attributes[i] + + if (isSafeAttr(name, value)) attrs[name] = value } return { htmlAttrs: attrs } diff --git a/app/javascript/template_builder/dynamic_section.vue b/app/javascript/template_builder/dynamic_section.vue index 9fd81246..852a7a56 100644 --- a/app/javascript/template_builder/dynamic_section.vue +++ b/app/javascript/template_builder/dynamic_section.vue @@ -646,9 +646,7 @@ export default { return } - const container = document.createElement('div') - - container.innerHTML = clipboardHtml + const container = new DOMParser().parseFromString(clipboardHtml, 'text/html').body const fieldNodes = [...container.querySelectorAll('dynamic-field[data-field][data-area]')]