extract-msg 0.54.0__tar.gz → 0.55.0__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.54.0 → extract_msg-0.55.0}/CHANGELOG.md +11 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/PKG-INFO +3 -3
- {extract_msg-0.54.0 → extract_msg-0.55.0}/README.rst +2 -2
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/__init__.py +2 -2
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/attachment.py +5 -3
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/constants/re.py +12 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/message_base.py +19 -22
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/msg.py +19 -3
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/open_msg.py +1 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/utils.py +59 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg.egg-info/PKG-INFO +3 -3
- {extract_msg-0.54.0 → extract_msg-0.55.0}/LICENSE.txt +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/MANIFEST.in +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/__main__.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/_rtf/__init__.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/_rtf/create_doc.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/_rtf/inject_rtf.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/_rtf/token.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/_rtf/tokenize_rtf.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/__init__.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/attachment_base.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/broken_att.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att_handler/__init__.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att_handler/custom_handler.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att_handler/lnk_obj_att.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att_handler/outlook_image_dib.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att_handler/outlook_image_meta.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/emb_msg_att.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/signed_att.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/unsupported_att.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/web_att.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/constants/__init__.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/constants/ps.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/constants/st.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/data/logging-config/logging-nt.json +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/data/logging-config/logging-posix.json +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/__init__.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/__init__.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_mac_ce.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_mac_cyrillic.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_mac_greek.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_mac_iceland.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_mac_turkish.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_win874_dec.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_win950_dec.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/utils.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/enums.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/exceptions.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/__init__.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/appointment.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/calendar.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/calendar_base.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/contact.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/journal.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/meeting_cancellation.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/meeting_exception.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/meeting_forward.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/meeting_related.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/meeting_request.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/meeting_response.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/message.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/message_signed.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/message_signed_base.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/post.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/sticky_note.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/task.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/task_request.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/null_date.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/ole_writer.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/properties/__init__.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/properties/named.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/properties/prop.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/properties/properties_store.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/py.typed +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/recipient.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/__init__.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/_helpers.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/business_card.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/cfoas.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/contact_link_entry.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/dev_mode_a.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/dv_target_device.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/entry_id.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/misc_id.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/mon_stream.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/odt.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/ole_pres.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/ole_stream_struct.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/recurrence_pattern.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/report_tag.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/system_time.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/time_zone_definition.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/time_zone_struct.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/toc_entry.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/tz_rule.py +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg.egg-info/SOURCES.txt +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg.egg-info/dependency_links.txt +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg.egg-info/entry_points.txt +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg.egg-info/requires.txt +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg.egg-info/top_level.txt +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/requirements.txt +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/setup.cfg +0 -0
- {extract_msg-0.54.0 → extract_msg-0.55.0}/setup.py +0 -0
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
**v0.55.0**
|
|
2
|
+
* [[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
|
+
* Adjusted the default value of `maxNameLength` for `MessageBase.save()` to 40 instead of 256.
|
|
4
|
+
* Adjusted exception handling for `MessageBase.save()` to properly report the reason a folder fails to be created.
|
|
5
|
+
* Simplified some of the code for `MessageBase.save()`.
|
|
6
|
+
* Fixed some typing information.
|
|
7
|
+
|
|
8
|
+
**v0.54.1**
|
|
9
|
+
* [[TeamMsgExtractor #462](https://github.com/TeamMsgExtractor/msg-extractor/issues/462)] Fix potential issue where child MSG might have incompatible encoding to parent MSG when trying to grab a stream from the parent.
|
|
10
|
+
* Added code to attempt to significantly improve RTF deencapsulation times. This tries to strip away unneeded data before passing it to `RTFDE`. This shows improvements on all files that take more than one second. Currently, this actually fixes some files previously outputting wrong from `RTFDE` when deencapsulating the HTML body, specifically around non breaking spaces sometimes not transferring over.
|
|
11
|
+
|
|
1
12
|
**v0.54.0**
|
|
2
13
|
* [[TeamMsgExtractor #456](https://github.com/TeamMsgExtractor/msg-extractor/issues/456)] Changed the prepared html output to use plainly encoded HTML instead of prettified, since current prettification options used mangles the output and causes the output to sometimes be very large.
|
|
3
14
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: extract_msg
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.55.0
|
|
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.55.0-blue.svg
|
|
282
|
+
:target: https://pypi.org/project/extract-msg/0.55.0/
|
|
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.55.0-blue.svg
|
|
264
|
+
:target: https://pypi.org/project/extract-msg/0.55.0/
|
|
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__ = '2025-
|
|
31
|
-
__version__ = '0.
|
|
30
|
+
__date__ = '2025-08-12'
|
|
31
|
+
__version__ = '0.55.0'
|
|
32
32
|
|
|
33
33
|
__all__ = [
|
|
34
34
|
# Modules:
|
|
@@ -13,7 +13,7 @@ import random
|
|
|
13
13
|
import string
|
|
14
14
|
import zipfile
|
|
15
15
|
|
|
16
|
-
from typing import TYPE_CHECKING
|
|
16
|
+
from typing import Optional, TYPE_CHECKING
|
|
17
17
|
|
|
18
18
|
from .. import constants
|
|
19
19
|
from .attachment_base import AttachmentBase
|
|
@@ -72,7 +72,7 @@ class Attachment(AttachmentBase):
|
|
|
72
72
|
|
|
73
73
|
return filename
|
|
74
74
|
|
|
75
|
-
def regenerateRandomName(self) ->
|
|
75
|
+
def regenerateRandomName(self) -> None:
|
|
76
76
|
"""
|
|
77
77
|
Used to regenerate the random filename used if the attachment cannot
|
|
78
78
|
find a usable filename.
|
|
@@ -166,9 +166,11 @@ class Attachment(AttachmentBase):
|
|
|
166
166
|
_zip.close()
|
|
167
167
|
|
|
168
168
|
@property
|
|
169
|
-
def data(self) -> bytes:
|
|
169
|
+
def data(self) -> Optional[bytes]:
|
|
170
170
|
"""
|
|
171
171
|
The bytes making up the attachment data.
|
|
172
|
+
|
|
173
|
+
If the attachment data stream does not exist, returns None.
|
|
172
174
|
"""
|
|
173
175
|
return self.__data
|
|
174
176
|
|
|
@@ -8,6 +8,9 @@ __all__ = [
|
|
|
8
8
|
'HTML_SAN_SPACE',
|
|
9
9
|
'INVALID_FILENAME_CHARS',
|
|
10
10
|
'INVALID_OLE_PATH',
|
|
11
|
+
'RTF_BODY_STRIP_INIT',
|
|
12
|
+
'RTF_BODY_STRIP_PRE_CLOSE',
|
|
13
|
+
'RTF_BODY_STRIP_PRE_OPEN',
|
|
11
14
|
'RTF_ENC_BODY_START',
|
|
12
15
|
]
|
|
13
16
|
|
|
@@ -40,3 +43,12 @@ RTF_ENC_BODY_START: Final[_RE_BYTES_TYPE] = re.compile(br'\{\\\*\\htmltag[0-9]*
|
|
|
40
43
|
# invalid.
|
|
41
44
|
INVALID_OLE_PATH: Final[_RE_STR_TYPE] = re.compile(r'[:/\\!]')
|
|
42
45
|
|
|
46
|
+
# Used as the initial step in stripping RTF files for deencapsulation. Finds
|
|
47
|
+
# ignored sections that do not contrain groups *and* finds HTML tag sections
|
|
48
|
+
# that are entirely empty. It also then finds sections of data that can be
|
|
49
|
+
# merged together without affecting the results
|
|
50
|
+
RTF_BODY_STRIP_INIT: Final[_RE_BYTES_TYPE] = re.compile(rb'(\\htmlrtf[^0{}][^{}]*?\\htmlrtf0 ?)|(\{\\\*\\htmltag[0-9]+\})|(\\htmlrtf0 ?\\htmlrtf1? ?)|(\\htmlrtf1? ?\{\}\\htmlrtf0 ?)|(\\htmlrtf1? ?\\\'[a-fA-F0-9]{2}\\htmlrtf0 ?)')
|
|
51
|
+
|
|
52
|
+
# Preprocessing steps to simplify the RTF.
|
|
53
|
+
RTF_BODY_STRIP_PRE_CLOSE: Final[_RE_BYTES_TYPE] = re.compile(rb'(\\htmlrtf1? ?}\\htmlrtf0 ?)|(\\htmlrtf1? ?[^0{}][^{}]*?} ?\\htmlrtf0 ?)')
|
|
54
|
+
RTF_BODY_STRIP_PRE_OPEN: Final[_RE_BYTES_TYPE] = re.compile(rb'\\htmlrtf1? ?{[^{}]*?\\htmlrtf0 ?')
|
|
@@ -48,7 +48,8 @@ from ..recipient import Recipient
|
|
|
48
48
|
from ..utils import (
|
|
49
49
|
addNumToDir, addNumToZipDir, createZipOpen, decodeRfc2047, findWk,
|
|
50
50
|
htmlSanitize, inputToBytes, inputToString, isEncapsulatedRtf,
|
|
51
|
-
prepareFilename, rtfSanitizeHtml, rtfSanitizePlain,
|
|
51
|
+
prepareFilename, rtfSanitizeHtml, rtfSanitizePlain, stripRtf,
|
|
52
|
+
validateHtml
|
|
52
53
|
)
|
|
53
54
|
|
|
54
55
|
|
|
@@ -725,12 +726,12 @@ class MessageBase(MSGFile):
|
|
|
725
726
|
pdf = kwargs.get('pdf', False)
|
|
726
727
|
allowFallback = kwargs.get('allowFallback', False)
|
|
727
728
|
_zip = kwargs.get('zip')
|
|
728
|
-
maxNameLength = kwargs.get('maxNameLength',
|
|
729
|
+
maxNameLength = kwargs.get('maxNameLength', 40)
|
|
729
730
|
|
|
730
731
|
# Variables involved in the save location.
|
|
731
732
|
customFilename = kwargs.get('customFilename')
|
|
732
733
|
useMsgFilename = kwargs.get('useMsgFilename', False)
|
|
733
|
-
#maxPathLength = kwargs.get('maxPathLength', 255)
|
|
734
|
+
#maxPathLength = kwargs.get('maxPathLength', 255) # TODO
|
|
734
735
|
|
|
735
736
|
# Track if we are only saving the attachments.
|
|
736
737
|
attachOnly = kwargs.get('attachmentsOnly', False)
|
|
@@ -741,6 +742,8 @@ class MessageBase(MSGFile):
|
|
|
741
742
|
# raising an exception.
|
|
742
743
|
skipBodyNotFound = kwargs.get('skipBodyNotFound', False)
|
|
743
744
|
|
|
745
|
+
fext = None
|
|
746
|
+
|
|
744
747
|
if pdf:
|
|
745
748
|
kwargs['preparedHtml'] = True
|
|
746
749
|
|
|
@@ -757,30 +760,22 @@ class MessageBase(MSGFile):
|
|
|
757
760
|
if self.htmlBody:
|
|
758
761
|
useHtml = True
|
|
759
762
|
fext = 'html'
|
|
760
|
-
elif not allowFallback:
|
|
761
|
-
|
|
762
|
-
fext = None
|
|
763
|
-
else:
|
|
764
|
-
raise DataNotFoundError('Could not find the htmlBody.')
|
|
763
|
+
elif not allowFallback and not skipBodyNotFound:
|
|
764
|
+
raise DataNotFoundError('Could not find the htmlBody.')
|
|
765
765
|
|
|
766
766
|
if pdf:
|
|
767
767
|
if self.htmlBody:
|
|
768
768
|
usePdf = True
|
|
769
769
|
fext = 'pdf'
|
|
770
|
-
elif not allowFallback:
|
|
771
|
-
|
|
772
|
-
fext = None
|
|
773
|
-
else:
|
|
774
|
-
raise DataNotFoundError('Count not find the htmlBody to convert to pdf.')
|
|
770
|
+
elif not allowFallback and not skipBodyNotFound:
|
|
771
|
+
raise DataNotFoundError('Count not find the htmlBody to convert to pdf.')
|
|
775
772
|
|
|
776
773
|
if rtf or (html and not useHtml) or (pdf and not usePdf):
|
|
777
774
|
if self.rtfBody:
|
|
778
775
|
useRtf = True
|
|
779
776
|
fext = 'rtf'
|
|
780
777
|
elif not allowFallback:
|
|
781
|
-
if skipBodyNotFound:
|
|
782
|
-
fext = None
|
|
783
|
-
else:
|
|
778
|
+
if not skipBodyNotFound:
|
|
784
779
|
raise DataNotFoundError('Could not find the rtfBody.')
|
|
785
780
|
else:
|
|
786
781
|
# This was the last resort before plain text, so fall
|
|
@@ -793,10 +788,7 @@ class MessageBase(MSGFile):
|
|
|
793
788
|
# We need to check if the plain text body was found. If it
|
|
794
789
|
# was found but was empty that is considered valid, so we
|
|
795
790
|
# specifically check against None.
|
|
796
|
-
if self.body is None:
|
|
797
|
-
if skipBodyNotFound:
|
|
798
|
-
fext = None
|
|
799
|
-
else:
|
|
791
|
+
if self.body is None and not skipBodyNotFound:
|
|
800
792
|
if allowFallback:
|
|
801
793
|
raise DataNotFoundError('Could not find a valid body using current options.')
|
|
802
794
|
else:
|
|
@@ -871,12 +863,12 @@ class MessageBase(MSGFile):
|
|
|
871
863
|
if not _zip:
|
|
872
864
|
try:
|
|
873
865
|
os.makedirs(path)
|
|
874
|
-
except Exception:
|
|
866
|
+
except Exception as e:
|
|
875
867
|
newDirName = addNumToDir(path)
|
|
876
868
|
if newDirName:
|
|
877
869
|
path = newDirName
|
|
878
870
|
else:
|
|
879
|
-
raise OSError(f'Failed to create directory "{path}".
|
|
871
|
+
raise OSError(f'Failed to create directory "{path}". Reason: {e}')
|
|
880
872
|
else:
|
|
881
873
|
# In my testing I ended up with multiple files in a zip at the
|
|
882
874
|
# same location so let's try to handle that.
|
|
@@ -1012,6 +1004,11 @@ class MessageBase(MSGFile):
|
|
|
1012
1004
|
while body and body[-1] != 125:
|
|
1013
1005
|
body = body[:-1]
|
|
1014
1006
|
|
|
1007
|
+
# Some files take a long time due to how they are structured and
|
|
1008
|
+
# how RTFDE works. The longer a file would normally take, the
|
|
1009
|
+
# better this fix works:
|
|
1010
|
+
body = stripRtf(body)
|
|
1011
|
+
|
|
1015
1012
|
try:
|
|
1016
1013
|
deencapsultor = RTFDE.DeEncapsulator(body)
|
|
1017
1014
|
deencapsultor.deencapsulate()
|
|
@@ -203,7 +203,23 @@ class MSGFile:
|
|
|
203
203
|
self.__overrideEncoding = overrideEncoding
|
|
204
204
|
|
|
205
205
|
if prefix and not filename:
|
|
206
|
-
|
|
206
|
+
# We actually need to get this from the parent.
|
|
207
|
+
msg = None
|
|
208
|
+
parentNeedsClose = False
|
|
209
|
+
if self.__parentMsg:
|
|
210
|
+
msg = self.__parentMsg()
|
|
211
|
+
if msg is None:
|
|
212
|
+
# We *NEED* the parent here, so we're going to do something
|
|
213
|
+
# dumb and just generate it *manually*, grab what we need, # and them immediately close it.
|
|
214
|
+
#
|
|
215
|
+
# We don't need anything more advanced than MSGFile.
|
|
216
|
+
msg = MSGFile(path, prefix = prefixl[:-2], delayAttachments = True)
|
|
217
|
+
parentNeedsClose = True
|
|
218
|
+
# Now that we know we have the parent, grab the stream.
|
|
219
|
+
filename = msg.getStringStream(prefixl[:-1] + ['__substg1.0_3001'], prefix = False)
|
|
220
|
+
# Now if we opened the parent, close it.
|
|
221
|
+
if parentNeedsClose:
|
|
222
|
+
msg.close()
|
|
207
223
|
if filename:
|
|
208
224
|
self.filename = filename
|
|
209
225
|
elif hasattr(path, '__len__'):
|
|
@@ -492,7 +508,7 @@ class MSGFile:
|
|
|
492
508
|
|
|
493
509
|
:param path: A path-like object (including strings and ``pathlib.Path``
|
|
494
510
|
objects) or an IO device with a write method which accepts bytes.
|
|
495
|
-
:param allowBadEmbed: If True, attempts to skip steps that will fail if
|
|
511
|
+
:param allowBadEmbed: If True, attempts to skip steps that will fail if
|
|
496
512
|
the embedded MSG file violates standards. It will also attempt to repair the data to try to ensure it can open in Outlook.
|
|
497
513
|
"""
|
|
498
514
|
from ..ole_writer import OleWriter
|
|
@@ -507,7 +523,7 @@ class MSGFile:
|
|
|
507
523
|
"""
|
|
508
524
|
Saves a new copy of the MSG file, returning the bytes.
|
|
509
525
|
|
|
510
|
-
:param allowBadEmbed: If True, attempts to skip steps that will fail if
|
|
526
|
+
:param allowBadEmbed: If True, attempts to skip steps that will fail if
|
|
511
527
|
the embedded MSG file violates standards. It will also attempt to repair the data to try to ensure it can open in Outlook.
|
|
512
528
|
"""
|
|
513
529
|
out = io.BytesIO()
|
|
@@ -109,6 +109,7 @@ def openMsg(path, **kwargs) -> MSGFile:
|
|
|
109
109
|
# lower function. So let's make sure we got a good return first.
|
|
110
110
|
if not ct:
|
|
111
111
|
if kwargs.get('strict', True):
|
|
112
|
+
msg.close()
|
|
112
113
|
raise InvalidFileFormatError('File was confirmed to be an olefile, but was not an MSG file.')
|
|
113
114
|
else:
|
|
114
115
|
# If strict mode is off, we'll just return an MSGFile anyways.
|
|
@@ -37,6 +37,7 @@ __all__ = [
|
|
|
37
37
|
'rtfSanitizeHtml',
|
|
38
38
|
'rtfSanitizePlain',
|
|
39
39
|
'setupLogging',
|
|
40
|
+
'stripRtf',
|
|
40
41
|
'tryGetMimetype',
|
|
41
42
|
'unsignedToSignedInt',
|
|
42
43
|
'unwrapMsg',
|
|
@@ -61,6 +62,7 @@ import logging
|
|
|
61
62
|
import logging.config
|
|
62
63
|
import os
|
|
63
64
|
import pathlib
|
|
65
|
+
import re
|
|
64
66
|
import shutil
|
|
65
67
|
import struct
|
|
66
68
|
import sys
|
|
@@ -1012,6 +1014,63 @@ def setupLogging(defaultPath = None, defaultLevel = logging.WARN, logfile = None
|
|
|
1012
1014
|
return True
|
|
1013
1015
|
|
|
1014
1016
|
|
|
1017
|
+
def stripRtf(rtfBody: bytes) -> bytes:
|
|
1018
|
+
"""
|
|
1019
|
+
Cleans up RTF before sending it to RTFDE.
|
|
1020
|
+
|
|
1021
|
+
Attempts to find common sections of RTF data that will
|
|
1022
|
+
"""
|
|
1023
|
+
# First, do a pre-strip to try and simplify ignored sections as much as possible.
|
|
1024
|
+
rtfBody = constants.re.RTF_BODY_STRIP_PRE_OPEN.sub(_stripRtfOpenHelper, rtfBody)
|
|
1025
|
+
rtfBody = constants.re.RTF_BODY_STRIP_PRE_CLOSE.sub(_stripRtfCloseHelper, rtfBody)
|
|
1026
|
+
# Second do an initial strip to simplify our data stream.
|
|
1027
|
+
rtfBody = constants.re.RTF_BODY_STRIP_INIT.sub(b'', rtfBody)
|
|
1028
|
+
# Do it one more time to help with some things that might not have gotten
|
|
1029
|
+
# caught the first time, perhaps because something now exists after
|
|
1030
|
+
# stripping.
|
|
1031
|
+
rtfBody = constants.re.RTF_BODY_STRIP_INIT.sub(b'', rtfBody)
|
|
1032
|
+
|
|
1033
|
+
# TODO: Further processing...
|
|
1034
|
+
|
|
1035
|
+
return rtfBody
|
|
1036
|
+
|
|
1037
|
+
def _stripRtfCloseHelper(match: re.Match) -> bytes:
|
|
1038
|
+
if (ret := match.expand(b'\\g<0>')).count(b'\\htmlrtf0') > 1:
|
|
1039
|
+
return ret
|
|
1040
|
+
|
|
1041
|
+
if b'\\f' in ret:
|
|
1042
|
+
return ret
|
|
1043
|
+
|
|
1044
|
+
return b'\\htmlrtf}\\htmlrtf0 '
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
def _stripRtfOpenHelper(match: re.Match) -> bytes:
|
|
1048
|
+
if b'\\f' in (ret := match.expand(b'\\g<0>')):
|
|
1049
|
+
return ret
|
|
1050
|
+
|
|
1051
|
+
return b'\\htmlrtf{\\htmlrtf0 '
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
def _stripRtfHelper(match: re.Match) -> bytes:
|
|
1055
|
+
res = match.string
|
|
1056
|
+
|
|
1057
|
+
# If these don't match, don't even try.
|
|
1058
|
+
if res.count(b'{') != res.count(b'}') or res.count(b'{') == 0:
|
|
1059
|
+
return res
|
|
1060
|
+
|
|
1061
|
+
# If any group markers are prefixed by a backslash, give up.
|
|
1062
|
+
if res.find(b'\\{') != -1 or res.find(b'\\}') != -1:
|
|
1063
|
+
return res
|
|
1064
|
+
|
|
1065
|
+
# Last little bit of processing to validate everything. We know the {}
|
|
1066
|
+
# match, but let's be *absolutely* sure.
|
|
1067
|
+
# TODO
|
|
1068
|
+
|
|
1069
|
+
return res
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
|
|
1015
1074
|
def tryGetMimetype(att: AttachmentBase, mimetype: Union[str, None]) -> Union[str, None]:
|
|
1016
1075
|
"""
|
|
1017
1076
|
Uses an optional dependency to try and get the mimetype of an attachment.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: extract-msg
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.55.0
|
|
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.55.0-blue.svg
|
|
282
|
+
:target: https://pypi.org/project/extract-msg/0.55.0/
|
|
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/
|
|
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.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att_handler/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att_handler/lnk_obj_att.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
|
|
File without changes
|
{extract_msg-0.54.0 → extract_msg-0.55.0}/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
|