Skip to content

Commit 43fec9b

Browse files
committed
Merge issue python#28164 and issue python#29409
2 parents 38dbace + 722e3e2 commit 43fec9b

5 files changed

Lines changed: 101 additions & 39 deletions

File tree

Lib/test/test_fileio.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from weakref import proxy
1010
from functools import wraps
1111

12-
from test.support import TESTFN, check_warnings, run_unittest, make_bad_fd, cpython_only
12+
from test.support import (TESTFN, TESTFN_UNICODE, check_warnings, run_unittest,
13+
make_bad_fd, cpython_only)
1314
from collections import UserList
1415

1516
import _io # C implementation of io
@@ -432,6 +433,23 @@ def testBytesOpen(self):
432433
finally:
433434
os.unlink(TESTFN)
434435

436+
@unittest.skipIf(sys.getfilesystemencoding() != 'utf-8',
437+
"test only works for utf-8 filesystems")
438+
def testUtf8BytesOpen(self):
439+
# Opening a UTF-8 bytes filename
440+
try:
441+
fn = TESTFN_UNICODE.encode("utf-8")
442+
except UnicodeEncodeError:
443+
self.skipTest('could not encode %r to utf-8' % TESTFN_UNICODE)
444+
f = self.FileIO(fn, "w")
445+
try:
446+
f.write(b"abc")
447+
f.close()
448+
with open(TESTFN_UNICODE, "rb") as f:
449+
self.assertEqual(f.read(), b"abc")
450+
finally:
451+
os.unlink(TESTFN_UNICODE)
452+
435453
def testConstructorHandlesNULChars(self):
436454
fn_with_NUL = 'foo\0bar'
437455
self.assertRaises(ValueError, self.FileIO, fn_with_NUL, 'w')

Lib/test/test_winconsoleio.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
'''Tests for WindowsConsoleIO
22
'''
33

4+
import os
45
import io
5-
import unittest
66
import sys
7+
import unittest
8+
import tempfile
79

810
if sys.platform != 'win32':
911
raise unittest.SkipTest("test only relevant on win32")
@@ -19,6 +21,16 @@ def test_abc(self):
1921
self.assertFalse(issubclass(ConIO, io.TextIOBase))
2022

2123
def test_open_fd(self):
24+
self.assertRaisesRegex(ValueError,
25+
"negative file descriptor", ConIO, -1)
26+
27+
fd, _ = tempfile.mkstemp()
28+
try:
29+
self.assertRaisesRegex(ValueError,
30+
"Cannot open non-console file", ConIO, fd)
31+
finally:
32+
os.close(fd)
33+
2234
try:
2335
f = ConIO(0)
2436
except ValueError:
@@ -56,6 +68,20 @@ def test_open_fd(self):
5668
f.close()
5769

5870
def test_open_name(self):
71+
self.assertRaises(ValueError, ConIO, sys.executable)
72+
73+
f = open('C:/con', 'rb', buffering=0)
74+
self.assertIsInstance(f, ConIO)
75+
f.close()
76+
77+
f = open(r'\\.\conin$', 'rb', buffering=0)
78+
self.assertIsInstance(f, ConIO)
79+
f.close()
80+
81+
f = open('//?/conout$', 'wb', buffering=0)
82+
self.assertIsInstance(f, ConIO)
83+
f.close()
84+
5985
f = ConIO("CON")
6086
self.assertTrue(f.readable())
6187
self.assertFalse(f.writable())

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,10 @@ Library
871871
Windows
872872
-------
873873

874+
- Issue #28164: Correctly handle special console filenames (patch by Eryk Sun)
875+
876+
- Issue #29409: Implement PEP 529 for io.FileIO (Patch by Eryk Sun)
877+
874878
- Issue #29392: Prevent crash when passing invalid arguments into msvcrt module.
875879

876880
- Issue #25778: winreg does not truncate string correctly (Patch by Eryk Sun)

Modules/_io/fileio.c

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -230,12 +230,13 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode,
230230
int closefd, PyObject *opener)
231231
/*[clinic end generated code: output=23413f68e6484bbd input=193164e293d6c097]*/
232232
{
233-
const char *name = NULL;
234-
PyObject *stringobj = NULL;
235-
const char *s;
236233
#ifdef MS_WINDOWS
237234
Py_UNICODE *widename = NULL;
235+
#else
236+
const char *name = NULL;
238237
#endif
238+
PyObject *stringobj = NULL;
239+
const char *s;
239240
int ret = 0;
240241
int rwa = 0, plus = 0;
241242
int flags = 0;
@@ -277,24 +278,21 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode,
277278
PyErr_Clear();
278279
}
279280

