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,481 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
'Named',
|
|
6
|
+
'NamedProperties',
|
|
7
|
+
'NamedPropertyBase',
|
|
8
|
+
'NumericalNamedProperty',
|
|
9
|
+
'StringNamedProperty',
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
import abc
|
|
14
|
+
import copy
|
|
15
|
+
import logging
|
|
16
|
+
import pprint
|
|
17
|
+
import weakref
|
|
18
|
+
|
|
19
|
+
from typing import (
|
|
20
|
+
Any, Dict, Iterable, Iterator, List, Optional, Tuple, TYPE_CHECKING,
|
|
21
|
+
TypeVar, Union
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from .. import constants
|
|
25
|
+
from ..enums import ErrorBehavior, NamedPropertyType
|
|
26
|
+
from ..exceptions import InvalidPropertyIdError, StandardViolationError
|
|
27
|
+
from ..utils import bytesToGuid, divide, msgPathToString, verifyPropertyId
|
|
28
|
+
from compressed_rtf.crc32 import crc32
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Allow for nice type checking.
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from ..msg_classes.msg import MSGFile
|
|
34
|
+
from ..attachments.attachment_base import AttachmentBase
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
logger.addHandler(logging.NullHandler())
|
|
38
|
+
|
|
39
|
+
_T = TypeVar('_T')
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Named:
|
|
43
|
+
"""
|
|
44
|
+
Class for handling access to the named properties themselves.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
__dir = '__nameid_version1.0'
|
|
48
|
+
|
|
49
|
+
def __init__(self, msg: MSGFile):
|
|
50
|
+
self.__msg = weakref.ref(msg)
|
|
51
|
+
# Get the basic streams. If all are emtpy, then nothing to do.
|
|
52
|
+
guidStream = self.getStream('__substg1.0_00020102')
|
|
53
|
+
entryStream = self.getStream('__substg1.0_00030102')
|
|
54
|
+
self.guidStream = guidStream
|
|
55
|
+
self.entryStream = entryStream
|
|
56
|
+
self.namesStream = self.getStream('__substg1.0_00040102')
|
|
57
|
+
|
|
58
|
+
self.__propertiesDict: Dict[Tuple[str, str], NamedPropertyBase] = {}
|
|
59
|
+
|
|
60
|
+
self.__streamIDDict: Dict[str, Tuple[str, str]] = {}
|
|
61
|
+
|
|
62
|
+
if guidStream is None:
|
|
63
|
+
if ErrorBehavior.STANDARDS_VIOLATION in msg.errorBehavior:
|
|
64
|
+
logger.warning('Standards Violation: Guid stream missing from named properties.')
|
|
65
|
+
guidStream = b''
|
|
66
|
+
else:
|
|
67
|
+
raise StandardViolationError('Guid stream missing from named properties.')
|
|
68
|
+
|
|
69
|
+
if entryStream is None:
|
|
70
|
+
if ErrorBehavior.STANDARDS_VIOLATION in msg.errorBehavior:
|
|
71
|
+
logger.warning('Standards Violation: Entry stream missing from named properties.')
|
|
72
|
+
entryStream = b''
|
|
73
|
+
else:
|
|
74
|
+
raise StandardViolationError('Entry stream missing from named properties.')
|
|
75
|
+
|
|
76
|
+
if self.namesStream is None:
|
|
77
|
+
if ErrorBehavior.STANDARDS_VIOLATION in msg.errorBehavior:
|
|
78
|
+
logger.error('Standards Violation: Guid stream missing from named properties. Will not parse named properties.')
|
|
79
|
+
# Return immediately since the entry stream will likely fail.
|
|
80
|
+
return
|
|
81
|
+
else:
|
|
82
|
+
raise StandardViolationError('Guid stream missing from named properties.')
|
|
83
|
+
|
|
84
|
+
# Check that we even have any entries. If there are none, nothing to do.
|
|
85
|
+
if entryStream:
|
|
86
|
+
guids = tuple([None, constants.ps.PS_MAPI, constants.ps.PS_PUBLIC_STRINGS] + [bytesToGuid(x) for x in divide(guidStream, 16)])
|
|
87
|
+
entries: List[Dict[str, Any]]= []
|
|
88
|
+
for rawStream in divide(entryStream, 8):
|
|
89
|
+
tmp = constants.st.ST_NP_ENT.unpack(rawStream)
|
|
90
|
+
entry = {
|
|
91
|
+
'id': tmp[0],
|
|
92
|
+
'pid': tmp[2],
|
|
93
|
+
'guid_index': tmp[1] >> 1,
|
|
94
|
+
'pkind': NamedPropertyType(tmp[1] & 1), # 0 if numerical, 1 if string.
|
|
95
|
+
'rawStream': rawStream,
|
|
96
|
+
}
|
|
97
|
+
entry['guid'] = guids[entry['guid_index']]
|
|
98
|
+
entries.append(entry)
|
|
99
|
+
|
|
100
|
+
properties: List[NamedPropertyBase] = []
|
|
101
|
+
|
|
102
|
+
for entry in entries:
|
|
103
|
+
if entry['pkind'] == NamedPropertyType.STRING_NAMED:
|
|
104
|
+
name = None
|
|
105
|
+
try:
|
|
106
|
+
name = self.__getName(entry['id'])
|
|
107
|
+
except ValueError as e:
|
|
108
|
+
if ErrorBehavior.NAMED_NAME_STREAM in msg.errorBehavior:
|
|
109
|
+
logger.error(f'Dropping named property because it failed to acquire name from name stream: {e}')
|
|
110
|
+
else:
|
|
111
|
+
raise
|
|
112
|
+
|
|
113
|
+
if name:
|
|
114
|
+
properties.append(StringNamedProperty(entry, name))
|
|
115
|
+
else:
|
|
116
|
+
properties.append(NumericalNamedProperty(entry))
|
|
117
|
+
|
|
118
|
+
for property in properties:
|
|
119
|
+
id_ = property.identifier
|
|
120
|
+
self.__propertiesDict[id_] = property
|
|
121
|
+
self.__streamIDDict[property.propertyStreamID] = id_
|
|
122
|
+
|
|
123
|
+
def __contains__(self, key) -> bool:
|
|
124
|
+
return key in self.__propertiesDict
|
|
125
|
+
|
|
126
|
+
def __getitem__(self, propertyName: Tuple[str, str]) -> NamedPropertyBase:
|
|
127
|
+
# Validate the key.
|
|
128
|
+
if not hasattr(propertyName, '__len__') or len(propertyName) != 2:
|
|
129
|
+
raise TypeError('Named property key must be a tuple of two strings.')
|
|
130
|
+
|
|
131
|
+
# Case insensitive search of the dictionary.
|
|
132
|
+
propertyName = (propertyName[0].upper(), propertyName[1].upper())
|
|
133
|
+
for key in self.__propertiesDict.keys():
|
|
134
|
+
if propertyName == (key[0].upper(), key[1].upper()):
|
|
135
|
+
return self.__propertiesDict[key]
|
|
136
|
+
|
|
137
|
+
raise KeyError(propertyName)
|
|
138
|
+
|
|
139
|
+
def __iter__(self) -> Iterator[Tuple[str, str]]:
|
|
140
|
+
return self.__propertiesDict.__iter__()
|
|
141
|
+
|
|
142
|
+
def __len__(self) -> int:
|
|
143
|
+
return self.__propertiesDict.__len__()
|
|
144
|
+
|
|
145
|
+
def __getName(self, offset: int) -> str:
|
|
146
|
+
"""
|
|
147
|
+
Parses the offset into the named stream and returns the name found.
|
|
148
|
+
"""
|
|
149
|
+
# We used to parse names by handing it as an array, as specified by the
|
|
150
|
+
# documentation, but this new method allows for a little bit more wiggle
|
|
151
|
+
# room in terms of what is accepted by the module.
|
|
152
|
+
if offset & 3 != 0:
|
|
153
|
+
# If the offset is not a multiple of 4, that is an error, but we are
|
|
154
|
+
# reducing it to a warning.
|
|
155
|
+
logger.warning(f'Malformed named properties detected due to bad offset ({offset}). Ignoring.')
|
|
156
|
+
# Check that offset is in string stream.
|
|
157
|
+
if offset > len(self.namesStream):
|
|
158
|
+
raise ValueError('Failed to parse named property: offset was not in string stream.')
|
|
159
|
+
|
|
160
|
+
# Get the length, in bytes, of the string.
|
|
161
|
+
length = constants.st.ST_LE_I32.unpack(self.namesStream[offset:offset + 4])[0]
|
|
162
|
+
offset += 4
|
|
163
|
+
|
|
164
|
+
# Make sure the string can be read entirely. If it can't, something was
|
|
165
|
+
# corrupt.
|
|
166
|
+
if offset + length > len(self.namesStream):
|
|
167
|
+
raise ValueError(f'Failed to parse named property: length ({length}) of string overflows the string stream. This is probably due to a bad offset.')
|
|
168
|
+
|
|
169
|
+
return self.namesStream[offset:offset + length].decode('utf-16-le')
|
|
170
|
+
|
|
171
|
+
def exists(self, filename: constants.MSG_PATH) -> bool:
|
|
172
|
+
"""
|
|
173
|
+
Checks if stream exists inside the named properties folder.
|
|
174
|
+
|
|
175
|
+
:raises ReferenceError: The associated ``MSGFile`` instance has been
|
|
176
|
+
garbage collected.
|
|
177
|
+
"""
|
|
178
|
+
if (msg := self.__msg()) is None:
|
|
179
|
+
raise ReferenceError('The MSGFile for this Named instance has been garbage collected.')
|
|
180
|
+
return msg.exists([self.__dir, msgPathToString(filename)], False)
|
|
181
|
+
|
|
182
|
+
def get(self, propertyName: Tuple[str, str], default: _T = None) -> Union[NamedPropertyBase, _T]:
|
|
183
|
+
"""
|
|
184
|
+
Tries to get a named property based on its key.
|
|
185
|
+
|
|
186
|
+
Returns :param default: if not found. Key is a tuple of the name and
|
|
187
|
+
the property set GUID.
|
|
188
|
+
"""
|
|
189
|
+
try:
|
|
190
|
+
return self[propertyName]
|
|
191
|
+
except KeyError:
|
|
192
|
+
return default
|
|
193
|
+
|
|
194
|
+
def getPropNameByStreamID(self, streamID: Union[int, str]) -> Optional[Tuple[str, str]]:
|
|
195
|
+
"""
|
|
196
|
+
Gets the name of a property (as a key for the internal dict) that is stored in the specified stream.
|
|
197
|
+
|
|
198
|
+
Useful for determining if a stream/property stream entry is a named
|
|
199
|
+
property.
|
|
200
|
+
|
|
201
|
+
:param streamID: A 4 hex character identifier that will be checked. May
|
|
202
|
+
also be an integer that can convert to 4 hex characters.
|
|
203
|
+
|
|
204
|
+
:returns: The name, if the stream is a named property, otherwise
|
|
205
|
+
``None``.
|
|
206
|
+
|
|
207
|
+
:raises InvalidPropertyIdError: The Stream ID is invalid.
|
|
208
|
+
:raises TypeError: The Stream ID is not a valid type.
|
|
209
|
+
"""
|
|
210
|
+
if isinstance(streamID, int):
|
|
211
|
+
if streamID < 0x8000:
|
|
212
|
+
# Definitely doesn't refer to a name property, so just return
|
|
213
|
+
# None.
|
|
214
|
+
return None
|
|
215
|
+
if streamID > 0xFFFF:
|
|
216
|
+
raise InvalidPropertyIdError('Stream ID is out of range.')
|
|
217
|
+
streamID = f'{streamID:04X}'
|
|
218
|
+
elif isinstance(streamID, str):
|
|
219
|
+
verifyPropertyId(streamID)
|
|
220
|
+
else:
|
|
221
|
+
raise TypeError(':param streamID: MUST be an int or str.')
|
|
222
|
+
|
|
223
|
+
return self.__streamIDDict.get(streamID)
|
|
224
|
+
|
|
225
|
+
def getStream(self, filename: constants.MSG_PATH) -> Optional[bytes]:
|
|
226
|
+
"""
|
|
227
|
+
Gets a binary representation of the requested stream.
|
|
228
|
+
|
|
229
|
+
This should ALWAYS return a ``bytes`` object if it was found, otherwise
|
|
230
|
+
returns ``None``.
|
|
231
|
+
|
|
232
|
+
:raises ReferenceError: The associated ``MSGFile`` instance has been
|
|
233
|
+
garbage collected.
|
|
234
|
+
"""
|
|
235
|
+
if (msg := self.__msg()) is None:
|
|
236
|
+
raise ReferenceError('The MSGFile for this Named instance has been garbage collected.')
|
|
237
|
+
return msg.getStream([self.__dir, msgPathToString(filename)], False)
|
|
238
|
+
|
|
239
|
+
def items(self) -> Iterable[Tuple[Tuple[str, str], NamedPropertyBase]]:
|
|
240
|
+
return self.__propertiesDict.items()
|
|
241
|
+
|
|
242
|
+
def keys(self) -> Iterable[Tuple[str, str]]:
|
|
243
|
+
return self.__propertiesDict.keys()
|
|
244
|
+
|
|
245
|
+
def pprintKeys(self) -> None:
|
|
246
|
+
"""
|
|
247
|
+
Uses the pprint function on a sorted list of keys.
|
|
248
|
+
"""
|
|
249
|
+
pprint.pprint(sorted(self.__propertiesDict.keys()))
|
|
250
|
+
|
|
251
|
+
def values(self) -> Iterable[NamedPropertyBase]:
|
|
252
|
+
return self.__propertiesDict.values()
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def dir(self):
|
|
256
|
+
"""
|
|
257
|
+
Returns the directory inside the MSG file where the named properties are located.
|
|
258
|
+
"""
|
|
259
|
+
return self.__dir
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def msg(self) -> MSGFile:
|
|
263
|
+
"""
|
|
264
|
+
Returns the Message instance the attachment belongs to.
|
|
265
|
+
|
|
266
|
+
:raises ReferenceError: The associated ``MSGFile`` instance has been
|
|
267
|
+
garbage collected.
|
|
268
|
+
"""
|
|
269
|
+
if (msg := self.__msg()) is None:
|
|
270
|
+
raise ReferenceError('The MSGFile for this Named instance has been garbage collected.')
|
|
271
|
+
return msg
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def namedProperties(self) -> Dict[Tuple[str, str], NamedPropertyBase]:
|
|
275
|
+
"""
|
|
276
|
+
Returns a copy of the dictionary containing all the named properties.
|
|
277
|
+
"""
|
|
278
|
+
return copy.deepcopy(self.__propertiesDict)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class NamedProperties:
|
|
283
|
+
"""
|
|
284
|
+
An instance that uses a Named instance and an extract-msg class to read the
|
|
285
|
+
data of named properties.
|
|
286
|
+
"""
|
|
287
|
+
def __init__(self, named: Named, streamSource: Union[MSGFile, AttachmentBase]):
|
|
288
|
+
"""
|
|
289
|
+
:param named: The Named instance to refer to for named properties
|
|
290
|
+
entries.
|
|
291
|
+
:param streamSource: The source to use for acquiring the data of a named
|
|
292
|
+
property.
|
|
293
|
+
"""
|
|
294
|
+
self.__named = named
|
|
295
|
+
self.__streamSource = weakref.ref(streamSource)
|
|
296
|
+
|
|
297
|
+
def __getitem__(self, item: Union[Tuple[str, str], NamedPropertyBase]):
|
|
298
|
+
"""
|
|
299
|
+
Get a named property using the [] operator. Item must be a named
|
|
300
|
+
property instance or a tuple with 2 items: the name and the GUID string.
|
|
301
|
+
|
|
302
|
+
:raises ReferenceError: The associated instance for getting actual
|
|
303
|
+
property data has been garbage collected.
|
|
304
|
+
"""
|
|
305
|
+
if (source := self.__streamSource()) is None:
|
|
306
|
+
raise ReferenceError('The stream source for the NamedProperties instance has been garbage collected.')
|
|
307
|
+
if isinstance(item, NamedPropertyBase):
|
|
308
|
+
return source._getTypedData(item.propertyStreamID)
|
|
309
|
+
else:
|
|
310
|
+
return source._getTypedData(self.__named[item].propertyStreamID)
|
|
311
|
+
|
|
312
|
+
def get(self, item: Union[Tuple[str, str], NamedPropertyBase], default: _T = None) -> Union[Any, _T]:
|
|
313
|
+
"""
|
|
314
|
+
Get a named property, returning the value of :param default: if not
|
|
315
|
+
found. Item must be a tuple with 2 items: the name and the GUID string.
|
|
316
|
+
|
|
317
|
+
:raises ReferenceError: The associated instance for getting actual
|
|
318
|
+
property data has been garbage collected.
|
|
319
|
+
"""
|
|
320
|
+
try:
|
|
321
|
+
return self[item]
|
|
322
|
+
except KeyError:
|
|
323
|
+
return default
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class NamedPropertyBase(abc.ABC):
|
|
328
|
+
def __init__(self, entry: Dict[str, Any]):
|
|
329
|
+
self.__entry = entry
|
|
330
|
+
self.__guidIndex = entry['guid_index']
|
|
331
|
+
self.__namedPropertyID = entry['pid']
|
|
332
|
+
self.__guid = entry['guid']
|
|
333
|
+
self.__propertyStreamID = f'{0x8000 + self.__namedPropertyID:04X}'
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def guid(self) -> str:
|
|
337
|
+
"""
|
|
338
|
+
The guid of the property's property set.
|
|
339
|
+
"""
|
|
340
|
+
return self.__guid
|
|
341
|
+
|
|
342
|
+
@property
|
|
343
|
+
def guidIndex(self) -> int:
|
|
344
|
+
"""
|
|
345
|
+
The guid index of the property's property set.
|
|
346
|
+
"""
|
|
347
|
+
return self.__guidIndex
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
@abc.abstractmethod
|
|
351
|
+
def identifier(self) -> Tuple[str, str]:
|
|
352
|
+
"""
|
|
353
|
+
An identifier that can be used to full identify the property.
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
def namedPropertyID(self) -> int:
|
|
358
|
+
"""
|
|
359
|
+
The named property id.
|
|
360
|
+
"""
|
|
361
|
+
return self.__namedPropertyID
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def propertyStreamID(self) -> str:
|
|
365
|
+
"""
|
|
366
|
+
An ID usable for grabbing the value stream.
|
|
367
|
+
"""
|
|
368
|
+
return self.__propertyStreamID
|
|
369
|
+
|
|
370
|
+
@property
|
|
371
|
+
def rawEntry(self) -> Dict[str, Any]:
|
|
372
|
+
return copy.deepcopy(self.__entry)
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def rawEntryStream(self) -> bytes:
|
|
376
|
+
"""
|
|
377
|
+
The raw data used for the entry.
|
|
378
|
+
"""
|
|
379
|
+
return self.__entry['rawStream']
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
@abc.abstractmethod
|
|
383
|
+
def type(self) -> NamedPropertyType:
|
|
384
|
+
"""
|
|
385
|
+
Returns the type of the named property. This will be a member of the
|
|
386
|
+
NamedPropertyType enum.
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
class StringNamedProperty(NamedPropertyBase):
|
|
392
|
+
def __init__(self, entry: Dict, name: str):
|
|
393
|
+
super().__init__(entry)
|
|
394
|
+
self.__name = name
|
|
395
|
+
|
|
396
|
+
# Finally got this to be correct after asking about it on a Microsoft
|
|
397
|
+
# forum. Apparently it uses the same CRC-32 as the Compressed RTF
|
|
398
|
+
# standard does, so we can just use the function defined in the
|
|
399
|
+
# compressed-rtf Python module.
|
|
400
|
+
#
|
|
401
|
+
# First thing to note is that the name should only ever be lowered if it
|
|
402
|
+
# is part of the PS_INTERNET_HEADERS property set **AND** it is
|
|
403
|
+
# generated by certain versions of Outlook. As such, a little bit of
|
|
404
|
+
# additional code will need to run to determine exactly what the stream
|
|
405
|
+
# ID should be if it is in that property set.
|
|
406
|
+
if self.guid == constants.ps.PS_INTERNET_HEADERS:
|
|
407
|
+
# To be sure if it needs to be lower the most effective method would
|
|
408
|
+
# be to just get the Stream ID and then check if the entry is in
|
|
409
|
+
# there. If it isn't, then check the regular case and see. If it is
|
|
410
|
+
# not in either... well, we don't use it for anything so it will
|
|
411
|
+
# just be a warning, and the Stream ID will be set to 0.
|
|
412
|
+
#
|
|
413
|
+
# TODO: Unfortunately, doing this will need to be put off until a
|
|
414
|
+
# different version, preferably after Python 2 support is removed,
|
|
415
|
+
# as this will require restructuring a lot of internal code. For now
|
|
416
|
+
# we just assume that it is lowercase.
|
|
417
|
+
self.__streamID = 0x1000 + (crc32(name.lower().encode('utf-16-le')) ^ (self.guidIndex << 1 | 1)) % 0x1F
|
|
418
|
+
|
|
419
|
+
else:
|
|
420
|
+
# No special logic here to determine what to do.
|
|
421
|
+
self.__streamID = 0x1000 + (crc32(name.encode('utf-16-le')) ^ (self.guidIndex << 1 | 1)) % 0x1F
|
|
422
|
+
|
|
423
|
+
@property
|
|
424
|
+
def identifier(self) -> Tuple[str, str]:
|
|
425
|
+
return (self.name, self.guid)
|
|
426
|
+
|
|
427
|
+
@property
|
|
428
|
+
def name(self) -> str:
|
|
429
|
+
"""
|
|
430
|
+
The name of the property.
|
|
431
|
+
"""
|
|
432
|
+
return self.__name
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def streamID(self) -> int:
|
|
436
|
+
"""
|
|
437
|
+
Returns the streamID of the named property. This may not be accurate.
|
|
438
|
+
"""
|
|
439
|
+
return self.__streamID
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def type(self) -> NamedPropertyType:
|
|
443
|
+
"""
|
|
444
|
+
Returns the type of the named property. This will be a member of the
|
|
445
|
+
NamedPropertyType enum.
|
|
446
|
+
"""
|
|
447
|
+
return NamedPropertyType.STRING_NAMED
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class NumericalNamedProperty(NamedPropertyBase):
|
|
452
|
+
def __init__(self, entry: Dict):
|
|
453
|
+
super().__init__(entry)
|
|
454
|
+
self.__propertyID = f'{entry["id"]:04X}'
|
|
455
|
+
self.__streamID = 0x1000 + (entry['id'] ^ (self.guidIndex << 1)) % 0x1F
|
|
456
|
+
|
|
457
|
+
@property
|
|
458
|
+
def identifier(self) -> Tuple[str, str]:
|
|
459
|
+
return (self.propertyID, self.guid)
|
|
460
|
+
|
|
461
|
+
@property
|
|
462
|
+
def propertyID(self) -> str:
|
|
463
|
+
"""
|
|
464
|
+
The actualy property id of the named property.
|
|
465
|
+
"""
|
|
466
|
+
return self.__propertyID
|
|
467
|
+
|
|
468
|
+
@property
|
|
469
|
+
def streamID(self) -> int:
|
|
470
|
+
"""
|
|
471
|
+
Returns the streamID of the named property. This may not be accurate.
|
|
472
|
+
"""
|
|
473
|
+
return self.__streamID
|
|
474
|
+
|
|
475
|
+
@property
|
|
476
|
+
def type(self) -> NamedPropertyType:
|
|
477
|
+
"""
|
|
478
|
+
Returns the type of the named property. This will be a member of the
|
|
479
|
+
NamedPropertyType enum.
|
|
480
|
+
"""
|
|
481
|
+
return NamedPropertyType.NUMERICAL_NAMED
|