mail-parser 4.6.2__tar.gz → 4.6.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mail_parser-4.6.2 → mail_parser-4.6.4}/.github/workflows/main.yml +2 -1
- {mail_parser-4.6.2 → mail_parser-4.6.4}/PKG-INFO +26 -7
- {mail_parser-4.6.2 → mail_parser-4.6.4}/README.md +19 -5
- {mail_parser-4.6.2 → mail_parser-4.6.4}/pyproject.toml +8 -1
- {mail_parser-4.6.2 → mail_parser-4.6.4}/src/mailparser/core.py +46 -27
- {mail_parser-4.6.2 → mail_parser-4.6.4}/src/mailparser/exceptions.py +13 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/src/mailparser/utils.py +263 -30
- {mail_parser-4.6.2 → mail_parser-4.6.4}/src/mailparser/version.py +1 -1
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/test_mail_parser.py +544 -9
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/test_utils.py +42 -4
- {mail_parser-4.6.2 → mail_parser-4.6.4}/uv.lock +25 -1
- {mail_parser-4.6.2 → mail_parser-4.6.4}/.claude/agents/security-reviewer.md +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/.github/FUNDING.yml +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/.github/copilot-instructions.md +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/.github/instructions/containerization-docker-best-practices.instructions.md +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/.github/instructions/github-actions-ci-cd-best-practices.instructions.md +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/.github/instructions/markdown.instructions.md +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/.github/instructions/python.instructions.md +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/.gitignore +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/.markdownlint.json +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/.pre-commit-config.yaml +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/CLAUDE.md +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/Dockerfile +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/LICENSE.txt +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/Makefile +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/NOTICE.txt +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/docker-compose.yml +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/docs/images/Bitcoin SpamScope.jpg +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/src/mailparser/__init__.py +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/src/mailparser/__main__.py +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/src/mailparser/const.py +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_malformed_1 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_malformed_2 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_malformed_3 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_outlook_1 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_1 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_10 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_11 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_12 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_13 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_14 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_15 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_16 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_17 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_18 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_19 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_2 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_3 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_4 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_5 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_6 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_7 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_8 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/mails/mail_test_9 +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/test_improved_received_patterns.py +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/test_main.py +0 -0
- {mail_parser-4.6.2 → mail_parser-4.6.4}/tests/test_received_corpus.py +0 -0
|
@@ -12,7 +12,7 @@ jobs:
|
|
|
12
12
|
runs-on: ubuntu-latest
|
|
13
13
|
strategy:
|
|
14
14
|
matrix:
|
|
15
|
-
python-version: ['3.
|
|
15
|
+
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14', '3.15']
|
|
16
16
|
|
|
17
17
|
steps:
|
|
18
18
|
- uses: actions/checkout@v4
|
|
@@ -23,6 +23,7 @@ jobs:
|
|
|
23
23
|
uses: actions/setup-python@v5
|
|
24
24
|
with:
|
|
25
25
|
python-version: ${{ matrix.python-version }}
|
|
26
|
+
allow-prereleases: true
|
|
26
27
|
|
|
27
28
|
- name: Install dependencies
|
|
28
29
|
run: |
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mail-parser
|
|
3
|
-
Version: 4.6.
|
|
3
|
+
Version: 4.6.4
|
|
4
4
|
Summary: A tool that parses emails by enhancing the Python standard library, extracting all details into a comprehensive object.
|
|
5
|
+
Project-URL: Homepage, https://github.com/SpamScope/mail-parser
|
|
6
|
+
Project-URL: Source, https://github.com/SpamScope/mail-parser
|
|
7
|
+
Project-URL: Issues, https://github.com/SpamScope/mail-parser/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/SpamScope/mail-parser/releases
|
|
5
9
|
Author-email: Fedele Mantuano <mantuano.fedele@gmail.com>
|
|
6
10
|
Maintainer-email: Fedele Mantuano <mantuano.fedele@gmail.com>
|
|
7
11
|
License-Expression: Apache-2.0
|
|
@@ -20,7 +24,8 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
20
24
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
25
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
26
|
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
-
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
28
|
+
Requires-Python: >=3.9
|
|
24
29
|
Provides-Extra: outlook
|
|
25
30
|
Requires-Dist: extract-msg>=0.54; extra == 'outlook'
|
|
26
31
|
Description-Content-Type: text/markdown
|
|
@@ -230,11 +235,15 @@ The `attachments` property returns a list of dictionaries, each containing compr
|
|
|
230
235
|
- `content-id` - Content identifier for referencing within HTML bodies
|
|
231
236
|
- `filename` - Original decoded filename from the email. This is untrusted input; never use it
|
|
232
237
|
directly to construct a filesystem path.
|
|
233
|
-
- `safe_filename` - Filename with directory components removed
|
|
234
|
-
no usable basename. When saving
|
|
235
|
-
and collision handling.
|
|
238
|
+
- `safe_filename` - Filename with directory components removed and truncated to fit the
|
|
239
|
+
filesystem name limit, or `None` when the original has no usable basename. When saving
|
|
240
|
+
attachments, prefer `write_attachments()` for full validation and collision handling.
|
|
236
241
|
- `mail_content_type` - MIME content type
|
|
237
|
-
- `payload` - Base64-encoded attachment data, ready for decoding or storage
|
|
242
|
+
- `payload` - Base64-encoded attachment data, ready for decoding or storage. An attachment is
|
|
243
|
+
kept as bytes: whatever encoding it was sent with, it is re-encoded to base64 and reports
|
|
244
|
+
`base64` as its `content_transfer_encoding`, so the payload always matches the encoding it
|
|
245
|
+
declares and hashes like the file the recipient received. Only `base64` parts keep their
|
|
246
|
+
original wire text, which is already lossless
|
|
238
247
|
|
|
239
248
|
To access custom or vendor-specific headers, replace hyphens with underscores. For example, to
|
|
240
249
|
access the `X-MSMail-Priority` header:
|
|
@@ -426,7 +435,13 @@ Attachment filenames are supplied by the email sender. The `filename` value in
|
|
|
426
435
|
not pass it directly to `open()` or join it to a directory. The `safe_filename` field provides a
|
|
427
436
|
sanitized basename when one exists, but applications saving files should prefer
|
|
428
437
|
`write_attachments()`, which also validates containment, rejects symlink destinations, and
|
|
429
|
-
deduplicates names within the attachment batch.
|
|
438
|
+
deduplicates names within the attachment batch. Deduplication is case-insensitive, because
|
|
439
|
+
APFS, exFAT and SMB collapse `Invoice.pdf` and `invoice.pdf` onto a single file.
|
|
440
|
+
|
|
441
|
+
A single unusable attachment never costs the rest of the batch: `write_attachments()` logs a
|
|
442
|
+
warning and moves on when a filename cannot be sanitized, a payload cannot be decoded, or the
|
|
443
|
+
write itself fails, so the remaining attachments are still saved. A containment failure is not
|
|
444
|
+
treated this way: it raises `MailParserPathError` and stops the batch.
|
|
430
445
|
|
|
431
446
|
# Usage from Command Line
|
|
432
447
|
|
|
@@ -520,7 +535,11 @@ MailParserError: Base MailParser Exception
|
|
|
520
535
|
|
|
|
521
536
|
\── MailParserOSError: Raised when there is an OS error
|
|
522
537
|
|
|
|
538
|
+
\── MailParserPathError: Raised when an attachment escapes the output directory
|
|
539
|
+
|
|
|
523
540
|
\── MailParserReceivedParsingError: Raised when a received header cannot be parsed
|
|
541
|
+
|
|
|
542
|
+
\── MailParserRecursionError: Raised when a message is nested too deeply to parse
|
|
524
543
|
```
|
|
525
544
|
|
|
526
545
|
# Docker Deployment
|
|
@@ -203,11 +203,15 @@ The `attachments` property returns a list of dictionaries, each containing compr
|
|
|
203
203
|
- `content-id` - Content identifier for referencing within HTML bodies
|
|
204
204
|
- `filename` - Original decoded filename from the email. This is untrusted input; never use it
|
|
205
205
|
directly to construct a filesystem path.
|
|
206
|
-
- `safe_filename` - Filename with directory components removed
|
|
207
|
-
no usable basename. When saving
|
|
208
|
-
and collision handling.
|
|
206
|
+
- `safe_filename` - Filename with directory components removed and truncated to fit the
|
|
207
|
+
filesystem name limit, or `None` when the original has no usable basename. When saving
|
|
208
|
+
attachments, prefer `write_attachments()` for full validation and collision handling.
|
|
209
209
|
- `mail_content_type` - MIME content type
|
|
210
|
-
- `payload` - Base64-encoded attachment data, ready for decoding or storage
|
|
210
|
+
- `payload` - Base64-encoded attachment data, ready for decoding or storage. An attachment is
|
|
211
|
+
kept as bytes: whatever encoding it was sent with, it is re-encoded to base64 and reports
|
|
212
|
+
`base64` as its `content_transfer_encoding`, so the payload always matches the encoding it
|
|
213
|
+
declares and hashes like the file the recipient received. Only `base64` parts keep their
|
|
214
|
+
original wire text, which is already lossless
|
|
211
215
|
|
|
212
216
|
To access custom or vendor-specific headers, replace hyphens with underscores. For example, to
|
|
213
217
|
access the `X-MSMail-Priority` header:
|
|
@@ -399,7 +403,13 @@ Attachment filenames are supplied by the email sender. The `filename` value in
|
|
|
399
403
|
not pass it directly to `open()` or join it to a directory. The `safe_filename` field provides a
|
|
400
404
|
sanitized basename when one exists, but applications saving files should prefer
|
|
401
405
|
`write_attachments()`, which also validates containment, rejects symlink destinations, and
|
|
402
|
-
deduplicates names within the attachment batch.
|
|
406
|
+
deduplicates names within the attachment batch. Deduplication is case-insensitive, because
|
|
407
|
+
APFS, exFAT and SMB collapse `Invoice.pdf` and `invoice.pdf` onto a single file.
|
|
408
|
+
|
|
409
|
+
A single unusable attachment never costs the rest of the batch: `write_attachments()` logs a
|
|
410
|
+
warning and moves on when a filename cannot be sanitized, a payload cannot be decoded, or the
|
|
411
|
+
write itself fails, so the remaining attachments are still saved. A containment failure is not
|
|
412
|
+
treated this way: it raises `MailParserPathError` and stops the batch.
|
|
403
413
|
|
|
404
414
|
# Usage from Command Line
|
|
405
415
|
|
|
@@ -493,7 +503,11 @@ MailParserError: Base MailParser Exception
|
|
|
493
503
|
|
|
|
494
504
|
\── MailParserOSError: Raised when there is an OS error
|
|
495
505
|
|
|
|
506
|
+
\── MailParserPathError: Raised when an attachment escapes the output directory
|
|
507
|
+
|
|
|
496
508
|
\── MailParserReceivedParsingError: Raised when a received header cannot be parsed
|
|
509
|
+
|
|
|
510
|
+
\── MailParserRecursionError: Raised when a message is nested too deeply to parse
|
|
497
511
|
```
|
|
498
512
|
|
|
499
513
|
# Docker Deployment
|
|
@@ -4,7 +4,7 @@ dynamic = ["version"]
|
|
|
4
4
|
description = "A tool that parses emails by enhancing the Python standard library, extracting all details into a comprehensive object."
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
readme = "README.md"
|
|
7
|
-
requires-python = ">=3.9
|
|
7
|
+
requires-python = ">=3.9"
|
|
8
8
|
keywords = ["email", "mail", "parser", "security", "forensics", "threat detection", "phishing", "malware", "spam"]
|
|
9
9
|
classifiers = [
|
|
10
10
|
"Natural Language :: English",
|
|
@@ -19,6 +19,7 @@ classifiers = [
|
|
|
19
19
|
"Programming Language :: Python :: 3.12",
|
|
20
20
|
"Programming Language :: Python :: 3.13",
|
|
21
21
|
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Programming Language :: Python :: 3.15",
|
|
22
23
|
]
|
|
23
24
|
authors = [
|
|
24
25
|
{ name = "Fedele Mantuano", email = "mantuano.fedele@gmail.com" }
|
|
@@ -28,6 +29,12 @@ maintainers = [
|
|
|
28
29
|
]
|
|
29
30
|
dependencies = []
|
|
30
31
|
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/SpamScope/mail-parser"
|
|
34
|
+
Source = "https://github.com/SpamScope/mail-parser"
|
|
35
|
+
Issues = "https://github.com/SpamScope/mail-parser/issues"
|
|
36
|
+
Changelog = "https://github.com/SpamScope/mail-parser/releases"
|
|
37
|
+
|
|
31
38
|
[project.optional-dependencies]
|
|
32
39
|
outlook = ["extract-msg>=0.54"]
|
|
33
40
|
|
|
@@ -36,6 +36,7 @@ from mailparser.exceptions import MailParserRecursionError
|
|
|
36
36
|
from mailparser.utils import (
|
|
37
37
|
_safe_attachment_filename,
|
|
38
38
|
_safe_remove,
|
|
39
|
+
as_string_safe,
|
|
39
40
|
convert_mail_date,
|
|
40
41
|
decode_header_part,
|
|
41
42
|
decode_headers,
|
|
@@ -51,6 +52,7 @@ from mailparser.utils import (
|
|
|
51
52
|
ported_open,
|
|
52
53
|
ported_string,
|
|
53
54
|
random_string,
|
|
55
|
+
raw_payload,
|
|
54
56
|
receiveds_parsing,
|
|
55
57
|
write_attachments,
|
|
56
58
|
)
|
|
@@ -555,42 +557,52 @@ class MailParser:
|
|
|
555
557
|
binary = False
|
|
556
558
|
mail_content_type = ported_string(p.get_content_type())
|
|
557
559
|
log.debug(f"Mail content type {mail_content_type!r} part {i!r}")
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
560
|
+
# Strip before comparing, exactly as email's own
|
|
561
|
+
# get_payload() does: a trailing space made every
|
|
562
|
+
# encoding branch below miss and the raw bytes fall
|
|
563
|
+
# through the text path, which drops the non-UTF-8 ones.
|
|
564
|
+
transfer_encoding = (
|
|
565
|
+
ported_string(p.get("content-transfer-encoding", ""))
|
|
566
|
+
.strip()
|
|
567
|
+
.lower()
|
|
568
|
+
)
|
|
561
569
|
log.debug(f"Transfer encoding {transfer_encoding!r} part {i!r}")
|
|
562
570
|
content_disposition = ported_string(p.get("content-disposition"))
|
|
563
571
|
log.debug(f"content-disposition {content_disposition!r} part {i!r}")
|
|
564
572
|
|
|
565
573
|
if p.is_multipart():
|
|
566
574
|
payload = "".join(
|
|
567
|
-
[m
|
|
575
|
+
[as_string_safe(m) for m in p.get_payload(decode=False)]
|
|
568
576
|
)
|
|
569
577
|
binary = False
|
|
570
578
|
log.debug(f"Filename {filename!r} part {i!r} is multipart")
|
|
571
|
-
elif transfer_encoding == "base64"
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
579
|
+
elif transfer_encoding == "base64":
|
|
580
|
+
payload = raw_payload(p)
|
|
581
|
+
if not isinstance(payload, str):
|
|
582
|
+
# The declared charset could not be applied, so
|
|
583
|
+
# the wire text is not recoverable: re-encode the
|
|
584
|
+
# decoded bytes to keep the payload base64.
|
|
585
|
+
payload = base64.b64encode(payload).decode("ascii")
|
|
576
586
|
binary = True
|
|
577
587
|
log.debug(f"Filename {filename!r} part {i!r} is binary")
|
|
578
|
-
|
|
579
|
-
#
|
|
588
|
+
else:
|
|
589
|
+
# Every other encoding — uuencode, quoted-printable,
|
|
590
|
+
# 7bit, 8bit, binary — is re-encoded to base64 from
|
|
591
|
+
# the decoded bytes. An attachment is a file, not
|
|
592
|
+
# text: reading it back through a charset dropped
|
|
593
|
+
# every byte that charset cannot represent (half of a
|
|
594
|
+
# binary payload), so the extracted file was neither
|
|
595
|
+
# the attachment nor the bytes on the wire, and its
|
|
596
|
+
# hash never matched what the recipient received.
|
|
580
597
|
payload = base64.b64encode(p.get_payload(decode=True)).decode(
|
|
581
598
|
"ascii"
|
|
582
599
|
)
|
|
583
600
|
binary = True
|
|
584
|
-
transfer_encoding = "base64"
|
|
585
601
|
log.debug(
|
|
586
|
-
f"Filename {filename!r} part {i!r} is binary
|
|
587
|
-
" re-encoded to base64)"
|
|
602
|
+
f"Filename {filename!r} part {i!r} is binary"
|
|
603
|
+
f" ({transfer_encoding!r} re-encoded to base64)"
|
|
588
604
|
)
|
|
589
|
-
|
|
590
|
-
payload = ported_string(
|
|
591
|
-
p.get_payload(decode=True), encoding=charset
|
|
592
|
-
)
|
|
593
|
-
log.debug(f"Filename {filename!r} part {i!r} is not binary")
|
|
605
|
+
transfer_encoding = "base64"
|
|
594
606
|
|
|
595
607
|
try:
|
|
596
608
|
safe_filename = _safe_attachment_filename(filename)
|
|
@@ -616,9 +628,15 @@ class MailParser:
|
|
|
616
628
|
log.debug(f"Email part {i!r} is not an attachment")
|
|
617
629
|
|
|
618
630
|
payload = p.get_payload(decode=True)
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
631
|
+
# Strip as well, for the same reason as the attachment
|
|
632
|
+
# branch above: a trailing space sent the part down the
|
|
633
|
+
# else branch, which re-reads the text through
|
|
634
|
+
# raw-unicode-escape and turns it into literal escapes.
|
|
635
|
+
cte = (
|
|
636
|
+
ported_string(p.get("Content-Transfer-Encoding", ""))
|
|
637
|
+
.strip()
|
|
638
|
+
.lower()
|
|
639
|
+
)
|
|
622
640
|
|
|
623
641
|
if not cte or cte in ["7bit", "8bit"]:
|
|
624
642
|
# message_from_bytes stores non-ASCII body bytes via
|
|
@@ -627,16 +645,17 @@ class MailParser:
|
|
|
627
645
|
# Unicode str (no surrogates). Detect which case we
|
|
628
646
|
# have via get_payload(decode=False) and decode
|
|
629
647
|
# accordingly so the declared charset is honoured.
|
|
630
|
-
raw_str = p
|
|
648
|
+
raw_str = raw_payload(p)
|
|
631
649
|
if isinstance(raw_str, str):
|
|
632
650
|
try:
|
|
633
651
|
# Raises if surrogates present (from_bytes path)
|
|
634
652
|
raw_str.encode("utf-8")
|
|
635
653
|
payload = raw_str
|
|
636
654
|
except UnicodeEncodeError:
|
|
637
|
-
#
|
|
638
|
-
|
|
639
|
-
|
|
655
|
+
# These encodings are not transformed by
|
|
656
|
+
# get_payload(decode=True), so ``payload``
|
|
657
|
+
# already holds the original bytes.
|
|
658
|
+
payload = ported_string(payload, encoding=charset)
|
|
640
659
|
else:
|
|
641
660
|
payload = ported_string(payload, encoding=charset)
|
|
642
661
|
else:
|
|
@@ -1081,7 +1100,7 @@ class MailParser:
|
|
|
1081
1100
|
"""
|
|
1082
1101
|
Return the entire message flattened as a string.
|
|
1083
1102
|
"""
|
|
1084
|
-
return self.message
|
|
1103
|
+
return as_string_safe(self.message) if self.message else ""
|
|
1085
1104
|
|
|
1086
1105
|
@property
|
|
1087
1106
|
def to_domains(self):
|
|
@@ -21,6 +21,7 @@ __all__ = (
|
|
|
21
21
|
"MailParserOutlookError",
|
|
22
22
|
"MailParserEnvironmentError",
|
|
23
23
|
"MailParserOSError",
|
|
24
|
+
"MailParserPathError",
|
|
24
25
|
"MailParserReceivedParsingError",
|
|
25
26
|
"MailParserRecursionError",
|
|
26
27
|
)
|
|
@@ -58,6 +59,18 @@ class MailParserOSError(MailParserError):
|
|
|
58
59
|
pass
|
|
59
60
|
|
|
60
61
|
|
|
62
|
+
class MailParserPathError(MailParserError):
|
|
63
|
+
"""
|
|
64
|
+
Raised when an attachment would be written outside the output directory.
|
|
65
|
+
|
|
66
|
+
This is a containment failure, not a per-attachment problem, so it keeps
|
|
67
|
+
its own type: ``write_attachments()`` skips individual attachments that
|
|
68
|
+
cannot be decoded or written, but must never swallow this one.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
61
74
|
class MailParserReceivedParsingError(MailParserError):
|
|
62
75
|
"""
|
|
63
76
|
Raised when a received header cannot be parsed
|