281+
if (fd < 0) {
280282
#ifdef MS_WINDOWS
281-
if (PyUnicode_Check(nameobj)) {
282283
Py_ssize_t length;
283-
widename = PyUnicode_AsUnicodeAndSize(nameobj, &length);
284-
if (widename == NULL)
285-
return -1;
286-
if (wcslen(widename) != length) {
287-
PyErr_SetString(PyExc_ValueError, "embedded null character");
284+
if (!PyUnicode_FSDecoder(nameobj, &stringobj)) {
288285
return -1;
289286
}
290-
} else
291-
#endif
292-
if (fd < 0)
293-
{
287+
widename = PyUnicode_AsUnicodeAndSize(stringobj, &length);
288+
if (widename == NULL)
289+
return -1;
290+
#else
294291
if (!PyUnicode_FSConverter(nameobj, &stringobj)) {
295292
return -1;
296293
}
297294
name = PyBytes_AS_STRING(stringobj);
295+
#endif
298296
}
299297

300298
s = mode;
@@ -386,11 +384,10 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode,
386384
do {
387385
Py_BEGIN_ALLOW_THREADS
388386
#ifdef MS_WINDOWS
389-
if (widename != NULL)
390-
self->fd = _wopen(widename, flags, 0666);
391-
else
387+
self->fd = _wopen(widename, flags, 0666);
388+
#else
389+
self->fd = open(name, flags, 0666);
392390
#endif
393-
self->fd = open(name, flags, 0666);
394391
Py_END_ALLOW_THREADS
395392
} while (self->fd < 0 && errno == EINTR &&
396393
!(async_err = PyErr_CheckSignals()));

Modules/_io/winconsoleio.c

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -60,51 +60,68 @@ char _get_console_type(HANDLE handle) {
6060
}
6161

6262
char _PyIO_get_console_type(PyObject *path_or_fd) {
63-
int fd;
64-
65-
fd = PyLong_AsLong(path_or_fd);
63+
int fd = PyLong_AsLong(path_or_fd);
6664
PyErr_Clear();
6765
if (fd >= 0) {
6866
HANDLE handle;
6967
_Py_BEGIN_SUPPRESS_IPH
7068
handle = (HANDLE)_get_osfhandle(fd);
7169
_Py_END_SUPPRESS_IPH
72-
if (!handle)
70+
if (handle == INVALID_HANDLE_VALUE)
7371
return '\0';
7472
return _get_console_type(handle);
7573
}
7674

77-
PyObject *decoded, *decoded_upper;
75+
PyObject *decoded;
76+
wchar_t *decoded_wstr;
7877

79-
int d = PyUnicode_FSDecoder(path_or_fd, &decoded);
80-
if (!d) {
78+
if (!PyUnicode_FSDecoder(path_or_fd, &decoded)) {
8179
PyErr_Clear();
8280
return '\0';
8381
}
84-
if (!PyUnicode_Check(decoded)) {
85-
Py_CLEAR(decoded);
86-
return '\0';
87-
}
88-
decoded_upper = PyObject_CallMethod(decoded, "upper", NULL);
82+
decoded_wstr = PyUnicode_AsWideCharString(decoded, NULL);
8983
Py_CLEAR(decoded);
90-
if (!decoded_upper) {
84+
if (!decoded_wstr) {
9185
PyErr_Clear();
9286
return '\0';
9387
}
9488

89+
DWORD length;
90+
wchar_t name_buf[MAX_PATH], *pname_buf = name_buf;
91+
92+
length = GetFullPathNameW(decoded_wstr, MAX_PATH, pname_buf, NULL);
93+
if (length > MAX_PATH) {
94+
pname_buf = PyMem_New(wchar_t, length);
95+
if (pname_buf)
96+
length = GetFullPathNameW(decoded_wstr, length, pname_buf, NULL);
97+
else
98+
length = 0;
99+
}
100+
PyMem_Free(decoded_wstr);
101+
95102
char m = '\0';
96-
if (_PyUnicode_EqualToASCIIString(decoded_upper, "CONIN$")) {
97-
m = 'r';
98-
} else if (_PyUnicode_EqualToASCIIString(decoded_upper, "CONOUT$")) {
99-
m = 'w';
100-
} else if (_PyUnicode_EqualToASCIIString(decoded_upper, "CON")) {
101-
m = 'x';
103+
if (length) {
104+
wchar_t *name = pname_buf;
105+
if (length >= 4 && name[3] == L'\\' &&
106+
(name[2] == L'.' || name[2] == L'?') &&
107+
name[1] == L'\\' && name[0] == L'\\') {
108+
name += 4;
109+
}
110+
if (!_wcsicmp(name, L"CONIN$")) {
111+
m = 'r';
112+
} else if (!_wcsicmp(name, L"CONOUT$")) {
113+
m = 'w';
114+
} else if (!_wcsicmp(name, L"CON")) {
115+
m = 'x';
116+
}
102117
}
103118

104-
Py_CLEAR(decoded_upper);
119+
if (pname_buf != name_buf)
120+
PyMem_Free(pname_buf);
105121
return m;
106122
}
107123

124+
108125
/*[clinic input]
109126
module _io
110127
class _io._WindowsConsoleIO "winconsoleio *" "&PyWindowsConsoleIO_Type"

0 commit comments

Comments
 (0)