エンコード・文字コード
URLの日本語・+・%20を正しく扱いたい
使うツール
同じ検索語を送ったつもりなのに、空白が消えたり、+が空白へ変わったりする。URLの不具合では、文字列を何回変換したかだけでなく、何の一部として変換したかが重要です。URL全体に残すべき記号も、一つの値に含まれる文字ならエンコードする必要があります。
URLエンコードツールで、短い文字列を一つずつ変換してみましょう。ここではURLへアクセスせず、表記だけを確認します。example.invalidは説明用の宛先です。変換結果が整っていることと、そのURLが存在すること、安全なリンクであることは別に扱います。
空白一つを、二つの方式で比べる
red blue構成要素のエンコードではred%20blue、フォーム形式ではred+blueになります。どちらかが常に間違いなのではなく、使う形式が違います。フォーム形式の規則を使う場所では+が空白を表せますが、すべてのURLのすべての位置で+が空白になるわけではありません。受け取る側がどの規則で読むかを確認します。
デコードでも対応する方式を選びます。red+blueを通常のデコードで戻すと+が残り、フォームの+を空白として扱う設定ではred blueになります。デコード結果が思ったものと違うとき、さらにデコードを重ねる前に、入力が通常の構成要素なのかフォームの値なのかを確認するのが近道です。
C++の+は、空白ではない
C++ guideC%2B%2B%20guide
C%2B%2B+guideフォーム形式でも、元からあった+は%2Bとして表します。空白由来の+と、データとしての+を区別するためです。元のC++ guideを、エンコードせずにフォームとしてデコードすると、プラス記号まで空白へ変わってしまいます。変換処理は、まだ変換していない入力と、すでに変換された入力を混ぜないことが重要です。
&を残すか変えるかは、文字の役割で決まる
tea & cakeこの値を構成要素として変換するとtea%20%26%20cakeになります。&をそのままクエリへ連結すると、別のパラメーターの区切りとして読まれる可能性があります。一方、完成したURLの?や&まで一つの値としてエンコードすると、URLの構造が使えなくなります。全体を作る段階と、一つの値を安全に収める段階を区別します。
URL全体モードは構造に使う記号を一部残しますが、入力された文字の役割を推測して壊れたURLを修理する機能ではありません。どこがホスト、パス、クエリ、fragmentなのかを確認したいなら、URL分解ツールへ渡します。特に#以降はfragmentとして扱われるので、検索語に含めたい#をそのまま連結しないようにします。
%2520は、空白が二回エンコードされた手掛かり
red blue → red%20blue → red%2520blue二回目では、空白ではなく%という文字が%25へ変わります。一回デコードしてもred%20blueが残るため、『デコードが効いていない』ように見えることがあります。入力が二重に変換されたのか、それとも%20という文字を本当に送りたかったのかは、値だけからは決められません。生成元と受け取り側の変換回数を追います。
%がなくなるまで繰り返しデコードする方法は避けます。元々のデータに含まれる%2Fなどを区切りへ変え、別の意味にしてしまう可能性があるからです。正しい回数は見た目のきれいさではなく、データ形式の取り決めで決まります。調査では、変換前、送信直前、受信後のそれぞれの値を区別して確認します。
日本語一文字が、複数の%表記になる理由
あ
UTF-8: e3 81 82
URL: %E3%81%82%の後の二桁は、この例ではUTF-8の一バイトを16進数で表したものです。あは三バイトなので、%表記も三組になります。UnicodeコードポイントのU+3042を、そのまま%3042と書く方式ではありません。テキストとバイト列の変換を併用すると、文字、符号化されたバイト、URL用の表記という三段階をつなげて確認できます。
途中で切れた%E3%81や、16進数になっていない%ZZなどは正しくデコードできません。エラーを消すために残りを推測して足すのではなく、元の値が途中で切れていないかを確認します。古いシステムがUTF-8以外のバイトを使っている場合も、このデコードの結果だけで元の文字コードを自動確定することはできません。
実装へ戻すときは、値をどの段階で変換するか決める
バグの報告には、元の検索語、選んだ形式、変換後の値、受け取り側が期待する形式を残します。red blueだけで通っても、C++ guideやtea & cakeを加えると見つかる問題があります。空白、+、&、#、%を含む小さな例を試験へ残せば、後でURLを組み立てる処理を変更したときも確認しやすくなります。
実際のコードでは、URLやクエリを構造として扱うAPIを利用すると、文字列の手作業連結を減らせます。ただし、そのAPIが値をエンコードするなら、渡す前に同じ変換を重ねないようにします。このツールの役割は、変換を増やすことではなく、どの層が責任を持って一回処理するかを見つけることです。
検索語の&と、Queryを区切る&を分ける
検索語が『red & blue』なら、&は検索語の一部です。それを?q=red & blueのようにそのままURLへ足すと、受け取る側が&を別パラメーターの開始として解釈する可能性があります。値だけを構成要素としてエンコードすれば、red%20%26%20blueです。フォーム形式ならred+%26+blueとなります。どちらも検索語の&を%26にする点は同じで、空白の表し方が違います。Query全体の区切り記号を残すことと、値の中の記号を守ることは別の処理です。
問題を調べるときは、まずエンコード前の値、組み立てたURL、受け取った値を並べます。ブラウザーのアドレス欄は見やすい形へ表示し直す場合があるため、見た目だけで二重変換の有無を決めないようにします。URL分解ツールでQueryの各値を確認し、元の『red & blue』へ戻るかを見ます。この確認は入力URLへのアクセスではなく文字列の解析です。値を直すつもりでURL全体を何度も復元すると、値の中に閉じ込めた%26を区切りの&へ変えてしまうので、変換する層を一つずつ決めます。
日本語を含む値だけ失敗する場合も、すぐ別の文字コードへ変えるより、どの処理がUTF-8のbyte列を扱っているかを確かめます。%で始まる二桁は文字番号そのものではなく、エンコードされたbyteの表記です。エンコード結果が長いからといって文字が増えたとは限りません。長さの上限を調べるなら、元の検索語の長さとURLとして組み立てた後の長さを分けて測ると、問題のある段階を絞れます。