improvement: sanitization via DOMPurify (#4513)
* swap `sanitize-html` for `dompurify` * set up node enzyme tests with jsdom dompurify, as the name suggests, needs a DOM or it won't work! * reconcile tests and sanitizer settings * remove obsolete sanitizeOptions * add `jsdom` dependency
This commit is contained in:
@@ -31,7 +31,7 @@
|
|||||||
"test": "npm run lint-errors && npm run just-test-in-node",
|
"test": "npm run lint-errors && npm run just-test-in-node",
|
||||||
"test-in-node": "npm run lint-errors && npm run just-test-in-node",
|
"test-in-node": "npm run lint-errors && npm run just-test-in-node",
|
||||||
"just-test": "karma start --config karma.conf.js",
|
"just-test": "karma start --config karma.conf.js",
|
||||||
"just-test-in-node": "mocha --recursive --compilers js:babel-core/register test/core test/components test/bugs test/swagger-ui-dist-package test/xss",
|
"just-test-in-node": "mocha --require test/setup.js --recursive --compilers js:babel-core/register test/core test/components test/bugs test/swagger-ui-dist-package test/xss",
|
||||||
"just-check-coverage": "nyc npm run just-test-in-node",
|
"just-check-coverage": "nyc npm run just-test-in-node",
|
||||||
"test-e2e": "sleep 3 && nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json",
|
"test-e2e": "sleep 3 && nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json",
|
||||||
"e2e-initial-render": "nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json --group initial-render",
|
"e2e-initial-render": "nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json --group initial-render",
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
"core-js": "^2.5.1",
|
"core-js": "^2.5.1",
|
||||||
"css.escape": "1.5.1",
|
"css.escape": "1.5.1",
|
||||||
"deep-extend": "0.4.1",
|
"deep-extend": "0.4.1",
|
||||||
|
"dompurify": "^1.0.4",
|
||||||
"expect": "1.20.2",
|
"expect": "1.20.2",
|
||||||
"getbase": "^2.8.2",
|
"getbase": "^2.8.2",
|
||||||
"ieee754": "^1.1.8",
|
"ieee754": "^1.1.8",
|
||||||
@@ -80,7 +81,6 @@
|
|||||||
"redux-logger": "*",
|
"redux-logger": "*",
|
||||||
"remarkable": "^1.7.1",
|
"remarkable": "^1.7.1",
|
||||||
"reselect": "2.5.3",
|
"reselect": "2.5.3",
|
||||||
"sanitize-html": "^1.14.1",
|
|
||||||
"scroll-to-element": "^2.0.0",
|
"scroll-to-element": "^2.0.0",
|
||||||
"serialize-error": "2.0.0",
|
"serialize-error": "2.0.0",
|
||||||
"shallowequal": "0.2.2",
|
"shallowequal": "0.2.2",
|
||||||
@@ -119,6 +119,7 @@
|
|||||||
"file-loader": "0.11.2",
|
"file-loader": "0.11.2",
|
||||||
"git-describe": "^4.0.1",
|
"git-describe": "^4.0.1",
|
||||||
"imports-loader": "0.7.1",
|
"imports-loader": "0.7.1",
|
||||||
|
"jsdom": "^11.10.0",
|
||||||
"json-loader": "0.5.4",
|
"json-loader": "0.5.4",
|
||||||
"json-server": "^0.11.0",
|
"json-server": "^0.11.0",
|
||||||
"karma": "^1.7.0",
|
"karma": "^1.7.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types"
|
||||||
import Remarkable from "remarkable"
|
import Remarkable from "remarkable"
|
||||||
import sanitize from "sanitize-html"
|
import DomPurify from "dompurify"
|
||||||
import cx from "classnames"
|
import cx from "classnames"
|
||||||
|
|
||||||
// eslint-disable-next-line no-useless-escape
|
// eslint-disable-next-line no-useless-escape
|
||||||
@@ -40,20 +40,8 @@ Markdown.propTypes = {
|
|||||||
|
|
||||||
export default Markdown
|
export default Markdown
|
||||||
|
|
||||||
const sanitizeOptions = {
|
|
||||||
allowedTags: sanitize.defaults.allowedTags.concat([ "h1", "h2", "img", "span" ]),
|
|
||||||
allowedAttributes: {
|
|
||||||
...sanitize.defaults.allowedAttributes,
|
|
||||||
"img": sanitize.defaults.allowedAttributes.img.concat(["title"]),
|
|
||||||
"td": [ "colspan" ],
|
|
||||||
"*": [ "class" ]
|
|
||||||
},
|
|
||||||
allowedSchemesByTag: { img: [ "http", "https", "data" ] },
|
|
||||||
textFilter: function(text) {
|
|
||||||
return text.replace(/"/g, "\"")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sanitizer(str) {
|
export function sanitizer(str) {
|
||||||
return sanitize(str, sanitizeOptions)
|
return DomPurify.sanitize(str, {
|
||||||
|
ADD_ATTR: ["target"]
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,19 +16,19 @@ describe("Markdown component", function() {
|
|||||||
it("allows td elements with colspan attrib", function() {
|
it("allows td elements with colspan attrib", function() {
|
||||||
const str = `<table><tr><td>ABC</td></tr></table>`
|
const str = `<table><tr><td>ABC</td></tr></table>`
|
||||||
const el = render(<Markdown source={str} />)
|
const el = render(<Markdown source={str} />)
|
||||||
expect(el.html()).toEqual(`<div class="markdown"><table><tr><td>ABC</td></tr></table></div>`)
|
expect(el.html()).toEqual(`<div class="markdown"><table><tbody><tr><td>ABC</td></tr></tbody></table></div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("allows image elements", function() {
|
it("allows image elements", function() {
|
||||||
const str = ``
|
const str = ``
|
||||||
const el = render(<Markdown source={str} />)
|
const el = render(<Markdown source={str} />)
|
||||||
expect(el.html()).toEqual(`<div class="markdown"><p><img src="http://image.source" title="Image title"></p>\n</div>`)
|
expect(el.html()).toEqual(`<div class="markdown"><p><img title="Image title" alt="Image alt text" src="http://image.source"></p>\n</div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("allows image elements with https scheme", function() {
|
it("allows image elements with https scheme", function() {
|
||||||
const str = ``
|
const str = ``
|
||||||
const el = render(<Markdown source={str} />)
|
const el = render(<Markdown source={str} />)
|
||||||
expect(el.html()).toEqual(`<div class="markdown"><p><img src="https://image.source" title="Image title"></p>\n</div>`)
|
expect(el.html()).toEqual(`<div class="markdown"><p><img title="Image title" alt="Image alt text" src="https://image.source"></p>\n</div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("allows image elements with data scheme", function() {
|
it("allows image elements with data scheme", function() {
|
||||||
@@ -52,7 +52,7 @@ describe("Markdown component", function() {
|
|||||||
it("allows links", function() {
|
it("allows links", function() {
|
||||||
const str = `[Link](https://example.com/)`
|
const str = `[Link](https://example.com/)`
|
||||||
const el = render(<Markdown source={str} />)
|
const el = render(<Markdown source={str} />)
|
||||||
expect(el.html()).toEqual(`<div class="markdown"><p><a href="https://example.com/" target="_blank">Link</a></p>\n</div>`)
|
expect(el.html()).toEqual(`<div class="markdown"><p><a target="_blank" href="https://example.com/">Link</a></p>\n</div>`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -60,13 +60,13 @@ describe("Markdown component", function() {
|
|||||||
it("allows image elements", function() {
|
it("allows image elements", function() {
|
||||||
const str = ``
|
const str = ``
|
||||||
const el = render(<OAS3Markdown source={str} />)
|
const el = render(<OAS3Markdown source={str} />)
|
||||||
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img src="http://image.source" title="Image title"></p></div></div>`)
|
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img title="Image title" alt="Image alt text" src="http://image.source"></p></div></div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("allows image elements with https scheme", function() {
|
it("allows image elements with https scheme", function() {
|
||||||
const str = ``
|
const str = ``
|
||||||
const el = render(<OAS3Markdown source={str} />)
|
const el = render(<OAS3Markdown source={str} />)
|
||||||
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img src="https://image.source" title="Image title"></p></div></div>`)
|
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img title="Image title" alt="Image alt text" src="https://image.source"></p></div></div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("allows image elements with data scheme", function() {
|
it("allows image elements with data scheme", function() {
|
||||||
|
|||||||
23
test/setup.js
Normal file
23
test/setup.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const { JSDOM } = require("jsdom")
|
||||||
|
const win = require("core/window")
|
||||||
|
|
||||||
|
const jsdom = new JSDOM("<!doctype html><html><body></body></html>")
|
||||||
|
const { window } = jsdom
|
||||||
|
|
||||||
|
function copyProps(src, target) {
|
||||||
|
const props = Object.getOwnPropertyNames(src)
|
||||||
|
.filter(prop => typeof target[prop] === "undefined")
|
||||||
|
.reduce((result, prop) => ({
|
||||||
|
...result,
|
||||||
|
[prop]: Object.getOwnPropertyDescriptor(src, prop),
|
||||||
|
}), {})
|
||||||
|
Object.defineProperties(target, props)
|
||||||
|
}
|
||||||
|
|
||||||
|
global.window = window
|
||||||
|
global.document = window.document
|
||||||
|
global.navigator = {
|
||||||
|
userAgent: "node.js",
|
||||||
|
}
|
||||||
|
copyProps(win, window) // use UI's built-in window wrapper
|
||||||
|
copyProps(window, global)
|
||||||
Reference in New Issue
Block a user