Skip to content

Commit 3a36ca6

Browse files
authored
fix: nunjucks/code blocks in HTML comments were incorrectly converted (#5616)
* fix: nunjucks/codefence nesting in comments * fix: code fence nesting in comments * Revert "fix: code fence nesting in comments" This reverts commit 33bdd82. * fix: code fence nesting in comments --------- Signed-off-by: D-Sketon <2055272094@qq.com>
1 parent 03a3a4e commit 3a36ca6

File tree

3 files changed

+109
-2
lines changed

3 files changed

+109
-2
lines changed

lib/hexo/post.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import type { NodeJSLikeCallback, RenderData } from '../types';
1313
const preservedKeys = ['title', 'slug', 'path', 'layout', 'date', 'content'];
1414

1515
const rHexoPostRenderEscape = /<hexoPostRenderCodeBlock>([\s\S]+?)<\/hexoPostRenderCodeBlock>/g;
16+
const rCommentEscape = /(<!--[\s\S]*?-->)/g;
1617
const rSwigTag = /(\{\{.+?\}\})|(\{#.+?#\})|(\{%.+?%\})/s;
1718

1819
const rSwigPlaceHolder = /(?:<|&lt;)!--swig\uFFFC(\d+)--(?:>|&gt;)/g;
1920
const rCodeBlockPlaceHolder = /(?:<|&lt;)!--code\uFFFC(\d+)--(?:>|&gt;)/g;
21+
const rCommentHolder = /(?:<|&lt;)!--comment\uFFFC(\d+)--(?:>|&gt;)/g;
2022

2123
const STATE_PLAINTEXT = 0;
2224
const STATE_SWIG_VAR = 1;
@@ -61,6 +63,14 @@ class PostRenderEscape {
6163
return str.replace(rCodeBlockPlaceHolder, PostRenderEscape.restoreContent(this.stored));
6264
}
6365

66+
restoreComments(str: string) {
67+
return str.replace(rCommentHolder, PostRenderEscape.restoreContent(this.stored));
68+
}
69+
70+
escapeComments(str: string) {
71+
return str.replace(rCommentEscape, (_, content) => PostRenderEscape.escapeContent(this.stored, 'comment', content));
72+
}
73+
6474
escapeCodeBlocks(str: string) {
6575
return str.replace(rHexoPostRenderEscape, (_, content) => PostRenderEscape.escapeContent(this.stored, 'code', content));
6676
}
@@ -502,6 +512,8 @@ class Post {
502512
// Run "before_post_render" filters
503513
return ctx.execFilter('before_post_render', data, { context: ctx });
504514
}).then(() => {
515+
// Escape all comments to avoid conflict with Nunjucks and code block
516+
data.content = cacheObj.escapeComments(data.content);
505517
data.content = cacheObj.escapeCodeBlocks(data.content);
506518
// Escape all Nunjucks/Swig tags
507519
let hasSwigTag = true;
@@ -535,6 +547,7 @@ class Post {
535547
}, options);
536548
}).then(content => {
537549
data.content = cacheObj.restoreCodeBlocks(content);
550+
data.content = cacheObj.restoreComments(data.content);
538551

539552
// Run "after_post_render" filters
540553
return ctx.execFilter('after_post_render', data, { context: ctx });

lib/plugins/filter/before_post_render/backtick_code_block.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import type { HighlightOptions } from '../../../extend/syntax_highlight';
22
import type Hexo from '../../../hexo';
33
import type { RenderData } from '../../../types';
4+
import assert from 'assert';
45

56
const rBacktick = /^((?:(?:[^\S\r\n]*>){0,3}|[-*+]|[0-9]+\.)[^\S\r\n]*)(`{3,}|~{3,})[^\S\r\n]*((?:.*?[^`\s])?)[^\S\r\n]*\n((?:[\s\S]*?\n)?)(?:(?:[^\S\r\n]*>){0,3}[^\S\r\n]*)\2[^\S\r\n]?(\n+|$)/gm;
67
const rAllOptions = /([^\s]+)\s+(.+?)\s+(https?:\/\/\S+|\/\S+)\s*(.+)?/;
78
const rLangCaption = /([^\s]+)\s*(.+)?/;
9+
const rCommentEscape = /(<!--[\s\S]*?-->)/g;
10+
const rCommentHolder = /(?:<|&lt;)!--comment\uFFFC(\d+)--(?:>|&gt;)/g;
811
const rAdditionalOptions = /\s((?:line_number|line_threshold|first_line|wrap|mark|language_attr|highlight):\S+)/g;
912

1013
const escapeSwigTag = (str: string) => str.replace(/{/g, '&#123;').replace(/}/g, '&#125;');
@@ -80,13 +83,44 @@ function parseArgs(args: string) {
8083
};
8184
}
8285

