@@ -48,15 +48,24 @@ const SAFE_SCHEMES = [
4848 'callto:' ,
4949 'cid:' ,
5050 'xmpp:' ,
51+ 'matrix:' ,
5152 'webcal:' ,
5253 'feed:' ,
5354 'geo:'
5455]
5556
57+ /**
58+ * Remove all control characters (e.g. line break, tab, escape) and trim the
59+ * string
60+ */
61+ function normalizeValue ( value : string ) {
62+ // eslint-disable-next-line no-control-regex
63+ return value . replace ( / [ \u0000 - \u001F \u007F ] / g, '' ) . trim ( )
64+ }
65+
5666// Extract the scheme from a URL-ish value, or `null` if it has none.
5767function extractScheme ( value : string ) : string | null {
58- const normalized = value . replace ( / [ \t \n \r ] / g, '' ) . trim ( )
59- const schemeMatch = normalized . match ( / ^ ( [ a - z A - Z ] [ a - z A - Z 0 - 9 + . - ] * ) : / )
68+ const schemeMatch = normalizeValue ( value ) . match ( / ^ ( [ a - z A - Z ] [ a - z A - Z 0 - 9 + . - ] * ) : / )
6069 return schemeMatch ? schemeMatch [ 1 ] . toLowerCase ( ) + ':' : null
6170}
6271
@@ -74,7 +83,7 @@ const DATA_URI_TAGS: ReadonlyArray<[string, string]> = [
7483// isn't, validate the scheme directly so server-rendered components don't
7584// silently drop every href.
7685function ssrSafeHref ( value : string , tag : string , attr : string ) : boolean {
77- const normalized = value . replace ( / [ \t \n \r ] / g , '' ) . trim ( )
86+ const normalized = normalizeValue ( value )
7887 if ( normalized === '' ) return true
7988 const firstChar = normalized . charAt ( 0 )
8089 if ( firstChar === '#' || firstChar === '/' || firstChar === '?' ) return true
@@ -110,7 +119,7 @@ function safeHref(
110119 tag : string ,
111120 attr : string
112121) : string | undefined {
113- if ( href == null ) return undefined
122+ if ( href == null || typeof href === 'undefined' ) return undefined
114123 const value = String ( href )
115124
116125 // Pre-check our scheme allowlist before DOMPurify, since DOMPurify's default
0 commit comments