PyAPI: Add Context.path_resolve wrapper that supports context members

This avoids script authors using `eval("context.%s" % data_path)`
to access paths starting from the context,
which isn't good practice especially if the data_path isn't trusted.

Now it's possible to resplve paths such as:

   context.path_resolve('active_object.modifiers[0].name')
This commit is contained in:
Campbell Barton 2022-04-06 11:42:46 +10:00
parent 5d31252c76
commit 7bb8eeb3a8
1 changed files with 53 additions and 0 deletions

View File

@ -8,12 +8,65 @@ StructRNA = bpy_types.bpy_struct
StructMetaPropGroup = bpy_types.bpy_struct_meta_idprop
# StructRNA = bpy_types.Struct
# Private dummy object use for comparison only.
_sentinal = object()
# Note that methods extended in C are defined in: 'bpy_rna_types_capi.c'
class Context(StructRNA):
__slots__ = ()
def path_resolve(self, path, coerce=True):
"""
Returns the property from the path, raise an exception when not found.
:arg path: patch which this property resolves.
:type path: string
:arg coerce: optional argument, when True, the property will be converted into its Python representation.
:type coerce: boolean
"""
# This is a convenience wrapper around `StructRNA.path_resolve` which doesn't support accessing context members.
# Without this wrapper many users were writing `exec("context.%s" % data_path)` which is a security
# concern if the `data_path` comes from an unknown source.
# This function performs the initial lookup, after that the regular `path_resolve` function is used.
# Extract the initial attribute into `(attr, path_rest)`.
sep = len(path)
div = ""
for div_test in (".", "["):
sep_test = path.find(div_test, 0, sep)
if sep_test != -1 and sep_test < sep:
sep = sep_test
div = div_test
if div:
attr = path[:sep]
if div == ".":
sep += 1
path_rest = path[sep:]
else:
attr = path
path_rest = ""
# Retrieve the value for `attr`.
# Match the value error exception with that of "path_resolve"
# to simplify exception handling for the caller.
value = getattr(self, attr, _sentinal)
if value is _sentinal:
raise ValueError("Path could not be resolved: %r" % attr)
if value is None:
return value
# Resolve the rest of the path if necessary.
if path_rest:
path_resolve_fn = getattr(value, "path_resolve", None)
if path_resolve_fn is None:
raise ValueError("Path %s resolves to a non RNA value" % attr)
return path_resolve_fn(path_rest, coerce)
return value
def copy(self):
from types import BuiltinMethodType
new_context = {}