From d32778952579c42597b081d62cdf44fa8e86c358 Mon Sep 17 00:00:00 2001 From: Sukka Date: Sat, 20 Jun 2020 16:00:32 +0800 Subject: [PATCH] fix(post): avoid swig in code being double escaped (#4362) --- lib/hexo/post.js | 13 ++++++------ test/scripts/hexo/post.js | 42 +++++++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/lib/hexo/post.js b/lib/hexo/post.js index 73e2328d55..5a384f8d67 100644 --- a/lib/hexo/post.js +++ b/lib/hexo/post.js @@ -10,6 +10,9 @@ const { slugize, escapeRegExp } = require('hexo-util'); const { copyDir, exists, listDir, mkdirs, readFile, rmdir, unlink, writeFile } = require('hexo-fs'); const yfm = require('hexo-front-matter'); +const replaceSwigTag = str => str.replace(/{/g, '\uFFFCleft\uFFFC').replace(/}/g, '\uFFFCright\uFFFC'); +const restoreReplacesSwigTag = str => str.replace(/\uFFFCleft\uFFFC/g, '{').replace(/\uFFFCright\uFFFC/g, '}'); + const preservedKeys = ['title', 'slug', 'path', 'layout', 'date', 'content']; const rPlaceholder = /(?:<|<)!--\uFFFC(\d+)--(?:>|>)/g; @@ -18,7 +21,7 @@ const rSwigComment = /\{#[\s\S]*?#\}/g; const rSwigBlock = /\{%[\s\S]*?%\}/g; const rSwigFullBlock = /\{% *(.+?)(?: *| +.*?)%\}[\s\S]+?\{% *end\1 *%\}/g; const rSwigRawFullBlock = /{% *raw *%\}[\s\S]+?\{% *endraw *%\}/g; -const rSwigTagInsideInlineCode = /`.*{.*}.*`/g; +const rSwigTagInsideInlineCode = /`.*?{.*?}.*?`/g; const _escapeContent = (cache, str) => { const placeholder = '\uFFFC'; @@ -48,9 +51,7 @@ class PostRenderCache { escapeAllSwigTags(str) { const escape = _str => _escapeContent(this.cache, _str); return str.replace(rSwigRawFullBlock, escape) // Escape {% raw %} first - .replace(rSwigTagInsideInlineCode, str => { - return str.replace(/{/g, '{').replace(/}/g, '}'); - }) + .replace(rSwigTagInsideInlineCode, replaceSwigTag) // Avoid double escaped by marked renderer .replace(rSwigFullBlock, escape) .replace(rSwigBlock, escape) .replace(rSwigComment, '') @@ -256,7 +257,6 @@ class Post { return promise.then(content => { data.content = content; - // Run "before_post_render" filters return ctx.execFilter('before_post_render', data, { context: ctx }); }).then(() => { @@ -277,7 +277,7 @@ class Post { toString: true, onRenderEnd(content) { // Replace cache data with real contents - data.content = cacheObj.loadContent(content); + data.content = cacheObj.loadContent(restoreReplacesSwigTag(content)); // Return content after replace the placeholders if (disableNunjucks) return data.content; @@ -287,6 +287,7 @@ class Post { } }, options); }).then(content => { + // restore { and } inside inline code data.content = content; // Run "after_post_render" filters diff --git a/test/scripts/hexo/post.js b/test/scripts/hexo/post.js index 0311c1e00b..8ff492f694 100644 --- a/test/scripts/hexo/post.js +++ b/test/scripts/hexo/post.js @@ -8,6 +8,7 @@ const { highlight, escapeHTML } = require('hexo-util'); const { spy, useFakeTimers } = require('sinon'); const frontMatter = require('hexo-front-matter'); const fixture = require('../../fixtures/post_render'); +const escapeSwigTag = str => str.replace(/{/g, '{').replace(/}/g, '}'); describe('Post', () => { const Hexo = require('../../../lib/hexo'); @@ -455,8 +456,8 @@ describe('Post', () => { const paths = [join(hexo.source_dir, '_posts', 'Hello-World-1.md')]; return Promise.all([ - post.create({title: 'Hello World', layout: 'draft'}), - post.create({title: 'Hello World'}) + post.create({ title: 'Hello World', layout: 'draft' }), + post.create({ title: 'Hello World' }) ]).then(data => { paths.push(data[1].path); @@ -473,8 +474,8 @@ describe('Post', () => { const path = join(hexo.source_dir, '_posts', 'Hello-World.md'); return Promise.all([ - post.create({title: 'Hello World', layout: 'draft'}), - post.create({title: 'Hello World'}) + post.create({ title: 'Hello World', layout: 'draft' }), + post.create({ title: 'Hello World' }) ]).then(data => post.publish({ slug: 'Hello-World' }, true)).then(data => { @@ -1015,7 +1016,7 @@ describe('Post', () => { engine: 'markdown' }); - data.content.trim().should.eql('

In Go’s templates, blocks look like this: &#123;&#123;block "template name" .&#125;&#125; (content) &#123;&#123;end&#125;&#125;.

'); + data.content.trim().should.eql(`

In Go’s templates, blocks look like this: ${escapeSwigTag(escapeHTML('{{block "template name" .}} (content) {{end}}'))}.

`); }); // test for https://github.com/hexojs/hexo/issues/3346#issuecomment-595497849 @@ -1027,7 +1028,7 @@ describe('Post', () => { engine: 'markdown' }); - data.content.trim().should.eql('

&#123;&#123; 1 + 1 &#125;&#125; 2

'); + data.content.trim().should.eql(`

${escapeSwigTag('{{ 1 + 1 }}')} 2

`); }); // https://github.com/hexojs/hexo/issues/4317 @@ -1083,6 +1084,35 @@ describe('Post', () => { data.content.trim().should.contains(highlightedHtml); }); + it('render() - escape & recover muilt {% raw %} and backticks', async () => { + const content = [ + '`{{ 1 + 1 }}` {{ 1 + 2 }} `{{ 2 + 2 }}`', + 'Text', + '{% raw %}', + 'Raw 1', + '{% endraw %}', + 'Another Text', + '{% raw %}', + 'Raw 2', + '{% endraw %}' + ].join('\n'); + + const data = await post.render(null, { + content, + engine: 'markdown' + }); + + data.content.trim().should.eql([ + `

${escapeSwigTag('{{ 1 + 1 }}')} 3 ${escapeSwigTag('{{ 2 + 2 }}')}
Text

`, + '', + 'Raw 1', + '', + '

Another Text

', + '', + 'Raw 2' + ].join('\n')); + }); + // https://github.com/hexojs/hexo/issues/4087 it('render() - issue #4087', async () => { // Adopted from https://github.com/hexojs/hexo/issues/4087#issuecomment-596999486