URLエンコードとは何か
URLエンコード(パーセントエンコーディング)とは、URLに含められない文字を %XX 形式(XXはバイト値の16進数2桁)に変換する仕組みです。現行の標準仕様は RFC 3986(2005年)で、ブラウザ実装はこれと並立するWHATWGのURL Living Standardに準拠しています。
RFC 3986は文字を「非予約文字」と「予約文字」に分類します。非予約文字は A-Z a-z 0-9 - . _ ~ の66文字で、エンコードしてもしなくても同じURIとみなされます。予約文字はURIの区切りとして意味を持つため、データとして送る場合はエンコードが必須です。
予約文字と非予約文字の区別
| 分類 | 文字 | 役割 |
|---|---|---|
| 非予約 (unreserved) | A-Z a-z 0-9 - . _ ~ | そのまま使える。正規化時はデコード推奨 |
| 予約: gen-delims | : / ? # [ ] @ | スキームやパスなど大きな区切り |
| 予約: sub-delims | ! $ & ' ( ) * + , ; = | クエリ内などの細かい区切り |
JavaScriptの3つのエンコード関数
JavaScriptには用途の異なるエンコード関数が複数あり、選択を誤ると区切り文字が壊れてバグになります。URLエンコード/デコードツールで実際の出力を見比べると違いが掴みやすいでしょう。
encodeURI と encodeURIComponent の違い
encodeURI() はURL全体を対象とし、予約文字18文字と # をエンコードしません。一方 encodeURIComponent() はクエリの値など「部品」を対象とし、/ ? # & = : なども含めてエンコードします。クエリパラメータの値には後者を使うのが鉄則です。
| 関数 | 主な用途 | スペース出力 | 備考 |
|---|---|---|---|
escape() | (使用禁止) | %20 | 非推奨。Latin-1依存でUTF-8非対応 |
encodeURI() | URL全体 | %20 | 予約文字と # は残す |
encodeURIComponent() | クエリ値・パス部品 | %20 | ! ' ( ) * ~ は残す点に注意 |
URLSearchParams | クエリ文字列全体 | + | form-urlencoded準拠 |
escape() は非推奨(deprecated)です。%uXXXX という非標準形式を使い、マルチバイト文字を正しく扱えないため、必ず encodeURIComponent() に置き換えてください。
// クエリ値は encodeURIComponent で安全に組み立てる
const q = "C++ & Rust";
const url = `/search?q=${encodeURIComponent(q)}`;
// → /search?q=C%2B%2B%20%26%20Rust
// 複数パラメータは URLSearchParams が確実
const p = new URLSearchParams();
p.append("q", q);
p.append("page", "1");
// → q=C%2B%2B+%26+Rust&page=1 (スペースは + になる)
+ と %20 ― form-urlencoded の罠
HTMLフォーム由来の application/x-www-form-urlencoded では、スペースが %20 ではなく + にエンコードされます。URLSearchParams もこの方式に従うため、encodeURIComponent() の出力(%20)と混在すると不一致が起きます。
| 項目 | RFC 3986 | form-urlencoded |
|---|---|---|
| スペース | %20 | + |
| リテラルの + | そのまま | %2B にする必要 |
| JSの実装 | encodeURIComponent() | URLSearchParams |
| PHPの実装 | rawurlencode() | urlencode() |
二重エンコードと2026年のセキュリティ動向
二重エンコード(double encoding)は % 自体を %25 にさらにエンコードしてしまう現象です。hello world を2回エンコードすると hello%2520world となり、1回デコードしても hello%20world までしか戻りません。
これは単なるバグに留まらず、OWASPが警告するセキュリティ問題でもあります。2025年にはAstroで二重URLエンコードを使った認証バイパス(CVE-2025-66202)が報告されており、フレームワーク層でのデコード多重化は今も実際の脆弱性を生んでいます。
実装面では、孤立サロゲートを encodeURIComponent() に渡すと URIError が出るため、ES2024で標準化された String.prototype.toWellFormed() で事前に整える手法が定着しつつあります。デコード側も try/catch でフォールバックを用意しておきましょう。