export type HTMLAllowList = {
[tag: string]:
| string[]
| { [attr: string]: boolean | ((value: string) => string) }
}
const globalAttrs = {
class: true,
dir: (value) => {
const normalized = value.toLowerCase()
return ['rtl', 'ltr', 'auto'].includes(normalized) ? normalized : ''
},
lang: true,
title: true,
} as const satisfies HTMLAllowList[string]
const generateUrlSanitizer =
(schemas: string[]) =>
(value: string): string => {
if (value.includes(':')) {
// Check the URL schema if it exists
const trimmed = value.trim().toLowerCase()
const schema = trimmed.split(':', 1)[0]
for (const allowedSchema of schemas) {
if (schema === allowedSchema) return value
if (allowedSchema.includes(':') && trimmed.startsWith(allowedSchema))
return value
}
return ''
}
return value
}
const webUrlSanitizer = generateUrlSanitizer(['http', 'https'])
const imageUrlSanitizer = generateUrlSanitizer(['http', 'https', 'data:image/'])
const srcSetSanitizer = (value: string): string => {
for (const src of value.split(',')) {
if (!imageUrlSanitizer(src)) return ''
}
return value
}
export const defaultHTMLAllowList: HTMLAllowList = Object.assign(
Object.create(null),
{
a: {
...globalAttrs,
href: webUrlSanitizer,
name: true, // deprecated attribute, but still useful in Marp for making stable anchor link
rel: true,
target: true,
},
abbr: globalAttrs,
address: globalAttrs,
article: globalAttrs,
aside: globalAttrs,
audio: {
...globalAttrs,
autoplay: true,
controls: true,
loop: true,
muted: true,
preload: true,
src: webUrlSanitizer,
},
b: globalAttrs,
bdi: globalAttrs,
bdo: globalAttrs,
big: globalAttrs,
blockquote: {
...globalAttrs,
cite: webUrlSanitizer,
},
br: globalAttrs,
caption: globalAttrs,
center: globalAttrs, // deprecated
cite: globalAttrs,
code: globalAttrs,
col: {
...globalAttrs,
align: true,
valign: true,
span: true,
width: true,
},
colgroup: {
...globalAttrs,
align: true,
valign: true,
span: true,
width: true,
},
dd: globalAttrs,
del: {
...globalAttrs,
cite: webUrlSanitizer,
datetime: true,
},
details: {
...globalAttrs,
open: true,
},
div: globalAttrs,
dl: globalAttrs,
dt: globalAttrs,
em: globalAttrs,
figcaption: globalAttrs,
figure: globalAttrs,
// footer: globalAttrs, // Inserted by Marpit directives so disallowed to avoid confusion
h1: globalAttrs,
h2: globalAttrs,
h3: globalAttrs,
h4: globalAttrs,
h5: globalAttrs,
h6: globalAttrs,
// header: globalAttrs, // Inserted by Marpit directives so disallowed to avoid confusion
hr: globalAttrs,
i: globalAttrs,
img: {
...globalAttrs,
align: true, // deprecated attribute, but still useful in Marp for aligning image
alt: true,
decoding: true,
height: true,
loading: true,
src: imageUrlSanitizer,
srcset: srcSetSanitizer,
title: true,
width: true,
},
ins: {
...globalAttrs,
cite: webUrlSanitizer,
datetime: true,
},
kbd: globalAttrs,
li: {
...globalAttrs,
type: true,
value: true,
},
mark: globalAttrs,
nav: globalAttrs,
ol: {
...globalAttrs,
reversed: true,
start: true,
type: true,
},
p: globalAttrs,
picture: globalAttrs,
pre: globalAttrs,
source: {
height: true,
media: true,
sizes: true,
src: imageUrlSanitizer,
srcset: srcSetSanitizer,
type: true,
width: true,
},
q: {
...globalAttrs,
cite: webUrlSanitizer,
},
rp: globalAttrs,
rt: globalAttrs,
ruby: globalAttrs,
s: globalAttrs,
section: globalAttrs,
small: globalAttrs,
span: globalAttrs,
sub: globalAttrs,
summary: globalAttrs,
sup: globalAttrs,
strong: globalAttrs,
strike: globalAttrs,
table: {
...globalAttrs,
width: true,
border: true,
align: true,
valign: true,
},
tbody: {
...globalAttrs,
align: true,
valign: true,
},
td: {
...globalAttrs,
width: true,
rowspan: true,
colspan: true,
align: true,
valign: true,
},
tfoot: {
...globalAttrs,
align: true,
valign: true,
},
th: {
...globalAttrs,
width: true,
rowspan: true,
colspan: true,
align: true,
valign: true,
},
thead: {
...globalAttrs,
align: true,
valign: true,
},
time: {
...globalAttrs,
datetime: true,
},
tr: {
...globalAttrs,
rowspan: true,
align: true,
valign: true,
},
u: globalAttrs,
ul: globalAttrs,
video: {
...globalAttrs,
autoplay: true,
controls: true,
loop: true,
muted: true,
playsinline: true,
poster: imageUrlSanitizer,
preload: true,
src: webUrlSanitizer,
height: true,
width: true,
},
wbr: globalAttrs,
} as const satisfies HTMLAllowList,
)