JavaScript Tips | 文字列ユーティリティ:検索・置換 - HTML アンエスケープ

JavaScript JavaScript
スポンサーリンク

「HTML アンエスケープ」とは何をするものか

前回の「HTML エスケープ」は、
<& などを「タグとして解釈されないように」安全な文字列に変える処理でした。

"<b>太字</b>" → "&lt;b&gt;太字&lt;/b&gt;"

HTML アンエスケープは、その逆方向です。

"&lt;b&gt;太字&lt;/b&gt;" → "<b>太字</b>"
"Tom &amp; Jerry" → "Tom & Jerry"

「エスケープされた文字列」を「元の記号に戻す」処理が、HTML アンエスケープです。


どんな場面で HTML アンエスケープが必要になるのか

すでにエスケープされた文字列を「人間が読む形」に戻したいとき

ログやデータベースに、
すでにエスケープ済みの文字列が保存されていることがあります。

タイトル: &lt;重要&gt; お知らせ
本文: Tom &amp; Jerry の新作です

これを画面上で「そのままの文字」として見せたいとき、
&lt;&amp; のままだと読みにくいので、
<& に戻したくなります。

このときに使うのが HTML アンエスケープです。

「テキストとして扱いたい」けれど、元の記号が欲しいとき

例えば、ユーザーに

入力例: <b>太字</b>

と表示したいのに、
データとしては &lt;b&gt;太字&lt;/b&gt; が渡されている場合、
アンエスケープしてから表示したほうが自然です。


まずは「エスケープ」と「アンエスケープ」の対応を整理する

HTML エスケープでよく使う変換は、だいたいこうでした。

&&amp;
<&lt;
>&gt;
"&quot;
'&#39;

アンエスケープは、この逆をやります。

&amp;&
&lt;<
&gt;>
&quot;"
&#39;'

この対応関係を、まず頭に置いておいてください。


シンプルな HTML アンエスケープユーティリティ

実装例

業務でよく使うレベルの、素直なアンエスケープ関数を書いてみます。

function unescapeHtml(value) {
  if (value == null) return "";

  return String(value)
    .replace(/</g, "<")
    .replace(/>/g, ">")
    .replace(/"/g, "\"")
    .replace(/'/g, "'")
    .replace(/&/g, "&");
}
JavaScript

ポイントを噛み砕きます。

null / undefined が来ても落ちないようにしている。
String(value) で文字列にしてから置換している。
&amp; の置換を一番最後にしている(先にやると、他の &lt; などが壊れるから)。
よく使う5種類のエンティティだけを対象にしている。

これで、基本的なアンエスケープは十分こなせます。


具体例で動きを確認する

タグっぽい文字列を元に戻す

unescapeHtml("<b>太字</b>");
// "<b>太字</b>"
JavaScript

これは「タグとして解釈させたい」わけではなく、
<b>太字</b> という文字列をそのまま見せたい」ケースで使います。

記号を含むテキストを元に戻す

unescapeHtml("Tom & Jerry");
// "Tom & Jerry"

unescapeHtml("He said "hello".");
// "He said \"hello\"."
JavaScript

&amp;&quot; が、
人間にとって自然な &" に戻っているのが分かると思います。


重要な注意点:アンエスケープしたものをそのまま HTML に入れない

ここが一番大事なポイントです。

HTML アンエスケープは、「安全にする処理」ではありません。
むしろ、場合によっては「危険に戻す処理」です。

例えば、次のような文字列があったとします。

"&lt;script&gt;alert('XSS');&lt;/script&gt;"

これをアンエスケープすると、

unescapeHtml("<script>alert('XSS');</script>");
// "<script>alert('XSS');</script>"
JavaScript

これをそのまま innerHTML に入れたら、
普通にスクリプトが実行されてしまいます。

// 絶対にやってはいけない例
element.innerHTML = unescapeHtml(storedValue);
JavaScript

なので、

アンエスケープしたものを「HTML として解釈させる」のは、基本的に危険。
アンエスケープは「テキストとして見せたいとき」にだけ使う。

という線引きを、しっかり意識しておいてください。


どんなときに「アンエスケープしてから表示」してよいか

「HTML として解釈させない」表示方法なら OK

例えば、textContentinnerText に入れる場合は、
ブラウザは中身を「ただのテキスト」として扱います。

const raw = "<b>太字</b>";

element.textContent = unescapeHtml(raw);
// 画面には <b>太字</b> と“そのまま”表示される(タグとしては解釈されない)
JavaScript

この場合は、アンエスケープしても XSS にはなりません。
「人間にとって読みやすい形に戻す」用途として、素直に使えます。

ログやダウンロード用テキストなど

ログファイルに出力するときや、
テキストファイルとしてダウンロードさせるときなども、
「HTML として解釈されない」ので、アンエスケープして構いません。


HTML エスケープとアンエスケープの設計ルール

保存するのは「生のデータ」か「エスケープ済み」か

理想は、

DB や API では「生のデータ」を扱う。
画面に出すときだけ escapeHtml する。

です。

しかし、現実には「すでにエスケープ済みのデータが保存されている」システムもあります。
その場合、

画面に出すときに「二重エスケープ」になっていないか。
API で返すときに「エスケープ済み文字列」を返していないか。

などを確認する必要があります。

アンエスケープユーティリティは、
そういった「歴史的事情のあるデータ」を扱うときの“救済手段”として使うことが多いです。

「エスケープ」と「アンエスケープ」を混乱させない

プロジェクトのどこかに、共通のユーティリティとして

export function escapeHtml(value) { ... }
export function unescapeHtml(value) { ... }
JavaScript

を置いておき、
「どこでどちらを使うか」をチームで共有しておくと、
「ここはもうエスケープされてるんだっけ?」という混乱が減ります。


ちょっとだけ手を動かしてみる

コンソールで、次のあたりを試してみてください。

unescapeHtml("<b>太字</b>");
unescapeHtml("Tom & Jerry");
unescapeHtml("He said "hello" & left.");
JavaScript

そして、escapeHtml と組み合わせて、

const original = "<b>太字</b>";
const escaped = escapeHtml(original);
const unescaped = unescapeHtml(escaped);
JavaScript

を試してみてください。

originalescapedunescaped
どう変わって、どう元に戻るかを確認すると、
「エスケープ」と「アンエスケープ」の役割がかなりクリアになります。

そのうえで、自分のプロジェクトに

export function escapeHtml(value) { ... }
export function unescapeHtml(value) { ... }
JavaScript

を一つ置いて、

「HTML に埋め込むときは escapeHtml」
「テキストとして読みやすくしたいときだけ unescapeHtml(ただし HTML としては解釈させない)」

というルールにしてみてください。

それができた瞬間、あなたの文字列処理は
「なんとなく < と & をいじっている状態」から
「セキュリティと可読性を意識した設計」に、一段レベルアップします。

タイトルとURLをコピーしました