名前が文字列変数に含まれているときにメソッドを動的に呼び出すにはどうすればよいですか?例えば:
class MyClass
def foo; end
def bar; end
end
obj = MyClass.new
str = get_data_from_user # e.g. `gets`, `params`, DB access, etc.
str #=> "foo"
# somehow call `foo` on `obj` using the value in `str`.
これどうやってするの?これはセキュリティ上のリスクですか?
やりたいことは dynamic dispatch と呼ばれます。 Rubyでは非常に簡単です。 public_send
を使用するだけです:
method_name = 'foobar'
obj.public_send(method_name) if obj.respond_to? method_name
メソッドがプライベート/保護されている場合、代わりに send
を使用しますが、public_send
を優先します。
method_name
の値がユーザーに由来する場合、これは潜在的なセキュリティリスクです。脆弱性を防ぐには、実際に呼び出すことができるメソッドを検証する必要があります。例えば:
if obj.respond_to?(method_name) && %w[foo bar].include?(method_name)
obj.send(method_name)
end
Rubyで動的ディスパッチを実現する方法は複数あり、それぞれに長所と短所があります。状況に最も適した方法を選択するように注意する必要があります。
次の表に、より一般的な手法の一部を示します。
+---------------+-----------------+-----------------+------------+------------+
| Method | Arbitrary Code? | Access Private? | Dangerous? | Fastest On |
+---------------+-----------------+-----------------+------------+------------+
| eval | Yes | No | Yes | TBD |
| instance_eval | Yes | No | Yes | TBD |
| send | No | Yes | Yes | TBD |
| public_send | No | No | Yes | TBD |
| method | No | Yes | Yes | TBD |
+---------------+-----------------+-----------------+------------+------------+
一部の手法はメソッドの呼び出しのみに制限されていますが、他の手法は基本的に何でも実行できます。任意のコードの実行を許可するメソッドは、 完全に回避されない場合、細心の注意を払って使用されます である必要があります。
一部の手法はパブリックメソッドのみの呼び出しに制限されていますが、他の手法はパブリックメソッドとプライベートメソッドの両方を呼び出すことができます。理想的には、要件を満たす最小の可視性でメソッドを使用するよう努力する必要があります。
注:テクニックが任意のコードを実行できる場合、簡単に他の方法ではアクセスできない可能性があるプライベートメソッドにアクセスするために使用します。
テクニックが任意のコードを実行できない、またはプライベートメソッドを呼び出すことができないからといって、特にユーザーが指定した値を使用している場合、それが安全であることを意味しません。削除はパブリックメソッドです。
これらのテクニックのいくつかは、Rubyバージョン。従うべきベンチマークに応じて...
class MyClass
def foo(*args); end
private
def bar(*args); end
end
obj = MyClass.new
eval('obj.foo') #=> nil
eval('obj.bar') #=> NoMethodError: private method `bar' called
# With arguments:
eval('obj.foo(:arg1, :arg2)') #=> nil
eval('obj.bar(:arg1, :arg2)') #=> NoMethodError: private method `bar' called
obj.instance_eval('foo') #=> nil
obj.instance_eval('bar') #=> nil
# With arguments:
obj.instance_eval('foo(:arg1, :arg2)') #=> nil
obj.instance_eval('bar(:arg1, :arg2)') #=> nil
obj.send('foo') #=> nil
obj.send('bar') #=> nil
# With arguments:
obj.send('foo', :arg1, :arg2) #=> nil
obj.send('bar', :arg1, :arg2) #=> nil
obj.public_send('foo') #=> nil
obj.public_send('bar') #=> NoMethodError: private method `bar' called
# With arguments:
obj.public_send('foo', :arg1, :arg2) #=> nil
obj.public_send('bar', :arg1, :arg2) #=> NoMethodError: private method `bar' called
obj.method('foo').call #=> nil
obj.method('bar').call #=> nil
# With arguments:
obj.method('foo').call(:arg1, :arg2) #=> nil
obj.method('bar').call(:arg1, :arg2) #=> nil
あなたは本当にこれに注意したいでしょう。ユーザーデータを使用してsend
を介して任意のメソッドを呼び出すと、ユーザーが必要なメソッドを実行するためのスペースを空けることができます。 send
は、メソッド名を動的に呼び出すためによく使用されますが、入力値がtrustedであり、ユーザーが操作できないことを確認してください。
ゴールデンルールは、ユーザーからの入力を決して信頼しません。
send
を使用して、メソッドを動的に呼び出します。
obj.send(str)
respond_to?
を使用してメソッドの可用性を確認できます。使用可能な場合は、send
を呼び出します。例えば:
if obj.respond_to?(str)
obj.send(str)
end