Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,13 @@ that may require changes to your code.
raises :exc:`io.UnsupportedOperation` unless buffering is disabled.
(Contributed by An Long in :gh:`86768`.)

* The ``iso2022_*`` codecs no longer encode U+001B ESCAPE, and
``iso2022_kr`` no longer encodes U+000E SHIFT OUT and U+000F SHIFT IN.
Written as bare bytes, they start an escape or shift sequence, so any
ISO-2022 decoder misread the result. Encoding them now raises
:exc:`UnicodeEncodeError`, or calls the error handler.
(Contributed by Ahmed Semih Özmekik in :gh:`160302`.)


Build changes
=============
Expand Down
6 changes: 5 additions & 1 deletion Lib/email/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,11 @@ def set_payload(self, payload, charset=None):
return
if not isinstance(charset, Charset):
charset = Charset(charset)
payload = payload.encode(charset.output_charset, 'surrogateescape')
# If the input and output charsets differ, set_charset()
# converts the text, so don't encode it twice.
if charset == charset.get_output_charset():
payload = payload.encode(charset.output_charset,
'surrogateescape')
if hasattr(payload, 'decode'):
self._payload = payload.decode('ascii', 'surrogateescape')
else:
Expand Down
24 changes: 20 additions & 4 deletions Lib/test/test_codecencodings_iso2022.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,23 @@
(b'ab\xFFcd', 'replace', 'ab\uFFFDcd'),
(b'ab\x1Bdef', 'replace', 'ab\x1Bdef'),
(b'ab\x1B$def', 'replace', 'ab\uFFFD'),
# a bare ESC would start an escape sequence
('ab\x1Bdef', 'strict', None),
('x\x1B$B$"', 'strict', None),
('ab\x1Bdef', 'replace', b'ab?def'),
('ab\x1Bdef', 'ignore', b'abdef'),
('ab\x1Bdef', 'backslashreplace', b'ab\\x1bdef'),
)

# SO and SI are not shifts in these encodings
NO_SHIFT_CODEC_TESTS = (
('a\x0Eb\x0Fc', 'strict', b'a\x0Eb\x0Fc'),
)

class Test_ISO2022_JP(multibytecodec_support.TestBase, unittest.TestCase):
encoding = 'iso2022_jp'
tstring = multibytecodec_support.load_teststring('iso2022_jp')
codectests = COMMON_CODEC_TESTS + (
codectests = COMMON_CODEC_TESTS + NO_SHIFT_CODEC_TESTS + (
(b'ab\x1BNdef', 'replace', 'ab\x1BNdef'),
# there is no G1 in this encoding
(b'ab\x1B$)Bdef', 'strict', None),
Expand All @@ -24,7 +35,7 @@ class Test_ISO2022_JP(multibytecodec_support.TestBase, unittest.TestCase):
class Test_ISO2022_JP2(multibytecodec_support.TestBase, unittest.TestCase):
encoding = 'iso2022_jp_2'
tstring = multibytecodec_support.load_teststring('iso2022_jp')
codectests = COMMON_CODEC_TESTS + (
codectests = COMMON_CODEC_TESTS + NO_SHIFT_CODEC_TESTS + (
(b'ab\x1BNdef', 'replace', 'abdef'),
# a charset can only be designated to the G set (G0, G1 or G2)
# that RFC 1554 assigns it
Expand All @@ -40,7 +51,7 @@ class Test_ISO2022_JP2(multibytecodec_support.TestBase, unittest.TestCase):
class Test_ISO2022_JP3(multibytecodec_support.TestBase, unittest.TestCase):
encoding = 'iso2022_jp_3'
tstring = multibytecodec_support.load_teststring('iso2022_jp')
codectests = COMMON_CODEC_TESTS + (
codectests = COMMON_CODEC_TESTS + NO_SHIFT_CODEC_TESTS + (
(b'ab\x1BNdef', 'replace', 'ab\x1BNdef'),
(b'\x1B$(O\x2E\x23\x1B(B', 'strict', '\u3402' ),
(b'\x1B$(O\x2E\x22\x1B(B', 'strict', '\U0002000B' ),
Expand All @@ -63,7 +74,7 @@ class Test_ISO2022_JP3(multibytecodec_support.TestBase, unittest.TestCase):
class Test_ISO2022_JP2004(multibytecodec_support.TestBase, unittest.TestCase):
encoding = 'iso2022_jp_2004'
tstring = multibytecodec_support.load_teststring('iso2022_jp')
codectests = COMMON_CODEC_TESTS + (
codectests = COMMON_CODEC_TESTS + NO_SHIFT_CODEC_TESTS + (
(b'ab\x1BNdef', 'replace', 'ab\x1BNdef'),
(b'\x1B$(Q\x2E\x23\x1B(B', 'strict', '\u3402' ),
(b'\x1B$(Q\x2E\x22\x1B(B', 'strict', '\U0002000B' ),
Expand Down Expand Up @@ -94,6 +105,11 @@ class Test_ISO2022_KR(multibytecodec_support.TestBase, unittest.TestCase):
(b'ab\x1B$Cdefg', 'strict', None),
(b'ab\x1B)Bdefg', 'strict', None),
(b'ab\x1B$(Cdefg', 'replace', 'ab\ufffddefg'),
# SO and SI shift between ASCII and KS X 1001
('a\x0Eb', 'strict', None),
('a\x0Fb', 'strict', None),
('a\x0Eb\x0Fc', 'replace', b'a?b?c'),
('\uac00\x0E', 'replace', b'\x1B$)C\x0E\x30\x21\x0F?'),
)

# iso2022_kr.txt cannot be used to test "chunk coding": the escape
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
The ``iso2022_*`` codecs no longer encode U+001B ESCAPE, and ``iso2022_kr``
no longer encodes U+000E SHIFT OUT and U+000F SHIFT IN. Written as bare
bytes, they start an escape or shift sequence, so decoders misread the
result. Encoding them now raises :exc:`UnicodeEncodeError` or calls the
error handler.
7 changes: 7 additions & 0 deletions Modules/cjkcodecs/_codecs_iso2022.c
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ ENCODER(iso2022)
Py_ssize_t insize;

if (c < 0x80) {
/* A bare ESC would be read as the start of an escape sequence,
and SO and SI as shifts where they are used, so these
characters cannot be encoded. */
if (c == ESC ||
((c == SO || c == SI) && !CONFIG_ISSET(NO_SHIFT))) {
return 1;
}
if (STATE_G0 != CHARSET_ASCII) {
WRITEBYTE3(ESC, '(', 'B');
STATE_SETG0(CHARSET_ASCII);
Expand Down
Loading