1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-21 09:38:01 +02:00

Desktop, Mobile: Fixed issue where file:// URLs would not be rendered correctly

This commit is contained in:
Laurent Cozic 2021-01-02 16:53:59 +00:00
parent 34c1096307
commit 491714cde6
10 changed files with 138 additions and 63 deletions

View File

@ -1388,6 +1388,12 @@ packages/renderer/MdToHtml/rules/mermaid.js.map
packages/renderer/MdToHtml/rules/sanitize_html.d.ts
packages/renderer/MdToHtml/rules/sanitize_html.js
packages/renderer/MdToHtml/rules/sanitize_html.js.map
packages/renderer/MdToHtml/setupLinkify.d.ts
packages/renderer/MdToHtml/setupLinkify.js
packages/renderer/MdToHtml/setupLinkify.js.map
packages/renderer/MdToHtml/validateLinks.d.ts
packages/renderer/MdToHtml/validateLinks.js
packages/renderer/MdToHtml/validateLinks.js.map
packages/renderer/htmlUtils.d.ts
packages/renderer/htmlUtils.js
packages/renderer/htmlUtils.js.map

6
.gitignore vendored
View File

@ -1377,6 +1377,12 @@ packages/renderer/MdToHtml/rules/mermaid.js.map
packages/renderer/MdToHtml/rules/sanitize_html.d.ts
packages/renderer/MdToHtml/rules/sanitize_html.js
packages/renderer/MdToHtml/rules/sanitize_html.js.map
packages/renderer/MdToHtml/setupLinkify.d.ts
packages/renderer/MdToHtml/setupLinkify.js
packages/renderer/MdToHtml/setupLinkify.js.map
packages/renderer/MdToHtml/validateLinks.d.ts
packages/renderer/MdToHtml/validateLinks.js
packages/renderer/MdToHtml/validateLinks.js.map
packages/renderer/htmlUtils.d.ts
packages/renderer/htmlUtils.js
packages/renderer/htmlUtils.js.map

View File

@ -54,5 +54,5 @@ module.exports = {
testEnvironment: 'node',
setupFilesAfterEnv: [`${__dirname}/jest.setup.js`],
slowTestThreshold: 20,
slowTestThreshold: 40,
};

View File

@ -147,27 +147,86 @@ describe('MdToHtml', function() {
expect(result.html.trim()).toBe('<div id="rendered-md"><p>just <strong>testing</strong></p>\n</div>');
}));
// it('should render links correctly', (async () => {
// const mdToHtml = newTestMdToHtml();
it('should render links correctly', (async () => {
const testCases = [
// 0: input
// 1: output with linkify = off
// 2: output with linkify = on
[
'https://example.com',
'https://example.com',
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>',
],
[
'file://C:\\AUTOEXEC.BAT',
'file://C:\\AUTOEXEC.BAT',
'<a data-from-md title=\'file://C:%5CAUTOEXEC.BAT\' href=\'file://C:%5CAUTOEXEC.BAT\'>file://C:\\AUTOEXEC.BAT</a>',
],
[
'example.com',
'example.com',
'example.com',
],
[
'oo.ps',
'oo.ps',
'oo.ps',
],
[
'test@example.com',
'test@example.com',
'test@example.com',
],
[
'<https://example.com>',
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>',
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>',
],
[
'[ok](https://example.com)',
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>',
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>',
],
[
'[bla.pdf](file:///Users/tessus/Downloads/bla.pdf)',
'<a data-from-md title=\'file:///Users/tessus/Downloads/bla.pdf\' href=\'file:///Users/tessus/Downloads/bla.pdf\'>bla.pdf</a>',
'<a data-from-md title=\'file:///Users/tessus/Downloads/bla.pdf\' href=\'file:///Users/tessus/Downloads/bla.pdf\'>bla.pdf</a>',
],
];
// const testCases = [
// // None of these should result in a link
// ['https://example.com', 'https://example.com'],
// ['file://C:\\AUTOEXEC.BAT', 'file://C:\\AUTOEXEC.BAT'],
// ['example.com', 'example.com'],
// ['oo.ps', 'oo.ps'],
// ['test@example.com', 'test@example.com'],
const mdToHtmlLinkifyOn = newTestMdToHtml({
pluginOptions: {
linkify: { enabled: true },
},
});
// // Those should be converted to links
// ['<https://example.com>', '<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>'],
// ['[ok](https://example.com)', '<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>'],
// ];
const mdToHtmlLinkifyOff = newTestMdToHtml({
pluginOptions: {
linkify: { enabled: false },
},
});
// for (const testCase of testCases) {
// const [input, expected] = testCase;
// const actual = await mdToHtml.render(input, null, { bodyOnly: true, plainResourceRendering: true });
// expect(actual.html).toBe(expected);
// }
// }));
for (const testCase of testCases) {
const [input, expectedLinkifyOff, expectedLinkifyOn] = testCase;
{
const actual = await mdToHtmlLinkifyOn.render(input, null, {
bodyOnly: true,
plainResourceRendering: true,
});
expect(actual.html).toBe(expectedLinkifyOn);
}
{
const actual = await mdToHtmlLinkifyOff.render(input, null, {
bodyOnly: true,
plainResourceRendering: true,
});
expect(actual.html).toBe(expectedLinkifyOff);
}
}
}));
});

