Fix non UTF8 paths for Python functions which take path arguments

Use PyC_ParseUnicodeAsBytesAndSize parser instead of "s" / "z" type
specifier. This relates to #111033, resolving Python exceptions which
causes icons not to load (for e.g.).

Now bytes are also supported as path arguments.
This commit is contained in:
Campbell Barton
2023-08-11 14:59:55 +10:00
parent 2e286bcf8b
commit 8ce2ac0d9a
8 changed files with 154 additions and 87 deletions

View File

@@ -12,6 +12,9 @@
#define PY_SSIZE_T_CLEAN
#include "blf_py_api.h"
#include "../generic/py_capi_utils.h"
#include <Python.h>
#include "../../blenfont/BLF_api.h"
@@ -402,18 +405,24 @@ PyDoc_STRVAR(py_blf_load_doc,
" Load a new font.\n"
"\n"
" :arg filepath: the filepath of the font.\n"
" :type filepath: string\n"
" :type filepath: string or bytes\n"
" :return: the new font's fontid or -1 if there was an error.\n"
" :rtype: integer\n");
static PyObject *py_blf_load(PyObject * /*self*/, PyObject *args)
{
const char *filepath;
if (!PyArg_ParseTuple(args, "s:blf.load", &filepath)) {
PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
if (!PyArg_ParseTuple(args,
"O&" /* `filepath` */
":blf.load",
PyC_ParseUnicodeAsBytesAndSize,
&filepath_data))
{
return nullptr;
}
const int font_id = BLF_load(filepath_data.value);
Py_XDECREF(filepath_data.value_coerce);
return PyLong_FromLong(BLF_load(filepath));
return PyLong_FromLong(font_id);
}
PyDoc_STRVAR(py_blf_unload_doc,
@@ -422,16 +431,21 @@ PyDoc_STRVAR(py_blf_unload_doc,
" Unload an existing font.\n"
"\n"
" :arg filepath: the filepath of the font.\n"
" :type filepath: string\n");
" :type filepath: string or bytes\n");
static PyObject *py_blf_unload(PyObject * /*self*/, PyObject *args)
{
const char *filepath;
if (!PyArg_ParseTuple(args, "s:blf.unload", &filepath)) {
PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
if (!PyArg_ParseTuple(args,
"O&" /* `filepath` */
":blf.unload",
PyC_ParseUnicodeAsBytesAndSize,
&filepath_data))
{
return nullptr;
}
BLF_unload(filepath);
BLF_unload(filepath_data.value);
Py_XDECREF(filepath_data.value_coerce);
Py_RETURN_NONE;
}

View File

@@ -467,30 +467,8 @@ static PyObject *M_imbuf_new(PyObject * /*self*/, PyObject *args, PyObject *kw)
return Py_ImBuf_CreatePyObject(ibuf);
}
PyDoc_STRVAR(M_imbuf_load_doc,
".. function:: load(filepath)\n"
"\n"
" Load an image from a file.\n"
"\n"
" :arg filepath: the filepath of the image.\n"
" :type filepath: string\n"
" :return: the newly loaded image.\n"
" :rtype: :class:`ImBuf`\n");
static PyObject *M_imbuf_load(PyObject * /*self*/, PyObject *args, PyObject *kw)
static PyObject *imbuf_load_impl(const char *filepath)
{
const char *filepath;
static const char *_keywords[] = {"filepath", nullptr};
static _PyArg_Parser _parser = {
"s" /* `filepath` */
":load",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kw, &_parser, &filepath)) {
return nullptr;
}
const int file = BLI_open(filepath, O_BINARY | O_RDONLY, 0);
if (file == -1) {
PyErr_Format(PyExc_IOError, "load: %s, failed to open file '%s'", strerror(errno), filepath);
@@ -512,6 +490,48 @@ static PyObject *M_imbuf_load(PyObject * /*self*/, PyObject *args, PyObject *kw)
return Py_ImBuf_CreatePyObject(ibuf);
}
PyDoc_STRVAR(M_imbuf_load_doc,
".. function:: load(filepath)\n"
"\n"
" Load an image from a file.\n"
"\n"
" :arg filepath: the filepath of the image.\n"
" :type filepath: string or bytes\n"
" :return: the newly loaded image.\n"
" :rtype: :class:`ImBuf`\n");
static PyObject *M_imbuf_load(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
static const char *_keywords[] = {"filepath", nullptr};
static _PyArg_Parser _parser = {
"O&" /* `filepath` */
":load",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kw, &_parser, PyC_ParseUnicodeAsBytesAndSize, &filepath_data))
{
return nullptr;
}
PyObject *result = imbuf_load_impl(filepath_data.value);
Py_XDECREF(filepath_data.value_coerce);
return result;
}
static PyObject *imbuf_write_impl(ImBuf *ibuf, const char *filepath)
{
const bool ok = IMB_saveiff(ibuf, filepath, IB_rect);
if (ok == false) {
PyErr_Format(
PyExc_IOError, "write: Unable to write image file (%s) '%s'", strerror(errno), filepath);
return nullptr;
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(
M_imbuf_write_doc,
".. function:: write(image, filepath=image.filepath)\n"
@@ -521,37 +541,40 @@ PyDoc_STRVAR(
" :arg image: the image to write.\n"
" :type image: :class:`ImBuf`\n"
" :arg filepath: Optional filepath of the image (fallback to the images file path).\n"
" :type filepath: string\n");
" :type filepath: string or bytes\n");
static PyObject *M_imbuf_write(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
Py_ImBuf *py_imb;
const char *filepath = nullptr;
PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
static const char *_keywords[] = {"image", "filepath", nullptr};
static _PyArg_Parser _parser = {
"O!" /* `image` */
"|$" /* Optional keyword only arguments. */
"s" /* `filepath` */
"O&" /* `filepath` */
":write",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kw, &_parser, &Py_ImBuf_Type, &py_imb, &filepath)) {
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kw,
&_parser,
&Py_ImBuf_Type,
&py_imb,
PyC_ParseUnicodeAsBytesAndSize,
&filepath_data))
{
return nullptr;
}
const char *filepath = filepath_data.value;
if (filepath == nullptr) {
/* Argument omitted, use images path. */
filepath = py_imb->ibuf->filepath;
}
const bool ok = IMB_saveiff(py_imb->ibuf, filepath, IB_rect);
if (ok == false) {
PyErr_Format(
PyExc_IOError, "write: Unable to write image file (%s) '%s'", strerror(errno), filepath);
return nullptr;
}
Py_RETURN_NONE;
PyObject *result = imbuf_write_impl(py_imb->ibuf, filepath);
Py_XDECREF(filepath_data.value_coerce);
return result;
}
/** \} */

