Ruby 2.1 と 2.2 における、URI#parseの挙動の違い

# 症状
Ruby 2.1では、URIに使用できない文字（アンダースコア、アンダーバー）を含んだ文字列( http://abc_def.com/foobar/ ※1)を`URI#parse`に与えた際に`URI::InvalidURIError`の例外が発生する。

_2015/08/15追記：_
_※1…この文字列はURIとしては **RFC違反** です。ホスト名にアンダースコア、アンダーバーを含むことはできません。@[key-amb](http://qiita.com/key-amb)様、[ご指摘](http://qiita.com/dogwood008/items/923e3b7280af902c292e#comment-e4f3473bcae309366776)頂きありがとうございます。_

_なお、この記事では_
_「RFC違反の文字列に対して、同じURI#parseを使っているが、Rubyのマイナーバージョンに依って挙動が違う」_
_という点にのみ焦点を当てて、実際にどのように異なっているのか、Ruby2.2で2.1の挙動が欲しい場合にどうすれば良いのかについて論じます。_


```ruby:pry
[1] pry(main)> require 'uri'
=> true
[2] pry(main)> url="http://abc_def.com/foobar/"
=> "http://abc_def.com/foobar/"
[3] pry(main)> URI.parse url
URI::InvalidURIError: the scheme http does not accept registry part: abc_def.com (or bad hostname?)
```

しかし、Ruby 2.2では、そのような文字列も問題なくパースできてしまう。

```ruby:pry
[1] pry(main)> require 'uri'
=> true
[2] pry(main)> url="http://abc_def.com/foobar/"
=> "http://abc_def.com/foobar/"
[3] pry(main)> URI.parse url
=> #<URI::HTTP http://abc_def.com/foobar/>
```

# 原因
標準ライブラリの`URI`が参照するRFCが変更されたため。
古い **RFC2396**ではなく、新しい **RFC3986** を参照し、Ruby 2.2からはこれに準拠するようになったため。


# RFC2396に準拠したパースを行うには
`URI::RFC2396_Parser`を使用する。または`URI::Parser`を使う。

## URI::RFC2396_Parserを使う

```ruby:pry
[1] pry(main)> require 'uri'
=> true
[2] pry(main)> url="http://abc_def.com/foobar/"
=> "http://abc_def.com/foobar/"
[3] pry(main)> p=URI::RFC2396_Parser.new
=> #<URI::RFC2396_Parser:0x007fb8ac036eb8>
[4] pry(main)> p.parse url
URI::InvalidURIError: the scheme http does not accept registry part: abc_def.com (or bad hostname?)
```

## URI::Parserを使う

```ruby:pry
[1] pry(main)> require 'uri'
=> true
[2] pry(main)> url="http://abc_def.com/foobar/"
=> "http://abc_def.com/foobar/"
[3] pry(main)> p=URI::Parser.new
=> #<URI::RFC2396_Parser:0x007f96ad317e50>
[4] pry(main)> p.parse url
URI::InvalidURIError: the scheme http does not accept registry part: abc_def.com (or bad hostname?)
```


# 参考

- https://github.com/ruby/ruby/blob/trunk/lib/uri/rfc2396_parser.rb
- https://bugs.ruby-lang.org/issues/10669
- https://bugs.ruby-lang.org/issues/2542
- https://github.com/rack/rack/blob/ab172af1b63f0d8e91ce579dd2907c43b96cf82a/lib/rack/mock.rb#L82-L85
- [URIに使ってよい文字の話 - RFC2396 と RFC3986](http://freak-da.hatenablog.com/entry/20080321/p1)

# 補足

Ruby 2.2, 2.1 標準のURIライブラリでは、日本語などのマルチバイト文字を含むURIをparseすることができません。

```ruby:pry
[1] pry(main)> require 'uri'
=> true
[2] pry(main)> URI.parse 'http://example.com/日本語'
URI::InvalidURIError: URI must be ascii only "http://example.com/\u{65e5}\u{672c}\u{8a9e}"
[3] pry(main)>
[4] pry(main)> p = URI::Parser.new
=> #<URI::RFC2396_Parser:0x007fb3c1341300>
[5] pry(main)> p.parse 'http://example.com/日本語'
URI::InvalidURIError: bad URI(is not URI?): http://example.com/日本語


[6] pry(main)> URI.parse 'http://example.com/Ω'
URI::InvalidURIError: URI must be ascii only "http://example.com/\u{3a9}"
[7] pry(main)> p.parse 'http://example.com/Ω'
URI::InvalidURIError: bad URI(is not URI?): http://example.com/Ω

```

[addressable](https://github.com/sporkmonger/addressable)というgemを使用すると、マルチバイト文字もparseできます。RFC 3986, RFC 3987, RFC 6570に準拠しているようです。

```ruby:pry
[1] pry(main)> require 'addressable/uri'
=> true
[2] pry(main)> url = 'http://example.com/日本語'
=> "http://example.com/日本語"
[3] pry(main)> Addressable::URI.parse url
=> #<Addressable::URI:0x3fe81a09b900 URI:http://example.com/日本語>
```



# 謝辞

修正前の記事は、「RFC3986によって、ホスト名にアンダーバーを含むことができるようになった」との誤解を与えかねない記事でした。また、更新によって有用なgemの紹介もすることができました。以下の皆様のご指摘で記事を修正、更新することができました。感謝の意を表明致します。

@[key-amb](http://qiita.com/dogwood008/items/923e3b7280af902c292e#comment-e4f3473bcae309366776)様
id:[tmatsuu](http://b.hatena.ne.jp/entry/261535540/comment/tmatsuu)様
id:[tohokuaiki](http://b.hatena.ne.jp/entry/261535540/comment/tohokuaiki)様
id:[toyama0919](http://b.hatena.ne.jp/entry/261535540/comment/toyama0919)様



# Original

[Back of Flier: Ruby 2.1 と 2.2 における、URI#parseの挙動の違い](http://blog.dogwood008.com/2015/08/DifferenceBetweenRuby20And21OnURIParse.html)