From 71d1e6eb82117b5b3c2d541eb358007ea31d0fc9 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Fri, 6 Mar 2020 00:54:21 +0000 Subject: [PATCH] All: Fixes #2667: Fixed sanitize function so that it does not break HTML --- BUILD.md | 20 +- CliClient/package-lock.json | 144 ++++++++------ CliClient/package.json | 6 +- CliClient/tests/MdToHtml.js | 8 +- .../tests/html_to_html/sanitize.dest.html | 4 +- CliClient/tests/md_to_html/sanitize.html | 4 +- CliClient/tests/md_to_html/sanitize_2.html | 2 +- CliClient/tests/md_to_html/sanitize_3.html | 1 + CliClient/tests/md_to_html/sanitize_3.md | 1 + CliClient/tests/md_to_html/sanitize_4.html | 4 + CliClient/tests/md_to_html/sanitize_4.md | 6 + CliClient/tests/md_to_html/sanitize_5.html | 6 + CliClient/tests/md_to_html/sanitize_5.md | 6 + CliClient/tests/md_to_html/sanitize_6.html | 1 + CliClient/tests/md_to_html/sanitize_6.md | 1 + CliClient/tests/md_to_html/sanitize_7.html | 0 CliClient/tests/md_to_html/sanitize_7.md | 1 + CliClient/tests/md_to_html/sanitize_8.html | 2 + CliClient/tests/md_to_html/sanitize_8.md | 3 + CliClient/tests/md_to_html/sanitize_9.html | 1 + CliClient/tests/md_to_html/sanitize_9.md | 1 + ElectronClient/package-lock.json | 51 +++-- ElectronClient/package.json | 4 +- ElectronClient/patches/sax+1.2.4.patch | 18 -- .../lib/joplin-renderer/MdToHtml.js | 2 +- .../MdToHtml/rules/html_image.js | 2 +- .../MdToHtml/rules/sanitize_html.ts | 48 +---- .../lib/joplin-renderer/htmlUtils.js | 99 +++++++--- .../lib/joplin-renderer/package.json | 1 - ReactNativeClient/package-lock.json | 183 ++++++++++++++++-- ReactNativeClient/package.json | 5 +- patches/htmlparser2+4.1.0.patch | 47 +++++ .../patches => patches}/sax+1.2.4.patch | 0 33 files changed, 479 insertions(+), 203 deletions(-) create mode 100644 CliClient/tests/md_to_html/sanitize_3.html create mode 100644 CliClient/tests/md_to_html/sanitize_3.md create mode 100644 CliClient/tests/md_to_html/sanitize_4.html create mode 100644 CliClient/tests/md_to_html/sanitize_4.md create mode 100644 CliClient/tests/md_to_html/sanitize_5.html create mode 100644 CliClient/tests/md_to_html/sanitize_5.md create mode 100644 CliClient/tests/md_to_html/sanitize_6.html create mode 100644 CliClient/tests/md_to_html/sanitize_6.md create mode 100644 CliClient/tests/md_to_html/sanitize_7.html create mode 100644 CliClient/tests/md_to_html/sanitize_7.md create mode 100644 CliClient/tests/md_to_html/sanitize_8.html create mode 100644 CliClient/tests/md_to_html/sanitize_8.md create mode 100644 CliClient/tests/md_to_html/sanitize_9.html create mode 100644 CliClient/tests/md_to_html/sanitize_9.md delete mode 100644 ElectronClient/patches/sax+1.2.4.patch create mode 100644 patches/htmlparser2+4.1.0.patch rename {CliClient/patches => patches}/sax+1.2.4.patch (100%) diff --git a/BUILD.md b/BUILD.md index a2a108690..5b32bc7dd 100644 --- a/BUILD.md +++ b/BUILD.md @@ -20,25 +20,25 @@ Then you can test the various applications: ## Testing the desktop application cd ElectronClient - npm run start + npm start If you'd like to auto-reload the app on changes rather than having to quit and restart it manually each time, you can use [watchman-make](https://facebook.github.io/watchman/docs/watchman-make.html): ```sh cd ElectronClient -watchman-make -p '**/*.js' '**/*.jsx' --run "npm run start" +watchman-make -p '**/*.js' '**/*.jsx' --run "npm start" ``` -It still requires you to quit the application each time you want it to rebuild, but at least you don't have to re-run `"npm run start"` each time. Here's what the workflow loop looks like in practice: +It still requires you to quit the application each time you want it to rebuild, but at least you don't have to re-run `"npm start"` each time. Here's what the workflow loop looks like in practice: 1. Edit and save files in your text editor. 2. Switch to the Electron app and cmd+Q to quit it. -3. `watchman` immediately restarts the app for you (whereas usually you'd have to switch back to the terminal, type `"npm run start"`, and hit enter). +3. `watchman` immediately restarts the app for you (whereas usually you'd have to switch back to the terminal, type `"npm start"`, and hit enter). ## Testing the Terminal application cd CliClient - npm run start + npm start ## Testing the Mobile application @@ -47,12 +47,12 @@ First you need to setup React Native to build projects with native code. For thi Then: cd ReactNativeClient - npm run start-android - # Or: npm run start-ios + npm start-android + # Or: npm start-ios To run the iOS application, it might be easier to open the file `ios/Joplin.xcworkspace` on XCode and run the app from there. -Normally the bundler should start automatically with the application. If it doesn't run `npm run start`. +Normally the bundler should start automatically with the application. If it doesn't run `npm start`. ## Building the clipper @@ -72,9 +72,9 @@ Running `npm run build` would have the same effect, but without watching. ## Running an application with additional parameters -You can specify additional parameters when running the desktop or CLI application. To do so, add `--` to the `npm run start` command, followed by your flags. For example: +You can specify additional parameters when running the desktop or CLI application. To do so, add `--` to the `npm start` command, followed by your flags. For example: - npm run start -- --profile ~/MyTestProfile + npm start -- --profile ~/MyTestProfile ## TypeScript diff --git a/CliClient/package-lock.json b/CliClient/package-lock.json index 57c507c7a..f728f4c3d 100644 --- a/CliClient/package-lock.json +++ b/CliClient/package-lock.json @@ -726,10 +726,68 @@ "minimist": "^1.1.1" }, "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": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "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" + } } } }, @@ -1311,19 +1369,14 @@ "integrity": "sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg==" }, "dom-serializer": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz", - "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", "requires": { "domelementtype": "^2.0.1", "entities": "^2.0.0" }, "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": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", @@ -1332,9 +1385,9 @@ } }, "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" }, "domexception": { "version": "1.0.1", @@ -1345,20 +1398,21 @@ } }, "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz", + "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==", "requires": { - "domelementtype": "1" + "domelementtype": "^2.0.1" } }, "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz", + "integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==", "requires": { - "dom-serializer": "0", - "domelementtype": "1" + "dom-serializer": "^0.2.1", + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0" } }, "dot-prop": { @@ -3042,40 +3096,20 @@ } }, "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", + "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", "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" + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" }, "dependencies": { - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "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" - } + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" } } }, @@ -4538,14 +4572,6 @@ "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" - } - }, "node-persist": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-2.1.0.tgz", diff --git a/CliClient/package.json b/CliClient/package.json index d72f6481e..cad32eabf 100644 --- a/CliClient/package.json +++ b/CliClient/package.json @@ -5,7 +5,7 @@ "author": "Laurent Cozic", "scripts": { "test": "gulp buildTests -L && jasmine --config=tests/support/jasmine.json", - "postinstall": "patch-package && npm run build", + "postinstall": "patch-package --patch-dir ../patches && npm run build", "build": "gulp build", "start": "gulp build -L && node 'build/main.js' --profile ~/Temp/TestNotes2 --stack-trace-enabled --log-level debug --env dev" }, @@ -52,6 +52,7 @@ "highlight.js": "^9.17.1", "html-entities": "^1.2.1", "html-minifier": "^3.5.15", + "htmlparser2": "^4.1.0", "image-data-uri": "^2.0.0", "image-type": "^3.0.0", "joplin-turndown": "^4.0.19", @@ -108,8 +109,7 @@ "valid-url": "^1.0.9", "word-wrap": "^1.2.3", "xml2js": "^0.4.19", - "yargs-parser": "^7.0.0", - "node-html-parser": "^1.2.4" + "yargs-parser": "^7.0.0" }, "devDependencies": { "gulp": "^4.0.2", diff --git a/CliClient/tests/MdToHtml.js b/CliClient/tests/MdToHtml.js index e53ea4917..8ab3adfff 100644 --- a/CliClient/tests/MdToHtml.js +++ b/CliClient/tests/MdToHtml.js @@ -30,7 +30,11 @@ describe('MdToHtml', function() { 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(); + const mdToHtml = new MdToHtml({ + ResourceModel: { + isResourceUrl: () => false, + }, + }); for (let i = 0; i < files.length; i++) { const mdFilename = files[i].path; @@ -39,7 +43,7 @@ describe('MdToHtml', function() { const mdFilePath = `${basePath}/${mdFilename}`; const htmlPath = `${basePath}/${filename(mdFilePath)}.html`; - // if (mdFilename !== 'table_with_header.html') continue; + // if (mdFilename !== 'sanitize_9.md') continue; const mdToHtmlOptions = { bodyOnly: true, diff --git a/CliClient/tests/html_to_html/sanitize.dest.html b/CliClient/tests/html_to_html/sanitize.dest.html index b0f104132..7f871c790 100644 --- a/CliClient/tests/html_to_html/sanitize.dest.html +++ b/CliClient/tests/html_to_html/sanitize.dest.html @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/CliClient/tests/md_to_html/sanitize.html b/CliClient/tests/md_to_html/sanitize.html index b0f104132..7f871c790 100644 --- a/CliClient/tests/md_to_html/sanitize.html +++ b/CliClient/tests/md_to_html/sanitize.html @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/CliClient/tests/md_to_html/sanitize_2.html b/CliClient/tests/md_to_html/sanitize_2.html index 7b0e5c380..e095d7d48 100644 --- a/CliClient/tests/md_to_html/sanitize_2.html +++ b/CliClient/tests/md_to_html/sanitize_2.html @@ -1 +1 @@ -

