Skip to content

Commit c755ca8

Browse files
miss-islingtonserhiy-storchaka
authored andcommitted
[3.7] bpo-24214: Fixed the UTF-8 and UTF-16 incremental decoders. (GH-14304) (GH-14369)
* bpo-24214: Fixed the UTF-8 and UTF-16 incremental decoders. (GH-14304) * The UTF-8 incremental decoders fails now fast if encounter a sequence that can't be handled by the error handler. * The UTF-16 incremental decoders with the surrogatepass error handler decodes now a lone low surrogate with final=False. (cherry picked from commit 894263b) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 695d7ad commit c755ca8

File tree

4 files changed

+37
-6
lines changed

4 files changed

+37
-6
lines changed

Lib/test/test_codecs.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,11 +404,19 @@ def test_lone_surrogates(self):
404404
def test_incremental_surrogatepass(self):
405405
# Test incremental decoder for surrogatepass handler:
406406
# see issue #24214
407+
# High surrogate
407408
data = '\uD901'.encode(self.encoding, 'surrogatepass')
408409
for i in range(1, len(data)):
409410
dec = codecs.getincrementaldecoder(self.encoding)('surrogatepass')
410411
self.assertEqual(dec.decode(data[:i]), '')
411412
self.assertEqual(dec.decode(data[i:], True), '\uD901')
413+
# Low surrogate
414+
data = '\uDC02'.encode(self.encoding, 'surrogatepass')
415+
for i in range(1, len(data)):
416+
dec = codecs.getincrementaldecoder(self.encoding)('surrogatepass')
417+
self.assertEqual(dec.decode(data[:i]), '')
418+
final = self.encoding == "cp65001"
419+
self.assertEqual(dec.decode(data[i:], final), '\uDC02')
412420

413421

414422
class UTF32Test(ReadTest, unittest.TestCase):
@@ -849,6 +857,23 @@ def test_surrogatepass_handler(self):
849857
with self.assertRaises(UnicodeDecodeError):
850858
b"abc\xed\xa0z".decode(self.encoding, "surrogatepass")
851859

860+
def test_incremental_errors(self):
861+
# Test that the incremental decoder can fail with final=False.
862+
# See issue #24214
863+
cases = [b'\x80', b'\xBF', b'\xC0', b'\xC1', b'\xF5', b'\xF6', b'\xFF']
864+
for prefix in (b'\xC2', b'\xDF', b'\xE0', b'\xE0\xA0', b'\xEF',
865+
b'\xEF\xBF', b'\xF0', b'\xF0\x90', b'\xF0\x90\x80',
866+
b'\xF4', b'\xF4\x8F', b'\xF4\x8F\xBF'):
867+
for suffix in b'\x7F', b'\xC0':
868+
cases.append(prefix + suffix)
869+
cases.extend((b'\xE0\x80', b'\xE0\x9F', b'\xED\xA0\x80',
870+
b'\xED\xBF\xBF', b'\xF0\x80', b'\xF0\x8F', b'\xF4\x90'))
871+
872+
for data in cases:
873+
with self.subTest(data=data):
874+
dec = codecs.getincrementaldecoder(self.encoding)()
875+
self.assertRaises(UnicodeDecodeError, dec.decode, data)
876+
852877

853878
@unittest.skipUnless(sys.platform == 'win32',
854879
'cp65001 is a Windows-only codec')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improved support of the surrogatepass error handler in the UTF-8 and UTF-16
2+
incremental decoders.

Objects/stringlib/codecs.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ STRINGLIB(utf8_decode)(const char **inptr, const char *end,
207207
goto InvalidContinuation1;
208208
} else if (ch == 0xF4 && ch2 >= 0x90) {
209209
/* invalid sequence
210-
\xF4\x90\x80\80- -- 110000- overflow */
210+
\xF4\x90\x80\x80- -- 110000- overflow */
211211
goto InvalidContinuation1;
212212
}
213213
if (!IS_CONTINUATION_BYTE(ch3)) {
@@ -573,10 +573,10 @@ STRINGLIB(utf16_decode)(const unsigned char **inptr, const unsigned char *e,
573573
}
574574

575575
/* UTF-16 code pair: */
576-
if (q >= e)
577-
goto UnexpectedEnd;
578576
if (!Py_UNICODE_IS_HIGH_SURROGATE(ch))
579577
goto IllegalEncoding;
578+
if (q >= e)
579+
goto UnexpectedEnd;
580580
ch2 = (q[ihi] << 8) | q[ilo];
581581
q += 2;
582582
if (!Py_UNICODE_IS_LOW_SURROGATE(ch2))

Objects/unicodeobject.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4888,11 +4888,15 @@ PyUnicode_DecodeUTF8Stateful(const char *s,
48884888
endinpos = startinpos + 1;
48894889
break;
48904890
case 2:
4891-
case 3:
4892-
case 4:
4893-
if (s == end || consumed) {
4891+
if (consumed && (unsigned char)s[0] == 0xED && end - s == 2
4892+
&& (unsigned char)s[1] >= 0xA0 && (unsigned char)s[1] <= 0xBF)
4893+
{
4894+
/* Truncated surrogate code in range D800-DFFF */
48944895
goto End;
48954896
}
4897+
/* fall through */
4898+
case 3:
4899+
case 4:
48964900
errmsg = "invalid continuation byte";
48974901
startinpos = s - starts;
48984902
endinpos = startinpos + ch - 1;

0 commit comments

Comments
 (0)