Skip to content

Commit

Permalink
mailbox.MailBox splitted to: BaseMailBox, MailBox, MailBoxUnencrypted
Browse files Browse the repository at this point in the history
MailBox ssl argument deleted
mailbox.MessageFlags class moved to utils.MessageFlags
Add PySocks proxy examples
  • Loading branch information
ikvk committed May 18, 2020
1 parent 92a4a41 commit 3df2af5
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 49 deletions.
183 changes: 183 additions & 0 deletions examples/pysocks_proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
"""
MailBox traffic through proxy servers using https://github.com/Anorov/PySocks
NOTE: examples NOT checked!
"""
import ssl
import socks
from imaplib import IMAP4


class Imap4Proxy(IMAP4):
def __init__(self,
host: str = "",
port: int = 143,
p_timeout: int = None,
p_source_address: tuple = None,
p_proxy_type: socks.PROXY_TYPES = "HTTP",
p_proxy_addr: str = None,
p_proxy_port: int = None,
p_proxy_rdns=True,
p_proxy_username: str = None,
p_proxy_password: str = None,
p_socket_options: iter = None,
):
self._host = host
self._port = port
self._p_timeout = p_timeout
self._p_source_address = p_source_address
self._p_proxy_type = p_proxy_type
self._p_proxy_addr = p_proxy_addr
self._p_proxy_port = p_proxy_port
self._p_proxy_rdns = p_proxy_rdns
self._p_proxy_username = p_proxy_username
self._p_proxy_password = p_proxy_password
self._p_socket_options = p_socket_options
super().__init__(host, port)

def _create_socket(self):
return socks.create_connection(
dest_pair=(self._host, self._port),
timeout=self._p_timeout,
source_address=self._p_source_address,
proxy_type=self._p_proxy_type,
proxy_addr=self._p_proxy_addr,
proxy_port=self._p_proxy_port,
proxy_rdns=self._p_proxy_rdns,
proxy_username=self._p_proxy_username,
proxy_password=self._p_proxy_password,
socket_options=self._p_socket_options,
)


class Imap4SslProxy(Imap4Proxy):
def __init__(self,
host: str = "",
port: int = 993,
keyfile=None,
certfile=None,
ssl_context=None,
p_timeout: int = None,
p_source_address: tuple = None,
p_proxy_type: socks.PROXY_TYPES = "HTTP",
p_proxy_addr: str = None,
p_proxy_port: int = None,
p_proxy_rdns=True,
p_proxy_username: str = None,
p_proxy_password: str = None,
p_socket_options: iter = None,
):
self._host = host
self._port = port
self._p_timeout = p_timeout
self._p_source_address = p_source_address
self._p_proxy_type = p_proxy_type
self._p_proxy_addr = p_proxy_addr
self._p_proxy_port = p_proxy_port
self._p_proxy_rdns = p_proxy_rdns
self._p_proxy_username = p_proxy_username
self._p_proxy_password = p_proxy_password
self._p_socket_options = p_socket_options

if ssl_context is not None and keyfile is not None:
raise ValueError("ssl_context and keyfile arguments are mutually exclusive")
if ssl_context is not None and certfile is not None:
raise ValueError("ssl_context and certfile arguments are mutually exclusive")
if keyfile is not None or certfile is not None:
import warnings
warnings.warn("keyfile and certfile are deprecated, use ssl_context instead", DeprecationWarning, 2)

if ssl_context is None:
ssl_context = ssl._create_stdlib_context(certfile=certfile, keyfile=keyfile) # noqa

self.keyfile = keyfile
self.certfile = certfile
self.ssl_context = ssl_context

super().__init__(host, port, p_timeout, p_source_address, p_proxy_type, p_proxy_addr, p_proxy_port,
p_proxy_rdns, p_proxy_username, p_proxy_password, p_socket_options)

def _create_socket(self):
sock = super()._create_socket()
server_hostname = self.host if ssl.HAS_SNI else None
return self.ssl_context.wrap_socket(sock, server_hostname=server_hostname)

