Skip to content

Commit 58d66e5

Browse files
authored
fix: escape html in renderer (#3495)
BREAKING CHANGE: escape html in renderers for all tokens.
1 parent dc4ae3b commit 58d66e5

File tree

6 files changed

+148
-85
lines changed

6 files changed

+148
-85
lines changed

src/Lexer.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -355,12 +355,7 @@ export class _Lexer {
355355
if (token = this.tokenizer.tag(src)) {
356356
src = src.substring(token.raw.length);
357357
lastToken = tokens[tokens.length - 1];
358-
if (lastToken && token.type === 'text' && lastToken.type === 'text') {
359-
lastToken.raw += token.raw;
360-
lastToken.text += token.text;
361-
} else {
362-
tokens.push(token);
363-
}
358+
tokens.push(token);
364359
continue;
365360
}
366361

src/Parser.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,15 @@ export class _Parser {
9898
let textToken = token;
9999
let body = this.renderer.text(textToken);
100100
while (i + 1 < tokens.length && tokens[i + 1].type === 'text') {
101-
textToken = tokens[++i] as Tokens.Text | Tokens.Tag;
101+
textToken = tokens[++i] as Tokens.Text;
102102
body += '\n' + this.renderer.text(textToken);
103103
}
104104
if (top) {
105105
out += this.renderer.paragraph({
106106
type: 'paragraph',
107107
raw: body,
108108
text: body,
109-
tokens: [{ type: 'text', raw: body, text: body }],
109+
tokens: [{ type: 'text', raw: body, text: body, escaped: true }],
110110
});
111111
} else {
112112
out += body;

src/Renderer.ts

+11-7
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,15 @@ export class _Renderer {
7979
if (item.tokens.length > 0 && item.tokens[0].type === 'paragraph') {
8080
item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
8181
if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
82-
item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text;
82+
item.tokens[0].tokens[0].text = checkbox + ' ' + escape(item.tokens[0].tokens[0].text);
83+
item.tokens[0].tokens[0].escaped = true;
8384
}
8485
} else {
8586
item.tokens.unshift({
8687
type: 'text',
8788
raw: checkbox + ' ',
8889
text: checkbox + ' ',
90+
escaped: true,
8991
});
9092
}
9193
} else {
@@ -164,7 +166,7 @@ export class _Renderer {
164166
}
165167

166168
codespan({ text }: Tokens.Codespan): string {
167-
return `<code>${text}</code>`;
169+
return `<code>${escape(text, true)}</code>`;
168170
}
169171

170172
br(token: Tokens.Br): string {
@@ -184,7 +186,7 @@ export class _Renderer {
184186
href = cleanHref;
185187
let out = '<a href="' + href + '"';
186188
if (title) {
187-
out += ' title="' + title + '"';
189+
out += ' title="' + (escape(title)) + '"';
188190
}
189191
out += '>' + text + '</a>';
190192
return out;
@@ -193,19 +195,21 @@ export class _Renderer {
193195
image({ href, title, text }: Tokens.Image): string {
194196
const cleanHref = cleanUrl(href);
195197
if (cleanHref === null) {
196-
return text;
198+
return escape(text);
197199
}
198200
href = cleanHref;
199201

200202
let out = `<img src="${href}" alt="${text}"`;
201203
if (title) {
202-
out += ` title="${title}"`;
204+
out += ` title="${escape(title)}"`;
203205
}
204206
out += '>';
205207
return out;
206208
}
207209

208-
text(token: Tokens.Text | Tokens.Escape | Tokens.Tag) : string {
209-
return 'tokens' in token && token.tokens ? this.parser.parseInline(token.tokens) : token.text;
210+
text(token: Tokens.Text | Tokens.Escape) : string {
211+
return 'tokens' in token && token.tokens
212+
? this.parser.parseInline(token.tokens)
213+
: ('escaped' in token && token.escaped ? token.text : escape(token.text));
210214
}
211215
}

src/Tokenizer.ts

+10-16
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { _defaults } from './defaults.ts';
22
import {
33
rtrim,
44
splitCells,
5-
escape,
65
findClosingBracket,
76
} from './helpers.ts';
87
import type { Rules } from './rules.ts';
@@ -12,7 +11,7 @@ import type { MarkedOptions } from './MarkedOptions.ts';
1211

1312
function outputLink(cap: string[], link: Pick<Tokens.Link, 'href' | 'title'>, raw: string, lexer: _Lexer): Tokens.Link | Tokens.Image {
1413
const href = link.href;
15-
const title = link.title ? escape(link.title) : null;
14+
const title = link.title || null;
1615
const text = cap[1].replace(/\\([\[\]])/g, '$1');
1716

1817
if (cap[0].charAt(0) !== '!') {
@@ -33,7 +32,7 @@ function outputLink(cap: string[], link: Pick<Tokens.Link, 'href' | 'title'>, ra
3332
raw,
3433
href,
3534
title,
36-
text: escape(text),
35+
text,
3736
};
3837
}
3938

@@ -583,7 +582,7 @@ export class _Tokenizer {
583582
return {
584583
type: 'escape',
585584
raw: cap[0],
586-
text: escape(cap[1]),
585+
text: cap[1],
587586
};
588587
}
589588
}
@@ -766,7 +765,6 @@ export class _Tokenizer {
766765
if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
767766
text = text.substring(1, text.length - 1);
768767
}
769-
text = escape(text, true);
770768
return {
771769
type: 'codespan',
772770
raw: cap[0],
@@ -802,10 +800,10 @@ export class _Tokenizer {
802800
if (cap) {
803801
let text, href;
804802
if (cap[2] === '@') {
805-
text = escape(cap[1]);
803+
text = cap[1];
806804
href = 'mailto:' + text;
807805
} else {
808-
text = escape(cap[1]);
806+
text = cap[1];
809807
href = text;
810808
}
811809

@@ -830,7 +828,7 @@ export class _Tokenizer {
830828
if (cap = this.rules.inline.url.exec(src)) {
831829
let text, href;
832830
if (cap[2] === '@') {
833-
text = escape(cap[0]);
831+
text = cap[0];
834832
href = 'mailto:' + text;
835833
} else {
836834
// do extended autolink path validation
@@ -839,7 +837,7 @@ export class _Tokenizer {
839837
prevCapZero = cap[0];
840838
cap[0] = this.rules.inline._backpedal.exec(cap[0])?.[0] ?? '';
841839
} while (prevCapZero !== cap[0]);
842-
text = escape(cap[0]);
840+
text = cap[0];
843841
if (cap[1] === 'www.') {
844842
href = 'http://' + cap[0];
845843
} else {
@@ -865,16 +863,12 @@ export class _Tokenizer {
865863
inlineText(src: string): Tokens.Text | undefined {
866864
const cap = this.rules.inline.text.exec(src);
867865
if (cap) {
868-
let text;
869-
if (this.lexer.state.inRawBlock) {
870-
text = cap[0];
871-
} else {
872-
text = escape(cap[0]);
873-
}
866+
const escaped = this.lexer.state.inRawBlock;
874867
return {
875868
type: 'text',
876869
raw: cap[0],
877-
text,
870+
text: cap[0],
871+
escaped,
878872
};
879873
}
880874
}

src/Tokens.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export namespace Tokens {
125125
raw: string;
126126
text: string;
127127
tokens?: Token[];
128+
escaped?: boolean;
128129
}
129130

130131
export interface Def {
@@ -142,7 +143,7 @@ export namespace Tokens {
142143
}
143144

144145
export interface Tag {
145-
type: 'text' | 'html';
146+
type: 'html';
146147
raw: string;
147148
inLink: boolean;
148149
inRawBlock: boolean;

0 commit comments

Comments
 (0)