先にまとめ
JavaScript の encodeURIComponent と encodeURI と同じエンコードするメソッドを String クラスに作るモンキーパッチ。
class String def encodeURIComponent unescaped_form = /([#{Regexp.escape(';/?:@&=+$,<>#%"{}|\\^[]`' + (0x0..0x1f).map{|c| c.chr }.join + "\x7f" + (0x80..0xff).map{|c| c.chr }.join)}])/n self.force_encoding("ASCII-8BIT").gsub(unescaped_form){ "%%%02X" % $1.ord } end def encodeURI unescaped = /([#{Regexp.escape('<>#%"{}|\\^[]`' + (0x0..0x1f).map{|c| c.chr }.join + "\x7f" + (0x80..0xff).map{|c| c.chr }.join)}])/n self.force_encoding("ASCII-8BIT").gsub(unescaped){ "%%%02X" % $1.ord } end end
encodeURIComponent() と encodeURI() と同じく、文字列を引数を取るメソッドはこうなります。
def encodeURIComponent(str) unescaped_form = /([#{Regexp.escape(';/?:@&=+$,<>#%"{}|\\^[]`' + (0x0..0x1f).map{|c| c.chr }.join + "\x7f" + (0x80..0xff).map{|c| c.chr }.join)}])/n str.force_encoding("ASCII-8BIT").gsub(unescaped_form){ "%%%02X" % $1.ord } end def encodeURI(str) unescaped = /([#{Regexp.escape('<>#%"{}|\\^[]`' + (0x0..0x1f).map{|c| c.chr }.join + "\x7f" + (0x80..0xff).map{|c| c.chr }.join)}])/n str.force_encoding("ASCII-8BIT").gsub(unescaped){ "%%%02X" % $1.ord } end
使用の際の注意
まとめ
◎escape
古くからある関数。ブラウザ・バージョン・HTMLの文字コードによって挙動が変わる。推奨されない。
◎encodeURI
URI全体に適用するための関数。URIの予約文字をエンコードしないため、不完全なエンコードとなる。
◎encodeURIComponent
URIを構成する部分文字列に適用するための関数。本来のエンコード目的であればこれを使うべき。URI全体にかけてしまうと無効なURIになる。「エンコードが必要な部分ごとにencodeURIComponentをかけた上で結合しURIを完成させる」というのがあるべき姿だと思います。
escape()とencodeURI()とencodeURIComponent()の違い - Miuran Business Systems
経緯など
以前こういう記事を書きました。
ERB::Util.url_encode ┆ CGI.escape ┆ ┆ URI.encode_www_form_component ┆ ┆ ┆ WEBrick::HTTPUtils.escape_form ┆ ┆ ┆ ┆ WEBrick::HTTPUtils.escape ┆ ┆ ┆ ┆ ┆ URI.escape ┆ ┆ ┆ ┆ ┆ ┆["%20", "+", "+", "+", "%20", "%20"] ["%21", "%21", "%21", "!", "!", "!"] ["%24", "%24", "%24", "%24", "$", "$"] ["%26", "%26", "%26", "%26", "&", "&"] ["%27", "%27", "%27", "'", "'", "'"] ["%28", "%28", "%28", "(", "(", "("] ["%29", "%29", "%29", ")", ")", ")"] ["%2A", "%2A", "*", "*", "*", "*"] ["%2B", "%2B", "%2B", "%2B", "+", "+"] ["%2C", "%2C", "%2C", "%2C", ",", ","] ["%2F", "%2F", "%2F", "%2F", "/", "/"] ["%3A", "%3A", "%3A", "%3A", ":", ":"] ["%3B", "%3B", "%3B", "%3B", ";", ";"] ["%3D", "%3D", "%3D", "%3D", "=", "="] ["%3F", "%3F", "%3F", "%3F", "?", "?"] ["%40", "%40", "%40", "%40", "@", "@"] ["%5B", "%5B", "%5B", "%5B", "%5B", "["] ["%5D", "%5D", "%5D", "%5D", "%5D", "]"] ["%7E", "%7E", "%7E", "~", "~", "~"]
これを書いたときにはまだ JavaScript に興味がさほどなく、単に URL エンコードしたかっただけでした。
encodeURIComponentを改めて確認
英数字以外のASCII文字をどうエンコードするのか確認してみます。
> console.log(encodeURIComponent(" !\"#$%&\'")+"\n"+ encodeURIComponent("()*+,-./")+"\n"+ encodeURIComponent(":;<=>?@")+"\n"+ encodeURIComponent("[\\]^_`{|}~")); %20!%22%23%24%25%26' ()*%2B%2C-.%2F %3A%3B%3C%3D%3E%3F%40 %5B%5C%5D%5E_%60%7B%7C%7D~
Rubyのメソッドで近そうなのはWEBrick::HTTPUtils.escape_form
> require 'webrick/httputils' > puts WEBrick::HTTPUtils.escape_form(" !\"#$%&\'") + "\n" + * WEBrick::HTTPUtils.escape_form("()*+,-./") + "\n" + * WEBrick::HTTPUtils.escape_form(":;<=>?@") + "\n" + * WEBrick::HTTPUtils.escape_form("[\\]^_`{|}~") +!%22%23%24%25%26' ()*%2B%2C-.%2F %3A%3B%3C%3D%3E%3F%40 %5B%5C%5D%5E_%60%7B%7C%7D~
ほぼ同じなのですが、唯一の違いが半角スペースのエンコード。
encodeURIComponent だと %20 ですが、 WEBrick::HTTPUtils.escape_form だと + (プラス記号)になります。
+を%20に置換すればencodeURIComponentと等しい
ということは + を %20 に置換してやればOKですね。
> puts WEBrick::HTTPUtils.escape_form(" !\"#$%&\'").gsub("+", "%20") %20!%22%23%24%25%26'
モンキーパッチしちゃいましょう
class String require 'webrick/httputils' def encodeURIComponent WEBrick::HTTPUtils.escape_form(self).gsub("+", "%20") end end
WEBrick::HTTPUtils.escape_formを確認
module WEBrick module HTTPUtils reserved = ';/?:@&=+$,' control = (0x0..0x1f).collect{|c| c.chr }.join + "\x7f" delims = '<>#%"' unwise = '{}|\\^[]`' nonascii = (0x80..0xff).collect{|c| c.chr }.join def _make_regex(str) /([#{Regexp.escape(str)}])/n end UNESCAPED_FORM = _make_regex(reserved+control+delims+unwise+nonascii) def _escape(str, regex) str.gsub(regex){ "%%%02X" % $1.ord } end def escape_form(str) ret = _escape(str, UNESCAPED_FORM) ret.gsub!(/ /, "+") ret end end end
これを使って encodeURIComponent に合致する URL エンコードを作ることも出来ますね。
モンキーパッチしちゃいましょう2
class String def encodeURIComponent unescaped_form = /([#{Regexp.escape(';/?:@&=+$,<>#%"{}|\\^[]`' + (0x0..0x1f).map{|c| c.chr }.join + "\x7f" + (0x80..0xff).map{|c| c.chr }.join)}])/n self.force_encoding("ASCII-8BIT").gsub(unescaped_form){ "%%%02X" % $1.ord } end end
encodeURIだと
> console.log(encodeURI(" !\"#$%&\'")+"\n"+ encodeURI("()*+,-./")+"\n"+ encodeURI(":;<=>?@")+"\n"+ encodeURI("[\\]^_`{|}~")); %20!%22#$%25&' ()*+,-./ :;%3C=%3E?@ %5B%5C%5D%5E_%60%7B%7C%7D~
Rubyのメソッドで近そうなのはWEBrick::HTTPUtils.escape
> puts WEBrick::HTTPUtils.escape(" !\"#$%&\'") + "\n" + * WEBrick::HTTPUtils.escape("()*+,-./") + "\n" + * WEBrick::HTTPUtils.escape(":;<=>?@") + "\n" + * WEBrick::HTTPUtils.escape("[\\]^_`{|}~") %20!%22%23$%25&' ()*+,-./ :;%3C=%3E?@ %5B%5C%5D%5E_%60%7B%7C%7D~
ほぼ同じなのですが、唯一の違いがシャープ # のエンコード。
encodeURI だと # のままですが、 WEBrick::HTTPUtils.escape だと %23 になります。
%23を#に置換すればencodeURIと等しい
> puts WEBrick::HTTPUtils.escape(" !\"#$%&\'").gsub("%23", "#") %20!%22#$%25&'
モンキーパッチしちゃいましょう3
class String require 'webrick/httputils' def encodeURI WEBrick::HTTPUtils.escape(self).gsub("%23", "#") end end
require 'webrick/httputils' がウザイですな。
モンキーパッチしちゃいましょう4
class String def encodeURI unescaped = /([#{Regexp.escape('<>#%"{}|\\^[]`' + (0x0..0x1f).map{|c| c.chr }.join + "\x7f" + (0x80..0xff).map{|c| c.chr }.join)}])/n self.force_encoding("ASCII-8BIT").gsub(unescaped){ "%%%02X" % $1.ord } end end