extract-msg 0.55.0__tar.gz → 0.56.1__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.
- {extract_msg-0.55.0 → extract_msg-0.56.1}/CHANGELOG.md +10 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/PKG-INFO +3 -3
- {extract_msg-0.55.0 → extract_msg-0.56.1}/README.rst +2 -2
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/__init__.py +2 -2
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/__main__.py +2 -1
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/custom_att_handler/lnk_obj_att.py +2 -2
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/web_att.py +1 -1
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/constants/ps.py +1 -1
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/constants/st.py +2 -2
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/message_base.py +118 -8
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/utils.py +1 -1
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg.egg-info/PKG-INFO +3 -3
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg.egg-info/requires.txt +2 -2
- {extract_msg-0.55.0 → extract_msg-0.56.1}/requirements.txt +4 -2
- {extract_msg-0.55.0 → extract_msg-0.56.1}/LICENSE.txt +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/MANIFEST.in +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/_rtf/__init__.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/_rtf/create_doc.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/_rtf/inject_rtf.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/_rtf/token.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/_rtf/tokenize_rtf.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/__init__.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/attachment.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/attachment_base.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/broken_att.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/custom_att.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/custom_att_handler/__init__.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/custom_att_handler/custom_handler.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/custom_att_handler/outlook_image_dib.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/custom_att_handler/outlook_image_meta.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/emb_msg_att.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/signed_att.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/unsupported_att.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/constants/__init__.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/constants/re.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/data/logging-config/logging-nt.json +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/data/logging-config/logging-posix.json +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/encoding/__init__.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/encoding/_dt/__init__.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/encoding/_dt/_mac_ce.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/encoding/_dt/_mac_cyrillic.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/encoding/_dt/_mac_greek.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/encoding/_dt/_mac_iceland.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/encoding/_dt/_mac_turkish.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/encoding/_dt/_win874_dec.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/encoding/_dt/_win950_dec.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/encoding/utils.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/enums.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/exceptions.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/__init__.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/appointment.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/calendar.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/calendar_base.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/contact.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/journal.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/meeting_cancellation.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/meeting_exception.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/meeting_forward.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/meeting_related.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/meeting_request.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/meeting_response.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/message.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/message_signed.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/message_signed_base.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/msg.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/post.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/sticky_note.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/task.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/msg_classes/task_request.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/null_date.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/ole_writer.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/open_msg.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/properties/__init__.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/properties/named.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/properties/prop.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/properties/properties_store.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/py.typed +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/recipient.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/__init__.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/_helpers.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/business_card.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/cfoas.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/contact_link_entry.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/dev_mode_a.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/dv_target_device.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/entry_id.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/misc_id.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/mon_stream.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/odt.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/ole_pres.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/ole_stream_struct.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/recurrence_pattern.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/report_tag.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/system_time.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/time_zone_definition.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/time_zone_struct.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/toc_entry.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/structures/tz_rule.py +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg.egg-info/SOURCES.txt +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg.egg-info/dependency_links.txt +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg.egg-info/entry_points.txt +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg.egg-info/top_level.txt +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/setup.cfg +0 -0
- {extract_msg-0.55.0 → extract_msg-0.56.1}/setup.py +0 -0
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
**v0.56.1**
|
|
2
|
+
* [[TeamMsgExtractor #484](https://github.com/TeamMsgExtractor/msg-extractor/issues/484)] Corrected the way the HTML body is decoded in `MessageBase.asEmailMessage()` to prevent encoding errors.
|
|
3
|
+
|
|
4
|
+
**v0.56.0**
|
|
5
|
+
* [[TeamMsgExtractor #478](https://github.com/TeamMsgExtractor/msg-extractor/issues/478)] Expand allowable versions of `ebcdic` to include `2.x`. The 2.0 release is a pure technical release with no functional changes; it only drops support for Python 2 and 3.8. The lower bound is unchanged so installs on Python 3.8 will continue to resolve to `ebcdic 1.1.1`.
|
|
6
|
+
* [[TeamMsgExtractor #469](https://github.com/TeamMsgExtractor/msg-extractor/issues/469)] Expanded `beautifulsoup` to allow for versions up to `5.*.*`. I was previously hesitant to do so because they sometimes do what seems like non backwards compatible changes in minor versions, but it looks like this should be fine.
|
|
7
|
+
* [[TeamMsgExtractor #471](https://github.com/TeamMsgExtractor/msg-extractor/issues/471)] Added `.strip()` to file name determination to ensure that folders with trailing spaces would be corrected before being used.
|
|
8
|
+
* [[TeamMsgExtractor #481](https://github.com/TeamMsgExtractor/msg-extractor/issues/481)] Fixed a typo that caused an HTML body created from plain text to end with `</head>` instead of `</html>`.
|
|
9
|
+
* [[TeamMsgExtractor #476](https://github.com/TeamMsgExtractor/msg-extractor/issues/476)] Fixed `MessageBase.asEmailMessage()` raising `ValueError` when a message has multiple `To` recipients.
|
|
10
|
+
|
|
1
11
|
**v0.55.0**
|
|
2
12
|
* [[TeamMsgExtractor #465](https://github.com/TeamMsgExtractor/msg-extractor/issues/465)] Added missing `msg.close()` to `openMsg()`. If the MSG file was actually just a plain OLE file, it would be left open.
|
|
3
13
|
* Adjusted the default value of `maxNameLength` for `MessageBase.save()` to 40 instead of 256.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: extract_msg
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.56.1
|
|
4
4
|
Summary: Extracts emails and attachments saved in Microsoft Outlook's .msg files
|
|
5
5
|
Home-page: https://github.com/TeamMsgExtractor/msg-extractor
|
|
6
6
|
Download-URL: https://github.com/TeamMsgExtractor/msg-extractor/archives/master
|
|
@@ -278,8 +278,8 @@ your access to the newest major version of extract-msg.
|
|
|
278
278
|
.. |License: GPL v3| image:: https://img.shields.io/badge/License-GPLv3-blue.svg
|
|
279
279
|
:target: LICENSE.txt
|
|
280
280
|
|
|
281
|
-
.. |PyPI3| image:: https://img.shields.io/badge/pypi-0.
|
|
282
|
-
:target: https://pypi.org/project/extract-msg/0.
|
|
281
|
+
.. |PyPI3| image:: https://img.shields.io/badge/pypi-0.56.1-blue.svg
|
|
282
|
+
:target: https://pypi.org/project/extract-msg/0.56.1/
|
|
283
283
|
|
|
284
284
|
.. |PyPI2| image:: https://img.shields.io/badge/python-3.8+-brightgreen.svg
|
|
285
285
|
:target: https://www.python.org/downloads/release/python-3810/
|
|
@@ -260,8 +260,8 @@ your access to the newest major version of extract-msg.
|
|
|
260
260
|
.. |License: GPL v3| image:: https://img.shields.io/badge/License-GPLv3-blue.svg
|
|
261
261
|
:target: LICENSE.txt
|
|
262
262
|
|
|
263
|
-
.. |PyPI3| image:: https://img.shields.io/badge/pypi-0.
|
|
264
|
-
:target: https://pypi.org/project/extract-msg/0.
|
|
263
|
+
.. |PyPI3| image:: https://img.shields.io/badge/pypi-0.56.1-blue.svg
|
|
264
|
+
:target: https://pypi.org/project/extract-msg/0.56.1/
|
|
265
265
|
|
|
266
266
|
.. |PyPI2| image:: https://img.shields.io/badge/python-3.8+-brightgreen.svg
|
|
267
267
|
:target: https://www.python.org/downloads/release/python-3810/
|
|
@@ -27,8 +27,8 @@ https://github.com/TeamMsgExtractor/msg-extractor
|
|
|
27
27
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
28
28
|
|
|
29
29
|
__author__ = 'Destiny Peterson & Matthew Walker'
|
|
30
|
-
__date__ = '
|
|
31
|
-
__version__ = '0.
|
|
30
|
+
__date__ = '2026-08-13'
|
|
31
|
+
__version__ = '0.56.1'
|
|
32
32
|
|
|
33
33
|
__all__ = [
|
|
34
34
|
# Modules:
|
|
@@ -101,7 +101,7 @@ def main(argv: List[str] = sys.argv) -> None:
|
|
|
101
101
|
msg.saveAttachments(**kwargs)
|
|
102
102
|
else:
|
|
103
103
|
msg.save(**kwargs)
|
|
104
|
-
except Exception
|
|
104
|
+
except Exception:
|
|
105
105
|
try:
|
|
106
106
|
print(f'Error with file "{x}": {traceback.format_exc()}')
|
|
107
107
|
except UnicodeEncodeError:
|
|
@@ -111,5 +111,6 @@ def main(argv: List[str] = sys.argv) -> None:
|
|
|
111
111
|
if createdZip:
|
|
112
112
|
_zip.close()
|
|
113
113
|
|
|
114
|
+
|
|
114
115
|
if __name__ == '__main__':
|
|
115
116
|
main(sys.argv)
|
{extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/custom_att_handler/lnk_obj_att.py
RENAMED
|
@@ -7,7 +7,7 @@ __all__ = [
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
from functools import cached_property
|
|
10
|
-
from typing import
|
|
10
|
+
from typing import Optional, TYPE_CHECKING
|
|
11
11
|
|
|
12
12
|
from . import registerHandler
|
|
13
13
|
from .custom_handler import CustomAttachmentHandler
|
|
@@ -106,4 +106,4 @@ class LinkedObjectAttachment(CustomAttachmentHandler):
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
|
|
109
|
-
registerHandler(LinkedObjectAttachment)
|
|
109
|
+
registerHandler(LinkedObjectAttachment)
|
|
@@ -43,4 +43,4 @@ PSETID_AIRSYNC: Final[str] = '{71035549-0739-4DCB-9163-00F0580DBBDF}'
|
|
|
43
43
|
PSETID_SHARING: Final[str] = '{00062040-0000-0000-C000-000000000046}'
|
|
44
44
|
PSETID_XMLEXTRACTEDENTITIES: Final[str] = '{23239608-685D-4732-9C55-4C95CB4E8E33}'
|
|
45
45
|
PSETID_ATTACHMENT: Final[str] = '{96357F7F-59E1-47D0-99A7-46515C183B54}'
|
|
46
|
-
PSETID_CALENDAR_ASSISTANT: Final[str] = '{11000E07-B51B-40D6-AF21-CAA85EDAB1D0}'
|
|
46
|
+
PSETID_CALENDAR_ASSISTANT: Final[str] = '{11000E07-B51B-40D6-AF21-CAA85EDAB1D0}'
|
|
@@ -93,7 +93,7 @@ ST_BE_UI8: Final[struct.Struct] = struct.Struct('>B')
|
|
|
93
93
|
ST_BE_UI16: Final[struct.Struct] = struct.Struct('>H')
|
|
94
94
|
ST_BE_UI32: Final[struct.Struct] = struct.Struct('>I')
|
|
95
95
|
ST_BE_UI64: Final[struct.Struct] = struct.Struct('>Q')
|
|
96
|
-
ST_BE_F32: Final[struct.Struct]
|
|
96
|
+
ST_BE_F32: Final[struct.Struct] = struct.Struct('>f')
|
|
97
97
|
ST_BE_F64: Final[struct.Struct] = struct.Struct('>d')
|
|
98
98
|
# Structs that use the system byte order, where consistency on a single system
|
|
99
99
|
# is all that matters. Mainly used for quick casts between signed and unsigned.
|
|
@@ -107,4 +107,4 @@ ST_SBO_UI32: Final[struct.Struct] = struct.Struct('@I')
|
|
|
107
107
|
ST_SBO_UI64: Final[struct.Struct] = struct.Struct('@Q')
|
|
108
108
|
# Struct for an RGB value that, in little endian, would be an int written in hex
|
|
109
109
|
# as 0x00BBGGRR.
|
|
110
|
-
ST_RGB: Final[struct.Struct] = struct.Struct('<BBBx')
|
|
110
|
+
ST_RGB: Final[struct.Struct] = struct.Struct('<BBBx')
|
|
@@ -25,6 +25,7 @@ import RTFDE.exceptions
|
|
|
25
25
|
|
|
26
26
|
from email import policy
|
|
27
27
|
from email.charset import Charset, QP
|
|
28
|
+
from email.header import decode_header as _decode_header, Header as _Header
|
|
28
29
|
from email.message import EmailMessage
|
|
29
30
|
from email.mime.multipart import MIMEMultipart
|
|
30
31
|
from email.mime.text import MIMEText
|
|
@@ -56,6 +57,77 @@ from ..utils import (
|
|
|
56
57
|
logger = logging.getLogger(__name__)
|
|
57
58
|
logger.addHandler(logging.NullHandler())
|
|
58
59
|
|
|
60
|
+
_RFC2047_WORD = re.compile(r'=\?[^?]+\?[bBqQ]\?[^?]*\?=')
|
|
61
|
+
|
|
62
|
+
# Encodings to try when a declared charset fails, keyed by normalised charset name.
|
|
63
|
+
# GBK is a strict superset of GB2312 and accepts the ASCII-range second bytes that
|
|
64
|
+
# GB2312 rejects, so real-world GB2312-labelled headers often decode correctly as GBK.
|
|
65
|
+
_CHARSET_FALLBACKS: Dict[str, Tuple[str, ...]] = {
|
|
66
|
+
'gb2312': ('gbk', 'cp936'),
|
|
67
|
+
'gb_2312': ('gbk', 'cp936'),
|
|
68
|
+
'gb_2312-80': ('gbk', 'cp936'),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _fix_encoded_word(m: re.Match) -> str:
|
|
73
|
+
"""
|
|
74
|
+
Regex substitution callback: decode one RFC 2047 encoded word and
|
|
75
|
+
re-emit it as a valid UTF-8 encoded word.
|
|
76
|
+
|
|
77
|
+
If the declared charset fails (e.g. GB2312-labelled GBK bytes), charset
|
|
78
|
+
fallbacks from _CHARSET_FALLBACKS are tried before falling back to
|
|
79
|
+
latin-1 with replacement characters.
|
|
80
|
+
"""
|
|
81
|
+
word = m.group(0)
|
|
82
|
+
try:
|
|
83
|
+
parts = _decode_header(word)
|
|
84
|
+
if len(parts) != 1 or not isinstance(parts[0][0], bytes):
|
|
85
|
+
return word
|
|
86
|
+
btext, cs = parts[0]
|
|
87
|
+
try:
|
|
88
|
+
text = btext.decode(cs or 'ascii')
|
|
89
|
+
except (UnicodeDecodeError, LookupError):
|
|
90
|
+
for fallback in _CHARSET_FALLBACKS.get((cs or '').lower(), ()):
|
|
91
|
+
try:
|
|
92
|
+
text = btext.decode(fallback)
|
|
93
|
+
break
|
|
94
|
+
except (UnicodeDecodeError, LookupError):
|
|
95
|
+
continue
|
|
96
|
+
else:
|
|
97
|
+
text = btext.decode('latin-1', 'replace')
|
|
98
|
+
return str(_Header(text, charset='utf-8'))
|
|
99
|
+
except Exception:
|
|
100
|
+
return word
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _preprocess_encoded_words(text: str) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Replace every RFC 2047 encoded word in *text* with a clean UTF-8
|
|
106
|
+
encoded word. Safe to apply to an entire raw header block before
|
|
107
|
+
feeding it to the modern email policy parser.
|
|
108
|
+
"""
|
|
109
|
+
return _RFC2047_WORD.sub(_fix_encoded_word, text)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _sanitize_header(value: str) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Prepare a header value from a compat32-parsed Message for safe assignment
|
|
115
|
+
to an EmailMessage (fallback path when no raw headerText is available).
|
|
116
|
+
|
|
117
|
+
RFC 2047 encoded words are re-encoded as UTF-8; raw non-ASCII characters
|
|
118
|
+
are also RFC 2047-encoded so that EmailMessage.as_bytes() never raises
|
|
119
|
+
UnicodeEncodeError.
|
|
120
|
+
"""
|
|
121
|
+
if '=?' in value:
|
|
122
|
+
value = _RFC2047_WORD.sub(_fix_encoded_word, value)
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
value.encode('ascii')
|
|
126
|
+
except (UnicodeEncodeError, UnicodeDecodeError):
|
|
127
|
+
value = str(_Header(value, charset='utf-8'))
|
|
128
|
+
|
|
129
|
+
return value
|
|
130
|
+
|
|
59
131
|
|
|
60
132
|
class MessageBase(MSGFile):
|
|
61
133
|
"""
|
|
@@ -165,10 +237,48 @@ class MessageBase(MSGFile):
|
|
|
165
237
|
"""
|
|
166
238
|
ret = EmailMessage()
|
|
167
239
|
|
|
168
|
-
#
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
240
|
+
# Prefer the raw transport-header block: pre-fix any malformed RFC 2047
|
|
241
|
+
# encoded words (e.g. GB2312-labelled GBK bytes), then parse with the
|
|
242
|
+
# modern policy so that RFC 5322 unfolding and RFC 2047 decoding are
|
|
243
|
+
# handled natively and the values arrive as clean Unicode strings.
|
|
244
|
+
# Fall back to the compat32-derived self.header when no raw text is
|
|
245
|
+
# stored (synthesised headers from MAPI properties).
|
|
246
|
+
_ADDRESS_HEADERS = frozenset({'to', 'cc', 'bcc', 'reply-to'})
|
|
247
|
+
if self.headerText:
|
|
248
|
+
raw = self.headerText
|
|
249
|
+
if raw.startswith('Microsoft Mail Internet Headers Version 2.0'):
|
|
250
|
+
raw = raw[43:].lstrip()
|
|
251
|
+
raw = _preprocess_encoded_words(raw)
|
|
252
|
+
source_items = list(HeaderParser(policy = policy.default).parsestr(raw).items())
|
|
253
|
+
sanitize = False
|
|
254
|
+
else:
|
|
255
|
+
source_items = list(self.header.items())
|
|
256
|
+
sanitize = True
|
|
257
|
+
|
|
258
|
+
# Address headers (To/CC/BCC/Reply-To) may appear once per recipient in
|
|
259
|
+
# the stored header block; merge them into a single comma-separated value.
|
|
260
|
+
# All other headers — including multi-valued trace headers like Received
|
|
261
|
+
# and Authentication-Results — are forwarded as-is; EmailMessage appends
|
|
262
|
+
# rather than replaces, so natural repetition is preserved correctly.
|
|
263
|
+
address_merged: Dict[str, Tuple[str, str]] = {}
|
|
264
|
+
for key, value in source_items:
|
|
265
|
+
if key.lower() == 'content-type':
|
|
266
|
+
continue
|
|
267
|
+
if sanitize:
|
|
268
|
+
# compat32 values are folded and may contain raw encoded words.
|
|
269
|
+
value = re.sub(r'\r?\n[ \t]', ' ', value)
|
|
270
|
+
value = value.replace('\r\n', '').replace('\n', '')
|
|
271
|
+
value = _sanitize_header(value)
|
|
272
|
+
lower = key.lower()
|
|
273
|
+
if lower in _ADDRESS_HEADERS:
|
|
274
|
+
if lower in address_merged:
|
|
275
|
+
address_merged[lower] = (address_merged[lower][0], address_merged[lower][1] + ', ' + value)
|
|
276
|
+
else:
|
|
277
|
+
address_merged[lower] = (key, value)
|
|
278
|
+
else:
|
|
279
|
+
ret[key] = value
|
|
280
|
+
for _, (key, value) in address_merged.items():
|
|
281
|
+
ret[key] = value
|
|
172
282
|
|
|
173
283
|
ret['Content-Type'] = 'multipart/mixed'
|
|
174
284
|
|
|
@@ -184,7 +294,7 @@ class MessageBase(MSGFile):
|
|
|
184
294
|
if self.body:
|
|
185
295
|
bodyParts.attach(MIMEText(self.body, 'plain', c))
|
|
186
296
|
if self.htmlBody:
|
|
187
|
-
bodyParts.attach(MIMEText(self.htmlBody.
|
|
297
|
+
bodyParts.attach(MIMEText(str(bs4.BeautifulSoup(self.htmlBody, features = 'html.parser')), 'html', c))
|
|
188
298
|
|
|
189
299
|
# Process attachments.
|
|
190
300
|
for att in self.attachments:
|
|
@@ -855,9 +965,9 @@ class MessageBase(MSGFile):
|
|
|
855
965
|
raise ValueError(f'Invalid filename found in self.filename: "{self.filename}"')
|
|
856
966
|
|
|
857
967
|
# Add the file name to the path.
|
|
858
|
-
path /= filename[:maxNameLength]
|
|
968
|
+
path /= filename[:maxNameLength].strip()
|
|
859
969
|
else:
|
|
860
|
-
path /= self.defaultFolderName[:maxNameLength]
|
|
970
|
+
path /= self.defaultFolderName[:maxNameLength].strip()
|
|
861
971
|
|
|
862
972
|
# Create the folders.
|
|
863
973
|
if not _zip:
|
|
@@ -1178,7 +1288,7 @@ class MessageBase(MSGFile):
|
|
|
1178
1288
|
# Convert the plain text body to html.
|
|
1179
1289
|
logger.info('HTML body was not found, attempting to generate from plain text body.')
|
|
1180
1290
|
correctedBody = html.escape(self.body).replace('\r', '').replace('\n', '<br />')
|
|
1181
|
-
htmlBody = f'<html><body>{correctedBody}</body></
|
|
1291
|
+
htmlBody = f'<html><body>{correctedBody}</body></html>'.encode('ascii', 'xmlcharrefreplace')
|
|
1182
1292
|
|
|
1183
1293
|
if not htmlBody:
|
|
1184
1294
|
logger.info('HTML body could not be found nor generated.')
|
|
@@ -458,7 +458,7 @@ def getCommandArgs(args: Sequence[str]) -> argparse.Namespace:
|
|
|
458
458
|
help='Extracts the embedded MSG files as MSG files instead of running their save functions.')
|
|
459
459
|
# --overwrite-existing
|
|
460
460
|
parser.add_argument('--overwrite-existing', dest='overwriteExisting', action='store_true',
|
|
461
|
-
help='Disables filename conflict resolution code for attachments when saving a file, causing files to be
|
|
461
|
+
help='Disables filename conflict resolution code for attachments when saving a file, causing files to be overwritten if two attachments with the same filename are on an MSG file.')
|
|
462
462
|
# --skip-not-implemented
|
|
463
463
|
parser.add_argument('--skip-not-implemented', '--skip-ni', dest='skipNotImplemented', action='store_true',
|
|
464
464
|
help='Skips any attachments that are not implemented, allowing saving of the rest of the message.')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: extract-msg
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.56.1
|
|
4
4
|
Summary: Extracts emails and attachments saved in Microsoft Outlook's .msg files
|
|
5
5
|
Home-page: https://github.com/TeamMsgExtractor/msg-extractor
|
|
6
6
|
Download-URL: https://github.com/TeamMsgExtractor/msg-extractor/archives/master
|
|
@@ -278,8 +278,8 @@ your access to the newest major version of extract-msg.
|
|
|
278
278
|
.. |License: GPL v3| image:: https://img.shields.io/badge/License-GPLv3-blue.svg
|
|
279
279
|
:target: LICENSE.txt
|
|
280
280
|
|
|
281
|
-
.. |PyPI3| image:: https://img.shields.io/badge/pypi-0.
|
|
282
|
-
:target: https://pypi.org/project/extract-msg/0.
|
|
281
|
+
.. |PyPI3| image:: https://img.shields.io/badge/pypi-0.56.1-blue.svg
|
|
282
|
+
:target: https://pypi.org/project/extract-msg/0.56.1/
|
|
283
283
|
|
|
284
284
|
.. |PyPI2| image:: https://img.shields.io/badge/python-3.8+-brightgreen.svg
|
|
285
285
|
:target: https://www.python.org/downloads/release/python-3810/
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
olefile==0.47
|
|
6
6
|
tzlocal>=4.2,<6
|
|
7
7
|
compressed-rtf>=1.0.6,<2
|
|
8
|
-
ebcdic>=1.1.1,<
|
|
9
|
-
|
|
8
|
+
ebcdic>=1.1.1,<3
|
|
9
|
+
# Beautiful soup can sometimes make weird versioning, so something *could*
|
|
10
|
+
# break, but so long as they at least reasonably do sem-var this should be fine.
|
|
11
|
+
beautifulsoup4>=4.11.1,<5
|
|
10
12
|
RTFDE>=0.1.1,<0.2
|
|
11
13
|
red-black-tree-mod>=1.20, <=1.23
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/attachments/custom_att_handler/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{extract_msg-0.55.0 → extract_msg-0.56.1}/extract_msg/data/logging-config/logging-posix.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|