Skip to content

Commit 6f600ff

Browse files
bpo-32922: dbm.open() now encodes filename with the filesystem encoding. (GH-5832)
1 parent 973cae0 commit 6f600ff

File tree

8 files changed

+134
-19
lines changed

8 files changed

+134
-19
lines changed

‎Lib/test/test_dbm_dumb.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,21 @@ def test_readonly_files(self):
281281
self.assertEqual(sorted(f.keys()), sorted(self._dict))
282282
f.close() # don't write
283283

284+
@unittest.skipUnless(support.TESTFN_NONASCII,
285+
'requires OS support of non-ASCII encodings')
286+
def test_nonascii_filename(self):
287+
filename = support.TESTFN_NONASCII
288+
for suffix in ['.dir', '.dat', '.bak']:
289+
self.addCleanup(support.unlink, filename + suffix)
290+
with dumbdbm.open(filename, 'c') as db:
291+
db[b'key'] = b'value'
292+
self.assertTrue(os.path.exists(filename + '.dat'))
293+
self.assertTrue(os.path.exists(filename + '.dir'))
294+
with dumbdbm.open(filename, 'r') as db:
295+
self.assertEqual(list(db.keys()), [b'key'])
296+
self.assertTrue(b'key' in db)
297+
self.assertEqual(db[b'key'], b'value')
298+
284299
def tearDown(self):
285300
_delete_files()
286301

‎Lib/test/test_dbm_gnu.py‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
gdbm = support.import_module("dbm.gnu") #skip if not supported
33
import unittest
44
import os
5-
from test.support import TESTFN, unlink
5+
from test.support import TESTFN, TESTFN_NONASCII, unlink
66

77

88
filename = TESTFN
@@ -93,5 +93,39 @@ def test_context_manager(self):
9393
self.assertEqual(str(cm.exception),
9494
"GDBM object has already been closed")
9595

96+
def test_bytes(self):
97+
with gdbm.open(filename, 'c') as db:
98+
db[b'bytes key \xbd'] = b'bytes value \xbd'
99+
with gdbm.open(filename, 'r') as db:
100+
self.assertEqual(list(db.keys()), [b'bytes key \xbd'])
101+
self.assertTrue(b'bytes key \xbd' in db)
102+
self.assertEqual(db[b'bytes key \xbd'], b'bytes value \xbd')
103+
104+
def test_unicode(self):
105+
with gdbm.open(filename, 'c') as db:
106+
db['Unicode key \U0001f40d'] = 'Unicode value \U0001f40d'
107+
with gdbm.open(filename, 'r') as db:
108+
self.assertEqual(list(db.keys()), ['Unicode key \U0001f40d'.encode()])
109+
self.assertTrue('Unicode key \U0001f40d'.encode() in db)
110+
self.assertTrue('Unicode key \U0001f40d' in db)
111+
self.assertEqual(db['Unicode key \U0001f40d'.encode()],
112+
'Unicode value \U0001f40d'.encode())
113+
self.assertEqual(db['Unicode key \U0001f40d'],
114+
'Unicode value \U0001f40d'.encode())
115+
116+
@unittest.skipUnless(TESTFN_NONASCII,
117+
'requires OS support of non-ASCII encodings')
118+
def test_nonascii_filename(self):
119+
filename = TESTFN_NONASCII
120+
self.addCleanup(unlink, filename)
121+
with gdbm.open(filename, 'c') as db:
122+
db[b'key'] = b'value'
123+
self.assertTrue(os.path.exists(filename))
124+
with gdbm.open(filename, 'r') as db:
125+
self.assertEqual(list(db.keys()), [b'key'])
126+
self.assertTrue(b'key' in db)
127+
self.assertEqual(db[b'key'], b'value')
128+
129+
96130
if __name__ == '__main__':
97131
unittest.main()

‎Lib/test/test_dbm_ndbm.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from test import support
22
support.import_module("dbm.ndbm") #skip if not supported
3+
import os
34
import unittest
45
import dbm.ndbm
56
from dbm.ndbm import error
@@ -47,6 +48,42 @@ def test_context_manager(self):
4748
self.assertEqual(str(cm.exception),
4849
"DBM object has already been closed")
4950

