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,697 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
'AddressBookEntryID',
|
|
6
|
+
'ContactAddressEntryID',
|
|
7
|
+
'EntryID',
|
|
8
|
+
'FolderEntryID',
|
|
9
|
+
'MessageEntryID',
|
|
10
|
+
'NNTPNewsgroupFolderEntryID',
|
|
11
|
+
'OneOffRecipient',
|
|
12
|
+
'PermanentEntryID',
|
|
13
|
+
'PersonalDistributionListEntryID',
|
|
14
|
+
'StoreObjectEntryID',
|
|
15
|
+
'WrappedEntryID',
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
import abc
|
|
20
|
+
import logging
|
|
21
|
+
|
|
22
|
+
from typing import Optional, Union
|
|
23
|
+
|
|
24
|
+
from ._helpers import BytesReader
|
|
25
|
+
from .. import constants
|
|
26
|
+
from ..enums import (
|
|
27
|
+
AddressBookType, ContactAddressIndex, DisplayType, EntryIDType,
|
|
28
|
+
MacintoshEncoding, MessageFormat, MessageType, OORBodyFormat,
|
|
29
|
+
WrappedType
|
|
30
|
+
)
|
|
31
|
+
from ..exceptions import FeatureNotImplemented
|
|
32
|
+
from ..utils import bitwiseAdjustedAnd, bytesToGuid
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
logger.addHandler(logging.NullHandler())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# First we define the main EntryID structure that is the base for the others.
|
|
40
|
+
class EntryID(abc.ABC):
|
|
41
|
+
"""
|
|
42
|
+
Base class for all EntryID structures. Use :classmethod autoCreate: to
|
|
43
|
+
automatically create the correct EntryID structure type from the specified
|
|
44
|
+
data.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def autoCreate(cls, data: Optional[bytes]) -> Optional[EntryID]:
|
|
49
|
+
"""
|
|
50
|
+
Automatically determines the type of EntryID and returns an instance of
|
|
51
|
+
the correct subclass. If the subclass cannot be determined, will return
|
|
52
|
+
a plain EntryID instance.
|
|
53
|
+
"""
|
|
54
|
+
if not data:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
if len(data) < 20:
|
|
58
|
+
raise ValueError('Cannot create an EntryID with less than 20 bytes.')
|
|
59
|
+
providerUID = data[4:20]
|
|
60
|
+
try:
|
|
61
|
+
providerUID = EntryIDType(providerUID)
|
|
62
|
+
except ValueError:
|
|
63
|
+
raise ValueError(f'Unrecognized UID "{"".join(f"{x:02X}" for x in providerUID)}". You should probably report this to the developers.') from None
|
|
64
|
+
|
|
65
|
+
# Now check the Provider UID against the known ones.
|
|
66
|
+
if providerUID == EntryIDType.ADDRESS_BOOK_RECIPIENT:
|
|
67
|
+
return AddressBookEntryID(data)
|
|
68
|
+
if providerUID == EntryIDType.CA_OR_PDL_RECIPIENT:
|
|
69
|
+
# Verify that the type signature is correct.
|
|
70
|
+
if data[24:28] not in (b'\x04\x00\x00\x00', b'\x05\x00\x00\x00'):
|
|
71
|
+
raise ValueError(f'Found Entry ID matching ContactAddress or PersonalDistributionList but the type was invalid ({data[24:28]}).')
|
|
72
|
+
if data[24] == 4:
|
|
73
|
+
return ContactAddressEntryID(data)
|
|
74
|
+
else:
|
|
75
|
+
return PersonalDistributionListEntryID(data)
|
|
76
|
+
if providerUID == EntryIDType.NNTP_NEWSGROUP_FOLDER:
|
|
77
|
+
# This is, of course, another one with a shared unique identifier.
|
|
78
|
+
# Technically it's the UID of the provider, but why do multiple
|
|
79
|
+
# structures use the same provider?
|
|
80
|
+
if data[20] == 0xC:
|
|
81
|
+
return NNTPNewsgroupFolderEntryID(data)
|
|
82
|
+
else:
|
|
83
|
+
return StoreObjectEntryID(data)
|
|
84
|
+
if providerUID == EntryIDType.ONE_OFF_RECIPIENT:
|
|
85
|
+
return OneOffRecipient(data)
|
|
86
|
+
if providerUID == EntryIDType.PUBLIC_MESSAGE_STORE:
|
|
87
|
+
if len(data) == 46:
|
|
88
|
+
return FolderEntryID(data)
|
|
89
|
+
else:
|
|
90
|
+
return MessageEntryID(data)
|
|
91
|
+
if providerUID == EntryIDType.WRAPPED:
|
|
92
|
+
return WrappedEntryID(data)
|
|
93
|
+
|
|
94
|
+
raise FeatureNotImplemented(f'UID for EntryID found in database, but no class was specified for it: {providerUID}')
|
|
95
|
+
|
|
96
|
+
def __init__(self, data: bytes):
|
|
97
|
+
self.__flags = data[:4]
|
|
98
|
+
self.__providerUID = data[4:20]
|
|
99
|
+
self.__rawData = data
|
|
100
|
+
|
|
101
|
+
def __bytes__(self) -> bytes:
|
|
102
|
+
return self.toBytes()
|
|
103
|
+
|
|
104
|
+
def toBytes(self) -> bytes:
|
|
105
|
+
return self.__rawData
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def flags(self) -> bytes:
|
|
109
|
+
"""
|
|
110
|
+
The flags for this Entry ID.
|
|
111
|
+
"""
|
|
112
|
+
return self.__flags
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def entryIDType(self) -> Union[EntryIDType, bytes]:
|
|
116
|
+
"""
|
|
117
|
+
Returns an instance of EntryIDType corresponding to the provider UID of
|
|
118
|
+
this EntryID. If none is found, returns the bytes.
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
return EntryIDType(self.__providerUID)
|
|
122
|
+
except ValueError:
|
|
123
|
+
return self.__providerUID
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def longTerm(self) -> bool:
|
|
127
|
+
"""
|
|
128
|
+
Whether the EntryID is long term or not.
|
|
129
|
+
"""
|
|
130
|
+
return self.__flags == b'\x00\x00\x00\x00'
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
@abc.abstractmethod
|
|
134
|
+
def position(self) -> int:
|
|
135
|
+
"""
|
|
136
|
+
Used to tell the amount of bytes read in this EntryID. Useful for
|
|
137
|
+
EntryID data that has been chained together with no separator.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def providerUID(self) -> bytes:
|
|
142
|
+
"""
|
|
143
|
+
The 16 byte UID that identifies the type of Entry ID.
|
|
144
|
+
"""
|
|
145
|
+
return self.__providerUID
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# Now for the specific types.
|
|
150
|
+
class AddressBookEntryID(EntryID):
|
|
151
|
+
"""
|
|
152
|
+
An Address Book EntryID structure, as specified in [MS-OXCDATA].
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(self, data: bytes):
|
|
156
|
+
super().__init__(data)
|
|
157
|
+
reader = BytesReader(data[20:])
|
|
158
|
+
# Version *MUST* be 1.
|
|
159
|
+
self.__version = reader.readUnsignedInt()
|
|
160
|
+
if self.__version != 1:
|
|
161
|
+
raise ValueError(f'Version must be 1 on address book entry IDs (got {self.__version}).')
|
|
162
|
+
|
|
163
|
+
self.__type = AddressBookType(reader.readUnsignedInt())
|
|
164
|
+
self.__X500DN = reader.readByteString()
|
|
165
|
+
self.__position = reader.tell() + 20
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def position(self) -> int:
|
|
169
|
+
return self.__position
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def type(self) -> AddressBookType:
|
|
173
|
+
"""
|
|
174
|
+
The type of the object.
|
|
175
|
+
"""
|
|
176
|
+
return self.__type
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def version(self) -> int:
|
|
180
|
+
"""
|
|
181
|
+
The version. MUST be 1.
|
|
182
|
+
"""
|
|
183
|
+
return self.__version
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def X5000DN(self) -> bytes:
|
|
187
|
+
"""
|
|
188
|
+
The X500 DN of the Address Book object.
|
|
189
|
+
"""
|
|
190
|
+
return self.__X500DN
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class ContactAddressEntryID(EntryID):
|
|
195
|
+
"""
|
|
196
|
+
A Contact Address EntryID structure, as defined in [MS-OXCDATA]. Specifies a
|
|
197
|
+
set of data representing recipients whose information is stored in a Contact
|
|
198
|
+
object.
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
def __init__(self, data: bytes):
|
|
202
|
+
super().__init__(data)
|
|
203
|
+
reader = BytesReader(data[20:])
|
|
204
|
+
if (version := reader.readUnsignedInt()) != 3:
|
|
205
|
+
raise ValueError(f'Version must be 3 (got {version}).')
|
|
206
|
+
if (type_ := reader.readUnsignedInt()) != 4:
|
|
207
|
+
raise ValueError(f'Type must be 4 (got {type_}).')
|
|
208
|
+
self.__index = ContactAddressIndex(reader.readUnsignedInt())
|
|
209
|
+
self.__entryIdCount = reader.readUnsignedInt()
|
|
210
|
+
self.__entryID = MessageEntryID(reader.read(self.__entryIdCount))
|
|
211
|
+
self.__position = reader.tell() + 20
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def entryID(self) -> MessageEntryID:
|
|
215
|
+
"""
|
|
216
|
+
The EntryID contained in this object.
|
|
217
|
+
"""
|
|
218
|
+
return self.__entryID
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def entryIDCount(self) -> int:
|
|
222
|
+
"""
|
|
223
|
+
The size, in bytes, of the EntryID contained in this object.
|
|
224
|
+
"""
|
|
225
|
+
return self.__entryIdCount
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def index(self) -> ContactAddressIndex:
|
|
229
|
+
"""
|
|
230
|
+
The electronic address in the contact information to use.
|
|
231
|
+
"""
|
|
232
|
+
return self.__index
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def position(self) -> int:
|
|
236
|
+
return self.__position
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class FolderEntryID(EntryID):
|
|
241
|
+
"""
|
|
242
|
+
A Folder EntryID structure, as defined in [MS-OXCDATA].
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
__SIZE__: int = 46
|
|
246
|
+
|
|
247
|
+
def __init__(self, data: bytes):
|
|
248
|
+
super().__init__(data)
|
|
249
|
+
reader = BytesReader(data[20:])
|
|
250
|
+
self.__folderType = MessageType(reader.readUnsignedShort())
|
|
251
|
+
self.__databaseGuid = bytesToGuid(reader.read(16))
|
|
252
|
+
# This entry is 6 bytes, so we pull some shenanigans to unpack it.
|
|
253
|
+
self.__globalCounter = constants.st.ST_LE_UI64.unpack(reader.read(6) + b'\x00\x00')[0]
|
|
254
|
+
reader.assertNull(2, 'Pad bytes were not 0.')
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def databaseGuid(self) -> str:
|
|
258
|
+
"""
|
|
259
|
+
A GUID associated with the Store Object and corresponding to the
|
|
260
|
+
ReplicaID field of the FID structure.
|
|
261
|
+
"""
|
|
262
|
+
return self.__databaseGuid
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def folderType(self) -> MessageType:
|
|
266
|
+
"""
|
|
267
|
+
The type of folder.
|
|
268
|
+
"""
|
|
269
|
+
return self.__folderType
|
|
270
|
+
|
|
271
|
+
@property
|
|
272
|
+
def globalCounter(self) -> int:
|
|
273
|
+
"""
|
|
274
|
+
An unsigned integer identifying the folder.
|
|
275
|
+
"""
|
|
276
|
+
return self.__globalCounter
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def position(self) -> int:
|
|
280
|
+
return self.__SIZE__
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class MessageEntryID(EntryID):
|
|
285
|
+
"""
|
|
286
|
+
A Message EntryID structure, as defined in [MS-OXCDATA].
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
__SIZE__: int = 70
|
|
290
|
+
|
|
291
|
+
def __init__(self, data: bytes):
|
|
292
|
+
super().__init__(data)
|
|
293
|
+
reader = BytesReader(data[20:])
|
|
294
|
+
self.__messageType = MessageType(reader.readUnsignedShort())
|
|
295
|
+
self.__folderDatabaseGuid = bytesToGuid(reader.read(16))
|
|
296
|
+
# This entry is 6 bytes, so we pull some shenanigans to unpack it.
|
|
297
|
+
self.__folderGlobalCounter = constants.st.ST_LE_UI64.unpack(reader.read(6) + b'\x00\x00')[0]
|
|
298
|
+
reader.assertNull(2, 'Pad bytes were not 0.')
|
|
299
|
+
self.__messageDatabaseGuid = bytesToGuid(reader.read(16))
|
|
300
|
+
# This entry is 6 bytes, so we pull some shenanigans to unpack it.
|
|
301
|
+
self.__messageGlobalCounter = constants.st.ST_LE_UI64.unpack(reader.read(6) + b'\x00\x00')[0]
|
|
302
|
+
reader.assertNull(2, 'Pad bytes were not 0.')
|
|
303
|
+
# Not sure why Microsoft decided to say "yes, let's do 2 6-byte integers
|
|
304
|
+
# followed by 2 pad bytes each" instead of just 2 8-byte integers with a
|
|
305
|
+
# maximum value, but here we are.
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def folderDatabaseGuid(self) -> str:
|
|
309
|
+
"""
|
|
310
|
+
A GUID associated with the Store object of the folder in which the
|
|
311
|
+
message resides and corresponding to the ReplicaId field in the folder
|
|
312
|
+
ID structure.
|
|
313
|
+
"""
|
|
314
|
+
return self.__folderDatabaseGuid
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def folderGlobalCounter(self) -> int:
|
|
318
|
+
"""
|
|
319
|
+
An unsigned integer identifying the folder in which the message resides.
|
|
320
|
+
"""
|
|
321
|
+
return self.__folderGlobalCounter
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def messageDatabaseGuid(self) -> str:
|
|
325
|
+
"""
|
|
326
|
+
A GUID associated with the Store object of the message and corresponding
|
|
327
|
+
to the ReplicaId field of the Message ID structure.
|
|
328
|
+
"""
|
|
329
|
+
return self.__messageDatabaseGuid
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def messageGlobalCounter(self) -> int:
|
|
333
|
+
"""
|
|
334
|
+
An unsigned integer identifying the message.
|
|
335
|
+
"""
|
|
336
|
+
return self.__messageGlobalCounter
|
|
337
|
+
|
|
338
|
+
@property
|
|
339
|
+
def messageType(self) -> MessageType:
|
|
340
|
+
"""
|
|
341
|
+
The Store object type.
|
|
342
|
+
"""
|
|
343
|
+
return self.__messageType
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def position(self) -> int:
|
|
347
|
+
return self.__SIZE__
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class NNTPNewsgroupFolderEntryID(EntryID):
|
|
352
|
+
"""
|
|
353
|
+
A NNTP Newsgroup Folder EntryID structure, as defined in [MS-OXCDATA].
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
def __init__(self, data: bytes):
|
|
357
|
+
super().__init__(data)
|
|
358
|
+
reader = BytesReader(data[20:])
|
|
359
|
+
self.__folderType = reader.readUnsignedShort()
|
|
360
|
+
if self.__folderType != 0x000C:
|
|
361
|
+
raise ValueError(f'Folder type was not 0x000C (got {self.__folderType})')
|
|
362
|
+
self.__newsgroupName = reader.readByteString()
|
|
363
|
+
self.__position = reader.tell() + 20
|
|
364
|
+
|
|
365
|
+
@property
|
|
366
|
+
def folderType(self) -> int:
|
|
367
|
+
"""
|
|
368
|
+
The type of folder. MUST be ``0x000C``.
|
|
369
|
+
"""
|
|
370
|
+
return self.__folderType
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def newsgroupName(self) -> bytes:
|
|
374
|
+
"""
|
|
375
|
+
The name of the newsgroup, as an ANSI string.
|
|
376
|
+
"""
|
|
377
|
+
return self.__newsgroupName
|
|
378
|
+
|
|
379
|
+
@property
|
|
380
|
+
def position(self) -> int:
|
|
381
|
+
return self.__position
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class OneOffRecipient(EntryID):
|
|
386
|
+
"""
|
|
387
|
+
A One-Off EntryID structure, as specified in [MS-OXCDATA].
|
|
388
|
+
"""
|
|
389
|
+
|
|
390
|
+
def __init__(self, data: bytes):
|
|
391
|
+
super().__init__(data)
|
|
392
|
+
# Create a reader to easily
|
|
393
|
+
reader = BytesReader(data[20:])
|
|
394
|
+
self.__version = reader.readUnsignedShort()
|
|
395
|
+
# It's not really flags, but I can't come up with a descriptive name for
|
|
396
|
+
# this collection of data, so `flagsThing` it is.
|
|
397
|
+
flagsThing = reader.readUnsignedShort()
|
|
398
|
+
|
|
399
|
+
# Just a little forewarning, I am *well* aware that these masks for each
|
|
400
|
+
# flag do not match the specification as you might expect. That is
|
|
401
|
+
# because, unlike with other parts of the documentation, these bytes
|
|
402
|
+
# are, for some reason, *not read together*. This means they are not
|
|
403
|
+
# meant to be swapped for little endian, and as such I had to flip the
|
|
404
|
+
# masks to compensate. Again, this is *despite other portions of the
|
|
405
|
+
# documentation using an identical format and being read in little
|
|
406
|
+
# endian. Took me way too long to figure out why this was not working
|
|
407
|
+
# despite following the documentation to the letter. If I had to guess,
|
|
408
|
+
# the reason this one is not flipped and the others are is because this
|
|
409
|
+
# is not grouped together.
|
|
410
|
+
|
|
411
|
+
self.__macintoshEncoding = MacintoshEncoding(bitwiseAdjustedAnd(flagsThing, 0xC))
|
|
412
|
+
self.__format = OORBodyFormat(bitwiseAdjustedAnd(flagsThing, 0x1E))
|
|
413
|
+
# Flag to indicate how messages are to be sent. 0 means TNEF, 1 means
|
|
414
|
+
# MIME.
|
|
415
|
+
self.__messageFormat = MessageFormat(bitwiseAdjustedAnd(flagsThing, 0x1))
|
|
416
|
+
# Whether the strings are UTF-16 or not.
|
|
417
|
+
self.__stringFormatUnicode = bitwiseAdjustedAnd(flagsThing, 0x8000) == 1
|
|
418
|
+
self.__canLookup = bitwiseAdjustedAnd(flagsThing, 0x1000) == 0
|
|
419
|
+
if self.__stringFormatUnicode:
|
|
420
|
+
self.__displayName = reader.readUtf16String()
|
|
421
|
+
self.__addressType = reader.readUtf16String()
|
|
422
|
+
self.__emailAddress = reader.readUtf16String()
|
|
423
|
+
else:
|
|
424
|
+
# Don't actually know how to properly handle this kind of encoding,
|
|
425
|
+
# since the documentation doesn't define exactly what encoding to
|
|
426
|
+
# use for this of even how to find out, so for now we just don't
|
|
427
|
+
# decode it at all and just leave it as bytes.
|
|
428
|
+
self.__displayName = reader.readByteString()
|
|
429
|
+
self.__addressType = reader.readByteString()
|
|
430
|
+
self.__emailAddress = reader.readByteString()
|
|
431
|
+
|
|
432
|
+
self.__position = reader.tell() + 20
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def addressType(self) -> Union[str, bytes]:
|
|
436
|
+
"""
|
|
437
|
+
The address type for this Recipient.
|
|
438
|
+
"""
|
|
439
|
+
return self.__addressType
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def areStringUnicode(self) -> bool:
|
|
443
|
+
"""
|
|
444
|
+
Whether or not the strings are in UTF-16 format.
|
|
445
|
+
"""
|
|
446
|
+
return self.__stringFormatUnicode
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def canLookup(self) -> bool:
|
|
450
|
+
"""
|
|
451
|
+
Whether the server can lookup the user's email in the address book.
|
|
452
|
+
"""
|
|
453
|
+
return self.__canLookup
|
|
454
|
+
|
|
455
|
+
@property
|
|
456
|
+
def displayName(self) -> Union[str, bytes]:
|
|
457
|
+
"""
|
|
458
|
+
The display name for this Recipient.
|
|
459
|
+
"""
|
|
460
|
+
return self.__displayName
|
|
461
|
+
|
|
462
|
+
@property
|
|
463
|
+
def emailAddress(self) -> Union[str, bytes]:
|
|
464
|
+
"""
|
|
465
|
+
The email address for this Recipient.
|
|
466
|
+
"""
|
|
467
|
+
return self.__emailAddress
|
|
468
|
+
|
|
469
|
+
@property
|
|
470
|
+
def format(self) -> OORBodyFormat:
|
|
471
|
+
"""
|
|
472
|
+
The message body format desired for this recipient.
|
|
473
|
+
"""
|
|
474
|
+
return self.__format
|
|
475
|
+
|
|
476
|
+
@property
|
|
477
|
+
def macintoshEncoding(self) -> MacintoshEncoding:
|
|
478
|
+
"""
|
|
479
|
+
The encoding used for Macintosh-specific data attachments.
|
|
480
|
+
"""
|
|
481
|
+
return self.__macintoshEncoding
|
|
482
|
+
|
|
483
|
+
@property
|
|
484
|
+
def messageFormat(self) -> MessageFormat:
|
|
485
|
+
"""
|
|
486
|
+
The message format to use for messages sent to this recipient.
|
|
487
|
+
"""
|
|
488
|
+
return self.__messageFormat
|
|
489
|
+
|
|
490
|
+
@property
|
|
491
|
+
def position(self) -> int:
|
|
492
|
+
return self.__position
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
class PermanentEntryID(EntryID):
|
|
497
|
+
"""
|
|
498
|
+
A Permanent EntryID structure, as defined in [MS-OXNSPI].
|
|
499
|
+
"""
|
|
500
|
+
|
|
501
|
+
def __init__(self, data: bytes):
|
|
502
|
+
super().__init__(data)
|
|
503
|
+
reader = BytesReader(data)
|
|
504
|
+
unpacked = reader.readStruct(constants.st.ST_PEID)
|
|
505
|
+
if unpacked[0] != 0:
|
|
506
|
+
raise TypeError(f'Not a PermanentEntryID (expected 0, got {unpacked[0]}).')
|
|
507
|
+
self.__displayTypeString = DisplayType(unpacked[2])
|
|
508
|
+
self.__distinguishedName = reader.readAsciiString()
|
|
509
|
+
self.__position = reader.tell()
|
|
510
|
+
|
|
511
|
+
@property
|
|
512
|
+
def displayTypeString(self) -> DisplayType:
|
|
513
|
+
"""
|
|
514
|
+
Returns the display type string value.
|
|
515
|
+
"""
|
|
516
|
+
return self.__displayTypeString
|
|
517
|
+
|
|
518
|
+
@property
|
|
519
|
+
def distinguishedName(self) -> str:
|
|
520
|
+
"""
|
|
521
|
+
Returns the distinguished name.
|
|
522
|
+
"""
|
|
523
|
+
return self.__distinguishedName
|
|
524
|
+
|
|
525
|
+
@property
|
|
526
|
+
def position(self) -> int:
|
|
527
|
+
return self.__position
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
class PersonalDistributionListEntryID(EntryID):
|
|
532
|
+
"""
|
|
533
|
+
A Personal Distribution List EntryID structure, as defined in [MS-OXCDATA].
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
def __init__(self, data: bytes):
|
|
537
|
+
super().__init__(data)
|
|
538
|
+
reader = BytesReader(data[20:])
|
|
539
|
+
if (arg := reader.readUnsignedInt()) != 3:
|
|
540
|
+
raise ValueError(f'Version must be 3 (got {arg}).')
|
|
541
|
+
if (arg := reader.readUnsignedInt()) != 5:
|
|
542
|
+
raise ValueError(f'Type must be 5 (got {arg}).')
|
|
543
|
+
if (arg := reader.readUnsignedInt()) != 0xFF:
|
|
544
|
+
raise ValueError(f'Index must be 255 (got {arg}).')
|
|
545
|
+
self.__entryIdCount = reader.readUnsignedInt()
|
|
546
|
+
self.__entryID = MessageEntryID(reader.read(self.__entryIdCount))
|
|
547
|
+
self.__position = reader.tell() + 20
|
|
548
|
+
|
|
549
|
+
@property
|
|
550
|
+
def entryID(self) -> MessageEntryID:
|
|
551
|
+
"""
|
|
552
|
+
The EntryID contained in this object.
|
|
553
|
+
"""
|
|
554
|
+
return self.__entryID
|
|
555
|
+
|
|
556
|
+
@property
|
|
557
|
+
def entryIDCount(self) -> int:
|
|
558
|
+
"""
|
|
559
|
+
The size, in bytes, of the EntryID contained in this object.
|
|
560
|
+
"""
|
|
561
|
+
return self.__entryIdCount
|
|
562
|
+
|
|
563
|
+
@property
|
|
564
|
+
def position(self) -> int:
|
|
565
|
+
return self.__position
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
class StoreObjectEntryID(EntryID):
|
|
570
|
+
"""
|
|
571
|
+
A Store Object EntryID structure, as defined in [MS-OXCDATA].
|
|
572
|
+
"""
|
|
573
|
+
|
|
574
|
+
def __init__(self, data: bytes):
|
|
575
|
+
super().__init__(data)
|
|
576
|
+
reader = BytesReader(data[20:])
|
|
577
|
+
|
|
578
|
+
self.__version = reader.readUnsignedByte()
|
|
579
|
+
if self.__version != 0:
|
|
580
|
+
raise ValueError(f'Version was not set to 0 (got {self.__version}).')
|
|
581
|
+
|
|
582
|
+
self.__flag = reader.readUnsignedByte()
|
|
583
|
+
if self.__flag != 0:
|
|
584
|
+
raise ValueError(f'Flag was not set to 0 (got {self.__flag}).')
|
|
585
|
+
|
|
586
|
+
self.__dllFileName = reader.read(14)
|
|
587
|
+
|
|
588
|
+
self.__wrappedFlags = reader.readUnsignedInt()
|
|
589
|
+
if self.__wrappedFlags != 0:
|
|
590
|
+
raise ValueError(f'Wrapped flags was not set to 0 (got {self.__wrappedFlags}).')
|
|
591
|
+
|
|
592
|
+
self.__wrappedProviderUID = reader.read(16)
|
|
593
|
+
self.__wrappedType = WrappedType(reader.readUnsignedInt())
|
|
594
|
+
# Don't know how this is encoded, just that it is "single-byte
|
|
595
|
+
# characters".
|
|
596
|
+
self.__serverShortname = reader.readByteString()
|
|
597
|
+
if self.__wrappedProviderUID == b'\x1B\x55\xFA\x20\xAA\x66\x11\xCD\x9B\xC8\x00\xAA\x00\x2F\xC4\x5A':
|
|
598
|
+
self.__mailboxDN = reader.readAsciiString()
|
|
599
|
+
else:
|
|
600
|
+
self.__mailboxDN = None
|
|
601
|
+
|
|
602
|
+
self.__position = reader.tell() + 20
|
|
603
|
+
|
|
604
|
+
@property
|
|
605
|
+
def dllFileName(self) -> bytes:
|
|
606
|
+
"""
|
|
607
|
+
Must be set to b'emsmdb.dll\\x00\\x00\\x00\\x00'.
|
|
608
|
+
"""
|
|
609
|
+
return self.__dllFileName
|
|
610
|
+
|
|
611
|
+
@property
|
|
612
|
+
def flag(self) -> int:
|
|
613
|
+
return self.__flag
|
|
614
|
+
|
|
615
|
+
@property
|
|
616
|
+
def mailboxDN(self) -> Optional[str]:
|
|
617
|
+
"""
|
|
618
|
+
A string representing the X500 DN of the mailbox, as specified in
|
|
619
|
+
[MS-OXOAB]. THis field is present only for mailbox databases.
|
|
620
|
+
"""
|
|
621
|
+
return self.__mailboxDN
|
|
622
|
+
|
|
623
|
+
@property
|
|
624
|
+
def position(self) -> int:
|
|
625
|
+
return self.__position
|
|
626
|
+
|
|
627
|
+
@property
|
|
628
|
+
def serverShortname(self) -> bytes:
|
|
629
|
+
"""
|
|
630
|
+
A string of single-byte characters indicating the short name or NetBIOS
|
|
631
|
+
name of the server.
|
|
632
|
+
"""
|
|
633
|
+
return self.__serverShortname
|
|
634
|
+
|
|
635
|
+
@property
|
|
636
|
+
def version(self) -> int:
|
|
637
|
+
return self.__version
|
|
638
|
+
|
|
639
|
+
@property
|
|
640
|
+
def wrappedProviderUID(self) -> bytes:
|
|
641
|
+
return self.__wrappedProviderUID
|
|
642
|
+
|
|
643
|
+
@property
|
|
644
|
+
def wrappedType(self) -> WrappedType:
|
|
645
|
+
"""
|
|
646
|
+
Determined by where the folder is located.
|
|
647
|
+
"""
|
|
648
|
+
return self.__wrappedType
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
class WrappedEntryID(EntryID):
|
|
653
|
+
"""
|
|
654
|
+
A WrappedEntryId structure, as specified in [MS-OXOCNTC].
|
|
655
|
+
"""
|
|
656
|
+
|
|
657
|
+
def __init__(self, data: bytes):
|
|
658
|
+
super().__init__(data)
|
|
659
|
+
# Grab the type byte and parse it.
|
|
660
|
+
self.__type = data[20]
|
|
661
|
+
bits = self.__type & 0xF
|
|
662
|
+
if bits == 0:
|
|
663
|
+
self.__embeddedEntryID = OneOffRecipient(data[21:])
|
|
664
|
+
elif bits == 3 or bits == 4:
|
|
665
|
+
self.__embeddedEntryID = MessageEntryID(data[21:])
|
|
666
|
+
elif bits == 5 or bits == 6:
|
|
667
|
+
self.__embeddedEntryID = AddressBookEntryID(data[21:])
|
|
668
|
+
else:
|
|
669
|
+
raise ValueError(f'Found wrapped entry id with invalid type (type bits were {bits}).')
|
|
670
|
+
|
|
671
|
+
self.__embeddedIsOneOff = self.__type & 0x80 == 0
|
|
672
|
+
self.__position = 21 + self.__embeddedEntryID.position
|
|
673
|
+
|
|
674
|
+
@property
|
|
675
|
+
def embeddedEntryID(self) -> EntryID:
|
|
676
|
+
"""
|
|
677
|
+
The embedded EntryID of this object.
|
|
678
|
+
"""
|
|
679
|
+
return self.__embeddedEntryID
|
|
680
|
+
|
|
681
|
+
@property
|
|
682
|
+
def embeddedIsOneOff(self) -> bool:
|
|
683
|
+
"""
|
|
684
|
+
Whether the embedded EntryID is a One-Off EntryID.
|
|
685
|
+
"""
|
|
686
|
+
return self.__embeddedIsOneOff
|
|
687
|
+
|
|
688
|
+
@property
|
|
689
|
+
def position(self) -> int:
|
|
690
|
+
return self.__position
|
|
691
|
+
|
|
692
|
+
@property
|
|
693
|
+
def type(self) -> int:
|
|
694
|
+
"""
|
|
695
|
+
The type bits of this object.
|
|
696
|
+
"""
|
|
697
|
+
return self.__type
|