123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- import DOMPurify from 'dompurify';
- import { getSecureDOMPurifyConfig } from './secureDOMPurify';
- var Markdown = require('markdown-it')({
- html: true,
- linkify: true,
- typographer: true,
- breaks: true,
- });
- //import markdownItMermaid from "@wekanteam/markdown-it-mermaid";
- // Static URL Scheme Listing
- var urlschemes = [
- "aodroplink",
- "thunderlink",
- "cbthunderlink",
- "onenote",
- "file",
- "abasurl",
- "conisio",
- "mailspring"
- ];
- // Better would be a field in the admin backend to set this dynamically
- // instead of putting all known or wanted url schemes here hard into code
- // but i was not able to access those settings
- // var urlschemes = currentSetting.automaticLinkedUrlSchemes.split('\n');
- // put all url schemes into the linkify configuration to automatically make it clickable
- for(var i=0; i<urlschemes.length;i++){
- Markdown.linkify.add(urlschemes[i]+":",'http:');
- }
- // Try to load emoji support, but don't fail if it's not available
- try {
- var emoji = require('markdown-it-emoji');
- Markdown.use(emoji);
- } catch (e) {
- console.warn('markdown-it-emoji not available, emoji rendering disabled:', e.message);
- }
- // Try to load mathjax3, but don't fail if it's not available
- try {
- var mathjax = require('markdown-it-mathjax3');
- Markdown.use(mathjax);
- } catch (e) {
- console.warn('markdown-it-mathjax3 not available, math rendering disabled:', e.message);
- }
- // Custom plugin to prevent SVG-based DoS attacks
- Markdown.use(function(md) {
- // Filter out dangerous SVG content in markdown
- md.core.ruler.push('svg-dos-protection', function(state) {
- const tokens = state.tokens;
- for (let i = 0; i < tokens.length; i++) {
- const token = tokens[i];
- // Check for image tokens that might contain SVG
- if (token.type === 'image') {
- const src = token.attrGet('src');
- if (src) {
- // Block SVG data URIs and .svg files
- if (src.startsWith('data:image/svg') || src.endsWith('.svg')) {
- if (process.env.DEBUG === 'true') {
- console.warn('Blocked potentially malicious SVG image in markdown:', src);
- }
- // Replace with a warning message
- token.type = 'paragraph_open';
- token.tag = 'p';
- token.nesting = 1;
- token.attrSet('style', 'color: red; background: #ffe6e6; padding: 8px; border: 1px solid #ff9999;');
- token.attrSet('title', 'Blocked potentially malicious SVG image');
- // Add warning text token
- const warningToken = {
- type: 'text',
- content: '⚠️ Blocked potentially malicious SVG image for security reasons',
- level: token.level,
- markup: '',
- info: '',
- meta: null,
- block: true,
- hidden: false
- };
- // Insert warning token after the paragraph open
- tokens.splice(i + 1, 0, warningToken);
- // Add paragraph close token
- const closeToken = {
- type: 'paragraph_close',
- tag: 'p',
- nesting: -1,
- level: token.level,
- markup: '',
- info: '',
- meta: null,
- block: true,
- hidden: false
- };
- tokens.splice(i + 2, 0, closeToken);
- // Remove the original image token
- tokens.splice(i, 1);
- i--; // Adjust index since we removed a token
- }
- }
- }
- // Check for HTML tokens that might contain SVG or malicious content
- if (token.type === 'html_block' || token.type === 'html_inline') {
- const content = token.content;
- if (content) {
- // Check for SVG content
- const hasSVG = content.includes('<svg') ||
- content.includes('data:image/svg') ||
- content.includes('xlink:href') ||
- content.includes('<use') ||
- content.includes('<defs>');
-
- // Check for malicious img tags with SVG data URIs
- const hasMaliciousImg = content.includes('<img') &&
- (content.includes('data:image/svg') ||
- content.includes('src="data:image/svg'));
-
- // Check for base64 encoded SVG with script tags
- const hasBase64SVG = content.includes('data:image/svg+xml;base64,');
-
- if (hasSVG || hasMaliciousImg || hasBase64SVG) {
- if (process.env.DEBUG === 'true') {
- console.warn('Blocked potentially malicious SVG content in HTML:', content.substring(0, 100) + '...');
- }
-
- // Additional check for base64 encoded SVG with script tags
- if (hasBase64SVG) {
- try {
- const base64Match = content.match(/data:image\/svg\+xml;base64,([^"'\s]+)/);
- if (base64Match) {
- const decodedContent = atob(base64Match[1]);
- if (decodedContent.includes('<script') || decodedContent.includes('javascript:')) {
- if (process.env.DEBUG === 'true') {
- console.warn('Blocked SVG with embedded JavaScript in markdown');
- }
- }
- }
- } catch (e) {
- // If decoding fails, continue with blocking
- }
- }
-
- // Replace with warning
- token.type = 'paragraph_open';
- token.tag = 'p';
- token.nesting = 1;
- token.attrSet('style', 'color: red; background: #ffe6e6; padding: 8px; border: 1px solid #ff9999;');
- token.attrSet('title', 'Blocked potentially malicious SVG content');
- // Add warning text
- const warningToken = {
- type: 'text',
- content: '⚠️ Blocked potentially malicious SVG content for security reasons',
- level: token.level,
- markup: '',
- info: '',
- meta: null,
- block: true,
- hidden: false
- };
- // Insert warning token after the paragraph open
- tokens.splice(i + 1, 0, warningToken);
- // Add paragraph close token
- const closeToken = {
- type: 'paragraph_close',
- tag: 'p',
- nesting: -1,
- level: token.level,
- markup: '',
- info: '',
- meta: null,
- block: true,
- hidden: false
- };
- tokens.splice(i + 2, 0, closeToken);
- // Remove the original HTML token
- tokens.splice(i, 1);
- i--; // Adjust index since we removed a token
- }
- }
- }
- }
- });
- });
- // Try to fix Mermaid Diagram error: Maximum call stack size exceeded.
- // Added bigger text size for Diagram.
- // https://github.com/wekan/wekan/issues/4251
- // https://stackoverflow.com/questions/66825888/maximum-text-size-in-diagram-exceeded-mermaid-js
- // https://github.com/mermaid-js/mermaid/blob/74b1219d62dd76d98d60abeeb36d4520f64faceb/src/defaultConfig.js#L39
- // https://github.com/wekan/cli-table3
- // https://www.npmjs.com/package/@wekanteam/markdown-it-mermaid
- // https://github.com/wekan/markdown-it-mermaid
- //Markdown.use(markdownItMermaid,{
- // maxTextSize: 200000,
- //});
- if (Package.ui) {
- const Template = Package.templating.Template;
- const UI = Package.ui.UI;
- const HTML = Package.htmljs.HTML;
- const Blaze = Package.blaze.Blaze; // implied by `ui`
- UI.registerHelper('markdown', new Template('markdown', function () {
- const self = this;
- let text = '';
- if (self.templateContentBlock) {
- text = Blaze._toText(self.templateContentBlock, HTML.TEXTMODE.STRING);
- }
- if (text.includes("[]")) {
- // Prevent hiding info: https://wekan.github.io/hall-of-fame/invisiblebleed/
- // If markdown link does not have description, do not render markdown, instead show all of markdown source code using preformatted text.
- // Also show html comments.
- return HTML.Raw('<pre style="background-color: red;" title="Warning! Hidden markdown link description!" aria-label="Warning! Hidden markdown link description!">' + DOMPurify.sanitize(text.replace('<!--', '<!--').replace('-->', '-->'), getSecureDOMPurifyConfig()) + '</pre>');
- } else {
- // Prevent hiding info: https://wekan.github.io/hall-of-fame/invisiblebleed/
- // If text does not have hidden markdown link, render all markdown.
- // Also show html comments.
- const renderedMarkdown = Markdown.render(text).replace('<!--', '<font color="red" title="Warning! Hidden HTML comment!" aria-label="Warning! Hidden HTML comment!"><!--</font>').replace('-->', '<font color="red" title="Warning! Hidden HTML comment!" aria-label="Warning! Hidden HTML comment!">--></font>');
- const sanitized = DOMPurify.sanitize(renderedMarkdown, getSecureDOMPurifyConfig());
- return HTML.Raw(sanitized);
- }
- }));
- }
|