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,134 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
'EmbeddedMsgAttachment',
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import pathlib
|
|
11
|
+
import zipfile
|
|
12
|
+
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
from .. import constants
|
|
16
|
+
from .attachment_base import AttachmentBase
|
|
17
|
+
from ..enums import AttachmentType, SaveType
|
|
18
|
+
from ..open_msg import openMsg
|
|
19
|
+
from ..utils import createZipOpen, prepareFilename
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from ..msg_classes import MSGFile
|
|
24
|
+
from ..properties import PropertiesStore
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_saveDoc = AttachmentBase.save.__doc__
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class EmbeddedMsgAttachment(AttachmentBase):
|
|
31
|
+
"""
|
|
32
|
+
The attachment entry for an Embedded MSG file.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, msg: MSGFile, dir_: str, propStore: PropertiesStore):
|
|
36
|
+
super().__init__(msg, dir_, propStore)
|
|
37
|
+
self.__prefix = msg.prefixList + [dir_, '__substg1.0_3701000D']
|
|
38
|
+
self.__data = openMsg(self.msg.path, prefix = self.__prefix, parentMsg = self.msg, treePath = self.treePath, **self.msg.kwargs)
|
|
39
|
+
|
|
40
|
+
def getFilename(self, **kwargs) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Returns the filename to use for the attachment.
|
|
43
|
+
|
|
44
|
+
:param contentId: Use the contentId, if available.
|
|
45
|
+
:param customFilename: A custom name to use for the file.
|
|
46
|
+
|
|
47
|
+
If the filename starts with "UnknownFilename" then there is no guarantee
|
|
48
|
+
that the files will have exactly the same filename.
|
|
49
|
+
"""
|
|
50
|
+
customFilename = kwargs.get('customFilename')
|
|
51
|
+
if customFilename:
|
|
52
|
+
customFilename = str(customFilename)
|
|
53
|
+
# First we need to validate it. If there are invalid characters,
|
|
54
|
+
# this will detect it.
|
|
55
|
+
if constants.re.INVALID_FILENAME_CHARS.search(customFilename):
|
|
56
|
+
raise ValueError('Invalid character found in customFilename. Must not contain any of the following characters: \\/:*?"<>|')
|
|
57
|
+
return customFilename
|
|
58
|
+
else:
|
|
59
|
+
return self.name
|
|
60
|
+
|
|
61
|
+
def save(self, **kwargs) -> constants.SAVE_TYPE:
|
|
62
|
+
# First check if we are skipping embedded messages and stop
|
|
63
|
+
# *immediately* if we are.
|
|
64
|
+
if kwargs.get('skipEmbedded'):
|
|
65
|
+
return (SaveType.NONE, None)
|
|
66
|
+
|
|
67
|
+
# We only need to handle things if we are saving as bytes.
|
|
68
|
+
if kwargs.get('extractEmbedded', False):
|
|
69
|
+
# Get the filename to use.
|
|
70
|
+
filename = self.getFilename(**kwargs)
|
|
71
|
+
|
|
72
|
+
# Someone managed to have a null character here, so let's get rid of
|
|
73
|
+
# that
|
|
74
|
+
filename = prepareFilename(filename)
|
|
75
|
+
|
|
76
|
+
# Get the maximum name length.
|
|
77
|
+
maxNameLength = kwargs.get('maxNameLength', 256)
|
|
78
|
+
|
|
79
|
+
# Make sure the filename is not longer than it should be.
|
|
80
|
+
if len(filename) > maxNameLength:
|
|
81
|
+
name, ext = os.path.splitext(filename)
|
|
82
|
+
filename = name[:maxNameLength - len(ext)] + ext
|
|
83
|
+
|
|
84
|
+
# Check if we are doing a zip file.
|
|
85
|
+
_zip = kwargs.get('zip')
|
|
86
|
+
|
|
87
|
+
createdZip = False
|
|
88
|
+
try:
|
|
89
|
+
# ZipFile handling.
|
|
90
|
+
if _zip:
|
|
91
|
+
# If we are doing a zip file, first check that we have been given a path.
|
|
92
|
+
if isinstance(_zip, (str, pathlib.Path)):
|
|
93
|
+
# If we have a path then we use the zip file.
|
|
94
|
+
_zip = zipfile.ZipFile(_zip, 'a', zipfile.ZIP_DEFLATED)
|
|
95
|
+
kwargs['zip'] = _zip
|
|
96
|
+
createdZip = True
|
|
97
|
+
# Path needs to be done in a special way if we are in a zip file.
|
|
98
|
+
customPath = pathlib.Path(kwargs.get('customPath', ''))
|
|
99
|
+
# Set the open command to be that of the zip file.
|
|
100
|
+
_open = createZipOpen(_zip.open)
|
|
101
|
+
# Zip files use w for writing in binary.
|
|
102
|
+
mode = 'w'
|
|
103
|
+
else:
|
|
104
|
+
customPath = pathlib.Path(kwargs.get('customPath', '.')).absolute()
|
|
105
|
+
mode = 'wb'
|
|
106
|
+
_open = open
|
|
107
|
+
|
|
108
|
+
fullFilename = self._handleFnc(_zip, filename, customPath, kwargs)
|
|
109
|
+
|
|
110
|
+
with _open(str(fullFilename), mode) as f:
|
|
111
|
+
self.data.export(f)
|
|
112
|
+
|
|
113
|
+
return (SaveType.FILE, str(fullFilename))
|
|
114
|
+
finally:
|
|
115
|
+
# Close the ZipFile if this function created it.
|
|
116
|
+
if _zip and createdZip:
|
|
117
|
+
_zip.close()
|
|
118
|
+
else:
|
|
119
|
+
# If we are letting the MSG file create stuff, just let it handle
|
|
120
|
+
# everything.
|
|
121
|
+
return self.data.save(**kwargs)
|
|
122
|
+
|
|
123
|
+
save.__doc__ = _saveDoc
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def data(self) -> MSGFile:
|
|
127
|
+
"""
|
|
128
|
+
Returns the attachment data.
|
|
129
|
+
"""
|
|
130
|
+
return self.__data
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def type(self) -> AttachmentType:
|
|
134
|
+
return AttachmentType.MSG
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
'SignedAttachment',
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import email.message
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import pathlib
|
|
13
|
+
import weakref
|
|
14
|
+
import zipfile
|
|
15
|
+
|
|
16
|
+
from typing import List, Optional, Type, TYPE_CHECKING, Union
|
|
17
|
+
|
|
18
|
+
from .. import constants
|
|
19
|
+
from ..enums import AttachmentType, SaveType
|
|
20
|
+
from ..open_msg import openMsg
|
|
21
|
+
from ..utils import createZipOpen, inputToString, makeWeakRef, prepareFilename
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Allow for nice type checking.
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from ..msg_classes.msg import MSGFile
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
logger.addHandler(logging.NullHandler())
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SignedAttachment:
|
|
33
|
+
def __init__(self, msg, data: bytes, name: str, mimetype: str, node: email.message.Message):
|
|
34
|
+
"""
|
|
35
|
+
:param msg: The MSGFile instance this attachment is associated with.
|
|
36
|
+
:param data: The bytes that compose this attachment.
|
|
37
|
+
:param name: The reported name of the attachment.
|
|
38
|
+
:param mimetype: The reported mimetype of the attachment.
|
|
39
|
+
:param node: The email Message instance for this node.
|
|
40
|
+
"""
|
|
41
|
+
self.__asBytes = data
|
|
42
|
+
self.__name = name
|
|
43
|
+
self.__mimetype = mimetype
|
|
44
|
+
self.__msg = makeWeakRef(msg)
|
|
45
|
+
self.__node = node
|
|
46
|
+
self.__treePath = msg.treePath + [makeWeakRef(self)]
|
|
47
|
+
|
|
48
|
+
self.__data = None
|
|
49
|
+
# To add support for embedded MSG files, we are going to completely
|
|
50
|
+
# ignore the mimetype and just do a few simple checks to see if we can
|
|
51
|
+
# use the bytes as am embedded file.
|
|
52
|
+
if data[:8] == b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1':
|
|
53
|
+
try:
|
|
54
|
+
# While we have to pass a lot of data down to the file, we don't
|
|
55
|
+
# pass the prefix and parent MSG data, as it is not an *actual*
|
|
56
|
+
# embedded MSG file. We are just pretending that it is for the
|
|
57
|
+
# external API.
|
|
58
|
+
self.__data = openMsg(data, treePath = self.__treePath, **msg.kwargs)
|
|
59
|
+
except Exception:
|
|
60
|
+
logger.exception('Signed message was an OLE file, but could not be read as an MSG file due to an exception.')
|
|
61
|
+
|
|
62
|
+
if self.__data is None:
|
|
63
|
+
self.__data = data
|
|
64
|
+
|
|
65
|
+
def save(self, **kwargs) -> constants.SAVE_TYPE:
|
|
66
|
+
"""
|
|
67
|
+
Saves the attachment data.
|
|
68
|
+
|
|
69
|
+
The name of the file is determined by several factors. The first
|
|
70
|
+
thing that is checked is if you have provided :param customFilename:
|
|
71
|
+
to this function. If you have, that is the name that will be used.
|
|
72
|
+
Otherwise, the name from :attr:`name` will be used. After the name to
|
|
73
|
+
use has been determined, it will then be shortened to make sure that it
|
|
74
|
+
is not more than the value of :param maxNameLength:.
|
|
75
|
+
|
|
76
|
+
To change the directory that the attachment is saved to, set the value
|
|
77
|
+
of :param customPath: when calling this function. The default save
|
|
78
|
+
directory is the working directory.
|
|
79
|
+
|
|
80
|
+
If you want to save the contents into a ``ZipFile`` or similar object,
|
|
81
|
+
either pass a path to where you want to create one or pass an instance
|
|
82
|
+
to :param zip:. If :param zip: is an instance, :param customPath: will
|
|
83
|
+
refer to a location inside the zip file.
|
|
84
|
+
"""
|
|
85
|
+
# First check if we are skipping embedded messages and stop
|
|
86
|
+
# *immediately* if we are.
|
|
87
|
+
if self.type is AttachmentType.SIGNED_EMBEDDED and kwargs.get('skipEmbedded'):
|
|
88
|
+
return (SaveType.NONE, None)
|
|
89
|
+
|
|
90
|
+
# If we are running the save function for the MSG file, just let it
|
|
91
|
+
# handle everything.
|
|
92
|
+
if (self.type is AttachmentType.SIGNED_EMBEDDED and
|
|
93
|
+
not kwargs.get('extractEmbedded', False)):
|
|
94
|
+
return self.saveEmbededMessage(**kwargs)
|
|
95
|
+
|
|
96
|
+
# Check if the user has specified a custom filename
|
|
97
|
+
filename = self.name
|
|
98
|
+
|
|
99
|
+
# Someone managed to have a null character here, so let's get rid of that
|
|
100
|
+
filename = prepareFilename(inputToString(filename, self.msg.stringEncoding))
|
|
101
|
+
|
|
102
|
+
# Get the maximum name length.
|
|
103
|
+
maxNameLength = kwargs.get('maxNameLength', 256)
|
|
104
|
+
|
|
105
|
+
# Make sure the filename is not longer than it should be.
|
|
106
|
+
if len(filename) > maxNameLength:
|
|
107
|
+
name, ext = os.path.splitext(filename)
|
|
108
|
+
filename = name[:maxNameLength - len(ext)] + ext
|
|
109
|
+
|
|
110
|
+
# Check if we are doing a zip file.
|
|
111
|
+
_zip = kwargs.get('zip')
|
|
112
|
+
|
|
113
|
+
createdZip = True
|
|
114
|
+
try:
|
|
115
|
+
# ZipFile handling.
|
|
116
|
+
if _zip:
|
|
117
|
+
# If we are doing a zip file, first check that we have been
|
|
118
|
+
# given a path.
|
|
119
|
+
if isinstance(_zip, (str, pathlib.Path)):
|
|
120
|
+
# If we have a path then we use the zip file.
|
|
121
|
+
_zip = zipfile.ZipFile(_zip, 'a', zipfile.ZIP_DEFLATED)
|
|
122
|
+
kwargs['zip'] = _zip
|
|
123
|
+
createdZip = True
|
|
124
|
+
else:
|
|
125
|
+
createdZip = False
|
|
126
|
+
# Path needs to be done in a special way if we are in a zip
|
|
127
|
+
# file.
|
|
128
|
+
customPath = pathlib.Path(kwargs.get('customPath', ''))
|
|
129
|
+
# Set the open command to be that of the zip file.
|
|
130
|
+
_open = createZipOpen(_zip.open)
|
|
131
|
+
# Zip files use w for writing in binary.
|
|
132
|
+
mode = 'w'
|
|
133
|
+
else:
|
|
134
|
+
customPath = pathlib.Path(kwargs.get('customPath', '.')).absolute()
|
|
135
|
+
mode = 'wb'
|
|
136
|
+
_open = open
|
|
137
|
+
|
|
138
|
+
fullFilename = self._handleFnc(_zip, filename, customPath, kwargs)
|
|
139
|
+
|
|
140
|
+
if self.type is AttachmentType.DATA:
|
|
141
|
+
with _open(str(fullFilename), mode) as f:
|
|
142
|
+
f.write(self.__data)
|
|
143
|
+
|
|
144
|
+
return (SaveType.FILE, str(fullFilename))
|
|
145
|
+
else:
|
|
146
|
+
with _open(str(fullFilename), mode) as f:
|
|
147
|
+
# We just use the data we were given for this one.
|
|
148
|
+
f.write(self.__asBytes)
|
|
149
|
+
return (SaveType.FILE, str(fullFilename))
|
|
150
|
+
finally:
|
|
151
|
+
# Close the ZipFile if this function created it.
|
|
152
|
+
if _zip and createdZip:
|
|
153
|
+
_zip.close()
|
|
154
|
+
|
|
155
|
+
def saveEmbededMessage(self, **kwargs) -> constants.SAVE_TYPE:
|
|
156
|
+
"""
|
|
157
|
+
Seperate function from save to allow it to easily be overridden by a
|
|
158
|
+
subclass.
|
|
159
|
+
"""
|
|
160
|
+
return self.data.save(**kwargs)
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def asBytes(self) -> bytes:
|
|
164
|
+
return self.__asBytes
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def data(self) -> Union[bytes, MSGFile]:
|
|
168
|
+
"""
|
|
169
|
+
The bytes that compose this attachment.
|
|
170
|
+
"""
|
|
171
|
+
return self.__data
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def dataType(self) -> Optional[Type[type]]:
|
|
175
|
+
"""
|
|
176
|
+
The class that the data type will use, if it can be retrieved.
|
|
177
|
+
|
|
178
|
+
This is a safe way to do type checking on data before knowing if it will
|
|
179
|
+
raise an exception. Returns ``None`` if no data will be returns or if an
|
|
180
|
+
exception will be raised.
|
|
181
|
+
"""
|
|
182
|
+
try:
|
|
183
|
+
return None if self.data is None else self.data.__class__
|
|
184
|
+
except Exception:
|
|
185
|
+
# All exceptions that accessing data would cause should be silenced.
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def emailMessage(self) -> email.message.Message:
|
|
190
|
+
"""
|
|
191
|
+
The email Message instance that is the source for this attachment.
|
|
192
|
+
"""
|
|
193
|
+
return self.__node
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def mimetype(self) -> str:
|
|
197
|
+
"""
|
|
198
|
+
The reported mimetype of the attachment.
|
|
199
|
+
"""
|
|
200
|
+
return self.__mimetype
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def msg(self) -> MSGFile:
|
|
204
|
+
"""
|
|
205
|
+
The ``MSGFile`` instance this attachment belongs to.
|
|
206
|
+
|
|
207
|
+
:raises ReferenceError: The associated ``MSGFile`` instance has been
|
|
208
|
+
garbage collected.
|
|
209
|
+
"""
|
|
210
|
+
if (msg := self.__msg()) is None:
|
|
211
|
+
raise ReferenceError('The MSGFile for this Attachment instance has been garbage collected.')
|
|
212
|
+
return msg
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def name(self) -> str:
|
|
216
|
+
"""
|
|
217
|
+
The reported name of this attachment.
|
|
218
|
+
"""
|
|
219
|
+
return self.__name
|
|
220
|
+
|
|
221
|
+
longFilename = name
|
|
222
|
+
shortFilename = name
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def treePath(self) -> List[weakref.ReferenceType]:
|
|
226
|
+
"""
|
|
227
|
+
A path, as a tuple of instances, needed to get to this instance through
|
|
228
|
+
the MSGFile-Attachment tree.
|
|
229
|
+
"""
|
|
230
|
+
return self.__treePath
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def type(self) -> AttachmentType:
|
|
234
|
+
return AttachmentType.SIGNED if isinstance(self.__data, bytes) else AttachmentType.SIGNED_EMBEDDED
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'UnsupportedAttachment',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from .. import constants
|
|
7
|
+
from .attachment_base import AttachmentBase
|
|
8
|
+
from ..enums import AttachmentType, SaveType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UnsupportedAttachment(AttachmentBase):
|
|
12
|
+
"""
|
|
13
|
+
An attachment whose type is not currently supported.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def getFilename(self, **_) -> str:
|
|
17
|
+
raise NotImplementedError('Unsupported attachments cannot be saved.')
|
|
18
|
+
|
|
19
|
+
def save(self, **kwargs) -> constants.SAVE_TYPE:
|
|
20
|
+
"""
|
|
21
|
+
Raises a NotImplementedError unless :param skipNotImplemented: is set to
|
|
22
|
+
``True``.
|
|
23
|
+
|
|
24
|
+
If it is, returns a value that indicates no data was saved.
|
|
25
|
+
"""
|
|
26
|
+
if kwargs.get('skipNotImplemented', False):
|
|
27
|
+
return (SaveType.NONE, None)
|
|
28
|
+
|
|
29
|
+
raise NotImplementedError('Unsupported attachments cannot be saved.')
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def data(self) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Broken attachments have no data.
|
|
35
|
+
"""
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def type(self) -> AttachmentType:
|
|
40
|
+
return AttachmentType.UNSUPPORTED
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'WebAttachment',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import functools
|
|
7
|
+
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from .. import constants
|
|
11
|
+
from .attachment_base import AttachmentBase
|
|
12
|
+
from ..enums import AttachmentPermissionType, AttachmentType, SaveType
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class WebAttachment(AttachmentBase):
|
|
16
|
+
"""
|
|
17
|
+
An attachment that exists on the internet and not attached to the MSG file
|
|
18
|
+
directly.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def getFilename(self) -> str:
|
|
22
|
+
raise NotImplementedError('Cannot get the filename of a web attachment.')
|
|
23
|
+
|
|
24
|
+
def save(self, **kwargs) -> constants.SAVE_TYPE:
|
|
25
|
+
"""
|
|
26
|
+
Raises a NotImplementedError unless :param skipNotImplemented: is set to
|
|
27
|
+
True.
|
|
28
|
+
|
|
29
|
+
If it is, returns a value that indicates no data was saved.
|
|
30
|
+
"""
|
|
31
|
+
if kwargs.get('skipNotImplemented', False):
|
|
32
|
+
return (SaveType.NONE, None)
|
|
33
|
+
|
|
34
|
+
raise NotImplementedError('Web attachments cannot be saved.')
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def data(self) -> None:
|
|
38
|
+
"""
|
|
39
|
+
The bytes making up the attachment data.
|
|
40
|
+
"""
|
|
41
|
+
raise NotImplementedError('Cannot get the data of a web attachment.')
|
|
42
|
+
|
|
43
|
+
@functools.cached_property
|
|
44
|
+
def originalPermissionType(self) -> Optional[AttachmentPermissionType]:
|
|
45
|
+
"""
|
|
46
|
+
The permission type data associated with a web reference attachment.
|
|
47
|
+
"""
|
|
48
|
+
return self.getNamedAs('AttachmentOriginalPermissionType', constants.ps.PSETID_ATTACHMENT, AttachmentPermissionType)
|
|
49
|
+
|
|
50
|
+
@functools.cached_property
|
|
51
|
+
def permissionType(self) -> Optional[AttachmentPermissionType]:
|
|
52
|
+
"""
|
|
53
|
+
The permission type data associated with a web reference attachment.
|
|
54
|
+
"""
|
|
55
|
+
return self.getNamedAs('AttachmentPermissionType', constants.ps.PSETID_ATTACHMENT, AttachmentPermissionType)
|
|
56
|
+
|
|
57
|
+
@functools.cached_property
|
|
58
|
+
def providerName(self) -> Optional[str]:
|
|
59
|
+
"""
|
|
60
|
+
The type of web service manipulating the attachment.
|
|
61
|
+
"""
|
|
62
|
+
return self.getNamedProp('AttachmentProviderType', constants.ps.PSETID_ATTACHMENT)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def type(self) -> AttachmentType:
|
|
66
|
+
return AttachmentType.WEB
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def url(self) -> Optional[str]:
|
|
70
|
+
"""
|
|
71
|
+
The url for the web attachment. If this is not set, that is probably an
|
|
72
|
+
error.
|
|
73
|
+
"""
|
|
74
|
+
return self.longPathname
|