| @@ -27,6 +27,7 @@ import ThinkBlock from '@/app/components/base/markdown-blocks/think-block' | |||
| import { Theme } from '@/types/app' | |||
| import useTheme from '@/hooks/use-theme' | |||
| import cn from '@/utils/classnames' | |||
| import SVGRenderer from './svg-gallery' | |||
| // Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD | |||
| const capitalizationLanguageNameMap: Record<string, string> = { | |||
| @@ -136,14 +137,13 @@ const CodeBlock: any = memo(({ inline, className, children, ...props }: any) => | |||
| </div> | |||
| ) | |||
| } | |||
| // Attention: SVGRenderer has xss vulnerability | |||
| // else if (language === 'svg' && isSVG) { | |||
| // return ( | |||
| // <ErrorBoundary> | |||
| // <SVGRenderer content={content} /> | |||
| // </ErrorBoundary> | |||
| // ) | |||
| // } | |||
| else if (language === 'svg' && isSVG) { | |||
| return ( | |||
| <ErrorBoundary> | |||
| <SVGRenderer content={content} /> | |||
| </ErrorBoundary> | |||
| ) | |||
| } | |||
| else { | |||
| return ( | |||
| <SyntaxHighlighter | |||
| @@ -240,19 +240,11 @@ const Link = ({ node, ...props }: any) => { | |||
| } | |||
| } | |||
| function escapeSVGTags(htmlString: string): string { | |||
| return htmlString.replace(/(<svg[\s\S]*?>)([\s\S]*?)(<\/svg>)/gi, (match: string, openTag: string, innerContent: string, closeTag: string): string => { | |||
| return openTag.replace(/</g, '<').replace(/>/g, '>') | |||
| + innerContent.replace(/</g, '<').replace(/>/g, '>') | |||
| + closeTag.replace(/</g, '<').replace(/>/g, '>') | |||
| }) | |||
| } | |||
| export function Markdown(props: { content: string; className?: string; customDisallowedElements?: string[] }) { | |||
| const latexContent = flow([ | |||
| preprocessThinkTag, | |||
| preprocessLaTeX, | |||
| ])(escapeSVGTags(props.content)) | |||
| ])(props.content) | |||
| return ( | |||
| <div className={cn('markdown-body', '!text-text-primary', props.className)}> | |||
| @@ -1,6 +1,7 @@ | |||
| import { useEffect, useRef, useState } from 'react' | |||
| import { SVG } from '@svgdotjs/svg.js' | |||
| import ImagePreview from '@/app/components/base/image-uploader/image-preview' | |||
| import DOMPurify from 'dompurify' | |||
| export const SVGRenderer = ({ content }: { content: string }) => { | |||
| const svgRef = useRef<HTMLDivElement>(null) | |||
| @@ -44,7 +45,7 @@ export const SVGRenderer = ({ content }: { content: string }) => { | |||
| svgRef.current.style.width = `${Math.min(originalWidth, 298)}px` | |||
| const rootElement = draw.svg(content) | |||
| const rootElement = draw.svg(DOMPurify.sanitize(content)) | |||
| rootElement.click(() => { | |||
| setImagePreview(svgToDataURL(svgElement as Element)) | |||
| @@ -61,6 +61,7 @@ | |||
| "crypto-js": "^4.2.0", | |||
| "dayjs": "^1.11.13", | |||
| "decimal.js": "^10.4.3", | |||
| "dompurify": "^3.2.4", | |||
| "echarts": "^5.5.1", | |||
| "echarts-for-react": "^3.0.2", | |||
| "elkjs": "^0.9.3", | |||
| @@ -132,10 +133,10 @@ | |||
| }, | |||
| "devDependencies": { | |||
| "@antfu/eslint-config": "^4.1.1", | |||
| "@eslint/js": "^9.20.0", | |||
| "@chromatic-com/storybook": "^3.1.0", | |||
| "@eslint-react/eslint-plugin": "^1.15.0", | |||
| "@eslint/eslintrc": "^3.1.0", | |||
| "@eslint/js": "^9.20.0", | |||
| "@faker-js/faker": "^9.0.3", | |||
| "@next/eslint-plugin-next": "^15.2.3", | |||
| "@rgrove/parse-xml": "^4.1.0", | |||
| @@ -174,11 +175,11 @@ | |||
| "code-inspector-plugin": "^0.18.1", | |||
| "cross-env": "^7.0.3", | |||
| "eslint": "^9.20.1", | |||
| "eslint-config-next": "^15.0.0", | |||
| "eslint-plugin-react-hooks": "^5.1.0", | |||
| "eslint-plugin-react-refresh": "^0.4.19", | |||
| "eslint-plugin-storybook": "^0.11.2", | |||
| "eslint-plugin-tailwindcss": "^3.18.0", | |||
| "eslint-config-next": "^15.0.0", | |||
| "husky": "^9.1.6", | |||
| "jest": "^29.7.0", | |||
| "jest-environment-jsdom": "^29.7.0", | |||
| @@ -121,6 +121,9 @@ importers: | |||
| decimal.js: | |||
| specifier: ^10.4.3 | |||
| version: 10.4.3 | |||
| dompurify: | |||
| specifier: ^3.2.4 | |||
| version: 3.2.4 | |||
| echarts: | |||
| specifier: ^5.5.1 | |||
| version: 5.5.1 | |||
| @@ -4299,8 +4302,8 @@ packages: | |||
| resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} | |||
| engines: {node: '>= 4'} | |||
| dompurify@3.2.3: | |||
| resolution: {integrity: sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==} | |||
| dompurify@3.2.4: | |||
| resolution: {integrity: sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==} | |||
| domutils@2.8.0: | |||
| resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} | |||
| @@ -13070,7 +13073,7 @@ snapshots: | |||
| dependencies: | |||
| domelementtype: 2.3.0 | |||
| dompurify@3.2.3: | |||
| dompurify@3.2.4: | |||
| optionalDependencies: | |||
| '@types/trusted-types': 2.0.7 | |||
| @@ -15688,7 +15691,7 @@ snapshots: | |||
| d3-sankey: 0.12.3 | |||
| dagre-d3-es: 7.0.11 | |||
| dayjs: 1.11.13 | |||
| dompurify: 3.2.3 | |||
| dompurify: 3.2.4 | |||
| katex: 0.16.21 | |||
| khroma: 2.1.0 | |||
| lodash-es: 4.17.21 | |||