Testing inline text

+

Testing inline text

diff --git a/CliClient/tests/md_to_html/sanitize_3.html b/CliClient/tests/md_to_html/sanitize_3.html new file mode 100644 index 000000000..fd5681576 --- /dev/null +++ b/CliClient/tests/md_to_html/sanitize_3.html @@ -0,0 +1 @@ +

Should be red.

diff --git a/CliClient/tests/md_to_html/sanitize_3.md b/CliClient/tests/md_to_html/sanitize_3.md new file mode 100644 index 000000000..25a717145 --- /dev/null +++ b/CliClient/tests/md_to_html/sanitize_3.md @@ -0,0 +1 @@ +Should be red. \ No newline at end of file diff --git a/CliClient/tests/md_to_html/sanitize_4.html b/CliClient/tests/md_to_html/sanitize_4.html new file mode 100644 index 000000000..fbafec90e --- /dev/null +++ b/CliClient/tests/md_to_html/sanitize_4.html @@ -0,0 +1,4 @@ +
+
H5
+
H6
+
\ No newline at end of file diff --git a/CliClient/tests/md_to_html/sanitize_4.md b/CliClient/tests/md_to_html/sanitize_4.md new file mode 100644 index 000000000..a137daf4b --- /dev/null +++ b/CliClient/tests/md_to_html/sanitize_4.md @@ -0,0 +1,6 @@ +
+ +##### H5 +###### H6 + +
\ No newline at end of file diff --git a/CliClient/tests/md_to_html/sanitize_5.html b/CliClient/tests/md_to_html/sanitize_5.html new file mode 100644 index 000000000..819bccc5e --- /dev/null +++ b/CliClient/tests/md_to_html/sanitize_5.html @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/CliClient/tests/md_to_html/sanitize_5.md b/CliClient/tests/md_to_html/sanitize_5.md new file mode 100644 index 000000000..819bccc5e --- /dev/null +++ b/CliClient/tests/md_to_html/sanitize_5.md @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/CliClient/tests/md_to_html/sanitize_6.html b/CliClient/tests/md_to_html/sanitize_6.html new file mode 100644 index 000000000..e627988b4 --- /dev/null +++ b/CliClient/tests/md_to_html/sanitize_6.html @@ -0,0 +1 @@ +

