mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
All: Security: Fixed potential Arbitrary File Read via XSS
This commit is contained in:
parent
06d807d9ff
commit
3db47b575b
@ -55,3 +55,4 @@ ElectronClient/app/gui/ShareNoteDialog.js
|
|||||||
ReactNativeClient/lib/JoplinServerApi.js
|
ReactNativeClient/lib/JoplinServerApi.js
|
||||||
ReactNativeClient/PluginAssetsLoader.js
|
ReactNativeClient/PluginAssetsLoader.js
|
||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
|
||||||
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -51,3 +51,4 @@ ElectronClient/app/gui/ShareNoteDialog.js
|
|||||||
ReactNativeClient/lib/JoplinServerApi.js
|
ReactNativeClient/lib/JoplinServerApi.js
|
||||||
ReactNativeClient/PluginAssetsLoader.js
|
ReactNativeClient/PluginAssetsLoader.js
|
||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
|
||||||
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
|
||||||
|
14
CliClient/package-lock.json
generated
14
CliClient/package-lock.json
generated
@ -2667,6 +2667,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
|
||||||
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
|
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
|
||||||
},
|
},
|
||||||
|
"memory-cache": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz",
|
||||||
|
"integrity": "sha1-eJCwHVLADI68nVM+H46xfjA0hxo="
|
||||||
|
},
|
||||||
"micromatch": {
|
"micromatch": {
|
||||||
"version": "3.1.10",
|
"version": "3.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
||||||
@ -2920,6 +2925,15 @@
|
|||||||
"is-stream": "^1.0.1"
|
"is-stream": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node-html-parser": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-qHwPdGyGr9pOZBoSgUOuNPG20QYZVN00lFcxKQgjPUODSxVH7obQeLVVawa3B4cfSNtLIeczSzoy/xYA8XG5WQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"he": "1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node-persist": {
|
"node-persist": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-persist/-/node-persist-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-persist/-/node-persist-2.1.0.tgz",
|
||||||
|
@ -72,6 +72,7 @@
|
|||||||
"markdown-it-toc-done-right": "^4.1.0",
|
"markdown-it-toc-done-right": "^4.1.0",
|
||||||
"md5": "^2.2.1",
|
"md5": "^2.2.1",
|
||||||
"md5-file": "^4.0.0",
|
"md5-file": "^4.0.0",
|
||||||
|
"memory-cache": "^0.2.0",
|
||||||
"mime": "^2.0.3",
|
"mime": "^2.0.3",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"multiparty": "^4.2.1",
|
"multiparty": "^4.2.1",
|
||||||
@ -104,7 +105,8 @@
|
|||||||
"valid-url": "^1.0.9",
|
"valid-url": "^1.0.9",
|
||||||
"word-wrap": "^1.2.3",
|
"word-wrap": "^1.2.3",
|
||||||
"xml2js": "^0.4.19",
|
"xml2js": "^0.4.19",
|
||||||
"yargs-parser": "^7.0.0"
|
"yargs-parser": "^7.0.0",
|
||||||
|
"node-html-parser": "^1.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jasmine": "^3.5.0"
|
"jasmine": "^3.5.0"
|
||||||
|
79
CliClient/tests/HtmlToHtml.js
Normal file
79
CliClient/tests/HtmlToHtml.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
|
||||||
|
require('app-module-path').addPath(__dirname);
|
||||||
|
|
||||||
|
const os = require('os');
|
||||||
|
const { time } = require('lib/time-utils.js');
|
||||||
|
const { filename } = require('lib/path-utils.js');
|
||||||
|
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||||
|
const Folder = require('lib/models/Folder.js');
|
||||||
|
const Note = require('lib/models/Note.js');
|
||||||
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
|
const { shim } = require('lib/shim');
|
||||||
|
const HtmlToHtml = require('lib/joplin-renderer/HtmlToHtml');
|
||||||
|
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
|
||||||
|
|
||||||
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 60 * 1000; // Can run for a while since everything is in the same test unit
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('HtmlToHtml', function() {
|
||||||
|
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
await setupDatabaseAndSynchronizer(1);
|
||||||
|
await switchClient(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert from Html to Html', asyncTest(async () => {
|
||||||
|
const basePath = `${__dirname}/html_to_html`;
|
||||||
|
const files = await shim.fsDriver().readDirStats(basePath);
|
||||||
|
const htmlToHtml = new HtmlToHtml();
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const htmlSourceFilename = files[i].path;
|
||||||
|
if (htmlSourceFilename.indexOf('.src.html') < 0) continue;
|
||||||
|
|
||||||
|
const htmlSourceFilePath = `${basePath}/${htmlSourceFilename}`;
|
||||||
|
const htmlDestPath = `${basePath}/${filename(filename(htmlSourceFilePath))}.dest.html`;
|
||||||
|
|
||||||
|
// if (htmlSourceFilename !== 'table_with_header.html') continue;
|
||||||
|
|
||||||
|
const htmlToHtmlOptions = {
|
||||||
|
bodyOnly: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const sourceHtml = await shim.fsDriver().readFile(htmlSourceFilePath);
|
||||||
|
let expectedHtml = await shim.fsDriver().readFile(htmlDestPath);
|
||||||
|
|
||||||
|
const result = await htmlToHtml.render(sourceHtml, null, htmlToHtmlOptions);
|
||||||
|
let actualHtml = result.html;
|
||||||
|
|
||||||
|
if (os.EOL === '\r\n') {
|
||||||
|
expectedHtml = expectedHtml.replace(/\r\n/g, '\n');
|
||||||
|
actualHtml = actualHtml.replace(/\r\n/g, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actualHtml !== expectedHtml) {
|
||||||
|
console.info('');
|
||||||
|
console.info(`Error converting file: ${htmlSourceFilename}`);
|
||||||
|
console.info('--------------------------------- Got:');
|
||||||
|
console.info(actualHtml);
|
||||||
|
console.info('--------------------------------- Raw:');
|
||||||
|
console.info(actualHtml.split('\n'));
|
||||||
|
console.info('--------------------------------- Expected:');
|
||||||
|
console.info(expectedHtml.split('\n'));
|
||||||
|
console.info('--------------------------------------------');
|
||||||
|
console.info('');
|
||||||
|
|
||||||
|
expect(false).toBe(true);
|
||||||
|
// return;
|
||||||
|
} else {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
79
CliClient/tests/MdToHtml.js
Normal file
79
CliClient/tests/MdToHtml.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
|
||||||
|
require('app-module-path').addPath(__dirname);
|
||||||
|
|
||||||
|
const os = require('os');
|
||||||
|
const { time } = require('lib/time-utils.js');
|
||||||
|
const { filename } = require('lib/path-utils.js');
|
||||||
|
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||||
|
const Folder = require('lib/models/Folder.js');
|
||||||
|
const Note = require('lib/models/Note.js');
|
||||||
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
|
const { shim } = require('lib/shim');
|
||||||
|
const MdToHtml = require('lib/joplin-renderer/MdToHtml');
|
||||||
|
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
|
||||||
|
|
||||||
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 60 * 1000; // Can run for a while since everything is in the same test unit
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('MdToHtml', function() {
|
||||||
|
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
await setupDatabaseAndSynchronizer(1);
|
||||||
|
await switchClient(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert from Markdown to Html', asyncTest(async () => {
|
||||||
|
const basePath = `${__dirname}/md_to_html`;
|
||||||
|
const files = await shim.fsDriver().readDirStats(basePath);
|
||||||
|
const mdToHtml = new MdToHtml();
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const mdFilename = files[i].path;
|
||||||
|
if (mdFilename.indexOf('.md') < 0) continue;
|
||||||
|
|
||||||
|
const mdFilePath = `${basePath}/${mdFilename}`;
|
||||||
|
const htmlPath = `${basePath}/${filename(mdFilePath)}.html`;
|
||||||
|
|
||||||
|
// if (mdFilename !== 'table_with_header.html') continue;
|
||||||
|
|
||||||
|
const mdToHtmlOptions = {
|
||||||
|
bodyOnly: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const markdown = await shim.fsDriver().readFile(mdFilePath);
|
||||||
|
let expectedHtml = await shim.fsDriver().readFile(htmlPath);
|
||||||
|
|
||||||
|
const result = await mdToHtml.render(markdown, null, mdToHtmlOptions);
|
||||||
|
let actualHtml = result.html;
|
||||||
|
|
||||||
|
if (os.EOL === '\r\n') {
|
||||||
|
expectedHtml = expectedHtml.replace(/\r\n/g, '\n');
|
||||||
|
actualHtml = actualHtml.replace(/\r\n/g, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actualHtml !== expectedHtml) {
|
||||||
|
console.info('');
|
||||||
|
console.info(`Error converting file: ${mdFilename}`);
|
||||||
|
console.info('--------------------------------- Got:');
|
||||||
|
console.info(actualHtml);
|
||||||
|
console.info('--------------------------------- Raw:');
|
||||||
|
console.info(actualHtml.split('\n'));
|
||||||
|
console.info('--------------------------------- Expected:');
|
||||||
|
console.info(expectedHtml.split('\n'));
|
||||||
|
console.info('--------------------------------------------');
|
||||||
|
console.info('');
|
||||||
|
|
||||||
|
expect(false).toBe(true);
|
||||||
|
// return;
|
||||||
|
} else {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
2
CliClient/tests/html_to_html/sanitize.dest.html
Normal file
2
CliClient/tests/html_to_html/sanitize.dest.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<img src onerror="" />
|
||||||
|
<img src onerror="" />
|
3
CliClient/tests/html_to_html/sanitize.src.html
Normal file
3
CliClient/tests/html_to_html/sanitize.src.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<img src="" onerror="alert('ohno')"/>
|
||||||
|
<img src=""
|
||||||
|
onerror="alert('ohno')"/>
|
2
CliClient/tests/md_to_html/sanitize.html
Normal file
2
CliClient/tests/md_to_html/sanitize.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<img src onerror="" />
|
||||||
|
<img src onerror="" />
|
3
CliClient/tests/md_to_html/sanitize.md
Normal file
3
CliClient/tests/md_to_html/sanitize.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<img src="" onerror="alert('ohno')"/>
|
||||||
|
<img src=""
|
||||||
|
onerror="alert('ohno')"/>
|
@ -21,7 +21,7 @@
|
|||||||
function absoluteUrl(url) {
|
function absoluteUrl(url) {
|
||||||
if (!url) return url;
|
if (!url) return url;
|
||||||
const protocol = url.toLowerCase().split(':')[0];
|
const protocol = url.toLowerCase().split(':')[0];
|
||||||
if (['http', 'https', 'file'].indexOf(protocol) >= 0) return url;
|
if (['http', 'https', 'file', 'data'].indexOf(protocol) >= 0) return url;
|
||||||
|
|
||||||
if (url.indexOf('//') === 0) {
|
if (url.indexOf('//') === 0) {
|
||||||
return location.protocol + url;
|
return location.protocol + url;
|
||||||
|
156
ElectronClient/app/package-lock.json
generated
156
ElectronClient/app/package-lock.json
generated
@ -2233,10 +2233,68 @@
|
|||||||
"minimist": "^1.1.1"
|
"minimist": "^1.1.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"domelementtype": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
|
||||||
|
},
|
||||||
|
"domhandler": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
|
||||||
|
"requires": {
|
||||||
|
"domelementtype": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domutils": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
|
||||||
|
"requires": {
|
||||||
|
"dom-serializer": "0",
|
||||||
|
"domelementtype": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"htmlparser2": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
||||||
|
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
|
||||||
|
"requires": {
|
||||||
|
"domelementtype": "^1.3.1",
|
||||||
|
"domhandler": "^2.3.0",
|
||||||
|
"domutils": "^1.5.1",
|
||||||
|
"entities": "^1.1.1",
|
||||||
|
"inherits": "^2.0.1",
|
||||||
|
"readable-stream": "^3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
||||||
|
},
|
||||||
|
"readable-stream": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||||
|
"requires": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe-buffer": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
|
||||||
|
},
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "~5.2.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3158,19 +3216,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dom-serializer": {
|
"dom-serializer": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
|
||||||
"integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==",
|
"integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"domelementtype": "^2.0.1",
|
"domelementtype": "^2.0.1",
|
||||||
"entities": "^2.0.0"
|
"entities": "^2.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"domelementtype": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
|
|
||||||
},
|
|
||||||
"entities": {
|
"entities": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
|
||||||
@ -3179,9 +3232,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domelementtype": {
|
"domelementtype": {
|
||||||
"version": "1.3.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
|
||||||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
|
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
|
||||||
},
|
},
|
||||||
"domexception": {
|
"domexception": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
@ -3192,20 +3245,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domhandler": {
|
"domhandler": {
|
||||||
"version": "2.4.2",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz",
|
||||||
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
|
"integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"domelementtype": "1"
|
"domelementtype": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domutils": {
|
"domutils": {
|
||||||
"version": "1.7.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz",
|
||||||
"integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
|
"integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"dom-serializer": "0",
|
"dom-serializer": "^0.2.1",
|
||||||
"domelementtype": "1"
|
"domelementtype": "^2.0.1",
|
||||||
|
"domhandler": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dot-prop": {
|
"dot-prop": {
|
||||||
@ -4640,40 +4694,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"htmlparser2": {
|
"htmlparser2": {
|
||||||
"version": "3.10.1",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.0.0.tgz",
|
||||||
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
|
"integrity": "sha512-cChwXn5Vam57fyXajDtPXL1wTYc8JtLbr2TN76FYu05itVVVealxLowe2B3IEznJG4p9HAYn/0tJaRlGuEglFQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"domelementtype": "^1.3.1",
|
"domelementtype": "^2.0.1",
|
||||||
"domhandler": "^2.3.0",
|
"domhandler": "^3.0.0",
|
||||||
"domutils": "^1.5.1",
|
"domutils": "^2.0.0",
|
||||||
"entities": "^1.1.1",
|
"entities": "^2.0.0"
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"readable-stream": "^3.1.1"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"readable-stream": {
|
"entities": {
|
||||||
"version": "3.4.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
|
||||||
"integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
|
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
|
||||||
"requires": {
|
|
||||||
"inherits": "^2.0.3",
|
|
||||||
"string_decoder": "^1.1.1",
|
|
||||||
"util-deprecate": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"safe-buffer": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
|
|
||||||
},
|
|
||||||
"string_decoder": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
|
||||||
"requires": {
|
|
||||||
"safe-buffer": "~5.2.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -5582,6 +5616,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.4.tgz",
|
||||||
"integrity": "sha512-P0z5IeAH6qHHGkJIXWw0xC2HNEgkx/9uWWBQw64FJj3/ol14VYdfVGWWr0fXfjhhv3TKVIqUq65os6O4GUNksA=="
|
"integrity": "sha512-P0z5IeAH6qHHGkJIXWw0xC2HNEgkx/9uWWBQw64FJj3/ol14VYdfVGWWr0fXfjhhv3TKVIqUq65os6O4GUNksA=="
|
||||||
},
|
},
|
||||||
|
"memory-cache": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz",
|
||||||
|
"integrity": "sha1-eJCwHVLADI68nVM+H46xfjA0hxo="
|
||||||
|
},
|
||||||
"mermaid": {
|
"mermaid": {
|
||||||
"version": "8.4.6",
|
"version": "8.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.4.6.tgz",
|
||||||
@ -6183,6 +6222,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node-html-parser": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-qHwPdGyGr9pOZBoSgUOuNPG20QYZVN00lFcxKQgjPUODSxVH7obQeLVVawa3B4cfSNtLIeczSzoy/xYA8XG5WQ==",
|
||||||
|
"requires": {
|
||||||
|
"he": "1.1.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"he": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node-notifier": {
|
"node-notifier": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-6.0.0.tgz",
|
||||||
|
@ -131,11 +131,13 @@
|
|||||||
"markdown-it-toc-done-right": "^4.1.0",
|
"markdown-it-toc-done-right": "^4.1.0",
|
||||||
"md5": "^2.2.1",
|
"md5": "^2.2.1",
|
||||||
"md5-file": "^4.0.0",
|
"md5-file": "^4.0.0",
|
||||||
|
"memory-cache": "^0.2.0",
|
||||||
"mermaid": "^8.4.6",
|
"mermaid": "^8.4.6",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"multiparty": "^4.2.1",
|
"multiparty": "^4.2.1",
|
||||||
"mustache": "^3.0.1",
|
"mustache": "^3.0.1",
|
||||||
"node-fetch": "^1.7.3",
|
"node-fetch": "^1.7.3",
|
||||||
|
"node-html-parser": "^1.2.4",
|
||||||
"node-notifier": "^6.0.0",
|
"node-notifier": "^6.0.0",
|
||||||
"promise": "^8.0.1",
|
"promise": "^8.0.1",
|
||||||
"query-string": "^5.1.1",
|
"query-string": "^5.1.1",
|
||||||
|
@ -5,8 +5,8 @@ BUILD_DIR="$ROOT_DIR/app"
|
|||||||
|
|
||||||
rsync -a --delete "$ROOT_DIR/../ReactNativeClient/lib/" "$BUILD_DIR/lib/"
|
rsync -a --delete "$ROOT_DIR/../ReactNativeClient/lib/" "$BUILD_DIR/lib/"
|
||||||
|
|
||||||
cd "$ROOT_DIR/.."
|
# cd "$ROOT_DIR/.."
|
||||||
npm run tsc
|
# npm run tsc
|
||||||
|
|
||||||
cd "$BUILD_DIR"
|
cd "$BUILD_DIR"
|
||||||
npm run compile
|
npm run compile
|
||||||
|
@ -539,27 +539,6 @@ class BaseApplication {
|
|||||||
return `${os.homedir()}/.config/${Setting.value('appName')}`;
|
return `${os.homedir()}/.config/${Setting.value('appName')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async testing() {
|
|
||||||
const markdownUtils = require('lib/markdownUtils');
|
|
||||||
const ClipperServer = require('lib/ClipperServer');
|
|
||||||
const server = new ClipperServer();
|
|
||||||
const HtmlToMd = require('lib/HtmlToMd');
|
|
||||||
const service = new HtmlToMd();
|
|
||||||
const html = await shim.fsDriver().readFile('/mnt/d/test.html');
|
|
||||||
let markdown = service.parse(html, { baseUrl: 'https://duckduckgo.com/' });
|
|
||||||
console.info(markdown);
|
|
||||||
console.info('--------------------------------------------------');
|
|
||||||
|
|
||||||
const imageUrls = markdownUtils.extractImageUrls(markdown);
|
|
||||||
let result = await server.downloadImages_(imageUrls);
|
|
||||||
result = await server.createResourcesFromPaths_(result);
|
|
||||||
console.info(result);
|
|
||||||
markdown = server.replaceImageUrlsByResources_(markdown, result);
|
|
||||||
console.info('--------------------------------------------------');
|
|
||||||
console.info(markdown);
|
|
||||||
console.info('--------------------------------------------------');
|
|
||||||
}
|
|
||||||
|
|
||||||
async start(argv) {
|
async start(argv) {
|
||||||
let startFlags = await this.handleStartFlags_(argv);
|
let startFlags = await this.handleStartFlags_(argv);
|
||||||
|
|
||||||
|
@ -1,33 +1,50 @@
|
|||||||
const htmlUtils = require('./htmlUtils');
|
const htmlUtils = require('./htmlUtils');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const noteStyle = require('./noteStyle');
|
const noteStyle = require('./noteStyle');
|
||||||
|
const memoryCache = require('memory-cache');
|
||||||
|
const md5 = require('md5');
|
||||||
|
|
||||||
class HtmlToHtml {
|
class HtmlToHtml {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
this.resourceBaseUrl_ = 'resourceBaseUrl' in options ? options.resourceBaseUrl : null;
|
this.resourceBaseUrl_ = 'resourceBaseUrl' in options ? options.resourceBaseUrl : null;
|
||||||
this.ResourceModel_ = options.ResourceModel;
|
this.ResourceModel_ = options.ResourceModel;
|
||||||
|
this.cache_ = new memoryCache.Cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
render(markup, theme, options) {
|
async render(markup, theme, options) {
|
||||||
const html = htmlUtils.processImageTags(markup, data => {
|
const cacheKey = md5(escape(markup));
|
||||||
if (!data.src) return null;
|
let html = this.cache_.get(cacheKey);
|
||||||
|
|
||||||
const r = utils.imageReplacement(this.ResourceModel_, data.src, options.resources, this.resourceBaseUrl_);
|
if (!html) {
|
||||||
if (!r) return null;
|
html = htmlUtils.sanitizeHtml(markup);
|
||||||
|
|
||||||
if (typeof r === 'string') {
|
html = htmlUtils.processImageTags(html, data => {
|
||||||
return {
|
if (!data.src) return null;
|
||||||
type: 'replaceElement',
|
|
||||||
html: r,
|
const r = utils.imageReplacement(this.ResourceModel_, data.src, options.resources, this.resourceBaseUrl_);
|
||||||
};
|
if (!r) return null;
|
||||||
} else {
|
|
||||||
return {
|
if (typeof r === 'string') {
|
||||||
type: 'setAttributes',
|
return {
|
||||||
attrs: r,
|
type: 'replaceElement',
|
||||||
};
|
html: r,
|
||||||
}
|
};
|
||||||
});
|
} else {
|
||||||
|
return {
|
||||||
|
type: 'setAttributes',
|
||||||
|
attrs: r,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.bodyOnly) return {
|
||||||
|
html: html,
|
||||||
|
pluginAssets: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.cache_.put(cacheKey, html, 1000 * 60 * 10);
|
||||||
|
|
||||||
const cssStrings = noteStyle(theme, options);
|
const cssStrings = noteStyle(theme, options);
|
||||||
const styleHtml = `<style>${cssStrings.join('\n')}</style>`;
|
const styleHtml = `<style>${cssStrings.join('\n')}</style>`;
|
||||||
|
@ -33,7 +33,7 @@ class MarkupToHtml {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
render(markupLanguage, markup, theme, options) {
|
async render(markupLanguage, markup, theme, options) {
|
||||||
return this.renderer(markupLanguage).render(markup, theme, options);
|
return this.renderer(markupLanguage).render(markup, theme, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ const MarkdownIt = require('markdown-it');
|
|||||||
const md5 = require('md5');
|
const md5 = require('md5');
|
||||||
const noteStyle = require('./noteStyle');
|
const noteStyle = require('./noteStyle');
|
||||||
const { fileExtension } = require('./pathUtils');
|
const { fileExtension } = require('./pathUtils');
|
||||||
|
const memoryCache = require('memory-cache');
|
||||||
const rules = {
|
const rules = {
|
||||||
image: require('./MdToHtml/rules/image'),
|
image: require('./MdToHtml/rules/image'),
|
||||||
checkbox: require('./MdToHtml/rules/checkbox'),
|
checkbox: require('./MdToHtml/rules/checkbox'),
|
||||||
@ -12,6 +13,7 @@ const rules = {
|
|||||||
code_inline: require('./MdToHtml/rules/code_inline'),
|
code_inline: require('./MdToHtml/rules/code_inline'),
|
||||||
fountain: require('./MdToHtml/rules/fountain'),
|
fountain: require('./MdToHtml/rules/fountain'),
|
||||||
mermaid: require('./MdToHtml/rules/mermaid').default,
|
mermaid: require('./MdToHtml/rules/mermaid').default,
|
||||||
|
sanitize_html: require('./MdToHtml/rules/sanitize_html').default,
|
||||||
};
|
};
|
||||||
const setupLinkify = require('./MdToHtml/setupLinkify');
|
const setupLinkify = require('./MdToHtml/setupLinkify');
|
||||||
const hljs = require('highlight.js');
|
const hljs = require('highlight.js');
|
||||||
@ -50,6 +52,7 @@ class MdToHtml {
|
|||||||
this.cachedHighlightedCode_ = {};
|
this.cachedHighlightedCode_ = {};
|
||||||
this.ResourceModel_ = options.ResourceModel;
|
this.ResourceModel_ = options.ResourceModel;
|
||||||
this.pluginOptions_ = options.pluginOptions ? options.pluginOptions : {};
|
this.pluginOptions_ = options.pluginOptions ? options.pluginOptions : {};
|
||||||
|
this.contextCache_ = new memoryCache.Cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginOptions(name) {
|
pluginOptions(name) {
|
||||||
@ -106,6 +109,7 @@ class MdToHtml {
|
|||||||
|
|
||||||
async render(body, style = null, options = null) {
|
async render(body, style = null, options = null) {
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
|
if (!('bodyOnly' in options)) options.bodyOnly = false;
|
||||||
if (!options.postMessageSyntax) options.postMessageSyntax = 'postMessage';
|
if (!options.postMessageSyntax) options.postMessageSyntax = 'postMessage';
|
||||||
if (!options.paddingBottom) options.paddingBottom = '0';
|
if (!options.paddingBottom) options.paddingBottom = '0';
|
||||||
if (!options.highlightedKeywords) options.highlightedKeywords = [];
|
if (!options.highlightedKeywords) options.highlightedKeywords = [];
|
||||||
@ -129,6 +133,7 @@ class MdToHtml {
|
|||||||
const context = {
|
const context = {
|
||||||
css: {},
|
css: {},
|
||||||
pluginAssets: {},
|
pluginAssets: {},
|
||||||
|
cache: this.contextCache_,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ruleOptions = Object.assign({}, options, {
|
const ruleOptions = Object.assign({}, options, {
|
||||||
@ -203,6 +208,7 @@ class MdToHtml {
|
|||||||
if (this.pluginEnabled('katex')) markdownIt.use(rules.katex(context, ruleOptions));
|
if (this.pluginEnabled('katex')) markdownIt.use(rules.katex(context, ruleOptions));
|
||||||
if (this.pluginEnabled('fountain')) markdownIt.use(rules.fountain(context, ruleOptions));
|
if (this.pluginEnabled('fountain')) markdownIt.use(rules.fountain(context, ruleOptions));
|
||||||
if (this.pluginEnabled('mermaid')) markdownIt.use(rules.mermaid(context, ruleOptions));
|
if (this.pluginEnabled('mermaid')) markdownIt.use(rules.mermaid(context, ruleOptions));
|
||||||
|
markdownIt.use(rules.sanitize_html(context, ruleOptions));
|
||||||
markdownIt.use(rules.highlight_keywords(context, ruleOptions));
|
markdownIt.use(rules.highlight_keywords(context, ruleOptions));
|
||||||
markdownIt.use(rules.code_inline(context, ruleOptions));
|
markdownIt.use(rules.code_inline(context, ruleOptions));
|
||||||
markdownIt.use(markdownItAnchor, { slugify: uslugify });
|
markdownIt.use(markdownItAnchor, { slugify: uslugify });
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
const md5 = require('md5');
|
||||||
|
const htmlUtils = require('../../htmlUtils');
|
||||||
|
|
||||||
|
// @ts-ignore: Keep the function signature as-is despite unusued arguments
|
||||||
|
function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any) {
|
||||||
|
markdownIt.core.ruler.push('sanitize_html', (state:any) => {
|
||||||
|
const tokens = state.tokens;
|
||||||
|
|
||||||
|
const walkHtmlTokens = (tokens:any[]) => {
|
||||||
|
if (!tokens || !tokens.length) return;
|
||||||
|
|
||||||
|
for (const token of tokens) {
|
||||||
|
if (!['html_block', 'html_inline'].includes(token.type)) {
|
||||||
|
walkHtmlTokens(token.children);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheKey = md5(escape(token.content));
|
||||||
|
let sanitizedContent = context.cache.get(cacheKey);
|
||||||
|
|
||||||
|
if (!sanitizedContent) {
|
||||||
|
sanitizedContent = htmlUtils.sanitizeHtml(token.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
token.content = sanitizedContent;
|
||||||
|
|
||||||
|
context.cache.put(cacheKey, sanitizedContent, 1000 * 60 * 60);
|
||||||
|
walkHtmlTokens(token.children);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
walkHtmlTokens(tokens);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function(context:any, ruleOptions:any) {
|
||||||
|
return function(md:any, mdOptions:any) {
|
||||||
|
installRule(md, mdOptions, ruleOptions, context);
|
||||||
|
};
|
||||||
|
}
|
@ -2,8 +2,11 @@ const Entities = require('html-entities').AllHtmlEntities;
|
|||||||
const htmlentities = new Entities().encode;
|
const htmlentities = new Entities().encode;
|
||||||
|
|
||||||
// [\s\S] instead of . for multiline matching
|
// [\s\S] instead of . for multiline matching
|
||||||
|
const NodeHtmlParser = require('node-html-parser');
|
||||||
|
|
||||||
// https://stackoverflow.com/a/16119722/561309
|
// https://stackoverflow.com/a/16119722/561309
|
||||||
const imageRegex = /<img([\s\S]*?)src=["']([\s\S]*?)["']([\s\S]*?)>/gi;
|
const imageRegex = /<img([\s\S]*?)src=["']([\s\S]*?)["']([\s\S]*?)>/gi;
|
||||||
|
const JS_EVENT_NAMES = ['onabort', 'onafterprint', 'onbeforeprint', 'onbeforeunload', 'onblur', 'oncanplay', 'oncanplaythrough', 'onchange', 'onclick', 'oncontextmenu', 'oncopy', 'oncuechange', 'oncut', 'ondblclick', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'ondurationchange', 'onemptied', 'onended', 'onerror', 'onfocus', 'onhashchange', 'oninput', 'oninvalid', 'onkeydown', 'onkeypress', 'onkeyup', 'onload', 'onloadeddata', 'onloadedmetadata', 'onloadstart', 'onmessage', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onoffline', 'ononline', 'onpagehide', 'onpageshow', 'onpaste', 'onpause', 'onplay', 'onplaying', 'onpopstate', 'onprogress', 'onratechange', 'onreset', 'onresize', 'onscroll', 'onsearch', 'onseeked', 'onseeking', 'onselect', 'onstalled', 'onstorage', 'onsubmit', 'onsuspend', 'ontimeupdate', 'ontoggle', 'onunload', 'onvolumechange', 'onwaiting', 'onwheel'];
|
||||||
|
|
||||||
class HtmlUtils {
|
class HtmlUtils {
|
||||||
|
|
||||||
@ -43,6 +46,34 @@ class HtmlUtils {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sanitizeHtml(html) {
|
||||||
|
const walkHtmlNodes = (nodes) => {
|
||||||
|
if (!nodes || !nodes.length) return;
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
for (const attr in node.attributes) {
|
||||||
|
if (!node.attributes.hasOwnProperty(attr)) continue;
|
||||||
|
if (JS_EVENT_NAMES.includes(attr)) node.setAttribute(attr, '');
|
||||||
|
}
|
||||||
|
walkHtmlNodes(node.childNodes);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Need to wrap in div, otherwise elements at the root will be skipped
|
||||||
|
// The DIV tags are removed below
|
||||||
|
const dom = NodeHtmlParser.parse(`<div>${html}</div>`, {
|
||||||
|
script: false,
|
||||||
|
style: true,
|
||||||
|
pre: true,
|
||||||
|
comment: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
walkHtmlNodes([dom]);
|
||||||
|
const output = dom.toString();
|
||||||
|
return output.substr(5, output.length - 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const htmlUtils = new HtmlUtils();
|
const htmlUtils = new HtmlUtils();
|
||||||
|
@ -37,6 +37,8 @@
|
|||||||
"markdown-it-toc-done-right": "^4.1.0",
|
"markdown-it-toc-done-right": "^4.1.0",
|
||||||
"md5": "^2.2.1",
|
"md5": "^2.2.1",
|
||||||
"mermaid": "^8.4.6",
|
"mermaid": "^8.4.6",
|
||||||
|
"memory-cache": "^0.2.0",
|
||||||
|
"node-html-parser": "^1.2.4",
|
||||||
"uslug": "^1.0.4"
|
"uslug": "^1.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,7 @@ function shimInit() {
|
|||||||
if (shim.isElectron()) {
|
if (shim.isElectron()) {
|
||||||
const nativeImage = require('electron').nativeImage;
|
const nativeImage = require('electron').nativeImage;
|
||||||
let image = nativeImage.createFromDataURL(imageDataUrl);
|
let image = nativeImage.createFromDataURL(imageDataUrl);
|
||||||
if (image.isEmpty()) throw new Error('Could not convert data URL to image'); // Would throw for example if the image format is no supported (eg. image/gif)
|
if (image.isEmpty()) throw new Error('Could not convert data URL to image - perhaps the format is not supported (eg. image/gif)'); // Would throw for example if the image format is no supported (eg. image/gif)
|
||||||
if (options.cropRect) {
|
if (options.cropRect) {
|
||||||
// Crop rectangle values need to be rounded or the crop() call will fail
|
// Crop rectangle values need to be rounded or the crop() call will fail
|
||||||
const c = options.cropRect;
|
const c = options.cropRect;
|
||||||
|
20
ReactNativeClient/package-lock.json
generated
20
ReactNativeClient/package-lock.json
generated
@ -5689,6 +5689,11 @@
|
|||||||
"mimic-fn": "^1.0.0"
|
"mimic-fn": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"memory-cache": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz",
|
||||||
|
"integrity": "sha1-eJCwHVLADI68nVM+H46xfjA0hxo="
|
||||||
|
},
|
||||||
"merge-stream": {
|
"merge-stream": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz",
|
||||||
@ -6380,6 +6385,21 @@
|
|||||||
"is-stream": "^1.0.1"
|
"is-stream": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node-html-parser": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-qHwPdGyGr9pOZBoSgUOuNPG20QYZVN00lFcxKQgjPUODSxVH7obQeLVVawa3B4cfSNtLIeczSzoy/xYA8XG5WQ==",
|
||||||
|
"requires": {
|
||||||
|
"he": "1.1.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"he": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node-int64": {
|
"node-int64": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||||
|
@ -72,6 +72,8 @@
|
|||||||
"react-native-version-info": "^0.5.1",
|
"react-native-version-info": "^0.5.1",
|
||||||
"react-native-webview": "^5.12.0",
|
"react-native-webview": "^5.12.0",
|
||||||
"react-redux": "5.0.7",
|
"react-redux": "5.0.7",
|
||||||
|
"memory-cache": "^0.2.0",
|
||||||
|
"node-html-parser": "^1.2.4",
|
||||||
"redux": "4.0.0",
|
"redux": "4.0.0",
|
||||||
"reselect": "^4.0.0",
|
"reselect": "^4.0.0",
|
||||||
"rn-fetch-blob": "^0.12.0",
|
"rn-fetch-blob": "^0.12.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user