From 7bb8eeb3a871eb1e72ee129f8ff441f2752b37be Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 6 Apr 2022 11:42:46 +1000 Subject: 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') --- release/scripts/modules/bpy_types.py | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'release/scripts/modules/bpy_types.py') 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 = {} -- cgit v1.2.3