入力またはクリックしたすべての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&page=1">
%26は&がクエリ区切り文字として解釈されるのを防ぎます。&は&がHTMLエンティティの開始として解釈されるのを防ぎます。
よくある落とし穴
異なるコンテキストでのスペース。 URLでは%20、フォームボディでは+、誤って二重エンコードした場合は%2520。どのコンテキストにいるかを知ってください。
ハッシュフラグメント。 #以降のすべてはサーバーに送信されません。フラグメントを含むURLをエンコードし、フラグメントが?または&を含む場合、サーバーはそれを見ません。フラグメントはクライアント専用です。
パーセント記号のエンコーディング。 %→%25。データに文字通りの%が現れる場合(%を含むパスワードなど)、エンコードする必要があります。エンコードしないと、パーサーはその後の2文字を16進シーケンスとして解釈します。
Unicode正規化。 一部のシステムはエンコーディング前にUnicodeを正規化します。café(組み合わせアクセント)とcafé(合成済みé)は異なるパーセントシーケンスにエンコードされます。これにより、ファイル名と国際化ドメイン名に問題が発生します。
クイックリファレンス
| 文字 | パーセントエンコード | コンテキスト |
|---|---|---|
| スペース | %20 |
URL |
| スペース | + |
フォームボディ |
& |
%26 |
クエリ文字列 |
= |
%3D |
クエリ文字列 |
/ |
%2F |
パスセグメント |
? |
%3F |
クエリ文字列 |
# |
%23 |
パス/クエリ |
% |
%25 |
どこでも |
+ |
%2B |
クエリ文字列 |
試してみる
デコードする必要があるURLまたはエンコードされた文字列、エンコードする必要がある生のデータがある場合は、ローカルツールを使用して変換がブラウザ内で行われるようにしてください — データはどこにも送信されません。