如果方法名称包含在字符串变量中,如何动态调用该方法?例如:
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`.
我该怎么做?这样做是否存在安全隐患?
答案 0 :(得分:127)
您要做的是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
答案 1 :(得分:82)
在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 |
+---------------+-----------------+-----------------+------------+------------+
某些技术仅限于调用方法,而其他技术基本上只能执行任何操作。允许执行任意代码的方法应该是used with extreme caution, if not avoided altogether。
某些技术仅限于调用公共方法,而其他技术可以同时调用公共方法和私有方法。理想情况下,您应该努力使用满足您要求的最低可见度的方法。
注意 :如果某项技术可以执行任意代码,则可以轻松地使用它来访问它可能无法访问的私有方法。
仅仅因为某项技术无法执行任意代码或调用私有方法并不意味着它是安全的,特别是如果您使用的是用户提供的值。删除是一种公共方法。
根据您的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
答案 2 :(得分:13)
你真的要小心这个。使用用户数据通过send
调用任何方法可以为用户打开空间以执行他们想要的任何方法。 send
通常用于动态调用方法名称,但要确保输入值信任且用户无法操纵。
黄金法则永远不会信任来自用户的任何输入。
答案 3 :(得分:10)
使用send
动态调用方法:
obj.send(str)
答案 4 :(得分:7)
您可以使用respond_to?
检查方法的可用性。如果它可用,则拨打send
。例如:
if obj.respond_to?(str)
obj.send(str)
end