mirror of
https://github.com/docsifyjs/docsify.git
synced 2025-12-08 19:55:52 +00:00
feat: added html sanitizer for remote rendering (#1128)
Co-authored-by: Joe Pea <joe@trusktr.io>
This commit is contained in:
parent
0bf03f5810
commit
714ef29afe
@ -19,6 +19,7 @@ module.exports = {
|
||||
rules: {
|
||||
'prettier/prettier': ['error'],
|
||||
camelcase: ['warn'],
|
||||
'no-useless-escape': ['warn'],
|
||||
curly: ['error', 'all'],
|
||||
'dot-notation': ['error'],
|
||||
eqeqeq: ['error'],
|
||||
|
||||
2937
package-lock.json
generated
2937
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -57,6 +57,7 @@
|
||||
"*.js": "eslint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"dompurify": "^2.0.8",
|
||||
"marked": "^0.7.0",
|
||||
"medium-zoom": "^1.0.5",
|
||||
"opencollective-postinstall": "^2.0.2",
|
||||
@ -82,7 +83,7 @@
|
||||
"esm": "^3.1.4",
|
||||
"husky": "^3.1.0",
|
||||
"jsdom": "^16.2.2",
|
||||
"lerna": "^3.17.0",
|
||||
"lerna": "^3.22.1",
|
||||
"lint-staged": "^10.1.2",
|
||||
"live-server": "^1.2.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
|
||||
@ -3,6 +3,7 @@ import { resolve, basename } from 'path';
|
||||
import resolvePathname from 'resolve-pathname';
|
||||
import fetch from 'node-fetch';
|
||||
import debug from 'debug';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { AbstractHistory } from '../../src/core/router/history/abstract';
|
||||
import { Compiler } from '../../src/core/render/compiler';
|
||||
import { isAbsolutePath } from '../../src/core/router/util';
|
||||
@ -13,6 +14,32 @@ function cwd(...args) {
|
||||
return resolve(process.cwd(), ...args);
|
||||
}
|
||||
|
||||
function isExternal(url) {
|
||||
let match = url.match(
|
||||
/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/
|
||||
);
|
||||
if (
|
||||
typeof match[1] === 'string' &&
|
||||
match[1].length > 0 &&
|
||||
match[1].toLowerCase() !== location.protocol
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
typeof match[2] === 'string' &&
|
||||
match[2].length > 0 &&
|
||||
match[2].replace(
|
||||
new RegExp(
|
||||
':(' + { 'http:': 80, 'https:': 443 }[location.protocol] + ')?$'
|
||||
),
|
||||
''
|
||||
) !== location.host
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function mainTpl(config) {
|
||||
let html = `<nav class="app-nav${
|
||||
config.repo ? '' : ' no-badge'
|
||||
@ -60,6 +87,7 @@ export default class Renderer {
|
||||
|
||||
async renderToString(url) {
|
||||
this.url = url = this.router.parse(url).path;
|
||||
this.isRemoteUrl = isExternal(this.url);
|
||||
const { loadSidebar, loadNavbar, coverpage } = this.config;
|
||||
|
||||
const mainFile = this._getPath(url);
|
||||
@ -95,9 +123,8 @@ export default class Renderer {
|
||||
this._renderHtml('cover', await this._render(coverFile), 'cover');
|
||||
}
|
||||
|
||||
const html = this.html;
|
||||
const html = this.isRemoteUrl ? DOMPurify.sanitize(this.html) : this.html;
|
||||
this.html = this.template;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
@ -43,6 +43,11 @@
|
||||
"tweezer.js": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"dompurify": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.0.8.tgz",
|
||||
"integrity": "sha512-vIOSyOXkMx81ghEalh4MLBtDHMx1bhKlaqHDMqM2yeitJ996SLOk5mGdDpI9ifJAgokred8Rmu219fX4OltqXw=="
|
||||
},
|
||||
"good-listener": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
"docsify": "^4.11.2",
|
||||
"dompurify": "^2.0.8",
|
||||
"node-fetch": "^2.6.0",
|
||||
"resolve-pathname": "^3.0.0"
|
||||
}
|
||||
|
||||
@ -20,6 +20,32 @@ function loadNested(path, qs, file, next, vm, first) {
|
||||
).then(next, _ => loadNested(path, qs, file, next, vm));
|
||||
}
|
||||
|
||||
function isExternal(url) {
|
||||
let match = url.match(
|
||||
/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/
|
||||
);
|
||||
if (
|
||||
typeof match[1] === 'string' &&
|
||||
match[1].length > 0 &&
|
||||
match[1].toLowerCase() !== location.protocol
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
typeof match[2] === 'string' &&
|
||||
match[2].length > 0 &&
|
||||
match[2].replace(
|
||||
new RegExp(
|
||||
':(' + { 'http:': 80, 'https:': 443 }[location.protocol] + ')?$'
|
||||
),
|
||||
''
|
||||
) !== location.host
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function fetchMixin(proto) {
|
||||
let last;
|
||||
|
||||
@ -84,6 +110,7 @@ export function fetchMixin(proto) {
|
||||
const file = this.router.getFile(path);
|
||||
const req = request(file + qs, true, requestHeaders);
|
||||
|
||||
this.isRemoteUrl = isExternal(file);
|
||||
// Current page is html
|
||||
this.isHTML = /\.html$/g.test(file);
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import tinydate from 'tinydate';
|
||||
import DOMPurify from 'dompurify';
|
||||
import * as dom from '../util/dom';
|
||||
import cssVars from '../util/polyfill/css-vars';
|
||||
import { callHook } from '../init/lifecycle';
|
||||
@ -172,6 +173,7 @@ export function renderMixin(proto) {
|
||||
},
|
||||
tokens => {
|
||||
html = this.compiler.compile(tokens);
|
||||
html = this.isRemoteUrl ? DOMPurify.sanitize(html) : html;
|
||||
callback();
|
||||
next();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user