1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Merge branch 'release-2.12' into dev

This commit is contained in:
Laurent Cozic 2023-08-23 19:10:58 +01:00
commit 13b7e3657b
31 changed files with 298 additions and 218 deletions

View File

@ -133,6 +133,7 @@ packages/app-desktop/gui/ClipperConfigScreen.js
packages/app-desktop/gui/ConfigScreen/ButtonBar.js packages/app-desktop/gui/ConfigScreen/ButtonBar.js
packages/app-desktop/gui/ConfigScreen/ConfigScreen.js packages/app-desktop/gui/ConfigScreen/ConfigScreen.js
packages/app-desktop/gui/ConfigScreen/Sidebar.js packages/app-desktop/gui/ConfigScreen/Sidebar.js
packages/app-desktop/gui/ConfigScreen/controls/MissingPasswordHelpLink.js
packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.js packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js

1
.gitignore vendored
View File

@ -119,6 +119,7 @@ packages/app-desktop/gui/ClipperConfigScreen.js
packages/app-desktop/gui/ConfigScreen/ButtonBar.js packages/app-desktop/gui/ConfigScreen/ButtonBar.js
packages/app-desktop/gui/ConfigScreen/ConfigScreen.js packages/app-desktop/gui/ConfigScreen/ConfigScreen.js
packages/app-desktop/gui/ConfigScreen/Sidebar.js packages/app-desktop/gui/ConfigScreen/Sidebar.js
packages/app-desktop/gui/ConfigScreen/controls/MissingPasswordHelpLink.js
packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.js packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js

View File

@ -35,7 +35,7 @@
], ],
"owner": "Laurent Cozic" "owner": "Laurent Cozic"
}, },
"version": "2.12.0", "version": "2.12.1",
"bin": "./main.js", "bin": "./main.js",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"

View File