def open(self, host='', port=993):
super().open(host, port)


class MailBoxUnencryptedProxy:
"""Working with the email box through IMAP4 through proxy"""

def __init__(self,
host: str = "",
port: int = 143,
p_timeout: int = None,
p_source_address: tuple = None,
p_proxy_type: socks.PROXY_TYPES = "HTTP",
p_proxy_addr: str = None,
p_proxy_port: int = None,
p_proxy_rdns=True,
p_proxy_username: str = None,
p_proxy_password: str = None,
p_socket_options: iter = None,
):
self._host = host
self._port = port
self._p_timeout = p_timeout
self._p_source_address = p_source_address
self._p_proxy_type = p_proxy_type
self._p_proxy_addr = p_proxy_addr
self._p_proxy_port = p_proxy_port
self._p_proxy_rdns = p_proxy_rdns
self._p_proxy_username = p_proxy_username
self._p_proxy_password = p_proxy_password
self._p_socket_options = p_socket_options
super().__init__()

def _get_mailbox_client(self):
return Imap4Proxy(
self._host, self._port,
self._p_timeout, self._p_source_address, self._p_proxy_type, self._p_proxy_addr, self._p_proxy_port,
self._p_proxy_rdns, self._p_proxy_username, self._p_proxy_password, self._p_socket_options)


class MailBoxProxy:
"""Working with the email box through IMAP4 over SSL connection through proxy"""

def __init__(self,
host: str = "",
port: int = 993,
keyfile=None,
certfile=None,
ssl_context=None,
p_timeout: int = None,
p_source_address: tuple = None,
p_proxy_type: socks.PROXY_TYPES = "HTTP",
p_proxy_addr: str = None,
p_proxy_port: int = None,
p_proxy_rdns=True,
p_proxy_username: str = None,
p_proxy_password: str = None,
p_socket_options: iter = None,
):
self._host = host
self._port = port
self._keyfile = keyfile
self._certfile = certfile
self._ssl_context = ssl_context
self._p_timeout = p_timeout
self._p_source_address = p_source_address
self._p_proxy_type = p_proxy_type
self._p_proxy_addr = p_proxy_addr
self._p_proxy_port = p_proxy_port
self._p_proxy_rdns = p_proxy_rdns
self._p_proxy_username = p_proxy_username
self._p_proxy_password = p_proxy_password
self._p_socket_options = p_socket_options
super().__init__()

def _get_mailbox_client(self):
return Imap4SslProxy(
self._host, self._port, self._keyfile, self._certfile, self._ssl_context,
self._p_timeout, self._p_source_address, self._p_proxy_type, self._p_proxy_addr, self._p_proxy_port,
self._p_proxy_rdns, self._p_proxy_username, self._p_proxy_password, self._p_socket_options)
2 changes: 1 addition & 1 deletion imap_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
from .folder import *
from .utils import *

__version__ = '0.14.3'
__version__ = '0.15.0'
95 changes: 49 additions & 46 deletions imap_tools/mailbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,31 @@

from .message import MailMessage
from .folder import MailBoxFolderManager
from .utils import cleaned_uid_set, check_command_status
from .utils import cleaned_uid_set, check_command_status, MessageFlags

# Maximal line length when calling readline(). This is to prevent reading arbitrary length lines.
imaplib._MAXLINE = 4 * 1024 * 1024 # 4Mb


class MessageFlags:
"""Standard email message flags"""
SEEN = 'SEEN'
ANSWERED = 'ANSWERED'
FLAGGED = 'FLAGGED'
DELETED = 'DELETED'
DRAFT = 'DRAFT'
RECENT = 'RECENT'
all = (
SEEN, ANSWERED, FLAGGED, DELETED, DRAFT, RECENT
)


class MailBox:
"""Working with the email box through IMAP4"""
class BaseMailBox:
"""Working with the email box"""

email_message_class = MailMessage
folder_manager_class = MailBoxFolderManager

