extract-msg 0.48.2__py3-none-any.whl
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/__init__.py +71 -0
- extract_msg/__main__.py +115 -0
- extract_msg/_rtf/__init__.py +21 -0
- extract_msg/_rtf/create_doc.py +29 -0
- extract_msg/_rtf/inject_rtf.py +174 -0
- extract_msg/_rtf/token.py +35 -0
- extract_msg/_rtf/tokenize_rtf.py +253 -0
- extract_msg/attachments/__init__.py +144 -0
- extract_msg/attachments/attachment.py +188 -0
- extract_msg/attachments/attachment_base.py +730 -0
- extract_msg/attachments/broken_att.py +42 -0
- extract_msg/attachments/custom_att.py +173 -0
- extract_msg/attachments/custom_att_handler/__init__.py +80 -0
- extract_msg/attachments/custom_att_handler/custom_handler.py +134 -0
- extract_msg/attachments/custom_att_handler/lnk_obj_att.py +109 -0
- extract_msg/attachments/custom_att_handler/outlook_image_dib.py +141 -0
- extract_msg/attachments/emb_msg_att.py +134 -0
- extract_msg/attachments/signed_att.py +234 -0
- extract_msg/attachments/unsupported_att.py +40 -0
- extract_msg/attachments/web_att.py +74 -0
- extract_msg/constants/__init__.py +294 -0
- extract_msg/constants/ps.py +46 -0
- extract_msg/constants/re.py +42 -0
- extract_msg/constants/st.py +110 -0
- extract_msg/data/logging-config/logging-nt.json +64 -0
- extract_msg/data/logging-config/logging-posix.json +64 -0
- extract_msg/encoding/__init__.py +277 -0
- extract_msg/encoding/_dt/__init__.py +4 -0
- extract_msg/encoding/_dt/_mac_ce.py +8 -0
- extract_msg/encoding/_dt/_mac_cyrillic.py +8 -0
- extract_msg/encoding/_dt/_mac_greek.py +8 -0
- extract_msg/encoding/_dt/_mac_iceland.py +8 -0
- extract_msg/encoding/_dt/_mac_turkish.py +8 -0
- extract_msg/encoding/_dt/_win874_dec.py +8 -0
- extract_msg/encoding/_dt/_win950_dec.py +6 -0
- extract_msg/encoding/utils.py +329 -0
- extract_msg/enums.py +2067 -0
- extract_msg/exceptions.py +170 -0
- extract_msg/msg_classes/__init__.py +49 -0
- extract_msg/msg_classes/appointment.py +195 -0
- extract_msg/msg_classes/calendar.py +92 -0
- extract_msg/msg_classes/calendar_base.py +496 -0
- extract_msg/msg_classes/contact.py +1335 -0
- extract_msg/msg_classes/journal.py +131 -0
- extract_msg/msg_classes/meeting_cancellation.py +108 -0
- extract_msg/msg_classes/meeting_exception.py +55 -0
- extract_msg/msg_classes/meeting_forward.py +115 -0
- extract_msg/msg_classes/meeting_related.py +65 -0
- extract_msg/msg_classes/meeting_request.py +189 -0
- extract_msg/msg_classes/meeting_response.py +74 -0
- extract_msg/msg_classes/message.py +12 -0
- extract_msg/msg_classes/message_base.py +1382 -0
- extract_msg/msg_classes/message_signed.py +17 -0
- extract_msg/msg_classes/message_signed_base.py +168 -0
- extract_msg/msg_classes/msg.py +1172 -0
- extract_msg/msg_classes/post.py +53 -0
- extract_msg/msg_classes/sticky_note.py +67 -0
- extract_msg/msg_classes/task.py +363 -0
- extract_msg/msg_classes/task_request.py +86 -0
- extract_msg/null_date.py +34 -0
- extract_msg/ole_writer.py +1086 -0
- extract_msg/open_msg.py +204 -0
- extract_msg/properties/__init__.py +23 -0
- extract_msg/properties/named.py +481 -0
- extract_msg/properties/prop.py +506 -0
- extract_msg/properties/properties_store.py +476 -0
- extract_msg/py.typed +0 -0
- extract_msg/recipient.py +362 -0
- extract_msg/structures/__init__.py +34 -0
- extract_msg/structures/_helpers.py +345 -0
- extract_msg/structures/business_card.py +422 -0
- extract_msg/structures/cfoas.py +108 -0
- extract_msg/structures/contact_link_entry.py +51 -0
- extract_msg/structures/dev_mode_a.py +621 -0
- extract_msg/structures/dv_target_device.py +168 -0
- extract_msg/structures/entry_id.py +697 -0
- extract_msg/structures/misc_id.py +222 -0
- extract_msg/structures/mon_stream.py +55 -0
- extract_msg/structures/odt.py +74 -0
- extract_msg/structures/ole_pres.py +302 -0
- extract_msg/structures/ole_stream_struct.py +117 -0
- extract_msg/structures/recurrence_pattern.py +199 -0
- extract_msg/structures/report_tag.py +127 -0
- extract_msg/structures/system_time.py +158 -0
- extract_msg/structures/time_zone_definition.py +111 -0
- extract_msg/structures/time_zone_struct.py +102 -0
- extract_msg/structures/toc_entry.py +137 -0
- extract_msg/structures/tz_rule.py +148 -0
- extract_msg/utils.py +1270 -0
- extract_msg-0.48.2.dist-info/LICENSE.txt +674 -0
- extract_msg-0.48.2.dist-info/METADATA +321 -0
- extract_msg-0.48.2.dist-info/RECORD +95 -0
- extract_msg-0.48.2.dist-info/WHEEL +5 -0
- extract_msg-0.48.2.dist-info/entry_points.txt +2 -0
- extract_msg-0.48.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'MessageSigned',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from typing import TypeVar
|
|
7
|
+
|
|
8
|
+
from .message_signed_base import MessageSignedBase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_T = TypeVar('_T')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MessageSigned(MessageSignedBase[_T]):
|
|
15
|
+
"""
|
|
16
|
+
Parser for Signed Microsoft Outlook message files.
|
|
17
|
+
"""
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'MessageSignedBase',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import functools
|
|
7
|
+
import html
|
|
8
|
+
import logging
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
from typing import Generic, List, Optional, Type, TypeVar
|
|
12
|
+
|
|
13
|
+
from ..attachments import AttachmentBase, SignedAttachment
|
|
14
|
+
from ..enums import AttachmentType, DeencapType, ErrorBehavior
|
|
15
|
+
from ..exceptions import StandardViolationError
|
|
16
|
+
from .message_base import MessageBase
|
|
17
|
+
from ..utils import inputToBytes, inputToString, unwrapMultipart
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
logger.addHandler(logging.NullHandler())
|
|
22
|
+
|
|
23
|
+
_T = TypeVar('_T')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MessageSignedBase(MessageBase, Generic[_T]):
|
|
27
|
+
"""
|
|
28
|
+
Base class for Message-like MSG files that are signed.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, path, signedAttachmentClass: Type[_T] = SignedAttachment, **kwargs):
|
|
32
|
+
"""
|
|
33
|
+
Supports all of the options from :meth:`MessageBase.__init__` with some
|
|
34
|
+
additional ones.
|
|
35
|
+
|
|
36
|
+
:param signedAttachmentClass: optional, the class the object will use
|
|
37
|
+
for signed attachments.
|
|
38
|
+
"""
|
|
39
|
+
self.__sAttCls = signedAttachmentClass
|
|
40
|
+
super().__init__(path, **kwargs)
|
|
41
|
+
|
|
42
|
+
@functools.cached_property
|
|
43
|
+
def attachments(self) -> List[_T]:
|
|
44
|
+
"""
|
|
45
|
+
A list of all attachments.
|
|
46
|
+
|
|
47
|
+
:raises StandardViolationError: The standard for signed messages was
|
|
48
|
+
blatantly violated.
|
|
49
|
+
"""
|
|
50
|
+
# Set these to None to make error handling easier.
|
|
51
|
+
self._signedBody = None
|
|
52
|
+
self._signedHtmlBody = None
|
|
53
|
+
|
|
54
|
+
atts = self.rawAttachments
|
|
55
|
+
|
|
56
|
+
if len(atts) != 1:
|
|
57
|
+
if ErrorBehavior.STANDARDS_VIOLATION in self.errorBehavior:
|
|
58
|
+
if len(atts) == 0:
|
|
59
|
+
logger.error('Standards Violation: Signed message has no attachments.')
|
|
60
|
+
return []
|
|
61
|
+
# If there is at least one attachment, log an error about it
|
|
62
|
+
# and then try to use the first one.
|
|
63
|
+
logger.error('Standards Violation: Signed message has more than one attachment. Attempting to use first one.')
|
|
64
|
+
else:
|
|
65
|
+
raise StandardViolationError('Signed messages without exactly 1 (regular) attachment constitute a violation of the standard.')
|
|
66
|
+
|
|
67
|
+
# If we are here, validate that the found attachment is acceptable.
|
|
68
|
+
if atts[0].type is not AttachmentType.DATA:
|
|
69
|
+
# This is unacceptable and the attachment cannot be used.
|
|
70
|
+
if ErrorBehavior.STANDARDS_VIOLATION in self.errorBehavior:
|
|
71
|
+
logger.error('Standards Violation: Attachment on signed message is unacceptable (not binary).')
|
|
72
|
+
return []
|
|
73
|
+
raise StandardViolationError('Signed messages *must* have a binary attachment. Signed attachments cannot be parsed otherwise.')
|
|
74
|
+
|
|
75
|
+
# Check the mimetype *directly*. Detection of mimetype through data is
|
|
76
|
+
# unacceptable for this check.
|
|
77
|
+
#temp = atts[0].getStringStream('__substg1.0_370E')
|
|
78
|
+
#if temp == 'multipart/signed':
|
|
79
|
+
## Do nothing, but this is how we detect that it is clear signed.
|
|
80
|
+
#pass
|
|
81
|
+
#else:
|
|
82
|
+
## Do nothing, but this is how we detect that it is *not* clear
|
|
83
|
+
# signed.
|
|
84
|
+
#pass
|
|
85
|
+
|
|
86
|
+
# We need to unwrap the multipart stream.
|
|
87
|
+
unwrapped = unwrapMultipart(atts[0].data)
|
|
88
|
+
|
|
89
|
+
# Now store everything where it needs to be and make the
|
|
90
|
+
# attachments.
|
|
91
|
+
self._signedBody = unwrapped['plain_body']
|
|
92
|
+
self._signedHtmlBody = inputToBytes(unwrapped['html_body'], 'utf-8')
|
|
93
|
+
|
|
94
|
+
return [self.__sAttCls(self, **att) for att in unwrapped['attachments']]
|
|
95
|
+
|
|
96
|
+
@functools.cached_property
|
|
97
|
+
def body(self) -> Optional[str]:
|
|
98
|
+
"""
|
|
99
|
+
The message body, if it exists.
|
|
100
|
+
"""
|
|
101
|
+
if (body := self.getStringStream('__substg1.0_1000')) is not None:
|
|
102
|
+
pass
|
|
103
|
+
elif self.signedBody:
|
|
104
|
+
body = self.signedBody
|
|
105
|
+
elif self.rtfBody:
|
|
106
|
+
# If the body doesn't exist, see if we can get it from the RTF
|
|
107
|
+
# body.
|
|
108
|
+
body = self.deencapsulateBody(self.rtfBody, DeencapType.PLAIN)
|
|
109
|
+
|
|
110
|
+
if body:
|
|
111
|
+
body = inputToString(body, 'utf-8')
|
|
112
|
+
if re.search('\n', body):
|
|
113
|
+
if re.search('\r\n', body):
|
|
114
|
+
self._crlf = '\r\n'
|
|
115
|
+
|
|
116
|
+
return body
|
|
117
|
+
|
|
118
|
+
@functools.cached_property
|
|
119
|
+
def htmlBody(self) -> Optional[bytes]:
|
|
120
|
+
"""
|
|
121
|
+
The html body, if it exists.
|
|
122
|
+
"""
|
|
123
|
+
if (htmlBody := self.getStream('__substg1.0_10130102')) is not None:
|
|
124
|
+
pass
|
|
125
|
+
elif self.signedHtmlBody:
|
|
126
|
+
htmlBody = self.signedHtmlBody
|
|
127
|
+
elif self.rtfBody:
|
|
128
|
+
logger.info('HTML body was not found, attempting to generate from RTF.')
|
|
129
|
+
htmlBody = self.deencapsulateBody(self.rtfBody, DeencapType.HTML)
|
|
130
|
+
elif self.body:
|
|
131
|
+
# Convert the plain text body to html.
|
|
132
|
+
logger.info('HTML body was not found, attempting to generate from plain text body.')
|
|
133
|
+
correctedBody = html.escpae(self.body).replace('\r', '').replace('\n', '<br />')
|
|
134
|
+
htmlBody = f'<html><body>{correctedBody}</body></head>'.encode('utf-8')
|
|
135
|
+
else:
|
|
136
|
+
logger.info('HTML body could not be found nor generated.')
|
|
137
|
+
|
|
138
|
+
return htmlBody
|
|
139
|
+
|
|
140
|
+
@functools.cached_property
|
|
141
|
+
def rawAttachments(self) -> List[AttachmentBase]:
|
|
142
|
+
"""
|
|
143
|
+
A property to allow access to the non-signed attachments.
|
|
144
|
+
"""
|
|
145
|
+
return super().attachments
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def signedAttachmentClass(self) -> Type[_T]:
|
|
149
|
+
"""
|
|
150
|
+
The attachment class used for signed attachments.
|
|
151
|
+
"""
|
|
152
|
+
return self.__sAttCls
|
|
153
|
+
|
|
154
|
+
@functools.cached_property
|
|
155
|
+
def signedBody(self) -> Optional[str]:
|
|
156
|
+
"""
|
|
157
|
+
The body from the signed message if it exists.
|
|
158
|
+
"""
|
|
159
|
+
self.attachments
|
|
160
|
+
return self._signedBody
|
|
161
|
+
|
|
162
|
+
@functools.cached_property
|
|
163
|
+
def signedHtmlBody(self) -> Optional[bytes]:
|
|
164
|
+
"""
|
|
165
|
+
The HTML body from the signed message if it exists.
|
|
166
|
+
"""
|
|
167
|
+
self.attachments
|
|
168
|
+
return self._signedHtmlBody
|