authorgravatar for lucenz@proton.meSimone <lucenz@proton.me> 2026-08-13 21:08:13+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-13 15:08:13-04:00
log83ad26c3a3aeb877ee8bf07c05145ad804717e2e
treeae40506b18b9a0a9fba5bf1d48b56b53b987988b
parentfd51561b4e2de1893cb7eb4069937256d702572c
signature Signed by PGP key B5690EEEBB952194

fix(mobile): prevent invalid HTML entities from crashing markdown (#6495)

Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com>

2 files changed, 25 insertions(+), 2 deletions(-)

apps/mobile/modules/t3-markdown-text/src/nativeMarkdownText.ts+9-2
...@@ -70,15 +70,22 @@ const EMPTY_CONTEXT: RunContext = {...@@ -70,15 +70,22 @@ const EMPTY_CONTEXT: RunContext = {
7070
71const INLINE_HTML_TAG_PATTERN = /<\/?(?:kbd|mark|sub|sup|u)(?:\s[^>]*)?>/gi;71const INLINE_HTML_TAG_PATTERN = /<\/?(?:kbd|mark|sub|sup|u)(?:\s[^>]*)?>/gi;
7272
73function decodeCodePoint(codePoint: number, entity: string): string {
74 if (!Number.isInteger(codePoint) || codePoint < 0 || codePoint > 0x10ffff) {
75 return entity;
76 }
77 return String.fromCodePoint(codePoint);
78}
79
73function decodeHtmlEntitiesOnce(value: string): string {80function decodeHtmlEntitiesOnce(value: string): string {
74 return value.replace(81 return value.replace(
75 /&(?:#(\d+)|#x([0-9a-f]+)|amp|apos|gt|lt|nbsp|quot);/gi,82 /&(?:#(\d+)|#x([0-9a-f]+)|amp|apos|gt|lt|nbsp|quot);/gi,
76 (entity, decimal: string | undefined, hexadecimal: string | undefined) => {83 (entity, decimal: string | undefined, hexadecimal: string | undefined) => {
77 if (decimal) {84 if (decimal) {
78 return String.fromCodePoint(Number.parseInt(decimal, 10));85 return decodeCodePoint(Number.parseInt(decimal, 10), entity);
79 }86 }
80 if (hexadecimal) {87 if (hexadecimal) {
81 return String.fromCodePoint(Number.parseInt(hexadecimal, 16));88 return decodeCodePoint(Number.parseInt(hexadecimal, 16), entity);
82 }89 }
83 switch (entity.toLowerCase()) {90 switch (entity.toLowerCase()) {
84 case "&amp;":91 case "&amp;":
apps/mobile/src/lib/nativeMarkdownText.test.ts+16
...@@ -126,6 +126,22 @@ describe("nativeMarkdownTextRuns", () => {...@@ -126,6 +126,22 @@ describe("nativeMarkdownTextRuns", () => {
126 ]);126 ]);
127 });127 });
128128
129 it.each([
130 ["&#128512;", "😀"],
131 ["&#x1f680;", "🚀"],
132 ["&#9999999999;", "&#9999999999;"],
133 ["&#x110000;", "&#x110000;"],
134 ["&amp;#9999999999;", "&#9999999999;"],
135 ["&amp;#x110000;", "&#x110000;"],
136 ])("normalizes numeric entity %s without throwing", (content, expected) => {
137 const node: MarkdownNode = {
138 type: "paragraph",
139 children: [{ type: "text", content }],
140 };
141
142 expect(nativeMarkdownTextRuns(node)).toEqual([{ text: expected }]);
143 });
144
129 it("reads inline content from nested text nodes", () => {145 it("reads inline content from nested text nodes", () => {
130 const node: MarkdownNode = {146 const node: MarkdownNode = {
131 type: "paragraph",147 type: "paragraph",