51+
def test_bytes(self):
52+
with dbm.ndbm.open(self.filename, 'c') as db:
53+
db[b'bytes key \xbd'] = b'bytes value \xbd'
54+
with dbm.ndbm.open(self.filename, 'r') as db:
55+
self.assertEqual(list(db.keys()), [b'bytes key \xbd'])
56+
self.assertTrue(b'bytes key \xbd' in db)
57+
self.assertEqual(db[b'bytes key \xbd'], b'bytes value \xbd')
58+
59+
def test_unicode(self):
60+
with dbm.ndbm.open(self.filename, 'c') as db:
61+
db['Unicode key \U0001f40d'] = 'Unicode value \U0001f40d'
62+
with dbm.ndbm.open(self.filename, 'r') as db:
63+
self.assertEqual(list(db.keys()), ['Unicode key \U0001f40d'.encode()])
64+
self.assertTrue('Unicode key \U0001f40d'.encode() in db)
65+
self.assertTrue('Unicode key \U0001f40d' in db)
66+
self.assertEqual(db['Unicode key \U0001f40d'.encode()],
67+
'Unicode value \U0001f40d'.encode())
68+
self.assertEqual(db['Unicode key \U0001f40d'],
69+
'Unicode value \U0001f40d'.encode())
70+
71+
@unittest.skipUnless(support.TESTFN_NONASCII,
72+
'requires OS support of non-ASCII encodings')
73+
def test_nonascii_filename(self):
74+
filename = support.TESTFN_NONASCII
75+
for suffix in ['', '.pag', '.dir', '.db']:
76+
self.addCleanup(support.unlink, filename + suffix)
77+
with dbm.ndbm.open(filename, 'c') as db:
78+
db[b'key'] = b'value'
79+
self.assertTrue(any(os.path.exists(filename + suffix)
80+
for suffix in ['', '.pag', '.dir', '.db']))
81+
with dbm.ndbm.open(filename, 'r') as db:
82+
self.assertEqual(list(db.keys()), [b'key'])
83+
self.assertTrue(b'key' in db)
84+
self.assertEqual(db[b'key'], b'value')
85+
86+
5087

5188
if __name__ == '__main__':
5289
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dbm.open() now encodes filename with the filesystem encoding rather than
2+
default encoding.

