diff --git a/Lib/test/test_file2k.py b/Lib/test/test_file2k.py index da2ab674509..7b9f9de61c9 100644 --- a/Lib/test/test_file2k.py +++ b/Lib/test/test_file2k.py @@ -619,6 +619,39 @@ class StdoutTests(unittest.TestCase): finally: sys.stdout = save_stdout + def test_unicode(self): + import subprocess + + def get_message(encoding, *code): + code = '\n'.join(code) + env = os.environ.copy() + env['PYTHONIOENCODING'] = encoding + process = subprocess.Popen([sys.executable, "-c", code], + stdout=subprocess.PIPE, env=env) + stdout, stderr = process.communicate() + self.assertEqual(process.returncode, 0) + return stdout + + def check_message(text, encoding, expected): + stdout = get_message(encoding, + "import sys", + "sys.stdout.write(%r)" % text, + "sys.stdout.flush()") + self.assertEqual(stdout, expected) + + check_message(u'\u20ac\n', "iso-8859-15", "\xa4\n") + check_message(u'\u20ac\n', "utf-16-le", '\xac\x20\n\x00') + check_message(u'15\u20ac\n', "iso-8859-1:ignore", "15\n") + check_message(u'15\u20ac\n', "iso-8859-1:replace", "15?\n") + check_message(u'15\u20ac\n', "iso-8859-1:backslashreplace", + "15\\u20ac\n") + + for objtype in ('buffer', 'bytearray'): + stdout = get_message('ascii', + 'import sys', + r'sys.stdout.write(%s("\xe9\n"))' % objtype) + self.assertEqual(stdout, "\xe9\n") + def test_main(): # Historically, these tests have been sloppy about removing TESTFN. diff --git a/Misc/NEWS b/Misc/NEWS index 16304995e2b..9d7ec181468 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,10 @@ What's New in Python 2.7.1? Core and Builtins ----------------- +- Issue #4947: The write() method of sys.stdout and sys.stderr uses their + encoding and errors attributes instead of using utf-8 in strict mode, to get + the same behaviour than the print statement. + - Issue #9737: Fix a crash when trying to delete a slice or an item from a memoryview object. @@ -63,7 +67,7 @@ Library - Issue #8750: Fixed MutableSet's methods to correctly handle reflexive operations, namely x -= x and x ^= x. -- Issue #9129: smtpd.py is vulnerable to DoS attacks deriving from missing +- Issue #9129: smtpd.py is vulnerable to DoS attacks deriving from missing error handling when accepting a new connection. - Issue #658749: asyncore's connect() method now correctly interprets winsock diff --git a/Objects/fileobject.c b/Objects/fileobject.c index d83c054cf3e..b7de6a10cd7 100644 --- a/Objects/fileobject.c +++ b/Objects/fileobject.c @@ -1735,8 +1735,10 @@ static PyObject * file_write(PyFileObject *f, PyObject *args) { Py_buffer pbuf; - char *s; + const char *s; Py_ssize_t n, n2; + PyObject *encoded = NULL; + if (f->f_fp == NULL) return err_closed(); if (!f->writable) @@ -1746,14 +1748,41 @@ file_write(PyFileObject *f, PyObject *args) return NULL; s = pbuf.buf; n = pbuf.len; - } else - if (!PyArg_ParseTuple(args, "t#", &s, &n)) - return NULL; + } + else { + const char *encoding, *errors; + PyObject *text; + if (!PyArg_ParseTuple(args, "O", &text)) + return NULL; + + if (PyString_Check(text)) { + s = PyString_AS_STRING(text); + n = PyString_GET_SIZE(text); + } else if (PyUnicode_Check(text)) { + if (f->f_encoding != Py_None) + encoding = PyString_AS_STRING(f->f_encoding); + else + encoding = PyUnicode_GetDefaultEncoding(); + if (f->f_errors != Py_None) + errors = PyString_AS_STRING(f->f_errors); + else + errors = "strict"; + encoded = PyUnicode_AsEncodedString(text, encoding, errors); + if (encoded == NULL) + return NULL; + s = PyString_AS_STRING(encoded); + n = PyString_GET_SIZE(encoded); + } else { + if (PyObject_AsCharBuffer(text, &s, &n)) + return NULL; + } + } f->f_softspace = 0; FILE_BEGIN_ALLOW_THREADS(f) errno = 0; n2 = fwrite(s, 1, n, f->f_fp); FILE_END_ALLOW_THREADS(f) + Py_XDECREF(encoded); if (f->f_binary) PyBuffer_Release(&pbuf); if (n2 != n) {