◐ Shell
clean mode source ↗

BytesGenerator breaks UTF8 string

import io
import email.generator
from email.message import EmailMessage
from email.header import decode_header


def encode_decode(subject: str):
    # preparing EmailMessage
    msg = EmailMessage()
    msg['Subject'] = subject

    # below code sample was taken from "send_message" function (lib/python3.8/smtplib.py)
    # this is the place where problem actually appears
    with io.BytesIO() as bytesmsg:
        g = email.generator.BytesGenerator(bytesmsg)
        g.flatten(msg, linesep='\r\n')
        flatmsg = bytesmsg.getvalue()

    # assembling string and cutting off beginning part ('Subject: ')
    result = ''
    for string, encoding in decode_header(flatmsg.decode()):
        result += string.decode(encoding=encoding or 'utf8')
    return result[9:]


if __name__ == '__main__':
    test_cases = [
        'ффффффффффффффффффффффффф',   # ok
        'фффффффффффффффффффффффф ',   # ok
        'ффффффффффффффффффффффф ф',   # ok
        'фффффффффффффффффффффф фф',   # ok
        'ффффффффффффффффффффф ф ф',   # broken
        'фффффффффффффффффффф фф ф',   # ok
        'ффффффффффффффффффф ф ф ф',   # ok
        'фффффффффффффффффф ф ф ф ф',  # broken
        'ффффффффффффффффф ф фф ф ф',  # broken
        'фффффффффффффффф ф ффф ф ф',  # broken
        'ффффффффффффффф ф фффф ф ф',  # broken
        'фффффффффффффф ф ффффф ф ф',  # broken
        'ффффффффффффф ф фффффф ф ф',  # broken
        'фффффффффффф ф ффффффф ф ф',  # broken
        'ффффффффффф ф фффффффф ф ф',  # broken
        'фффффффффф ф ффффффффф ф ф',  # broken
        'ффффффффф ф фффффффффф ф ф',  # broken
        'фффффффф ф ффффффффффф ф ф',  # broken
        'ффффффф ф фффффффффффф ф ф',  # broken
        'фффффф ф ффффффффффффф ф ф',  # broken
        'ффффф ф фффффффффффффф ф ф',  # broken
        'фффф ф ффффффффффффффф ф ф',  # broken
        'ффф ф фффффффффффффффф ф ф',  # broken
        'фф ф ффффффффффффффффф ф ф',  # broken
        'ф ф фффффффффффффффффф ф ф',  # broken
        ' ф ффффффффффффффффффф ф ф',  # broken
        'ф фффффффффффффффффффф ф ф',  # ok
        ' ффффффффффффффффффффф ф ф',  # broken
        'фффффффффффффффффффффф ф ф',  # ok
    ]
    for in_ in test_cases:
        out_ = encode_decode(in_)
        res = 'ok' if out_ == in_ else 'broken'
        print(f'In  | {in_}', f'Out | {out_}', f'Res | {res}\n', sep='\n')