CarrierWaveのファイル名変換(original_filename)の挙動
ファイルアップロード機構にCarrierWaveを利用しています。
アップロードしたファイル名は、
self.file_column.file.original_filename
のように取得出来ますが、デフォルトでは日本語を利用出来ません。
ファイル名に日本語を利用する場合、
./config/initializers/carrierwave.rb
CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
のように設定します。
ここまではググればすぐ出てくる情報なのですが、
「Simple (1).csv」というファイル名が、「Simple__1_.csv」に変換されることが分かりました。
CarrierWaveのコードを見てみると、先の設定は、
./vendor/bundles/ruby/2.1.0/gems/carrierwave-0.10.0/lib/carrierwave/sanitized_file.rb
def sanitize(name) name = name.gsub("\\", "/") # work-around for IE name = File.basename(name) name = name.gsub(sanitize_regexp,"_") name = "_#{name}" if name =~ /\A\.+\z/ name = "unnamed" if name.size == 0 return name.mb_chars.to_s end
上記の、
name = name.gsub(sanitize_regexp,"_")
で変換されます。
指定した正規表現を「_」に変換するわけです。
[:word:]はPOSIX文字クラスで「単語構成文字」を表現する正規表現になりますので、ここを調整すると修正出来そうです。
http://docs.ruby-lang.org/ja/1.9.3/doc/spec=2fregexp.html
調べたところ、
[:print:] 表示可能な文字(空白を含む)
という文字クラスがあり、これを使えばファイル名に制限を無くすことが出来るはず。
./config/initializers/carrierwave.rb
CarrierWave::SanitizedFile.sanitize_regexp = /[^[:print:]]/
としたところ予想通り変換を回避する事が出来たのですが、念のため色々な記号をファイル名に使い試したところ、「+」が「半角スペース」に変換されてしまう事が分かりました。
どうもコードの意図通りに動いていないようです。
pry(main)> "+".gsub("[^[:print:]]","_") => "+"
やはり変換されない。
./vendor/bundles/ruby/2.1.0/gems/carrierwave-0.10.0/lib/carrierwave/sanitized_file.rb
def sanitize(name) name = name.gsub("\\", "/") # work-around for IE name = File.basename(name) name = name.gsub(sanitize_regexp,"_") name = "_#{name}" if name =~ /\A\.+\z/ name = "unnamed" if name.size == 0 return name.mb_chars.to_s end
に渡ってくるパラメータ(name)を見てみたところ、すでに「+」が半角スペースになっていました。
これはコアの動きっぽいと感じつつ、小一時間デバックをしてみたところ原因が分かりました。
./vendor/bundles/ruby/2.1.0/gems/rack-1.5.2/lib/rack/multipart/parser.rb
def get_filename(head) filename = nil if head =~ RFC2183 filename = Hash[head.scan(DISPPARM)]['filename'] filename = $1 if filename and filename =~ /^"(.*)"$/ elsif head =~ BROKEN_QUOTED filename = $1 elsif head =~ BROKEN_UNQUOTED filename = $1 end if filename && filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ } filename = Utils.unescape(filename) end if filename && filename !~ /\\[^\\"]/ filename = filename.gsub(/\\(.)/, '\1') end filename end
上記の、
filename = Utils.unescape(filename)
が犯人でした。
このメソッドの実態は、
./vendor/bundles/ruby/2.1.0/gems/rack-1.5.2/lib/rack/utils.rb
if defined?(::Encoding) def unescape(s, encoding = Encoding::UTF_8) URI.decode_www_form_component(s, encoding) end else def unescape(s, encoding = nil) URI.decode_www_form_component(s, encoding) end end module_function :unescape
で、この中の、
URI.decode_www_form_component
が変換処理を行っていました。
http://docs.ruby-lang.org/ja/2.0.0/method/URI/s/decode_www_form_component.html
ドキュメントを見ると、「"+" という文字は空白文字にデコードします」とあります。
すっきりしました。
ファイル名に"+"が使えないのはRails(Rack)の仕様という事でよさそうです。