越水です。

PerlのURI::Escape.pm (v3.13)では:

# Default unsafe characters. (RFC 2396 ^uric)
$text =~ s/([^;\/?:@&=+\$,A-Za-z0-9\-_.!~*'()])/$escapes{$1}/g;

となっています。コメントにあるuricというのは:

uric          = reserved | unreserved | escaped
reserved      = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
                "$" | ","
unreserved    = alphanum | mark
mark          = "-" | "_" | "." | "!" | "~" | "*" | "'" |
                "(" | ")"
escaped       = "%" hex hex

となっています。コードを見たのですが、実際にはescapedをさらにエスケー
プするようです。これはこれで実用上は構わないのでしょう。

ところで、IPv6のURLを定めているRFC 2732では、`['と`]'をreservedに移し
ています。それを考慮して、上のEscape.pmのコードを直すと:

$text =~ s/([^;\/?:@&=+\$,A-Za-z0-9\-_.!~*'()\[\]])/$escapes{$1}/g;

になります。これをRubyにすると:

def escape(string)
  string.gsub(/([^;\/?:@&=+$,A-Za-z0-9\-_.!~*'()\[\]])/n) do
    sprintf("%%%02X", $1.unpack("C")[0])
  end
end

となるでしょうか。正規表現の[^]を展開すると私の理解を越えてしまうので、
そのままにしておきました。

ただ、なひさんのおっしゃることにも一理あって、何をエスケープするかは、
ライブラリの実装者が決めかねるところでもあります。Escape.pmでは、それ
に対応するために:

uri_escape($string, [$unsafe])

として、どの文字をエスケープの対象にするか、ユーザが決められるようになっ
ています。それを真似て:

def escape(string, unsafe=nil)
  ## raise ArgumentError unless unsafe.nil? or unsafe.is_a? String
  if not unsafe
    string.gsub(/[^;\/?:@&=+$,A-Za-z0-9\-_.!~*'()\[\]]/n) do |match|
      sprintf("%%%02X", match.unpack("C")[0])
    end
  else
    string.gsub(/[#{unsafe}]/n) do |match|
      sprintf("%%%02X", match.unpack("C")[0])
    end
  end
end
p escape('http://foo/bar?a=b c=d')
p escape('http://foo/bar?a=b c=d', ' ')

としたほうがいいかも知れません。

` 'を`+'に変換する処理は、もともとURIの範疇ではありませんし、削ること
に異論はありません。ただ、考えてみれば、エスケープする機能は、正しい
URI文字列を作るためには必要ですね。

越水
greentea / fa2.so-net.ne.jp