Skip to content

Commit e3d91e2

Browse files
Merge branch 'release/3.12.0'
2 parents 16d4a50 + 5031003 commit e3d91e2

File tree

9 files changed

+81
-13
lines changed

9 files changed

+81
-13
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,13 @@ mail.text_not_managed: all not managed text (check the warning logs to find cont
187187
mail.to
188188
mail.to_domains
189189
mail.timezone: returns the timezone, offset from UTC
190-
mail_partial: returns only the mains parts of emails
190+
mail.mail_partial: returns only the mains parts of emails
191+
```
192+
193+
It's possible to write the attachments on disk with the method:
194+
195+
```
196+
mail.write_attachments(base_path)
191197
```
192198

193199
## Usage from command-line

mailparser/mailparser.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
ported_open,
4343
ported_string,
4444
receiveds_parsing,
45+
write_attachments,
4546
)
4647

4748
from .exceptions import MailParserEnvironmentError
@@ -348,10 +349,14 @@ def parse(self):
348349
# walk all mail parts
349350
for i, p in enumerate(parts):
350351
if not p.is_multipart():
351-
filename = decode_header_part(p.get_filename())
352352
charset = p.get_content_charset('utf-8')
353353
charset_raw = p.get_content_charset()
354354
log.debug("Charset {!r} part {!r}".format(charset, i))
355+
content_id = ported_string(p.get('content-id'))
356+
log.debug("content-id {!r} part {!r}".format(
357+
content_id, i))
358+
filename = decode_header_part(
359+
p.get_filename("{}".format(content_id)))
355360

356361
# this is an attachment
357362
if filename:
@@ -365,9 +370,6 @@ def parse(self):
365370
p.get('content-transfer-encoding', '')).lower()
366371
log.debug("Transfer encoding {!r} part {!r}".format(
367372
transfer_encoding, i))
368-
content_id = ported_string(p.get('content-id'))
369-
log.debug("content-id {!r} part {!r}".format(
370-
content_id, i))
371373
content_disposition = ported_string(
372374
p.get('content-disposition'))
373375
log.debug("content-disposition {!r} part {!r}".format(
@@ -473,6 +475,16 @@ def get_server_ipaddress(self, trust):
473475
log.debug("IP {!r} not private".format(ip_str))
474476
return ip_str
475477

478+
def write_attachments(self, base_path):
479+
""" This method writes the attachments of mail on disk
480+
481+
Arguments:
482+
base_path {str} -- Base path where write the attachments
483+
"""
484+
write_attachments(
485+
attachments=self.attachments,
486+
base_path=base_path)
487+
476488
def __getattr__(self, name):
477489
name = name.strip("_").lower()
478490
name_header = name.replace("_", "-")

mailparser/utils.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@
3131
import hashlib
3232
import logging
3333
import os
34+
import random
3435
import re
3536
import simplejson as json
37+
import string
3638
import subprocess
3739
import sys
3840
import tempfile
@@ -456,12 +458,19 @@ def get_header(message, name):
456458
name (string): header to get
457459
458460
Returns:
459-
decoded header
461+
str if there is an header
462+
list if there are more than one
460463
"""
461-
header = message.get(name)
462-
log.debug("Getting header {!r}: {!r}".format(name, header))
463-
if header:
464-
return decode_header_part(header)
464+
465+
headers = message.get_all(name)
466+
log.debug("Getting header {!r}: {!r}".format(name, headers))
467+
if headers:
468+
headers = [decode_header_part(i) for i in headers]
469+
if len(headers) == 1:
470+
# in this case return a string
471+
return headers[0]
472+
# in this case return a list
473+
return headers
465474
return six.text_type()
466475

467476

@@ -551,3 +560,16 @@ def write_sample(binary, payload, path, filename): # pragma: no cover
551560
else:
552561
with open(sample, "w") as f:
553562
f.write(payload)
563+
564+
565+
def random_string(string_length=10):
566+
""" Generate a random string of fixed length
567+
568+
Keyword Arguments:
569+
string_length {int} -- String length (default: {10})
570+
571+
Returns:
572+
str -- Random string
573+
"""
574+
letters = string.ascii_lowercase
575+
return ''.join(random.choice(letters) for i in range(string_length))

mailparser/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
limitations under the License.
1818
"""
1919

20-
__version__ = "3.11.0"
20+
__version__ = "3.12.0"
2121

2222
if __name__ == "__main__":
2323
print(__version__)

requirements-dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# tool
22
ipaddress==1.0.23
33
simplejson==3.17.0
4-
six==1.13.0
4+
six==1.14.0
55

66
# dev
77
coverage==5.0.2

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
ipaddress==1.0.23
22
simplejson==3.17.0
3-
six==1.13.0
3+
six==1.14.0

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
license="Apache License, Version 2.0",
4242
url="https://github.com/SpamScope/mail-parser",
4343
long_description=long_description,
44+
long_description_content_type="text/markdown",
4445
version=__version__,
4546
author="Fedele Mantuano",
4647
author_email="[email protected]",

tests/mails/mail_test_14

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Date: Wed, 24 Apr 2019 10:05:02 +0200 (CEST)
44
Mime-Version: 1.0
55
Content-Type: multipart/mixed; boundary="===============8544575414772382491=="
66
7+
Received-SPF: custom_header1
8+
Received-SPF: custom_header2
79

810
--===============8544575414772382491==
911
Content-Type: text/html; charset=UTF-8

tests/test_mail_parser.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import datetime
2121
import logging
2222
import os
23+
import shutil
2324
import six
2425
import sys
2526
import unittest
@@ -42,6 +43,7 @@
4243
ported_string,
4344
receiveds_parsing,
4445
parse_received,
46+
random_string,
4547
)
4648

4749
from mailparser.exceptions import MailParserEnvironmentError
@@ -87,6 +89,27 @@ def setUp(self):
8789
mail_malformed_2,
8890
mail_malformed_3)
8991

92+
def test_write_attachments(self):
93+
attachments = [
94+
"<_1_0B4E44A80B15F6FC005C1243C12580DD>",
95+
"<_1_0B4E420C0B4E3DD0005C1243C12580DD>",
96+
"<_1_0B4E24640B4E1564005C1243C12580DD>",
97+
"Move To Eight ZWEP6227F.pdf"]
98+
random_path = os.path.join(root, "tests", random_string())
99+
mail = mailparser.parse_from_file(mail_test_10)
100+
os.makedirs(random_path)
101+
mail.write_attachments(random_path)
102+
for i in attachments:
103+
self.assertTrue(os.path.exists(os.path.join(random_path, i)))
104+
shutil.rmtree(random_path)
105+
106+
def test_issue62(self):
107+
mail = mailparser.parse_from_file(mail_test_14)
108+
received_spf = mail.Received_SPF
109+
self.assertIsInstance(received_spf, list)
110+
self.assertIn("custom_header1", received_spf)
111+
self.assertIn("custom_header2", received_spf)
112+
90113
def test_html_field(self):
91114
mail = mailparser.parse_from_file(mail_malformed_1)
92115
self.assertIsInstance(mail.text_html, list)
@@ -117,6 +140,8 @@ def test_mail_partial(self):
117140
self.assertNotIn("x-ibm-av-version", mail.mail_partial)
118141
result = mail.mail_partial_json
119142
self.assertIsInstance(result, six.text_type)
143+
nr_attachments = len(mail._attachments)
144+
self.assertEqual(nr_attachments, 4)
120145

121146
def test_not_parsed_received(self):
122147
mail = mailparser.parse_from_file(mail_test_9)

0 commit comments

Comments
 (0)