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/readme.md b/readme.md index 949b180..0739514 100644 --- a/readme.md +++ b/readme.md @@ -34,7 +34,7 @@ React component to render markdown. * [`Markdown`](#markdown) * [`MarkdownAsync`](#markdownasync) * [`MarkdownHooks`](#markdownhooks) - * [`defaultUrlTransform(url)`](#defaulturltransformurl) + * [`defaultUrlTransform(url, safeProtocol)`](#defaulturltransformurl-safeprotocol) * [`AllowElement`](#allowelement) * [`Components`](#components) * [`ExtraProps`](#extraprops) @@ -238,18 +238,21 @@ see [`MarkdownAsync`][api-markdown-async]. React node (`ReactNode`). -### `defaultUrlTransform(url)` +### `defaultUrlTransform(url, safeProtocol)` 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. ###### Parameters * `url` (`string`) — URL +* `safeProtocol` (`RegExp`, optional) + — regex matching protocols to allow (default: `/^(https?|ircs?|mailto|xmpp)$/i`) ###### Returns @@ -829,7 +832,7 @@ abide by its terms. [api-components]: #components -[api-default-url-transform]: #defaulturltransformurl +[api-default-url-transform]: #defaulturltransformurl-safeprotocol [api-extra-props]: #extraprops 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,