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.
Files changed (95) hide show
  1. extract_msg/__init__.py +71 -0
  2. extract_msg/__main__.py +115 -0
  3. extract_msg/_rtf/__init__.py +21 -0
  4. extract_msg/_rtf/create_doc.py +29 -0
  5. extract_msg/_rtf/inject_rtf.py +174 -0
  6. extract_msg/_rtf/token.py +35 -0
  7. extract_msg/_rtf/tokenize_rtf.py +253 -0
  8. extract_msg/attachments/__init__.py +144 -0
  9. extract_msg/attachments/attachment.py +188 -0
  10. extract_msg/attachments/attachment_base.py +730 -0
  11. extract_msg/attachments/broken_att.py +42 -0
  12. extract_msg/attachments/custom_att.py +173 -0
  13. extract_msg/attachments/custom_att_handler/__init__.py +80 -0
  14. extract_msg/attachments/custom_att_handler/custom_handler.py +134 -0
  15. extract_msg/attachments/custom_att_handler/lnk_obj_att.py +109 -0
  16. extract_msg/attachments/custom_att_handler/outlook_image_dib.py +141 -0
  17. extract_msg/attachments/emb_msg_att.py +134 -0
  18. extract_msg/attachments/signed_att.py +234 -0
  19. extract_msg/attachments/unsupported_att.py +40 -0
  20. extract_msg/attachments/web_att.py +74 -0
  21. extract_msg/constants/__init__.py +294 -0
  22. extract_msg/constants/ps.py +46 -0
  23. extract_msg/constants/re.py +42 -0
  24. extract_msg/constants/st.py +110 -0
  25. extract_msg/data/logging-config/logging-nt.json +64 -0
  26. extract_msg/data/logging-config/logging-posix.json +64 -0
  27. extract_msg/encoding/__init__.py +277 -0
  28. extract_msg/encoding/_dt/__init__.py +4 -0
  29. extract_msg/encoding/_dt/_mac_ce.py +8 -0
  30. extract_msg/encoding/_dt/_mac_cyrillic.py +8 -0
  31. extract_msg/encoding/_dt/_mac_greek.py +8 -0
  32. extract_msg/encoding/_dt/_mac_iceland.py +8 -0
  33. extract_msg/encoding/_dt/_mac_turkish.py +8 -0
  34. extract_msg/encoding/_dt/_win874_dec.py +8 -0
  35. extract_msg/encoding/_dt/_win950_dec.py +6 -0
  36. extract_msg/encoding/utils.py +329 -0
  37. extract_msg/enums.py +2067 -0
  38. extract_msg/exceptions.py +170 -0
  39. extract_msg/msg_classes/__init__.py +49 -0
  40. extract_msg/msg_classes/appointment.py +195 -0
  41. extract_msg/msg_classes/calendar.py +92 -0
  42. extract_msg/msg_classes/calendar_base.py +496 -0
  43. extract_msg/msg_classes/contact.py +1335 -0
  44. extract_msg/msg_classes/journal.py +131 -0
  45. extract_msg/msg_classes/meeting_cancellation.py +108 -0
  46. extract_msg/msg_classes/meeting_exception.py +55 -0
  47. extract_msg/msg_classes/meeting_forward.py +115 -0
  48. extract_msg/msg_classes/meeting_related.py +65 -0
  49. extract_msg/msg_classes/meeting_request.py +189 -0
  50. extract_msg/msg_classes/meeting_response.py +74 -0
  51. extract_msg/msg_classes/message.py +12 -0
  52. extract_msg/msg_classes/message_base.py +1382 -0
  53. extract_msg/msg_classes/message_signed.py +17 -0
  54. extract_msg/msg_classes/message_signed_base.py +168 -0
  55. extract_msg/msg_classes/msg.py +1172 -0
  56. extract_msg/msg_classes/post.py +53 -0
  57. extract_msg/msg_classes/sticky_note.py +67 -0
  58. extract_msg/msg_classes/task.py +363 -0
  59. extract_msg/msg_classes/task_request.py +86 -0
  60. extract_msg/null_date.py +34 -0
  61. extract_msg/ole_writer.py +1086 -0
  62. extract_msg/open_msg.py +204 -0
  63. extract_msg/properties/__init__.py +23 -0
  64. extract_msg/properties/named.py +481 -0
  65. extract_msg/properties/prop.py +506 -0
  66. extract_msg/properties/properties_store.py +476 -0
  67. extract_msg/py.typed +0 -0
  68. extract_msg/recipient.py +362 -0
  69. extract_msg/structures/__init__.py +34 -0
  70. extract_msg/structures/_helpers.py +345 -0
  71. extract_msg/structures/business_card.py +422 -0
  72. extract_msg/structures/cfoas.py +108 -0
  73. extract_msg/structures/contact_link_entry.py +51 -0
  74. extract_msg/structures/dev_mode_a.py +621 -0
  75. extract_msg/structures/dv_target_device.py +168 -0
  76. extract_msg/structures/entry_id.py +697 -0
  77. extract_msg/structures/misc_id.py +222 -0
  78. extract_msg/structures/mon_stream.py +55 -0
  79. extract_msg/structures/odt.py +74 -0
  80. extract_msg/structures/ole_pres.py +302 -0
  81. extract_msg/structures/ole_stream_struct.py +117 -0
  82. extract_msg/structures/recurrence_pattern.py +199 -0
  83. extract_msg/structures/report_tag.py +127 -0
  84. extract_msg/structures/system_time.py +158 -0
  85. extract_msg/structures/time_zone_definition.py +111 -0
  86. extract_msg/structures/time_zone_struct.py +102 -0
  87. extract_msg/structures/toc_entry.py +137 -0
  88. extract_msg/structures/tz_rule.py +148 -0
  89. extract_msg/utils.py +1270 -0
  90. extract_msg-0.48.2.dist-info/LICENSE.txt +674 -0
  91. extract_msg-0.48.2.dist-info/METADATA +321 -0
  92. extract_msg-0.48.2.dist-info/RECORD +95 -0
  93. extract_msg-0.48.2.dist-info/WHEEL +5 -0
  94. extract_msg-0.48.2.dist-info/entry_points.txt +2 -0
  95. 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