marked.tsx 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import dompurify from 'dompurify';
  2. import marked from 'marked'; // eslint-disable-line no-restricted-imports
  3. import {IS_ACCEPTANCE_TEST, NODE_ENV} from 'sentry/constants';
  4. // Only https and mailto, (e.g. no javascript, vbscript, data protocols)
  5. const safeLinkPattern = /^(https?:|mailto:)/i;
  6. const safeImagePattern = /^https?:\/\/./i;
  7. function isSafeHref(href: string, pattern: RegExp) {
  8. try {
  9. return pattern.test(decodeURIComponent(unescape(href)));
  10. } catch {
  11. return false;
  12. }
  13. }
  14. /**
  15. * Implementation of marked.Renderer which additonally sanitizes URLs.
  16. */
  17. class SafeRenderer extends marked.Renderer {
  18. link(href: string, title: string, text: string) {
  19. // For a bad link, just return the plain text href
  20. if (!isSafeHref(href, safeLinkPattern)) {
  21. return href;
  22. }
  23. const out = `<a href="${href}"${title ? ` title="${title}"` : ''}>${text}</a>`;
  24. return dompurify.sanitize(out);
  25. }
  26. image(href: string, title: string, text: string) {
  27. // For a bad image, return an empty string
  28. if (this.options.sanitize && !isSafeHref(href, safeImagePattern)) {
  29. return '';
  30. }
  31. return `<img src="${href}" alt="${text}"${title ? ` title="${title}"` : ''} />`;
  32. }
  33. }
  34. class NoParagraphRenderer extends SafeRenderer {
  35. paragraph(text: string) {
  36. return text;
  37. }
  38. }
  39. marked.setOptions({
  40. renderer: new SafeRenderer(),
  41. sanitize: true,
  42. // Silence sanitize deprecation warning in test / ci (CI sets NODE_NV
  43. // to production, but specifies `CI`).
  44. //
  45. // [!!] This has the side effect of causing failed markdown content to render
  46. // as a html error, instead of throwing an exception, however none of
  47. // our tests are rendering failed markdown so this is likely a safe
  48. // tradeoff to turn off off the deprecation warning.
  49. silent: !!IS_ACCEPTANCE_TEST || NODE_ENV === 'test',
  50. });
  51. const sanitizedMarked = (...args: Parameters<typeof marked>) =>
  52. dompurify.sanitize(marked(...args));
  53. const singleLineRenderer = (text: string, options: marked.MarkedOptions = {}) =>
  54. sanitizedMarked(text, {...options, renderer: new NoParagraphRenderer()});
  55. export {singleLineRenderer};
  56. export default sanitizedMarked;