diff --git a/src/Renderer.ts b/src/Renderer.ts index 890ac2c4ec..b04adc3a3a 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -157,18 +157,22 @@ export class _Renderer { return `${this.parser.parseInline(tokens)}` as RendererOutput; } - link({ href, title, tokens }: Tokens.Link): RendererOutput { - const text = this.parser.parseInline(tokens) as string; + link({ href, title, text, tokens, autolink }: Tokens.Link): RendererOutput { + // References are not resolved inside an autolink, so every `&` there is + // literal. Elsewhere only an `&` that cannot start one needs escaping. + const parsedText = autolink + ? escapeHtmlEntities(text, true) + : this.parser.parseInline(tokens) as string; const cleanHref = cleanUrl(href); if (cleanHref === null) { - return text as RendererOutput; + return parsedText as RendererOutput; } - href = cleanHref; + href = escapeHtmlEntities(cleanHref, autolink); let out = ''; + out += '>' + parsedText + ''; return out as RendererOutput; } @@ -182,7 +186,7 @@ export class _Renderer { } href = cleanHref; - let out = `${escapeHtmlEntities(text)} { raw: cap[0], text, href, + autolink: true, tokens: [ { type: 'text', @@ -951,6 +952,7 @@ export class _Tokenizer { raw: cap[0], text, href, + autolink: true, tokens: [ { type: 'text', diff --git a/src/Tokens.ts b/src/Tokens.ts index 35c058691d..afc5dd75ee 100644 --- a/src/Tokens.ts +++ b/src/Tokens.ts @@ -136,6 +136,11 @@ export namespace Tokens { title?: string | null; text: string; tokens: Token[]; + /** + * Set for autolinks and extended (GFM) urls, where character references are + * not resolved, so the destination and text are literal. + */ + autolink?: boolean; } export interface List { diff --git a/test/specs/new/link_destination_character_references.html b/test/specs/new/link_destination_character_references.html new file mode 100644 index 0000000000..5e37641032 --- /dev/null +++ b/test/specs/new/link_destination_character_references.html @@ -0,0 +1,15 @@ +

https://example.com/?x=1<2

+

https://example.com/?y=1&2

+

https://example.com/?a=1&b=2

+

https://example.com/?x=1<2

+

https://example.com/?y=1&2

+

https://example.com/?a=1&b=2

+

https://example.com/?x=1<2

+

https://example.com/?y=1&2

+

https://example.com/?a=1&b=2

+

https://example.com/?x=1<2

+

https://example.com/?y=1&2

+

https://example.com/?a=1&b=2

+

https://example.com/?x=1<2

+

https://example.com/?y=1&2

+

https://example.com/?a=1&b=2

diff --git a/test/specs/new/link_destination_character_references.md b/test/specs/new/link_destination_character_references.md new file mode 100644 index 0000000000..1bceff43f6 --- /dev/null +++ b/test/specs/new/link_destination_character_references.md @@ -0,0 +1,45 @@ +--- +# Character references resolve in a link destination but not in an autolink, so +# the same query string has to be escaped differently depending on where it is +# written. +# +# The `<` inline links, reference links and images below do not match +# CommonMark. It resolves the reference and percent-encodes the result, giving +# `?x=1%3C2`, where marked keeps `?x=1<2`. That difference comes from URL +# encoding rather than from this escaping, and it is the same on `master`. +# Everything else here matches CommonMark. +renderExact: true +--- +https://example.com/?x=1<2 + +https://example.com/?y=1&2 + +https://example.com/?a=1&b=2 + + + + + + + +[https://example.com/?x=1<2](https://example.com/?x=1<2) + +[https://example.com/?y=1&2](https://example.com/?y=1&2) + +[https://example.com/?a=1&b=2](https://example.com/?a=1&b=2) + +[https://example.com/?x=1<2][link1] + +[https://example.com/?y=1&2][link2] + +[https://example.com/?a=1&b=2][link3] + +![https://example.com/?x=1<2](https://example.com/?x=1<2) + +![https://example.com/?y=1&2](https://example.com/?y=1&2) + +![https://example.com/?a=1&b=2](https://example.com/?a=1&b=2) + +[link1]: https://example.com/?x=1<2 +[link2]: https://example.com/?y=1&2 +[link3]: https://example.com/?a=1&b=2 diff --git a/test/unit/Lexer.test.js b/test/unit/Lexer.test.js index 8064ff59b4..0afbd256b2 100644 --- a/test/unit/Lexer.test.js +++ b/test/unit/Lexer.test.js @@ -2042,6 +2042,7 @@ paragraph raw: '', text: 'https://example.com', href: 'https://example.com', + autolink: true, tokens: [ { type: 'text', @@ -2064,6 +2065,7 @@ paragraph raw: '', text: 'test@example.com', href: 'mailto:test@example.com', + autolink: true, tokens: [ { type: 'text', @@ -2085,6 +2087,7 @@ paragraph raw: 'https://example.com', text: 'https://example.com', href: 'https://example.com', + autolink: true, tokens: [ { type: 'text', @@ -2107,6 +2110,7 @@ paragraph raw: 'test@example.com', text: 'test@example.com', href: 'mailto:test@example.com', + autolink: true, tokens: [ { type: 'text',