Skip to content

Commit

Permalink
Merge branch 'fix-tests2'
Browse files Browse the repository at this point in the history
  • Loading branch information
e3rd committed Jan 7, 2025
1 parent 3ea5615 commit 5c0ad37
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 42 deletions.
22 changes: 20 additions & 2 deletions envelope/address.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
from os import environ
import re
import sys
from .utils import assure_list

environ['PY3VE_IGNORE_UPDATER'] = '1'
Expand All @@ -11,6 +12,23 @@
logger = logging.getLogger(__name__)


def _getaddresses(*args):
# NOTE Python finally changed the old way of parsing wrong addresses.
# We might start using strict=True (default) in the future.
if sys.version_info <= (3, 11):
return getaddresses(*args)
return getaddresses(*args, strict=False)


def _parseaddr(*args):
# NOTE Python finally changed the old way of parsing wrong addresses.
# We might start using strict=True (default) in the future.
# README should reflect that.
if sys.version_info <= (3, 11):
return parseaddr(*args)
return parseaddr(*args, strict=False)


class Address(str):
"""
You can safely access the `self.name` property to access the real name and `self.address` to access the e-mail address.
Expand All @@ -37,7 +55,7 @@ class Address(str):

def __new__(cls, displayed_email=None, name=None, address=None):
if displayed_email:
v = parseaddr(cls.remedy(displayed_email))
v = _parseaddr(cls.remedy(displayed_email))
name, address = v[0] or name, v[1] or address
if name:
displayed_email = f"{name} <{address}>"
Expand Down Expand Up @@ -143,7 +161,7 @@ def parse(cls, email_or_list, single=False, allow_false=False):
if allow_false and email_or_list is False:
return False

addrs = getaddresses(cls.remedy(x) for x in assure_list(email_or_list))
addrs = _getaddresses(cls.remedy(x) for x in assure_list(email_or_list))
addresses = [Address(name=real_name, address=address)
for real_name, address in addrs if not (real_name == address == "")]
if single:
Expand Down
60 changes: 25 additions & 35 deletions envelope/envelope.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,23 @@
import sys
from tempfile import NamedTemporaryFile
from unittest import mock
import warnings
from base64 import b64decode, b64encode
from base64 import b64decode
from configparser import ConfigParser
from copy import deepcopy
from email import message_from_bytes
from email.header import decode_header
from email.generator import Generator
from email.message import EmailMessage, Message
from email.parser import BytesParser
from email.utils import make_msgid, formatdate, getaddresses
from getpass import getpass
from email.utils import make_msgid, formatdate
from itertools import chain
from os import environ, urandom
from os import environ
from pathlib import Path
from quopri import decodestring
from types import GeneratorType
from typing import Literal, Union, List, Set, Optional, Any
from typing import Literal, Union, Optional, Any

from .address import Address
from .address import Address, _getaddresses
from .attachment import Attachment
from .constants import ISSUE_LINK, smime_import_error, gnupg, CRLF, AUTO, PLAIN, HTML, SIMULATION, SAFE_LOCALE
from .message import _Message
Expand All @@ -35,7 +33,6 @@
from .utils import AutoSubmittedHeader, Fetched, is_gpg_importable_key, assure_list, assure_fetched, get_mimetype



__doc__ = """Quick layer over python-gnupg, M2Crypto, smtplib, magic and email handling packages.
Their common use cases merged into a single function. Want to sign a text and tired of forgetting how to do it right?
You do not need to know everything about GPG or S/MIME, you do not have to bother with importing keys.
Expand Down Expand Up @@ -320,14 +317,14 @@ def __init__(self, message=None, from_=None, to=None, subject=None, headers=None
# that explicitly states we have no from header
self._from: Union[Address, False, None] = None # e-mail From header
self._from_addr: Optional[Address] = None # SMTP envelope MAIL FROM address
self._to: List[Address] = []
self._cc: List[Address] = []
self._bcc: List[Address] = []
self._reply_to: List[Address] = []
self._to: list[Address] = []
self._cc: list[Address] = []
self._bcc: list[Address] = []
self._reply_to: list[Address] = []
self._subject: Union[str, None] = None
self._subject_encrypted: Union[str, bool] = True
self._smtp = None
self._attachments: List[Attachment] = []
self._attachments: list[Attachment] = []
self._mime = AUTO
self._nl2br = AUTO
self._headers = EmailMessage() # object for storing headers the most standard way possible
Expand All @@ -336,7 +333,7 @@ def __init__(self, message=None, from_=None, to=None, subject=None, headers=None
# variables defined while processing
self._status: bool = False # whether we successfully encrypted/signed/send
self._processed: bool = False # prevent the user from mistakenly call .sign().send() instead of .signature().send()
self._result: List[Union[str, EmailMessage, Message]] = [] # text output for str() conversion
self._result: list[Union[str, EmailMessage, Message]] = [] # text output for str() conversion
self._result_cache: Optional[str] = None
self._result_cache_hash: Optional[int] = None
self._smtp = SMTPHandler()
Expand Down Expand Up @@ -407,32 +404,32 @@ def _parse_addresses(registry, email_or_more):
if addresses:
registry += (a for a in Address.parse(addresses) if a not in registry)

def to(self, email_or_more=None) -> Union["Envelope", List[Address]]:
def to(self, email_or_more=None) -> Union["Envelope", list[Address]]:
""" Multiple addresses may be given in a string, delimited by comma (or semicolon).
(The same is valid for `to`, `cc`, `bcc` and `reply-to`.)
:param email_or_more: str|Tuple[str]|List[str]|Generator[str]|Set[str]|Frozenset[str]
:param email_or_more: str|Tuple[str]|list[str]|Generator[str]|Set[str]|Frozenset[str]
Set e-mail address/es. If None, we are reading.
return: Envelope if `email_or_more` set or List[Address] if not set
return: Envelope if `email_or_more` set or list[Address] if not set
"""
if email_or_more is None:
return self._to
self._parse_addresses(self._to, email_or_more)
return self

def cc(self, email_or_more=None) -> Union["Envelope", List[Address]]:
def cc(self, email_or_more=None) -> Union["Envelope", list[Address]]:
if email_or_more is None:
return self._cc
self._parse_addresses(self._cc, email_or_more)
return self

def bcc(self, email_or_more=None) -> Union["Envelope", List[Address]]:
def bcc(self, email_or_more=None) -> Union["Envelope", list[Address]]:
if email_or_more is None:
return self._bcc
self._parse_addresses(self._bcc, email_or_more)
return self

def reply_to(self, email_or_more=None) -> Union["Envelope", List[Address]]:
def reply_to(self, email_or_more=None) -> Union["Envelope", list[Address]]:
if email_or_more is None:
return self._reply_to
self._parse_addresses(self._reply_to, email_or_more)
Expand Down Expand Up @@ -586,7 +583,7 @@ def subject(self, subject=None, encrypted: Union[str, bool] = None) -> Union["En
self._subject_encrypted = encrypted
return self

def mime(self, subtype=AUTO, nl2br: Literal["auto"] | bool=AUTO):
def mime(self, subtype=AUTO, nl2br: Literal["auto"] | bool = AUTO):
"""
Ignored if `Content-Type` header put to the message.
@type subtype: str Set contents mime subtype: "auto" (default), "html" or "plain" for plain text.
Expand Down Expand Up @@ -1084,7 +1081,6 @@ def _gpg_import_or_fail(self, key):
else:
raise ValueError(f"Could not import key starting: {key[:80]}...")


def _get_gnupg_home(self, for_help=False):
s = self._gpg if type(self._gpg) is str else None
if for_help:
Expand Down Expand Up @@ -1236,7 +1232,7 @@ def _encrypt_gpg_now(self, message, encrypt, sign_fingerprint):
return False

def _gpg_list_keys(self, secret=False):
return ((key, address) for key in self._gnupg.list_keys(secret) for _, address in getaddresses(key["uids"]))
return ((key, address) for key in self._gnupg.list_keys(secret) for _, address in _getaddresses(key["uids"]))

def _gpg_verify(self, signature: bytes, data: bytes):
""" Allows verifying detached GPG signature.
Expand All @@ -1250,7 +1246,7 @@ def _gpg_verify(self, signature: bytes, data: bytes):
fp.seek(0)
return bool(self._gnupg.verify_data(fp.name, data))

def _get_decipherers(self) -> Set[str]:
def _get_decipherers(self) -> set[str]:
"""
:return: Set of e-mail addresses
"""
Expand Down Expand Up @@ -1299,7 +1295,6 @@ def smime_sign_only(self, email, sign):

return signed_email


def smime_sign_encrypt(self, email, sign, encrypt):
from cryptography.hazmat.primitives.serialization import load_pem_private_key, pkcs7
from cryptography.x509 import load_pem_x509_certificate
Expand Down Expand Up @@ -1347,7 +1342,7 @@ def smime_sign_encrypt(self, email, sign, encrypt):
raise ValueError("failed to load certificate from file")

recipient_certs.append(c)

try:
pubkey = load_pem_x509_certificate(pubkey)
except ValueError as e:
Expand All @@ -1360,7 +1355,6 @@ def smime_sign_encrypt(self, email, sign, encrypt):
for recip in recipient_certs:
envelope_builder = envelope_builder.add_recipient(recip)


options = [pkcs7.PKCS7Options.Binary]
encrypted_email = envelope_builder.encrypt(serialization.Encoding.SMIME, options)
return encrypted_email
Expand All @@ -1371,7 +1365,6 @@ def smime_encrypt_only(self, email, encrypt):
from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.primitives import serialization


if self._cert:
certificates = [self._cert]
else:
Expand All @@ -1396,16 +1389,15 @@ def smime_encrypt_only(self, email, encrypt):

return encrypted_email


def _encrypt_smime_now(self, email, sign, encrypt: Union[None, bool, bytes, List[bytes]]):
def _encrypt_smime_now(self, email, sign, encrypt: Union[None, bool, bytes, list[bytes]]):
"""
:type encrypt: Can be None, False, bytes or list[bytes]
"""

# passphrase has to be bytes
if (self._passphrase is not None):
self._passphrase = self._passphrase.encode('utf-8')
self._passphrase = self._passphrase.encode('utf-8')

if sign is not None and type(sign) != bool:
sign = assure_fetched(sign, bytes)
Expand All @@ -1421,8 +1413,6 @@ def _encrypt_smime_now(self, email, sign, encrypt: Union[None, bool, bytes, List

return output



def _compose_gpg_signed(self, email, text, micalg=None):
msg_payload = email
email = EmailMessage()
Expand Down Expand Up @@ -1600,7 +1590,7 @@ def _prepare_email(self, plain: bytes, html: bytes, encrypt_gpg: bool, sign_gpg:
email["Subject"] = self._subject
return email

def recipients(self, *, clear=False) -> Union[Set[Address], 'Envelope']:
def recipients(self, *, clear=False) -> Union[set[Address], 'Envelope']:
""" Return set of all recipients – To, Cc, Bcc
:param: clear If true, all To, Cc and Bcc recipients are removed and the object is returned.
Expand Down Expand Up @@ -1632,7 +1622,7 @@ def _report(self) -> Union[dict, None]:
raise NotImplemented("Current multipart/report has not been impemented."
f"Please post current message as a new issue at {ISSUE_LINK}")

def attachments(self, name=None, inline=None) -> Union[Attachment, List[Attachment], bool]:
def attachments(self, name=None, inline=None) -> Union[Attachment, list[Attachment], bool]:
""" Access the attachments.
XX make available from CLI too
--attachments(-inline)(-enclosed) [name]
Expand Down
9 changes: 4 additions & 5 deletions test_.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from email.utils import getaddresses, parseaddr
import logging
import re
import sys
Expand All @@ -14,7 +13,7 @@
from unittest import main, TestCase, mock

from envelope import Envelope
from envelope.address import Address
from envelope.address import Address, _parseaddr, _getaddresses
from envelope.constants import AUTO, PLAIN, HTML
from envelope.parser import Parser
from envelope.smtp_handler import SMTPHandler
Expand Down Expand Up @@ -1245,11 +1244,11 @@ def test_disguised_addresses(self):
# https://github.com/python/cpython/issues/40889#issuecomment-1094001067
disguise_addr = "[email protected] <[email protected]>"
same = "[email protected] <[email protected]>"
self.assertEqual(('', '[email protected]'), parseaddr(disguise_addr))
self.assertEqual(('', '[email protected]'), _parseaddr(disguise_addr))
self.assertEqual([('', '[email protected]'), ('', '[email protected]')],
getaddresses([disguise_addr]))
_getaddresses([disguise_addr]))
self.assertEqual([('', '[email protected]'), ('', '[email protected]')],
getaddresses([same]))
_getaddresses([same]))

# For the same input, Envelope receives better results.
self.assertEqual(Address(name='[email protected]', address='[email protected]'), Address(disguise_addr))
Expand Down

0 comments on commit 5c0ad37

Please sign in to comment.