86+
class CodeBlockEscape {
87+
public stored: string[];
88+
89+
constructor() {
90+
this.stored = [];
91+
}
92+
93+
static escapeContent(cache: string[], flag: string, str: string) {
94+
return `<!--${flag}\uFFFC${cache.push(str) - 1}-->`;
95+
}
96+
97+
static restoreContent(cache: string[]) {
98+
return (_: string, index: number) => {
99+
assert(cache[index]);
100+
const value = cache[index];
101+
cache[index] = null;
102+
return value;
103+
};
104+
}
105+
106+
restoreComments(str: string) {
107+
return str.replace(rCommentHolder, CodeBlockEscape.restoreContent(this.stored));
108+
}
109+
110+
escapeComments(str: string) {
111+
return str.replace(rCommentEscape, (_, content) => CodeBlockEscape.escapeContent(this.stored, 'comment', content));
112+
}
113+
114+
}
115+
83116
export = (ctx: Hexo): (data: RenderData) => void => {
84117
return function backtickCodeBlock(data: RenderData): void {
85118
const dataContent = data.content;
86119

87120
if ((!dataContent.includes('```') && !dataContent.includes('~~~')) || !ctx.extend.highlight.query(ctx.config.syntax_highlighter)) return;
88-
89-
data.content = dataContent.replace(rBacktick, ($0, start, $2, _args, _content, end) => {
121+
const cacheObj = new CodeBlockEscape();
122+
data.content = cacheObj.escapeComments(data.content);
123+
data.content = data.content.replace(rBacktick, ($0, start, $2, _args, _content, end) => {
90124
let content = _content.replace(/\n$/, '');
91125

92126
// neither highlight or prismjs is enabled, return escaped content directly.
@@ -148,5 +182,6 @@ export = (ctx: Hexo): (data: RenderData) => void => {
148182
+ '</hexoPostRenderCodeBlock>'
149183
+ end;
150184
});
185+
data.content = cacheObj.restoreComments(data.content);
151186
};
152187
};

test/scripts/hexo/post.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,4 +1601,63 @@ describe('Post', () => {
16011601

16021602
data.content.should.eql('<a href="https://hexo.io/" title="tttitle" target="">foobar</a>');
16031603
});
1604+
1605+
// https://github.com/hexojs/hexo/issues/5433
1606+
it('render() - nunjucks nesting in comments', async () => {
1607+
const content = [
1608+
'foo',
1609+
'<!--',
1610+
'{% raw %}',
1611+
'test',
1612+
'{% endraw %}',
1613+
'-->',
1614+
'bar'
1615+
].join('\n');
1616+
1617+
const data = await post.render('', {
1618+
content,
1619+
engine: 'markdown'
1620+
});
1621+
1622+
data.content.should.eql([
1623+
'<p>foo</p>',
1624+
'<!--',
1625+
'{% raw %}',
1626+
'test',
1627+
'{% endraw %}',
1628+
'-->',
1629+
'<p>bar</p>',
1630+
''
1631+
].join('\n'));
1632+
});
1633+
1634+
// https://github.com/hexojs/hexo/issues/5433
1635+
it('render() - code fence nesting in comments', async () => {
1636+
const code = 'alert("Hello world")';
1637+
const content = [
1638+
'foo',
1639+
'<!--',
1640+
'```',
1641+
code,
1642+
'```',
1643+
'-->',
1644+
'bar'
1645+
].join('\n');
1646+
1647+
const data = await post.render('', {
1648+
content,
1649+
engine: 'markdown'
1650+
});
1651+
1652+
data.content.should.eql([
1653+
'<p>foo</p>',
1654+
'<!--',
1655+
'```',
1656+
code,
1657+
'```',
1658+
'-->',
1659+
'<p>bar</p>',
1660+
''
1661+
].join('\n'));
1662+
});
16041663
});

0 commit comments

Comments
 (0)