Skip to content

Commit 714ef29

Browse files
anikethsahatrusktr
andauthored
feat: added html sanitizer for remote rendering (#1128)
Co-authored-by: Joe Pea <[email protected]>
1 parent 0bf03f5 commit 714ef29

File tree

8 files changed

+2074
-1071
lines changed

8 files changed

+2074
-1071
lines changed

Diff for: .eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module.exports = {
1919
rules: {
2020
'prettier/prettier': ['error'],
2121
camelcase: ['warn'],
22+
'no-useless-escape': ['warn'],
2223
curly: ['error', 'all'],
2324
'dot-notation': ['error'],
2425
eqeqeq: ['error'],

Diff for: package-lock.json

+2,007-1,068
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"*.js": "eslint --fix"
5858
},
5959
"dependencies": {
60+
"dompurify": "^2.0.8",
6061
"marked": "^0.7.0",
6162
"medium-zoom": "^1.0.5",
6263
"opencollective-postinstall": "^2.0.2",
@@ -82,7 +83,7 @@
8283
"esm": "^3.1.4",
8384
"husky": "^3.1.0",
8485
"jsdom": "^16.2.2",
85-
"lerna": "^3.17.0",
86+
"lerna": "^3.22.1",
8687
"lint-staged": "^10.1.2",
8788
"live-server": "^1.2.1",
8889
"mkdirp": "^0.5.1",

Diff for: packages/docsify-server-renderer/index.js

+29-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { resolve, basename } from 'path';
33
import resolvePathname from 'resolve-pathname';
44
import fetch from 'node-fetch';
55
import debug from 'debug';
6+
import DOMPurify from 'dompurify';
67
import { AbstractHistory } from '../../src/core/router/history/abstract';
78
import { Compiler } from '../../src/core/render/compiler';
89
import { isAbsolutePath } from '../../src/core/router/util';
@@ -13,6 +14,32 @@ function cwd(...args) {
1314
return resolve(process.cwd(), ...args);
1415
}
1516

17+
function isExternal(url) {
18+
let match = url.match(
19+
/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/
20+
);
21+
if (
22+
typeof match[1] === 'string' &&
23+
match[1].length > 0 &&
24+
match[1].toLowerCase() !== location.protocol
25+
) {
26+
return true;
27+
}
28+
if (
29+
typeof match[2] === 'string' &&
30+
match[2].length > 0 &&
31+
match[2].replace(
32+
new RegExp(
33+
':(' + { 'http:': 80, 'https:': 443 }[location.protocol] + ')?$'
34+
),
35+
''
36+
) !== location.host
37+
) {
38+
return true;
39+
}
40+
return false;
41+
}
42+
1643
function mainTpl(config) {
1744
let html = `<nav class="app-nav${
1845
config.repo ? '' : ' no-badge'
@@ -60,6 +87,7 @@ export default class Renderer {
6087

6188
async renderToString(url) {
6289
this.url = url = this.router.parse(url).path;
90+
this.isRemoteUrl = isExternal(this.url);
6391
const { loadSidebar, loadNavbar, coverpage } = this.config;
6492

6593
const mainFile = this._getPath(url);
@@ -95,9 +123,8 @@ export default class Renderer {
95123
this._renderHtml('cover', await this._render(coverFile), 'cover');
96124
}
97125

98-
const html = this.html;
126+
const html = this.isRemoteUrl ? DOMPurify.sanitize(this.html) : this.html;
99127
this.html = this.template;
100-
101128
return html;
102129
}
103130

Diff for: packages/docsify-server-renderer/package-lock.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: packages/docsify-server-renderer/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"dependencies": {
1818
"debug": "^4.1.1",
1919
"docsify": "^4.11.2",
20+
"dompurify": "^2.0.8",
2021
"node-fetch": "^2.6.0",
2122
"resolve-pathname": "^3.0.0"
2223
}

Diff for: src/core/fetch/index.js

+27
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,32 @@ function loadNested(path, qs, file, next, vm, first) {
2020
).then(next, _ => loadNested(path, qs, file, next, vm));
2121
}
2222

23+
function isExternal(url) {
24+
let match = url.match(
25+
/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/
26+
);
27+
if (
28+
typeof match[1] === 'string' &&
29+
match[1].length > 0 &&
30+
match[1].toLowerCase() !== location.protocol
31+
) {
32+
return true;
33+
}
34+
if (
35+
typeof match[2] === 'string' &&
36+
match[2].length > 0 &&
37+
match[2].replace(
38+
new RegExp(
39+
':(' + { 'http:': 80, 'https:': 443 }[location.protocol] + ')?$'
40+
),
41+
''
42+
) !== location.host
43+
) {
44+
return true;
45+
}
46+
return false;
47+
}
48+
2349
export function fetchMixin(proto) {
2450
let last;
2551

@@ -84,6 +110,7 @@ export function fetchMixin(proto) {
84110
const file = this.router.getFile(path);
85111
const req = request(file + qs, true, requestHeaders);
86112

113+
this.isRemoteUrl = isExternal(file);
87114
// Current page is html
88115
this.isHTML = /\.html$/g.test(file);
89116

Diff for: src/core/render/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable no-unused-vars */
22
import tinydate from 'tinydate';
3+
import DOMPurify from 'dompurify';
34
import * as dom from '../util/dom';
45
import cssVars from '../util/polyfill/css-vars';
56
import { callHook } from '../init/lifecycle';
@@ -172,6 +173,7 @@ export function renderMixin(proto) {
172173
},
173174
tokens => {
174175
html = this.compiler.compile(tokens);
176+
html = this.isRemoteUrl ? DOMPurify.sanitize(html) : html;
175177
callback();
176178
next();
177179
}

0 commit comments

Comments
 (0)