Skip to content

Commit 03e8416

Browse files
committed
fix(NcRichText)!: do not render non-resolved relative links
- allow tel: and mail: to reduce breaking changes Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
1 parent cdf9171 commit 03e8416

2 files changed

Lines changed: 36 additions & 11 deletions

File tree

src/components/NcRichText/NcRichText.vue

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,16 +631,26 @@ export default {
631631
if (String(type) === 'a') {
632632
const route = getRoute(this.$router, props.href)
633633
if (route) {
634+
// Resolved link to this app; render RouterLink
634635
delete props.href
635636
delete props.target
636637
637638
return h(RouterLink, {
638639
...props,
639640
to: route,
640641
}, { default: () => children })
641-
} else {
642+
}
643+
644+
const isAllowedScheme = /^(https?:\/\/|tel:|mailto:)/.test(props.href)
645+
if (isAllowedScheme) {
646+
// External link; render normally, open in the new tab
642647
props.href = props.href.trim()
643648
return h(NcRichTextExternalLink, props, children)
649+
} else {
650+
// Unresolved relative link that does not belong to this app; render only children
651+
delete props.href
652+
delete props.target
653+
return h('span', props, children)
644654
}
645655
}
646656
return h(type, props, children)

tests/component/components/NcRichText/markown-rendering.spec.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -420,33 +420,48 @@ test.describe('inline code', () => {
420420
})
421421

422422
test.describe('links', () => {
423+
const TestRouteComponent = {
424+
template: '<div />',
425+
}
426+
423427
const testLink = (key: string, { text, href = text, name = text }) => {
424428
test(key, async ({ mount }) => {
425429
const component = await mount(NcRichText, {
426430
props: {
427431
text,
428432
useExtendedMarkdown: true,
429433
},
434+
hooksConfig: {
435+
routes: [{ path: '/world', component: TestRouteComponent }],
436+
},
430437
})
431438
await expect(component.getByRole('link', { name }))
432439
.toHaveAttribute('href', href)
433440
})
434441
}
435442
testLink('autolink', { text: 'https://autolink.me' })
436-
testLink('relative link', { text: '[hello](world)', href: 'world', name: 'hello' })
443+
testLink('relative link', { text: '[hello](/world)', href: '/world', name: 'hello' })
437444
testLink('absolute link', { text: '[hello](https://nextcloud.com)', href: 'https://nextcloud.com', name: 'hello' })
438445
testLink('tel link', { text: '[hello](tel:+49123456789)', href: 'tel:+49123456789', name: 'hello' })
446+
testLink('mailto link', { text: '[hello](mailto:+49123456789)', href: 'mailto:+49123456789', name: 'hello' })
439447

440-
test('no link to unknown protocols', async ({ mount }) => {
441-
const component = await mount(NcRichText, {
442-
props: {
443-
text: '[link](other:proto)',
444-
useExtendedMarkdown: true,
445-
},
448+
const testNoLink = (key: string, { text, name = text }) => {
449+
test(key, async ({ mount }) => {
450+
const component = await mount(NcRichText, {
451+
props: {
452+
text,
453+
useExtendedMarkdown: true,
454+
},
455+
hooksConfig: {
456+
routes: [{ path: '/world', component: TestRouteComponent }],
457+
},
458+
})
459+
await expect(component).toContainText(name)
460+
await expect(component.getByText(name)).not.toHaveRole('link')
446461
})
447-
await expect(component).toContainText('link')
448-
await expect(component.getByText('link')).not.toHaveRole('link')
449-
})
462+
}
463+
testNoLink('no link to unknown protocols', { text: '[hello](other:proto)', name: 'hello' })
464+
testNoLink('no link to unresolved relative link (by router)', { text: '[hello](world)', name: 'hello' })
450465
})
451466

452467
test.describe('multiline code', () => {

0 commit comments

Comments
 (0)