Coverage for daklib/mail.py: 93%

25 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2026-01-04 16:18 +0000

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 assert sig is not None 

31 mime_sig = email.message.MIMEPart() 

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

33 mime_sig.set_payload(sig) 

34 

35 msg.clear_content() 

36 del msg["Content-Type"] 

37 msg["Content-Type"] = ( 

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

39 ) 

40 msg.set_payload([mime_data, mime_sig]) 

41 return msg 

42 

43 

44# TODO [python3.10, pep604]: 

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

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

47 if isinstance(msg, str): 47 ↛ 55line 47 didn't jump to line 55 because the condition on line 47 was always true

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

49 return cast( 

50 email.message.EmailMessage, 

51 email.message_from_string(msg, policy=email.policy.SMTPUTF8), # type: ignore[arg-type] 

52 ) 

53 else: 

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

55 return cast( 

56 email.message.EmailMessage, 

57 email.message_from_bytes(msg, policy=email.policy.SMTPUTF8), # type: ignore[arg-type] 

58 )