diff --git a/__tests__/src/components/SanitizedHtml.test.js b/__tests__/src/components/SanitizedHtml.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9d62c9e02d342fe44fa919eb1d13211ff728b0dc --- /dev/null +++ b/__tests__/src/components/SanitizedHtml.test.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import SanitizedHtml from '../../../src/components/SanitizedHtml'; + +const wrapper = shallow( + <SanitizedHtml + htmlString="<script>doBadThings()</script><b>Don't worry!</b>" + ruleSet="iiif" + />, +); + +describe('SanitizedHtml', () => { + it('should render needed elements', () => { + expect(wrapper.find('span').length).toBe(1); + }); + + it('should pass correct class name to root element', () => { + expect(wrapper.find('span').first().props().className).toBe('mirador-third-party-html'); + }); + + it('should pass sanitized html string to dangerouslySetInnerHTML attribute', () => { + expect(wrapper.find('span').first().props().dangerouslySetInnerHTML) + .toEqual({ __html: "<b>Don't worry!</b>" }); + }); +}); diff --git a/package.json b/package.json index da64e5c5f081717f3b973647b80ca8d378187dba..92c84719f18dadc21ac0fd3f38727c2e84afcd00 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "classnames": "^2.2.6", "css-ns": "^1.2.2", "deepmerge": "^3.1.0", + "dompurify": "^1.0.9", "i18next": "^14.0.1", "intersection-observer": "^0.5.1", "lodash": "^4.17.11", diff --git a/src/components/SanitizedHtml.js b/src/components/SanitizedHtml.js new file mode 100644 index 0000000000000000000000000000000000000000..299bf2eab8f6b93699e9ee2bac4a9b445f39ef5a --- /dev/null +++ b/src/components/SanitizedHtml.js @@ -0,0 +1,30 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { sanitize } from 'dompurify'; +import ns from '../config/css-ns'; +import htmlRules from '../lib/htmlRules'; + +/** +*/ +class SanitizedHtml extends Component { + /** + */ + render() { + const { htmlString, ruleSet } = this.props; + return ( + <span + className={ns('third-party-html')} + dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger + __html: sanitize(htmlString, htmlRules[ruleSet]), + }} + /> + ); + } +} + +SanitizedHtml.propTypes = { + ruleSet: PropTypes.string.isRequired, + htmlString: PropTypes.string.isRequired, +}; + +export default SanitizedHtml; diff --git a/src/lib/htmlRules.js b/src/lib/htmlRules.js new file mode 100644 index 0000000000000000000000000000000000000000..30b190428c014a52cc6f44e453803f300212d857 --- /dev/null +++ b/src/lib/htmlRules.js @@ -0,0 +1,27 @@ + +// Only remove security related tags and attributes. Allow each other. +const liberal = {}; + +// No html at all. Only text will remain. +const noHtml = { + ALLOWED_TAGS: [], +}; + +// Presentation API 2 suggestion. +const iiif = { + ALLOWED_TAGS: ['a', 'b', 'br', 'i', 'img', 'p', 'span'], + ALLOWED_ATTR: ['href', 'src', 'alt'], +}; + +// Rule set that is used in Mirador 2. +const mirador2 = { + ALLOWED_TAGS: ['a', 'b', 'br', 'i', 'img', 'p', 'span', 'strong', 'em', 'ul', 'ol', 'li'], + ALLOWED_ATTR: ['href', 'target', 'src', 'alt', 'dir'], +}; + +export default { + liberal, + noHtml, + iiif, + mirador2, +};