パーセントエンコーディング

URL で特殊文字を %XX 形式の 16 進数で表現するエンコーディング方式。URL エンコーディングとも呼ばれる。

パーセントエンコーディング (Percent-Encoding) は、URL 内で使用できない文字や特殊文字を % に続く 2 桁の 16 進数で表現するエンコーディング方式です。URL エンコーディングとも呼ばれ、RFC 3986 で標準化されています。Web の基盤技術として、ブラウザとサーバー間の通信で非 ASCII 文字や予約文字を安全にやり取りするために不可欠な仕組みです。

エンコーディングの仕組みはシンプルです。対象の文字を UTF-8 でバイト列に変換し、各バイトを % + 2 桁の 16 進数で表現します。たとえば半角スペースは %20 (0x20)、日本語の「あ」は UTF-8 で 3 バイト (0xE3, 0x81, 0x82) なので %E3%81%82 となります。RFC 3986 は、非予約文字 (A-Z, a-z, 0-9, -, _, ., ~) についてはパーセントエンコードしないことを求めています。エンコードした形と意味は変わりませんが、表記を一貫させるために生の文字で書く、という規定です。一方、/、?、#、&、= などの予約文字は、区切り子としての役割で使うならそのまま書き、値の一部として使うならエンコードする、という使い分けになります。「非予約文字以外はすべてエンコードが必要」と覚えてしまうと、区切り子まで潰して URL の構造を壊すことになります。

実務では、検索クエリやフォームデータの送信時にブラウザが自動的にパーセントエンコーディングを適用します。JavaScript では encodeURIComponent() でエンコード、decodeURIComponent() でデコードを行います。一方、encodeURI() は URL 全体を対象とし、/ や ? などの予約文字はエンコードしません。この 2 つの関数の使い分けを誤ると、URL が壊れたりセキュリティ上の問題が生じたりするため注意が必要です。もう一段細かい話として、encodeURIComponent() が変換せずに残す ASCII 文字は RFC 3986 の非予約文字集合と完全には一致せず、!、'、(、)、* の 5 文字が生のまま残ります。RFC 3986 の非予約文字だけを残す形に厳密に揃えたい場合は、エンコード後にこの 5 文字を %21、%27、%28、%29、%2A へ自分で置き換えます。

よくある誤解として、パーセントエンコーディングと HTML エンティティ (& など) を混同するケースがあります。パーセントエンコーディングは URL 専用の仕組みであり、HTML 内の特殊文字のエスケープとは目的も構文も異なります。また、日本語 URL を扱う際は、ブラウザのアドレスバーが表示上はデコードして見せている点にも注意しましょう。実際の HTTP リクエストではエンコード済みの URL が送信されています。

類似の概念として Base64 エンコーディングがありますが、用途が異なります。パーセントエンコーディングは URL 内の個々の文字を安全に表現するためのもので、Base64 はバイナリデータをテキスト形式に変換するためのものです。また、application/x-www-form-urlencoded 形式ではスペースを %20 ではなく + で表現するなど、コンテキストによる差異も存在します。この差はデコード側で事故になりやすく、decodeURIComponent('a+b') は + をスペースに戻さずそのまま返します。フォーム由来のクエリを手作業でデコードすると、スペースが + のまま本文に混ざることになります。クエリ文字列はフォーム形式を理解する API に任せるのが安全で、new URLSearchParams({ q: 'a b' }).toString() は q=a+b を返し、読み出し時には + をスペースへ戻してくれます。

文字数カウントの観点では、パーセントエンコーディングは元の文字数と URL 上のバイト数の乖離を生みます。ひらがなや漢字は UTF-8 で 3 バイトのため、パーセントエンコーディング後は 1 文字が 9 文字 (%XX%XX%XX) に膨張します。4 バイトを使う絵文字ならさらに長く、12 文字になります。URL の長さについては、ブラウザ・Web サーバー・間に入る中継機器がそれぞれ独自の上限を持ち、公表されていないものもあるため、特定の数値を安全圏として当てにはできません。日本語を含むクエリパラメータがエンコード後に何文字になるかを事前に見積もり、長くなるようなら URL に載せず POST の本体で送る設計に切り替えるのが確実です。

この記事を共有