1"""Mail handling 

2 

3@copyright: 2022 Ansgar <ansgar@debian.org> 

4@license: GNU General Public License version 2 or later 

5""" 

6 

7import email 

8import email.message 

9import email.policy 

10from typing import cast 

11 

12import daklib.gpg 

13 

14 

15def sign_mail( 

16 msg: email.message.EmailMessage, *, digest_algorithm: str = "SHA256", **kwargs 

17) -> email.message.EmailMessage: 

18 """sign an email message using GnuPG. 

19 

20 This only handles non-multipart messages. 

21 """ 

22 mime_data = email.message.MIMEPart() 

23 mime_data.set_content(msg.get_payload()) 

24 # Copy Content-Transfer-Encoding from unsigned message 

25 del mime_data["Content-Transfer-Encoding"] 

26 mime_data["Content-Transfer-Encoding"] = msg["Content-Transfer-Encoding"] 

27 data = mime_data.as_bytes(policy=email.policy.SMTP) 

28 

29 sig = daklib.gpg.sign(data, **kwargs, digest_algorithm=digest_algorithm) 

30 mime_sig = email.message.MIMEPart() 

31 mime_sig["Content-Type"] = "application/pgp-signature" 

32 mime_sig.set_payload(sig) 

33 

34 msg.clear_content() 

35 del msg["Content-Type"] 

36 msg["Content-Type"] = ( 

37 f'multipart/signed; micalg="pgp-{digest_algorithm.lower()}"; protocol="application/pgp-signature"' 

38 ) 

39 msg.set_payload([mime_data, mime_sig]) 

40 return msg 

41 

42 

43# TODO [python3.10, pep604]: 

44# def parse_mail(msg: bytes | str) -> email.message.EmailMessage: 

45def parse_mail(msg) -> email.message.EmailMessage: 

46 if isinstance(msg, str): 46 ↛ 54line 46 didn't jump to line 54, because the condition on line 46 was never false

47 # We need a cast as the return type depends on the `policy` argument. 

48 return cast( 

49 email.message.EmailMessage, 

50 email.message_from_string(msg, policy=email.policy.SMTPUTF8), 

51 ) 

52 else: 

53 # We need a cast as the return type depends on the `policy` argument. 

54 return cast( 

55 email.message.EmailMessage, 

56 email.message_from_bytes(msg, policy=email.policy.SMTPUTF8), 

57 )