mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
gh-98393: os module reject bytes-like, only accept bytes (#98394)
The os module and the PyUnicode_FSDecoder() function no longer accept bytes-like paths, like bytearray and memoryview types: only the exact bytes type is accepted for bytes strings.
This commit is contained in:
parent
9da5215000
commit
db03c8066a
9 changed files with 48 additions and 89 deletions
|
@ -515,6 +515,11 @@ Changes in the Python API
|
||||||
in Python 3.9.
|
in Python 3.9.
|
||||||
(Contributed by Victor Stinner in :gh:`94352`.)
|
(Contributed by Victor Stinner in :gh:`94352`.)
|
||||||
|
|
||||||
|
* The :mod:`os` module no longer accepts bytes-like paths, like
|
||||||
|
:class:`bytearray` and :class:`memoryview` types: only the exact
|
||||||
|
:class:`bytes` type is accepted for bytes strings.
|
||||||
|
(Contributed by Victor Stinner in :gh:`98393`.)
|
||||||
|
|
||||||
|
|
||||||
Build Changes
|
Build Changes
|
||||||
=============
|
=============
|
||||||
|
@ -631,6 +636,11 @@ Porting to Python 3.12
|
||||||
to traverse and clear their instance's dictionaries.
|
to traverse and clear their instance's dictionaries.
|
||||||
To clear weakrefs, call :c:func:`PyObject_ClearWeakRefs`, as before.
|
To clear weakrefs, call :c:func:`PyObject_ClearWeakRefs`, as before.
|
||||||
|
|
||||||
|
* The :c:func:`PyUnicode_FSDecoder` function no longer accepts bytes-like
|
||||||
|
paths, like :class:`bytearray` and :class:`memoryview` types: only the exact
|
||||||
|
:class:`bytes` type is accepted for bytes strings.
|
||||||
|
(Contributed by Victor Stinner in :gh:`98393`.)
|
||||||
|
|
||||||
Deprecated
|
Deprecated
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|
|
@ -494,9 +494,8 @@ if 1:
|
||||||
code = compile('pass', filename, 'exec')
|
code = compile('pass', filename, 'exec')
|
||||||
self.assertEqual(code.co_filename, 'file.py')
|
self.assertEqual(code.co_filename, 'file.py')
|
||||||
for filename in bytearray(b'file.py'), memoryview(b'file.py'):
|
for filename in bytearray(b'file.py'), memoryview(b'file.py'):
|
||||||
with self.assertWarns(DeprecationWarning):
|
with self.assertRaises(TypeError):
|
||||||
code = compile('pass', filename, 'exec')
|
compile('pass', filename, 'exec')
|
||||||
self.assertEqual(code.co_filename, 'file.py')
|
|
||||||
self.assertRaises(TypeError, compile, 'pass', list(b'file.py'), 'exec')
|
self.assertRaises(TypeError, compile, 'pass', list(b'file.py'), 'exec')
|
||||||
|
|
||||||
@support.cpython_only
|
@support.cpython_only
|
||||||
|
|
|
@ -3860,11 +3860,11 @@ class OSErrorTests(unittest.TestCase):
|
||||||
|
|
||||||
for filenames, func, *func_args in funcs:
|
for filenames, func, *func_args in funcs:
|
||||||
for name in filenames:
|
for name in filenames:
|
||||||
try:
|
if not isinstance(name, (str, bytes)):
|
||||||
if isinstance(name, (str, bytes)):
|
with self.assertRaises(TypeError):
|
||||||
func(name, *func_args)
|
func(name, *func_args)
|
||||||
else:
|
else:
|
||||||
with self.assertWarnsRegex(DeprecationWarning, 'should be'):
|
try:
|
||||||
func(name, *func_args)
|
func(name, *func_args)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
self.assertIs(err.filename, name, str(func))
|
self.assertIs(err.filename, name, str(func))
|
||||||
|
@ -4350,16 +4350,8 @@ class TestScandir(unittest.TestCase):
|
||||||
|
|
||||||
for cls in bytearray, memoryview:
|
for cls in bytearray, memoryview:
|
||||||
path_bytes = cls(os.fsencode(self.path))
|
path_bytes = cls(os.fsencode(self.path))
|
||||||
with self.assertWarns(DeprecationWarning):
|
with self.assertRaises(TypeError):
|
||||||
entries = list(os.scandir(path_bytes))
|
list(os.scandir(path_bytes))
|
||||||
self.assertEqual(len(entries), 1, entries)
|
|
||||||
entry = entries[0]
|
|
||||||
|
|
||||||
self.assertEqual(entry.name, b'file.txt')
|
|
||||||
self.assertEqual(entry.path,
|
|
||||||
os.fsencode(os.path.join(self.path, 'file.txt')))
|
|
||||||
self.assertIs(type(entry.name), bytes)
|
|
||||||
self.assertIs(type(entry.path), bytes)
|
|
||||||
|
|
||||||
@unittest.skipUnless(os.listdir in os.supports_fd,
|
@unittest.skipUnless(os.listdir in os.supports_fd,
|
||||||
'fd support for listdir required for this test.')
|
'fd support for listdir required for this test.')
|
||||||
|
|
|
@ -634,7 +634,7 @@ class PosixTester(unittest.TestCase):
|
||||||
self.assertTrue(posix.stat(os_helper.TESTFN))
|
self.assertTrue(posix.stat(os_helper.TESTFN))
|
||||||
self.assertTrue(posix.stat(os.fsencode(os_helper.TESTFN)))
|
self.assertTrue(posix.stat(os.fsencode(os_helper.TESTFN)))
|
||||||
|
|
||||||
self.assertWarnsRegex(DeprecationWarning,
|
self.assertRaisesRegex(TypeError,
|
||||||
'should be string, bytes, os.PathLike or integer, not',
|
'should be string, bytes, os.PathLike or integer, not',
|
||||||
posix.stat, bytearray(os.fsencode(os_helper.TESTFN)))
|
posix.stat, bytearray(os.fsencode(os_helper.TESTFN)))
|
||||||
self.assertRaisesRegex(TypeError,
|
self.assertRaisesRegex(TypeError,
|
||||||
|
@ -841,11 +841,8 @@ class PosixTester(unittest.TestCase):
|
||||||
|
|
||||||
def test_listdir_bytes_like(self):
|
def test_listdir_bytes_like(self):
|
||||||
for cls in bytearray, memoryview:
|
for cls in bytearray, memoryview:
|
||||||
with self.assertWarns(DeprecationWarning):
|
with self.assertRaises(TypeError):
|
||||||
names = posix.listdir(cls(b'.'))
|
posix.listdir(cls(b'.'))
|
||||||
self.assertIn(os.fsencode(os_helper.TESTFN), names)
|
|
||||||
for name in names:
|
|
||||||
self.assertIs(type(name), bytes)
|
|
||||||
|
|
||||||
@unittest.skipUnless(posix.listdir in os.supports_fd,
|
@unittest.skipUnless(posix.listdir in os.supports_fd,
|
||||||
"test needs fd support for posix.listdir()")
|
"test needs fd support for posix.listdir()")
|
||||||
|
|
|
@ -222,10 +222,9 @@ class SymtableTest(unittest.TestCase):
|
||||||
checkfilename("def f(x): foo)(", 14) # parse-time
|
checkfilename("def f(x): foo)(", 14) # parse-time
|
||||||
checkfilename("def f(x): global x", 11) # symtable-build-time
|
checkfilename("def f(x): global x", 11) # symtable-build-time
|
||||||
symtable.symtable("pass", b"spam", "exec")
|
symtable.symtable("pass", b"spam", "exec")
|
||||||
with self.assertWarns(DeprecationWarning), \
|
with self.assertRaises(TypeError):
|
||||||
self.assertRaises(TypeError):
|
|
||||||
symtable.symtable("pass", bytearray(b"spam"), "exec")
|
symtable.symtable("pass", bytearray(b"spam"), "exec")
|
||||||
with self.assertWarns(DeprecationWarning):
|
with self.assertRaises(TypeError):
|
||||||
symtable.symtable("pass", memoryview(b"spam"), "exec")
|
symtable.symtable("pass", memoryview(b"spam"), "exec")
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
symtable.symtable("pass", list(b"spam"), "exec")
|
symtable.symtable("pass", list(b"spam"), "exec")
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
The :c:func:`PyUnicode_FSDecoder` function no longer accepts bytes-like
|
||||||
|
paths, like :class:`bytearray` and :class:`memoryview` types: only the exact
|
||||||
|
:class:`bytes` type is accepted for bytes strings. Patch by Victor Stinner.
|
|
@ -0,0 +1,3 @@
|
||||||
|
The :mod:`os` module no longer accepts bytes-like paths, like
|
||||||
|
:class:`bytearray` and :class:`memoryview` types: only the exact
|
||||||
|
:class:`bytes` type is accepted for bytes strings. Patch by Victor Stinner.
|
|
@ -1120,7 +1120,7 @@ path_converter(PyObject *o, void *p)
|
||||||
path_t *path = (path_t *)p;
|
path_t *path = (path_t *)p;
|
||||||
PyObject *bytes = NULL;
|
PyObject *bytes = NULL;
|
||||||
Py_ssize_t length = 0;
|
Py_ssize_t length = 0;
|
||||||
int is_index, is_buffer, is_bytes, is_unicode;
|
int is_index, is_bytes, is_unicode;
|
||||||
const char *narrow;
|
const char *narrow;
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
PyObject *wo = NULL;
|
PyObject *wo = NULL;
|
||||||
|
@ -1158,11 +1158,10 @@ path_converter(PyObject *o, void *p)
|
||||||
/* Only call this here so that we don't treat the return value of
|
/* Only call this here so that we don't treat the return value of
|
||||||
os.fspath() as an fd or buffer. */
|
os.fspath() as an fd or buffer. */
|
||||||
is_index = path->allow_fd && PyIndex_Check(o);
|
is_index = path->allow_fd && PyIndex_Check(o);
|
||||||
is_buffer = PyObject_CheckBuffer(o);
|
|
||||||
is_bytes = PyBytes_Check(o);
|
is_bytes = PyBytes_Check(o);
|
||||||
is_unicode = PyUnicode_Check(o);
|
is_unicode = PyUnicode_Check(o);
|
||||||
|
|
||||||
if (!is_index && !is_buffer && !is_unicode && !is_bytes) {
|
if (!is_index && !is_unicode && !is_bytes) {
|
||||||
/* Inline PyOS_FSPath() for better error messages. */
|
/* Inline PyOS_FSPath() for better error messages. */
|
||||||
PyObject *func, *res;
|
PyObject *func, *res;
|
||||||
|
|
||||||
|
@ -1225,27 +1224,6 @@ path_converter(PyObject *o, void *p)
|
||||||
bytes = o;
|
bytes = o;
|
||||||
Py_INCREF(bytes);
|
Py_INCREF(bytes);
|
||||||
}
|
}
|
||||||
else if (is_buffer) {
|
|
||||||
/* XXX Replace PyObject_CheckBuffer with PyBytes_Check in other code
|
|
||||||
after removing support of non-bytes buffer objects. */
|
|
||||||
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
|
|
||||||
"%s%s%s should be %s, not %.200s",
|
|
||||||
path->function_name ? path->function_name : "",
|
|
||||||
path->function_name ? ": " : "",
|
|
||||||
path->argument_name ? path->argument_name : "path",
|
|
||||||
path->allow_fd && path->nullable ? "string, bytes, os.PathLike, "
|
|
||||||
"integer or None" :
|
|
||||||
path->allow_fd ? "string, bytes, os.PathLike or integer" :
|
|
||||||
path->nullable ? "string, bytes, os.PathLike or None" :
|
|
||||||
"string, bytes or os.PathLike",
|
|
||||||
_PyType_Name(Py_TYPE(o)))) {
|
|
||||||
goto error_exit;
|
|
||||||
}
|
|
||||||
bytes = PyBytes_FromObject(o);
|
|
||||||
if (!bytes) {
|
|
||||||
goto error_exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (is_index) {
|
else if (is_index) {
|
||||||
if (!_fd_converter(o, &path->fd)) {
|
if (!_fd_converter(o, &path->fd)) {
|
||||||
goto error_exit;
|
goto error_exit;
|
||||||
|
@ -4126,8 +4104,8 @@ _posix_listdir(path_t *path, PyObject *list)
|
||||||
const char *name;
|
const char *name;
|
||||||
if (path->narrow) {
|
if (path->narrow) {
|
||||||
name = path->narrow;
|
name = path->narrow;
|
||||||
/* only return bytes if they specified a bytes-like object */
|
/* only return bytes if they specified a bytes object */
|
||||||
return_str = !PyObject_CheckBuffer(path->object);
|
return_str = !PyBytes_Check(path->object);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
name = ".";
|
name = ".";
|
||||||
|
@ -14049,7 +14027,7 @@ DirEntry_from_posix_info(PyObject *module, path_t *path, const char *name,
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!path->narrow || !PyObject_CheckBuffer(path->object)) {
|
if (!path->narrow || !PyBytes_Check(path->object)) {
|
||||||
entry->name = PyUnicode_DecodeFSDefaultAndSize(name, name_len);
|
entry->name = PyUnicode_DecodeFSDefaultAndSize(name, name_len);
|
||||||
if (joined_path)
|
if (joined_path)
|
||||||
entry->path = PyUnicode_DecodeFSDefault(joined_path);
|
entry->path = PyUnicode_DecodeFSDefault(joined_path);
|
||||||
|
|
|
@ -3619,48 +3619,25 @@ PyUnicode_FSConverter(PyObject* arg, void* addr)
|
||||||
int
|
int
|
||||||
PyUnicode_FSDecoder(PyObject* arg, void* addr)
|
PyUnicode_FSDecoder(PyObject* arg, void* addr)
|
||||||
{
|
{
|
||||||
int is_buffer = 0;
|
|
||||||
PyObject *path = NULL;
|
|
||||||
PyObject *output = NULL;
|
|
||||||
if (arg == NULL) {
|
if (arg == NULL) {
|
||||||
Py_DECREF(*(PyObject**)addr);
|
Py_DECREF(*(PyObject**)addr);
|
||||||
*(PyObject**)addr = NULL;
|
*(PyObject**)addr = NULL;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_buffer = PyObject_CheckBuffer(arg);
|
PyObject *path = PyOS_FSPath(arg);
|
||||||
if (!is_buffer) {
|
|
||||||
path = PyOS_FSPath(arg);
|
|
||||||
if (path == NULL) {
|
if (path == NULL) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
|
||||||
path = arg;
|
|
||||||
Py_INCREF(arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
PyObject *output = NULL;
|
||||||
if (PyUnicode_Check(path)) {
|
if (PyUnicode_Check(path)) {
|
||||||
output = path;
|
output = path;
|
||||||
}
|
}
|
||||||
else if (PyBytes_Check(path) || is_buffer) {
|
else if (PyBytes_Check(path)) {
|
||||||
PyObject *path_bytes = NULL;
|
output = PyUnicode_DecodeFSDefaultAndSize(PyBytes_AS_STRING(path),
|
||||||
|
PyBytes_GET_SIZE(path));
|
||||||
if (!PyBytes_Check(path) &&
|
|
||||||
PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
|
|
||||||
"path should be string, bytes, or os.PathLike, not %.200s",
|
|
||||||
Py_TYPE(arg)->tp_name)) {
|
|
||||||
Py_DECREF(path);
|
Py_DECREF(path);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
path_bytes = PyBytes_FromObject(path);
|
|
||||||
Py_DECREF(path);
|
|
||||||
if (!path_bytes) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
output = PyUnicode_DecodeFSDefaultAndSize(PyBytes_AS_STRING(path_bytes),
|
|
||||||
PyBytes_GET_SIZE(path_bytes));
|
|
||||||
Py_DECREF(path_bytes);
|
|
||||||
if (!output) {
|
if (!output) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -3672,6 +3649,7 @@ PyUnicode_FSDecoder(PyObject* arg, void* addr)
|
||||||
Py_DECREF(path);
|
Py_DECREF(path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (findchar(PyUnicode_DATA(output), PyUnicode_KIND(output),
|
if (findchar(PyUnicode_DATA(output), PyUnicode_KIND(output),
|
||||||
PyUnicode_GET_LENGTH(output), 0, 1) >= 0) {
|
PyUnicode_GET_LENGTH(output), 0, 1) >= 0) {
|
||||||
PyErr_SetString(PyExc_ValueError, "embedded null character");
|
PyErr_SetString(PyExc_ValueError, "embedded null character");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue