diff --git a/addon/components/render-mobiledoc.js b/addon/components/render-mobiledoc.js index 4ce62cc..50dd1fc 100644 --- a/addon/components/render-mobiledoc.js +++ b/addon/components/render-mobiledoc.js @@ -4,6 +4,7 @@ import { RENDER_TYPE } from 'ember-mobiledoc-dom-renderer'; import layout from '../templates/components/render-mobiledoc'; import { getDocument } from '../utils/document'; import assign from '../utils/polyfilled-assign'; +import createMarkupSanitizer from '../utils/create-markup-sanitizer'; const { assert, @@ -122,6 +123,7 @@ export default Ember.Component.extend({ let passedOptions = this.get('cardOptions'); let cardOptions = this.get('_cardOptions'); options.cardOptions = passedOptions ? assign(passedOptions, cardOptions) : cardOptions; + options.markupSanitizer = createMarkupSanitizer(this); let renderer = new Renderer(options); let { result, teardown } = renderer.render(mobiledoc); diff --git a/addon/utils/create-markup-sanitizer.js b/addon/utils/create-markup-sanitizer.js new file mode 100644 index 0000000..177f86e --- /dev/null +++ b/addon/utils/create-markup-sanitizer.js @@ -0,0 +1,24 @@ +import getProtocolForURLfn from './get-protocol-for-url'; + +export default function createMarkupSanitizer(component) { + let protocolForURL = getProtocolForURLfn(component); + let badProtocols = [ + 'vbscript:', // jshint ignore:line + 'javascript:' // jshint ignore:line + ]; + + let sanitizeHref = (href) => { + let protocol = protocolForURL(href); + if (protocol && badProtocols.indexOf(protocol) !== -1) { + return `unsafe:${href}`; + } else { + return href; + } + }; + + return ({tagName, attributeName, attributeValue}) => { + if (tagName === 'a' && attributeName === 'href') { + return sanitizeHref(attributeValue); + } + }; +} diff --git a/addon/utils/get-protocol-for-url.js b/addon/utils/get-protocol-for-url.js new file mode 100644 index 0000000..ed1e4bb --- /dev/null +++ b/addon/utils/get-protocol-for-url.js @@ -0,0 +1,16 @@ +import Ember from 'ember'; + +export default function getProtocolForURLfn(component) { + let glimmerEnv = Ember.getOwner(component).lookup('service:-glimmer-environment'); + if (glimmerEnv && glimmerEnv.protocolForURL) { + return glimmerEnv.protocolForURL; + } else { + return (str) => { + let colonIdx = str.indexOf(':'); + if (colonIdx === -1) { return; } + + return str.replace(/^\s+/,'').split(':')[0] + ':'; + }; + } +} + diff --git a/package.json b/package.json index 45af9a5..44c08eb 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "ember-cli-htmlbars": "^1.0.1", "ember-getowner-polyfill": "^1.2.2", "ember-wormhole": "^0.5.1", - "mobiledoc-dom-renderer": "^0.6.3" + "mobiledoc-dom-renderer": "^0.6.4-0" }, "ember-addon": { "configPath": "tests/dummy/config" diff --git a/tests/helpers/mobiledoc.js b/tests/helpers/mobiledoc.js index 0fb8716..2de99bd 100644 --- a/tests/helpers/mobiledoc.js +++ b/tests/helpers/mobiledoc.js @@ -14,12 +14,10 @@ export function createSimpleMobiledoc(text) { }; } -export function createMobiledocWithStrongMarkup(text) { +export function createMobiledocWithMarkup({markup, text}) { return { version: MOBILEDOC_VERSION, - markups: [ - ['STRONG'] - ], + markups: [ markup ], atoms: [], cards: [], sections: [ diff --git a/tests/integration/components/render-mobiledoc-test.js b/tests/integration/components/render-mobiledoc-test.js index 78f7dd5..5a55a7b 100644 --- a/tests/integration/components/render-mobiledoc-test.js +++ b/tests/integration/components/render-mobiledoc-test.js @@ -4,7 +4,7 @@ import { CARD_ELEMENT_CLASS, ATOM_ELEMENT_CLASS } from 'ember-mobiledoc-dom-rend import Ember from 'ember'; import { createSimpleMobiledoc, - createMobiledocWithStrongMarkup, + createMobiledocWithMarkup, createMobiledocWithCard, createMobiledocWithAtom } from '../../helpers/mobiledoc'; @@ -341,7 +341,7 @@ test('Can pass markupElementRenderer', function(assert) { this.set('markupElementRenderer', { strong(_, doc) { return doc.createElement('span'); } }); - this.set('mobiledoc', createMobiledocWithStrongMarkup('Hi')); + this.set('mobiledoc', createMobiledocWithMarkup({text: 'Hi', markup: ['strong']})); this.render(hbs`{{render-mobiledoc mobiledoc=mobiledoc markupElementRenderer=markupElementRenderer}}`); @@ -383,3 +383,13 @@ test('Can pass cardOptions and they appear for atoms', function(assert) { this.render(hbs`{{render-mobiledoc mobiledoc=mobiledoc atomNames=atomNames cardOptions=cardOptions}}`); }); + +test('A HREF values are sanitized', function(assert) { + let badHref = 'javascript:evil'; // jshint ignore:line + this.set('mobiledoc', createMobiledocWithMarkup({text: 'hi', markup: ['a', ['href', badHref]]})); + this.render(hbs`{{render-mobiledoc mobiledoc=mobiledoc}}`); + + let a = this.$('a'); + assert.ok(a.length, 'has a'); + assert.equal(a.attr('href'), 'unsafe:javascript:evil'); +});