diff --git a/README.md b/README.md index b57f31d..01f9205 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ marked: autolink: true mangle: true sanitizeUrl: false + dompurify: false, headerIds: true lazyload: false prependRoot: false @@ -61,6 +62,7 @@ marked: - **mangle** - Escape autolinked email address with HTML character references. * This is to obscure email address from _basic_ crawler used by spam bot, while still readable to web browsers. - **sanitizeUrl** - Remove URLs that start with `javascript:`, `vbscript:` and `data:`. +- **dompurify** - Enable [DOMPurify](https://github.com/cure53/DOMPurify) to be run on the rendered Markdown. See below for configuration - **headerIds** - Insert header id, e.g. `

text

`. Useful for inserting anchor link to each paragraph with a heading. - **anchorAlias** - Enables custom header id * Example: `## [foo](#bar)`, id will be set as "bar". @@ -91,6 +93,26 @@ For more options, see [Marked](https://marked.js.org/using_advanced#options). Du ## Extras +### Sanitize HTML with DOMPurify + +[DOMPurify](https://github.com/cure53/DOMPurify) can be enabled to sanitize the rendered HTML. + +To enable it, pass an object containing the DOMPurify options: + +```json +dompurify: true +``` + +Or you can enable specific DOMPurify options (but according to DOMPurify authors, the default options are safe): + +```yml +dompurify: + FORBID_TAGS: + - "style" +``` + +See https://github.com/cure53/DOMPurify#can-i-configure-dompurify for a full reference of available options + ### Definition/Description Lists `hexo-renderer-marked` also implements description/definition lists using the same syntax as [PHP Markdown Extra][PHP Markdown Extra]. diff --git a/index.js b/index.js index 43b1202..3781d18 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,7 @@ hexo.config.marked = Object.assign({ autolink: true, mangle: true, sanitizeUrl: false, + dompurify: false, headerIds: true, anchorAlias: false, lazyload: false, diff --git a/lib/renderer.js b/lib/renderer.js index 865de36..e31b91a 100644 --- a/lib/renderer.js +++ b/lib/renderer.js @@ -1,6 +1,10 @@ 'use strict'; const marked = require('marked'); + +let JSDOM, + createDOMPurify; + const { escape } = require('marked/src/helpers'); const { encodeURL, slugize, stripHTML, url_for, isExternalLink } = require('hexo-util'); const MarkedRenderer = marked.Renderer; @@ -236,7 +240,7 @@ class Tokenizer extends MarkedTokenizer { module.exports = function(data, options) { const { post_asset_folder, marked: markedCfg, source_dir } = this.config; - const { prependRoot, postAsset } = markedCfg; + const { prependRoot, postAsset, dompurify } = markedCfg; const { path, text } = data; // exec filter to extend renderer. @@ -258,8 +262,23 @@ module.exports = function(data, options) { } } - return marked(text, Object.assign({ + let sanitizer = function(html) { return html; }; + + if (dompurify) { + if (createDOMPurify === undefined && JSDOM === undefined) { + createDOMPurify = require('dompurify'); + JSDOM = require('jsdom').JSDOM; + } + const window = new JSDOM('').window; + const DOMPurify = createDOMPurify(window); + let param = {}; + if (dompurify !== true) { + param = dompurify; + } + sanitizer = function(html) { return DOMPurify.sanitize(html, param); }; + } + return sanitizer(marked(text, Object.assign({ renderer, tokenizer - }, markedCfg, options, { postPath })); + }, markedCfg, options, { postPath }))); }; diff --git a/package.json b/package.json index 78e5871..2357660 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,10 @@ ], "license": "MIT", "dependencies": { - "hexo-util": "^2.1.0", - "marked": "^2.0.0" + "hexo-util": "^2.5.0", + "marked": "^2.1.3", + "dompurify": "^2.3.0", + "jsdom": "^16.7.0" }, "devDependencies": { "chai": "^4.2.0", diff --git a/test/index.js b/test/index.js index 123fc21..1205b0a 100644 --- a/test/index.js +++ b/test/index.js @@ -949,4 +949,37 @@ describe('Marked renderer', () => { result.content.should.eql('

foo {% lorem %}

\n'); }); }); + + describe('sanitize HTML with DOMPurify', () => { + const body = [ + '**safe markdown**', + '', + 'unsafe link', + '', + '[Hexo](http://hexo.io)' + ].join('\n'); + + it('sanitize enabled, default options', () => { + hexo.config.marked.dompurify = true; + const result = r({text: body}); + + result.should.eql([ + '

safe markdown

\n', + '

unsafe link

\n', + '

Hexo

\n' + ].join('')); + }); + + it('sanitize enabled, with options', () => { + hexo.config.marked.dompurify = { FORBID_TAGS: ['strong'] }; + const result = r({text: body}); + + result.should.eql([ + '

safe markdown

\n', + '

unsafe link

\n', + '

Hexo

\n' + ].join('')); + }); + + }); });