如何根据名字dynamic调用方法?
当名字包含在stringvariables中时,如何dynamic调用方法? 例如:
class MyClass def foo; end def bar; end end obj = MyClass.new str = get_data_from_user # eg `gets`, `params`, DB access, etc. str #=> "foo" # somehow call `foo` on `obj` using the value in `str`.
我该怎么做? 这样做是否有安全风险?
你想做什么就叫做dynamic调度 。 在Ruby中非常简单,只需使用public_send
:
method_name = 'foobar' obj.public_send(method_name) if obj.respond_to? method_name
如果方法是private / protected,则使用send
来代替public_send
,而不要使用public_send
。
如果method_name
的值来自用户,则这是潜在的安全风险。 为了防止漏洞,你应该validation哪些方法可以被实际调用。 例如:
if obj.respond_to?(method_name) && %w[foo bar].include?(method_name) obj.send(method_name) end
Ruby中有多种方式来实现dynamic分派,每种方式都有各自的优缺点。 应该小心select最合适的方法。
下表列出了一些更常见的技术:
+---------------+-----------------+-----------------+------------+------------+ | 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
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
instance_eval的
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
public_send
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
通常用于dynamic调用方法名称,但要确保input值是可信的 ,不能由用户操作。
黄金法则永远不会相信来自用户的任何input。
使用send
dynamic调用方法:
obj.send(str)
你可以使用respond_to?
来检查方法的可用性respond_to?
。 如果可用,那么你打电话send
。 例如:
if obj.respond_to?(str) obj.send(str) end