use*_*052 94 ruby metaprogramming dynamic
当名称包含在字符串变量中时,如何动态调用方法?例如:
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`.
Run Code Online (Sandbox Code Playgroud)
我怎样才能做到这一点?这样做是否存在安全风险?
Dav*_*vid 132
您想要做的是称为动态调度.在Ruby中它很容易,只需使用public_send
:
method_name = 'foobar'
obj.public_send(method_name) if obj.respond_to? method_name
Run Code Online (Sandbox Code Playgroud)
如果该方法是私有/受保护的,请send
改用,但更喜欢public_send
.
如果价值method_name
来自用户,则存在潜在的安全风险.要防止漏洞,您应该验证哪些方法可以实际调用.例如:
if obj.respond_to?(method_name) && %w[foo bar].include?(method_name)
obj.send(method_name)
end
Run Code Online (Sandbox Code Playgroud)
Bra*_*rth 88
在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 |
+---------------+-----------------+-----------------+------------+------------+
Run Code Online (Sandbox Code Playgroud)
有些技术仅限于调用方法,而其他技术基本上只能执行任何操作.如果不能完全避免,应该极其谨慎地使用允许执行任意代码的方法.
有些技术仅限于调用公共方法,而其他技术可以调用公共方法和私有方法.理想情况下,您应该努力使用满足您要求的最低可见性的方法.
注意:如果某种技术可以执行任意代码,则可以轻松地使用它来访问它可能无法访问的私有方法.
仅仅因为技术不能执行任意代码或调用私有方法并不意味着它是安全的,特别是如果您使用用户提供的值.删除是一种公共方法.
根据您的Ruby版本,其中一些技术可能比其他技术更高效.基准跟随......
class MyClass
def foo(*args); end
private
def bar(*args); end
end
obj = MyClass.new
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
obj.send('foo') #=> nil
obj.send('bar') #=> nil
# With arguments:
obj.send('foo', :arg1, :arg2) #=> nil
obj.send('bar', :arg1, :arg2) #=> nil
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
nzi*_*nab 13
你真的要小心这个.使用用户数据来调用任何方法send
可以为用户打开空间以执行他们想要的任何方法. send
通常用于动态调用方法名称,但要确保输入值是可信的,用户不能操作.
黄金法则永远不会信任来自用户的任何输入.
您可以使用检查方法可用性respond_to?
.如果它可用,那么你打电话send
.例如:
if obj.respond_to?(str)
obj.send(str)
end
Run Code Online (Sandbox Code Playgroud)