2023-02-21 21:27:34 +02:00
import DOMPurify from 'dompurify' ;
2025-10-11 01:23:18 +03:00
import { getSecureDOMPurifyConfig } from './secureDOMPurify' ;
2025-10-10 22:16:47 +03:00
2020-09-11 02:04:17 +03:00
var Markdown = require ( 'markdown-it' ) ( {
html : true ,
linkify : true ,
2021-01-13 00:02:17 +02:00
typographer : true ,
breaks : true ,
2020-09-11 02:04:17 +03:00
} ) ;
2023-02-22 00:07:59 +02:00
//import markdownItMermaid from "@wekanteam/markdown-it-mermaid";
2021-03-04 16:30:06 +01:00
// Static URL Scheme Listing
var urlschemes = [
"aodroplink" ,
"thunderlink" ,
"cbthunderlink" ,
"onenote" ,
"file" ,
"abasurl" ,
"conisio" ,
"mailspring"
] ;
2021-10-26 01:50:28 +03:00
2021-05-07 02:13:20 +03:00
// Better would be a field in the admin backend to set this dynamically
2021-03-04 16:30:06 +01:00
// 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');
2021-10-26 01:50:28 +03:00
2021-03-04 16:30:06 +01:00
// 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:' ) ;
}
2025-10-20 02:28:41 +03:00
// 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 ) ;
}
2024-01-23 23:32:46 +02:00
2025-10-20 02:28:41 +03:00
// 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 ) ;
}
2022-09-22 21:24:07 +03:00
2025-10-10 22:16:47 +03:00
// 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
}
}
}
2025-10-11 04:47:17 +03:00
// Check for HTML tokens that might contain SVG or malicious content
2025-10-10 22:16:47 +03:00
if ( token . type === 'html_block' || token . type === 'html_inline' ) {
const content = token . content ;
2025-10-11 04:47:17 +03:00
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
2025-10-10 22:16:47 +03:00
}
}
}
}
} ) ;
} ) ;
2022-09-22 21:24:07 +03:00
// 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
2023-02-22 00:07:59 +02:00
//Markdown.use(markdownItMermaid,{
// maxTextSize: 200000,
//});
2020-06-06 11:26:06 +02:00
if ( Package . ui ) {
2021-01-13 00:02:17 +02:00
const Template = Package . templating . Template ;
const UI = Package . ui . UI ;
const HTML = Package . htmljs . HTML ;
const Blaze = Package . blaze . Blaze ; // implied by `ui`
2020-06-06 11:26:06 +02:00
2021-01-13 00:02:17 +02:00
UI . registerHelper ( 'markdown' , new Template ( 'markdown' , function ( ) {
const self = this ;
let text = '' ;
if ( self . templateContentBlock ) {
text = Blaze . _toText ( self . templateContentBlock , HTML . TEXTMODE . STRING ) ;
}
2025-10-20 01:01:55 +03:00
if ( text . includes ( "[]" ) ) {
2023-06-10 05:12:17 +03:00
// 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.
2025-10-10 22:16:47 +03:00
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>' ) ;
2023-06-09 20:08:25 +03:00
} else {
2023-06-10 05:12:17 +03:00
// 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.
2025-10-20 01:01:55 +03:00
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 ) ;
2023-06-09 20:08:25 +03:00
}
2021-01-13 00:02:17 +02:00
} ) ) ;
2020-06-06 11:26:06 +02:00
}