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
13 changes: 8 additions & 5 deletions Lib/encodings/utf_7_imap.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def flush(end):
flush(len(input))
return res.take_bytes(), len(input)

def utf_7_imap_decode(input, errors='strict'):
def utf_7_imap_decode(input, errors='strict', final=True):
if errors != 'strict':
raise UnicodeError(f"Unsupported error handling: {errors}")
input = bytes(input)
Expand All @@ -71,6 +71,8 @@ def flush(end):
flush(i)
j = input.find(b'-', i + 1)
if j < 0:
if not final:
return ''.join(res), i
raise UnicodeDecodeError('utf-7-imap', input, i, n,
'unterminated shift sequence')
if j == i + 1: # '&-'
Expand Down Expand Up @@ -106,15 +108,16 @@ class IncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input, final=False):
return utf_7_imap_encode(input, self.errors)[0]

class IncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input, final=False):
return utf_7_imap_decode(input, self.errors)[0]
class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
def _buffer_decode(self, input, errors, final):
return utf_7_imap_decode(input, errors, final)

class StreamWriter(Codec, codecs.StreamWriter):
pass

class StreamReader(Codec, codecs.StreamReader):
pass
def decode(self, input, errors='strict'):
return utf_7_imap_decode(input, errors, False)

### encodings module API

Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_codecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1467,6 +1467,24 @@ def test_only_strict_errors(self):
with self.assertRaises(UnicodeError):
b'x'.decode('utf-7-imap', 'ignore')

@support.subTests('uni,encoded', utf_7_imap_testcases)
def test_incremental_decode(self, uni, encoded):
# A shift sequence split across chunks is not an error: the joined
# output has to match the stateless decoder.
decoder = codecs.getincrementaldecoder('utf-7-imap')()
out = ''.join(decoder.decode(encoded[i:i + 1])
for i in range(len(encoded)))
self.assertEqual(out + decoder.decode(b'', True), uni)

def test_stream_read(self):
# Reading a fixed number of characters stops inside a shift sequence.
uni = '\u53f0' * 2000
encoded = uni.encode('utf-7-imap')
with io.TextIOWrapper(io.BytesIO(encoded), encoding='utf-7-imap') as f:
self.assertEqual(f.read(100), uni[:100])
chunks = [encoded[i:i + 16] for i in range(0, len(encoded), 16)]
self.assertEqual(''.join(codecs.iterdecode(chunks, 'utf-7-imap')), uni)

def test_stateless(self):
# The codec is registered and exposes the standard interface.
info = codecs.lookup('utf-7-imap')
Expand Down
Loading