diff options
author | Campbell Barton <campbell@blender.org> | 2022-04-06 04:42:46 +0300 |
---|---|---|
committer | Campbell Barton <campbell@blender.org> | 2022-04-06 04:48:51 +0300 |
commit | 7bb8eeb3a871eb1e72ee129f8ff441f2752b37be (patch) | |
tree | 8b94749024d4ed1ffa8d298075ad98dc186301a4 /release | |
parent | 5d31252c762b35a5af3d668f45dccf5bb397627e (diff) |
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')
Diffstat (limited to 'release')
-rw-r--r-- | release/scripts/modules/bpy_types.py | 53 |
1 files changed, 53 insertions, 0 deletions
diff --git a/release/scripts/modules/bpy_types.py b/release/scripts/modules/bpy_types.py index e0e20d0f8c9..45fd6d29dfa 100644 --- a/release/scripts/modules/bpy_types.py +++ b/release/scripts/modules/bpy_types.py @@ -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 = {} |