Comment puis-je appeler une méthode de manière dynamique lorsque son nom est contenu dans une variable chaîne? Par exemple:
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`.
Comment puis-je faire ceci? Est-ce que cela pose un risque pour la sécurité?
Ce que vous voulez faire s'appelle dispatch dynamique . C’est très facile avec Ruby, utilisez simplement public_send
:
method_name = 'foobar'
obj.public_send(method_name) if obj.respond_to? method_name
Si la méthode est privée/protégée, utilisez plutôt send
, mais préférez public_send
.
Ceci constitue un risque potentiel pour la sécurité si la valeur de method_name
vient de l'utilisateur. Pour éviter les vulnérabilités, vous devez valider les méthodes pouvant être appelées. Par exemple:
if obj.respond_to?(method_name) && %w[foo bar].include?(method_name)
obj.send(method_name)
end
Il existe plusieurs façons d’effectuer la répartition dynamique dans Ruby, chacune avec ses propres avantages et inconvénients. Il faut prendre soin de choisir la méthode la plus appropriée à la situation.
Le tableau suivant présente certaines des techniques les plus courantes:
+---------------+-----------------+-----------------+------------+------------+
| 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 |
+---------------+-----------------+-----------------+------------+------------+
Certaines techniques se limitent à appeler des méthodes, alors que d'autres peuvent exécuter pratiquement n'importe quoi. Les méthodes permettant l'exécution de code arbitraire doivent être tilisées avec une extrême prudence, si elles ne sont pas totalement évitées .
Certaines techniques sont limitées à l'appel de méthodes publiques, alors que d'autres peuvent appeler des méthodes publiques et privées. Idéalement, vous devriez vous efforcer d'utiliser la méthode avec le moins de visibilité possible pour répondre à vos exigences.
Remarque: Si une technique peut exécuter du code arbitraire, elle peut facilement être utilisée pour accéder à des méthodes privées qu’il n’aurait peut-être pas accès à.
Le fait qu’une technique ne puisse pas exécuter de code arbitraire ou appeler une méthode privée ne signifie pas qu’elle est sûre, en particulier si vous utilisez des valeurs fournies par l’utilisateur. Delete est une méthode publique.
Certaines de ces techniques peuvent être plus performantes que d’autres, selon votre version de Ruby. Critères à suivre ....
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
Vous êtes vraiment allez vouloir faire attention à cela. L'utilisation de données utilisateur pour appeler n'importe quelle méthode via send
pourrait laisser de la place à l'utilisateur pour qu'il puisse exécuter la méthode de son choix. send
est souvent utilisé pour appeler des noms de méthodes de manière dynamique - mais assurez-vous que les valeurs saisies sont fiables et ne peuvent pas être manipulées par les utilisateurs.
La règle d'or est de ne jamais faire confiance aux entrées qui proviennent de l'utilisateur.
Utilisez send
pour appeler une méthode de manière dynamique:
obj.send(str)
Vous pouvez vérifier la disponibilité de la méthode en utilisant respond_to?
. S'il est disponible, vous appelez send
. Par exemple:
if obj.respond_to?(str)
obj.send(str)
end