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,222 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Miscellaneous ID structures used in MSG files that don't fit into any of the
|
|
3
|
+
other ID structure classifications.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'FolderID',
|
|
9
|
+
'GlobalObjectID',
|
|
10
|
+
'MessageID',
|
|
11
|
+
'ServerID',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import datetime
|
|
16
|
+
|
|
17
|
+
from .. import constants
|
|
18
|
+
from ._helpers import BytesReader
|
|
19
|
+
from ..utils import filetimeToDatetime
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FolderID:
|
|
23
|
+
"""
|
|
24
|
+
A Folder ID structure specified in [MS-OXCDATA].
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
__SIZE__: int = 16
|
|
28
|
+
|
|
29
|
+
def __init__(self, data: bytes):
|
|
30
|
+
self.__rawData = data
|
|
31
|
+
self.__replicaID = constants.st.ST_LE_UI16.unpack(data[:2])[0]
|
|
32
|
+
# This entry is 6 bytes, so we pull some shenanigans to unpack it.
|
|
33
|
+
self.__globalCounter = constants.st.ST_LE_UI64.unpack(data[2:8] + b'\x00\x00')[0]
|
|
34
|
+
|
|
35
|
+
def __bytes__(self) -> bytes:
|
|
36
|
+
return self.toBytes()
|
|
37
|
+
|
|
38
|
+
def toBytes(self) -> bytes:
|
|
39
|
+
return self.__rawData
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def globalCounter(self) -> int:
|
|
43
|
+
"""
|
|
44
|
+
An unsigned integer identifying the folder within its Store object.
|
|
45
|
+
"""
|
|
46
|
+
return self.__globalCounter
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def replicaID(self) -> int:
|
|
50
|
+
"""
|
|
51
|
+
An unsigned integer identifying a Store object.
|
|
52
|
+
"""
|
|
53
|
+
return self.__replicaID
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class GlobalObjectID:
|
|
58
|
+
"""
|
|
59
|
+
A GlobalObjectID structure, as specified in [MS-OXOCAL].
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, data: bytes):
|
|
63
|
+
self.__rawData = data
|
|
64
|
+
reader = BytesReader(data)
|
|
65
|
+
expectedBytes = b'\x04\x00\x00\x00\x82\x00\xE0\x00\x74\xC5\xB7\x10\x1A\x82\xE0\x08'
|
|
66
|
+
errorMsg = 'Byte Array ID did not match for GlobalObjectID (got {actual}).'
|
|
67
|
+
self.__byteArrayID = reader.assertRead(expectedBytes, errorMsg)
|
|
68
|
+
self.__yh = reader.read(1)
|
|
69
|
+
self.__yl = reader.read(1)
|
|
70
|
+
self.__year = constants.st.ST_BE_UI16.unpack(self.__yh + self.__yl)[0]
|
|
71
|
+
self.__month = reader.readUnsignedByte()
|
|
72
|
+
self.__day = reader.readUnsignedByte()
|
|
73
|
+
self.__creationTime = filetimeToDatetime(reader.readUnsignedLong())
|
|
74
|
+
reader.assertNull(8, 'Reserved was not set to null.')
|
|
75
|
+
size = reader.readUnsignedInt()
|
|
76
|
+
self.__data = reader.read(size)
|
|
77
|
+
|
|
78
|
+
def __bytes__(self) -> bytes:
|
|
79
|
+
return self.toBytes()
|
|
80
|
+
|
|
81
|
+
def toBytes(self) -> bytes:
|
|
82
|
+
return self.__rawData
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def byteArrayID(self) -> bytes:
|
|
86
|
+
"""
|
|
87
|
+
An array of 16 bytes identifying the bytes this BLOB as a Global Object
|
|
88
|
+
ID.
|
|
89
|
+
"""
|
|
90
|
+
return self.__byteArrayID
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def creationTime(self) -> datetime.datetime:
|
|
94
|
+
"""
|
|
95
|
+
The date and time when this Global Object ID was generated.
|
|
96
|
+
"""
|
|
97
|
+
return self.__creationTime
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def data(self) -> bytes:
|
|
101
|
+
"""
|
|
102
|
+
An array of bytes that ensures the uniqueness of the Global Object ID
|
|
103
|
+
amoung all Calendar objects in all mailboxes.
|
|
104
|
+
"""
|
|
105
|
+
return self.__data
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def day(self) -> int:
|
|
109
|
+
"""
|
|
110
|
+
The day from the PidLidExceptionReplaceTime property if the object
|
|
111
|
+
represents an exception. Otherwise, this is 0.
|
|
112
|
+
"""
|
|
113
|
+
return self.__day
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def month(self) -> int:
|
|
117
|
+
"""
|
|
118
|
+
The month from the PidLidExceptionReplaceTime property if the object
|
|
119
|
+
represents an exception. Otherwise, this is 0.
|
|
120
|
+
"""
|
|
121
|
+
return self.__month
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def year(self) -> int:
|
|
125
|
+
"""
|
|
126
|
+
The year from the PidLidExceptionReplaceTime property if the object
|
|
127
|
+
represents an exception. Otherwise, this is 0.
|
|
128
|
+
"""
|
|
129
|
+
return self.__year
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class MessageID:
|
|
134
|
+
"""
|
|
135
|
+
A Message ID structure, as defined in [MS-OXCDATA].
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
__SIZE__: int = 8
|
|
139
|
+
|
|
140
|
+
def __init__(self, data: bytes):
|
|
141
|
+
self.__rawData = data
|
|
142
|
+
self.__replicaID = constants.st.ST_LE_UI16.unpack(data[:2])[0]
|
|
143
|
+
# This entry is 6 bytes, so we pull some shenanigans to unpack it.
|
|
144
|
+
self.__globalCounter = constants.st.ST_LE_UI64.unpack(data[2:8] + b'\x00\x00')[0]
|
|
145
|
+
|
|
146
|
+
def __bytes__(self) -> bytes:
|
|
147
|
+
return self.toBytes()
|
|
148
|
+
|
|
149
|
+
def toBytes(self) -> bytes:
|
|
150
|
+
return self.__rawData
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def globalCounter(self) -> int:
|
|
154
|
+
"""
|
|
155
|
+
An unsigned integer identifying the folder within its Store object.
|
|
156
|
+
"""
|
|
157
|
+
return self.__globalCounter
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def isFolder(self) -> bool:
|
|
161
|
+
"""
|
|
162
|
+
Tells if the object pointed to is actually a folder.
|
|
163
|
+
"""
|
|
164
|
+
return self.__globalCounter == 0 and self.__replicaID == 0
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def replicaID(self) -> int:
|
|
168
|
+
"""
|
|
169
|
+
An unsigned integer identifying a Store object.
|
|
170
|
+
"""
|
|
171
|
+
return self.__replicaID
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class ServerID:
|
|
176
|
+
"""
|
|
177
|
+
Class representing a PtypServerId.
|
|
178
|
+
"""
|
|
179
|
+
def __init__(self, data: bytes):
|
|
180
|
+
"""
|
|
181
|
+
:param data: The data to use to create the ServerID.
|
|
182
|
+
|
|
183
|
+
:raises TypeError: The data is not a ServerID.
|
|
184
|
+
"""
|
|
185
|
+
# According to the docs, the first byte being a 1 means it follows this
|
|
186
|
+
# structure. A value of 0 means it does not.
|
|
187
|
+
if data[0] != 1:
|
|
188
|
+
raise TypeError('Not a standard ServerID.')
|
|
189
|
+
self.__rawData = data
|
|
190
|
+
self.__folderID = FolderID(data[1:9])
|
|
191
|
+
self.__messageID = MessageID(data[9:17])
|
|
192
|
+
self.__instance = constants.st.ST_LE_UI32.unpack(data[17:21])[0]
|
|
193
|
+
|
|
194
|
+
def __bytes__(self) -> bytes:
|
|
195
|
+
return self.toBytes()
|
|
196
|
+
|
|
197
|
+
def toBytes(self) -> bytes:
|
|
198
|
+
return self.__rawData
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def folderID(self) -> FolderID:
|
|
202
|
+
"""
|
|
203
|
+
The folder the message will be in.
|
|
204
|
+
"""
|
|
205
|
+
return self.__folderID
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def instance(self) -> int:
|
|
209
|
+
"""
|
|
210
|
+
Instance number that is only used in multivalue properties for searching
|
|
211
|
+
for a specific ServerID. Will otherwise be 0.
|
|
212
|
+
"""
|
|
213
|
+
return self.__instance
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def messageID(self) -> MessageID:
|
|
217
|
+
"""
|
|
218
|
+
A MessageID identifying a message in a folder identified by the folderID
|
|
219
|
+
instance of this object. If the object pointed to is a folder, both
|
|
220
|
+
properties will be 0.
|
|
221
|
+
"""
|
|
222
|
+
return self.__messageID
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'MonikerStream',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from typing import final, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@final
|
|
10
|
+
class MonikerStream:
|
|
11
|
+
def __init__(self, data: Optional[bytes] = None):
|
|
12
|
+
if data:
|
|
13
|
+
self.__clsid = data[:16]
|
|
14
|
+
self.__streamData = data[16:]
|
|
15
|
+
else:
|
|
16
|
+
self.__clsid = b'\x00' * 16
|
|
17
|
+
self.__streamData = b''
|
|
18
|
+
|
|
19
|
+
def __bytes__(self) -> bytes:
|
|
20
|
+
return self.toBytes()
|
|
21
|
+
|
|
22
|
+
def toBytes(self) -> bytes:
|
|
23
|
+
return self.__clsid + self.__streamData
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def clsid(self) -> bytes:
|
|
27
|
+
"""
|
|
28
|
+
The CLSID, as a stream of 16 bytes, of an implementation specific object
|
|
29
|
+
capable of processing the stream data.
|
|
30
|
+
"""
|
|
31
|
+
return self.__clsid
|
|
32
|
+
|
|
33
|
+
@clsid.setter
|
|
34
|
+
def clsid(self, data: bytes) -> None:
|
|
35
|
+
if not isinstance(data, bytes):
|
|
36
|
+
raise TypeError('CLSID MUST be bytes.')
|
|
37
|
+
if len(data) != 16:
|
|
38
|
+
raise ValueError('CLSID MUST be 16 bytes.')
|
|
39
|
+
|
|
40
|
+
self.__clsid = data
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def streamData(self) -> bytes:
|
|
44
|
+
"""
|
|
45
|
+
An array of bytes that specifies the reference to the linked object.
|
|
46
|
+
"""
|
|
47
|
+
return self.__streamData
|
|
48
|
+
|
|
49
|
+
@streamData.setter
|
|
50
|
+
def streamData(self, data: bytes) -> None:
|
|
51
|
+
if not isinstance(data, bytes):
|
|
52
|
+
raise TypeError('Stream data MUST be bytes.')
|
|
53
|
+
|
|
54
|
+
self.__streamData = data
|
|
55
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'ODTStruct',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
import struct
|
|
6
|
+
from typing import final, Optional
|
|
7
|
+
|
|
8
|
+
from ..enums import ODTCf, ODTPersist1, ODTPersist2
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@final
|
|
12
|
+
class ODTStruct:
|
|
13
|
+
def __init__(self, data: Optional[bytes] = None):
|
|
14
|
+
if data:
|
|
15
|
+
values = struct.unpack('<HH', data[:8])
|
|
16
|
+
self.__cf = ODTCf(values[0])
|
|
17
|
+
self.__persist1 = ODTPersist1(values[1])
|
|
18
|
+
if len(data) >= 6:
|
|
19
|
+
self.__persist2 = ODTPersist2(struct.unpack('<H', data[4:6])[0])
|
|
20
|
+
else:
|
|
21
|
+
self.__persist2 = ODTPersist2.NONE
|
|
22
|
+
else:
|
|
23
|
+
self.__cf = ODTCf.UNSPECIFIED
|
|
24
|
+
self.__persist1 = ODTPersist1.NONE
|
|
25
|
+
self.__persist2 = ODTPersist2.NONE
|
|
26
|
+
|
|
27
|
+
def __bytes__(self) -> bytes:
|
|
28
|
+
return self.toBytes()
|
|
29
|
+
|
|
30
|
+
def toBytes(self) -> bytes:
|
|
31
|
+
return struct.pack('<HHH', self.__cf, self.__persist1, self.__persist2)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def cf(self) -> ODTCf:
|
|
35
|
+
"""
|
|
36
|
+
An enum value that specifies the format this OLE object uses to
|
|
37
|
+
transmit data to the host application.
|
|
38
|
+
"""
|
|
39
|
+
return self.__cf
|
|
40
|
+
|
|
41
|
+
@cf.setter
|
|
42
|
+
def cf(self, value: ODTCf) -> None:
|
|
43
|
+
if not isinstance(value, ODTCf):
|
|
44
|
+
raise TypeError(':property cf: MUST be of type ODTCf.')
|
|
45
|
+
|
|
46
|
+
self.__cf = value
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def odtPersist1(self) -> ODTPersist1:
|
|
50
|
+
"""
|
|
51
|
+
Flags that specify information about the OLE object.
|
|
52
|
+
"""
|
|
53
|
+
return self.__persist1
|
|
54
|
+
|
|
55
|
+
@odtPersist1.setter
|
|
56
|
+
def odtPersist1(self, value: ODTPersist1) -> None:
|
|
57
|
+
if not isinstance(value, ODTPersist1):
|
|
58
|
+
raise TypeError(':property odtPersist1: MUST be of type ODTPersist1.')
|
|
59
|
+
|
|
60
|
+
self.__persist1 = value
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def odtPersist2(self) -> ODTPersist2:
|
|
64
|
+
"""
|
|
65
|
+
Flags that specify additional information about the OLE object.
|
|
66
|
+
"""
|
|
67
|
+
return self.__persist2
|
|
68
|
+
|
|
69
|
+
@odtPersist2.setter
|
|
70
|
+
def odtPersist2(self, value: ODTPersist2) -> None:
|
|
71
|
+
if not isinstance(value, ODTPersist2):
|
|
72
|
+
raise TypeError(':property odtPersist2: MUST be of type ODTPersist2.')
|
|
73
|
+
|
|
74
|
+
self.__persist2 = value
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
'OLEPresentationStream',
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from typing import List, Optional, Union
|
|
10
|
+
|
|
11
|
+
from ._helpers import BytesReader
|
|
12
|
+
from .cfoas import ClipboardFormatOrAnsiString
|
|
13
|
+
from ..constants import st
|
|
14
|
+
from .dv_target_device import DVTargetDevice
|
|
15
|
+
from ..enums import ADVF, ClipboardFormat, DVAspect
|
|
16
|
+
from .toc_entry import TOCEntry
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class OLEPresentationStream:
|
|
20
|
+
"""
|
|
21
|
+
[MS-OLEDS] OLEPresentationStream.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, data: bytes):
|
|
25
|
+
reader = BytesReader(data)
|
|
26
|
+
acf = self.__ansiClipboardFormat = ClipboardFormatOrAnsiString(reader)
|
|
27
|
+
|
|
28
|
+
# Validate the structure based on the documentation.
|
|
29
|
+
if acf.markerOrLength == 0:
|
|
30
|
+
raise ValueError('Invalid OLEPresentationStream (MarkerOrLength is 0).')
|
|
31
|
+
if acf.clipboardFormat is ClipboardFormat.CF_BITMAP:
|
|
32
|
+
raise ValueError('Invalid OLEPresentationStream (Format is CF_BITMAP).')
|
|
33
|
+
if 0x201 < acf.markerOrLength < 0xFFFFFFFE:
|
|
34
|
+
raise ValueError('Invalid OLEPresentationStream (ANSI length was more than 0x201).')
|
|
35
|
+
|
|
36
|
+
targetDeviceSize = reader.readUnsignedInt()
|
|
37
|
+
if targetDeviceSize < 0x4:
|
|
38
|
+
raise ValueError('Invalid OLEPresentationStream (TargetDeviceSize was less than 4).')
|
|
39
|
+
if targetDeviceSize > 0x4:
|
|
40
|
+
# Read the TargetDevice field.
|
|
41
|
+
self.__targetDevice = DVTargetDevice(reader.read(targetDeviceSize))
|
|
42
|
+
else:
|
|
43
|
+
self.__targetDevice = None
|
|
44
|
+
|
|
45
|
+
self.__aspect = reader.readUnsignedInt()
|
|
46
|
+
self.__lindex = reader.readUnsignedInt()
|
|
47
|
+
self.__advf = reader.readUnsignedInt()
|
|
48
|
+
|
|
49
|
+
# Reserved1.
|
|
50
|
+
self.__reserved1 = reader.read(4)
|
|
51
|
+
|
|
52
|
+
self.__width = reader.readUnsignedInt()
|
|
53
|
+
self.__height = reader.readUnsignedInt()
|
|
54
|
+
size = reader.readUnsignedInt()
|
|
55
|
+
self.__data = reader.read(size)
|
|
56
|
+
|
|
57
|
+
if acf.clipboardFormat is ClipboardFormat.CF_METAFILEPICT:
|
|
58
|
+
self.__reserved2 = reader.read(18)
|
|
59
|
+
else:
|
|
60
|
+
self.__reserved2 = b'\x00' * 18.
|
|
61
|
+
|
|
62
|
+
self.__tocSignature = reader.readUnsignedInt()
|
|
63
|
+
self.__tocEntries = []
|
|
64
|
+
if self.__tocSignature == 0x494E414E: # b'NANI' in little endian.
|
|
65
|
+
for _ in range(reader.readUnsignedInt()):
|
|
66
|
+
self.__tocEntries.append(TOCEntry(reader))
|
|
67
|
+
|
|
68
|
+
def __bytes__(self) -> bytes:
|
|
69
|
+
return self.toBytes()
|
|
70
|
+
|
|
71
|
+
def toBytes(self) -> bytes:
|
|
72
|
+
ret = bytes(self.__ansiClipboardFormat)
|
|
73
|
+
|
|
74
|
+
if self.__targetDevice is None:
|
|
75
|
+
ret += b'\x04\x00\x00\x00'
|
|
76
|
+
else:
|
|
77
|
+
dvData = bytes(self.__targetDevice)
|
|
78
|
+
ret += st.ST_LE_UI32.pack(len(dvData))
|
|
79
|
+
ret += dvData
|
|
80
|
+
|
|
81
|
+
ret += st.ST_LE_UI32.pack(self.__aspect)
|
|
82
|
+
ret += st.ST_LE_UI32.pack(self.__lindex)
|
|
83
|
+
ret += st.ST_LE_UI32.pack(self.__advf)
|
|
84
|
+
ret += self.__reserved1
|
|
85
|
+
ret += st.ST_LE_UI32.pack(self.__width)
|
|
86
|
+
ret += st.ST_LE_UI32.pack(self.__height)
|
|
87
|
+
ret += st.ST_LE_UI32.pack(len(self.__data)) + self.__data
|
|
88
|
+
if self.reserved2: # Shortcut since this property has protection.
|
|
89
|
+
ret += self.__reserved2
|
|
90
|
+
|
|
91
|
+
if self.__tocSignature == 0x494E414E:
|
|
92
|
+
ret += st.ST_LE_UI32.pack(len(self.__tocEntries))
|
|
93
|
+
for entry in self.__tocEntries:
|
|
94
|
+
ret += bytes(entry)
|
|
95
|
+
else:
|
|
96
|
+
ret += b'\x00\x00\x00\x00'
|
|
97
|
+
|
|
98
|
+
return ret
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def advf(self) -> Union[int, ADVF]:
|
|
102
|
+
"""
|
|
103
|
+
An implementation specific hint on how to render the presentation data
|
|
104
|
+
on screen. May be ignored on processing.
|
|
105
|
+
"""
|
|
106
|
+
return self.__advf
|
|
107
|
+
|
|
108
|
+
@advf.setter
|
|
109
|
+
def advf(self, val: Union[int, ADVF]) -> None:
|
|
110
|
+
if not isinstance(val, int):
|
|
111
|
+
raise TypeError(':property advf: must be an int.')
|
|
112
|
+
if val < 0:
|
|
113
|
+
raise ValueError(':property advf: must be positive.')
|
|
114
|
+
if val > 0xFFFFFFFF:
|
|
115
|
+
raise ValueError(':property advf: cannot be greater than 0xFFFFFFFF.')
|
|
116
|
+
|
|
117
|
+
self.__advf = val
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def ansiClipboardFormat(self) -> ClipboardFormatOrAnsiString:
|
|
121
|
+
return self.__ansiClipboardFormat
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def aspect(self) -> Union[int, DVAspect]:
|
|
125
|
+
"""
|
|
126
|
+
An implementation specific hint on how to render the presentation data
|
|
127
|
+
on screen. May be ignored on processing.
|
|
128
|
+
"""
|
|
129
|
+
return self.__aspect
|
|
130
|
+
|
|
131
|
+
@aspect.setter
|
|
132
|
+
def aspect(self, val: Union[int, DVAspect]) -> None:
|
|
133
|
+
if not isinstance(val, int):
|
|
134
|
+
raise TypeError(':property aspect: must be an int.')
|
|
135
|
+
if val < 0:
|
|
136
|
+
raise ValueError(':property aspect: must be positive.')
|
|
137
|
+
if val > 0xFFFFFFFF:
|
|
138
|
+
raise ValueError(':property aspect: cannot be greater than 0xFFFFFFFF.')
|
|
139
|
+
|
|
140
|
+
self.__aspect = val
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def data(self) -> bytes:
|
|
144
|
+
"""
|
|
145
|
+
The presentation data. The form of this data depends on :property clipboardFormat: of :property ansiClipboardFormat:.
|
|
146
|
+
"""
|
|
147
|
+
return self.__data
|
|
148
|
+
|
|
149
|
+
@data.setter
|
|
150
|
+
def data(self, val: bytes) -> None:
|
|
151
|
+
if not isinstance(val, bytes):
|
|
152
|
+
raise TypeError(':property data: must be bytes.')
|
|
153
|
+
self.__data = val
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def height(self) -> int:
|
|
157
|
+
"""
|
|
158
|
+
The height, in pixels, of the presentation data.
|
|
159
|
+
"""
|
|
160
|
+
return self.__height
|
|
161
|
+
|
|
162
|
+
@height.setter
|
|
163
|
+
def height(self, val: int) -> None:
|
|
164
|
+
if not isinstance(val, int):
|
|
165
|
+
raise TypeError(':property height: must be an int.')
|
|
166
|
+
if val < 0:
|
|
167
|
+
raise ValueError(':property height: must be positive.')
|
|
168
|
+
if val > 0xFFFFFFFF:
|
|
169
|
+
raise ValueError(':property height: cannot be greater than 0xFFFFFFFF.')
|
|
170
|
+
|
|
171
|
+
self.__height = val
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def lindex(self) -> int:
|
|
175
|
+
"""
|
|
176
|
+
An implementation specific hint on how to render the presentation data on screen. May be ignored on processing.
|
|
177
|
+
"""
|
|
178
|
+
return self.__lindex
|
|
179
|
+
|
|
180
|
+
@lindex.setter
|
|
181
|
+
def lindex(self, val: int) -> None:
|
|
182
|
+
if not isinstance(val, int):
|
|
183
|
+
raise TypeError(':property lindex: must be an int.')
|
|
184
|
+
if val < 0:
|
|
185
|
+
raise ValueError(':property lindex: must be positive.')
|
|
186
|
+
if val > 0xFFFFFFFF:
|
|
187
|
+
raise ValueError(':property lindex: cannot be greater than 0xFFFFFFFF.')
|
|
188
|
+
|
|
189
|
+
self.__lindex = val
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def reserved1(self) -> bytes:
|
|
193
|
+
"""
|
|
194
|
+
4 bytes that can contain any arbitrary data. Must be *exactly* 4 bytes
|
|
195
|
+
when setting.
|
|
196
|
+
"""
|
|
197
|
+
return self.__reserved1
|
|
198
|
+
|
|
199
|
+
@reserved1.setter
|
|
200
|
+
def reserved1(self, val: bytes) -> None:
|
|
201
|
+
if not isinstance(val, bytes):
|
|
202
|
+
raise TypeError(':property reserved1: must by bytes.')
|
|
203
|
+
if len(val) != 4:
|
|
204
|
+
raise ValueError(':property reserved1: must be exactly 4 bytes.')
|
|
205
|
+
|
|
206
|
+
self.__reserved1 = val
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def reserved2(self) -> Optional[bytes]:
|
|
210
|
+
"""
|
|
211
|
+
Optional additional data that is only set if the clipboard format of
|
|
212
|
+
:property ansiClipboardFormat: is ``CF_METAFILEPICT``.
|
|
213
|
+
|
|
214
|
+
Getting this will automatically correct the value retrieved based on
|
|
215
|
+
the clipboard format, but will *not* modify the underlying data.
|
|
216
|
+
|
|
217
|
+
Must be *exactly* 18 bytes when setting.
|
|
218
|
+
"""
|
|
219
|
+
if self.__ansiClipboardFormat.clipboardFormat is not ClipboardFormat.CF_METAFILEPICT:
|
|
220
|
+
return None
|
|
221
|
+
return self.__reserved2
|
|
222
|
+
|
|
223
|
+
@reserved2.setter
|
|
224
|
+
def reserved2(self, val: bytes) -> None:
|
|
225
|
+
if self.__ansiClipboardFormat.clipboardFormat is not ClipboardFormat.CF_METAFILEPICT:
|
|
226
|
+
raise ValueError(':property reserved2: cannot be set if the clipboard format (from :property ansiClipboardFormat:) is not CF_METAFILEPICT.')
|
|
227
|
+
if not isinstance(val, bytes):
|
|
228
|
+
raise TypeError(':property reserved2: must by bytes.')
|
|
229
|
+
if len(val) != 18:
|
|
230
|
+
raise ValueError(':property reserved2: must be exactly 18 bytes.')
|
|
231
|
+
|
|
232
|
+
self.__reserved2 = val
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def targetDevice(self) -> Optional[DVTargetDevice]:
|
|
236
|
+
return self.__targetDevice
|
|
237
|
+
|
|
238
|
+
@targetDevice.setter
|
|
239
|
+
def targetDevice(self, val: Optional[DVTargetDevice]) -> None:
|
|
240
|
+
if val is not None and not isinstance(val, DVTargetDevice):
|
|
241
|
+
raise TypeError(':property targetDevice: must be None or a DVTargetDevice.')
|
|
242
|
+
|
|
243
|
+
self.__targetDevice = val
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def tocEntries(self) -> List[TOCEntry]:
|
|
247
|
+
"""
|
|
248
|
+
A list of TOCEntry structures. If :property tocSignature: is not set to
|
|
249
|
+
``0x494E414E``, accessing this value will clear the list.
|
|
250
|
+
|
|
251
|
+
:returns: A direct reference to the list, allowing for modification.
|
|
252
|
+
This class WILL NOT change this reference over the lifetime of the
|
|
253
|
+
object.
|
|
254
|
+
"""
|
|
255
|
+
if self.__tocSignature != 0x494E414E:
|
|
256
|
+
self.__tocEntries.clear()
|
|
257
|
+
return self.__tocEntries
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def tocSignature(self) -> int:
|
|
261
|
+
"""
|
|
262
|
+
If this field does not contain ``0x494E414E``, then
|
|
263
|
+
:property tocEntries: MUST be empty. Modifications to the list will be lost when it is next retrieved, meaning changes while this property is not ``0x494E414E`` WILL be lost.
|
|
264
|
+
|
|
265
|
+
Setting this to a value other than ``0x494E414E`` will clear the list
|
|
266
|
+
immediately.
|
|
267
|
+
"""
|
|
268
|
+
return self.__tocSignature
|
|
269
|
+
|
|
270
|
+
@tocSignature.setter
|
|
271
|
+
def tocSignature(self, val: int) -> None:
|
|
272
|
+
if not isinstance(val, int):
|
|
273
|
+
raise TypeError(':property tocSignature: must be an int.')
|
|
274
|
+
if val < 0:
|
|
275
|
+
raise ValueError(':property tocSignature: must be positive.')
|
|
276
|
+
if val > 0xFFFFFFFF:
|
|
277
|
+
raise ValueError(':property tocSignature: cannot be greater than 0xFFFFFFFF.')
|
|
278
|
+
|
|
279
|
+
if val != 0x494E414E:
|
|
280
|
+
self.__tocEntries.clear()
|
|
281
|
+
|
|
282
|
+
self.__tocSignature = val
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def width(self) -> int:
|
|
286
|
+
"""
|
|
287
|
+
The width, in pixels, of the presentation data.
|
|
288
|
+
"""
|
|
289
|
+
return self.__width
|
|
290
|
+
|
|
291
|
+
@width.setter
|
|
292
|
+
def width(self, val: int) -> None:
|
|
293
|
+
if not isinstance(val, int):
|
|
294
|
+
raise TypeError(':property width: must be an int.')
|
|
295
|
+
if val < 0:
|
|
296
|
+
raise ValueError(':property width: must be positive.')
|
|
297
|
+
if val > 0xFFFFFFFF:
|
|
298
|
+
raise ValueError(':property width: cannot be greater than 0xFFFFFFFF.')
|
|
299
|
+
|
|
300
|
+
self.__width = val
|
|
301
|
+
|
|
302
|
+
|