Skip to content

Commit c996607

Browse files
harjothkharaclaude
andcommitted
gh-161518: Reject a non-bytes encoder result unless it is the ASCII fast path
TextIOWrapper.write() accepted any encoder result that was the same object as its input. That is only safe for ASCII text, so a custom encoder returning a non-ASCII str wrote corrupt data. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent ce4ac8c commit c996607

3 files changed

Lines changed: 28 additions & 1 deletion

File tree

‎Lib/test/test_io/test_textio.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,6 +1097,31 @@ def test_illegal_encoder(self):
10971097
t = self.TextIOWrapper(self.BytesIO(b'foo'), encoding="rot13")
10981098
self.assertRaises(TypeError, t.write, 'bar')
10991099

1100+
def test_encoder_returns_str(self):
1101+
# gh-161518: an encoder returning its non-ASCII input str must be
1102+
# rejected, not have its internal storage written as bytes.
1103+
class Encoder(codecs.IncrementalEncoder):
1104+
def encode(self, input, final=False):
1105+
return input
1106+
1107+
info = codecs.CodecInfo(
1108+
None, None, incrementalencoder=Encoder,
1109+
incrementaldecoder=codecs.getincrementaldecoder('utf-8'),
1110+
name='test_str_encoder')
1111+
search = lambda name: info if name == 'test_str_encoder' else None
1112+
codecs.register(search)
1113+
self.addCleanup(codecs.unregister, search)
1114+
1115+
t = self.TextIOWrapper(self.BytesIO(), encoding='test_str_encoder')
1116+
self.assertRaises(TypeError, t.write, 'caf\xe9')
1117+
1118+
# The ASCII fast path still works.
1119+
raw = self.BytesIO()
1120+
t = self.TextIOWrapper(raw, encoding='utf-8')
1121+
t.write('cafe')
1122+
t.flush()
1123+
self.assertEqual(raw.getvalue(), b'cafe')
1124+
11001125
def test_illegal_decoder(self):
11011126
# Issue #17106
11021127
# Bypass the early encoding check added in issue 20404
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:class:`io.TextIOWrapper` now raises :exc:`TypeError`, instead of writing
2+
corrupt output, when an encoder returns its non-ASCII input string.

‎Modules/_io/textio.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1831,7 +1831,7 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
18311831
Py_DECREF(text);
18321832
if (b == NULL)
18331833
return NULL;
1834-
if (b != text && !PyBytes_Check(b)) {
1834+
if (!PyBytes_Check(b) && !(b == text && PyUnicode_IS_ASCII(text))) {
18351835
PyErr_Format(PyExc_TypeError,
18361836
"encoder should return a bytes object, not '%.200s'",
18371837
Py_TYPE(b)->tp_name);

0 commit comments

Comments
 (0)