From ab4f8cecb7cc07168ef06017068f1de1bc44a736 Mon Sep 17 00:00:00 2001 From: Jeff Davidson Date: Sat, 21 Dec 2024 09:28:40 -0800 Subject: [PATCH] Fix double-encoding of HTML entities in certain chat messages. --- imports/client/components/ChatMessage.tsx | 15 +++++++++++---- package-lock.json | 9 +++++++-- package.json | 2 ++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/imports/client/components/ChatMessage.tsx b/imports/client/components/ChatMessage.tsx index 8e25705e3..83c3f7f2c 100644 --- a/imports/client/components/ChatMessage.tsx +++ b/imports/client/components/ChatMessage.tsx @@ -1,4 +1,5 @@ /* eslint-disable react/no-array-index-key */ +import * as he from "he"; import { marked } from "marked"; import React from "react"; import styled from "styled-components"; @@ -38,6 +39,9 @@ const StyledCodeBlock = styled.code` // Renders a markdown token to React components. const MarkdownToken = ({ token }: { token: marked.Token }) => { + // NOTE: Marked's lexer encodes using HTML entities in the text; see: + // https://github.com/markedjs/marked/discussions/1737 + // We need to decode the text since React will apply its own escaping. if (token.type === "text") { return {token.raw}; } else if (token.type === "space") { @@ -48,8 +52,9 @@ const MarkdownToken = ({ token }: { token: marked.Token }) => { const children = token.tokens.map((t, i) => ( )); - if (token.raw.length > token.text.length) { - const trail = token.raw.substring(token.text.length); + const decodedText = he.decode(token.text); + if (token.raw.length > decodedText.length) { + const trail = token.raw.substring(decodedText.length); if (trail.trim() === "") { const syntheticSpace: marked.Tokens.Space = { type: "space", @@ -95,9 +100,11 @@ const MarkdownToken = ({ token }: { token: marked.Token }) => { )); return {children}; } else if (token.type === "codespan") { - return {token.text}; + const decodedText = he.decode(token.text); + return {decodedText}; } else if (token.type === "code") { - return {token.text}; + const decodedText = he.decode(token.text); + return {decodedText}; } else { // Unhandled token types: just return the raw string with pre-wrap. // This covers things like bulleted or numbered lists, which we explicitly diff --git a/package-lock.json b/package-lock.json index be37886b6..927f7ad90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7062,6 +7062,12 @@ "@types/node": "*" } }, + "@types/he": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/he/-/he-1.2.3.tgz", + "integrity": "sha512-q67/qwlxblDzEDvzHhVkwc1gzVWxaNxeyHUBF4xElrvjL11O+Ytze+1fGpBHlr/H9myiBUaUXNnNPmBHxxfAcA==", + "dev": true + }, "@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -15055,8 +15061,7 @@ "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, "hoist-non-react-statics": { "version": "3.3.2", diff --git a/package.json b/package.json index 96792c16e..717cc9f87 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "form-data": "^4.0.1", "gaxios": "^6.7.1", "glob": "^8.1.0", + "he": "^1.2.0", "http-proxy": "^1.18.1", "ip-address": "^10.0.1", "logfmt": "^1.4.0", @@ -84,6 +85,7 @@ "@types/element-resize-detector": "^1.1.6", "@types/express": "^4.17.21", "@types/glob": "^8.1.0", + "@types/he": "^1.2.3", "@types/http-proxy": "^1.17.15", "@types/jsbn": "^1.2.33", "@types/lodash": "^4.17.13",