tl;dr
ssrf_filter
が 1.1.0 以上の場合にエラーになるので Gemfile に以下を追加し bundle install
、もしくは bundle update ssrf_filter
を実行する。
gem 'ssrf_filter', '1.0.8'
デバッグ
ある時 carrierwave
の remote_*_url=
メソッドを使用時にこのエラーに遭遇しました。
Medium.create!(remote_file_url: "https://mgre.co.jp/wp-content/themes/lanchester2017/assets/img/common/noimage1200x630.png")
NoMethodError: undefined method `closed?' for nil:NilClass
環境は ruby 3.0.1、rails 6.1.7、carrierwave 2.2.2 です。
モデルやアップローダーは以下です。一部省略していますが特別なところはありません。
mysql> desc media;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| file | varchar(255) | NO | | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
class Medium < ApplicationRecord
mount_uploader :file, MediumUploader
end
class MediumUploader < CarrierWave::Uploader::Base
process resize_to_limit: [750, 750]
end
ちなみに正確なエラーログは以下です。Net::HTTP
が関係するエラーのようです。
NoMethodError: undefined method `closed?' for nil:NilClass
from /Users/kurashita/.rbenv/versions/3.0.1/lib/ruby/3.0.0/net/http/response.rb:340:in `stream_check'
エラーログが少ないので remote_*_url
メソッドのソースコードを直接確認していきます。
メソッドの定義箇所は以下のように調べられます。
pry(main)> Medium.new.method(:remote_file_url=).source_location
=> ["/Users/kurashita/work/MGRe/MGRe/vendor/bundle/ruby/3.0.0/gems/carrierwave-2.2.2/lib/carrierwave/orm/activerecord.rb", 18]
ソースを確認すると以下のようになっています。
require 'active_record'
require 'carrierwave/validations/active_model'
module CarrierWave
module ActiveRecord
include CarrierWave::Mount
def mount_uploader(column, uploader=nil, options={}, &block)
super
mod = Module.new
prepend mod
mod.class_eval <<-RUBY, __FILE__, __LINE__+1
def remote_#{column}_url=(url)
column = _mounter(:#{column}).serialization_column
__send__(:"\#{column}_will_change!")
super
end
RUBY
end
...
end
end
処理の内容的にはほぼ super
しているだけなのでオーバーライドされているメソッドを見ていきます。
オーバーライド元のメソッドは以下のように調べることが出来ます。
pry(main)> Medium.new.method(:remote_file_url=).super_method.source_location
=> ["/Users/kurashita/work/MGRe/MGRe/vendor/bundle/ruby/3.0.0/gems/carrierwave-2.2.2/lib/carrierwave/mount.rb", 165]
module CarrierWave
module Mount
def mount_uploader(column, uploader=nil, options={}, &block)
mount_base(column, uploader, options, &block)
mod = Module.new
include mod
mod.class_eval <<-RUBY, __FILE__, __LINE__+1
...
def remote_#{column}_url=(url)
_mounter(:#{column}).remote_urls = [url]
end
...
RUBY
end
end
end
_mounter(:#{column}).remote_urls=
メソッドを呼んでいるので例によってソースを調べていきます。
これを何度か繰り返すので途中割愛し、最終的なメソッドを見つけます。
最終的には以下の download
メソッドに行き着きます。
require 'open-uri'
require 'ssrf_filter'
require 'addressable'
require 'carrierwave/downloader/remote_file'
module CarrierWave
module Downloader
class Base
attr_reader :uploader
...
def download(url, remote_headers = {})
headers = remote_headers.
reverse_merge('User-Agent' => "CarrierWave/#{CarrierWave::VERSION}")
uri = process_uri(url.to_s)
begin
if skip_ssrf_protection?(uri)
response = OpenURI.open_uri(process_uri(url.to_s), headers)
else
request = nil
response = SsrfFilter.get(uri, headers: headers) do |req|
request = req
end
response.uri = request.uri
response.value
end
rescue StandardError => e
raise CarrierWave::DownloadError, "could not download file: #{e.message}"
end
CarrierWave::Downloader::RemoteFile.new(response)
end
...
end
end
end
ここまで来て SsrfFilter
が出てきました。
ssrf_filter
では少し前に 1.0.8 から 1.1.0 のバージョンアップが行われているため、以下のどこかが影響している可能性があります。
https://github.com/arkadiyt/ssrf_filter/compare/1.0.8..1.1.0
ただ、今回は急ぎだったため以下のようにバージョンを落として対応しました。
gem 'ssrf_filter', '1.0.8'