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.
Files changed (104) hide show
  1. {extract_msg-0.54.0 → extract_msg-0.55.0}/CHANGELOG.md +11 -0
  2. {extract_msg-0.54.0 → extract_msg-0.55.0}/PKG-INFO +3 -3
  3. {extract_msg-0.54.0 → extract_msg-0.55.0}/README.rst +2 -2
  4. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/__init__.py +2 -2
  5. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/attachment.py +5 -3
  6. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/constants/re.py +12 -0
  7. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/message_base.py +19 -22
  8. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/msg.py +19 -3
  9. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/open_msg.py +1 -0
  10. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/utils.py +59 -0
  11. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg.egg-info/PKG-INFO +3 -3
  12. {extract_msg-0.54.0 → extract_msg-0.55.0}/LICENSE.txt +0 -0
  13. {extract_msg-0.54.0 → extract_msg-0.55.0}/MANIFEST.in +0 -0
  14. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/__main__.py +0 -0
  15. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/_rtf/__init__.py +0 -0
  16. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/_rtf/create_doc.py +0 -0
  17. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/_rtf/inject_rtf.py +0 -0
  18. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/_rtf/token.py +0 -0
  19. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/_rtf/tokenize_rtf.py +0 -0
  20. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/__init__.py +0 -0
  21. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/attachment_base.py +0 -0
  22. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/broken_att.py +0 -0
  23. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att.py +0 -0
  24. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att_handler/__init__.py +0 -0
  25. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att_handler/custom_handler.py +0 -0
  26. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att_handler/lnk_obj_att.py +0 -0
  27. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att_handler/outlook_image_dib.py +0 -0
  28. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/custom_att_handler/outlook_image_meta.py +0 -0
  29. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/emb_msg_att.py +0 -0
  30. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/signed_att.py +0 -0
  31. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/unsupported_att.py +0 -0
  32. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/attachments/web_att.py +0 -0
  33. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/constants/__init__.py +0 -0
  34. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/constants/ps.py +0 -0
  35. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/constants/st.py +0 -0
  36. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/data/logging-config/logging-nt.json +0 -0
  37. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/data/logging-config/logging-posix.json +0 -0
  38. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/__init__.py +0 -0
  39. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/__init__.py +0 -0
  40. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_mac_ce.py +0 -0
  41. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_mac_cyrillic.py +0 -0
  42. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_mac_greek.py +0 -0
  43. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_mac_iceland.py +0 -0
  44. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_mac_turkish.py +0 -0
  45. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_win874_dec.py +0 -0
  46. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/_dt/_win950_dec.py +0 -0
  47. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/encoding/utils.py +0 -0
  48. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/enums.py +0 -0
  49. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/exceptions.py +0 -0
  50. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/__init__.py +0 -0
  51. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/appointment.py +0 -0
  52. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/calendar.py +0 -0
  53. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/calendar_base.py +0 -0
  54. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/contact.py +0 -0
  55. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/journal.py +0 -0
  56. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/meeting_cancellation.py +0 -0
  57. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/meeting_exception.py +0 -0
  58. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/meeting_forward.py +0 -0
  59. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/meeting_related.py +0 -0
  60. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/meeting_request.py +0 -0
  61. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/meeting_response.py +0 -0
  62. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/message.py +0 -0
  63. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/message_signed.py +0 -0
  64. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/message_signed_base.py +0 -0
  65. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/post.py +0 -0
  66. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/sticky_note.py +0 -0
  67. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/task.py +0 -0
  68. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/msg_classes/task_request.py +0 -0
  69. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/null_date.py +0 -0
  70. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/ole_writer.py +0 -0
  71. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/properties/__init__.py +0 -0
  72. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/properties/named.py +0 -0
  73. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/properties/prop.py +0 -0
  74. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/properties/properties_store.py +0 -0
  75. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/py.typed +0 -0
  76. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/recipient.py +0 -0
  77. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/__init__.py +0 -0
  78. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/_helpers.py +0 -0
  79. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/business_card.py +0 -0
  80. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/cfoas.py +0 -0
  81. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/contact_link_entry.py +0 -0
  82. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/dev_mode_a.py +0 -0
  83. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/dv_target_device.py +0 -0
  84. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/entry_id.py +0 -0
  85. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/misc_id.py +0 -0
  86. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/mon_stream.py +0 -0
  87. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/odt.py +0 -0
  88. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/ole_pres.py +0 -0
  89. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/ole_stream_struct.py +0 -0
  90. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/recurrence_pattern.py +0 -0
  91. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/report_tag.py +0 -0
  92. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/system_time.py +0 -0
  93. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/time_zone_definition.py +0 -0
  94. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/time_zone_struct.py +0 -0
  95. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/toc_entry.py +0 -0
  96. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg/structures/tz_rule.py +0 -0
  97. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg.egg-info/SOURCES.txt +0 -0
  98. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg.egg-info/dependency_links.txt +0 -0
  99. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg.egg-info/entry_points.txt +0 -0
  100. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg.egg-info/requires.txt +0 -0
  101. {extract_msg-0.54.0 → extract_msg-0.55.0}/extract_msg.egg-info/top_level.txt +0 -0
  102. {extract_msg-0.54.0 → extract_msg-0.55.0}/requirements.txt +0 -0
  103. {extract_msg-0.54.0 → extract_msg-0.55.0}/setup.cfg +0 -0
  104. {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.54.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.54.0-blue.svg
282
- :target: https://pypi.org/project/extract-msg/0.54.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.54.0-blue.svg
264
- :target: https://pypi.org/project/extract-msg/0.54.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-03-23'
31
- __version__ = '0.54.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) -> str:
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, validateHtml
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', 256)
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
- if skipBodyNotFound:
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
- if skipBodyNotFound:
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}". Does it already exist?')
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
- filename = self.getStringStream(prefixl[:-1] + ['__substg1.0_3001'], prefix = False)
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.54.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.54.0-blue.svg
282
- :target: https://pypi.org/project/extract-msg/0.54.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