diff --git a/package-lock.json b/package-lock.json index 4ffb369..a28762e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1443,6 +1443,21 @@ "@types/express-serve-static-core": "*" } }, + "@types/domhandler": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/domhandler/-/domhandler-2.4.1.tgz", + "integrity": "sha512-cfBw6q6tT5sa1gSPFSRKzF/xxYrrmeiut7E0TxNBObiLSBTuFEHibcfEe3waQPEDbqBsq+ql/TOniw65EyDFMA==", + "dev": true + }, + "@types/domutils": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@types/domutils/-/domutils-1.7.2.tgz", + "integrity": "sha512-Nnwy1Ztwq42SSNSZSh9EXBJGrOZPR+PQ2sRT4VZy8hnsFXfCil7YlKO2hd2360HyrtFz2qwnKQ13ENrgXNxJbw==", + "dev": true, + "requires": { + "@types/domhandler": "*" + } + }, "@types/es6-shim": { "version": "0.31.39", "resolved": "https://registry.npmjs.org/@types/es6-shim/-/es6-shim-0.31.39.tgz", @@ -1494,6 +1509,17 @@ "integrity": "sha512-ui3WwXmjTaY73fOQ3/m3nnajU/Orhi6cEu5rzX+BrAAJxa3eITXZ5ch9suPqtM03OWhAHhPSyBGCN4UKoxO20Q==", "dev": true }, + "@types/htmlparser2": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@types/htmlparser2/-/htmlparser2-3.10.0.tgz", + "integrity": "sha512-keXxWwpNOTvRTWTS4cdLHPp3p6gSzitTCmLNgPJinEvS95QzjkhbEMSaQO4XkEp4ctXJu8P0j4xqEVOPsLj3vg==", + "dev": true, + "requires": { + "@types/domhandler": "*", + "@types/domutils": "*", + "@types/node": "*" + } + }, "@types/http-errors": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.6.1.tgz", @@ -1518,6 +1544,12 @@ "integrity": "sha512-UGEe/6RsNAxgWdknhzFZbCxuYc5I7b/YEKlfKbo+76SM8CJzGs7XKCj7zyugXViRbKYpXhSXhCYVQZL5tmDbpQ==", "dev": true }, + "@types/marked": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.6.5.tgz", + "integrity": "sha512-6kBKf64aVfx93UJrcyEZ+OBM5nGv4RLsI6sR1Ar34bpgvGVRoyTgpxn4ZmtxOM5aDTAaaznYuYUH8bUX3Nk3YA==", + "dev": true + }, "@types/mime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", @@ -1630,6 +1662,15 @@ "@types/react-router": "*" } }, + "@types/sanitize-html": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-1.18.3.tgz", + "integrity": "sha512-rc2QO3fudRfDckthQj4xJ9oFpbWV6g1bkjNG5NSXeYo89f3qvOwBiKnFk6I12c4d3y2Qjrpst5YPh8PrdEKJcg==", + "dev": true, + "requires": { + "@types/htmlparser2": "*" + } + }, "@types/serve-static": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", @@ -3703,6 +3744,14 @@ "requires": { "marked": "^0.5.0", "marked-terminal": "^3.0.0" + }, + "dependencies": { + "marked": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.5.2.tgz", + "integrity": "sha512-fdZvBa7/vSQIZCi4uuwo2N3q+7jJURpMVCcbaX0S1Mg65WZ5ilXvC67MviJAsdjqqgD+CEq4RKo5AYGgINkVAA==", + "dev": true + } } }, "cli-width": { @@ -8560,6 +8609,12 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", + "dev": true + }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -8578,6 +8633,18 @@ "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", "dev": true }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true + }, "lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", @@ -8769,9 +8836,9 @@ } }, "marked": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.5.2.tgz", - "integrity": "sha512-fdZvBa7/vSQIZCi4uuwo2N3q+7jJURpMVCcbaX0S1Mg65WZ5ilXvC67MviJAsdjqqgD+CEq4RKo5AYGgINkVAA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.2.tgz", + "integrity": "sha512-LqxwVH3P/rqKX4EKGz7+c2G9r98WeM/SW34ybhgNGhUQNKtf1GmmSkJ6cDGJ/t6tiyae49qRkpyTw2B9HOrgUA==", "dev": true }, "marked-terminal": { @@ -10490,6 +10557,34 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "postcss": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.14.tgz", + "integrity": "sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -11388,6 +11483,24 @@ } } }, + "sanitize-html": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.20.0.tgz", + "integrity": "sha512-BpxXkBoAG+uKCHjoXFmox6kCSYpnulABoGcZ/R3QyY9ndXbIM5S94eOr1IqnzTG8TnbmXaxWoDDzKC5eJv7fEQ==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "htmlparser2": "^3.10.0", + "lodash.clonedeep": "^4.5.0", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.mergewith": "^4.6.1", + "postcss": "^7.0.5", + "srcset": "^1.0.0", + "xtend": "^4.0.1" + } + }, "sass-graph": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", @@ -12128,6 +12241,16 @@ "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=", "dev": true }, + "srcset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz", + "integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=", + "dev": true, + "requires": { + "array-uniq": "^1.0.2", + "number-is-nan": "^1.0.0" + } + }, "sshpk": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.0.tgz", diff --git a/package.json b/package.json index 895c5f0..39ac8ef 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@types/http-errors": "^1.6.1", "@types/jest": "^23.3.12", "@types/js-yaml": "^3.12.0", + "@types/marked": "^0.6.5", "@types/nock": "^9.3.1", "@types/node": "^10.12.18", "@types/passport": "^1.0.0", @@ -28,6 +29,7 @@ "@types/react-dom": "^16.0.11", "@types/react-redux": "^6.0.12", "@types/react-router-dom": "^4.3.1", + "@types/sanitize-html": "^1.18.3", "@types/shortid": "0.0.29", "@types/std-mocks": "^1.0.0", "@types/supertest": "^2.0.7", @@ -43,6 +45,7 @@ "jest": "^24.5.0", "lerna": "^3.13.1", "loose-envify": "^1.4.0", + "marked": "^0.6.2", "mysql": "^2.16.0", "nock": "^10.0.6", "node-sass": "^4.11.0", @@ -55,6 +58,7 @@ "react-redux": "^6.0.0", "react-router-dom": "^4.4.0", "redux": "^4.0.1", + "sanitize-html": "^1.20.0", "sourceify": "git+https://github.com/jeremija/sourceify.git#sources-content", "std-mocks": "^1.0.1", "supertest": "^3.3.0", diff --git a/packages/common/src/Markdown.test.ts b/packages/common/src/Markdown.test.ts new file mode 100644 index 0000000..ac3fccc --- /dev/null +++ b/packages/common/src/Markdown.test.ts @@ -0,0 +1,23 @@ +import {Markdown} from './Markdown' + +describe('Markdown', () => { + + const markdown = new Markdown() + + describe('parse', () => { + it('sanitizes html', () => { + const html = markdown.parse( + `Hey check [this](example.com)! + + +Test +`) + expect(html).toEqual(`

Hey check this!

+ +

Test +

+`) + }) + }) + +}) diff --git a/packages/common/src/Markdown.ts b/packages/common/src/Markdown.ts new file mode 100644 index 0000000..5a738c0 --- /dev/null +++ b/packages/common/src/Markdown.ts @@ -0,0 +1,18 @@ +import marked from 'marked' +import sanitize from 'sanitize-html' + +const allowedTags = sanitize.defaults.allowedTags.concat(['img']) + +export class Markdown { + protected readonly markdownOptions = { + gfm: true, + } + protected readonly sanitizeOptions = { + allowedTags: sanitize.defaults.allowedTags.concat(['img']), + } + + parse(markdown: string): string { + const dangerousHTML = marked(markdown, this.markdownOptions) + return sanitize(dangerousHTML, this.sanitizeOptions) + } +}