View File

@ -1,7 +1,7 @@
import { validateLinks } from '@joplin/renderer';
const stringPadding = require('string-padding');
const urlUtils = require('./urlUtils');
const MarkdownIt = require('markdown-it');
const { setupLinkify } = require('@joplin/renderer');
// Taken from codemirror/addon/edit/continuelist.js
const listRegex = /^(\s*)([*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]\s))(\s*)/;
@ -47,7 +47,7 @@ const markdownUtils = {
// Returns the **encoded** URLs, so to be useful they should be decoded again before use.
extractImageUrls(md: string) {
const markdownIt = new MarkdownIt();
setupLinkify(markdownIt); // Necessary to support file:/// links
markdownIt.validateLink = validateLinks; // Necessary to support file:/// links
const env = {};
const tokens = markdownIt.parse(md, env);

View File

@ -1,6 +1,8 @@
import InMemoryCache from './InMemoryCache';
import noteStyle from './noteStyle';
import { fileExtension } from './pathUtils';
import setupLinkify from './MdToHtml/setupLinkify';
import validateLinks from './MdToHtml/validateLinks';
const MarkdownIt = require('markdown-it');
const md5 = require('md5');
@ -41,7 +43,6 @@ const rules: RendererRules = {
mermaid: require('./MdToHtml/rules/mermaid').default,
};
const setupLinkify = require('./MdToHtml/setupLinkify');
const hljs = require('highlight.js');
const uslug = require('uslug');
const markdownItAnchor = require('markdown-it-anchor');
@ -517,6 +518,8 @@ export default class MdToHtml {
}
}
markdownIt.validateLink = validateLinks;
if (this.pluginEnabled('linkify')) setupLinkify(markdownIt);
const renderedBody = markdownIt.render(body, context);

View File

@ -1,39 +0,0 @@
module.exports = function(markdownIt) {
// Add `file:` protocol in linkify to allow text in the format of "file://..." to translate into
// file-URL links in html view
markdownIt.linkify.add('file:', {
validate: function(text, pos, self) {
const tail = text.slice(pos);
if (!self.re.file) {
// matches all local file URI on Win/Unix/MacOS systems including reserved characters in some OS (i.e. no OS specific sanity check)
self.re.file = new RegExp('^[\\/]{2,3}[\\S]+');
}
if (self.re.file.test(tail)) {
return tail.match(self.re.file)[0].length;
}
return 0;
},
});
// enable file link URLs in MarkdownIt. Keeps other URL restrictions of MarkdownIt untouched.
// Format [link name](file://...)
markdownIt.validateLink = function(url) {
const BAD_PROTO_RE = /^(vbscript|javascript|data):/;
const GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/;
// url should be normalized at this point, and existing entities are decoded
const str = url.trim().toLowerCase();
if (str.indexOf('data:image/svg+xml,') === 0) {
return true;
}
return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true;
};
markdownIt.linkify.set({
'fuzzyLink': false,
'fuzzyIP': false,
'fuzzyEmail': false,
});
};

View File

@ -0,0 +1,23 @@
export default function(markdownIt: any) {
// Add `file:` protocol in linkify to allow text in the format of "file://..." to translate into
// file-URL links in html view
markdownIt.linkify.add('file:', {
validate: function(text: string, pos: number, self: any) {
const tail = text.slice(pos);
if (!self.re.file) {
// matches all local file URI on Win/Unix/MacOS systems including reserved characters in some OS (i.e. no OS specific sanity check)
self.re.file = new RegExp('^[\\/]{2,3}[\\S]+');
}
if (self.re.file.test(tail)) {
return tail.match(self.re.file)[0].length;
}
return 0;
},
});
markdownIt.linkify.set({
'fuzzyLink': false,
'fuzzyIP': false,
'fuzzyEmail': false,
});
}

View File

@ -0,0 +1,15 @@
// enable file link URLs in MarkdownIt. Keeps other URL restrictions of MarkdownIt untouched.
// Format [link name](file://...)
export default function(url: string) {
const BAD_PROTO_RE = /^(vbscript|javascript|data):/;
const GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/;
// url should be normalized at this point, and existing entities are decoded
const str = url.trim().toLowerCase();
if (str.indexOf('data:image/svg+xml,') === 0) {
return true;
}
return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true;
}

View File

@ -2,7 +2,8 @@ import MarkupToHtml, { MarkupLanguage } from './MarkupToHtml';
import MdToHtml from './MdToHtml';
import HtmlToHtml from './HtmlToHtml';
import utils from './utils';
const setupLinkify = require('./MdToHtml/setupLinkify');
import setupLinkify from './MdToHtml/setupLinkify';
import validateLinks from './MdToHtml/validateLinks';
const assetsToHeaders = require('./assetsToHeaders');
export {
@ -11,6 +12,7 @@ export {
MdToHtml,
HtmlToHtml,
setupLinkify,
validateLinks,
assetsToHeaders,
utils,
};