Skip to content

Commit 73f0d5c

Browse files
myakuraCopilot
andauthored
fix: コードブロックのタイトルをHTMLエスケープし、marked v17 APIの関数シグネチャを修正 (#60)
- renderLivecodeSection の title 引数を escapeHtml() でエスケープ タイトルに '<' '>' '&' '"' "'" などが含まれる場合にHTMLタグとして 解釈されてしまう問題を修正(fixes #59) - code() の関数シグネチャを marked の実際の API に合わせて修正 PR #57 では token オブジェクト形式を想定していたが、marked v17 でも 実際のレンダラー呼び出しは (code, lang, escaped) のまま変わっていない - タイトルのHTMLエスケープを確認するテストを追加 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 510894b commit 73f0d5c

2 files changed

Lines changed: 31 additions & 9 deletions

File tree

lib/renderer/md/code.js

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,18 @@ import { marked } from 'marked';
22

33
const origCodeRender = marked.Renderer.prototype.code;
44

5+
const escapeHtml = (str) =>
6+
str
7+
.replace(/&/g, '&amp;')
8+
.replace(/</g, '&lt;')
9+
.replace(/>/g, '&gt;')
10+
.replace(/"/g, '&quot;')
11+
.replace(/'/g, '&#39;');
12+
513
const renderLivecodeSection = (title, codeHtml) => `
614
<section class="CG2-livecode">
715
<header class="CG2-livecode__header">
8-
<div class="CG2-livecode__label">${title}</div>
16+
<div class="CG2-livecode__label">${escapeHtml(title)}</div>
917
</header>
1018
<div class="CG2-livecode__body">${codeHtml}</div>
1119
</section>
@@ -23,29 +31,30 @@ const renderLivecodeSection = (title, codeHtml) => `
2331
* ```html#たいとる 言語指定とタイトル指定 ```
2432
* みたいにすると、拡張したやつでレンダリングして返す
2533
*
26-
* @param {object} token
27-
* marked v17 のコードトークン ({type, raw, lang, text})
34+
* @param {string} code
35+
* コード文字列
36+
* @param {string} lang
37+
* 言語指定文字列(`lang#タイトル` の形式を取る場合もある)
38+
* @param {boolean} escaped
2839
* @return {string}
2940
* HTML文字列
3041
*
3142
*/
32-
export default function(token) {
33-
const { lang } = token;
34-
43+
export default function(code, lang, escaped) {
3544
// そもそも言語指定ないやつ
3645
if (!lang) {
37-
return origCodeRender.call(this, token);
46+
return origCodeRender.call(this, code, lang, escaped);
3847
}
3948

4049
const langArr = lang.split('#').map((t) => t.trim());
4150
// 言語の指定はあったが、タイトルがない = 今まで通り
4251
if (langArr.length === 1 || langArr[1].length === 0) {
43-
return origCodeRender.call(this, { ...token, lang: langArr[0] });
52+
return origCodeRender.call(this, code, langArr[0], escaped);
4453
}
4554

4655
// 言語の指定もタイトル指定もちゃんとあるやつ
4756
return renderLivecodeSection(
4857
langArr[1], // タイトル
49-
origCodeRender.call(this, { ...token, lang: langArr[0] }) // コード本体
58+
origCodeRender.call(this, code, langArr[0], escaped) // コード本体
5059
);
5160
}

test/cgmd/renderer/md/code.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,18 @@ describe('#code', function() {
3939
assert(html1 === expect);
4040
assert(html2 === expect);
4141
});
42+
it('タイトルにHTML特殊文字が含まれる場合はエスケープされる', function() {
43+
const html = renderer.render('```html#<template>の使い方\nhoge\n```');
44+
const expect = '' +
45+
'<section class="CG2-livecode">\n' +
46+
'<header class="CG2-livecode__header">\n' +
47+
'<div class="CG2-livecode__label">&lt;template&gt;の使い方</div>\n' +
48+
'</header>\n' +
49+
'<div class="CG2-livecode__body">' +
50+
'<pre><code class="language-html">hoge\n</code></pre>\n' +
51+
'</div>\n' +
52+
'</section>\n';
53+
assert(html === expect, `expected escaped HTML, got: ${html}`);
54+
});
4255
});
4356
});

0 commit comments

Comments
 (0)