From 1831816801abb8851ef57596d33d3931d15ab1bf Mon Sep 17 00:00:00 2001 From: Philippe Lagadec Date: Wed, 13 Dec 2023 09:35:56 +0100 Subject: [PATCH] added _clsid_from_str, OleDirectoryEntry._to_bytes, tests for _clsid functions --- olefile/olefile.py | 46 ++++++++++++++++++++++++++++++++++++++++--- tests/test_olefile.py | 24 ++++++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/olefile/olefile.py b/olefile/olefile.py index 60f8963..2ce3cd3 100644 --- a/olefile/olefile.py +++ b/olefile/olefile.py @@ -104,6 +104,7 @@ import io import sys import struct, array, os.path, datetime, logging, warnings, traceback +import uuid #=== COMPATIBILITY WORKAROUNDS ================================================ @@ -364,8 +365,9 @@ def _clsid(clsid): """ Converts a CLSID to a human-readable string. - :param clsid: string of length 16. + :param clsid: bytes string of length 16. """ + # TODO: replace by str(uuid.UUID(bytes_le=clsid)).upper() assert len(clsid) == 16 # if clsid is only made of null bytes, return an empty string: # (PL: why not simply return the string with zeroes?) @@ -375,6 +377,15 @@ def _clsid(clsid): ((i32(clsid, 0), i16(clsid, 4), i16(clsid, 6)) + tuple(map(i8, clsid[8:16])))) +def _clsid_from_str(s): + """ + Converts a CLSID from the human-readable string format to bytes + + :param s: string representing the CLSID, e.g. '00020906-0000-0000-C000-000000000046'. + :return: bytes string of length 16 + """ + # Here we use bytes_le (little endian) to match Microsoft's format + return uuid.UUID(s).bytes_le def filetime2datetime(filetime): @@ -787,7 +798,7 @@ def __init__(self, entry, sid, ole_file): self.sid_left, self.sid_right, self.sid_child, - clsid, + self.clsid_raw, self.dwUserFlags, self.createTime, self.modifyTime, @@ -836,7 +847,7 @@ def __init__(self, entry, sid, ole_file): self.size = self.sizeLow + (long(self.sizeHigh)<<32) log.debug(' - size: %d (sizeLow=%d, sizeHigh=%d)' % (self.size, self.sizeLow, self.sizeHigh)) - self.clsid = _clsid(clsid) + self.clsid = _clsid(self.clsid_raw) # a storage should have a null size, BUT some implementations such as # Word 8 for Mac seem to allow non-null values => Potential defect: if self.entry_type == STGTY_STORAGE and self.size != 0: @@ -1013,6 +1024,35 @@ def getctime(self): return None return filetime2datetime(self.createTime) + def _to_bytes(self): + """ + Convert this directory entry to its bytes form, ready to be written to disk + + :returns: bytes string of size 128 + + new in version 0.50 + """ + # TODO: convert name back to UTF-16, adjust namelength + # convert clsid back from text to bytes: + self.clsid_raw = _clsid_from_str(self.clsid) + return struct.pack(OleDirectoryEntry.STRUCT_DIRENTRY, + self.name_raw, # 64s: string containing entry name in unicode UTF-16 (max 31 chars) + null char = 64 bytes + self.namelength, # H: uint16, number of bytes used in name buffer, including null = (len+1)*2 + self.entry_type, + self.color, + self.sid_left, + self.sid_right, + self.sid_child, + self.clsid_raw, + self.dwUserFlags, + self.createTime, + self.modifyTime, + self.isectStart, + self.sizeLow, + self.sizeHigh + ) + + #--- OleFileIO ---------------------------------------------------------------- diff --git a/tests/test_olefile.py b/tests/test_olefile.py index 7e6b12f..33ce8f6 100644 --- a/tests/test_olefile.py +++ b/tests/test_olefile.py @@ -22,6 +22,30 @@ import olefile +class Test_clsid(unittest.TestCase): + 'Tests for the CLSID conversion functions' + + def setUp(self): + # bytes and text representations of a random CLSID + self.clsid_bytes = b'\x0c\xc8\xf9?4\x1d\x9e^\x8b\x1fv\x89\x99\xa6\xf2\xed' + self.clsid_text = '3FF9C80C-1D34-5E9E-8B1F-768999A6F2ED' + + def test_clsid_bytes2text(self): + 'Test CLSID conversion from bytes to text using _clsid()' + self.assertEqual( + olefile.olefile._clsid(self.clsid_bytes), + self.clsid_text + ) + + + def test_clsid_text2bytes(self): + 'Test CLSID conversion from text to bytes using _clsid_tobytes()' + self.assertEqual( + olefile.olefile._clsid_from_str(self.clsid_text), + self.clsid_bytes + ) + + class Test_isOleFile(unittest.TestCase): 'Tests for the isOleFile function'