From 0497414921e8b68585c5482171dc355dea4f0890 Mon Sep 17 00:00:00 2001 From: Jani Kinnunen Date: Tue, 28 Apr 2026 07:41:29 +0300 Subject: [PATCH 1/2] Add safeProtocol as a parameter for defaultUrlTransform --- lib/index.js | 13 ++++++---- test.jsx | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/lib/index.js b/lib/index.js index 06f07e1..f227930 100644 --- a/lib/index.js +++ b/lib/index.js @@ -121,7 +121,7 @@ const changelog = const emptyPlugins = [] /** @type {Readonly} */ const emptyRemarkRehypeOptions = {allowDangerousHtml: true} -const safeProtocol = /^(https?|ircs?|mailto|xmpp)$/i +const defaultSafeProtocol = /^(https?|ircs?|mailto|xmpp)$/i // Mutable because we `delete` any time it’s used and a message is sent. /** @type {ReadonlyArray>} */ @@ -317,7 +317,7 @@ function post(tree, options) { const disallowedElements = options.disallowedElements const skipHtml = options.skipHtml const unwrapDisallowed = options.unwrapDisallowed - const urlTransform = options.urlTransform || defaultUrlTransform + const urlTransform = options.urlTransform || ((v) => defaultUrlTransform(v)) for (const deprecation of deprecations) { if (Object.hasOwn(options, deprecation.from)) { @@ -413,16 +413,19 @@ function post(tree, options) { * Make a URL safe. * * This follows how GitHub works. - * It allows the protocols `http`, `https`, `irc`, `ircs`, `mailto`, and `xmpp`, - * and URLs relative to the current protocol (such as `/something`). + * By default it allows the protocols `http`, `https`, `irc`, `ircs`, `mailto`, + * and `xmpp`, and URLs relative to the current protocol (such as `/something`); + * pass `safeProtocol` to override which protocols are allowed. * * @satisfies {UrlTransform} * @param {string} value * URL. + * @param {RegExp} [safeProtocol] + * Regex matching protocols to allow (default: `/^(https?|ircs?|mailto|xmpp)$/i`); * @returns {string} * Safe URL. */ -export function defaultUrlTransform(value) { +export function defaultUrlTransform(value, safeProtocol = defaultSafeProtocol) { // Same as: // // But without the `encode` part. diff --git a/test.jsx b/test.jsx index 1b254db..3383bec 100644 --- a/test.jsx +++ b/test.jsx @@ -24,7 +24,11 @@ import {render, waitFor} from '@testing-library/react' import concatStream from 'concat-stream' import {Component} from 'react' import {renderToPipeableStream, renderToStaticMarkup} from 'react-dom/server' -import Markdown, {MarkdownAsync, MarkdownHooks} from 'react-markdown' +import Markdown, { + MarkdownAsync, + MarkdownHooks, + defaultUrlTransform +} from 'react-markdown' import rehypeRaw from 'rehype-raw' import rehypeStarryNight from 'rehype-starry-night' import remarkGfm from 'remark-gfm' @@ -414,6 +418,40 @@ test('Markdown', async function (t) { ) }) + await t.test( + 'should support a custom `safeProtocol` via `urlTransform`', + function () { + assert.equal( + renderToStaticMarkup( + + ), + '

a

' + ) + } + ) + + await t.test( + 'should drop URLs whose protocol is not in `safeProtocol`', + function () { + assert.equal( + renderToStaticMarkup( + + ), + '

a

' + ) + } + ) + await t.test('should support `skipHtml`', function () { const actual = renderToStaticMarkup( @@ -1058,6 +1096,37 @@ test('Markdown', async function (t) { }) }) +test('defaultUrlTransform', async function (t) { + await t.test('should allow default protocols', function () { + assert.equal( + defaultUrlTransform('https://example.com'), + 'https://example.com' + ) + assert.equal(defaultUrlTransform('mailto:a@b.c'), 'mailto:a@b.c') + }) + + await t.test('should drop unsafe protocols by default', function () { + assert.equal(defaultUrlTransform('data:text/html,