Skip to content

Commit 3db47b5

Browse files
committed
All: Security: Fixed potential Arbitrary File Read via XSS
1 parent 06d807d commit 3db47b5

File tree

24 files changed

+437
-98
lines changed

24 files changed

+437
-98
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,4 @@ ElectronClient/app/gui/ShareNoteDialog.js
5555
ReactNativeClient/lib/JoplinServerApi.js
5656
ReactNativeClient/PluginAssetsLoader.js
5757
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
58+
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,4 @@ ElectronClient/app/gui/ShareNoteDialog.js
5151
ReactNativeClient/lib/JoplinServerApi.js
5252
ReactNativeClient/PluginAssetsLoader.js
5353
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
54+
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js

CliClient/package-lock.json

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CliClient/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"markdown-it-toc-done-right": "^4.1.0",
7373
"md5": "^2.2.1",
7474
"md5-file": "^4.0.0",
75+
"memory-cache": "^0.2.0",
7576
"mime": "^2.0.3",
7677
"moment": "^2.24.0",
7778
"multiparty": "^4.2.1",
@@ -104,7 +105,8 @@
104105
"valid-url": "^1.0.9",
105106
"word-wrap": "^1.2.3",
106107
"xml2js": "^0.4.19",
107-
"yargs-parser": "^7.0.0"
108+
"yargs-parser": "^7.0.0",
109+
"node-html-parser": "^1.2.4"
108110
},
109111
"devDependencies": {
110112
"jasmine": "^3.5.0"

CliClient/tests/HtmlToHtml.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/* eslint-disable no-unused-vars */
2+
3+
require('app-module-path').addPath(__dirname);
4+
5+
const os = require('os');
6+
const { time } = require('lib/time-utils.js');
7+
const { filename } = require('lib/path-utils.js');
8+
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
9+
const Folder = require('lib/models/Folder.js');
10+
const Note = require('lib/models/Note.js');
11+
const BaseModel = require('lib/BaseModel.js');
12+
const { shim } = require('lib/shim');
13+
const HtmlToHtml = require('lib/joplin-renderer/HtmlToHtml');
14+
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
15+
16+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 60 * 1000; // Can run for a while since everything is in the same test unit
17+
18+
process.on('unhandledRejection', (reason, p) => {
19+
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
20+
});
21+
22+
describe('HtmlToHtml', function() {
23+
24+
beforeEach(async (done) => {
25+
await setupDatabaseAndSynchronizer(1);
26+
await switchClient(1);
27+
done();
28+
});
29+
30+
it('should convert from Html to Html', asyncTest(async () => {
31+
const basePath = `${__dirname}/html_to_html`;
32+
const files = await shim.fsDriver().readDirStats(basePath);
33+
const htmlToHtml = new HtmlToHtml();
34+
35+
for (let i = 0; i < files.length; i++) {
36+
const htmlSourceFilename = files[i].path;
37+
if (htmlSourceFilename.indexOf('.src.html') < 0) continue;
38+
39+
const htmlSourceFilePath = `${basePath}/${htmlSourceFilename}`;
40+
const htmlDestPath = `${basePath}/${filename(filename(htmlSourceFilePath))}.dest.html`;
41+
42+
// if (htmlSourceFilename !== 'table_with_header.html') continue;
43+
44+
const htmlToHtmlOptions = {
45+
bodyOnly: true,
46+
};
47+
48+
const sourceHtml = await shim.fsDriver().readFile(htmlSourceFilePath);
49+
let expectedHtml = await shim.fsDriver().readFile(htmlDestPath);
50+
51+
const result = await htmlToHtml.render(sourceHtml, null, htmlToHtmlOptions);
52+
let actualHtml = result.html;
53+
54+
if (os.EOL === '\r\n') {
55+
expectedHtml = expectedHtml.replace(/\r\n/g, '\n');
56+
actualHtml = actualHtml.replace(/\r\n/g, '\n');
57+
}
58+
59+
if (actualHtml !== expectedHtml) {
60+
console.info('');
61+
console.info(`Error converting file: ${htmlSourceFilename}`);
62+
console.info('--------------------------------- Got:');
63+
console.info(actualHtml);
64+
console.info('--------------------------------- Raw:');
65+
console.info(actualHtml.split('\n'));
66+
console.info('--------------------------------- Expected:');
67+
console.info(expectedHtml.split('\n'));
68+
console.info('--------------------------------------------');
69+
console.info('');
70+
71+
expect(false).toBe(true);
72+
// return;
73+
} else {
74+
expect(true).toBe(true);
75+
}
76+
}
77+
}));
78+
79+
});

CliClient/tests/MdToHtml.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/* eslint-disable no-unused-vars */
2+
3+
require('app-module-path').addPath(__dirname);
4+
5+
const os = require('os');
6+
const { time } = require('lib/time-utils.js');
7+
const { filename } = require('lib/path-utils.js');
8+
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
9+
const Folder = require('lib/models/Folder.js');
10+
const Note = require('lib/models/Note.js');
11+
const BaseModel = require('lib/BaseModel.js');
12+
const { shim } = require('lib/shim');
13+
const MdToHtml = require('lib/joplin-renderer/MdToHtml');
14+
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
15+
16+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 60 * 1000; // Can run for a while since everything is in the same test unit
17+
18+
process.on('unhandledRejection', (reason, p) => {
19+
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
20+
});
21+
22+
describe('MdToHtml', function() {
23+
24+
beforeEach(async (done) => {
25+
await setupDatabaseAndSynchronizer(1);
26+
await switchClient(1);
27+
done();
28+
});
29+
30+
it('should convert from Markdown to Html', asyncTest(async () => {
31+
const basePath = `${__dirname}/md_to_html`;
32+
const files = await shim.fsDriver().readDirStats(basePath);
33+
const mdToHtml = new MdToHtml();
34+
35+
for (let i = 0; i < files.length; i++) {
36+
const mdFilename = files[i].path;
37+
if (mdFilename.indexOf('.md') < 0) continue;
38+
39+
const mdFilePath = `${basePath}/${mdFilename}`;
40+
const htmlPath = `${basePath}/${filename(mdFilePath)}.html`;
41+
42+
// if (mdFilename !== 'table_with_header.html') continue;
43+
44+
const mdToHtmlOptions = {
45+
bodyOnly: true,
46+
};
47+
48+
const markdown = await shim.fsDriver().readFile(mdFilePath);
49+
let expectedHtml = await shim.fsDriver().readFile(htmlPath);
50+
51+
const result = await mdToHtml.render(markdown, null, mdToHtmlOptions);
52+
let actualHtml = result.html;
53+
54+
if (os.EOL === '\r\n') {
55+
expectedHtml = expectedHtml.replace(/\r\n/g, '\n');
56+
actualHtml = actualHtml.replace(/\r\n/g, '\n');
57+
}
58+
59+
if (actualHtml !== expectedHtml) {
60+
console.info('');
61+
console.info(`Error converting file: ${mdFilename}`);
62+
console.info('--------------------------------- Got:');
63+
console.info(actualHtml);
64+
console.info('--------------------------------- Raw:');
65+
console.info(actualHtml.split('\n'));
66+
console.info('--------------------------------- Expected:');
67+
console.info(expectedHtml.split('\n'));
68+
console.info('--------------------------------------------');
69+
console.info('');
70+
71+
expect(false).toBe(true);
72+
// return;
73+
} else {
74+
expect(true).toBe(true);
75+
}
76+
}
77+
}));
78+
79+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<img src onerror="" />
2+
<img src onerror="" />
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<img src="" onerror="alert('ohno')"/>
2+
<img src=""
3+
onerror="alert('ohno')"/>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<img src onerror="" />
2+
<img src onerror="" />
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<img src="" onerror="alert('ohno')"/>
2+
<img src=""
3+
onerror="alert('ohno')"/>

Clipper/joplin-webclipper/content_scripts/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
function absoluteUrl(url) {
2222
if (!url) return url;
2323
const protocol = url.toLowerCase().split(':')[0];
24-
if (['http', 'https', 'file'].indexOf(protocol) >= 0) return url;
24+
if (['http', 'https', 'file', 'data'].indexOf(protocol) >= 0) return url;
2525

2626
if (url.indexOf('//') === 0) {
2727
return location.protocol + url;

0 commit comments

Comments
 (0)