2023-07-29 08:31:34 +02:00
import type { MarkedOptions } from './MarkedOptions.ts' ;
2023-08-07 16:50:43 -06:00
import type { ResultCallback } from './Instance.ts' ;
2023-07-29 08:31:34 +02:00
import type { Rule } from './rules.ts' ;
2019-11-05 15:29:42 -06:00
/ * *
* Helpers
* /
2019-12-04 21:14:51 +01:00
const escapeTest = /[&<>"']/ ;
2022-11-20 10:05:26 -06:00
const escapeReplace = new RegExp ( escapeTest . source , 'g' ) ;
const escapeTestNoEncode = /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/ ;
const escapeReplaceNoEncode = new RegExp ( escapeTestNoEncode . source , 'g' ) ;
2023-08-09 23:33:46 -06:00
const escapeReplacements : { [ index : string ] : string } = {
2019-12-04 21:14:51 +01:00
'&' : '&' ,
'<' : '<' ,
'>' : '>' ,
'"' : '"' ,
"'" : '''
} ;
2023-07-29 08:31:34 +02:00
const getEscapeReplacement = ( ch : string ) = > escapeReplacements [ ch ] ;
export function escape ( html : string , encode? : boolean ) {
2019-11-05 15:29:42 -06:00
if ( encode ) {
2019-12-04 21:14:51 +01:00
if ( escapeTest . test ( html ) ) {
return html . replace ( escapeReplace , getEscapeReplacement ) ;
2019-11-05 15:29:42 -06:00
}
} else {
2019-12-04 21:14:51 +01:00
if ( escapeTestNoEncode . test ( html ) ) {
return html . replace ( escapeReplaceNoEncode , getEscapeReplacement ) ;
2019-11-05 15:29:42 -06:00
}
}
return html ;
}
2019-12-04 21:14:51 +01:00
const unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig ;
2019-11-06 11:11:06 -06:00
2023-07-29 08:31:34 +02:00
export function unescape ( html : string ) {
2019-11-05 15:29:42 -06:00
// explicitly match decimal, hex, and named HTML entities
2019-12-04 21:14:51 +01:00
return html . replace ( unescapeTest , ( _ , n ) = > {
2019-11-05 15:29:42 -06:00
n = n . toLowerCase ( ) ;
if ( n === 'colon' ) return ':' ;
if ( n . charAt ( 0 ) === '#' ) {
return n . charAt ( 1 ) === 'x'
? String . fromCharCode ( parseInt ( n . substring ( 2 ) , 16 ) )
: String . fromCharCode ( + n . substring ( 1 ) ) ;
}
return '' ;
} ) ;
}
2019-12-04 21:14:51 +01:00
const caret = /(^|[^\[])\^/g ;
2022-03-30 17:42:54 +02:00
2023-07-29 08:31:34 +02:00
export function edit ( regex : Rule , opt? : string ) {
2022-03-30 17:42:54 +02:00
regex = typeof regex === 'string' ? regex : regex.source ;
2019-11-05 15:29:42 -06:00
opt = opt || '' ;
2019-11-07 12:46:32 -06:00
const obj = {
2023-07-29 08:31:34 +02:00
replace : ( name : string | RegExp , val : string | RegExp ) = > {
val = typeof val === 'object' && 'source' in val ? val.source : val ;
2019-12-04 21:14:51 +01:00
val = val . replace ( caret , '$1' ) ;
2023-07-29 08:31:34 +02:00
regex = ( regex as string ) . replace ( name , val ) ;
2019-11-07 12:46:32 -06:00
return obj ;
2019-11-05 15:29:42 -06:00
} ,
2019-11-07 12:46:32 -06:00
getRegex : ( ) = > {
2019-11-05 15:29:42 -06:00
return new RegExp ( regex , opt ) ;
}
} ;
2019-11-07 12:46:32 -06:00
return obj ;
2019-11-05 15:29:42 -06:00
}
2019-12-04 21:14:51 +01:00
const nonWordAndColonTest = /[^\w:]/g ;
const originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i ;
2022-03-30 17:42:54 +02:00
2023-07-29 08:31:34 +02:00
export function cleanUrl ( sanitize : boolean | undefined , base : string | undefined | null , href : string ) {
2019-11-05 15:29:42 -06:00
if ( sanitize ) {
let prot ;
try {
prot = decodeURIComponent ( unescape ( href ) )
2019-12-04 21:14:51 +01:00
. replace ( nonWordAndColonTest , '' )
2019-11-05 15:29:42 -06:00
. toLowerCase ( ) ;
} catch ( e ) {
return null ;
}
if ( prot . indexOf ( 'javascript:' ) === 0 || prot . indexOf ( 'vbscript:' ) === 0 || prot . indexOf ( 'data:' ) === 0 ) {
return null ;
}
}
2019-12-04 21:14:51 +01:00
if ( base && ! originIndependentUrl . test ( href ) ) {
2019-11-05 15:29:42 -06:00
href = resolveUrl ( base , href ) ;
}
try {
href = encodeURI ( href ) . replace ( /%25/g , '%' ) ;
} catch ( e ) {
return null ;
}
return href ;
}
2019-12-04 21:14:51 +01:00
2023-07-29 08:31:34 +02:00
const baseUrls : Record < string , string > = { } ;
2019-12-04 21:14:51 +01:00
const justDomain = /^[^:]+:\/*[^/]*$/ ;
const protocol = /^([^:]+:)[\s\S]*$/ ;
const domain = /^([^:]+:\/*[^/]*)[\s\S]*$/ ;
2019-11-05 15:29:42 -06:00
2023-07-29 08:31:34 +02:00
export function resolveUrl ( base : string , href : string ) {
2019-12-04 21:14:51 +01:00
if ( ! baseUrls [ ' ' + base ] ) {
2019-11-05 15:29:42 -06:00
// we can ignore everything in base after the last slash of its path component,
// but we might need to add _that_
// https://tools.ietf.org/html/rfc3986#section-3
2019-12-04 21:14:51 +01:00
if ( justDomain . test ( base ) ) {
baseUrls [ ' ' + base ] = base + '/' ;
2019-11-05 15:29:42 -06:00
} else {
2019-12-04 21:14:51 +01:00
baseUrls [ ' ' + base ] = rtrim ( base , '/' , true ) ;
2019-11-05 15:29:42 -06:00
}
}
2019-12-04 21:14:51 +01:00
base = baseUrls [ ' ' + base ] ;
2019-11-05 15:29:42 -06:00
const relativeBase = base . indexOf ( ':' ) === - 1 ;
2019-11-07 12:46:32 -06:00
if ( href . substring ( 0 , 2 ) === '//' ) {
2019-11-05 15:29:42 -06:00
if ( relativeBase ) {
return href ;
}
2019-12-04 21:14:51 +01:00
return base . replace ( protocol , '$1' ) + href ;
2019-11-05 15:29:42 -06:00
} else if ( href . charAt ( 0 ) === '/' ) {
if ( relativeBase ) {
return href ;
}
2019-12-04 21:14:51 +01:00
return base . replace ( domain , '$1' ) + href ;
2019-11-05 15:29:42 -06:00
} else {
return base + href ;
}
}
2023-07-29 08:31:34 +02:00
export const noopTest = { exec : ( ) = > null } ;
2019-11-05 15:29:42 -06:00
2023-08-09 23:33:46 -06:00
export function splitCells ( tableRow : string , count? : number ) {
2019-11-05 15:29:42 -06:00
// ensure that every cell-delimiting pipe has a space
// before it to distinguish it from an escaped pipe
2019-11-07 12:46:32 -06:00
const row = tableRow . replace ( /\|/g , ( match , offset , str ) = > {
2019-11-06 11:11:06 -06:00
let escaped = false ,
curr = offset ;
while ( -- curr >= 0 && str [ curr ] === '\\' ) escaped = ! escaped ;
if ( escaped ) {
// odd number of slashes means | is escaped
// so we leave it alone
return '|' ;
} else {
// add space before unescaped |
return ' |' ;
}
} ) ,
cells = row . split ( / \|/ ) ;
2019-11-05 15:29:42 -06:00
let i = 0 ;
2021-08-02 15:12:43 -04:00
// First/last cell in a row cannot be empty if it has no leading/trailing pipe
2023-07-29 08:31:34 +02:00
if ( ! cells [ 0 ] . trim ( ) ) {
cells . shift ( ) ;
}
if ( cells . length > 0 && ! cells [ cells . length - 1 ] . trim ( ) ) {
cells . pop ( ) ;
}
2021-08-02 15:12:43 -04:00
2023-08-09 23:33:46 -06:00
if ( count ) {
if ( cells . length > count ) {
cells . splice ( count ) ;
} else {
while ( cells . length < count ) cells . push ( '' ) ;
}
2019-11-05 15:29:42 -06:00
}
for ( ; i < cells . length ; i ++ ) {
// leading or trailing whitespace is ignored per the gfm spec
cells [ i ] = cells [ i ] . trim ( ) . replace ( /\\\|/g , '|' ) ;
}
return cells ;
}
2022-03-30 17:42:54 +02:00
/ * *
* Remove trailing 'c' s . Equivalent to str . replace ( /c*$/ , '' ) .
* /c*$/ is vulnerable to REDOS .
*
2023-07-29 08:31:34 +02:00
* @param str
* @param c
* @param invert Remove suffix of non - c chars instead . Default falsey .
2022-03-30 17:42:54 +02:00
* /
2023-07-29 08:31:34 +02:00
export function rtrim ( str : string , c : string , invert? : boolean ) {
2019-11-06 11:11:06 -06:00
const l = str . length ;
if ( l === 0 ) {
2019-11-05 15:29:42 -06:00
return '' ;
}
// Length of suffix matching the invert condition.
let suffLen = 0 ;
// Step left until we fail to match the invert condition.
2019-11-06 11:11:06 -06:00
while ( suffLen < l ) {
const currChar = str . charAt ( l - suffLen - 1 ) ;
2019-11-05 15:29:42 -06:00
if ( currChar === c && ! invert ) {
suffLen ++ ;
} else if ( currChar !== c && invert ) {
suffLen ++ ;
} else {
break ;
}
}
2022-03-19 06:11:14 +01:00
return str . slice ( 0 , l - suffLen ) ;
2019-11-05 15:29:42 -06:00
}
2023-07-29 08:31:34 +02:00
export function findClosingBracket ( str : string , b : string ) {
2019-11-05 15:29:42 -06:00
if ( str . indexOf ( b [ 1 ] ) === - 1 ) {
return - 1 ;
}
2019-11-06 11:11:06 -06:00
const l = str . length ;
let level = 0 ,
i = 0 ;
for ( ; i < l ; i ++ ) {
2019-11-05 15:29:42 -06:00
if ( str [ i ] === '\\' ) {
i ++ ;
} else if ( str [ i ] === b [ 0 ] ) {
level ++ ;
} else if ( str [ i ] === b [ 1 ] ) {
level -- ;
if ( level < 0 ) {
return i ;
}
}
}
return - 1 ;
}
2023-07-29 08:31:34 +02:00
export function checkDeprecations ( opt : MarkedOptions , callback? : ResultCallback ) {
2023-05-01 23:30:06 -05:00
if ( ! opt || opt . silent ) {
return ;
}
if ( callback ) {
console . warn ( 'marked(): callback is deprecated since version 5.0.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/using_pro#async' ) ;
}
if ( opt . sanitize || opt . sanitizer ) {
2019-11-05 15:29:42 -06:00
console . warn ( 'marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options' ) ;
}
2023-05-01 23:30:06 -05:00
2023-05-06 15:47:09 -05:00
if ( opt . highlight || opt . langPrefix !== 'language-' ) {
2023-05-01 23:30:06 -05:00
console . warn ( 'marked(): highlight and langPrefix parameters are deprecated since version 5.0.0, should not be used and will be removed in the future. Instead use https://www.npmjs.com/package/marked-highlight.' ) ;
}
if ( opt . mangle ) {
2023-05-11 11:13:50 -04:00
console . warn ( 'marked(): mangle parameter is enabled by default, but is deprecated since version 5.0.0, and will be removed in the future. To clear this warning, install https://www.npmjs.com/package/marked-mangle, or disable by setting `{mangle: false}`.' ) ;
2023-05-01 23:30:06 -05:00
}
if ( opt . baseUrl ) {
console . warn ( 'marked(): baseUrl parameter is deprecated since version 5.0.0, should not be used and will be removed in the future. Instead use https://www.npmjs.com/package/marked-base-url.' ) ;
}
if ( opt . smartypants ) {
console . warn ( 'marked(): smartypants parameter is deprecated since version 5.0.0, should not be used and will be removed in the future. Instead use https://www.npmjs.com/package/marked-smartypants.' ) ;
}
if ( opt . xhtml ) {
console . warn ( 'marked(): xhtml parameter is deprecated since version 5.0.0, should not be used and will be removed in the future. Instead use https://www.npmjs.com/package/marked-xhtml.' ) ;
}
if ( opt . headerIds || opt . headerPrefix ) {
2023-05-11 11:13:50 -04:00
console . warn ( 'marked(): headerIds and headerPrefix parameters enabled by default, but are deprecated since version 5.0.0, and will be removed in the future. To clear this warning, install https://www.npmjs.com/package/marked-gfm-heading-id, or disable by setting `{headerIds: false}`.' ) ;
2023-05-01 23:30:06 -05:00
}
2019-11-05 15:29:42 -06:00
}