View File

@@ -221,27 +221,32 @@ static PyObject *bpy_user_resource(PyObject * /*self*/, PyObject *args, PyObject
{0, nullptr},
};
PyC_StringEnum type = {type_items};
const char *subdir = nullptr;
const char *path;
PyC_UnicodeAsBytesAndSize_Data subdir_data = {nullptr};
static const char *_keywords[] = {"type", "path", nullptr};
static _PyArg_Parser _parser = {
"O&" /* `type` */
"|$" /* Optional keyword only arguments. */
"s" /* `path` */
"O&" /* `path` */
":user_resource",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kw, &_parser, PyC_ParseStringEnum, &type, &subdir)) {
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kw,
&_parser,
PyC_ParseStringEnum,
&type,
PyC_ParseUnicodeAsBytesAndSize,
&subdir_data))
{
return nullptr;
}
/* same logic as BKE_appdir_folder_id_create(),
* but best leave it up to the script author to create */
path = BKE_appdir_folder_id_user_notest(type.value_found, subdir);
const char *path = BKE_appdir_folder_id_user_notest(type.value_found, subdir_data.value);
Py_XDECREF(subdir_data.value_coerce);
return PyC_UnicodeFromBytes(path ? path : "");
}
@@ -254,7 +259,7 @@ PyDoc_STRVAR(bpy_system_resource_doc,
" :arg type: string in ['DATAFILES', 'SCRIPTS', 'PYTHON'].\n"
" :type type: string\n"
" :arg path: Optional subdirectory.\n"
" :type path: string\n");
" :type path: string or bytes\n");
static PyObject *bpy_system_resource(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
const PyC_StringEnumItems type_items[] = {
@@ -265,24 +270,30 @@ static PyObject *bpy_system_resource(PyObject * /*self*/, PyObject *args, PyObje
};
PyC_StringEnum type = {type_items};
const char *subdir = nullptr;
const char *path;
PyC_UnicodeAsBytesAndSize_Data subdir_data = {nullptr};
static const char *_keywords[] = {"type", "path", nullptr};
static _PyArg_Parser _parser = {
"O&" /* `type` */
"|$" /* Optional keyword only arguments. */
"s" /* `path` */
"O&" /* `path` */
":system_resource",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kw, &_parser, PyC_ParseStringEnum, &type, &subdir)) {
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kw,
&_parser,
PyC_ParseStringEnum,
&type,
PyC_ParseUnicodeAsBytesAndSize,
&subdir_data))
{
return nullptr;
}
path = BKE_appdir_folder_id(type.value_found, subdir);
const char *path = BKE_appdir_folder_id(type.value_found, subdir_data.value);
Py_XDECREF(subdir_data.value_coerce);
return PyC_UnicodeFromBytes(path ? path : "");
}