@ -21,8 +21,7 @@ import getDefaultPluginsInfo from '@joplin/lib/services/plugins/defaultPlugins/d
import JoplinCloudConfigScreen from '../JoplinCloudConfigScreen'; import JoplinCloudConfigScreen from '../JoplinCloudConfigScreen';
import ToggleAdvancedSettingsButton from './controls/ToggleAdvancedSettingsButton'; import ToggleAdvancedSettingsButton from './controls/ToggleAdvancedSettingsButton';
import shouldShowMissingPasswordWarning from '@joplin/lib/components/shared/config/shouldShowMissingPasswordWarning'; import shouldShowMissingPasswordWarning from '@joplin/lib/components/shared/config/shouldShowMissingPasswordWarning';
import shim from '@joplin/lib/shim'; import MacOSMissingPasswordHelpLink from './controls/MissingPasswordHelpLink';
import StyledLink from '../style/StyledLink';
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen'); const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');
const settingKeyToControl: any = { const settingKeyToControl: any = {
@ -190,25 +189,14 @@ class ConfigScreenComponent extends React.Component<any, any> {
// saved yet). // saved yet).
const matchesSavedTarget = settings['sync.target'] === this.props.settings['sync.target']; const matchesSavedTarget = settings['sync.target'] === this.props.settings['sync.target'];
if (matchesSavedTarget && shouldShowMissingPasswordWarning(settings['sync.target'], settings)) { if (matchesSavedTarget && shouldShowMissingPasswordWarning(settings['sync.target'], settings)) {
const openMissingPasswordFAQ = () =>
bridge().openExternal('https://joplinapp.org/faq#why-did-my-sync-and-encryption-passwords-disappear-after-updating-joplin');
const macInfoLink = (
<StyledLink href="#"
onClick={openMissingPasswordFAQ}
style={theme.linkStyle}
>
{_('Help')}
</StyledLink>
);
// The FAQ section related to missing passwords is specific to MacOS/ARM -- only show it
// in that case.
const showMacInfoLink = shim.isMac() && process.arch === 'arm64';
settingComps.push( settingComps.push(
<p key='missing-password-warning' style={warningStyle}> <p key='missing-password-warning' style={warningStyle}>
{_('Warning: Missing password.')}{' '}{showMacInfoLink ? macInfoLink : null} {_('%s: Missing password.', _('Warning'))}
{' '}
<MacOSMissingPasswordHelpLink
theme={theme}
text={_('Help')}
/>
</p>, </p>,
); );
} }

View File

@ -0,0 +1,35 @@
import * as React from 'react';
import shim from '@joplin/lib/shim';
import bridge from '../../../services/bridge';
import StyledLink from '../../style/StyledLink';
interface Props {
theme: any;
text: string;
}
const openMissingPasswordFAQ = () =>
bridge().openExternal('https://joplinapp.org/faq#why-did-my-sync-and-encryption-passwords-disappear-after-updating-joplin');
// A link to a specific part of the FAQ related to passwords being cleared when upgrading
// to a MacOS/ARM release.
const MacOSMissingPasswordHelpLink: React.FunctionComponent<Props> = props => {
const macInfoLink = (
<StyledLink href="#"
onClick={openMissingPasswordFAQ}
style={props.theme.linkStyle}
>
{props.text}
</StyledLink>
);
// The FAQ section related to missing passwords is specific to MacOS/ARM -- only show it
// in that case.
const newArchitectureReleasedRecently = Date.now() <= Date.UTC(2023, 11); // 11 = December
const showMacInfoLink = shim.isMac() && process.arch === 'arm64' && newArchitectureReleasedRecently;
return showMacInfoLink ? macInfoLink : null;
};
export default MacOSMissingPasswordHelpLink;

View File

@ -17,6 +17,7 @@ import Setting from '@joplin/lib/models/Setting';
import CommandService from '@joplin/lib/services/CommandService'; import CommandService from '@joplin/lib/services/CommandService';
import { PublicPrivateKeyPair } from '@joplin/lib/services/e2ee/ppk'; import { PublicPrivateKeyPair } from '@joplin/lib/services/e2ee/ppk';
import ToggleAdvancedSettingsButton from '../ConfigScreen/controls/ToggleAdvancedSettingsButton'; import ToggleAdvancedSettingsButton from '../ConfigScreen/controls/ToggleAdvancedSettingsButton';
import MacOSMissingPasswordHelpLink from '../ConfigScreen/controls/MissingPasswordHelpLink';
interface Props { interface Props {
themeId: any; themeId: any;
@ -252,7 +253,16 @@ const EncryptionConfigScreen = (props: Props) => {
const buttonTitle = CommandService.instance().label('openMasterPasswordDialog'); const buttonTitle = CommandService.instance().label('openMasterPasswordDialog');
const needPasswordMessage = !needMasterPassword ? null : ( const needPasswordMessage = !needMasterPassword ? null : (
<p className="needpassword">{_('Your password is needed to decrypt some of your data.')}<br/>{_('Please click on "%s" to proceed, or set the passwords in the "%s" list below.', buttonTitle, _('Encryption keys'))}</p> <p className="needpassword">
{_('Your password is needed to decrypt some of your data.')}
<br/>
{_('Please click on "%s" to proceed, or set the passwords in the "%s" list below.', buttonTitle, _('Encryption keys'))}
<br/>
<MacOSMissingPasswordHelpLink
theme={theme}
text={_('%s: Missing password', _('Help'))}
/>
</p>
); );
return ( return (

View File

@ -3,7 +3,8 @@ import shim from '@joplin/lib/shim';
import { _ } from '@joplin/lib/locale'; import { _ } from '@joplin/lib/locale';
import bridge from '../../../services/bridge'; import bridge from '../../../services/bridge';
import { openItemById } from '../../NoteEditor/utils/contextMenu'; import { openItemById } from '../../NoteEditor/utils/contextMenu';
const { parseResourceUrl, urlProtocol, fileUriToPath } = require('@joplin/lib/urlUtils'); const { parseResourceUrl, urlProtocol } = require('@joplin/lib/urlUtils');
import { fileUriToPath } from '@joplin/utils/url';
const { urlDecode } = require('@joplin/lib/string-utils'); const { urlDecode } = require('@joplin/lib/string-utils');
export const declaration: CommandDeclaration = { export const declaration: CommandDeclaration = {

View File

@ -1,10 +1,15 @@
import Setting from '@joplin/lib/models/Setting';
import { processPastedHtml } from './resourceHandling'; import { processPastedHtml } from './resourceHandling';
describe('resourceHandling', () => { describe('resourceHandling', () => {
it('should sanitize pasted HTML', async () => { it('should sanitize pasted HTML', async () => {
Setting.setConstant('resourceDir', '/home/.config/joplin/resources');
const testCases = [ const testCases = [
['Test: <style onload="evil()"></style>', 'Test: <style></style>'], ['Test: <style onload="evil()"></style>', 'Test: <style></style>'],
['<a href="javascript: alert()">test</a>', '<a href="#">test</a>'], ['<a href="javascript: alert()">test</a>', '<a href="#">test</a>'],
['<a href="file:///home/.config/joplin/resources/test.pdf">test</a>', '<a href="file:///home/.config/joplin/resources/test.pdf">test</a>'],
['<a href="file:///etc/passwd">evil.pdf</a>', '<a href="#">evil.pdf</a>'],
['<script >evil()</script>', ''], ['<script >evil()</script>', ''],
['<script>evil()</script>', ''], ['<script>evil()</script>', ''],
[ [

View File

@ -8,7 +8,7 @@ import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
import htmlUtils from '@joplin/lib/htmlUtils'; import htmlUtils from '@joplin/lib/htmlUtils';
import rendererHtmlUtils, { extractHtmlBody } from '@joplin/renderer/htmlUtils'; import rendererHtmlUtils, { extractHtmlBody } from '@joplin/renderer/htmlUtils';
import Logger from '@joplin/utils/Logger'; import Logger from '@joplin/utils/Logger';
const { fileUriToPath } = require('@joplin/lib/urlUtils'); import { fileUriToPath } from '@joplin/utils/url';
const joplinRendererUtils = require('@joplin/renderer').utils; const joplinRendererUtils = require('@joplin/renderer').utils;
const { clipboard } = require('electron'); const { clipboard } = require('electron');
const mimeUtils = require('@joplin/lib/mime-utils.js').mime; const mimeUtils = require('@joplin/lib/mime-utils.js').mime;
@ -179,6 +179,8 @@ export async function processPastedHtml(html: string) {
return extractHtmlBody(rendererHtmlUtils.sanitizeHtml( return extractHtmlBody(rendererHtmlUtils.sanitizeHtml(
htmlUtils.replaceImageUrls(html, (src: string) => { htmlUtils.replaceImageUrls(html, (src: string) => {
return mappedResources[src]; return mappedResources[src];
}), }), {
allowedFilePrefixes: [Setting.value('resourceDir')],
},
)); ));
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@joplin/app-desktop", "name": "@joplin/app-desktop",
"version": "2.12.11", "version": "2.12.14",
"description": "Joplin for Desktop", "description": "Joplin for Desktop",
"main": "main.js", "main": "main.js",
"private": true, "private": true,

View File

@ -1,7 +1,7 @@
{ {
"name": "@joplin/fork-htmlparser2", "name": "@joplin/fork-htmlparser2",
"description": "Fast & forgiving HTML/XML/RSS parser", "description": "Fast & forgiving HTML/XML/RSS parser",
"version": "4.1.45", "version": "4.1.46",
"author": "Felix Boehm <me@feedic.com>", "author": "Felix Boehm <me@feedic.com>",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@ -2,7 +2,7 @@
"name": "@joplin/fork-sax", "name": "@joplin/fork-sax",
"description": "An evented streaming XML parser in JavaScript", "description": "An evented streaming XML parser in JavaScript",
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)", "author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
"version": "1.2.49", "version": "1.2.50",
"main": "lib/sax.js", "main": "lib/sax.js",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@ -1,6 +1,6 @@
{ {
"name": "@joplin/fork-uslug", "name": "@joplin/fork-uslug",
"version": "1.0.10", "version": "1.0.11",
"description": "A permissive slug generator that works with unicode.", "description": "A permissive slug generator that works with unicode.",
"author": "Jeremy Selier <jerem.selier@gmail.com>", "author": "Jeremy Selier <jerem.selier@gmail.com>",
"publishConfig": { "publishConfig": {

View File

@ -1,6 +1,7 @@
{ {
"name": "generate-plugin-doc", "name": "generate-plugin-doc",
"packageManager": "yarn@3.6.0", "packageManager": "yarn@3.6.0",
"private": true,
"scripts": { "scripts": {
"buildPluginDoc_": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme '../../Assets/PluginDocTheme/' --readme '../../Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out ../../../joplin-website/docs/api/references/plugin_api ../lib/services/plugins/api/" "buildPluginDoc_": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme '../../Assets/PluginDocTheme/' --readme '../../Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out ../../../joplin-website/docs/api/references/plugin_api ../lib/services/plugins/api/"
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "@joplin/htmlpack", "name": "@joplin/htmlpack",
"version": "2.12.0", "version": "2.12.1",
"description": "Pack an HTML file and all its linked resources into a single HTML file", "description": "Pack an HTML file and all its linked resources into a single HTML file",
"main": "dist/index.js", "main": "dist/index.js",
"types": "src/index.ts", "types": "src/index.ts",
@ -14,7 +14,7 @@
"author": "Laurent Cozic", "author": "Laurent Cozic",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@joplin/fork-htmlparser2": "^4.1.45", "@joplin/fork-htmlparser2": "^4.1.46",
"css": "3.0.0", "css": "3.0.0",
"datauri": "4.1.0", "datauri": "4.1.0",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",

View File

@ -1,6 +1,6 @@
{ {
"name": "@joplin/lib", "name": "@joplin/lib",
"version": "2.12.0", "version": "2.12.1",
"description": "Joplin Core library", "description": "Joplin Core library",
"author": "Laurent Cozic", "author": "Laurent Cozic",
"homepage": "", "homepage": "",
@ -31,14 +31,14 @@
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "3.296.0", "@aws-sdk/client-s3": "3.296.0",
"@aws-sdk/s3-request-presigner": "3.296.0", "@aws-sdk/s3-request-presigner": "3.296.0",
"@joplin/fork-htmlparser2": "^4.1.44", "@joplin/fork-htmlparser2": "^4.1.46",
"@joplin/fork-sax": "^1.2.48", "@joplin/fork-sax": "^1.2.50",
"@joplin/fork-uslug": "^1.0.9", "@joplin/fork-uslug": "^1.0.11",
"@joplin/htmlpack": "~2.12", "@joplin/htmlpack": "^2.12.1",
"@joplin/renderer": "~2.12", "@joplin/renderer": "^2.12.1",
"@joplin/turndown": "^4.0.66", "@joplin/turndown": "^4.0.68",
"@joplin/turndown-plugin-gfm": "^1.0.48", "@joplin/turndown-plugin-gfm": "^1.0.50",
"@joplin/utils": "~2.12", "@joplin/utils": "^2.12.1",
"@types/nanoid": "3.0.0", "@types/nanoid": "3.0.0",
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"base-64": "1.0.0", "base-64": "1.0.0",

View File

@ -24,9 +24,9 @@ import * as ArrayUtils from '../../../ArrayUtils';
import Logger from '@joplin/utils/Logger'; import Logger from '@joplin/utils/Logger';
const { mimeTypeFromHeaders } = require('../../../net-utils'); const { mimeTypeFromHeaders } = require('../../../net-utils');
const { fileExtension, safeFileExtension, safeFilename, filename } = require('../../../path-utils'); const { fileExtension, safeFileExtension, safeFilename, filename } = require('../../../path-utils');
const { fileUriToPath } = require('../../../urlUtils');
const { MarkupToHtml } = require('@joplin/renderer'); const { MarkupToHtml } = require('@joplin/renderer');
const { ErrorNotFound } = require('../utils/errors'); const { ErrorNotFound } = require('../utils/errors');
import { fileUriToPath } from '@joplin/utils/url';
const logger = Logger.create('routes/notes'); const logger = Logger.create('routes/notes');

View File

@ -105,101 +105,4 @@ urlUtils.objectToQueryString = function(query) {
return queryString; return queryString;
}; };
// This is a modified version of the file-uri-to-path package:
//
// - It removes the dependency to the "path" package, which wouldn't work with
// React Native.
//
// - It always returns paths with forward slashes "/". This is normally handled
// properly everywhere.
//
// - Adds the "platform" parameter to optionall return paths with "\" for win32
function fileUriToPath_(uri, platform) {
const sep = '/';
if (
typeof uri !== 'string' ||
uri.length <= 7 ||
uri.substring(0, 7) !== 'file://'
) {
throw new TypeError(
'must pass in a file:// URI to convert to a file path',
);
}
const rest = decodeURI(uri.substring(7));
const firstSlash = rest.indexOf('/');
let host = rest.substring(0, firstSlash);
let path = rest.substring(firstSlash + 1);
// 2. Scheme Definition
// As a special case, <host> can be the string "localhost" or the empty
// string; this is interpreted as "the machine from which the URL is
// being interpreted".
if (host === 'localhost') {
host = '';
}
if (host) {
host = sep + sep + host;
}
// 3.2 Drives, drive letters, mount points, file system root
// Drive letters are mapped into the top of a file URI in various ways,
// depending on the implementation; some applications substitute
// vertical bar ("|") for the colon after the drive letter, yielding
// "file:///c|/tmp/test.txt". In some cases, the colon is left
// unchanged, as in "file:///c:/tmp/test.txt". In other cases, the
// colon is simply omitted, as in "file:///c/tmp/test.txt".
path = path.replace(/^(.+)\|/, '$1:');
// for Windows, we need to invert the path separators from what a URI uses
// if (sep === '\\') {
// path = path.replace(/\//g, '\\');
// }
if (/^.+:/.test(path)) {
// has Windows drive at beginning of path
} else {
// unix path…
path = sep + path;
}
if (platform === 'win32') {
return (host + path).replace(/\//g, '\\');
} else {
return host + path;
}
}
urlUtils.fileUriToPath = (path, platform = 'linux') => {
const output = fileUriToPath_(path, platform);
// The file-uri-to-path module converts Windows path such as
//
// file://c:/autoexec.bat => \\c:\autoexec.bat
//
// Probably because a file:// that starts with only two slashes is not
// quite valid. If we use three slashes, it works:
//
// file:///c:/autoexec.bat => c:\autoexec.bat
//
// However there are various places in the app where we can find
// paths with only two slashes because paths are often constructed
// as `file://${resourcePath}` - which works in all OSes except
// Windows.
//
// So here we introduce a special case - if we detect that we have
// an invalid Windows path that starts with \\x:, we just remove
// the first two backslashes.
//
// https://github.com/laurent22/joplin/issues/5693
if (output.match(/^\/\/[a-zA-Z]:/)) {
return output.substr(2);
}
return output;
};
module.exports = urlUtils; module.exports = urlUtils;

View File

@ -71,30 +71,4 @@ describe('urlUtils', () => {
} }
})); }));
it('should convert a file URI to a file path', (async () => {
// Tests imported from https://github.com/TooTallNate/file-uri-to-path/tree/master/test
const testCases = {
'file://host/path': '//host/path',
'file://localhost/etc/fstab': '/etc/fstab',
'file:///etc/fstab': '/etc/fstab',
'file:///c:/WINDOWS/clock.avi': 'c:/WINDOWS/clock.avi',
'file://localhost/c|/WINDOWS/clock.avi': 'c:/WINDOWS/clock.avi',
'file:///c|/WINDOWS/clock.avi': 'c:/WINDOWS/clock.avi',
'file://localhost/c:/WINDOWS/clock.avi': 'c:/WINDOWS/clock.avi',
'file://hostname/path/to/the%20file.txt': '//hostname/path/to/the file.txt',
'file:///c:/path/to/the%20file.txt': 'c:/path/to/the file.txt',
'file:///C:/Documents%20and%20Settings/davris/FileSchemeURIs.doc': 'C:/Documents and Settings/davris/FileSchemeURIs.doc',
'file:///C:/caf%C3%A9/%C3%A5r/d%C3%BCnn/%E7%89%9B%E9%93%83/Ph%E1%BB%9F/%F0%9F%98%B5.exe': 'C:/café/år/dünn/牛铃/Phở/😵.exe',
};
for (const [input, expected] of Object.entries(testCases)) {
const actual = urlUtils.fileUriToPath(input);
expect(actual).toBe(expected);
}
expect(urlUtils.fileUriToPath('file://c:/not/quite/right')).toBe('c:/not/quite/right');
expect(urlUtils.fileUriToPath('file:///d:/better')).toBe('d:/better');
expect(urlUtils.fileUriToPath('file:///c:/AUTOEXEC.BAT', 'win32')).toBe('c:\\AUTOEXEC.BAT');
}));
}); });

View File

@ -1,6 +1,6 @@
{ {
"name": "@joplin/plugin-repo-cli", "name": "@joplin/plugin-repo-cli",
"version": "2.12.0", "version": "2.12.1",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"bin": "./dist/index.js", "bin": "./dist/index.js",
@ -18,9 +18,9 @@
"author": "", "author": "",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@joplin/lib": "~2.12", "@joplin/lib": "^2.12.1",
"@joplin/tools": "~2.12", "@joplin/tools": "^2.12.1",
"@joplin/utils": "~2.12", "@joplin/utils": "^2.12.1",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"gh-release-assets": "2.0.1", "gh-release-assets": "2.0.1",
"node-fetch": "2.6.7", "node-fetch": "2.6.7",

View File

@ -1,6 +1,6 @@
{ {
"name": "@joplin/react-native-saf-x", "name": "@joplin/react-native-saf-x",
"version": "2.12.0", "version": "2.12.1",
"description": "a module to help work with scoped storages on android easily", "description": "a module to help work with scoped storages on android easily",
"main": "src/index", "main": "src/index",
"react-native": "src/index", "react-native": "src/index",

View File

@ -1,5 +1,6 @@
const Entities = require('html-entities').AllHtmlEntities; const Entities = require('html-entities').AllHtmlEntities;
const htmlentities = new Entities().encode; const htmlentities = new Entities().encode;
import { fileUriToPath } from '@joplin/utils/url';
const htmlparser2 = require('@joplin/fork-htmlparser2'); const htmlparser2 = require('@joplin/fork-htmlparser2');
// [\s\S] instead of . for multiline matching // [\s\S] instead of . for multiline matching
@ -31,7 +32,8 @@ const selfClosingElements = [
]; ];
interface SanitizeHtmlOptions { interface SanitizeHtmlOptions {
addNoMdConvClass: boolean; addNoMdConvClass?: boolean;
allowedFilePrefixes?: string[];
} }
export const attributesHtml = (attr: Record<string, string>) => { export const attributesHtml = (attr: Record<string, string>) => {
@ -157,20 +159,36 @@ class HtmlUtils {
.replace(/</g, '&lt;'); .replace(/</g, '&lt;');
} }
private isAcceptedUrl(url: string): boolean { private isAcceptedUrl(url: string, allowedFilePrefixes: string[]): boolean {
url = url.toLowerCase(); url = url.toLowerCase();
return url.startsWith('https://') || if (url.startsWith('https://') ||
url.startsWith('http://') || url.startsWith('http://') ||
url.startsWith('mailto://') || url.startsWith('mailto://') ||
// We also allow anchors but only with a specific set of a characters. // We also allow anchors but only with a specific set of a characters.
// Fixes https://github.com/laurent22/joplin/issues/8286 // Fixes https://github.com/laurent22/joplin/issues/8286
!!url.match(/^#[a-zA-Z0-9-]+$/); !!url.match(/^#[a-zA-Z0-9-]+$/)) return true;
if (url.startsWith('file://')) {
// We need to do a case insensitive comparison because the URL we
// get appears to be converted to lowercase somewhere. To be
// completely sure, we make it lowercase explicitely.
const filePath = fileUriToPath(url).toLowerCase();
for (const filePrefix of allowedFilePrefixes) {
if (filePath.startsWith(filePrefix.toLowerCase())) return true;
}
}
return false;
} }
public sanitizeHtml(html: string, options: SanitizeHtmlOptions = null) { public sanitizeHtml(html: string, options: SanitizeHtmlOptions = null) {
options = { // If true, adds a "jop-noMdConv" class to all the tags. options = {
// If true, adds a "jop-noMdConv" class to all the tags.
// It can be used afterwards to restore HTML tags in Markdown. // It can be used afterwards to restore HTML tags in Markdown.
addNoMdConvClass: false, ...options }; addNoMdConvClass: false,
allowedFilePrefixes: [],
...options,
};
const output: string[] = []; const output: string[] = [];
@ -247,7 +265,7 @@ class HtmlUtils {
// particular we want to exclude `javascript:` URLs. This // particular we want to exclude `javascript:` URLs. This
// applies to A tags, and also AREA ones but to be safe we don't // applies to A tags, and also AREA ones but to be safe we don't
// filter on the tag name and process all HREF attributes. // filter on the tag name and process all HREF attributes.
if ('href' in attrs && !this.isAcceptedUrl(attrs['href'])) { if ('href' in attrs && !this.isAcceptedUrl(attrs['href'], options.allowedFilePrefixes)) {
attrs['href'] = '#'; attrs['href'] = '#';
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@joplin/renderer", "name": "@joplin/renderer",
"version": "2.12.0", "version": "2.12.1",
"description": "The Joplin note renderer, used the mobile and desktop application", "description": "The Joplin note renderer, used the mobile and desktop application",
"repository": "https://github.com/laurent22/joplin/tree/dev/packages/renderer", "repository": "https://github.com/laurent22/joplin/tree/dev/packages/renderer",
"main": "index.js", "main": "index.js",
@ -26,8 +26,9 @@
"typescript": "5.1.3" "typescript": "5.1.3"
}, },
"dependencies": { "dependencies": {
"@joplin/fork-htmlparser2": "^4.1.44", "@joplin/fork-htmlparser2": "^4.1.46",
"@joplin/fork-uslug": "^1.0.9", "@joplin/fork-uslug": "^1.0.11",
"@joplin/utils": "~2.12",
"font-awesome-filetypes": "2.1.0", "font-awesome-filetypes": "2.1.0",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"highlight.js": "11.8.0", "highlight.js": "11.8.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "@joplin/tools", "name": "@joplin/tools",
"version": "2.12.0", "version": "2.12.1",
"description": "Various tools for Joplin", "description": "Various tools for Joplin",
"main": "index.js", "main": "index.js",
"author": "Laurent Cozic", "author": "Laurent Cozic",
@ -20,9 +20,9 @@
}, },
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@joplin/lib": "~2.12", "@joplin/lib": "^2.12.1",
"@joplin/renderer": "~2.12", "@joplin/renderer": "^2.12.1",
"@joplin/utils": "~2.12", "@joplin/utils": "^2.12.1",
"compare-versions": "3.6.0", "compare-versions": "3.6.0",
"dayjs": "1.11.9", "dayjs": "1.11.9",
"execa": "4.1.0", "execa": "4.1.0",
@ -42,7 +42,7 @@
"yargs": "17.7.2" "yargs": "17.7.2"
}, },
"devDependencies": { "devDependencies": {
"@joplin/fork-htmlparser2": "^4.1.44", "@joplin/fork-htmlparser2": "^4.1.46",
"@rmp135/sql-ts": "1.18.0", "@rmp135/sql-ts": "1.18.0",
"@types/fs-extra": "11.0.1", "@types/fs-extra": "11.0.1",
"@types/jest": "29.5.3", "@types/jest": "29.5.3",

View File

@ -4,7 +4,7 @@
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
"version": "1.0.49", "version": "1.0.50",
"author": "Dom Christie", "author": "Dom Christie",
"main": "lib/turndown-plugin-gfm.cjs.js", "main": "lib/turndown-plugin-gfm.cjs.js",
"devDependencies": { "devDependencies": {

View File

@ -1,7 +1,7 @@
{ {
"name": "@joplin/turndown", "name": "@joplin/turndown",
"description": "A library that converts HTML to Markdown", "description": "A library that converts HTML to Markdown",
"version": "4.0.67", "version": "4.0.68",
"author": "Dom Christie", "author": "Dom Christie",
"main": "lib/turndown.cjs.js", "main": "lib/turndown.cjs.js",
"publishConfig": { "publishConfig": {

View File

@ -1,16 +1,17 @@
{ {
"name": "@joplin/utils", "name": "@joplin/utils",
"version": "2.12.0", "version": "2.12.1",
"description": "Utilities for Joplin", "description": "Utilities for Joplin",
"repository": "https://github.com/laurent22/joplin/tree/dev/packages/utils", "repository": "https://github.com/laurent22/joplin/tree/dev/packages/utils",
"exports": { "exports": {
".": "./dist/index.js", ".": "./dist/index.js",
"./net": "./dist/net.js",
"./fs": "./dist/fs.js",
"./env": "./dist/env.js", "./env": "./dist/env.js",
"./types": "./dist/types.js", "./fs": "./dist/fs.js",
"./time": "./dist/time.js",
"./html": "./dist/html.js", "./html": "./dist/html.js",
"./net": "./dist/net.js",
"./time": "./dist/time.js",
"./types": "./dist/types.js",
"./url": "./dist/url.js",
"./Logger": "./dist/Logger.js" "./Logger": "./dist/Logger.js"
}, },
"publishConfig": { "publishConfig": {

View File

@ -0,0 +1,31 @@
import { fileUriToPath } from './url';
describe('utils/url', () => {
it('should convert a file URI to a file path', (async () => {
// Tests imported from https://github.com/TooTallNate/file-uri-to-path/tree/master/test
const testCases = {
'file://host/path': '//host/path',
'file://localhost/etc/fstab': '/etc/fstab',
'file:///etc/fstab': '/etc/fstab',
'file:///c:/WINDOWS/clock.avi': 'c:/WINDOWS/clock.avi',
'file://localhost/c|/WINDOWS/clock.avi': 'c:/WINDOWS/clock.avi',
'file:///c|/WINDOWS/clock.avi': 'c:/WINDOWS/clock.avi',
'file://localhost/c:/WINDOWS/clock.avi': 'c:/WINDOWS/clock.avi',
'file://hostname/path/to/the%20file.txt': '//hostname/path/to/the file.txt',
'file:///c:/path/to/the%20file.txt': 'c:/path/to/the file.txt',
'file:///C:/Documents%20and%20Settings/davris/FileSchemeURIs.doc': 'C:/Documents and Settings/davris/FileSchemeURIs.doc',
'file:///C:/caf%C3%A9/%C3%A5r/d%C3%BCnn/%E7%89%9B%E9%93%83/Ph%E1%BB%9F/%F0%9F%98%B5.exe': 'C:/café/år/dünn/牛铃/Phở/😵.exe',
};
for (const [input, expected] of Object.entries(testCases)) {
const actual = fileUriToPath(input);
expect(actual).toBe(expected);
}
expect(fileUriToPath('file://c:/not/quite/right')).toBe('c:/not/quite/right');
expect(fileUriToPath('file:///d:/better')).toBe('d:/better');
expect(fileUriToPath('file:///c:/AUTOEXEC.BAT', 'win32')).toBe('c:\\AUTOEXEC.BAT');
}));
});

98
packages/utils/url.ts Normal file
View File

@ -0,0 +1,98 @@
/* eslint-disable import/prefer-default-export */
// This is a modified version of the file-uri-to-path package:
//
// - It removes the dependency to the "path" package, which wouldn't work with
// React Native.
//
// - It always returns paths with forward slashes "/". This is normally handled
// properly everywhere.
//
// - Adds the "platform" parameter to optionall return paths with "\" for win32
function fileUriToPath_(uri: string, platform: string) {
const sep = '/';
if (
typeof uri !== 'string' ||
uri.length <= 7 ||
uri.substring(0, 7) !== 'file://'
) {
throw new TypeError(
'must pass in a file:// URI to convert to a file path',
);
}
const rest = decodeURI(uri.substring(7));
const firstSlash = rest.indexOf('/');
let host = rest.substring(0, firstSlash);
let path = rest.substring(firstSlash + 1);
// 2. Scheme Definition
// As a special case, <host> can be the string "localhost" or the empty
// string; this is interpreted as "the machine from which the URL is
// being interpreted".
if (host === 'localhost') {
host = '';
}
if (host) {
host = sep + sep + host;
}
// 3.2 Drives, drive letters, mount points, file system root
// Drive letters are mapped into the top of a file URI in various ways,
// depending on the implementation; some applications substitute
// vertical bar ("|") for the colon after the drive letter, yielding
// "file:///c|/tmp/test.txt". In some cases, the colon is left
// unchanged, as in "file:///c:/tmp/test.txt". In other cases, the
// colon is simply omitted, as in "file:///c/tmp/test.txt".
path = path.replace(/^(.+)\|/, '$1:');
// for Windows, we need to invert the path separators from what a URI uses
// if (sep === '\\') {
// path = path.replace(/\//g, '\\');
// }
if (/^.+:/.test(path)) {
// has Windows drive at beginning of path
} else {
// unix path…
path = sep + path;
}
if (platform === 'win32') {
return (host + path).replace(/\//g, '\\');
} else {
return host + path;
}
}
export const fileUriToPath = (path: string, platform = 'linux') => {
const output = fileUriToPath_(path, platform);
// The file-uri-to-path module converts Windows path such as
//
// file://c:/autoexec.bat => \\c:\autoexec.bat
//
// Probably because a file:// that starts with only two slashes is not
// quite valid. If we use three slashes, it works:
//
// file:///c:/autoexec.bat => c:\autoexec.bat
//
// However there are various places in the app where we can find
// paths with only two slashes because paths are often constructed
// as `file://${resourcePath}` - which works in all OSes except
// Windows.
//
// So here we introduce a special case - if we detect that we have
// an invalid Windows path that starts with \\x:, we just remove
// the first two backslashes.
//
// https://github.com/laurent22/joplin/issues/5693
if (output.match(/^\/\/[a-zA-Z]:/)) {
return output.substr(2);
}
return output;
};

View File

@ -1,5 +1,14 @@
# Joplin terminal app changelog # Joplin terminal app changelog
## [cli-v2.12.1](https://github.com/laurent22/joplin/releases/tag/cli-v2.12.1) - 2023-08-23T12:53:19Z
- New: Add support for share permissions (#8491)
- Improved: Allow importing Evernote task lists (#8440 by Rob Moffat)
- Improved: Rotating log files (#8376) (#5521 by [@hubert](https://github.com/hubert))
- Improved: Updated packages @rmp135/sql-ts (v1.18.0), buildTools, clean-html (v2), dayjs (v1.11.9), domhandler (v5), gettext-parser (v7.0.1), glob (v10.3.3), highlight.js (v11.8.0), jsdom (v22.1.0), sass (v1.63.6), sharp (v0.32.3), standard (v17.1.0), word-wrap (v1.2.4)
- Improved: WebDAV: Show a more descriptive error message when the password is empty (#8477) (#8466 by Henry Heino)
- Security: Prevent XSS when passing specially encoded string to a link (57b4198)
## [cli-v2.11.1](https://github.com/laurent22/joplin/releases/tag/cli-v2.11.1) - 2023-06-27T09:28:01Z ## [cli-v2.11.1](https://github.com/laurent22/joplin/releases/tag/cli-v2.11.1) - 2023-06-27T09:28:01Z
- Improved: Updated packages aws, buildTools, domutils (v3.1.0), fs-extra (v11.1.1), jsdom (v21.1.2), markdown-it-multimd-table (v4.2.2), nanoid (v3.3.6), node-persist (v3.1.3), open (v8.4.2), reselect (v4.1.8), sass (v1.62.1), sharp (v0.32.1), sqlite3 (v5.1.6), tar (v6.1.15), turndown (v7.1.2), yargs (v17.7.2) - Improved: Updated packages aws, buildTools, domutils (v3.1.0), fs-extra (v11.1.1), jsdom (v21.1.2), markdown-it-multimd-table (v4.2.2), nanoid (v3.3.6), node-persist (v3.1.3), open (v8.4.2), reselect (v4.1.8), sass (v1.62.1), sharp (v0.32.1), sqlite3 (v5.1.6), tar (v6.1.15), turndown (v7.1.2), yargs (v17.7.2)

View File

@ -4626,7 +4626,7 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@joplin/fork-htmlparser2@^4.1.44, @joplin/fork-htmlparser2@^4.1.45, @joplin/fork-htmlparser2@workspace:packages/fork-htmlparser2": "@joplin/fork-htmlparser2@^4.1.46, @joplin/fork-htmlparser2@workspace:packages/fork-htmlparser2":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@joplin/fork-htmlparser2@workspace:packages/fork-htmlparser2" resolution: "@joplin/fork-htmlparser2@workspace:packages/fork-htmlparser2"
dependencies: dependencies:
@ -4647,7 +4647,7 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@joplin/fork-sax@^1.2.48, @joplin/fork-sax@workspace:packages/fork-sax": "@joplin/fork-sax@^1.2.50, @joplin/fork-sax@workspace:packages/fork-sax":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@joplin/fork-sax@workspace:packages/fork-sax" resolution: "@joplin/fork-sax@workspace:packages/fork-sax"
dependencies: dependencies:
@ -4656,7 +4656,7 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@joplin/fork-uslug@^1.0.9, @joplin/fork-uslug@workspace:packages/fork-uslug": "@joplin/fork-uslug@^1.0.11, @joplin/fork-uslug@workspace:packages/fork-uslug":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@joplin/fork-uslug@workspace:packages/fork-uslug" resolution: "@joplin/fork-uslug@workspace:packages/fork-uslug"
dependencies: dependencies:
@ -4666,11 +4666,11 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@joplin/htmlpack@workspace:packages/htmlpack, @joplin/htmlpack@~2.12": "@joplin/htmlpack@^2.12.1, @joplin/htmlpack@workspace:packages/htmlpack":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@joplin/htmlpack@workspace:packages/htmlpack" resolution: "@joplin/htmlpack@workspace:packages/htmlpack"
dependencies: dependencies:
"@joplin/fork-htmlparser2": ^4.1.45 "@joplin/fork-htmlparser2": ^4.1.46
"@types/fs-extra": 11.0.1 "@types/fs-extra": 11.0.1
css: 3.0.0 css: 3.0.0
datauri: 4.1.0 datauri: 4.1.0
@ -4679,20 +4679,20 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@joplin/lib@workspace:packages/lib, @joplin/lib@~2.12": "@joplin/lib@^2.12.1, @joplin/lib@workspace:packages/lib, @joplin/lib@~2.12":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@joplin/lib@workspace:packages/lib" resolution: "@joplin/lib@workspace:packages/lib"
dependencies: dependencies:
"@aws-sdk/client-s3": 3.296.0 "@aws-sdk/client-s3": 3.296.0
"@aws-sdk/s3-request-presigner": 3.296.0 "@aws-sdk/s3-request-presigner": 3.296.0
"@joplin/fork-htmlparser2": ^4.1.44 "@joplin/fork-htmlparser2": ^4.1.46
"@joplin/fork-sax": ^1.2.48 "@joplin/fork-sax": ^1.2.50
"@joplin/fork-uslug": ^1.0.9 "@joplin/fork-uslug": ^1.0.11
"@joplin/htmlpack": ~2.12 "@joplin/htmlpack": ^2.12.1
"@joplin/renderer": ~2.12 "@joplin/renderer": ^2.12.1
"@joplin/turndown": ^4.0.66 "@joplin/turndown": ^4.0.68
"@joplin/turndown-plugin-gfm": ^1.0.48 "@joplin/turndown-plugin-gfm": ^1.0.50
"@joplin/utils": ~2.12 "@joplin/utils": ^2.12.1
"@types/fs-extra": 11.0.1 "@types/fs-extra": 11.0.1
"@types/jest": 29.5.3 "@types/jest": 29.5.3
"@types/js-yaml": 4.0.5 "@types/js-yaml": 4.0.5
@ -4796,9 +4796,9 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@joplin/plugin-repo-cli@workspace:packages/plugin-repo-cli" resolution: "@joplin/plugin-repo-cli@workspace:packages/plugin-repo-cli"
dependencies: dependencies:
"@joplin/lib": ~2.12 "@joplin/lib": ^2.12.1
"@joplin/tools": ~2.12 "@joplin/tools": ^2.12.1
"@joplin/utils": ~2.12 "@joplin/utils": ^2.12.1
"@types/fs-extra": 11.0.1 "@types/fs-extra": 11.0.1
"@types/jest": 29.5.3 "@types/jest": 29.5.3
"@types/node": 18.16.18 "@types/node": 18.16.18
@ -4847,12 +4847,13 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@joplin/renderer@workspace:packages/renderer, @joplin/renderer@~2.12": "@joplin/renderer@^2.12.1, @joplin/renderer@workspace:packages/renderer, @joplin/renderer@~2.12":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@joplin/renderer@workspace:packages/renderer" resolution: "@joplin/renderer@workspace:packages/renderer"
dependencies: dependencies:
"@joplin/fork-htmlparser2": ^4.1.44 "@joplin/fork-htmlparser2": ^4.1.46
"@joplin/fork-uslug": ^1.0.9 "@joplin/fork-uslug": ^1.0.11
"@joplin/utils": ~2.12
"@types/jest": 29.5.3 "@types/jest": 29.5.3
"@types/node": 18.16.18 "@types/node": 18.16.18
font-awesome-filetypes: 2.1.0 font-awesome-filetypes: 2.1.0
@ -4945,14 +4946,14 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@joplin/tools@workspace:packages/tools, @joplin/tools@~2.12": "@joplin/tools@^2.12.1, @joplin/tools@workspace:packages/tools, @joplin/tools@~2.12":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@joplin/tools@workspace:packages/tools" resolution: "@joplin/tools@workspace:packages/tools"
dependencies: dependencies:
"@joplin/fork-htmlparser2": ^4.1.44 "@joplin/fork-htmlparser2": ^4.1.46
"@joplin/lib": ~2.12 "@joplin/lib": ^2.12.1
"@joplin/renderer": ~2.12 "@joplin/renderer": ^2.12.1
"@joplin/utils": ~2.12 "@joplin/utils": ^2.12.1
"@rmp135/sql-ts": 1.18.0 "@rmp135/sql-ts": 1.18.0
"@types/fs-extra": 11.0.1 "@types/fs-extra": 11.0.1
"@types/jest": 29.5.3 "@types/jest": 29.5.3
@ -4988,7 +4989,7 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@joplin/turndown-plugin-gfm@^1.0.48, @joplin/turndown-plugin-gfm@workspace:packages/turndown-plugin-gfm": "@joplin/turndown-plugin-gfm@^1.0.50, @joplin/turndown-plugin-gfm@workspace:packages/turndown-plugin-gfm":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@joplin/turndown-plugin-gfm@workspace:packages/turndown-plugin-gfm" resolution: "@joplin/turndown-plugin-gfm@workspace:packages/turndown-plugin-gfm"
dependencies: dependencies:
@ -5000,7 +5001,7 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@joplin/turndown@^4.0.66, @joplin/turndown@workspace:packages/turndown": "@joplin/turndown@^4.0.68, @joplin/turndown@workspace:packages/turndown":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@joplin/turndown@workspace:packages/turndown" resolution: "@joplin/turndown@workspace:packages/turndown"
dependencies: dependencies:
@ -5017,7 +5018,7 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@joplin/utils@workspace:packages/utils, @joplin/utils@~2.12": "@joplin/utils@^2.12.1, @joplin/utils@workspace:packages/utils, @joplin/utils@~2.12":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@joplin/utils@workspace:packages/utils" resolution: "@joplin/utils@workspace:packages/utils"
dependencies: dependencies: