|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2015 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a class to read flash cookies. |
|
8 """ |
|
9 |
|
10 # |
|
11 # Note: The code is based on s2x.py |
|
12 # |
|
13 |
|
14 from __future__ import unicode_literals |
|
15 |
|
16 import struct |
|
17 import io |
|
18 |
|
19 from PyQt5.QtCore import QDateTime |
|
20 |
|
21 |
|
22 class FlashCookieReaderError(Exception): |
|
23 """ |
|
24 Class containing data of a reader error. |
|
25 """ |
|
26 def __init__(self, msg): |
|
27 """ |
|
28 Constructor |
|
29 |
|
30 @param msg error message |
|
31 @type str |
|
32 """ |
|
33 self.msg = msg |
|
34 |
|
35 |
|
36 class FlashCookieReader(object): |
|
37 """ |
|
38 Class implementing a reader for flash cookies (*.sol files). |
|
39 """ |
|
40 Number = b'\x00' |
|
41 Boolean = b'\x01' |
|
42 String = b'\x02' |
|
43 ObjObj = b'\x03' |
|
44 Null = b'\x05' |
|
45 Undef = b'\x06' |
|
46 ObjArr = b'\x08' |
|
47 ObjDate = b'\x0B' |
|
48 ObjM = b'\x0D' |
|
49 ObjXml = b'\x0F' |
|
50 ObjCc = b'\x10' |
|
51 |
|
52 EpochCorrectionMsecs = 31 * 24 * 60 * 60 * 1000 |
|
53 # Flash Epoch starts at 1969-12-01 |
|
54 |
|
55 def __init__(self): |
|
56 """ |
|
57 Constructor |
|
58 """ |
|
59 self.__result = {} |
|
60 # dictionary with element name as key and tuple of |
|
61 # type and value as value |
|
62 self.__data = None |
|
63 self.__parsed = False |
|
64 |
|
65 def setBytes(self, solData): |
|
66 """ |
|
67 Public method to set the contents of a sol file to be parsed. |
|
68 |
|
69 @param solData contents of the file |
|
70 @type bytes |
|
71 """ |
|
72 self.__data = io.BytesIO(solData) |
|
73 |
|
74 def setFileName(self, solFilename): |
|
75 """ |
|
76 Public method to set the name of a sol file to be parsed. |
|
77 |
|
78 @param solFilename name of the sol file |
|
79 @type str |
|
80 """ |
|
81 self.__data = open(solFilename, "rb") |
|
82 |
|
83 def setFile(self, solFile): |
|
84 """ |
|
85 Public method to set an open sol file to be parsed. |
|
86 |
|
87 @param solFile sol file to be parsed |
|
88 @type io.FileIO |
|
89 """ |
|
90 self.__data = solFile |
|
91 |
|
92 def parse(self): |
|
93 """ |
|
94 Public method to parse the sol file. |
|
95 |
|
96 @exception FlashCookieReaderError raised when encountering a parse |
|
97 issue |
|
98 """ |
|
99 if self.__data is None: |
|
100 return |
|
101 |
|
102 self.__data.seek(0, 2) |
|
103 lenSolData = self.__data.tell() |
|
104 self.__data.seek(0) |
|
105 self.__data.read(2) |
|
106 sLenData = self.__data.read(4) |
|
107 lenData, = struct.unpack(">L", sLenData) # unsigned long, big-endian |
|
108 if lenSolData != lenData + 6: |
|
109 raise FlashCookieReaderError( |
|
110 "Flash cookie data lengths don't match\n" |
|
111 " file length: {0}\n" |
|
112 " data length: {1}" |
|
113 .format(lenSolData - 6, lenData)) |
|
114 sDataType = self.__data.read(4).decode("utf-8") # 'TCSO' |
|
115 if sDataType != "TCSO": |
|
116 raise FlashCookieReaderError( |
|
117 "Flash cookie type is not 'TCSO'; found '{0}'." |
|
118 .format(sDataType)) |
|
119 self.__data.read(6) |
|
120 lenSolName, = struct.unpack(">H", self.__data.read(2)) |
|
121 # unsigned short, big-endian |
|
122 solName = self.__data.read(lenSolName) |
|
123 solName = solName.decode("utf-8", "replace") |
|
124 self.__result["SolName"] = ("string", solName) |
|
125 self.__data.read(4) |
|
126 while self.__data.tell() < lenSolData: |
|
127 lenVariableName, = struct.unpack(">H", self.__data.read(2)) |
|
128 # unsigned short, big-endian |
|
129 variableName = self.__data.read(lenVariableName) |
|
130 variableName = variableName.decode("utf-8", "replace") |
|
131 variableType = self.__data.read(1) |
|
132 if len(variableType): |
|
133 if variableType == self.Number: |
|
134 self.__parseNumber(variableName, self.__result) |
|
135 elif variableType == self.Boolean: |
|
136 self.__parseBoolean(variableName, self.__result) |
|
137 elif variableType == self.String: |
|
138 self.__parseString(variableName, self.__result) |
|
139 elif variableType == self.ObjObj: |
|
140 self.__parseObject(variableName, self.__result) |
|
141 elif variableType == self.ObjArr: |
|
142 self.__parseArray(variableName, self.__result) |
|
143 elif variableType == self.ObjDate: |
|
144 self.__parseDate(variableName, self.__result) |
|
145 elif variableType == self.ObjXml: |
|
146 self.__parseXml(variableName, self.__result) |
|
147 elif variableType == self.ObjCc: |
|
148 self.__parseOcc(variableName, self.__result) |
|
149 elif variableType == self.ObjM: |
|
150 self.__parseOjm(variableName, self.__result) |
|
151 elif variableType == self.Null: |
|
152 self.__parseNull(variableName, self.__result) |
|
153 elif variableType == self.Undef: |
|
154 self.__parseUndefined(variableName, self.__result) |
|
155 else: |
|
156 raise FlashCookieReaderError( |
|
157 "Unexpected Data Type: " + hex(ord(variableType))) |
|
158 self.__data.read(1) # '\x00' |
|
159 self.__data.close() |
|
160 self.__parsed = True |
|
161 |
|
162 def __parseNumber(self, variableName, parent): |
|
163 """ |
|
164 Private method to parse a number. |
|
165 |
|
166 @param variableName name of the variable to be parsed |
|
167 @type str |
|
168 @param parent reference to the dictionary to insert the result into |
|
169 @type dict |
|
170 """ |
|
171 b = self.__data.read(8) |
|
172 if b == b"\x7F\xF0\x00\x00\x00\x00\x00\x00": |
|
173 value = "Infinity" |
|
174 elif b == b"\xFF\xF0\x00\x00\x00\x00\x00\x00": |
|
175 value = "-Infinity" |
|
176 elif b == b"\x7F\xF8\x00\x00\x00\x00\x00\x00": |
|
177 value = "NaN" |
|
178 else: |
|
179 value, = struct.unpack(">d", b) # double, big-endian |
|
180 value = str(value) |
|
181 parent[variableName] = ("number", value) |
|
182 |
|
183 def __parseBoolean(self, variableName, parent): |
|
184 """ |
|
185 Private method to parse a boolean. |
|
186 |
|
187 @param variableName name of the variable to be parsed |
|
188 @type str |
|
189 @param parent reference to the dictionary to insert the result into |
|
190 @type dict |
|
191 """ |
|
192 b = self.__data.read(1) |
|
193 if b == b"\x00": |
|
194 value = "False" |
|
195 elif b == b"\x01": |
|
196 value = "True" |
|
197 else: |
|
198 # boolean value error; default to True |
|
199 value = "True" |
|
200 parent[variableName] = ("boolean", value) |
|
201 |
|
202 def __parseString(self, variableName, parent): |
|
203 """ |
|
204 Private method to parse a string. |
|
205 |
|
206 @param variableName name of the variable to be parsed |
|
207 @type str |
|
208 @param parent reference to the dictionary to insert the result into |
|
209 @type dict |
|
210 """ |
|
211 lenStr, = struct.unpack(">H", self.__data.read(2)) |
|
212 # unsigned short, big-endian |
|
213 b = self.__data.read(lenStr) |
|
214 value = b.decode("utf-8", "replace") |
|
215 parent[variableName] = ("string", value) |
|
216 |
|
217 def __parseDate(self, variableName, parent): |
|
218 """ |
|
219 Private method to parse a date. |
|
220 |
|
221 @param variableName name of the variable to be parsed |
|
222 @type str |
|
223 @param parent reference to the dictionary to insert the result into |
|
224 @type dict |
|
225 """ |
|
226 msec, = struct.unpack(">d", self.__data.read(8)) |
|
227 # double, big-endian |
|
228 # DateObject: Milliseconds Count From Dec. 1, 1969 |
|
229 msec -= self.EpochCorrectionMsecs # correct for Unix epoch |
|
230 minOffset, = struct.unpack(">h", self.__data.read(2)) |
|
231 # short, big-endian |
|
232 offset = minOffset // 60 # offset in hours |
|
233 # Timezone: UTC + Offset |
|
234 value = QDateTime() |
|
235 value.setMSecsSinceEpoch(msec) |
|
236 value.setOffsetFromUtc(offset * 3600) |
|
237 parent[variableName] = ("date", |
|
238 value.toString("yyyy-MM-dd HH:mm:ss t")) |
|
239 |
|
240 def __parseXml(self, variableName, parent): |
|
241 """ |
|
242 Private method to parse XML. |
|
243 |
|
244 @param variableName name of the variable to be parsed |
|
245 @type str |
|
246 @param parent reference to the dictionary to insert the result into |
|
247 @type dict |
|
248 """ |
|
249 lenCData, = struct.unpack(">L", self.__data.read(4)) |
|
250 # unsigned long, big-endian |
|
251 cData = self.__data.read(lenCData) |
|
252 value = cData.decode("utf-8", "replace") |
|
253 parent[variableName] = ("xml", value) |
|
254 |
|
255 def __parseOjm(self, variableName, parent): |
|
256 """ |
|
257 Private method to parse an m_object. |
|
258 |
|
259 @param variableName name of the variable to be parsed |
|
260 @type str |
|
261 @param parent reference to the dictionary to insert the result into |
|
262 @type dict |
|
263 """ |
|
264 parent[variableName] = ("m_object", "") |
|
265 |
|
266 def __parseNull(self, variableName, parent): |
|
267 """ |
|
268 Private method to parse a null object. |
|
269 |
|
270 @param variableName name of the variable to be parsed |
|
271 @type str |
|
272 @param parent reference to the dictionary to insert the result into |
|
273 @type dict |
|
274 """ |
|
275 parent[variableName] = ("null", "") |
|
276 |
|
277 def __parseUndefined(self, variableName, parent): |
|
278 """ |
|
279 Private method to parse an undefined object. |
|
280 |
|
281 @param variableName name of the variable to be parsed |
|
282 @type str |
|
283 @param parent reference to the dictionary to insert the result into |
|
284 @type dict |
|
285 """ |
|
286 parent[variableName] = ("undefined", "") |
|
287 |
|
288 def __parseObject(self, variableName, parent): |
|
289 """ |
|
290 Private method to parse an object. |
|
291 |
|
292 @param variableName name of the variable to be parsed |
|
293 @type str |
|
294 @param parent reference to the dictionary to insert the result into |
|
295 @type dict |
|
296 @exception FlashCookieReaderError raised when an issue with the cookie |
|
297 file is found |
|
298 """ |
|
299 value = {} |
|
300 parent[variableName] = ("object", value) |
|
301 |
|
302 lenVariableName, = struct.unpack(">H", self.__data.read(2)) |
|
303 # unsigned short, big-endian |
|
304 while lenVariableName != 0: |
|
305 variableName = self.__data.read(lenVariableName) |
|
306 variableName = variableName.decode("utf-8", "replace") |
|
307 variableType = self.__data.read(1) |
|
308 if variableType == self.Number: |
|
309 self.__parseNumber(variableName, value) |
|
310 elif variableType == self.Boolean: |
|
311 self.__parseBoolean(variableName, value) |
|
312 elif variableType == self.String: |
|
313 self.__parseString(variableName, value) |
|
314 elif variableType == self.ObjObj: |
|
315 self.__parseObject(variableName, value) |
|
316 elif variableType == self.ObjArr: |
|
317 self.__parseArray(variableName, value) |
|
318 elif variableType == self.ObjDate: |
|
319 self.__parseDate(variableName, value) |
|
320 elif variableType == self.ObjXml: |
|
321 self.__parseXml(variableName, value) |
|
322 elif variableType == self.ObjCc: |
|
323 self.__parseOcc(variableName, value) |
|
324 elif variableType == self.ObjM: |
|
325 self.__parseOjm(variableName, value) |
|
326 elif variableType == self.Null: |
|
327 self.__parseNull(variableName, value) |
|
328 elif variableType == self.Undef: |
|
329 self.__parseUndefined(variableName, value) |
|
330 else: |
|
331 raise FlashCookieReaderError( |
|
332 "Unexpected Data Type: " + hex(ord(variableType))) |
|
333 lenVariableName, = struct.unpack(">H", self.__data.read(2)) |
|
334 self.__data.read(1) # '\x09' |
|
335 |
|
336 def __parseArray(self, variableName, parent): |
|
337 """ |
|
338 Private method to parse an array. |
|
339 |
|
340 @param variableName name of the variable to be parsed |
|
341 @type str |
|
342 @param parent reference to the dictionary to insert the result into |
|
343 @type dict |
|
344 @exception FlashCookieReaderError raised when an issue with the cookie |
|
345 file is found |
|
346 """ |
|
347 arrayLength, = struct.unpack(">L", self.__data.read(4)) |
|
348 # unsigned long, big-endian |
|
349 |
|
350 value = {} |
|
351 parent[variableName] = ("array; length={0}".format(arrayLength), value) |
|
352 |
|
353 lenVariableName, = struct.unpack(">H", self.__data.read(2)) |
|
354 # unsigned short, big-endian |
|
355 while lenVariableName != 0: |
|
356 variableName = self.__data.read(lenVariableName) |
|
357 variableName = variableName.decode("utf-8", "replace") |
|
358 variableType = self.__data.read(1) |
|
359 if variableType == self.Number: |
|
360 self.__parseNumber(variableName, value) |
|
361 elif variableType == self.Boolean: |
|
362 self.__parseBoolean(variableName, value) |
|
363 elif variableType == self.String: |
|
364 self.__parseString(variableName, value) |
|
365 elif variableType == self.ObjObj: |
|
366 self.__parseObject(variableName, value) |
|
367 elif variableType == self.ObjArr: |
|
368 self.__parseArray(variableName, value) |
|
369 elif variableType == self.ObjDate: |
|
370 self.__parseDate(variableName, value) |
|
371 elif variableType == self.ObjXml: |
|
372 self.__parseXml(variableName, value) |
|
373 elif variableType == self.ObjCc: |
|
374 self.__parseOcc(variableName, value) |
|
375 elif variableType == self.ObjM: |
|
376 self.__parseOjm(variableName, value) |
|
377 elif variableType == self.Null: |
|
378 self.__parseNull(variableName, value) |
|
379 elif variableType == self.Undef: |
|
380 self.__parseUndefined(variableName, value) |
|
381 else: |
|
382 raise FlashCookieReaderError( |
|
383 "Unexpected Data Type: " + hex(ord(variableType))) |
|
384 lenVariableName, = struct.unpack(">H", self.__data.read(2)) |
|
385 self.__data.read(1) # '\x09' |
|
386 |
|
387 def __parseOcc(self, variableName, parent): |
|
388 """ |
|
389 Private method to parse a c_object. |
|
390 |
|
391 @param variableName name of the variable to be parsed |
|
392 @type str |
|
393 @param parent reference to the dictionary to insert the result into |
|
394 @type dict |
|
395 @exception FlashCookieReaderError raised when an issue with the cookie |
|
396 file is found |
|
397 """ |
|
398 lenCname = struct.unpack(">H", self.__data.read(2)) |
|
399 # unsigned short, big-endian |
|
400 cname = self.__data.read(lenCname) |
|
401 cname = cname.decode("utf-8", "replace") |
|
402 |
|
403 value = {} |
|
404 parent[variableName] = ("c_object; cname={0}".format(cname), value) |
|
405 |
|
406 lenVariableName, = struct.unpack(">H", self.__data.read(2)) |
|
407 # unsigned short, big-endian |
|
408 while lenVariableName != 0: |
|
409 variableName = self.__data.read(lenVariableName) |
|
410 variableName = variableName.decode("utf-8", "replace") |
|
411 variableType = self.__data.read(1) |
|
412 if variableType == self.Number: |
|
413 self.__parseNumber(variableName, value) |
|
414 elif variableType == self.Boolean: |
|
415 self.__parseBoolean(variableName, value) |
|
416 elif variableType == self.String: |
|
417 self.__parseString(variableName, value) |
|
418 elif variableType == self.ObjObj: |
|
419 self.__parseObject(variableName, value) |
|
420 elif variableType == self.ObjArr: |
|
421 self.__parseArray(variableName, value) |
|
422 elif variableType == self.ObjDate: |
|
423 self.__parseDate(variableName, value) |
|
424 elif variableType == self.ObjXml: |
|
425 self.__parseXml(variableName, value) |
|
426 elif variableType == self.ObjCc: |
|
427 self.__parseOcc(variableName, value) |
|
428 elif variableType == self.ObjM: |
|
429 self.__parseOjm(variableName, value) |
|
430 elif variableType == self.Null: |
|
431 self.__parseNull(variableName, value) |
|
432 elif variableType == self.Undef: |
|
433 self.__parseUndefined(variableName, value) |
|
434 else: |
|
435 raise FlashCookieReaderError( |
|
436 "Unexpected Data Type: " + hex(ord(variableType))) |
|
437 lenVariableName, = struct.unpack(">H", self.__data.read(2)) |
|
438 self.__data.read(1) # '\x09' |
|
439 |
|
440 def toString(self, indent=0, parent=None): |
|
441 """ |
|
442 Public method to convert the parsed cookie to a string representation. |
|
443 |
|
444 @param indent indentation level |
|
445 @type int |
|
446 @param parent reference to the dictionary to be converted |
|
447 @type dict |
|
448 @return string representation of the cookie |
|
449 @rtype str |
|
450 """ |
|
451 indentStr = " " * indent |
|
452 strArr = [] |
|
453 |
|
454 if parent is None: |
|
455 parent = self.__result |
|
456 |
|
457 if not parent: |
|
458 return "" |
|
459 |
|
460 for variableName in sorted(parent.keys()): |
|
461 variableType, value = parent[variableName] |
|
462 if isinstance(value, dict): |
|
463 resultStr = self.toString(indent + 1, value) |
|
464 if resultStr: |
|
465 strArr.append("{0}{1}:\n{2}" |
|
466 .format(indentStr, variableName, resultStr)) |
|
467 else: |
|
468 strArr.append("{0}{1}:" |
|
469 .format(indentStr, variableName)) |
|
470 else: |
|
471 strArr.append("{0}{1}: {2}" |
|
472 .format(indentStr, variableName, value)) |
|
473 |
|
474 return "\n".join(strArr) |