Skip to content

Commit

Permalink
Refactor to improve safe URL detection
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Sep 27, 2023
1 parent a1fc6d9 commit 08ead9e
Show file tree
Hide file tree
Showing 2 changed files with 7 additions and 57 deletions.
39 changes: 3 additions & 36 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import {unreachable} from 'devlop'
import {toJsxRuntime} from 'hast-util-to-jsx-runtime'
import {urlAttributes} from 'html-url-attributes'
import {sanitizeUri} from 'micromark-util-sanitize-uri'
// @ts-expect-error: untyped.
import {Fragment, jsx, jsxs} from 'react/jsx-runtime'
import remarkParse from 'remark-parse'
Expand All @@ -89,8 +90,6 @@ import {unified} from 'unified'
import {visit} from 'unist-util-visit'
import {VFile} from 'vfile'

const safeProtocols = ['http', 'https', 'mailto', 'tel']

const own = {}.hasOwnProperty
const changelog =
'https://github.com/remarkjs/react-markdown/blob/main/changelog.md'
Expand All @@ -99,6 +98,7 @@ const changelog =
const emptyPlugins = []
/** @type {Readonly<RemarkRehypeOptions>} */
const emptyRemarkRehypeOptions = {allowDangerousHtml: true}
const safeProtocol = /^(https?|ircs?|mailto|xmpp)$/i

// Mutable because we `delete` any time it’s used and a message is sent.
/** @type {ReadonlyArray<Readonly<Deprecation>>} */
Expand Down Expand Up @@ -293,38 +293,5 @@ export function Markdown(options) {
* Safe URL.
*/
export function defaultUrlTransform(value) {
const url = value.trim()
const first = url.charAt(0)

if (first === '#' || first === '/') {
return url
}

const colon = url.indexOf(':')
if (colon === -1) {
return url
}

for (const protocol of safeProtocols) {
if (
colon === protocol.length &&
url.slice(0, protocol.length).toLowerCase() === protocol
) {
return url
}
}

let index = url.indexOf('?')
if (index !== -1 && colon > index) {
return url
}

index = url.indexOf('#')
if (index !== -1 && colon > index) {
return url
}

// To do: is there an alternative?
// eslint-disable-next-line no-script-url
return 'javascript:void(0)'
return sanitizeUri(value, safeProtocol)
}
25 changes: 4 additions & 21 deletions test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -288,43 +288,31 @@ test('react-markdown', async function (t) {
})

await t.test('should make a `javascript:` URL safe', function () {
const consoleError = console.error
console.error = noop
assert.equal(
asHtml(<Markdown children="[](javascript:alert(1))" />),
'<p><a href="javascript:void(0)"></a></p>'
'<p><a href=""></a></p>'
)
console.error = consoleError
})

await t.test('should make a `vbscript:` URL safe', function () {
const consoleError = console.error
console.error = noop
assert.equal(
asHtml(<Markdown children="[](vbscript:alert(1))" />),
'<p><a href="javascript:void(0)"></a></p>'
'<p><a href=""></a></p>'
)
console.error = consoleError
})

await t.test('should make a `VBSCRIPT:` URL safe', function () {
const consoleError = console.error
console.error = noop
assert.equal(
asHtml(<Markdown children="[](VBSCRIPT:alert(1))" />),
'<p><a href="javascript:void(0)"></a></p>'
'<p><a href=""></a></p>'
)
console.error = consoleError
})

await t.test('should make a `file:` URL safe', function () {
const consoleError = console.error
console.error = noop
assert.equal(
asHtml(<Markdown children="[](file:///etc/passwd)" />),
'<p><a href="javascript:void(0)"></a></p>'
'<p><a href=""></a></p>'
)
console.error = consoleError
})

await t.test('should allow an empty URL', function () {
Expand Down Expand Up @@ -1054,8 +1042,3 @@ test('react-markdown', async function (t) {
function asHtml(input) {
return renderToStaticMarkup(input)
}

/**
* @returns {undefined}
*/
function noop() {}

0 comments on commit 08ead9e

Please sign in to comment.