HTMLエンティティエンコーダー / デコーダー

HTML特殊文字のエンコード・デコード

Common HTML Entities

& = &
< = &lt;
> = &gt;
" = &quot;
' = &#39;
/ = &#47;
` = &#96;
= = &#61;

コンテキストがすべて: どの文字をどこでエスケープするか

エスケープは文字列の属性ではなく、その文字列が最終的に置かれる場所の属性です。HTML には 5 つの構造文字 — & < > " ' — がありますが、レンダリングコンテキストごとに問題になる部分集合が違います。要素コンテンツ(タグの間)でパースを変えられるのは & と < だけです。< はタグを開き、& は character reference を開始します。ダブルクォート属性値の中ではパーサーは " でしか値を終端しないので & と " をエスケープし、シングルクォート属性なら & と ' です。本当に危険なのはクォートなし(unquoted)の属性値です。スペース、タブ、改行、form feed で終端し、/ や = とも悪い相互作用をするため、'x onmouseover=alert(1)' のような無害に見える payload が 5 文字を一切使わずに脱出できます。すべてのセキュリティガイドが「属性は必ずクォートで囲め」と言うのはこのためで、エスケープ対象が 2 文字に減ります。 エンティティエンコードには明確な限界もあります。エンティティエンコード済みの文字列でも <script> ブロックの中では依然として完全に危険です。HTML パーサーは script データ内の character reference をデコードしませんが、文字列がブロックを早期に終端させたり JS として解釈されたりし得るからです。href に置かれた javascript: URL はエンティティエンコードを完全にすり抜けます — ブラウザは URL を解決する前にエンティティをデコードするからです。インラインイベントハンドラーは JS パースの上に HTML デコードが重なるため、両方のエスケープが必要です。OWASP XSS Prevention Cheat Sheet はこれらのコンテキストを正確に列挙しています。ルールは、データではなく出力先に合わせてエンコーダーを選ぶことです。
<!-- element content: escape & and < -->
<p>Tickets &lt; $20 &amp; free entry</p>

<!-- double-quoted attribute: escape & and " -->
<input value="say &quot;hi&quot; &amp; wave">

<!-- UNQUOTED attribute: breakout with none of the five chars -->
<input value=x onmouseover=alert(1)>

<!-- entity encoding does NOT help here -->
<a href="javascript:alert(1)">  <!-- decoded before URL resolution -->

Named エンティティと数値エンティティ — そして XML の罠

WHATWG の HTML Living Standard はちょうど 2,231 個の named character reference を定義しています — &nbsp; &amp; &mdash; のような常用組から &CounterClockwiseContourIntegral; のような珍種まで。あらゆる Unicode code point は数値でも書けます。10 進の &#8594; と 16 進の &#x2192; はどちらも右向き矢印になり、16 進形式は Unicode チャートで見る U+2192 表記にそのまま対応するので検証が楽です。 罠は、この 2,231 個の名前が HTML 専用の語彙だという点です。XML が事前定義するのは 5 つだけ: &amp; &lt; &gt; &quot; &apos;。&nbsp; を XML パーサーに渡すと — application/xhtml+xml で配信される XHTML ページ、RSS/Atom フィード、単体の XML として処理される SVG ファイル — 警告ではなく致命的なパースエラーになります。移植可能な書き方は &#160; です。逆方向では、&apos; は XML と HTML5 には定義されていますが HTML 4 には存在しなかったため、古いパーサーや一部のメールレンダラーはそのまま表示してしまいます。どこでも通用するアポストロフィは &#39; で、PHP の htmlspecialchars のようなシリアライザーが &apos; ではなく &#39; を出力するのはこのためです。 手でデコードするときは、named reference が大文字小文字を区別することを忘れずに。&Auml; は Ä、&auml; は ä で、&AMP; は存在しますが &AMp; はありません。テーブル参照の前に入力を小文字化するデコーダーはテキストを静かに壊します。
Rightwards arrow (U+2192):
  named:   &rarr;      (HTML only)
  decimal: &#8594;
  hex:     &#x2192;    (matches U+2192)

Safe everywhere (HTML + XML/RSS/SVG):
  &amp; &lt; &gt; &quot; &#39; &#160;

Fatal parse error in XML: &nbsp; &mdash; &copy; ...

セミコロン欠落、URL 内の &amp;、そして二重エンコード

HTML パーサーは、末尾セミコロンなしで書かれたレガシー named reference のリスト — &amp、&lt、&copy など — を今でも認識します。HTML5 以前のコンテンツがこれに依存していたからです。この寛容さが古典的な URL バグを生みました。?page=1&copy=2 の &copy というシーケンスは歴史的に © へデコードされ、クエリ文字列を静かに壊しました。現代の仕様は属性値の例外(レガシー reference の直後に = か英数字が続く場合はそのまま)でこれを修正しましたが、教訓は残ります。href や src の中の生の & はすべて &amp; と書くべきです。バリデーターが URL 内の裸のアンパサンドを今でも警告するのはまさにこの理由で、ブラウザはリクエスト前に &amp; を & に戻すのでコストはゼロです。 二重エンコードは逆方向の失敗です。&lt; をもう一度エンコードすると &amp;lt; になり、< ではなくリテラルの &lt; というテキストとして表示されます。ページで &amp;quot; や &amp;amp; を見かけたら、2 つのレイヤーがそれぞれ「親切に」エスケープしたのです。典型的には、手動でエンコード済みの出力をテンプレートエンジンがさらに自動エスケープした、あるいは DB 書き込み前にエンティティエンコードした HTML を表示時にまたエンコードした、というパターンです。このクラス全体を防ぐ黄金律: 生のテキストを保存し、出力時に、出力先コンテキスト用のエンコーダーで、ちょうど 1 回だけエンコードする。このツールのようなデコーダーは、壊れた文字列が何層なのかを診断する最速の方法です — 出力が変化しなくなるまで繰り返しデコードして回数を数えるだけです。
?a=1&copy=2       legacy parsing: "a=1©=2"  (broken)
?a=1&amp;copy=2   safe: browser requests "a=1&copy=2"

"<b>"  encoded once   →  "&lt;b&gt;"          (correct)
       encoded twice  →  "&amp;lt;b&amp;gt;"  (renders "&lt;b&gt;")

UTF-8 なら、エンティティは思っているよりずっと少なくて済む

ページが <meta charset="utf-8"> を宣言しているなら — 2026 年なら当然そうすべきで、ウェブの 98% 以上が UTF-8 で配信されています — em ダッシュ、曲がった引用符、矢印、CJK、絵文字にエンティティは不要です。&mdash; の代わりに — を直接打てばソースは小さくなり(7 バイトが 1〜3 バイトに)、なにより読めるようになります。&#x2192; だらけの diff はレビュー不能です。今でもエンティティが必要な文字は 2 グループ。第一に構造文字 5 つ — これは永遠です。第二に、エディタ上で不可視か区別不能で、リテラルのまま残すと危険な文字たち: no-break space(&nbsp; — リテラルの U+00A0 は普通のスペースと見分けがつかず、次の人のエディタに「修正」されます)、soft hyphen の &shy;、絵文字シーケンスやペルシア語・インド系文字の字形結合に使う zero-width joiner の &zwj; と zero-width non-joiner の &zwnj;、そして bidi 制御文字。これらをエンティティで書くこと自体がドキュメントになります。 ひとつ注意: — と打った場所に — のような文字化けが出るなら、ファイルが誤ったエンコーディングで保存・配信されています。エンティティはそのバグを隠すだけで、charset を直すのが本当の治療です — &mdash; で覆い隠した mojibake は、誰かが non-ASCII コンテンツを貼り付けた瞬間に再発します。

textContent と innerHTML、そしてサニタイザーが必要になるとき

DOM で最も安全なエンコーダーは、そもそも呼ばないエンコーダーです。element.textContent = userInput は文字列を純粋なテキストとして挿入します — パースもエンコードも不要で、どんなネストの深さでも安全です。element.innerHTML = userInput は教科書どおりの XSS sink です。文字列が HTML パーサーを丸ごと通るため、中のマークアップがすべて実行されます。(setAttribute も値に関しては同様に安全ですが、属性そのものが問題になります — setAttribute('href', 'javascript:...') は生きた URL のままです。) テンプレートフレームワークはこの教訓を内面化しました。Svelte の {expression}、React の JSX、Vue の二重波括弧の補間はすべてデフォルトでエスケープします。だからコンポーネントに渡す前にデータを手でエンコードするのは、安全性の向上ではなく二重エンコードのバグです。各フレームワークには明示的な抜け道もあります — Svelte の {@html ...}、React の dangerouslySetInnerHTML、Vue の v-html。わざと扱いにくく付けられた名前が、責任があなたに移ったことを示しています。 そこから最後の区別が導かれます。エンコードとサニタイズは別の問題を解きます。エンコードはマークアップを見えるテキストに変えます — ユーザーが HTML を入力し、それをそのまま見せるべきときに正解です。サニタイズ(DOMPurify、あるいは登場しつつあるネイティブの Sanitizer API)はマークアップをパースし、安全なタグの allowlist を残してスクリプトのベクターを除去します — ユーザーが正当にリッチテキストを書き、それをマークアップとして表示すべきときに正解です。コメント欄はエンコード、WYSIWYG の投稿本文はサニタイズ、がどちらも正しく、入れ替えると表示が壊れるか穴が開きます。
// safe: never parsed as markup
el.textContent = userInput;

// XSS sink: parsed and executed
el.innerHTML = userInput;

// frameworks escape by default; escape hatches opt out
// Svelte:  {comment.text}   vs  {@html post.trustedHtml}
// React:   {comment.text}   vs  dangerouslySetInnerHTML
// Vue:     interpolation    vs  v-html

// rich text: sanitize, don't encode
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(userHtml);
最終更新:

ツールについて

HTML エンティティエンコーダは <、>、&、" など HTML で特別な意味を持つ文字を &lt;、&gt;、&amp;、&quot; などのエンティティ参照に変換します。ユーザー入力を画面に出すときは必ずエンコードして、テキストとして扱わせるのが XSS 対策の基本です。

使い方

  1. Pick Encode to convert raw characters into HTML entities, or Decode to do the reverse.
  2. Paste the text or HTML snippet into the input box.
  3. Read the encoded or decoded output on the right.
  4. Click Copy to grab the result for use in your template, email, or doc.
  5. Use the entity reference card at the bottom as a quick lookup for common characters.

主な使用例

  • Sanitising user-generated content before injecting it into a server-rendered page.
  • Embedding code snippets that contain < and > inside a Markdown or HTML document.
  • Preparing email HTML so apostrophes and ampersands render correctly across clients.
  • Decoding scraped HTML where entities like &amp;quot; were double-encoded.
  • Verifying that an output escapes correctly before flagging an XSS bug.
  • Showing literal HTML markup in a tutorial or technical doc.

よくある質問

Q. Do I still need to encode if I use a templating engine?

A. Most modern engines (Svelte, React, Jinja, ERB) auto-escape by default. But once you reach for a "raw" or "unsafe" helper you take responsibility for encoding yourself.

Q. What is the difference between &amp;#39; and &amp;apos;?

A. Both encode an apostrophe. &apos; is technically XHTML/XML; some legacy HTML parsers do not recognise it, so &#39; is the safer choice for HTML output.

Q. Does encoding fix all XSS issues?

A. No. Context matters — JavaScript strings, URL attributes, and CSS each need their own escaping. Use a battle-tested sanitizer for HTML you want to render with markup intact.

Q. Why does decoding a non-encoded string return it unchanged?

A. There is nothing to decode. Decoding only swaps recognised entities back to characters; raw text passes through untouched.