Skip to content

Commit

Permalink
MailBoxTls (#157)
Browse files Browse the repository at this point in the history
* MailBoxTls
  • Loading branch information
ikvk authored Mar 6, 2022
1 parent 69dfa95 commit d509806
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 41 deletions.
7 changes: 4 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,9 @@ Info about lib are at: *this page*, issues, pull requests, examples, source, sta
subjects = [msg.subject for msg in mailbox.fetch(AND(all=True))]
mailbox.logout()
MailBox(BaseMailBox), MailBoxUnencrypted(BaseMailBox) - for create mailbox instance.
MailBox, MailBoxTls, MailBoxUnencrypted - for create mailbox client. `TLS example <https://github.com/ikvk/imap_tools/blob/master/examples/tls.py>`_.

BaseMailBox.login, MailBox.xoauth2 - authentication functions. TLS connection
`example <https://github.com/ikvk/imap_tools/blob/master/examples/tls.py>`_.
BaseMailBox.login, MailBox.xoauth2 - authentication functions.

BaseMailBox.fetch - first searches email nums by criteria in current folder, then fetch and yields `MailMessage <#email-attributes>`_:

Expand All @@ -79,6 +78,8 @@ BaseMailBox.<action> - `copy, move, delete, flag, append <#actions-with-emails>`

BaseMailBox.folder - `folder manager <#actions-with-folders>`_

BaseMailBox.idle - `idle manager <#idle-workflow>`_

BaseMailBox.numbers - search mailbox for matching message numbers in current folder, returns [str]

BaseMailBox.box - imaplib.IMAP4/IMAP4_SSL client instance.
Expand Down
4 changes: 4 additions & 0 deletions docs/release_notes.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
0.52.0
======
* [Breaking] STARTTLS logic moved to MailBoxTls

0.51.1
======
* Fix IdleManager
Expand Down
2 changes: 1 addition & 1 deletion imap_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
from .utils import EmailAddress
from .errors import *

__version__ = '0.51.1'
__version__ = '0.52.0'
2 changes: 0 additions & 2 deletions imap_tools/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

UID_PATTERN = re.compile(r'(^|\s+|\W)UID\s+(?P<uid>\d+)')

TIMEOUT_ARG_SUPPORT_ERROR = 'imaplib.IMAP4 timeout argument supported since python 3.9'


class MailMessageFlags:
"""
Expand Down
85 changes: 50 additions & 35 deletions imap_tools/mailbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
from collections import UserString
from typing import AnyStr, Optional, List, Iterable, Sequence, Union, Tuple

from .consts import UID_PATTERN, TIMEOUT_ARG_SUPPORT_ERROR
from .consts import UID_PATTERN
from .message import MailMessage
from .folder import MailBoxFolderManager
from .idle import IdleManager
from .utils import clean_uids, check_command_status, chunks, encode_folder, clean_flags, decode_value
from .utils import clean_uids, check_command_status, chunks, encode_folder, clean_flags, decode_value, \
check_timeout_arg_support
from .errors import MailboxStarttlsError, MailboxLoginError, MailboxLogoutError, MailboxNumbersError, \
MailboxFetchError, MailboxExpungeError, MailboxDeleteError, MailboxCopyError, MailboxFlagError, \
MailboxAppendError, MailboxUidsError, MailboxTaggedResponseError
Expand All @@ -20,6 +21,8 @@

Criteria = Union[AnyStr, UserString]

PYTHON_VERSION_MINOR = sys.version_info.minor


class BaseMailBox:
"""Working with the email box"""
Expand Down Expand Up @@ -224,7 +227,7 @@ def append(self, message: Union[MailMessage, bytes],
:param flag_set: email message flags, no flags by default. System flags at consts.MailMessageFlags.all
:return: command results
"""
if sys.version_info.minor < 6:
if PYTHON_VERSION_MINOR < 6:
timezone = datetime.timezone(datetime.timedelta(hours=0))
else:
timezone = datetime.datetime.now().astimezone().tzinfo # system timezone
Expand All @@ -239,6 +242,16 @@ def append(self, message: Union[MailMessage, bytes],
check_command_status(append_result, MailboxAppendError)
return append_result

def xoauth2(self, username: str, access_token: str, initial_folder: str = 'INBOX') -> 'BaseMailBox':
"""Authenticate to account using OAuth 2.0 mechanism"""
auth_string = 'user={}\1auth=Bearer {}\1\1'.format(username, access_token)
result = self.box.authenticate('XOAUTH2', lambda x: auth_string) # noqa
check_command_status(result, MailboxLoginError)
self.folder = self.folder_manager_class(self)
self.folder.set(initial_folder)
self.login_result = result
return self


class MailBoxUnencrypted(BaseMailBox):
"""Working with the email box through IMAP4"""
Expand All @@ -249,15 +262,14 @@ def __init__(self, host='', port=143, timeout=None):
:param port: port number
:param timeout: timeout in seconds for the connection attempt, since python 3.9
"""
if timeout and sys.version_info.minor < 9:
raise ValueError(TIMEOUT_ARG_SUPPORT_ERROR)
check_timeout_arg_support(timeout)
self._host = host
self._port = port
self._timeout = timeout
super().__init__()

def _get_mailbox_client(self) -> imaplib.IMAP4:
if sys.version_info.minor < 9:
if PYTHON_VERSION_MINOR < 9:
return imaplib.IMAP4(self._host, self._port)
else:
return imaplib.IMAP4(self._host, self._port, self._timeout) # noqa
Expand All @@ -266,51 +278,54 @@ def _get_mailbox_client(self) -> imaplib.IMAP4:
class MailBox(BaseMailBox):
"""Working with the email box through IMAP4 over SSL connection"""

def __init__(self, host='', port=993, timeout=None, keyfile=None, certfile=None, ssl_context=None, starttls=False):
def __init__(self, host='', port=993, timeout=None, keyfile=None, certfile=None, ssl_context=None):
"""
:param host: host's name (default: localhost)
:param port: port number
:param timeout: timeout in seconds for the connection attempt, since python 3.9
:param keyfile: PEM formatted file that contains your private key (deprecated)
:param certfile: PEM formatted certificate chain file (deprecated)
:param ssl_context: SSLContext object that contains your certificate chain and private key
:param starttls: whether to use starttls
"""
if timeout and sys.version_info.minor < 9:
raise ValueError(TIMEOUT_ARG_SUPPORT_ERROR)
check_timeout_arg_support(timeout)
self._host = host
self._port = port
self._timeout = timeout
self._keyfile = keyfile
self._certfile = certfile
self._ssl_context = ssl_context
self._starttls = starttls
super().__init__()

def _get_mailbox_client(self) -> imaplib.IMAP4:
if self._starttls:
if self._keyfile or self._certfile:
raise ValueError("starttls cannot be combined with keyfile neither with certfile.")
if sys.version_info.minor < 9:
client = imaplib.IMAP4(self._host, self._port)
else:
client = imaplib.IMAP4(self._host, self._port, self._timeout) # noqa
result = client.starttls(self._ssl_context)
check_command_status(result, MailboxStarttlsError)
return client
if PYTHON_VERSION_MINOR < 9:
return imaplib.IMAP4_SSL(self._host, self._port, self._keyfile, self._certfile, self._ssl_context)
else:
if sys.version_info.minor < 9:
return imaplib.IMAP4_SSL(self._host, self._port, self._keyfile, self._certfile, self._ssl_context)
else:
return imaplib.IMAP4_SSL(self._host, self._port, self._keyfile, # noqa
self._certfile, self._ssl_context, self._timeout)
return imaplib.IMAP4_SSL(self._host, self._port, self._keyfile, self._certfile, self._ssl_context,
self._timeout)

def xoauth2(self, username: str, access_token: str, initial_folder: str = 'INBOX') -> 'BaseMailBox':
"""Authenticate to account using OAuth 2.0 mechanism"""
auth_string = 'user={}\1auth=Bearer {}\1\1'.format(username, access_token)
result = self.box.authenticate('XOAUTH2', lambda x: auth_string) # noqa
check_command_status(result, MailboxLoginError)
self.folder = self.folder_manager_class(self)
self.folder.set(initial_folder)
self.login_result = result
return self

class MailBoxTls(BaseMailBox):
"""Working with the email box through IMAP4 with STARTTLS"""

def __init__(self, host='', port=993, timeout=None, ssl_context=None):
"""
:param host: host's name (default: localhost)
:param port: port number
:param timeout: timeout in seconds for the connection attempt, since python 3.9
:param ssl_context: SSLContext object that contains your certificate chain and private key
"""
check_timeout_arg_support(timeout)
self._host = host
self._port = port
self._timeout = timeout
self._ssl_context = ssl_context
super().__init__()

def _get_mailbox_client(self) -> imaplib.IMAP4:
if PYTHON_VERSION_MINOR < 9:
client = imaplib.IMAP4(self._host, self._port)
else:
client = imaplib.IMAP4(self._host, self._port, self._timeout) # noqa
result = client.starttls(self._ssl_context)
check_command_status(result, MailboxStarttlsError)
return client
7 changes: 7 additions & 0 deletions imap_tools/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import sys
import datetime
from itertools import zip_longest
from email.utils import getaddresses, parsedate_to_datetime
Expand Down Expand Up @@ -184,3 +185,9 @@ def clean_flags(flag_set: Union[str, Iterable[str]]) -> List[str]:
if flag.upper() not in upper_sys_flags and flag.startswith('\\'):
raise ValueError('Non system flag must not start with "\\"')
return flag_set


def check_timeout_arg_support(timeout):
"""If timeout arg not supports - raise ValueError"""
if timeout is not None and sys.version_info.minor < 9:
raise ValueError('imaplib.IMAP4 timeout argument supported since python 3.9')

0 comments on commit d509806

Please sign in to comment.