diff --git a/packages/ui-default/backendlib/markdown-it-xss.js b/packages/ui-default/backendlib/markdown-it-xss.ts similarity index 73% rename from packages/ui-default/backendlib/markdown-it-xss.js rename to packages/ui-default/backendlib/markdown-it-xss.ts index f554dd797..2dba4cc30 100644 --- a/packages/ui-default/backendlib/markdown-it-xss.js +++ b/packages/ui-default/backendlib/markdown-it-xss.ts @@ -1,4 +1,7 @@ -const Xss = require('xss'); +import * as Xss from 'xss'; + +const stack = []; +let isFull = false; const xss = new Xss.FilterXSS({ whiteList: { @@ -73,8 +76,35 @@ const xss = new Xss.FilterXSS({ if (name === 'class') return value.replace(/badge/g, 'xss-badge'); return value; }, + onTag(tag, html, options) { + if (!options.isWhite || !isFull) return null; + if (!options.isClosing) { + stack.push(tag); + return null; + } + if (stack.length === 0) return `</${tag}>`; // 没有标签可供闭合 + if (stack[stack.length - 1] === tag) { + stack.pop(); // 正常关闭 + return null; + } + if (stack.length - 2 >= 0 && stack[stack.length - 2] === tag) { + // 可能丢失了一个结束标签 + stack.pop(); + stack.pop(); + return null; + } + return `</${tag}>`; // 可能多出了一个结束标签 + }, }); +xss.process = ((original) => (html: string, full: boolean = false) => { + stack.length = 0; + isFull = full; + const res = original(html); + if (!full) return res; + return res + stack.map((i) => ``).join(''); +})(xss.process.bind(xss)); + function xssProtector(md) { function protector(state) { for (let i = 0; i < state.tokens.length; i++) { diff --git a/packages/ui-default/backendlib/template.js b/packages/ui-default/backendlib/template.js index 0570b4e18..9b127f17e 100644 --- a/packages/ui-default/backendlib/template.js +++ b/packages/ui-default/backendlib/template.js @@ -63,8 +63,8 @@ class Nunjucks extends nunjucks.Environment { this.addFilter('dumpYaml', (self) => yaml.dump(self)); this.addFilter('serialize', (self, ignoreFunction = true) => serialize(self, { ignoreFunction })); this.addFilter('assign', (self, data) => Object.assign(self, data)); - this.addFilter('markdown', (self, html = false) => markdown.render(self, html)); - this.addFilter('markdownInline', (self, html = false) => markdown.renderInline(self, html)); + this.addFilter('markdown', (self, html = false) => xss.process(markdown.render(self, html), true)); + this.addFilter('markdownInline', (self, html = false) => xss.process(markdown.renderInline(self, html), true)); this.addFilter('ansi', (self) => misc.ansiToHtml(self)); this.addFilter('base64_encode', (s) => Buffer.from(s).toString('base64')); this.addFilter('base64_decode', (s) => Buffer.from(s, 'base64').toString()); @@ -87,7 +87,7 @@ class Nunjucks extends nunjucks.Environment { else s = s[langs[0]]; } if (s instanceof Array) s = buildContent(s, html ? 'html' : 'markdown', (str) => str.translate(language)); - return html ? xss.process(s) : markdown.render(s); + return xss.process(html ? s : markdown.render(s), true); }); this.addFilter('log', (self) => { console.log(self); diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 29b64d53e..d2e0eca34 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.31.8", + "version": "4.31.9", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js",