パーセントエンコーディング
俗にURLエスケープ、URLエンコーディングと呼ばれる。
主にURL文字列で直接使えない文字をエンコードするのに使う。
パスにASCIIで表示できない文字を使う場合。漢字など。
たとえば、ここのURLはhttps://scrapbox.io/bbr-program-memo/%E3%83%91%E3%83%BC%E3%82%BB%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0
クエリ文字列で、区切り文字(&,=)や空白をエスケープするのに使う。
何が問題か?
正しくエスケープするためには、エスケープしてはいけない文字をエスケープしてはならないし、エスケープすべき文字をエスケープしなければならない。(当たり前)
正しくアンエスケープするためには、アンエスケープしてはいけない文字をアンエスケープしてはならないし、アンエスケープすべき文字をアンエスケープしなければならない。(当たり前)
エスケープ元の文字が不正であれば、何らかの対処が必要になる。
例外を出す。
代替文字(あるいは代替文字列)に変える。
多くの場合はこれが適切。
削除する。
例えば JavaScript には encodeURI, decodeURI, encodeURIComponent, decodeURIComponent という API があるが、使い方が雑だと、正しくエスケープ、アンエスケープできない。
間違ったエスケープ、アンエスケープだと、以下の問題がある。
エスケープで切り分け用に残さなければならない文字が残されず、あるいはアンエスケープで切り分け用の文字が混ざり込む。これにより切り分けが狂うことになる。
二重にエスケープ、アンエスケープされることがある。(ほとんどの場合間違い)
URI と URL とは異なる。
URI の場合、本質的には以下のようにスキームとスキーム特定部分(scheme-specific-part)とが別れるところまでしか規定されていない。
code:txt
<scheme>:<scheme-specific-part>
例としては Data URI scheme がある。
URLの場合
組み立ての場合
解析の場合
URL はどう解析すべきかが WHATWG の URL の所で提示されている。
解析する場合は、アンエスケープをどの段階で行うかがとても重要となる。
切り分け場所が狂わないようにする必要がある。絶対に、切り分ける前にアンエスケープしてはならない。
decodeURI, encodeUR
正しい分割方法は組み立て方法の逆となる。
URLはスキーム(例えば http)とスキーム特定部分(scheme-specific-part)に分けることができる。
code:txt
<scheme>:<scheme-specific-part>
scheme = alpha *( alpha | digit | "+" | "-" | "." )
正規表現であれば、以下のようになる。
code:regexp
# グループ2が scheme
# グループ3が scheme-specific-part
スキームは本来エスケープされない。(エスケープしなければならない文字は必ず含まれない)
スキームに対してエスケープした文字を受け入れる必要は恐らく存在しない。脆弱性を作り込むことなるので受け入れるべきではない。
http または類似スキームの場合
スキーム特定部分の先頭が // ならば/,?,終端のいずれかが現れるまでが
以後パス文字列を繰り返すことになる。
encodeURIComponent に対して過不足がある場合、自力で再変換する方法がある。(パフォーマンスは恐らくあまりよくない)
code:encodeURIComponentEx.js
var r = encodeURIComponent(s)
.replace(/%(24|40|7E)/g, decodeURIComponent) // 過剰分の例
.replace(/!'()*/g, function(c) { return '%' + c.charCodeAt(0).toString(16);
}); // 不足分の例。0x10~0x7F までは正しく動く。
encodeURIComponent はパーセントエンコーディングをする文字としない文字が固定的。
escape はパーセントエンコーディングをする文字としない文字が固定的かつ実装依存。
少なくとも、encodeURIComponent と escape とでエスケープされる文字は異なる。
code:chrome_escape_sample.js
var r = (function() {
var a = [];
for (i = 0; i <= 0x7f; i++) { a.push(String.fromCharCode(i)); }
return a.join("");
})();
console.log(encodeURIComponent(r));
// Chrome 90, Firefox 88, IE11 -> %00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20!%22%23%24%25%26'()*%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F
console.log(escape(r));
// Chrome 90, Firefox 88, IE11 -> %00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29*+%2C-./0123456789%3A%3B%3C%3D%3E%3F@ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D%7E%7F
decodeURIComponent は不正シーケンス以外のすべてのパーセントエンコーディングをデコードするため、特に場合分けなどをする必要はない。
decodeURIComponent は不正シーケンスの場合に例外を出してしまうため、出さない実装が作られている。
関連