mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-21 10:45:20 -04:00
Add dom purify (#1609)
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
994d22eb35
commit
84ee1d9cd9
9 changed files with 103 additions and 81 deletions
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { Document } from 'domhandler'
|
||||
import render from 'dom-serializer'
|
||||
import DOMPurify from 'dompurify'
|
||||
import { parseDocument } from 'htmlparser2'
|
||||
|
||||
const customTags = ['app-linemarker', 'app-katex', 'app-gist', 'app-youtube', 'app-vimeo', 'app-asciinema']
|
||||
|
||||
/**
|
||||
* Sanitizes the given {@link Document document}.
|
||||
*
|
||||
* @param document The dirty document
|
||||
* @return the sanitized Document
|
||||
*/
|
||||
export const domPurifierNodePreprocessor = (document: Document): Document => {
|
||||
const sanitizedHtml = DOMPurify.sanitize(render(document), {
|
||||
ADD_TAGS: customTags
|
||||
})
|
||||
return parseDocument(sanitizedHtml)
|
||||
}
|
|
@ -11,6 +11,7 @@ import convertHtmlToReact from '@hedgedoc/html-to-react'
|
|||
import type { Document } from 'domhandler'
|
||||
import { NodeToReactTransformer } from '../utils/node-to-react-transformer'
|
||||
import { LineIdMapper } from '../utils/line-id-mapper'
|
||||
import { domPurifierNodePreprocessor } from './dom-purifier-node-preprocessor'
|
||||
|
||||
/**
|
||||
* Renders markdown code into react elements
|
||||
|
@ -40,9 +41,13 @@ export const useConvertMarkdownToReactDom = (
|
|||
|
||||
return useMemo(() => {
|
||||
const html = markdownIt.render(markdownCode)
|
||||
|
||||
return convertHtmlToReact(html, {
|
||||
transform: (node, index) => htmlToReactTransformer.translateNodeToReactElement(node, index),
|
||||
preprocessNodes: preprocessNodes
|
||||
preprocessNodes: (document: Document): Document => {
|
||||
const processedDocument = preprocessNodes ? preprocessNodes(document) : document
|
||||
return domPurifierNodePreprocessor(processedDocument)
|
||||
}
|
||||
})
|
||||
}, [htmlToReactTransformer, markdownCode, markdownIt, preprocessNodes])
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Logger } from '../../utils/logger'
|
|||
|
||||
const log = new Logger('reveal.js > Comment Node Preprocessor')
|
||||
const revealCommandSyntax = /^\s*\.(\w*):(.*)$/g
|
||||
const dataAttributesSyntax = /\s*([\w-]*)=(?:"((?:[^"\\]|\\"|\\)*)"|'([^']*)')/g
|
||||
const dataAttributesSyntax = /\s*(data-[\w-]*|class)=(?:"((?:[^"\\]|\\"|\\)*)"|'([^']*)')/g
|
||||
|
||||
/**
|
||||
* Travels through the given {@link Document}, searches for reveal command comments and applies them.
|
||||
|
|
|
@ -14,7 +14,7 @@ export const legacySlideshareShortCode: MarkdownIt.PluginSimple = (markdownIt) =
|
|||
name: 'legacy-slideshare-short-code',
|
||||
regex: finalRegex,
|
||||
replace: (match: string) => {
|
||||
return `<a target="_blank" rel="noopener noreferrer" href="https://www.slideshare.net/${match}">https://www.slideshare.net/${match}</a>`
|
||||
return `<a href="https://www.slideshare.net/${match}">https://www.slideshare.net/${match}</a>`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { isTag } from 'domhandler'
|
|||
import type MarkdownIt from 'markdown-it'
|
||||
import mathJax from 'markdown-it-mathjax'
|
||||
import React from 'react'
|
||||
import { ComponentReplacer } from '../component-replacer'
|
||||
import { ComponentReplacer, DO_NOT_REPLACE } from '../component-replacer'
|
||||
import './katex.scss'
|
||||
|
||||
/**
|
||||
|
@ -18,23 +18,24 @@ import './katex.scss'
|
|||
* @param node the node to check
|
||||
* @return The given node if it is a KaTeX block element, undefined otherwise.
|
||||
*/
|
||||
const getNodeIfKatexBlock = (node: Element): Element | undefined => {
|
||||
const containsKatexBlock = (node: Element): Element | undefined => {
|
||||
if (node.name !== 'p' || !node.children || node.children.length === 0) {
|
||||
return
|
||||
}
|
||||
return node.children.filter(isTag).find((subnode) => {
|
||||
return subnode.name === 'app-katex' && subnode.attribs?.inline === undefined
|
||||
return isKatexTag(subnode, false) ? subnode : undefined
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given node is a KaTeX inline element.
|
||||
* Checks if the given node is a KaTeX element.
|
||||
*
|
||||
* @param node the node to check
|
||||
* @return The given node if it is a KaTeX inline element, undefined otherwise.
|
||||
* @param expectedInline defines if the found katex element is expected to be an inline or block element.
|
||||
* @return {@code true} if the given node is a katex element.
|
||||
*/
|
||||
const getNodeIfInlineKatex = (node: Element): Element | undefined => {
|
||||
return node.name === 'app-katex' && node.attribs?.inline !== undefined ? node : undefined
|
||||
const isKatexTag = (node: Element, expectedInline: boolean) => {
|
||||
return node.name === 'app-katex' && (node.attribs?.['data-inline'] !== undefined) === expectedInline
|
||||
}
|
||||
|
||||
const KaTeX = React.lazy(() => import(/* webpackChunkName: "katex" */ '@matejmazur/react-katex'))
|
||||
|
@ -46,18 +47,18 @@ export class KatexReplacer extends ComponentReplacer {
|
|||
public static readonly markdownItPlugin: MarkdownIt.PluginSimple = mathJax({
|
||||
beforeMath: '<app-katex>',
|
||||
afterMath: '</app-katex>',
|
||||
beforeInlineMath: '<app-katex inline>',
|
||||
beforeInlineMath: '<app-katex data-inline="true">',
|
||||
afterInlineMath: '</app-katex>',
|
||||
beforeDisplayMath: '<app-katex>',
|
||||
afterDisplayMath: '</app-katex>'
|
||||
})
|
||||
|
||||
public replace(node: Element): React.ReactElement | undefined {
|
||||
const katex = getNodeIfKatexBlock(node) || getNodeIfInlineKatex(node)
|
||||
if (katex?.children && katex.children[0]) {
|
||||
const mathJaxContent = ComponentReplacer.extractTextChildContent(katex)
|
||||
const isInline = katex.attribs?.inline !== undefined
|
||||
return <KaTeX block={!isInline} math={mathJaxContent} errorColor={'#cc0000'} />
|
||||
if (!(isKatexTag(node, true) || containsKatexBlock(node)) || node.children?.[0] === undefined) {
|
||||
return DO_NOT_REPLACE
|
||||
}
|
||||
const latexContent = ComponentReplacer.extractTextChildContent(node)
|
||||
const isInline = !!node.attribs?.['data-inline']
|
||||
return <KaTeX block={!isInline} math={latexContent} errorColor={'#cc0000'} />
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,14 +35,7 @@ export class LinkReplacer extends ComponentReplacer {
|
|||
}
|
||||
|
||||
const url = node.attribs.href.trim()
|
||||
|
||||
// eslint-disable-next-line no-script-url
|
||||
if (url.startsWith('data:') || url.startsWith('javascript:') || url.startsWith('vbscript:')) {
|
||||
return <span>{node.attribs.href}</span>
|
||||
}
|
||||
|
||||
const isJumpMark = url.substr(0, 1) === '#'
|
||||
|
||||
const id = url.substr(1)
|
||||
|
||||
try {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue