Skip to content

Commit 29be168

Browse files
authored
fix(url sanitization): Pass an A markupElementRenderer when necessary (in Fastboot) (#42)
* Pass an A markupElementRenderer when necessary (in Fastboot) Fixes #38 * run fastboot tests on travis
1 parent 908485f commit 29be168

File tree

12 files changed

+298
-3
lines changed

12 files changed

+298
-3
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ env:
1717
- EMBER_TRY_SCENARIO=ember-release
1818
- EMBER_TRY_SCENARIO=ember-beta
1919
- EMBER_TRY_SCENARIO=ember-canary
20+
- EMBER_TRY_SCENARIO=fastboot-addon-tests
2021

2122
matrix:
2223
fast_finish: true

addon/components/render-mobiledoc.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { RENDER_TYPE } from 'ember-mobiledoc-dom-renderer';
44
import layout from '../templates/components/render-mobiledoc';
55
import { getDocument } from '../utils/document';
66
import assign from '../utils/polyfilled-assign';
7+
import createMarkupElementRenderer from '../utils/create-markup-element-renderer';
78

89
const {
910
assert,
@@ -123,6 +124,11 @@ export default Ember.Component.extend({
123124
let cardOptions = this.get('_cardOptions');
124125
options.cardOptions = passedOptions ? assign(passedOptions, cardOptions) : cardOptions;
125126

127+
options.markupElementRenderer = assign(
128+
createMarkupElementRenderer(this),
129+
options.markupElementRenderer
130+
);
131+
126132
let renderer = new Renderer(options);
127133
let { result, teardown } = renderer.render(mobiledoc);
128134

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/* global module */
2+
import Ember from 'ember';
3+
4+
const isNode = typeof window === 'undefined' && (typeof module === 'object' && typeof module.require === 'function');
5+
const hasDOM = typeof document === 'object';
6+
const needsMarkupElementRenderer = !isNode && !hasDOM;
7+
8+
function fallbackProtocolParser(str) {
9+
let colonIdx = str.indexOf(':');
10+
if (colonIdx === -1) { return; }
11+
12+
return str.replace(/^\s+/,'').split(':')[0] + ':';
13+
}
14+
15+
function getProtocolForURLfn(component) {
16+
let glimmerEnv = Ember.getOwner(component).lookup('service:-glimmer-environment');
17+
if (glimmerEnv && glimmerEnv.protocolForURL) {
18+
return glimmerEnv.protocolForURL;
19+
} else {
20+
return fallbackProtocolParser;
21+
}
22+
}
23+
24+
function createHrefSanitizer(component) {
25+
let protocolForUrl = getProtocolForURLfn(component);
26+
const badProtocols = [
27+
'vbscript:', // jshint ignore:line
28+
'javascript:' // jshint ignore:line
29+
];
30+
31+
return (href) => {
32+
let protocol = protocolForUrl(href);
33+
if (protocol && badProtocols.indexOf(protocol) !== -1) {
34+
return `unsafe:${href}`;
35+
} else {
36+
return href;
37+
}
38+
};
39+
}
40+
41+
export default function createMarkupElementRenderer(component) {
42+
if (!needsMarkupElementRenderer) {
43+
return {};
44+
} else {
45+
let sanitizeHref = createHrefSanitizer(component);
46+
47+
return {
48+
A(tagName, dom, attrs) {
49+
let el = dom.createElement(tagName);
50+
Object.keys(attrs).forEach(attrName => {
51+
let attrValue = attrs[attrName];
52+
53+
if (attrName === 'href') {
54+
attrValue = sanitizeHref(attrValue);
55+
}
56+
57+
el.setAttribute(attrName, attrValue);
58+
});
59+
60+
return el;
61+
}
62+
};
63+
}
64+
}

addon/utils/polyfilled-assign.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,3 @@ const { merge, assign } = Ember;
55
let polyfilledAssign = assign || merge;
66

77
export default polyfilledAssign;
8-

config/ember-try.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ module.exports = {
9393
'ember-source': null
9494
}
9595
}
96+
},
97+
{
98+
name: 'fastboot-addon-tests',
99+
command: 'ember fastboot:test',
100+
bower: {
101+
dependencies: {}
102+
}
96103
}
97104
]
98105
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Ember from 'ember';
2+
import {
3+
createSimpleMobiledoc,
4+
createMobiledocWithMarkup
5+
} from '../utils/mobiledoc';
6+
7+
const mobiledocs = [
8+
{
9+
name: 'simple',
10+
mobiledoc: createSimpleMobiledoc('hello world')
11+
},
12+
{
13+
name: 'with-markup',
14+
mobiledoc: createMobiledocWithMarkup('markup text', ['em'])
15+
},
16+
{
17+
name: 'with-link',
18+
mobiledoc: createMobiledocWithMarkup('linked', ['a', ['href', 'http://example.com/with-link']])
19+
},
20+
{
21+
name: 'with-unsafe-link',
22+
mobiledoc: createMobiledocWithMarkup('linked unsafe', ['a', ['href', 'javascript:evil']])
23+
}
24+
];
25+
26+
export default Ember.Controller.extend({
27+
mobiledocs
28+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import Ember from 'ember';
2+
import config from './config/environment';
3+
4+
const Router = Ember.Router.extend({
5+
location: config.locationType,
6+
rootURL: config.rootURL
7+
});
8+
9+
Router.map(function() {
10+
});
11+
12+
export default Router;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{outlet}}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<h1>ember-fastboot-addon-tests</h1>
2+
3+
<div id="mobiledocs">
4+
{{#each mobiledocs as |mobiledocInfo|}}
5+
<div class="render-mobiledoc-wrapper {{mobiledocInfo.name}}">
6+
{{render-mobiledoc mobiledoc=mobiledocInfo.mobiledoc}}
7+
</div>
8+
{{/each}}
9+
</div>
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
const MOBILEDOC_VERSION = '0.3.1';
2+
3+
export function createSimpleMobiledoc(text) {
4+
return {
5+
version: MOBILEDOC_VERSION,
6+
markups: [],
7+
atoms: [],
8+
cards: [],
9+
sections: [
10+
[1, 'P', [
11+
[0, [], 0, text]
12+
]]
13+
]
14+
};
15+
}
16+
17+
export function createMobiledocWithStrongMarkup(text) {
18+
return {
19+
version: MOBILEDOC_VERSION,
20+
markups: [
21+
['STRONG']
22+
],
23+
atoms: [],
24+
cards: [],
25+
sections: [
26+
[1, 'P', [
27+
[0, [0], 1, text]
28+
]]
29+
]
30+
};
31+
}
32+
33+
export function createMobiledocWithMarkup(text, markup) {
34+
return {
35+
version: MOBILEDOC_VERSION,
36+
markups: [
37+
markup
38+
],
39+
atoms: [],
40+
cards: [],
41+
sections: [
42+
[1, 'P', [
43+
[0, [0], 1, text]
44+
]]
45+
]
46+
};
47+
}
48+
49+
export function createMobiledocWithAtom(atomName) {
50+
return {
51+
version: MOBILEDOC_VERSION,
52+
markups: [],
53+
atoms: [
54+
[atomName, 'value', {foo: 'bar'}]
55+
],
56+
cards: [],
57+
sections: [
58+
[1, 'P', [
59+
[1, [], 0, 0]
60+
]]
61+
]
62+
};
63+
}
64+
65+
export function createMobiledocWithCard(cardName) {
66+
return {
67+
version: MOBILEDOC_VERSION,
68+
markups: [],
69+
atoms: [],
70+
cards: [
71+
[cardName, {foo: 'bar'}]
72+
],
73+
sections: [
74+
[10, 0]
75+
]
76+
};
77+
}
78+
79+

fastboot-tests/index-test.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'use strict';
2+
3+
const expect = require('chai').expect;
4+
const setupTest = require('ember-fastboot-addon-tests').setupTest;
5+
6+
describe('index', function() {
7+
setupTest('fastboot'/*, options */);
8+
9+
it('renders', function() {
10+
return this.visit('/')
11+
.then(function(res) {
12+
let $ = res.jQuery;
13+
let response = res.response;
14+
15+
expect(response.statusCode).to.equal(200);
16+
expect($('body').length).to.equal(1);
17+
expect($('h1').text().trim()).to.equal('ember-fastboot-addon-tests');
18+
});
19+
});
20+
21+
it('renders simple mobiledoc', function() {
22+
let name = 'simple';
23+
24+
return this.visit('/')
25+
.then(function(res) {
26+
let $ = res.jQuery;
27+
let response = res.response;
28+
29+
let wrapper = $(`.render-mobiledoc-wrapper.${name}`);
30+
expect(wrapper.length).to.equal(1);
31+
32+
let rendered = $(`.render-mobiledoc-wrapper.${name} p`).text().trim();
33+
expect(rendered).to.equal('hello world');
34+
});
35+
});
36+
37+
it('renders mobiledoc with markup', function() {
38+
let name = 'with-markup';
39+
return this.visit('/')
40+
.then(function(res) {
41+
let $ = res.jQuery;
42+
let response = res.response;
43+
44+
let wrapper = $(`.render-mobiledoc-wrapper.${name}`);
45+
expect(wrapper.length).to.equal(1);
46+
47+
let markup = $(`.render-mobiledoc-wrapper.${name} em`);
48+
expect(markup.length).to.equal(1);
49+
expect(markup.text().trim()).to.equal('markup text');
50+
});
51+
});
52+
53+
it('renders mobiledoc with link', function() {
54+
let name = 'with-link';
55+
return this.visit('/')
56+
.then(function(res) {
57+
let $ = res.jQuery;
58+
let response = res.response;
59+
60+
let wrapper = $(`.render-mobiledoc-wrapper.${name}`);
61+
expect(wrapper.length).to.equal(1);
62+
63+
let link = $(`.render-mobiledoc-wrapper.${name} a`);
64+
expect(link.length).to.equal(1);
65+
expect(link.attr('href')).to.equal('http://example.com/with-link');
66+
expect(link.text().trim()).to.equal('linked');
67+
});
68+
});
69+
70+
it('renders mobiledoc with unsafe link', function() {
71+
let name = 'with-unsafe-link';
72+
return this.visit('/')
73+
.then(function(res) {
74+
let $ = res.jQuery;
75+
let response = res.response;
76+
77+
let wrapper = $(`.render-mobiledoc-wrapper.${name}`);
78+
expect(wrapper.length).to.equal(1);
79+
80+
let link = $(`.render-mobiledoc-wrapper.${name} a`);
81+
expect(link.length).to.equal(1);
82+
expect(link.attr('href')).to.equal('unsafe:javascript:evil');
83+
expect(link.text().trim()).to.equal('linked unsafe');
84+
});
85+
});
86+
87+
});

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@
2828
"ember-cli-htmlbars": "^1.1.1",
2929
"ember-getowner-polyfill": "^1.2.2",
3030
"ember-wormhole": "^0.5.1",
31-
"mobiledoc-dom-renderer": "^0.6.3"
31+
"mobiledoc-dom-renderer": "^0.6.4"
3232
},
3333
"devDependencies": {
3434
"broccoli-asset-rev": "^2.4.2",
35+
"chai": "^3.5.0",
3536
"conventional-changelog": "^1.1.0",
3637
"conventional-changelog-cli": "^1.1.1",
3738
"ember-cli": "2.11.1",
@@ -49,9 +50,10 @@
4950
"ember-disable-prototype-extensions": "^1.1.0",
5051
"ember-disable-proxy-controllers": "^1.0.1",
5152
"ember-export-application-global": "^1.0.5",
53+
"ember-fastboot-addon-tests": "0.2.2",
5254
"ember-load-initializers": "^0.6.0",
53-
"ember-source": "~2.11.0",
5455
"ember-resolver": "^2.0.3",
56+
"ember-source": "~2.11.0",
5557
"loader.js": "^4.0.10"
5658
},
5759
"engines": {

0 commit comments

Comments
 (0)