mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
html sanitize imported notes, #1137
This commit is contained in:
parent
51f094f87f
commit
5e18e7dc67
55
db/migrations/0160__attr_def_short.js
Normal file
55
db/migrations/0160__attr_def_short.js
Normal file
@ -0,0 +1,55 @@
|
||||
const sql = require('../../src/services/sql');
|
||||
|
||||
module.exports = () => {
|
||||
for (const attr of sql.getRows("SELECT * FROM attributes WHERE name LIKE 'label:%'")) {
|
||||
const obj = JSON.parse(attr.value);
|
||||
|
||||
const tokens = [];
|
||||
|
||||
if (obj.isPromoted) {
|
||||
tokens.push('promoted');
|
||||
}
|
||||
|
||||
if (obj.labelType) {
|
||||
tokens.push(obj.labelType);
|
||||
}
|
||||
|
||||
if (obj.multiplicityType === 'singlevalue') {
|
||||
tokens.push('single');
|
||||
} else if (obj.multiplicityType === 'multivalue') {
|
||||
tokens.push('multi');
|
||||
}
|
||||
|
||||
if (obj.numberPrecision) {
|
||||
tokens.push('precision='+obj.numberPrecision);
|
||||
}
|
||||
|
||||
const newValue = tokens.join(',');
|
||||
|
||||
sql.execute('UPDATE attributes SET value = ? WHERE attributeId = ?', [newValue, attr.attributeId]);
|
||||
}
|
||||
|
||||
for (const attr of sql.getRows("SELECT * FROM attributes WHERE name LIKE 'relation:%'")) {
|
||||
const obj = JSON.parse(attr.value);
|
||||
|
||||
const tokens = [];
|
||||
|
||||
if (obj.isPromoted) {
|
||||
tokens.push('promoted');
|
||||
}
|
||||
|
||||
if (obj.inverseRelation) {
|
||||
tokens.push('inverse=' + obj.inverseRelation);
|
||||
}
|
||||
|
||||
if (obj.multiplicityType === 'singlevalue') {
|
||||
tokens.push('single');
|
||||
} else if (obj.multiplicityType === 'multivalue') {
|
||||
tokens.push('multi');
|
||||
}
|
||||
|
||||
const newValue = tokens.join(',');
|
||||
|
||||
sql.execute('UPDATE attributes SET value = ? WHERE attributeId = ?', [newValue, attr.attributeId]);
|
||||
}
|
||||
};
|
4432
package-lock.json
generated
4432
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -50,7 +50,7 @@
|
||||
"image-type": "4.1.0",
|
||||
"ini": "1.3.5",
|
||||
"is-svg": "4.2.1",
|
||||
"jimp": "0.13.0",
|
||||
"jimp": "0.14.0",
|
||||
"mime-types": "2.1.27",
|
||||
"multer": "1.4.2",
|
||||
"node-abi": "2.18.0",
|
||||
@ -60,6 +60,7 @@
|
||||
"rcedit": "2.2.0",
|
||||
"rimraf": "3.0.2",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"sanitize-html": "^1.27.0",
|
||||
"sax": "1.2.4",
|
||||
"semver": "7.3.2",
|
||||
"serve-favicon": "2.5.0",
|
||||
|
@ -5,6 +5,7 @@ import treeService from "../../services/tree.js";
|
||||
const TPL = `
|
||||
<div class="note-detail-readonly-text note-detail-printable">
|
||||
<style>
|
||||
/* h1 should not be used at all since semantically that's a note title */
|
||||
.note-detail-readonly-text h1 { font-size: 2.0em; }
|
||||
.note-detail-readonly-text h2 { font-size: 1.8em; }
|
||||
.note-detail-readonly-text h3 { font-size: 1.6em; }
|
||||
|
@ -4,7 +4,7 @@ const build = require('./build');
|
||||
const packageJson = require('../../package');
|
||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
||||
|
||||
const APP_DB_VERSION = 159;
|
||||
const APP_DB_VERSION = 160;
|
||||
const SYNC_VERSION = 14;
|
||||
const CLIPPER_PROTOCOL_VERSION = "1.0";
|
||||
|
||||
|
29
src/services/html_sanitizer.js
Normal file
29
src/services/html_sanitizer.js
Normal file
@ -0,0 +1,29 @@
|
||||
const sanitizeHtml = require('sanitize-html');
|
||||
|
||||
// intended mainly as protection against XSS via import
|
||||
// secondarily it (partly) protects against "CSS takeover"
|
||||
function sanitize(dirtyHtml) {
|
||||
return sanitizeHtml(dirtyHtml, {
|
||||
allowedTags: [
|
||||
// h1 is removed since that should be note's title
|
||||
'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
|
||||
'li', 'b', 'i', 'strong', 'em', 'strike', 'abbr', 'code', 'hr', 'br', 'div',
|
||||
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'section', 'figure', 'span',
|
||||
'label', 'input'
|
||||
],
|
||||
allowedAttributes: {
|
||||
'a': [ 'href', 'class' ],
|
||||
'img': [ 'src' ],
|
||||
'section': [ 'class', 'data-note-id' ],
|
||||
'figure': [ 'class' ],
|
||||
'span': [ 'class', 'style' ],
|
||||
'label': [ 'class' ],
|
||||
'input': [ 'class', 'type', 'disabled' ],
|
||||
'code': [ 'class' ]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sanitize
|
||||
};
|
@ -7,6 +7,7 @@ const sql = require("../sql");
|
||||
const noteService = require("../notes");
|
||||
const imageService = require("../image");
|
||||
const protectedSessionService = require('../protected_session');
|
||||
const htmlSanitizer = require("../html_sanitizer");
|
||||
|
||||
// date format is e.g. 20181121T193703Z
|
||||
function parseDate(text) {
|
||||
@ -71,6 +72,8 @@ function importEnex(taskContext, file, parentNote) {
|
||||
content = content.replace(/<\/ol>\s+<\/ol>/g, "</ol></li></ol>");
|
||||
content = content.replace(/<\/ol>\s+<li>/g, "</ol></li><li>");
|
||||
|
||||
content = htmlSanitizer.sanitize(content);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@ -295,6 +298,8 @@ function importEnex(taskContext, file, parentNote) {
|
||||
}
|
||||
}
|
||||
|
||||
content = htmlSanitizer.sanitize(content);
|
||||
|
||||
// save updated content with links to files/images
|
||||
noteEntity.setContent(content);
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
const noteService = require('../../services/notes');
|
||||
const parseString = require('xml2js').parseString;
|
||||
const protectedSessionService = require('../protected_session');
|
||||
const htmlSanitizer = require('../html_sanitizer');
|
||||
|
||||
/**
|
||||
* @param {TaskContext} taskContext
|
||||
@ -44,6 +45,8 @@ function importOpml(taskContext, fileBuffer, parentNote) {
|
||||
throw new Error("Unrecognized OPML version " + opmlVersion);
|
||||
}
|
||||
|
||||
content = htmlSanitizer.sanitize(content);
|
||||
|
||||
const {note} = noteService.createNewNote({
|
||||
parentNoteId,
|
||||
title,
|
||||
|
@ -6,6 +6,7 @@ const protectedSessionService = require('../protected_session');
|
||||
const commonmark = require('commonmark');
|
||||
const mimeService = require('./mime');
|
||||
const utils = require('../../services/utils');
|
||||
const htmlSanitizer = require('../html_sanitizer');
|
||||
|
||||
function importSingleFile(taskContext, file, parentNote) {
|
||||
const mime = mimeService.getMime(file.originalname) || file.mimetype;
|
||||
@ -122,7 +123,9 @@ function importMarkdown(taskContext, file, parentNote) {
|
||||
const writer = new commonmark.HtmlRenderer();
|
||||
|
||||
const parsed = reader.parse(markdownContent);
|
||||
const htmlContent = writer.render(parsed);
|
||||
let htmlContent = writer.render(parsed);
|
||||
|
||||
htmlContent = htmlSanitizer.sanitize(htmlContent);
|
||||
|
||||
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
|
||||
|
||||
@ -142,7 +145,9 @@ function importMarkdown(taskContext, file, parentNote) {
|
||||
|
||||
function importHtml(taskContext, file, parentNote) {
|
||||
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
|
||||
const content = file.buffer.toString("UTF-8");
|
||||
let content = file.buffer.toString("UTF-8");
|
||||
|
||||
content = htmlSanitizer.sanitize(content);
|
||||
|
||||
const {note} = noteService.createNewNote({
|
||||
parentNoteId: parentNote.noteId,
|
||||
|
@ -16,6 +16,7 @@ const protectedSessionService = require('../protected_session');
|
||||
const mimeService = require("./mime");
|
||||
const sql = require("../sql");
|
||||
const treeService = require("../tree");
|
||||
const htmlSanitizer = require("../html_sanitizer");
|
||||
|
||||
/**
|
||||
* @param {TaskContext} taskContext
|
||||
@ -255,6 +256,8 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
|
||||
return /^(?:[a-z]+:)?\/\//i.test(url);
|
||||
}
|
||||
|
||||
content = htmlSanitizer.sanitize(content);
|
||||
|
||||
content = content.replace(/<html.*<body[^>]*>/gis, "");
|
||||
content = content.replace(/<\/body>.*<\/html>/gis, "");
|
||||
|
||||
|
@ -14,6 +14,7 @@ const protectedSessionService = require('../protected_session');
|
||||
const mimeService = require("./mime");
|
||||
const treeService = require("../tree");
|
||||
const yauzl = require("yauzl");
|
||||
const htmlSanitizer = require('../html_sanitizer');
|
||||
|
||||
/**
|
||||
* @param {TaskContext} taskContext
|
||||
@ -269,6 +270,17 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
return /^(?:[a-z]+:)?\/\//i.test(url);
|
||||
}
|
||||
|
||||
content = content.replace(/<h1>([^<]*)<\/h1>/gi, (match, text) => {
|
||||
if (noteTitle.trim() === text.trim()) {
|
||||
return ""; // remove whole H1 tag
|
||||
}
|
||||
else {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
|
||||
content = htmlSanitizer.sanitize(content);
|
||||
|
||||
content = content.replace(/<html.*<body[^>]*>/gis, "");
|
||||
content = content.replace(/<\/body>.*<\/html>/gis, "");
|
||||
|
||||
@ -296,15 +308,6 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
return `href="#root/${targetNoteId}"`;
|
||||
});
|
||||
|
||||
content = content.replace(/<h1>([^<]*)<\/h1>/gi, (match, text) => {
|
||||
if (noteTitle.trim() === text.trim()) {
|
||||
return ""; // remove whole H1 tag
|
||||
}
|
||||
else {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
|
||||
if (noteMeta) {
|
||||
const includeNoteLinks = (noteMeta.attributes || [])
|
||||
.filter(attr => attr.type === 'relation' && attr.name === 'includeNoteLink');
|
||||
|
@ -12,6 +12,11 @@ class Attribute {
|
||||
this.type = row.type;
|
||||
/** @param {string} */
|
||||
this.name = row.name.toLowerCase();
|
||||
|
||||
if (typeof row.value !== 'string') {
|
||||
row.value = JSON.stringify(row.value);
|
||||
}
|
||||
|
||||
/** @param {string} */
|
||||
this.value = row.type === 'label' ? row.value.toLowerCase() : row.value;
|
||||
/** @param {boolean} */
|
||||
|
Loading…
x
Reference in New Issue
Block a user