fix(markdown): Ensure abbr: links render correctly in react-markdown v9+ (#20648)
This commit is contained in:
@@ -7,7 +7,7 @@ import RemarkGfm from 'remark-gfm'
|
||||
import RehypeRaw from 'rehype-raw'
|
||||
import { flow } from 'lodash-es'
|
||||
import cn from '@/utils/classnames'
|
||||
import { preprocessLaTeX, preprocessThinkTag } from './markdown-utils'
|
||||
import { customUrlTransform, preprocessLaTeX, preprocessThinkTag } from './markdown-utils'
|
||||
import {
|
||||
AudioBlock,
|
||||
CodeBlock,
|
||||
@@ -65,6 +65,7 @@ export function Markdown(props: { content: string; className?: string; customDis
|
||||
}
|
||||
},
|
||||
]}
|
||||
urlTransform={customUrlTransform}
|
||||
disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', ...(props.customDisallowedElements || [])]}
|
||||
components={{
|
||||
code: CodeBlock,
|
||||
|
@@ -36,3 +36,52 @@ export const preprocessThinkTag = (content: string) => {
|
||||
(str: string) => str.replace(/(<\/details>)(?![^\S\r\n]*[\r\n])(?![^\S\r\n]*$)/g, '$1\n'),
|
||||
])(content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a URI for use in react-markdown, ensuring security and compatibility.
|
||||
* This function is designed to work with react-markdown v9+ which has stricter
|
||||
* default URL handling.
|
||||
*
|
||||
* Behavior:
|
||||
* 1. Always allows the custom 'abbr:' protocol.
|
||||
* 2. Always allows page-local fragments (e.g., "#some-id").
|
||||
* 3. Always allows protocol-relative URLs (e.g., "//example.com/path").
|
||||
* 4. Always allows purely relative paths (e.g., "path/to/file", "/abs/path").
|
||||
* 5. Allows absolute URLs if their scheme is in a permitted list (case-insensitive):
|
||||
* 'http:', 'https:', 'mailto:', 'xmpp:', 'irc:', 'ircs:'.
|
||||
* 6. Intelligently distinguishes colons used for schemes from colons within
|
||||
* paths, query parameters, or fragments of relative-like URLs.
|
||||
* 7. Returns the original URI if allowed, otherwise returns `undefined` to
|
||||
* signal that the URI should be removed/disallowed by react-markdown.
|
||||
*/
|
||||
export const customUrlTransform = (uri: string): string | undefined => {
|
||||
const PERMITTED_SCHEME_REGEX = /^(https?|ircs?|mailto|xmpp|abbr):$/i
|
||||
|
||||
if (uri.startsWith('#'))
|
||||
return uri
|
||||
|
||||
if (uri.startsWith('//'))
|
||||
return uri
|
||||
|
||||
const colonIndex = uri.indexOf(':')
|
||||
|
||||
if (colonIndex === -1)
|
||||
return uri
|
||||
|
||||
const slashIndex = uri.indexOf('/')
|
||||
const questionMarkIndex = uri.indexOf('?')
|
||||
const hashIndex = uri.indexOf('#')
|
||||
|
||||
if (
|
||||
(slashIndex !== -1 && colonIndex > slashIndex)
|
||||
|| (questionMarkIndex !== -1 && colonIndex > questionMarkIndex)
|
||||
|| (hashIndex !== -1 && colonIndex > hashIndex)
|
||||
)
|
||||
return uri
|
||||
|
||||
const scheme = uri.substring(0, colonIndex + 1).toLowerCase()
|
||||
if (PERMITTED_SCHEME_REGEX.test(scheme))
|
||||
return uri
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
Reference in New Issue
Block a user