def __init__(self, host='', port=None, ssl=True, keyfile=None, certfile=None, ssl_context=None):
"""
:param host: host's name (default: localhost)
:param port: port number (default: standard IMAP4 SSL port)
:param ssl: use client class over SSL connection (IMAP4_SSL) if True, else use IMAP4
:param keyfile: PEM formatted file that contains your private key (default: None)
:param certfile: PEM formatted certificate chain file (default: None)
:param ssl_context: SSLContext object that contains your certificate chain and private key (default: None)
Note: if ssl_context is provided, then parameters keyfile or
certfile should not be set otherwise ValueError is raised.
"""
self._host = host
self._port = port
self._keyfile = keyfile
self._certfile = certfile
self._ssl_context = ssl_context
if ssl:
self.box = imaplib.IMAP4_SSL(
host, port or imaplib.IMAP4_SSL_PORT, keyfile, certfile, ssl_context)
else:
self.box = imaplib.IMAP4(host, port or imaplib.IMAP4_PORT)
self._username = None
self._password = None
self._initial_folder = None
self.folder = None
def __init__(self):
self.folder = None # folder manager
self.login_result = None
self.box = self._get_mailbox_client()

def _get_mailbox_client(self) -> imaplib.IMAP4:
raise NotImplementedError

def login(self, username: str, password: str, initial_folder: str = 'INBOX'):
self._username = username
self._password = password
self._initial_folder = initial_folder
result = self.box.login(self._username, self._password)
result = self.box.login(username, password)
check_command_status('box.login', result)
self.folder = self.folder_manager_class(self)
self.folder.set(self._initial_folder)
self.folder.set(initial_folder)
self.login_result = result
return self # return self in favor of context manager

Expand Down Expand Up @@ -182,3 +147,41 @@ def __enter__(self):

def __exit__(self, exc_type, exc_value, exc_traceback):
self.logout()


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

def __init__(self, host='', port=143):
"""
:param host: host's name (default: localhost)
:param port: port number
"""
self._host = host
self._port = port
super().__init__()

def _get_mailbox_client(self):
return imaplib.IMAP4(self._host, self._port)


class MailBox(BaseMailBox):
"""Working with the email box through IMAP4 over SSL connection"""

def __init__(self, host='', port=993, keyfile=None, certfile=None, ssl_context=None):
"""
:param host: host's name (default: localhost)
:param port: port number
: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
"""
self._host = host
self._port = port
self._keyfile = keyfile
self._certfile = certfile
self._ssl_context = ssl_context
super().__init__()

def _get_mailbox_client(self):
return imaplib.IMAP4_SSL(self._host, self._port, self._keyfile, self._certfile, self._ssl_context)
13 changes: 13 additions & 0 deletions imap_tools/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,16 @@ def pairs_to_dict(items: list) -> dict:
if len(items) % 2 != 0:
raise ValueError('An even-length array is expected')
return dict((items[i * 2], items[i * 2 + 1]) for i in range(len(items) // 2))


class MessageFlags:
"""Standard email message flags"""
SEEN = 'SEEN'
ANSWERED = 'ANSWERED'
FLAGGED = 'FLAGGED'
DELETED = 'DELETED'
DRAFT = 'DRAFT'
RECENT = 'RECENT'
all = (
SEEN, ANSWERED, FLAGGED, DELETED, DRAFT, RECENT
)
7 changes: 7 additions & 0 deletions release_notes.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
0.15.0
======
* mailbox.MailBox splitted to: BaseMailBox, MailBox, MailBoxUnencrypted
* MailBox ssl argument deleted
* mailbox.MessageFlags class moved to utils.MessageFlags
* Add PySocks proxy examples

0.14.3
======
* Fixed multiple encodings case for attachment name
Expand Down
2 changes: 0 additions & 2 deletions todo.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
try to imitate long poll by NOOP

check https://docs.python.org/release/3.8.1/library/email.utils.html

check possibility to implement SOCKS without/with dependencies

0 comments on commit 3df2af5

Please sign in to comment.