Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85abe48c8c | ||
|
|
9956d182eb | ||
|
|
ef68d92021 | ||
|
|
43a37818ab | ||
|
|
7b1bda6126 | ||
|
|
fe965e7d19 | ||
|
|
cfca92df60 | ||
|
|
5664e9c48a | ||
|
|
6c11c759c0 | ||
|
|
2f72aa13df | ||
|
|
ffd4550524 | ||
|
|
14c592136e | ||
|
|
85025b34ff | ||
|
|
ed251ce999 | ||
|
|
6dc0e7b3f2 | ||
|
|
79601bef7a | ||
|
|
88143c7a1b | ||
|
|
7c6cc2fb5f | ||
|
|
5460bec4b5 | ||
|
|
ea4fd7d9ea | ||
|
|
c64bbd73e9 | ||
|
|
acfb2adc47 | ||
|
|
050e2d8f7f | ||
|
|
6e0ee232fe | ||
|
|
a490081e57 | ||
|
|
a8b5ce1c31 | ||
|
|
7c5a639cc8 | ||
|
|
a78641c79a | ||
|
|
7dad0edb72 | ||
|
|
2ed728485b | ||
|
|
4559149dc0 | ||
|
|
0a98b960ac | ||
|
|
ed86228e86 | ||
|
|
eadddd6c0b | ||
|
|
878c7991bf | ||
|
|
8a537de2f9 | ||
|
|
da450551d4 | ||
|
|
61a6c87fe6 | ||
|
|
05683b3765 |
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,3 +1,16 @@
|
||||
## 0.9.5 (5 December 2017)
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* `date__on` support: ([#109](https://github.com/martinrusev/imbox/pull/109))
|
||||
* Starttls support: ([#108](https://github.com/martinrusev/imbox/pull/108))
|
||||
* Mark emails as flagged/starred: ([#107](https://github.com/martinrusev/imbox/pull/107))
|
||||
* Messages filter can use date objects instead of stringified dates: ([#104](https://github.com/martinrusev/imbox/pull/104))
|
||||
* Fix attachment parsing when a semicolon character ends the Content-Disposition line: ([#100](https://github.com/martinrusev/imbox/pull/100))
|
||||
* Parsing - UnicecodeDecodeError() fixes: ([#96](https://github.com/martinrusev/imbox/pull/96))
|
||||
* Imbox() `with` support: ([#92](https://github.com/martinrusev/imbox/pull/92))
|
||||
|
||||
|
||||
## 0.9 (18 September 2017)
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
include LICENSE
|
||||
include MANIFEST.in
|
||||
include README.rst
|
||||
include CHANGELOG.md
|
||||
graft tests
|
||||
|
||||
43
README.rst
43
README.rst
@@ -12,7 +12,7 @@ Python library for reading IMAP mailboxes and converting email content to machin
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Python (3.2, 3.3, 3.4, 3.5, 3.6)
|
||||
Python (3.3, 3.4, 3.5, 3.6)
|
||||
|
||||
|
||||
Installation
|
||||
@@ -34,7 +34,10 @@ Usage
|
||||
username='username',
|
||||
password='password',
|
||||
ssl=True,
|
||||
ssl_context=None) as imbox:
|
||||
ssl_context=None,
|
||||
starttls=False) as imbox:
|
||||
# Get all folders
|
||||
status, folders_with_additional_info = imbox.folders()
|
||||
|
||||
# Gets all messages
|
||||
all_messages = imbox.messages()
|
||||
@@ -49,10 +52,13 @@ Usage
|
||||
messages_from = imbox.messages(sent_to='martin@amon.cx')
|
||||
|
||||
# Messages received before specific date
|
||||
messages_from = imbox.messages(date__lt='31-July-2013')
|
||||
messages_from = imbox.messages(date__lt=datetime.date(2013, 7, 31))
|
||||
|
||||
# Messages received after specific date
|
||||
messages_from = imbox.messages(date__gt='30-July-2013')
|
||||
messages_from = imbox.messages(date__gt=datetime.date(2013, 7, 30))
|
||||
|
||||
# Messages received on a specific date
|
||||
messages_from = imbox.messages(date__on=datetime.date(2013, 7, 30))
|
||||
|
||||
# Messages from a specific folder
|
||||
messages_folder = imbox.messages(folder='Social')
|
||||
@@ -113,5 +119,34 @@ Usage
|
||||
'subject': u 'Hello John, How are you today'
|
||||
}
|
||||
|
||||
# With the message id, several actions on the message are available:
|
||||
# delete the message
|
||||
imbox.delete(uid)
|
||||
|
||||
# mark the message as read
|
||||
imbox.mark_seen(uid)
|
||||
|
||||
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
`Changelog <https://github.com/martinrusev/imbox/blob/master/CHANGELOG.md>`_
|
||||
|
||||
|
||||
Running the tests
|
||||
-----------------
|
||||
|
||||
You can run the imbox tests with ``tox``.
|
||||
|
||||
Requirements:
|
||||
* the supported python versions
|
||||
* ``tox``. Tox is packaged in Debian and derivatives distributions.
|
||||
|
||||
On Ubuntu, you can install several python versions with:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
sudo add-apt-repository ppa:deadsnakes/ppa
|
||||
sudo apt update
|
||||
sudo apt install python3.X
|
||||
|
||||
@@ -9,17 +9,17 @@ logger = logging.getLogger(__name__)
|
||||
class Imbox:
|
||||
|
||||
def __init__(self, hostname, username=None, password=None, ssl=True,
|
||||
port=None, ssl_context=None, policy=None):
|
||||
port=None, ssl_context=None, policy=None, starttls=False):
|
||||
|
||||
self.server = ImapTransport(hostname, ssl=ssl, port=port,
|
||||
ssl_context=ssl_context)
|
||||
ssl_context=ssl_context, starttls=starttls)
|
||||
self.hostname = hostname
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.parser_policy = policy
|
||||
self.connection = self.server.connect(username, password)
|
||||
logger.info("Connected to IMAP Server with user {username} on {hostname}{ssl}".format(
|
||||
hostname=hostname, username=username, ssl=(" over SSL" if ssl else "")))
|
||||
hostname=hostname, username=username, ssl=(" over SSL" if ssl or starttls else "")))
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
@@ -60,6 +60,10 @@ class Imbox:
|
||||
logger.info("Mark UID {} with \\Seen FLAG".format(int(uid)))
|
||||
self.connection.uid('STORE', uid, '+FLAGS', '(\\Seen)')
|
||||
|
||||
def mark_flag(self, uid):
|
||||
logger.info("Mark UID {} with \\Flagged FLAG".format(int(uid)))
|
||||
self.connection.uid('STORE', uid, '+FLAGS', '(\\Flagged)')
|
||||
|
||||
def delete(self, uid):
|
||||
logger.info("Mark UID {} with \\Deleted FLAG and expunge.".format(int(uid)))
|
||||
mov, data = self.connection.uid('STORE', uid, '+FLAGS', '(\\Deleted)')
|
||||
|
||||
@@ -8,7 +8,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class ImapTransport:
|
||||
|
||||
def __init__(self, hostname, port=None, ssl=True, ssl_context=None):
|
||||
def __init__(self, hostname, port=None, ssl=True, ssl_context=None, starttls=False):
|
||||
self.hostname = hostname
|
||||
self.port = port
|
||||
kwargs = {}
|
||||
@@ -26,6 +26,8 @@ class ImapTransport:
|
||||
self.port = 143
|
||||
|
||||
self.server = self.transport(self.hostname, self.port, **kwargs)
|
||||
if starttls:
|
||||
self.server.starttls()
|
||||
logger.debug("Created IMAP4 transport for {host}:{port}"
|
||||
.format(host=self.hostname, port=self.port))
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ def decode_param(param):
|
||||
if type_ == 'Q':
|
||||
value = quopri.decodestring(code)
|
||||
elif type_ == 'B':
|
||||
value = base64.decodestring(code)
|
||||
value = base64.decodebytes(code.encode())
|
||||
value = str_encode(value, encoding)
|
||||
value_results.append(value)
|
||||
if value_results:
|
||||
@@ -82,7 +82,11 @@ def parse_attachment(message_part):
|
||||
# Check again if this is a valid attachment
|
||||
content_disposition = message_part.get("Content-Disposition", None)
|
||||
if content_disposition is not None and not message_part.is_multipart():
|
||||
dispositions = content_disposition.strip().split(";")
|
||||
dispositions = [
|
||||
disposition.strip()
|
||||
for disposition in content_disposition.split(";")
|
||||
if disposition.strip()
|
||||
]
|
||||
|
||||
if dispositions[0].lower() in ["attachment", "inline"]:
|
||||
file_data = message_part.get_payload(decode=True)
|
||||
@@ -97,13 +101,14 @@ def parse_attachment(message_part):
|
||||
attachment['filename'] = filename
|
||||
|
||||
for param in dispositions[1:]:
|
||||
name, value = decode_param(param)
|
||||
if param:
|
||||
name, value = decode_param(param)
|
||||
|
||||
if 'file' in name:
|
||||
attachment['filename'] = value
|
||||
if 'file' in name:
|
||||
attachment['filename'] = value[1:-1] if value.startswith('"') else value
|
||||
|
||||
if 'create-date' in name:
|
||||
attachment['create-date'] = value
|
||||
if 'create-date' in name:
|
||||
attachment['create-date'] = value
|
||||
|
||||
return attachment
|
||||
|
||||
@@ -121,7 +126,7 @@ def decode_content(message):
|
||||
|
||||
def parse_email(raw_email, policy=None):
|
||||
if isinstance(raw_email, bytes):
|
||||
raw_email = str_encode(raw_email, 'utf-8')
|
||||
raw_email = str_encode(raw_email, 'utf-8', errors='ignore')
|
||||
if policy is not None:
|
||||
email_parse_kwargs = dict(policy=policy)
|
||||
else:
|
||||
@@ -154,7 +159,7 @@ def parse_email(raw_email, policy=None):
|
||||
content = decode_content(part)
|
||||
|
||||
is_inline = content_disposition is None \
|
||||
or content_disposition == "inline"
|
||||
or content_disposition.startswith("inline")
|
||||
if content_type == "text/plain" and is_inline:
|
||||
body['plain'].append(content)
|
||||
elif content_type == "text/html" and is_inline:
|
||||
|
||||
@@ -4,27 +4,25 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
IMAP_MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
||||
|
||||
|
||||
def format_date(date):
|
||||
|
||||
return "%s-%s-%s" % (date.day, IMAP_MONTHS[date.month - 1], date.year)
|
||||
if isinstance(date, datetime.date):
|
||||
return date.strftime('%d-%b-%Y')
|
||||
else:
|
||||
return date
|
||||
|
||||
|
||||
def build_search_query(**kwargs):
|
||||
|
||||
# Parse keyword arguments
|
||||
unread = kwargs.get('unread', False)
|
||||
unflagged = kwargs.get('unflagged', False)
|
||||
flagged = kwargs.get('flagged', False)
|
||||
sent_from = kwargs.get('sent_from', False)
|
||||
sent_to = kwargs.get('sent_to', False)
|
||||
date__gt = kwargs.get('date__gt', False)
|
||||
if type(date__gt) is datetime.date:
|
||||
date__gt = format_date(date__gt)
|
||||
date__lt = kwargs.get('date__lt', False)
|
||||
if type(date__lt) is datetime.date:
|
||||
date__lt = format_date(date__lt)
|
||||
date__on = kwargs.get('date__on', False)
|
||||
subject = kwargs.get('subject')
|
||||
|
||||
query = []
|
||||
@@ -32,6 +30,12 @@ def build_search_query(**kwargs):
|
||||
if unread:
|
||||
query.append("(UNSEEN)")
|
||||
|
||||
if unflagged:
|
||||
query.append("(UNFLAGGED)")
|
||||
|
||||
if flagged:
|
||||
query.append("(FLAGGED)")
|
||||
|
||||
if sent_from:
|
||||
query.append('(FROM "%s")' % sent_from)
|
||||
|
||||
@@ -39,10 +43,13 @@ def build_search_query(**kwargs):
|
||||
query.append('(TO "%s")' % sent_to)
|
||||
|
||||
if date__gt:
|
||||
query.append('(SINCE "%s")' % date__gt)
|
||||
query.append('(SINCE "%s")' % format_date(date__gt))
|
||||
|
||||
if date__lt:
|
||||
query.append('(BEFORE "%s")' % date__lt)
|
||||
query.append('(BEFORE "%s")' % format_date(date__lt))
|
||||
|
||||
if date__on:
|
||||
query.append('(ON "%s")' % format_date(date__on))
|
||||
|
||||
if subject is not None:
|
||||
query.append('(SUBJECT "%s")' % subject)
|
||||
|
||||
4
setup.py
4
setup.py
@@ -1,7 +1,7 @@
|
||||
from setuptools import setup
|
||||
import os
|
||||
|
||||
version = '0.9'
|
||||
version = '0.9.5'
|
||||
|
||||
|
||||
def read(filename):
|
||||
@@ -22,10 +22,10 @@ setup(
|
||||
zip_safe=False,
|
||||
classifiers=(
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6'
|
||||
),
|
||||
test_suite='tests',
|
||||
)
|
||||
|
||||
22
tests/8422.msg
Normal file
22
tests/8422.msg
Normal file
@@ -0,0 +1,22 @@
|
||||
Delivered-To: receiver@example.com
|
||||
Return-Path: <sender@example.com>
|
||||
Date: Thu, 20 Jul 2017 07:34:22 -0500
|
||||
Message-ID: <59705CFE.A95F.0016.0@journeys.com>
|
||||
Subject: Following up Re: Looking to connect, let's schedule a call!
|
||||
From: sender@example.com
|
||||
To: "Receiver" <receiver@example.com>
|
||||
Mime-Version: 1.0
|
||||
Content-Type: multipart/mixed; boundary="=__PartBD85995F.0__="
|
||||
|
||||
This is a MIME message. If you are reading this text, you may want to
|
||||
consider changing to a mail reader or gateway that understands how to
|
||||
properly handle MIME multipart messages.
|
||||
|
||||
--=__PartBD85995F.0__=
|
||||
Content-Type: multipart/alternative; boundary="=__PartBD85995F.1__="
|
||||
|
||||
--=__PartBD85995F.1__=
|
||||
Content-Type: text/plain; charset=Windows-1252
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Following up on my previous message. I’d love to connect you with
|
||||
@@ -1,6 +1,7 @@
|
||||
import unittest
|
||||
from imbox.parser import *
|
||||
|
||||
import os
|
||||
import sys
|
||||
if sys.version_info.minor < 3:
|
||||
SMTP = False
|
||||
@@ -8,6 +9,9 @@ else:
|
||||
from email.policy import SMTP
|
||||
|
||||
|
||||
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
raw_email = """Delivered-To: johndoe@gmail.com
|
||||
X-Originating-Email: [martin@amon.cx]
|
||||
Message-ID: <test0@example.com>
|
||||
@@ -80,6 +84,197 @@ Content-Transfer-Encoding: quoted-printable
|
||||
"""
|
||||
|
||||
|
||||
raw_email_encoded_multipart = b"""Delivered-To: receiver@example.com
|
||||
Return-Path: <kkoudelka@wallvet.com>
|
||||
Date: Tue, 08 Aug 2017 08:15:11 -0700
|
||||
From: <kkoudelka@wallvet.com>
|
||||
To: interviews+347243@gethappie.me
|
||||
Message-Id: <20170808081511.2b876c018dd94666bcc18e28cf079afb.99766f164b.wbe@email24.godaddy.com>
|
||||
Subject: RE: Kari, are you open to this?
|
||||
Mime-Version: 1.0
|
||||
Content-Type: multipart/related;
|
||||
boundary="=_7c18e0b95b772890a22ed6c0f810a434"
|
||||
|
||||
--=_7c18e0b95b772890a22ed6c0f810a434
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Type: text/html; charset="utf-8"
|
||||
|
||||
<html><body><span style=3D"font-family:Verdana; color:#000; font-size:10pt;=
|
||||
"><div>Hi Richie,</div></span></body></html>
|
||||
--=_7c18e0b95b772890a22ed6c0f810a434
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Type: image/jpeg; charset=binary;
|
||||
name="sigimg0";
|
||||
Content-Disposition: inline;
|
||||
filename="sigimg0";
|
||||
|
||||
/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg
|
||||
jt0JaKhjm3xq23GR60UuZBZn/9k=
|
||||
--=_7c18e0b95b772890a22ed6c0f810a434
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Type: image/jpeg; charset=binary;
|
||||
name="sigimg1";
|
||||
Content-Disposition: inline;
|
||||
filename="sigimg1";
|
||||
|
||||
/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg
|
||||
SlBFRyB2NjIpLCBkZWZhdWx0IHF1YWxpdHkK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMP
|
||||
ooooA//Z
|
||||
--=_7c18e0b95b772890a22ed6c0f810a434--
|
||||
|
||||
"""
|
||||
|
||||
|
||||
raw_email_encoded_bad_multipart = b"""Delivered-To: receiver@example.com
|
||||
Return-Path: <sender@example.com>
|
||||
From: sender@example.com
|
||||
To: "Receiver" <receiver@example.com>, "Second\r\n Receiver" <recipient@example.com>
|
||||
Subject: Re: Looking to connect with you...
|
||||
Date: Thu, 20 Apr 2017 15:32:52 +0000
|
||||
Message-ID: <BN6PR16MB179579288933D60C4016D078C31B0@BN6PR16MB1795.namprd16.prod.outlook.com>
|
||||
Content-Type: multipart/related;
|
||||
boundary="_004_BN6PR16MB179579288933D60C4016D078C31B0BN6PR16MB1795namp_";
|
||||
type="multipart/alternative"
|
||||
MIME-Version: 1.0
|
||||
--_004_BN6PR16MB179579288933D60C4016D078C31B0BN6PR16MB1795namp_
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="_000_BN6PR16MB179579288933D60C4016D078C31B0BN6PR16MB1795namp_"
|
||||
--_000_BN6PR16MB179579288933D60C4016D078C31B0BN6PR16MB1795namp_
|
||||
Content-Type: text/plain; charset="utf-8"
|
||||
Content-Transfer-Encoding: base64
|
||||
SGkgRGFuaWVsbGUsDQoNCg0KSSBhY3R1YWxseSBhbSBoYXBweSBpbiBteSBjdXJyZW50IHJvbGUs
|
||||
Y3J1aXRlciB8IENoYXJsb3R0ZSwgTkMNClNlbnQgdmlhIEhhcHBpZQ0KDQoNCg==
|
||||
--_000_BN6PR16MB179579288933D60C4016D078C31B0BN6PR16MB1795namp_
|
||||
Content-Type: text/html; charset="utf-8"
|
||||
Content-Transfer-Encoding: base64
|
||||
PGh0bWw+DQo8aGVhZD4NCjxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIgY29udGVudD0i
|
||||
CjwvZGl2Pg0KPC9kaXY+DQo8L2JvZHk+DQo8L2h0bWw+DQo=
|
||||
--_000_BN6PR16MB179579288933D60C4016D078C31B0BN6PR16MB1795namp_--
|
||||
--_004_BN6PR16MB179579288933D60C4016D078C31B0BN6PR16MB1795namp_
|
||||
Content-Type: image/png; name="=?utf-8?B?T3V0bG9va0Vtb2ppLfCfmIoucG5n?="
|
||||
Content-Description: =?utf-8?B?T3V0bG9va0Vtb2ppLfCfmIoucG5n?=
|
||||
Content-Disposition: inline;
|
||||
filename="=?utf-8?B?T3V0bG9va0Vtb2ppLfCfmIoucG5n?="; size=488;
|
||||
creation-date="Thu, 20 Apr 2017 15:32:52 GMT";
|
||||
modification-date="Thu, 20 Apr 2017 15:32:52 GMT"
|
||||
Content-ID: <254962e2-f05c-40d1-aa11-0d34671b056c>
|
||||
Content-Transfer-Encoding: base64
|
||||
iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ
|
||||
cvED9AIR3TCAAAMAqh+p+YMVeBQAAAAASUVORK5CYII=
|
||||
--_004_BN6PR16MB179579288933D60C4016D078C31B0BN6PR16MB1795namp_--
|
||||
"""
|
||||
|
||||
|
||||
raw_email_encoded_another_bad_multipart = b"""Delivered-To: receiver@example.com
|
||||
Return-Path: <sender@example.com>
|
||||
Mime-Version: 1.0
|
||||
Date: Wed, 22 Mar 2017 15:21:55 -0500
|
||||
Message-ID: <58D29693.192A.0075.1@wimort.com>
|
||||
Subject: Re: Reaching Out About Peoples Home Equity
|
||||
From: sender@example.com
|
||||
To: receiver@example.com
|
||||
Content-Type: multipart/alternative; boundary="____NOIBTUQXSYRVOOAFLCHY____"
|
||||
|
||||
|
||||
--____NOIBTUQXSYRVOOAFLCHY____
|
||||
Content-Type: text/plain; charset=iso-8859-15
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Disposition: inline;
|
||||
modification-date="Wed, 22 Mar 2017 15:21:55 -0500"
|
||||
|
||||
Chloe,
|
||||
|
||||
--____NOIBTUQXSYRVOOAFLCHY____
|
||||
Content-Type: multipart/related; boundary="____XTSWHCFJMONXSVGPVDLY____"
|
||||
|
||||
|
||||
--____XTSWHCFJMONXSVGPVDLY____
|
||||
Content-Type: text/html; charset=iso-8859-15
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Disposition: inline;
|
||||
modification-date="Wed, 22 Mar 2017 15:21:55 -0500"
|
||||
|
||||
<HTML xmlns=3D"http://www.w3.org/1999/xhtml">
|
||||
<BODY style=3D"COLOR: black; FONT: 10pt Segoe UI; MARGIN: 4px 4px 1px" =
|
||||
leftMargin=3D0 topMargin=3D0 offset=3D"0" marginwidth=3D"0" marginheight=3D=
|
||||
"0">
|
||||
<DIV>Chloe,</DIV>
|
||||
<IMG src=3D"cid:VFXVGHA=
|
||||
GXNMI.36b3148cbf284ba18d35bdd8386ac266" width=3D1 height=3D1> </BODY></HTML=
|
||||
>
|
||||
--____XTSWHCFJMONXSVGPVDLY____
|
||||
Content-ID: <TLUACRGXVUBY.IMAGE_3.gif>
|
||||
Content-Type: image/gif
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
R0lGODlhHgHCAPf/AIOPr9GvT7SFcZZjVTEuMLS1tZKUlJN0Znp4eEA7PV1aWvz8+8V6Zl1BNYxX
|
||||
HvOZ1/zmOd95agUEADs=
|
||||
--____XTSWHCFJMONXSVGPVDLY____
|
||||
Content-ID: <VFXVGHAGXNMI.36b3148cbf284ba18d35bdd8386ac266>
|
||||
Content-Type: image/xxx
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==
|
||||
--____XTSWHCFJMONXSVGPVDLY____--
|
||||
|
||||
--____NOIBTUQXSYRVOOAFLCHY____--
|
||||
"""
|
||||
|
||||
|
||||
raw_email_with_trailing_semicolon_to_disposition_content = b"""Delivered-To: receiver@example.com
|
||||
Return-Path: <sender@example.com>
|
||||
Mime-Version: 1.0
|
||||
Date: Wed, 22 Mar 2017 15:21:55 -0500
|
||||
Message-ID: <58D29693.192A.0075.1@wimort.com>
|
||||
Subject: Re: Reaching Out About Peoples Home Equity
|
||||
From: sender@example.com
|
||||
To: receiver@example.com
|
||||
Content-Type: multipart/alternative; boundary="____NOIBTUQXSYRVOOAFLCHY____"
|
||||
|
||||
|
||||
--____NOIBTUQXSYRVOOAFLCHY____
|
||||
Content-Type: text/plain; charset=iso-8859-15
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Disposition: inline;
|
||||
modification-date="Wed, 22 Mar 2017 15:21:55 -0500"
|
||||
|
||||
Hello Chloe
|
||||
|
||||
--____NOIBTUQXSYRVOOAFLCHY____
|
||||
Content-Type: multipart/related; boundary="____XTSWHCFJMONXSVGPVDLY____"
|
||||
|
||||
|
||||
--____XTSWHCFJMONXSVGPVDLY____
|
||||
Content-Type: text/html; charset=iso-8859-15
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Disposition: inline;
|
||||
modification-date="Wed, 22 Mar 2017 15:21:55 -0500"
|
||||
|
||||
<HTML xmlns=3D"http://www.w3.org/1999/xhtml">
|
||||
<BODY>
|
||||
<DIV>Hello Chloe</DIV>
|
||||
</BODY>
|
||||
</HTML>
|
||||
--____XTSWHCFJMONXSVGPVDLY____
|
||||
Content-Type: application/octet-stream; name="abc.xyz"
|
||||
Content-Description: abc.xyz
|
||||
Content-Disposition: attachment; filename="abc.xyz";
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
R0lGODlhHgHCAPf/AIOPr9GvT7SFcZZjVTEuMLS1tZKUlJN0Znp4eEA7PV1aWvz8+8V6Zl1BNYxX
|
||||
HvOZ1/zmOd95agUEADs=
|
||||
--____XTSWHCFJMONXSVGPVDLY____
|
||||
Content-ID: <VFXVGHAGXNMI.36b3148cbf284ba18d35bdd8386ac266>
|
||||
Content-Type: image/xxx
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==
|
||||
--____XTSWHCFJMONXSVGPVDLY____--
|
||||
|
||||
--____NOIBTUQXSYRVOOAFLCHY____--
|
||||
"""
|
||||
|
||||
|
||||
class TestParser(unittest.TestCase):
|
||||
|
||||
def test_parse_email(self):
|
||||
@@ -96,16 +291,40 @@ class TestParser(unittest.TestCase):
|
||||
self.assertEqual('Выписка по карте', parsed_email.subject)
|
||||
self.assertEqual('Выписка по карте 1234', parsed_email.body['html'][0])
|
||||
|
||||
def test_parse_email_invalid_unicode(self):
|
||||
parsed_email = parse_email(open(os.path.join(TEST_DIR, '8422.msg'), 'rb').read())
|
||||
self.assertEqual("Following up Re: Looking to connect, let's schedule a call!", parsed_email.subject)
|
||||
|
||||
def test_parse_email_inline_body(self):
|
||||
parsed_email = parse_email(raw_email_encoded_another_bad_multipart)
|
||||
self.assertEqual("Re: Reaching Out About Peoples Home Equity", parsed_email.subject)
|
||||
self.assertTrue(parsed_email.body['plain'])
|
||||
self.assertTrue(parsed_email.body['html'])
|
||||
|
||||
def test_parse_email_multipart(self):
|
||||
parsed_email = parse_email(raw_email_encoded_multipart)
|
||||
self.assertEqual("RE: Kari, are you open to this?", parsed_email.subject)
|
||||
|
||||
def test_parse_email_bad_multipart(self):
|
||||
parsed_email = parse_email(raw_email_encoded_bad_multipart)
|
||||
self.assertEqual("Re: Looking to connect with you...", parsed_email.subject)
|
||||
|
||||
def test_parse_email_ignores_header_casing(self):
|
||||
self.assertEqual('one', parse_email('Message-ID: one').message_id)
|
||||
self.assertEqual('one', parse_email('Message-Id: one').message_id)
|
||||
self.assertEqual('one', parse_email('Message-id: one').message_id)
|
||||
self.assertEqual('one', parse_email('message-id: one').message_id)
|
||||
|
||||
# TODO - Complete the test suite
|
||||
def test_parse_attachment(self):
|
||||
pass
|
||||
parsed_email = parse_email(raw_email_with_trailing_semicolon_to_disposition_content)
|
||||
self.assertEqual(1, len(parsed_email.attachments))
|
||||
attachment = parsed_email.attachments[0]
|
||||
self.assertEqual('application/octet-stream', attachment['content-type'])
|
||||
self.assertEqual(71, attachment['size'])
|
||||
self.assertEqual('abc.xyz', attachment['filename'])
|
||||
self.assertTrue(attachment['content'])
|
||||
|
||||
# TODO - Complete the test suite
|
||||
def test_decode_mail_header(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -15,22 +15,36 @@ class TestQuery(unittest.TestCase):
|
||||
res = build_search_query(unread=True)
|
||||
self.assertEqual(res, "(UNSEEN)")
|
||||
|
||||
def test_unflagged(self):
|
||||
|
||||
res = build_search_query(unflagged=True)
|
||||
self.assertEqual(res, "(UNFLAGGED)")
|
||||
|
||||
def test_flagged(self):
|
||||
|
||||
res = build_search_query(flagged=True)
|
||||
self.assertEqual(res, "(FLAGGED)")
|
||||
|
||||
def test_sent_from(self):
|
||||
|
||||
res = build_search_query(sent_from='test@example.com')
|
||||
self.assertEqual(res, "(FROM \"test@example.com\")")
|
||||
self.assertEqual(res, '(FROM "test@example.com")')
|
||||
|
||||
def test_sent_to(self):
|
||||
|
||||
res = build_search_query(sent_to='test@example.com')
|
||||
self.assertEqual(res, "(TO \"test@example.com\")")
|
||||
self.assertEqual(res, '(TO "test@example.com")')
|
||||
|
||||
def test_date__gt(self):
|
||||
|
||||
res = build_search_query(date__gt=date(2014, 12, 31))
|
||||
self.assertEqual(res, "(SINCE \"31-Dec-2014\")")
|
||||
self.assertEqual(res, '(SINCE "31-Dec-2014")')
|
||||
|
||||
def test_date__lt(self):
|
||||
|
||||
res = build_search_query(date__lt=date(2014, 1, 1))
|
||||
self.assertEqual(res, "(BEFORE \"1-Jan-2014\")")
|
||||
self.assertEqual(res, '(BEFORE "01-Jan-2014")')
|
||||
|
||||
def test_date__on(self):
|
||||
res = build_search_query(date__on=date(2014, 1, 1))
|
||||
self.assertEqual(res, '(ON "01-Jan-2014")')
|
||||
|
||||
Reference in New Issue
Block a user