Java Tips | 文字列処理:HTMLエスケープ

Java Java
スポンサーリンク

HTMLエスケープは「文字列を“ただの文字”として安全に表示する」技

Web画面に文字列を表示するとき、その文字列が「HTMLとして解釈されるか」「ただの文字として扱われるか」は、とても重要です。

<b>重要</b>
これを「太字にしたいHTML」として扱うのか、
<b>重要</b> という“そのままの文字”として画面に見せたいのか。

さらに、悪意ある入力があった場合――

<script>alert('XSS');</script>

これをそのまま画面に出してしまうと、ブラウザが「スクリプト」として実行してしまいます。
ここで必要になるのが HTMLエスケープ、つまり「HTMLとして意味を持つ記号を、安全な文字列表現に変換する」技です。


HTMLエスケープの基本:「特別な記号を“無害な文字列”に変える」

代表的なエスケープ対象

HTMLでは、特別な意味を持つ記号があります。
最低限、次の5つは必ず押さえます。

&&amp;
<&lt;
>&gt;
"&quot;
'&#39;(または &apos; だが、実務では &#39; がよく使われる)

例えば、ユーザー入力として <b>重要</b> が来たとき、
これをそのままHTMLに埋め込むと「太字タグ」として解釈されますが、

&lt;b&gt;重要&lt;/b&gt;

とエスケープしておけば、ブラウザは「タグ」ではなく「文字」として表示します。


自前実装:シンプルなHTMLエスケープユーティリティ

まずは「5つの基本文字」だけを確実に置き換える

業務で最低限困らないレベルのHTMLエスケープは、自前でも簡単に書けます。

public final class HtmlEscaper {

    private HtmlEscaper() {}

    public static String escape(String text) {
        if (text == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder(text.length() * 2);
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            switch (c) {
                case '&':
                    sb.append("&");
                    break;
                case '<':
                    sb.append("<");
                    break;
                case '>':
                    sb.append(">");
                    break;
                case '"':
                    sb.append(""");
                    break;
                case '\'':
                    sb.append("'");
                    break;
                default:
                    sb.append(c);
            }
        }
        return sb.toString();
    }
}
Java

使い方はこうなります。

System.out.println(HtmlEscaper.escape("<b>重要</b>"));
// <b>重要</b>

System.out.println(HtmlEscaper.escape("Tom & Jerry"));
// Tom & Jerry

System.out.println(HtmlEscaper.escape("\"quote\" and 'single'"));
// "quote" and 'single'
Java

ここで深掘りしたい重要ポイントは二つです。

一つ目は、「& を必ず最初に、かつ確実に &amp; に変えている」ことです。
&lt;&gt; などのエスケープ済み文字列が混ざっていても、
二重エスケープにならないようにするには、「入力は“生の文字列”である」という前提を守ることが大事です。
(すでにエスケープ済みのものを再度エスケープしない、という運用ルールもセットで必要になります。)

二つ目は、「**エスケープ対象を“最小限に絞っている”」ことです。
HTMLとして意味を持つ記号だけを変換し、それ以外はそのまま通します。
これにより、「日本語や記号が変な文字列になる」といった副作用を避けられます。


例題:ユーザー入力をそのまま画面に出すときの危険と対策

「入力値をそのままHTMLに埋め込む」は危険

例えば、こんなコードを考えてみます。

String name = request.getParameter("name");
out.println("<p>こんにちは、" + name + " さん</p>");
Java

ここで、ユーザーが次のような値を送ってきたらどうなるでしょう。

<script>alert('XSS');</script>

そのままHTMLに埋め込まれると、ブラウザはこれをスクリプトとして実行してしまいます。
これが典型的な XSS(クロスサイトスクリプティング)です。

これを防ぐには、画面に出す直前で必ずHTMLエスケープする必要があります。

String name = request.getParameter("name");
String safeName = HtmlEscaper.escape(name);
out.println("<p>こんにちは、" + safeName + " さん</p>");
Java

こうしておけば、画面には次のように表示されます。

<p>こんにちは、&lt;script&gt;alert('XSS');&lt;/script&gt; さん</p>

ブラウザはこれを「ただの文字」として扱うので、スクリプトは実行されません。

ここでの重要ポイントは、「“入力時”ではなく“出力時(HTMLに埋め込む直前)”にエスケープする」という設計です。
入力の段階でエスケープしてしまうと、
「DBにはエスケープ済み」「ログには生の値」など状態がバラバラになり、
どこが安全でどこが危険なのか分かりにくくなります。


例題:属性値の中に埋め込むときの注意点

<input value="ここに入る"> のようなケース

HTMLエスケープは、「テキストノード」と「属性値」で少し意味合いが変わります。

例えば、次のようなHTMLを出力したいとします。

<input type="text" name="q" value="ユーザー入力">

ここで value にユーザー入力を埋め込むときも、必ずエスケープが必要です。

String q = request.getParameter("q");
String safeQ = HtmlEscaper.escape(q);
out.println("<input type=\"text\" name=\"q\" value=\"" + safeQ + "\">");
Java

もしエスケープしないと、こんな入力で壊れます。

" onfocus="alert('XSS')

これがそのまま入ると、HTMLはこうなります。

<input type="text" name="q" value="" onfocus="alert('XSS')">

属性が途中で閉じられ、意図しない属性が追加されてしまいます。

"'&quot;&#39; にエスケープしておけば、
ブラウザはそれを「属性の区切り」ではなく「ただの文字」として扱います。

ここでのポイントは、「テキストノードでも属性値でも、“HTMLとして意味を持つ記号”は必ずエスケープする」ということです。
<, >, &, ", ' の5つを押さえておけば、基本的なXSS対策としてはかなり強くなります。


ライブラリを使う場合との違いと使い分け

Apache Commons Text / Spring などの既存実装

実務では、自前実装ではなくライブラリを使うことも多いです。

例えば、Apache Commons Text には StringEscapeUtils.escapeHtml4 があり、
HTML4仕様に沿ったエスケープをしてくれます。

import org.apache.commons.text.StringEscapeUtils;

String safe = StringEscapeUtils.escapeHtml4("<b>重要</b> & \"test\"");
System.out.println(safe);
// <b>重要</b> & "test"
Java

ライブラリを使うメリットは、

  • 仕様に沿った細かいエスケープ(特殊文字やサロゲートペアなど)を面倒見てくれる
  • 自前実装のバグ(抜け漏れ・二重エスケープなど)を減らせる

といったところです。

一方で、自前ユーティリティを持つメリットもあります。

  • プロジェクトごとのルール(どこまでエスケープするか)を明示できる
  • ライブラリに依存しない軽量な処理が書ける
  • テストしやすく、挙動を完全に把握できる

現場では、「基本はライブラリを使うが、テストや学習用に自前実装も理解しておく」というスタンスが一番強いです。


まとめ:HTMLエスケープユーティリティで身につけたい感覚

HTMLエスケープは、「文字列を“HTMLとして解釈させないようにする”」ための、安全面で非常に重要なテクニックです。

押さえておきたい感覚は、まず「<, >, &, ", ' の5つは必ずエスケープする」ということ。
次に、「入力時ではなく“HTMLに出力する直前”でエスケープする」という設計を徹底すること。
そして、「テキストノードでも属性値でも、HTMLとして意味を持つ記号はすべて“ただの文字”に変換する」という意識です。

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