URL エンコードとデコードについて
URL エンコードとは?なぜ文字が %E4%B8%AD になるのか
URL の仕様(RFC 3986)で使えるのは英数字とごく一部の記号だけで、漢字や仮名、スペース、引用符は含まれていません。これらをリンクに入れるには、まず何らかの文字セットでバイト列に変換し、各バイトをパーセント記号と 16 進 2 桁で書き表す必要があります。これがパーセントエンコーディング、別名 URL エンコードです。「中」という漢字は UTF-8 では 3 バイトの E4 B8 AD で、書き下すと %E4%B8%AD になります。つまり %E4%B8... の並びは文字化けでも暗号でもなく、同じ文字列の別の書き方にすぎません。 %XX が 3 つひと組で並んでいれば、UTF-8 でエンコードされた漢字や仮名だとほぼ判断できます。
encodeURIComponent と encodeURI のどちらを使うか
コードを書くときにいちばん間違えやすいところです。encodeURIComponent は : / ? # & = といった構造を表す文字までエンコードします。向いているのは個々のパラメータ値です。一方 encodeURI はそれらを残すので、向いているのはすでに組み立て終えた URL 全体です。取り違えたときの結果は具体的です。URL 全体に使うのが encodeURIComponent、https:// のような部分まで https%3A%2F%2F になってリンクが壊れます。逆に、 encodeURI で、 & を含むパラメータ値(たとえば検索語が「A&B」の場合)をエンコードすると、その & はそのまま残るため、サーバー側は区切り文字と解釈し、1 つのパラメータが 2 つに割れてしまいます。上のエンコード方式は両方用意してあり、既定はパラメータ値のほうです。
URL エンコードは暗号化ではない
URL エンコードは Base64と同じくエンコードであって、暗号化ではありません。規則は完全に公開されていて鍵もなく、文字列さえあれば誰でも一手で元に戻せます——このページがまさにそれをしています。存在意義は、特殊な文字を URL という経路に安全に通すことであり、秘匿性はいっさい提供しません。注文番号や電話番号、内部 ID を URL エンコードしてリンクに入れても、何も保護したことになりません。アドレスバーにもサーバーのログにも Referer ヘッダーにも、平文のまま残ります。本当に不可逆にしたい場合は SHA-256 を、機微なパラメータを送るなら HTTPS 上の POST を使ってください。ちなみに「URL 復号」という言い方はそもそも成り立ちません。正しい言葉はデコードです。
+ と %20 と文字化け、3 つの落とし穴
スペースには 2 通りの書き方があります。フォーム送信で使われる application/x-www-form-urlencoded はスペースを + にエンコードしますが、パスや現代的な API では一般に %20 が使われます。厄介なのは、JavaScript の decodeURIComponent が + を解釈せず、プラス記号をそのまま残してしまうことです。そのため、このページには切り替えを用意し、既定ではスペースとして解釈します。逆に、内容にもともとプラス記号が含まれている場合(たとえば 1+1)、エンコードすると %2B になりますが、これで正しい動作です。
文字セットがもう 1 つの落とし穴です。いまはどこも UTF-8 が既定ですが、古いサイトや古いシステムには GBK(中国語圏で使われてきた文字セット)を使うものが少なくありません。「中」は GBK では 2 バイトの %D6%D0 です。GBK でエンコードされた内容を UTF-8 として読めば、出てくるのは「?」や「□」です。このページはデコード側で GBK に切り替えられます。エンコード側は UTF-8 のみです。ブラウザが標準で生成できるのが UTF-8 だけだからです。
言語ごとの関数名の対応。PHP の urlencode はスペースを +、rawurlencode は %20 にエンコードします。対応するデコードは urldecode / rawurldecode です。Java の URLEncoder.encode(s, "UTF-8") はフォーム送信の流儀に従うので、こちらも + です。Python は urllib.parse.quote / unquote を使い、 quote_plus だけが + にします。JavaScript には encodeURIComponent / decodeURIComponent しかなく、常に %20 です。フロントエンドとバックエンドで結果が食い違うときは、たいていこのプラス記号が原因です。