Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/python/py_imagespec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ declare_imagespec(py_module& m)
int chan) { return spec.channelformat(chan); })
.def("channel_name",
[](const ImageSpec& spec, int chan) {
return oiio_py::str(spec.channel_name(chan));
return py_str_escaped(spec.channel_name(chan));
})
.def("channelindex",
[](const ImageSpec& spec, const std::string& name) {
Expand Down Expand Up @@ -200,7 +200,7 @@ declare_imagespec(py_module& m)
"get_string_attribute",
[](const ImageSpec& spec, const std::string& name,
const std::string& def) {
return oiio_py::str(spec.get_string_attribute(name, def));
return py_str_escaped(spec.get_string_attribute(name, def));
},
"name"_a, "defaultval"_a = "")
.def(
Expand Down Expand Up @@ -233,7 +233,7 @@ declare_imagespec(py_module& m)
.def_static(
"metadata_val",
[](const ParamValue& p, bool human) {
return oiio_py::str(ImageSpec::metadata_val(p, human));
return py_str_escaped(ImageSpec::metadata_val(p, human));
},
"param"_a, "human"_a = false)
.def(
Expand All @@ -250,11 +250,11 @@ declare_imagespec(py_module& m)
verb = ImageSpec::SerialDetailed;
else if (Strutil::iequals(verbose, "detailedhuman"))
verb = ImageSpec::SerialDetailedHuman;
return oiio_py::str(spec.serialize(fmt, verb));
return py_str_escaped(spec.serialize(fmt, verb));
},
"format"_a = "text", "verbose"_a = "detailed")
.def("to_xml",
[](const ImageSpec& spec) { return oiio_py::str(spec.to_xml()); })
[](const ImageSpec& spec) { return py_str_escaped(spec.to_xml()); })
.def("from_xml",
[](ImageSpec& self, const std::string& xml) {
self.from_xml(xml.c_str());
Expand Down
2 changes: 1 addition & 1 deletion src/python/py_oiio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ declare_global_attribute_functions(py_module& m)
m.def(
"get_string_attribute",
[](const std::string& name, const std::string& def) {
return oiio_py::str(OIIO::get_string_attribute(name, def));
return py_str_escaped(OIIO::get_string_attribute(name, def));
},
"name"_a, "defaultval"_a = "");
m.def("getattribute", &oiio_getattribute_typed, "name"_a,
Expand Down
36 changes: 36 additions & 0 deletions src/python/py_oiio.h
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,33 @@ py_to_stdvector(std::vector<T>& vals, const py::object& obj)
}


// Make a UTF-8 `py::str` from a string-like C++ type, using `'surrogateescape'`
// error handling for any non-UTF-8 byte sequences. Compared to calling
// `py::str` directly, this avoids raising a `UnicodeDecodeError` if the input
// is not valid UTF-8.
Comment thread
lgritz marked this conversation as resolved.
//
// It is important to do this for string return values that come from
// "untrusted" sources, such as being read from other files, so that we ensure
// that we are returning valid UTF-8 str's to the calling Python so that
// exceptions are not raised.
inline py::str
py_str_escaped(string_view value)
{
py::handle py_str = PyUnicode_DecodeUTF8(value.data(), value.length(),
"surrogateescape");
#if defined(OIIO_PY_BACKEND_NANOBIND)
if (!py_str) {
py::raise_python_error();
}
return py::steal<py::str>(py_str);
#else
if (!py_str) {
throw py::error_already_set();
}
return py::reinterpret_steal<py::str>(py_str);
#endif
Comment on lines +486 to +496

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with a single function definition containing implementation details for both backends, but it would be easy enough to just have two definitions in the oiio_py namespace with the Python API call duplicated if you prefer. Or we could split the difference and alias the exception type and handle stealing functions in py_backend.h in order to collapse this into one code path.

}


template<typename T>
inline py::tuple
Expand All @@ -477,6 +504,9 @@ C_to_tuple(cspan<T> vals)
return oiio_py::make_tuple(vals.size(), [&](size_t i) {
if constexpr (std::is_same_v<T, half>) {
return py::cast(static_cast<float>(vals[i]));
} else if constexpr (std::is_same_v<std::decay_t<T>, std::string>
|| std::is_same_v<std::decay_t<T>, const char*>) {
return py_str_escaped(vals[i]);
} else {
return py::cast(vals[i]);
}
Expand All @@ -500,6 +530,9 @@ C_to_tuple(const T* vals, size_t size)
return oiio_py::make_tuple(size, [&](size_t i) {
if constexpr (std::is_same_v<T, half>) {
return py::cast(static_cast<float>(vals[i]));
} else if constexpr (std::is_same_v<std::decay_t<T>, std::string>
|| std::is_same_v<std::decay_t<T>, const char*>) {
return py_str_escaped(vals[i]);
} else {
return py::cast(vals[i]);
}
Expand Down Expand Up @@ -540,6 +573,9 @@ C_to_val_or_tuple(const T* vals, TypeDesc type, int nvalues = 1)
if (n == 1 && !type.arraylen) {
if constexpr (std::is_same_v<T, half>) {
return py::cast(static_cast<float>(vals[0]));
} else if constexpr (std::is_same_v<std::decay_t<T>, std::string>
|| std::is_same_v<std::decay_t<T>, const char*>) {
return py_str_escaped(vals[0]);
} else {
return py::cast(vals[0]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/python/py_paramvalue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ declare_paramvalue(py_module& m)
py::class_<ParamValue>(m, "ParamValue")
.OIIO_PY_PROP_RO("name",
[](const ParamValue& self) {
return oiio_py::str(self.name().string());
return py_str_escaped(self.name().string());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flagging that I've also wrapped the ParamValue.name accessor, because there's nothing that normalizes/sanitizes attribute names in the C++ API.

})
.OIIO_PY_PROP_RO("type",
[](const ParamValue& self) { return self.type(); })
Expand Down
Loading