View File

@@ -92,28 +92,31 @@ PyDoc_STRVAR(bpy_app_icons_new_triangles_from_file_doc,
" Create a new icon from triangle geometry.\n"
"\n"
" :arg filepath: File path.\n"
" :type filepath: string.\n"
" :type filepath: string or bytes.\n"
" :return: Unique icon value (pass to interface ``icon_value`` argument).\n"
" :rtype: int\n");
static PyObject *bpy_app_icons_new_triangles_from_file(PyObject * /*self*/,
PyObject *args,
PyObject *kw)
{
/* bytes */
char *filepath;
PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
static const char *_keywords[] = {"filepath", nullptr};
static _PyArg_Parser _parser = {
"s" /* `filepath` */
"O&" /* `filepath` */
":new_triangles_from_file",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kw, &_parser, &filepath)) {
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kw, &_parser, PyC_ParseUnicodeAsBytesAndSize, &filepath_data))
{
return nullptr;
}
Icon_Geom *geom = BKE_icon_geom_from_file(filepath);
Icon_Geom *geom = BKE_icon_geom_from_file(filepath_data.value);
Py_XDECREF(filepath_data.value_coerce);
if (geom == nullptr) {
PyErr_SetString(PyExc_ValueError, "Unable to load from file");
return nullptr;

View File

@@ -165,7 +165,7 @@ PyDoc_STRVAR(
" Each object has attributes matching bpy.data which are lists of strings to be linked.\n"
"\n"
" :arg filepath: The path to a blend file.\n"
" :type filepath: string\n"
" :type filepath: string or bytes\n"
" :arg link: When False reference to the original file is lost.\n"
" :type link: bool\n"
" :arg relative: When True the path is stored relative to the open blend file.\n"
@@ -186,7 +186,7 @@ static PyObject *bpy_lib_load(BPy_PropertyRNA *self, PyObject *args, PyObject *k
Main *bmain_base = CTX_data_main(BPY_context_get());
Main *bmain = static_cast<Main *>(self->ptr.data); /* Typically #G_MAIN */
BPy_Library *ret;
const char *filepath = nullptr;
PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
bool is_rel = false, is_link = false, use_assets_only = false;
bool create_liboverrides = false, reuse_liboverrides = false,
create_liboverrides_runtime = false;
@@ -202,7 +202,7 @@ static PyObject *bpy_lib_load(BPy_PropertyRNA *self, PyObject *args, PyObject *k
nullptr,
};
static _PyArg_Parser _parser = {
"s" /* `filepath` */
"O&" /* `filepath` */
/* Optional keyword only arguments. */
"|$"
"O&" /* `link` */
@@ -218,7 +218,8 @@ static PyObject *bpy_lib_load(BPy_PropertyRNA *self, PyObject *args, PyObject *k
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kw,
&_parser,
&filepath,
PyC_ParseUnicodeAsBytesAndSize,
&filepath_data,
PyC_ParseBool,
&is_link,
PyC_ParseBool,
@@ -252,8 +253,10 @@ static PyObject *bpy_lib_load(BPy_PropertyRNA *self, PyObject *args, PyObject *k
ret = PyObject_New(BPy_Library, &bpy_lib_Type);
STRNCPY(ret->relpath, filepath);
STRNCPY(ret->abspath, filepath);
STRNCPY(ret->relpath, filepath_data.value);
Py_XDECREF(filepath_data.value_coerce);
STRNCPY(ret->abspath, ret->relpath);
BLI_path_abs(ret->abspath, BKE_main_blendfile_path(bmain));
ret->bmain = bmain;

View File

@@ -44,7 +44,7 @@ PyDoc_STRVAR(
" Indirectly referenced data-blocks will be expanded and written too.\n"
"\n"
" :arg filepath: The path to write the blend-file.\n"
" :type filepath: string\n"
" :type filepath: string or bytes\n"
" :arg datablocks: set of data-blocks (:class:`bpy.types.ID` instances).\n"
" :type datablocks: set\n"
" :arg path_remap: Optionally remap paths when writing the file:\n"
@@ -62,7 +62,7 @@ PyDoc_STRVAR(
static PyObject *bpy_lib_write(BPy_PropertyRNA *self, PyObject *args, PyObject *kw)
{
/* args */
const char *filepath;
PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
char filepath_abs[FILE_MAX];
PyObject *datablocks = nullptr;
@@ -86,7 +86,7 @@ static PyObject *bpy_lib_write(BPy_PropertyRNA *self, PyObject *args, PyObject *
nullptr,
};
static _PyArg_Parser _parser = {
"s" /* `filepath` */
"O&" /* `filepath` */
"O!" /* `datablocks` */
"|$" /* Optional keyword only arguments. */
"O&" /* `path_remap` */
@@ -99,7 +99,8 @@ static PyObject *bpy_lib_write(BPy_PropertyRNA *self, PyObject *args, PyObject *
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kw,
&_parser,
&filepath,
PyC_ParseUnicodeAsBytesAndSize,
&filepath_data,
&PySet_Type,
&datablocks,
PyC_ParseStringEnum,
@@ -119,7 +120,9 @@ static PyObject *bpy_lib_write(BPy_PropertyRNA *self, PyObject *args, PyObject *
write_flags |= G_FILE_COMPRESS;
}
STRNCPY(filepath_abs, filepath);
STRNCPY(filepath_abs, filepath_data.value);
Py_XDECREF(filepath_data.value_coerce);
BLI_path_abs(filepath_abs, BKE_main_blendfile_path_from_global());
BKE_blendfile_write_partial_begin(bmain_src);

View File

@@ -16,6 +16,8 @@
#include <Python.h>
#include <cstddef>
#include "../generic/py_capi_utils.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
@@ -130,30 +132,33 @@ PyDoc_STRVAR(bpy_rna_data_context_load_doc,
"\n"
" :arg filepath: The file path for the newly temporary data. "
"When None, the path of the currently open file is used.\n"
" :type filepath: str or NoneType\n"
" :type filepath: str, bytes or NoneType\n"
"\n"
" :return: Blend file data which is freed once the context exists.\n"
" :rtype: :class:`bpy.types.BlendData`\n");
static PyObject *bpy_rna_data_temp_data(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
BPy_DataContext *ret;
const char *filepath = nullptr;
static const char *_keywords[] = {"filepath", nullptr};
static _PyArg_Parser _parser = {
"|$" /* Optional keyword only arguments. */
"z" /* `filepath` */
"O&" /* `filepath` */
":temp_data",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kw, &_parser, &filepath)) {
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kw, &_parser, PyC_ParseUnicodeAsBytesAndSize_OrNone, &filepath_data))
{
return nullptr;
}
ret = PyObject_GC_New(BPy_DataContext, &bpy_rna_data_context_Type);
STRNCPY(ret->filepath, filepath ? filepath : G_MAIN->filepath);
STRNCPY(ret->filepath, filepath_data.value ? filepath_data.value : G_MAIN->filepath);
Py_XDECREF(filepath_data.value_coerce);
return (PyObject *)ret;
}

View File

@@ -78,7 +78,7 @@ PyDoc_STRVAR(
" :arg name: The name (unique id) identifying the preview.\n"
" :type name: string\n"
" :arg filepath: The file path to generate the preview from.\n"
" :type filepath: string\n"
" :type filepath: string or bytes\n"
" :arg filetype: The type of file, needed to generate the preview in [" STR_SOURCE_TYPES
"].\n"
" :type filetype: string\n"
@@ -92,7 +92,8 @@ PyDoc_STRVAR(
" :raises KeyError: if ``name`` already exists.");
static PyObject *bpy_utils_previews_load(PyObject * /*self*/, PyObject *args)
{
char *name, *filepath;
char *name;
PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
const PyC_StringEnumItems path_type_items[] = {
{THB_SOURCE_IMAGE, "IMAGE"},
{THB_SOURCE_MOVIE, "MOVIE"},
@@ -110,13 +111,14 @@ static PyObject *bpy_utils_previews_load(PyObject * /*self*/, PyObject *args)
if (!PyArg_ParseTuple(args,
"s" /* `name` */
"s" /* `filepath` */
"O&" /* `filepath` */
"O&" /* `filetype` */
"|" /* Optional arguments. */
"p" /* `force_reload` */
":load",
&name,
&filepath,
PyC_ParseUnicodeAsBytesAndSize,
&filepath_data,
PyC_ParseStringEnum,
&path_type,
&force_reload))
@@ -125,7 +127,10 @@ static PyObject *bpy_utils_previews_load(PyObject * /*self*/, PyObject *args)
}
PreviewImage *prv = BKE_previewimg_cached_thumbnail_read(
name, filepath, path_type.value_found, force_reload);
name, filepath_data.value, path_type.value_found, force_reload);
Py_XDECREF(filepath_data.value_coerce);
PointerRNA ptr;
RNA_pointer_create(nullptr, &RNA_ImagePreview, prv, &ptr);
return pyrna_struct_CreatePyObject(&ptr);