‎Modules/_dbmmodule.c‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ static PyTypeObject Dbmtype = {
412412
413413
_dbm.open as dbmopen
414414
415-
filename: str
415+
filename: unicode
416416
The filename to open.
417417
418418
flags: str="r"
@@ -429,9 +429,9 @@ Return a database object.
429429
[clinic start generated code]*/
430430

431431
static PyObject *
432-
dbmopen_impl(PyObject *module, const char *filename, const char *flags,
432+
dbmopen_impl(PyObject *module, PyObject *filename, const char *flags,
433433
int mode)
434-
/*[clinic end generated code: output=5fade8cf16e0755f input=226334bade5764e6]*/
434+
/*[clinic end generated code: output=9527750f5df90764 input=376a9d903a50df59]*/
435435
{
436436
int iflags;
437437

@@ -450,7 +450,20 @@ dbmopen_impl(PyObject *module, const char *filename, const char *flags,
450450
"arg 2 to open should be 'r', 'w', 'c', or 'n'");
451451
return NULL;
452452
}
453-
return newdbmobject(filename, iflags, mode);
453+
454+
PyObject *filenamebytes = PyUnicode_EncodeFSDefault(filename);
455+
if (filenamebytes == NULL) {
456+
return NULL;
457+
}
458+
const char *name = PyBytes_AS_STRING(filenamebytes);
459+
if (strlen(name) != (size_t)PyBytes_GET_SIZE(filenamebytes)) {
460+
Py_DECREF(filenamebytes);
461+
PyErr_SetString(PyExc_ValueError, "embedded null character");
462+
return NULL;
463+
}
464+
PyObject *self = newdbmobject(name, iflags, mode);
465+
Py_DECREF(filenamebytes);
466+
return self;
454467
}
455468

456469
static PyMethodDef dbmmodule_methods[] = {

‎Modules/_gdbmmodule.c‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ static PyTypeObject Dbmtype = {
527527

528528
/*[clinic input]
529529
_gdbm.open as dbmopen
530-
filename as name: str
530+
filename: unicode
531531
flags: str="r"
532532
mode: int(py_default="0o666") = 0o666
533533
/
@@ -557,8 +557,9 @@ when the database has to be created. It defaults to octal 0o666.
557557
[clinic start generated code]*/
558558

559559
static PyObject *
560-
dbmopen_impl(PyObject *module, const char *name, const char *flags, int mode)
561-
/*[clinic end generated code: output=31aa1bafdf5da688 input=55563cd60e51984a]*/
560+
dbmopen_impl(PyObject *module, PyObject *filename, const char *flags,
561+
int mode)
562+
/*[clinic end generated code: output=9527750f5df90764 input=3be0b0875974b928]*/
562563
{
563564
int iflags;
564565

@@ -606,7 +607,19 @@ dbmopen_impl(PyObject *module, const char *name, const char *flags, int mode)
606607
}
607608
}
608609

609-
return newdbmobject(name, iflags, mode);
610+
PyObject *filenamebytes = PyUnicode_EncodeFSDefault(filename);
611+
if (filenamebytes == NULL) {
612+
return NULL;
613+
}
614+
const char *name = PyBytes_AS_STRING(filenamebytes);
615+
if (strlen(name) != (size_t)PyBytes_GET_SIZE(filenamebytes)) {
616+
Py_DECREF(filenamebytes);
617+
PyErr_SetString(PyExc_ValueError, "embedded null character");
618+
return NULL;
619+
}
620+
PyObject *self = newdbmobject(name, iflags, mode);
621+
Py_DECREF(filenamebytes);
622+
return self;
610623
}
611624

612625
static const char dbmmodule_open_flags[] = "rwcn"

‎Modules/clinic/_dbmmodule.c.h‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,18 +121,18 @@ PyDoc_STRVAR(dbmopen__doc__,
121121
{"open", (PyCFunction)dbmopen, METH_FASTCALL, dbmopen__doc__},
122122

123123
static PyObject *
124-
dbmopen_impl(PyObject *module, const char *filename, const char *flags,
124+
dbmopen_impl(PyObject *module, PyObject *filename, const char *flags,
125125
int mode);
126126

127127
static PyObject *
128128
dbmopen(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
129129
{
130130
PyObject *return_value = NULL;
131-
const char *filename;
131+
PyObject *filename;
132132
const char *flags = "r";
133133
int mode = 438;
134134

135-
if (!_PyArg_ParseStack(args, nargs, "s|si:open",
135+
if (!_PyArg_ParseStack(args, nargs, "U|si:open",
136136
&filename, &flags, &mode)) {
137137
goto exit;
138138
}
@@ -141,4 +141,4 @@ dbmopen(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
141141
exit:
142142
return return_value;
143143
}
144-
/*[clinic end generated code: output=8ce71abac849155f input=a9049054013a1b77]*/
144+
/*[clinic end generated code: output=5c858b4080a011a4 input=a9049054013a1b77]*/

‎Modules/clinic/_gdbmmodule.c.h‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -234,23 +234,24 @@ PyDoc_STRVAR(dbmopen__doc__,
234234
{"open", (PyCFunction)dbmopen, METH_FASTCALL, dbmopen__doc__},
235235

236236
static PyObject *
237-
dbmopen_impl(PyObject *module, const char *name, const char *flags, int mode);
237+
dbmopen_impl(PyObject *module, PyObject *filename, const char *flags,
238+
int mode);
238239

239240
static PyObject *
240241
dbmopen(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
241242
{
242243
PyObject *return_value = NULL;
243-
const char *name;
244+
PyObject *filename;
244245
const char *flags = "r";
245246
int mode = 438;
246247

247-
if (!_PyArg_ParseStack(args, nargs, "s|si:open",
248-
&name, &flags, &mode)) {
248+
if (!_PyArg_ParseStack(args, nargs, "U|si:open",
249+
&filename, &flags, &mode)) {
249250
goto exit;
250251
}
251-
return_value = dbmopen_impl(module, name, flags, mode);
252+
return_value = dbmopen_impl(module, filename, flags, mode);
252253

253254
exit:
254255
return return_value;
255256
}
256-
/*[clinic end generated code: output=dc0aca8c00055d02 input=a9049054013a1b77]*/
257+
/*[clinic end generated code: output=dec05ff9c5aeaeae input=a9049054013a1b77]*/

0 commit comments

Comments
 (0)