1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-11-29 22:48:10 +02:00

Mobile: Rich Text Editor: Avoid rendering links with unknown protocols (#12943)

Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
This commit is contained in:
Henry Heino
2025-08-26 00:49:26 -07:00
committed by GitHub
parent ac05b7d389
commit c99780db1b
3 changed files with 71 additions and 5 deletions

View File

@@ -288,6 +288,26 @@ describe('RichTextEditor', () => {
});
});
it('should avoid rendering URLs with unknown protocols', async () => {
let body = '[link](unknown://test)';
render(<WrappedEditor
noteBody={body}
onBodyChange={newBody => { body = newBody; }}
/>);
const renderedLink = await findElement<HTMLAnchorElement>('a[href][data-original-href]');
expect(renderedLink.getAttribute('href')).toBe('#');
expect(renderedLink.getAttribute('data-original-href')).toBe('unknown://test');
const window = await getEditorWindow();
mockTyping(window, ' testing');
await waitFor(async () => {
expect(body.trim()).toBe('[link](unknown://test) testing');
});
});
it.each([
MarkupLanguage.Markdown, MarkupLanguage.Html,
])('should preserve image attachments on edit (case %#)', async (markupLanguage) => {

View File

@@ -10,6 +10,24 @@ import convertHtmlToMarkdown from './convertHtmlToMarkdown';
import { ExportedWebViewGlobals as MarkdownEditorWebViewGlobals } from '../../markdownEditorBundle/types';
import { EditorEventType } from '@joplin/editor/events';
const postprocessHtml = (html: HTMLElement) => {
// Fix resource URLs
const resources = html.querySelectorAll<HTMLImageElement>('img[data-resource-id]');
for (const resource of resources) {
const resourceId = resource.getAttribute('data-resource-id');
resource.src = `:/${resourceId}`;
}
// Restore HREFs
const links = html.querySelectorAll<HTMLAnchorElement>('a[href="#"][data-original-href]');
for (const link of links) {
link.href = link.getAttribute('data-original-href');
link.removeAttribute('data-original-href');
}
return html;
};
const wrapHtmlForMarkdownConversion = (html: HTMLElement) => {
// Add a container element -- when converting to HTML, Turndown
// sometimes doesn't process the toplevel element in the same way

View File

@@ -3,6 +3,8 @@ import { nodeSpecs as joplinEditableNodes } from './plugins/joplinEditablePlugin
import { tableNodes } from 'prosemirror-tables';
import { nodeSpecs as listNodes } from './plugins/listPlugin';
import { nodeSpecs as resourcePlaceholderNodes } from './plugins/resourcePlaceholderPlugin';
import { hasProtocol } from '@joplin/utils/url';
import { isResourceUrl } from '@joplin/lib/models/utils/resourceUtils';
import { nodeSpecs as detailsNodes } from './plugins/detailsPlugin';
// For reference, see:
@@ -259,8 +261,15 @@ const marks = {
tag: 'a[href]',
getAttrs: node => {
const resourceId = node.getAttribute('data-resource-id');
const href = node.getAttribute('href');
let href = node.getAttribute('href');
const isResourceLink = resourceId && href === '#';
if (isResourceLink) {
href = `:/${resourceId}`;
}
if (href === '#' && node.hasAttribute('data-original-href')) {
href = node.getAttribute('data-original-href');
}
return {
href: isResourceLink ? `:/${resourceId}` : href,
@@ -269,10 +278,29 @@ const marks = {
};
},
}],
toDOM: node => [
'a',
{ href: node.attrs.href, title: node.attrs.title, 'data-resource-id': node.attrs.dataResourceId },
],
toDOM: node => {
const isSafeForRendering = (href: string) => {
return hasProtocol(href, ['http', 'https', 'joplin']) || isResourceUrl(href);
};
// Avoid rendering URLs with unknown protocols (avoid rendering or pasting unsafe HREFs).
// Note that URL click handling is handled elsewhere and does not use the HTML "href" attribute.
// However "href" may be used by the right-click menu on web:
const safeHref = isSafeForRendering(node.attrs.href) ? node.attrs.href : '#';
return [
'a',
{
href: safeHref,
...(safeHref !== node.attrs.href ? {
'data-original-href': node.attrs.href,
} : {}),
title: node.attrs.title,
'data-resource-id': node.attrs.dataResourceId,
},
];
},
},
} satisfies Record<string, MarkSpec>;