mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 09:35:49 +02:00
Render markdown on the client side and use DOMPurify to prevent XSS.,
The parsedown parser has problems with links in <>, so we use marked.js now which is more conform with (GFM) CommonMark and offers more feautures. Also with the usage of DOMPurify you can now use every HTML tag in Markdown without need to worry about XSS.
This commit is contained in:
parent
7ec406d4a1
commit
be8f074ca5
9 changed files with 93 additions and 32 deletions
|
@ -795,3 +795,10 @@ table.dataTable {
|
||||||
max-width: 35%;
|
max-width: 35%;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown blockquote {
|
||||||
|
padding: 10px 10px;
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 17.5px;
|
||||||
|
border-left: 5px solid #eee;
|
||||||
|
}
|
||||||
|
|
|
@ -52,7 +52,10 @@ require('bootstrap-fileinput');
|
||||||
|
|
||||||
require('./datatables.js');
|
require('./datatables.js');
|
||||||
|
|
||||||
window.bootbox = require('bootbox')
|
window.bootbox = require('bootbox');
|
||||||
|
|
||||||
|
require("marked");
|
||||||
|
window.DOMPurify = require("dompurify");
|
||||||
|
|
||||||
// Includes required for tag input
|
// Includes required for tag input
|
||||||
require('./tagsinput.js');
|
require('./tagsinput.js');
|
||||||
|
|
|
@ -30,6 +30,8 @@
|
||||||
|
|
||||||
import {ajaxUI} from "./ajax_ui";
|
import {ajaxUI} from "./ajax_ui";
|
||||||
import "bootbox";
|
import "bootbox";
|
||||||
|
import "marked";
|
||||||
|
import * as marked from "marked";
|
||||||
|
|
||||||
/************************************
|
/************************************
|
||||||
*
|
*
|
||||||
|
@ -202,11 +204,11 @@ $(document).on("ajaxUI:start ajaxUI:reload", function() {
|
||||||
message: message,
|
message: message,
|
||||||
title: title,
|
title: title,
|
||||||
callback: function(result) {
|
callback: function(result) {
|
||||||
//If the dialog was confirmed, then submit the form.
|
//If the dialog was confirmed, then submit the form.
|
||||||
if(result) {
|
if(result) {
|
||||||
ajaxUI.submitForm(form);
|
ajaxUI.submitForm(form);
|
||||||
}
|
}
|
||||||
}});
|
}});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
@ -275,7 +277,7 @@ $(document).on("ajaxUI:reload ajaxUI:start", function () {
|
||||||
if (location.hash) {
|
if (location.hash) {
|
||||||
$activeTab = $('a[href=\'' + location.hash + '\']');
|
$activeTab = $('a[href=\'' + location.hash + '\']');
|
||||||
} else if(localStorage.getItem('activeTab')) {
|
} else if(localStorage.getItem('activeTab')) {
|
||||||
$activeTab = $('a[href="' + localStorage.getItem('activeTab') + '"]');
|
$activeTab = $('a[href="' + localStorage.getItem('activeTab') + '"]');
|
||||||
}
|
}
|
||||||
|
|
||||||
if($activeTab) {
|
if($activeTab) {
|
||||||
|
@ -372,20 +374,49 @@ $(document).on("ajaxUI:reload ajaxUI:start attachment:create", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
$(this).typeahead({
|
$(this).typeahead({
|
||||||
hint: true,
|
hint: true,
|
||||||
highlight: true,
|
highlight: true,
|
||||||
minLength: 1
|
minLength: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'states',
|
name: 'states',
|
||||||
source: engine
|
source: engine
|
||||||
});
|
});
|
||||||
|
|
||||||
//Make the typeahead input fill the container (remove block-inline attr)
|
//Make the typeahead input fill the container (remove block-inline attr)
|
||||||
$(this).parent(".twitter-typeahead").css('display', 'block');
|
$(this).parent(".twitter-typeahead").css('display', 'block');
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).on("ajaxUI:start", function () {
|
||||||
|
function decodeHTML(html) {
|
||||||
|
var txt = document.createElement('textarea');
|
||||||
|
txt.innerHTML = html;
|
||||||
|
return txt.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseMarkdown() {
|
||||||
|
$('.markdown').each(function() {
|
||||||
|
let unescaped = marked(decodeHTML( $(this).data('markdown')));
|
||||||
|
//@ts-ignore
|
||||||
|
let escaped = DOMPurify.sanitize(unescaped);
|
||||||
|
$(this).html(escaped);
|
||||||
|
//Remove markdown from DOM
|
||||||
|
$(this).removeAttr('data-markdown');
|
||||||
|
|
||||||
|
//Make all links external
|
||||||
|
$('a', this).addClass('link-external').attr('target', '_blank');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Configure markdown
|
||||||
|
marked.setOptions({
|
||||||
|
gfm: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
parseMarkdown();
|
||||||
|
$(document).on("ajaxUI:reload", parseMarkdown);
|
||||||
|
});
|
||||||
|
|
||||||
//Need for proper body padding, with every navbar height
|
//Need for proper body padding, with every navbar height
|
||||||
$(window).resize(function () {
|
$(window).resize(function () {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"typeRoots": ["../node_modules"],
|
"typeRoots": ["../node_modules"],
|
||||||
"types": ["jquery", "bootstrap", "jquery.form", "bootstrap-treeview", "bootbox", "typeahead"]
|
"types": ["jquery", "bootstrap", "jquery.form", "bootstrap-treeview", "bootbox", "typeahead", "marked"]
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"@types/bootstrap-treeview": "^1.20.0",
|
"@types/bootstrap-treeview": "^1.20.0",
|
||||||
"@types/jquery": "^3.3.29",
|
"@types/jquery": "^3.3.29",
|
||||||
"@types/jquery.form": "^3.26.30",
|
"@types/jquery.form": "^3.26.30",
|
||||||
|
"@types/marked": "^0.6.5",
|
||||||
"@types/typeahead": "^0.11.32",
|
"@types/typeahead": "^0.11.32",
|
||||||
"bootbox": "^5.1.0",
|
"bootbox": "^5.1.0",
|
||||||
"bootstrap-fileinput": "^5.0.1",
|
"bootstrap-fileinput": "^5.0.1",
|
||||||
|
@ -32,8 +33,10 @@
|
||||||
"datatables.net-colreorder-bs4": "^1.5.1",
|
"datatables.net-colreorder-bs4": "^1.5.1",
|
||||||
"datatables.net-fixedheader-bs4": "^3.1.5",
|
"datatables.net-fixedheader-bs4": "^3.1.5",
|
||||||
"datatables.net-select-bs4": "^1.2.7",
|
"datatables.net-select-bs4": "^1.2.7",
|
||||||
|
"dompurify": "^2.0.6",
|
||||||
"jquery-form": "^4.2.2",
|
"jquery-form": "^4.2.2",
|
||||||
"jszip": "^3.2.0",
|
"jszip": "^3.2.0",
|
||||||
|
"marked": "^0.7.0",
|
||||||
"patternfly-bootstrap-treeview": "^2.1.8",
|
"patternfly-bootstrap-treeview": "^2.1.8",
|
||||||
"pdfmake": "^0.1.53",
|
"pdfmake": "^0.1.53",
|
||||||
"ts-loader": "^5.3.3",
|
"ts-loader": "^5.3.3",
|
||||||
|
|
2
public/ckeditor/plugins/markdown/js/purify.min.js
vendored
Normal file
2
public/ckeditor/plugins/markdown/js/purify.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -26,22 +26,11 @@
|
||||||
|
|
||||||
var unchangedData = null;
|
var unchangedData = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove html tags from given string.
|
|
||||||
* Taken from here: https://stackoverflow.com/questions/822452/strip-html-from-text-javascript/47140708#47140708
|
|
||||||
* @param html
|
|
||||||
* @returns {string | string}
|
|
||||||
*/
|
|
||||||
function stripHtml(html)
|
|
||||||
{
|
|
||||||
var tmp = document.createElement("DIV");
|
|
||||||
tmp.innerHTML = html;
|
|
||||||
return tmp.textContent || tmp.innerText || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function overrideDataProcessor(editor)
|
function overrideDataProcessor(editor)
|
||||||
{
|
{
|
||||||
|
//Both showdown and DOMPurify must be loaded
|
||||||
if(typeof(showdown) == 'undefined') return;
|
if(typeof(showdown) == 'undefined') return;
|
||||||
|
if (typeof(DOMPurify) == 'undefined') return;
|
||||||
|
|
||||||
var converter = new showdown.Converter();
|
var converter = new showdown.Converter();
|
||||||
//Set some useful options on Showdown
|
//Set some useful options on Showdown
|
||||||
|
@ -67,7 +56,7 @@
|
||||||
//Strip html tags from data.
|
//Strip html tags from data.
|
||||||
//This is useful, to convert unsupported HTML feauters to plain text and adds an basic XSS protection
|
//This is useful, to convert unsupported HTML feauters to plain text and adds an basic XSS protection
|
||||||
//The HTML is inside an iframe so an XSS attack can not do much harm.
|
//The HTML is inside an iframe so an XSS attack can not do much harm.
|
||||||
data = stripHtml(data);
|
data = DOMPurify.sanitize(data);
|
||||||
|
|
||||||
return tmp = converter.makeHtml(data);
|
return tmp = converter.makeHtml(data);
|
||||||
},
|
},
|
||||||
|
@ -149,6 +138,12 @@
|
||||||
editor.setData(unchangedData);
|
editor.setData(unchangedData);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof(DOMPurify) == 'undefined') {
|
||||||
|
CKEDITOR.scriptLoader.load(rootPath + 'js/purify.min.js', function() {
|
||||||
|
overrideDataProcessor(editor);
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
afterInit: function( editor ) {
|
afterInit: function( editor ) {
|
||||||
|
|
|
@ -67,8 +67,13 @@ class MarkdownParser
|
||||||
*/
|
*/
|
||||||
public function parse(string $markdown, bool $inline_mode = false) : string
|
public function parse(string $markdown, bool $inline_mode = false) : string
|
||||||
{
|
{
|
||||||
|
return sprintf(
|
||||||
|
'<div class="markdown" data-markdown="%s">Markdown loading...</div>',
|
||||||
|
htmlspecialchars($markdown)
|
||||||
|
);
|
||||||
|
|
||||||
//Generate key
|
//Generate key
|
||||||
if ($inline_mode) {
|
/*if ($inline_mode) {
|
||||||
$key = 'markdown_i_' . md5($markdown);
|
$key = 'markdown_i_' . md5($markdown);
|
||||||
} else {
|
} else {
|
||||||
$key = 'markdown_' . md5($markdown);
|
$key = 'markdown_' . md5($markdown);
|
||||||
|
@ -82,6 +87,6 @@ class MarkdownParser
|
||||||
}
|
}
|
||||||
|
|
||||||
return '<div class="markdown">' . $this->parsedown->text($markdown) . '</div>';
|
return '<div class="markdown">' . $this->parsedown->text($markdown) . '</div>';
|
||||||
});
|
});*/
|
||||||
}
|
}
|
||||||
}
|
}
|
15
yarn.lock
15
yarn.lock
|
@ -739,6 +739,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/sizzle" "*"
|
"@types/sizzle" "*"
|
||||||
|
|
||||||
|
"@types/marked@^0.6.5":
|
||||||
|
version "0.6.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.6.5.tgz#3cf2a56ef615dad24aaf99784ef90a9eba4e29d8"
|
||||||
|
integrity sha512-6kBKf64aVfx93UJrcyEZ+OBM5nGv4RLsI6sR1Ar34bpgvGVRoyTgpxn4ZmtxOM5aDTAaaznYuYUH8bUX3Nk3YA==
|
||||||
|
|
||||||
"@types/minimatch@*":
|
"@types/minimatch@*":
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||||
|
@ -2538,6 +2543,11 @@ domhandler@^2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "1"
|
domelementtype "1"
|
||||||
|
|
||||||
|
dompurify@^2.0.6:
|
||||||
|
version "2.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.0.6.tgz#0a4196c211ce00e848240e52b1d49261af12a3be"
|
||||||
|
integrity sha512-1+AOpCYIKoLER/Ykd/Q/i11slhYy6T29/mmDrTsALH0JcMPB0pt9++8eDTGT70tv6ofmmeptrdqzZpmjMfFLRg==
|
||||||
|
|
||||||
domutils@1.5.1:
|
domutils@1.5.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
|
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
|
||||||
|
@ -4393,6 +4403,11 @@ map-visit@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
object-visit "^1.0.0"
|
object-visit "^1.0.0"
|
||||||
|
|
||||||
|
marked@^0.7.0:
|
||||||
|
version "0.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/marked/-/marked-0.7.0.tgz#b64201f051d271b1edc10a04d1ae9b74bb8e5c0e"
|
||||||
|
integrity sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==
|
||||||
|
|
||||||
md5.js@^1.3.4:
|
md5.js@^1.3.4:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue