エンコーディング · 2026年9月5日

URLエンコーディング — なぜ%20が存在し、いつエンコードし、APIを壊す落とし穴

URLエンコーディングは安全でない文字をパーセントエンコードされたシーケンスに変換します。簡単なはずが、二重エンコーディング、クエリパラメータ vs パス、90%のバグを防ぐ唯一のルールで複雑になります。

入力またはクリックしたすべてのURLはURLエンコーディングを経ています。“my file.txt”のスペースは%20になります。クエリ文字列の&は%26になります。パスの/は/のままですが、エンコーダーがどの文字が安全で哪个が安全でないかを知っているからです。

URLエンコーディング(公式には「パーセントエンコーディング」)は、非予約文字セットにない文字をURLで表現する方法です。簡単な変換ですが、エッジケースがほとんどのAPIバグが存在する場所です。

非予約文字

これらの文字はURLで常に安全で、エンコーディングが不要です:

A-Z a-z 0-9 - _ . ~

その他すべて — スペース、スラッシュ、アンパサンド、等号、非ASCII文字 — はパーセント記号に続いて2つの16進数字でエンコードする必要があります:

  • スペース → %20
  • & → %26
  • = → %3D
  • / → %2F
  • ? → %3F
  • # → %23

エンコーディングは文字のUTF-8バイト値で、それぞれ2つの16進数字で表現されます。é(UTF-8:0xC3 0xA9)のようなマルチバイト文字は%C3%A9になります。

エンコーディングが重要な場所

クエリパラメータ。 ここがURLエンコーディングが最も多くのバグを引き起こす場所です。クエリ値が&または=を含む場合、URLパーサーはそれらの文字で分割し、パラメータ構造を破壊します:

/search?q=cats&dogs     ← 曖昧:"dogs"は別のパラメータ?
/search?q=cats%26dogs   ← 正確:"cats&dogs"は1つの値

すべてのHTTPライブラリにはクエリパラメータをエンコードする関数があります。使用してください。決して手動でクエリ文字列に文字列を連結しないでください。

パス。 ファイル名のスペースはエンコードが必要です:

/download/my file.pdf     ← 壊れている
/download/my%20file.pdf   ← 動作する

しかしパスセグメント内のスラッシュもエンコードが必要です:

/file/path/segment   ← 2つのセグメント:/で分割された"file/path"
/file%2Fpath/segment ← 1つのセグメント:"file/path"

非ASCII文字。 URLはASCIIのみです。非ASCII文字(アクセント文字、CJK文字、絵文字)はパーセントエンコードする必要があります:

  • café → caf%C3%A9
  • 日本語 → %E6%97%A5%E6%9C%AC%E8%AA%9E
  • 🎉 → %F0%9F%8E%89

ほとんどのブラウザはアドレスバーにデコードされたバージョンを表示しますが、実際にワイヤーで送信されるバイトはエンコードされています。

二重エンコーディングの落とし穴

最も一般的なURLエンコーディングバグ:すでにエンコードされた文字列をエンコードすること。

パス/hello%20worldを取ってください。もう一度URLエンコードすると、%は%25になります:

/hello%20world      ← 元の(正しい)
/hello%2520world    ← 二重エンコード(壊れている)

サーバーは%25を%にデコードし、次に%20を見てそれをスペースにデコードします。/hello worldになります — サーバーが単一のデコードを行う場合のみです。2つのデコードを行う場合(そうするものがあります)、元のものが返されます。動作は一貫性がなく、予測不可能です。

ルール:一度エンコードし、一度デコード。 エンコードされた文字列を受信した場合、再エンコード前にデコードしてください。HTTPライブラリのURLビルダーが生の値または事前にエンコードされた値を期待するか確認してください — ほとんどのフレームワークでのpathとrawPathの違いは重要です。

クエリ文字列エンコーディング

クエリ文字列には独自のエンコーディングルールがあります。パスとの主な違い:+はスペースを表すクエリ文字列(application/x-www-form-urlencoded)で、%20もスペースを表します。どちらも有効ですが、異なる規格から来ています:

  • application/x-www-form-urlencoded(フォーム送信)— スペースには+
  • パーセントエンコーディング(URL)— スペースには%20

ほとんどのモダンAPIは両方を受け付けます。ただしフォーム送信からクエリ文字列を解析する場合、+→スペース。URLを構築する場合、%20→スペース。混在させると、スペースが+記号になったりその逆になったりする微妙なバグが発生します。

言語のURLエンコーディング関数を使用してください。区別を処理してくれます。

エンコーディング vs エスケープ

URLエンコーディングはHTMLエスケープではありません。異なる問題を解決します:

  • URLエンコーディング(%20)— URL内の文字用。文字がURL構文として解釈されるのを防止。
  • HTMLエスケープ(&、<)— HTML内の文字用。文字がHTMLタグやエンティティとして解釈されるのを防止。

HTMLのhref内のURLは両方が必要です:パラメータ値をURLエンコードし、次にURL全体をHTMLエンコード:

<a href="/search?q=cats%26dogs&amp;page=1">

%26は&がクエリ区切り文字として解釈されるのを防ぎます。&amp;は&がHTMLエンティティの開始として解釈されるのを防ぎます。

よくある落とし穴

異なるコンテキストでのスペース。 URLでは%20、フォームボディでは+、誤って二重エンコードした場合は%2520。どのコンテキストにいるかを知ってください。

ハッシュフラグメント。 #以降のすべてはサーバーに送信されません。フラグメントを含むURLをエンコードし、フラグメントが?または&を含む場合、サーバーはそれを見ません。フラグメントはクライアント専用です。

パーセント記号のエンコーディング。 %→%25。データに文字通りの%が現れる場合(%を含むパスワードなど)、エンコードする必要があります。エンコードしないと、パーサーはその後の2文字を16進シーケンスとして解釈します。

Unicode正規化。 一部のシステムはエンコーディング前にUnicodeを正規化します。café(組み合わせアクセント)とcafé(合成済みé)は異なるパーセントシーケンスにエンコードされます。これにより、ファイル名と国際化ドメイン名に問題が発生します。

クイックリファレンス

文字 パーセントエンコード コンテキスト
スペース %20 URL
スペース + フォームボディ
& %26 クエリ文字列
= %3D クエリ文字列
/ %2F パスセグメント
? %3F クエリ文字列
# %23 パス/クエリ
% %25 どこでも
+ %2B クエリ文字列

試してみる

デコードする必要があるURLまたはエンコードされた文字列、エンコードする必要がある生のデータがある場合は、ローカルツールを使用して変換がブラウザ内で行われるようにしてください — データはどこにも送信されません。