bla

diff --git a/CliClient/tests/md_to_html/sanitize_6.md b/CliClient/tests/md_to_html/sanitize_6.md new file mode 100644 index 000000000..75f1f64bd --- /dev/null +++ b/CliClient/tests/md_to_html/sanitize_6.md @@ -0,0 +1 @@ +bla \ No newline at end of file diff --git a/CliClient/tests/md_to_html/sanitize_7.html b/CliClient/tests/md_to_html/sanitize_7.html new file mode 100644 index 000000000..e69de29bb diff --git a/CliClient/tests/md_to_html/sanitize_7.md b/CliClient/tests/md_to_html/sanitize_7.md new file mode 100644 index 000000000..51839dfd2 --- /dev/null +++ b/CliClient/tests/md_to_html/sanitize_7.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/CliClient/tests/md_to_html/sanitize_8.html b/CliClient/tests/md_to_html/sanitize_8.html new file mode 100644 index 000000000..775977ea1 --- /dev/null +++ b/CliClient/tests/md_to_html/sanitize_8.html @@ -0,0 +1,2 @@ +
<a href="#" onclick="leavethisalone">testing fence</a>
+
diff --git a/CliClient/tests/md_to_html/sanitize_8.md b/CliClient/tests/md_to_html/sanitize_8.md new file mode 100644 index 000000000..c2460c2f0 --- /dev/null +++ b/CliClient/tests/md_to_html/sanitize_8.md @@ -0,0 +1,3 @@ +```html +testing fence +``` diff --git a/CliClient/tests/md_to_html/sanitize_9.html b/CliClient/tests/md_to_html/sanitize_9.html new file mode 100644 index 000000000..59fd9881e --- /dev/null +++ b/CliClient/tests/md_to_html/sanitize_9.html @@ -0,0 +1 @@ +
M&Ms
\ No newline at end of file diff --git a/CliClient/tests/md_to_html/sanitize_9.md b/CliClient/tests/md_to_html/sanitize_9.md new file mode 100644 index 000000000..a191d394d --- /dev/null +++ b/CliClient/tests/md_to_html/sanitize_9.md @@ -0,0 +1 @@ +
M&Ms
\ No newline at end of file diff --git a/ElectronClient/package-lock.json b/ElectronClient/package-lock.json index f80001b8a..9d3b10981 100644 --- a/ElectronClient/package-lock.json +++ b/ElectronClient/package-lock.json @@ -3629,6 +3629,24 @@ "webidl-conversions": "^4.0.2" } }, + "domhandler": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz", + "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==", + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz", + "integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==", + "requires": { + "dom-serializer": "^0.2.1", + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0" + } + }, "dot-prop": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", @@ -6257,6 +6275,24 @@ } } }, + "htmlparser2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", + "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + }, + "dependencies": { + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" + } + } + }, "http-cache-semantics": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz", @@ -7967,21 +8003,6 @@ } } }, - "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": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-6.0.0.tgz", diff --git a/ElectronClient/package.json b/ElectronClient/package.json index 694009bc1..3a0159ac0 100644 --- a/ElectronClient/package.json +++ b/ElectronClient/package.json @@ -5,7 +5,7 @@ "main": "main.js", "scripts": { "dist": "node_modules/.bin/electron-builder", - "build": "patch-package && gulp build", + "build": "patch-package --patch-dir ../patches && gulp build", "postinstall": "npm run build && gulp electronRebuild", "start": "gulp build -L && electron . --env dev --log-level debug --no-welcome --open-dev-tools" }, @@ -107,6 +107,7 @@ "highlight.js": "^9.17.1", "html-entities": "^1.2.1", "html-minifier": "^4.0.0", + "htmlparser2": "^4.1.0", "image-type": "^3.0.0", "joplin-turndown": "^4.0.19", "joplin-turndown-plugin-gfm": "^1.0.12", @@ -137,7 +138,6 @@ "multiparty": "^4.2.1", "mustache": "^3.0.1", "node-fetch": "^1.7.3", - "node-html-parser": "^1.2.4", "node-notifier": "^6.0.0", "pretty-bytes": "^5.3.0", "promise": "^8.0.1", diff --git a/ElectronClient/patches/sax+1.2.4.patch b/ElectronClient/patches/sax+1.2.4.patch deleted file mode 100644 index 2ea8282cf..000000000 --- a/ElectronClient/patches/sax+1.2.4.patch +++ /dev/null @@ -1,18 +0,0 @@ -diff --git a/node_modules/sax/lib/sax.js b/node_modules/sax/lib/sax.js -index 795d607..ccad5d8 100644 ---- a/node_modules/sax/lib/sax.js -+++ b/node_modules/sax/lib/sax.js -@@ -1040,6 +1040,13 @@ - parser.textNode += c - } - } -+ -+ // Sax is kind of buggy when handling large text node. It has a function to check that -+ // the buffer doesn't run out of space but it doesn't seem to call it for text node. -+ // The result is that parser.textNode reaches 1GB and then the app crashes. So here -+ // we call checkBufferLength to make sure the buffer is cleared and the "text" event -+ // emitted so that the caller can handle memory properly. -+ checkBufferLength(parser); - continue - - case S.SCRIPT: diff --git a/ReactNativeClient/lib/joplin-renderer/MdToHtml.js b/ReactNativeClient/lib/joplin-renderer/MdToHtml.js index 2fe2bbc3c..db348af46 100644 --- a/ReactNativeClient/lib/joplin-renderer/MdToHtml.js +++ b/ReactNativeClient/lib/joplin-renderer/MdToHtml.js @@ -201,6 +201,7 @@ class MdToHtml { // Using the `context` object, a plugin can define what additional assets they need (css, fonts, etc.) using context.pluginAssets. // The calling application will need to handle loading these assets. + markdownIt.use(rules.sanitize_html(context, ruleOptions)); markdownIt.use(rules.image(context, ruleOptions)); markdownIt.use(rules.checkbox(context, ruleOptions)); markdownIt.use(rules.link_open(context, ruleOptions)); @@ -208,7 +209,6 @@ class MdToHtml { if (this.pluginEnabled('katex')) markdownIt.use(rules.katex(context, ruleOptions)); if (this.pluginEnabled('fountain')) markdownIt.use(rules.fountain(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.code_inline(context, ruleOptions)); markdownIt.use(markdownItAnchor, { slugify: uslugify }); diff --git a/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js index aa94c4658..d3e41a9a2 100644 --- a/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js +++ b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js @@ -34,7 +34,7 @@ function installRule(markdownIt, mdOptions, ruleOptions) { if (!content.match(imageRegex)) return defaultRender(tokens, idx, options, env, self); return content.replace(imageRegex, (v, before, src, after) => { - if (!Resource.isResourceUrl(src)) return defaultRender(tokens, idx, options, env, self); + if (!Resource.isResourceUrl(src)) return ``; return renderImageHtml(before, src, after, ruleOptions); }); }; diff --git a/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.ts b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.ts index cc92bbe11..d221fa236 100644 --- a/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.ts +++ b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.ts @@ -1,20 +1,6 @@ const md5 = require('md5'); const htmlUtils = require('../../htmlUtils'); -function getOpenTagName(html:string):string { - const m = html.toLowerCase().match(/<([a-z]+)(\s|>)/); - if (!m || m.length < 2) return null; - return m[1]; -} - -function isSelfClosedTag(html:string):boolean { - return html.substr(-2) === '/>'; -} - -function stripOffClosingTag(html:string, tagName:string):string { - return html.substr(0, html.length - tagName.length - 3); -} - // @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) => { @@ -43,40 +29,10 @@ function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any // text: Testing // html_inline: // - // The problem for us is that when we pass this HTML fragment to the sanitize function - // it is going to turn it into valid HTML. Thus: - // - // "" becomes "" - // "" becomes "" - // - // So the result would be "Testing" - // - // Because of this, we need to be careful with html_inline: - // - // 0. Check if it's an opening or closing tag - only opening ones need to be processed - // 1. Sanitize the fragment - // 2. Strip off the closing tag that was added - // - // Also self-closing tags need to be handled. - // - // html_block is not a problem as the whole content is valid HTML. + // So the sanitizeHtml function must handle this kind of non-valid HTML. if (!sanitizedContent) { - if (token.type === 'html_inline') { - const openTagName = getOpenTagName(token.content); - const isSelfClosed = isSelfClosedTag(token.content); - - if (!openTagName) { - sanitizedContent = token.content; - } else { - sanitizedContent = htmlUtils.sanitizeHtml(token.content); - if (!isSelfClosed) { - sanitizedContent = stripOffClosingTag(sanitizedContent, openTagName); - } - } - } else { // html_block - sanitizedContent = htmlUtils.sanitizeHtml(token.content); - } + sanitizedContent = htmlUtils.sanitizeHtml(token.content); } token.content = sanitizedContent; diff --git a/ReactNativeClient/lib/joplin-renderer/htmlUtils.js b/ReactNativeClient/lib/joplin-renderer/htmlUtils.js index dc78c4a96..338af5d21 100644 --- a/ReactNativeClient/lib/joplin-renderer/htmlUtils.js +++ b/ReactNativeClient/lib/joplin-renderer/htmlUtils.js @@ -2,12 +2,32 @@ const Entities = require('html-entities').AllHtmlEntities; const htmlentities = new Entities().encode; // [\s\S] instead of . for multiline matching -const NodeHtmlParser = require('node-html-parser'); - // https://stackoverflow.com/a/16119722/561309 const imageRegex = //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']; +const selfClosingElements = [ + 'area', + 'base', + 'basefont', + 'br', + 'col', + 'command', + 'embed', + 'frame', + 'hr', + 'img', + 'input', + 'isindex', + 'keygen', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr', +]; + class HtmlUtils { attributesHtml(attr) { @@ -46,31 +66,64 @@ class HtmlUtils { }); } - sanitizeHtml(html) { - const walkHtmlNodes = (nodes) => { - if (!nodes || !nodes.length) return; + isSelfClosingTag(tagName) { + return selfClosingElements.includes(tagName.toLowerCase()); + } - 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); - } + sanitizeHtml(html) { + const htmlparser2 = require('htmlparser2'); + + const output = []; + + const tagStack = []; + + const currentTag = () => { + if (!tagStack.length) return ''; + return tagStack[tagStack.length - 1]; }; - // Need to wrap in div, otherwise elements at the root will be skipped - // The DIV tags are removed below - const dom = NodeHtmlParser.parse(`
${html}
`, { - script: false, - style: true, - pre: true, - comment: false, - }); + const disallowedTags = ['script', 'iframe', 'frameset', 'frame', 'object']; - walkHtmlNodes([dom]); - const output = dom.toString(); - return output.substr(5, output.length - 11); + const parser = new htmlparser2.Parser({ + + onopentag: (name, attrs) => { + tagStack.push(name.toLowerCase()); + + if (disallowedTags.includes(currentTag())) return; + + attrs = Object.assign({}, attrs); + for (const eventName of JS_EVENT_NAMES) { + delete attrs[eventName]; + } + let attrHtml = this.attributesHtml(attrs); + if (attrHtml) attrHtml = ` ${attrHtml}`; + const closingSign = this.isSelfClosingTag(name) ? '/>' : '>'; + output.push(`<${name}${attrHtml}${closingSign}`); + }, + + ontext: (decodedText) => { + if (disallowedTags.includes(currentTag())) return; + + output.push(htmlentities(decodedText)); + }, + + onclosetag: (name) => { + const current = currentTag(); + + if (current === name.toLowerCase()) tagStack.pop(); + + if (disallowedTags.includes(current)) return; + + if (this.isSelfClosingTag(name)) return; + output.push(``); + }, + + }, { decodeEntities: true }); + + parser.write(html); + parser.end(); + + return output.join(''); } diff --git a/ReactNativeClient/lib/joplin-renderer/package.json b/ReactNativeClient/lib/joplin-renderer/package.json index a3daa8533..7d964314b 100644 --- a/ReactNativeClient/lib/joplin-renderer/package.json +++ b/ReactNativeClient/lib/joplin-renderer/package.json @@ -38,7 +38,6 @@ "md5": "^2.2.1", "mermaid": "^8.4.6", "memory-cache": "^0.2.0", - "node-html-parser": "^1.2.4", "uslug": "^1.0.4" } } diff --git a/ReactNativeClient/package-lock.json b/ReactNativeClient/package-lock.json index b1d805b71..5b690bbd3 100644 --- a/ReactNativeClient/package-lock.json +++ b/ReactNativeClient/package-lock.json @@ -2171,6 +2171,12 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" }, + "@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -3853,12 +3859,44 @@ "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.4.tgz", "integrity": "sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg==" }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, "dom-walk": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=", "dev": true }, + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" + }, + "domhandler": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz", + "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==", + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz", + "integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==", + "requires": { + "dom-serializer": "^0.2.1", + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0" + } + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -4417,6 +4455,29 @@ } } }, + "find-yarn-workspace-root": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-1.2.1.tgz", + "integrity": "sha512-dVtfb0WuQG+8Ag2uWkbG79hOUzEsRrhBzgfn86g2sJPkzmcpGdghbNTfUKGTxymFrY/tLIodDzLoW9nOJ4FY8Q==", + "dev": true, + "requires": { + "fs-extra": "^4.0.3", + "micromatch": "^3.1.4" + }, + "dependencies": { + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, "findup-sync": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", @@ -5520,6 +5581,17 @@ } } }, + "htmlparser2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", + "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + }, "http-errors": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", @@ -6254,6 +6326,15 @@ "graceful-fs": "^4.1.9" } }, + "klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11" + } + }, "last-run": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", @@ -7485,21 +7566,6 @@ "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": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -7928,6 +7994,93 @@ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" }, + "patch-package": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.2.1.tgz", + "integrity": "sha512-dfCtQor63PPij6DDYtCzBRoO5nNAcMSg7Cmh+DLhR+s3t0OLQBdvFxJksZHBe1J2MjsSWDjTF4+oQKFbdkssIg==", + "dev": true, + "requires": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^2.4.2", + "cross-spawn": "^6.0.5", + "find-yarn-workspace-root": "^1.2.1", + "fs-extra": "^7.0.1", + "is-ci": "^2.0.0", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.0", + "rimraf": "^2.6.3", + "semver": "^5.6.0", + "slash": "^2.0.0", + "tmp": "^0.0.33" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", diff --git a/ReactNativeClient/package.json b/ReactNativeClient/package.json index d3fea9040..5fbc5f153 100644 --- a/ReactNativeClient/package.json +++ b/ReactNativeClient/package.json @@ -8,7 +8,7 @@ "start": "node node_modules/react-native/local-cli/cli.js start --reset-cache", "start-ios": "react-native run-ios", "start-android": "react-native run-android", - "postinstall": "jetify && npm run build", + "postinstall": "patch-package --patch-dir ../patches && jetify && npm run build", "build": "gulp build", "log-ios": "react-native-log-ios \"Joplin\"", "log-android": "adb logcat *:S ReactNative:V ReactNativeJS:V" @@ -26,6 +26,7 @@ "form-data": "^2.1.4", "highlight.js": "^9.17.1", "html-entities": "^1.2.1", + "htmlparser2": "^4.1.0", "jsc-android": "241213.1.0", "json-stringify-safe": "^5.0.1", "katex": "^0.11.1", @@ -46,7 +47,6 @@ "memory-cache": "^0.2.0", "mermaid": "^8.4.6", "moment": "^2.24.0", - "node-html-parser": "^1.2.4", "prop-types": "^15.6.0", "punycode": "^2.1.1", "query-string": "4.3.4", @@ -101,6 +101,7 @@ "gulp": "^4.0.2", "jetifier": "^1.6.5", "metro-react-native-babel-preset": "^0.54.1", + "patch-package": "^6.2.1", "react-test-renderer": "^16.8.3" } } diff --git a/patches/htmlparser2+4.1.0.patch b/patches/htmlparser2+4.1.0.patch new file mode 100644 index 000000000..87075ae84 --- /dev/null +++ b/patches/htmlparser2+4.1.0.patch @@ -0,0 +1,47 @@ +diff --git a/node_modules/htmlparser2/lib/Parser.js b/node_modules/htmlparser2/lib/Parser.js +index 44b4371..bcd7cc2 100644 +--- a/node_modules/htmlparser2/lib/Parser.js ++++ b/node_modules/htmlparser2/lib/Parser.js +@@ -212,6 +212,13 @@ var Parser = /** @class */ (function (_super) { + this._tagname = ""; + }; + Parser.prototype.onclosetag = function (name) { ++ // When this is true, the onclosetag event will always be emitted ++ // for closing tags (eg ) even if that tag was not previously ++ // open. This is needed because we reconstruct the HTML based on ++ // fragments that don't necessarily contain the opening tag. ++ // Without this patch, onopentagname would not be emitted, and ++ // so the closing tag would disappear from the output. ++ var alwaysClose = true; + this._updatePosition(1); + if (this._lowerCaseTagNames) { + name = name.toLowerCase(); +@@ -236,11 +243,15 @@ var Parser = /** @class */ (function (_super) { + else if (name === "p" && !this._options.xmlMode) { + this.onopentagname(name); + this._closeCurrentTag(); ++ } else if (!this._stack.length && alwaysClose) { ++ this._cbs.onclosetag(name); + } + } + else if (!this._options.xmlMode && (name === "br" || name === "p")) { + this.onopentagname(name); + this._closeCurrentTag(); ++ } else if (!this._stack.length && alwaysClose) { ++ this._cbs.onclosetag(name); + } + }; + Parser.prototype.onselfclosingtag = function () { +@@ -331,7 +342,11 @@ var Parser = /** @class */ (function (_super) { + }; + Parser.prototype.onend = function () { + if (this._cbs.onclosetag) { +- for (var i = this._stack.length; i > 0; this._cbs.onclosetag(this._stack[--i])) ++ // Prevent the parser from auto-closing tags. Since we deal with fragments that ++ // maybe contain the opening tag but not the closing one, we don't want that ++ // closing tag to be auto-added. ++ // ++ // for (var i = this._stack.length; i > 0; this._cbs.onclosetag(this._stack[--i])) + ; + } + if (this._cbs.onend) diff --git a/CliClient/patches/sax+1.2.4.patch b/patches/sax+1.2.4.patch similarity index 100% rename from CliClient/patches/sax+1.2.4.patch rename to patches/sax+1.2.4.patch