URLエンコード/エスケープに使うメソッドごとの違い

2014/11/19追記

JavaScriptのencodeURIComponentにRubyのメソッドで近そうなのはWEBrick::HTTPUtils.escape_form

ほぼ同じなのですが、唯一の違いが半角スペースのエンコード
encodeURIComponent だと %20 ですが、 WEBrick::HTTPUtils.escape_form だと + (プラス記号)になります。
WEBrick::HTTPUtils.escape_form してから + を %20 に置換(String#gsub("+", "%20"))してやればOK
JavaScriptのencodeURIComponentはRubyだとどう書く - 別館 子子子子子子(ねこのここねこ)

本編

URLエンコードなんだからURI.encode(もしくはURI.escape)とかだったよなーと思ったのだけど

  • escape(str, unsafe = URI::UNSAFE) -> String
  • encode(str, unsafe = URI::UNSAFE) -> String

URI 文字列をエンコードした文字列を返します。

このメソッドは obsolete です。
代わりに ERB::Util.#url_encode, CGI.escape, URI.encode_www_form_component, WEBrick::HTTPUtils.#escape_form, WEBrick::HTTPUtils.#escape などの使用を検討してください。 詳細は [ruby-core:29293] からのスレッドを参照してください。

…あらobsoleteだ。

メソッドの違いはどーなってんだか

いろいろメソッドがあるんだけど、当然ながら何かが違うから別メソッドなわけで。
何が違うんだろ、と思ったらさっきのMLにあった。

% ./ruby -rcgi -ruri -rwebrick/httputils -rerb -e '
table = []
(0..255).each {|c|
  s = [c].pack("C")
  e = [
    ERB::Util.url_encode(s),
    CGI.escape(s),
    URI.encode_www_form_component(s),
    WEBrick::HTTPUtils.escape_form(s),
    WEBrick::HTTPUtils.escape(s),
    URI.escape(s),
  ]
  next if e.uniq.length == 1
  p e
  table << e
}
'

結果がこちら。

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", "~",   "~",   "~"]
 http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/29373

ちなみに全体図はこうなります。右端に文字そのものを追加しておきました。

["%20", "+",   "+",   "+",   "%20", "%20", " "]
["%21", "%21", "%21", "!",   "!",   "!",   "!"]
["%22", "%22", "%22", "%22", "%22", "%22", """]
["%23", "%23", "%23", "%23", "%23", "%23", "#"]
["%24", "%24", "%24", "%24", "$",   "$",   "$"]
["%25", "%25", "%25", "%25", "%25", "%25", "%"]
["%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", "/",   "/",   "/"]
["0",   "0",   "0",   "0",   "0",   "0",   "0"]
["1",   "1",   "1",   "1",   "1",   "1",   "1"]
["2",   "2",   "2",   "2",   "2",   "2",   "2"]
["3",   "3",   "3",   "3",   "3",   "3",   "3"]
["4",   "4",   "4",   "4",   "4",   "4",   "4"]
["5",   "5",   "5",   "5",   "5",   "5",   "5"]
["6",   "6",   "6",   "6",   "6",   "6",   "6"]
["7",   "7",   "7",   "7",   "7",   "7",   "7"]
["8",   "8",   "8",   "8",   "8",   "8",   "8"]
["9",   "9",   "9",   "9",   "9",   "9",   "9"]
["%3A", "%3A", "%3A", "%3A", ":",   ":",   ":"]
["%3B", "%3B", "%3B", "%3B", ";",   ";",   ";"]
["%3C", "%3C", "%3C", "%3C", "%3C", "%3C", "<"]
["%3D", "%3D", "%3D", "%3D", "=",   "=",   "="]
["%3E", "%3E", "%3E", "%3E", "%3E", "%3E", ">"]
["%3F", "%3F", "%3F", "%3F", "?",   "?",   "?"]
["%40", "%40", "%40", "%40", "@",   "@",   "@"]
["A",   "A",   "A",   "A",   "A",   "A",   "A"]
["B",   "B",   "B",   "B",   "B",   "B",   "B"]
["C",   "C",   "C",   "C",   "C",   "C",   "C"]
["D",   "D",   "D",   "D",   "D",   "D",   "D"]
["E",   "E",   "E",   "E",   "E",   "E",   "E"]
["F",   "F",   "F",   "F",   "F",   "F",   "F"]
["G",   "G",   "G",   "G",   "G",   "G",   "G"]
["H",   "H",   "H",   "H",   "H",   "H",   "H"]
["I",   "I",   "I",   "I",   "I",   "I",   "I"]
["J",   "J",   "J",   "J",   "J",   "J",   "J"]
["K",   "K",   "K",   "K",   "K",   "K",   "K"]
["L",   "L",   "L",   "L",   "L",   "L",   "L"]
["M",   "M",   "M",   "M",   "M",   "M",   "M"]
["N",   "N",   "N",   "N",   "N",   "N",   "N"]
["O",   "O",   "O",   "O",   "O",   "O",   "O"]
["P",   "P",   "P",   "P",   "P",   "P",   "P"]
["Q",   "Q",   "Q",   "Q",   "Q",   "Q",   "Q"]
["R",   "R",   "R",   "R",   "R",   "R",   "R"]
["S",   "S",   "S",   "S",   "S",   "S",   "S"]
["T",   "T",   "T",   "T",   "T",   "T",   "T"]
["U",   "U",   "U",   "U",   "U",   "U",   "U"]
["V",   "V",   "V",   "V",   "V",   "V",   "V"]
["W",   "W",   "W",   "W",   "W",   "W",   "W"]
["X",   "X",   "X",   "X",   "X",   "X",   "X"]
["Y",   "Y",   "Y",   "Y",   "Y",   "Y",   "Y"]
["Z",   "Z",   "Z",   "Z",   "Z",   "Z",   "Z"]
["%5B", "%5B", "%5B", "%5B", "%5B", "[",   "["]
["%5C", "%5C", "%5C", "%5C", "%5C", "%5C", "\"]
["%5D", "%5D", "%5D", "%5D", "%5D", "]",   "]"]
["%5E", "%5E", "%5E", "%5E", "%5E", "%5E", "^"]
["_",   "_",   "_",   "_",   "_",   "_",   "_"]
["%60", "%60", "%60", "%60", "%60", "%60", "`"]
["a",   "a",   "a",   "a",   "a",   "a",   "a"]
["b",   "b",   "b",   "b",   "b",   "b",   "b"]
["c",   "c",   "c",   "c",   "c",   "c",   "c"]
["d",   "d",   "d",   "d",   "d",   "d",   "d"]
["e",   "e",   "e",   "e",   "e",   "e",   "e"]
["f",   "f",   "f",   "f",   "f",   "f",   "f"]
["g",   "g",   "g",   "g",   "g",   "g",   "g"]
["h",   "h",   "h",   "h",   "h",   "h",   "h"]
["i",   "i",   "i",   "i",   "i",   "i",   "i"]
["j",   "j",   "j",   "j",   "j",   "j",   "j"]
["k",   "k",   "k",   "k",   "k",   "k",   "k"]
["l",   "l",   "l",   "l",   "l",   "l",   "l"]
["m",   "m",   "m",   "m",   "m",   "m",   "m"]
["n",   "n",   "n",   "n",   "n",   "n",   "n"]
["o",   "o",   "o",   "o",   "o",   "o",   "o"]
["p",   "p",   "p",   "p",   "p",   "p",   "p"]
["q",   "q",   "q",   "q",   "q",   "q",   "q"]
["r",   "r",   "r",   "r",   "r",   "r",   "r"]
["s",   "s",   "s",   "s",   "s",   "s",   "s"]
["t",   "t",   "t",   "t",   "t",   "t",   "t"]
["u",   "u",   "u",   "u",   "u",   "u",   "u"]
["v",   "v",   "v",   "v",   "v",   "v",   "v"]
["w",   "w",   "w",   "w",   "w",   "w",   "w"]
["x",   "x",   "x",   "x",   "x",   "x",   "x"]
["y",   "y",   "y",   "y",   "y",   "y",   "y"]
["z",   "z",   "z",   "z",   "z",   "z",   "z"]
["%7B", "%7B", "%7B", "%7B", "%7B", "%7B", "{"]
["%7C", "%7C", "%7C", "%7C", "%7C", "%7C", "|"]
["%7D", "%7D", "%7D", "%7D", "%7D", "%7D", "}"]
["%7E", "%7E", "%7E", "~",   "~",   "~",   "~"]

CGI.escape_htmlは?

escapeと言うならば上記以外にCGI.escape_htmlもあるよな、と思った。でもちょっと調べたら、エンコードじゃなくて本当にエスケープだった。

  • escapeHTML(string) -> String [added by cgi/util]
  • escape_html(string) -> String [added by cgi/util]

与えられた文字列中の &"<> を実体参照に置換した文字列を新しく作成し返します。
singleton method CGI.escapeHTML (Ruby 1.9.3)

  • unescapeHTML(string) -> String [added by cgi/util]
  • unescape_html(string) -> String [added by cgi/util]

与えられた文字列中の実体参照のうち、&amp; &gt; &lt; &quot; と数値指定がされているもの (&#0ffff など) を元の文字列に置換します。
singleton method CGI.unescapeHTML (Ruby 1.9.3)

いちおう比較確認。encode.rbってのを書いてみた。

require 'uri'
require 'cgi/util'
(0..255).each do |c|
  s = [c].pack("C")
  e = [
    URI.escape(s),
    URI.encode_www_form_component(s),
    CGI.escape(s),
  ]
  if e.uniq.length != 1 || CGI.escape_html(s) != s
    e_length = e.uniq.length
    e << CGI.escape_html(s)
    p e
  end
end

実行。

URI.escapeURI.encode_www_form_component
   ┆     ┆   CGI.escape
   ┆     ┆     ┆   CGI.escape_html
   ┆     ┆     ┆     ┆
$ ruby encode.rb
["%20", "+",   "+",   " "]
["!",   "%21", "%21", "!"]
["%22", "%22", "%22", "&quot;"]
["$",   "%24", "%24", "$"]
["&",   "%26", "%26", "&amp;"]
["'",   "%27", "%27", "'"]
["(",   "%28", "%28", "("]
[")",   "%29", "%29", ")"]
["*",   "*",   "%2A", "*"]
["+",   "%2B", "%2B", "+"]
[",",   "%2C", "%2C", ","]
["/",   "%2F", "%2F", "/"]
[":",   "%3A", "%3A", ":"]
[";",   "%3B", "%3B", ";"]
["%3C", "%3C", "%3C", "&lt;"]
["=",   "%3D", "%3D", "="]
["%3E", "%3E", "%3E", "&gt;"]
["?",   "%3F", "%3F", "?"]
["@",   "%40", "%40", "@"]
["[",   "%5B", "%5B", "["]
["]",   "%5D", "%5D", "]"]
["~",   "%7E", "%7E", "~"]

あ、そういえば%20って半角スペースでしたね。「+」にエスケープされやすいから勘違いしてしまう。

まとめ

ということで、URI.escapeからの置き換えというわりにはかなり違うのですが、URLエンコードを使うのならばrequire 'cgi/util'してエスケープが必要な部分だけCGI.escapeしておきましょう(URL全体をCGI.escapeするとURLじゃなくなるので注意)。
逆の unescape に関しては、何でも戻してしまうっぽい CGI.unescape で良いでしょう。ただし、 CGI.unescape は「+」を半角スペースにしてしまうことに注意。