RubyのURI.parseでパスワードに含んではいけない文字をいれる

URI.parseで、FTPユーザのパスワードとかに%が入っていたりすると

1
URI::InvalidURIError: bad URI(is not URI?)

と言われます。
その時の(無理やり)の対処法のメモ

pryにて、まず成功するパターンのインスタンスの情報をみると

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pry(main)> uri = URI.parse("ftp://test_user:test_pass@ftp.hoge.jp/test.csv")
=> #<URI::FTP ftp://test_user:test_pass@ftp.hoge.jp/test.csv>
pry(main)> ls uri
URI::Generic#methods:
+ absolute? eql? hash hostname normalize parser port registry route_to user
- coerce find_proxy hierarchical? hostname= normalize! password port= registry= scheme user=
== component fragment host inspect opaque password= query relative? scheme= userinfo
absolute default_port fragment= host= merge! opaque= path= query= route_from select userinfo=
URI::FTP#methods: merge path to_s typecode typecode=
instance variables: @fragment @host @opaque @parser @password @path @port @query @scheme @typecode @user
class variables: @@schemes
pry(main)> cd uri
pry(#<URI::FTP>):1> @password
=> "test_pass"

となっています。
この@passwordがパスワードの情報になるわけですね。
で、これはインスタンスなので、instance_evalで値を直接書き換えられるわけです。

まずユーザとパスワード抜きでURIオブジェクトを作成します。

1
uri2 = URI.parse("ftp://ftp.hoge.jp/test.csv")

その後に、instance_evalで@passwordの値を書き換える

1
2
3
4
5
6
7
8
pry(main)> uri2.instance_eval do
pry(main)* @user = 'test_user'
pry(main)* @password = "test%pass"
pry(main)* end
=> "test%pass"
pry(main)> cd uri2
pry(#<URI::FTP>):1> @password
=> "test%pass"

無事にpasswordに’%’を設定できました。
良さそうです。

最終的なコードは以下のとおり

1
2
3
4
5
uri = URI.parse("ftp://ftp.hoge.jp/test.csv")
uri.instance_eval do
@user = 'test_user'
@password = 'test%pass'
end

ここまで書いて注意があります

このinstance_evalですが、インスタンス変数の値を直接書き換えるものです。
今回は@passwordというインスタンス変数がURIオブジェクトの中に存在したから良いのですが、仕様変更などで@passwordというインスタンス変数が使われなくなると途端に動かなくなるので注意が必要です。

お使いの際は、上記のことも念頭に入れてお使いください