import dompurify from 'dompurify';
import marked from 'marked'; // eslint-disable-line no-restricted-imports
import {IS_ACCEPTANCE_TEST, NODE_ENV} from 'sentry/constants';
// Only https and mailto, (e.g. no javascript, vbscript, data protocols)
const safeLinkPattern = /^(https?:|mailto:)/i;
const safeImagePattern = /^https?:\/\/./i;
function isSafeHref(href: string, pattern: RegExp) {
try {
return pattern.test(decodeURIComponent(unescape(href)));
} catch {
return false;
}
}
/**
* Implementation of marked.Renderer which additonally sanitizes URLs.
*/
class SafeRenderer extends marked.Renderer {
link(href: string, title: string, text: string) {
// For a bad link, just return the plain text href
if (!isSafeHref(href, safeLinkPattern)) {
return href;
}
const out = `${text}`;
return dompurify.sanitize(out);
}
image(href: string, title: string, text: string) {
// For a bad image, return an empty string
if (this.options.sanitize && !isSafeHref(href, safeImagePattern)) {
return '';
}
return ``;
}
}
class NoParagraphRenderer extends SafeRenderer {
paragraph(text: string) {
return text;
}
}
marked.setOptions({
renderer: new SafeRenderer(),
sanitize: true,
// Silence sanitize deprecation warning in test / ci (CI sets NODE_NV
// to production, but specifies `CI`).
//
// [!!] This has the side effect of causing failed markdown content to render
// as a html error, instead of throwing an exception, however none of
// our tests are rendering failed markdown so this is likely a safe
// tradeoff to turn off off the deprecation warning.
silent: !!IS_ACCEPTANCE_TEST || NODE_ENV === 'test',
});
const sanitizedMarked = (...args: Parameters) =>
dompurify.sanitize(marked(...args));
const singleLineRenderer = (text: string, options: marked.MarkedOptions = {}) =>
sanitizedMarked(text, {...options, renderer: new NoParagraphRenderer()});
export {singleLineRenderer};
export default sanitizedMarked;