""" Miscellaneous ID structures used in MSG files that don't fit into any of the other ID structure classifications. """ __all__ = [ 'FolderID', 'GlobalObjectID', 'MessageID', 'ServerID', ] import datetime from .. import constants from ._helpers import BytesReader from ..utils import filetimeToDatetime class FolderID: """ A Folder ID structure specified in [MS-OXCDATA]. """ __SIZE__: int = 16 def __init__(self, data: bytes): self.__rawData = data self.__replicaID = constants.st.ST_LE_UI16.unpack(data[:2])[0] # This entry is 6 bytes, so we pull some shenanigans to unpack it. self.__globalCounter = constants.st.ST_LE_UI64.unpack(data[2:8] + b'\x00\x00')[0] def __bytes__(self) -> bytes: return self.toBytes() def toBytes(self) -> bytes: return self.__rawData @property def globalCounter(self) -> int: """ An unsigned integer identifying the folder within its Store object. """ return self.__globalCounter @property def replicaID(self) -> int: """ An unsigned integer identifying a Store object. """ return self.__replicaID class GlobalObjectID: """ A GlobalObjectID structure, as specified in [MS-OXOCAL]. """ def __init__(self, data: bytes): self.__rawData = data reader = BytesReader(data) expectedBytes = b'\x04\x00\x00\x00\x82\x00\xE0\x00\x74\xC5\xB7\x10\x1A\x82\xE0\x08' errorMsg = 'Byte Array ID did not match for GlobalObjectID (got {actual}).' self.__byteArrayID = reader.assertRead(expectedBytes, errorMsg) self.__yh = reader.read(1) self.__yl = reader.read(1) self.__year = constants.st.ST_BE_UI16.unpack(self.__yh + self.__yl)[0] self.__month = reader.readUnsignedByte() self.__day = reader.readUnsignedByte() self.__creationTime = filetimeToDatetime(reader.readUnsignedLong()) reader.assertNull(8, 'Reserved was not set to null.') size = reader.readUnsignedInt() self.__data = reader.read(size) def __bytes__(self) -> bytes: return self.toBytes() def toBytes(self) -> bytes: return self.__rawData @property def byteArrayID(self) -> bytes: """ An array of 16 bytes identifying the bytes this BLOB as a Global Object ID. """ return self.__byteArrayID @property def creationTime(self) -> datetime.datetime: """ The date and time when this Global Object ID was generated. """ return self.__creationTime @property def data(self) -> bytes: """ An array of bytes that ensures the uniqueness of the Global Object ID amoung all Calendar objects in all mailboxes. """ return self.__data @property def day(self) -> int: """ The day from the PidLidExceptionReplaceTime property if the object represents an exception. Otherwise, this is 0. """ return self.__day @property def month(self) -> int: """ The month from the PidLidExceptionReplaceTime property if the object represents an exception. Otherwise, this is 0. """ return self.__month @property def year(self) -> int: """ The year from the PidLidExceptionReplaceTime property if the object represents an exception. Otherwise, this is 0. """ return self.__year class MessageID: """ A Message ID structure, as defined in [MS-OXCDATA]. """ __SIZE__: int = 8 def __init__(self, data: bytes): self.__rawData = data self.__replicaID = constants.st.ST_LE_UI16.unpack(data[:2])[0] # This entry is 6 bytes, so we pull some shenanigans to unpack it. self.__globalCounter = constants.st.ST_LE_UI64.unpack(data[2:8] + b'\x00\x00')[0] def __bytes__(self) -> bytes: return self.toBytes() def toBytes(self) -> bytes: return self.__rawData @property def globalCounter(self) -> int: """ An unsigned integer identifying the folder within its Store object. """ return self.__globalCounter @property def isFolder(self) -> bool: """ Tells if the object pointed to is actually a folder. """ return self.__globalCounter == 0 and self.__replicaID == 0 @property def replicaID(self) -> int: """ An unsigned integer identifying a Store object. """ return self.__replicaID class ServerID: """ Class representing a PtypServerId. """ def __init__(self, data: bytes): """ :param data: The data to use to create the ServerID. :raises TypeError: The data is not a ServerID. """ # According to the docs, the first byte being a 1 means it follows this # structure. A value of 0 means it does not. if data[0] != 1: raise TypeError('Not a standard ServerID.') self.__rawData = data self.__folderID = FolderID(data[1:9]) self.__messageID = MessageID(data[9:17]) self.__instance = constants.st.ST_LE_UI32.unpack(data[17:21])[0] def __bytes__(self) -> bytes: return self.toBytes() def toBytes(self) -> bytes: return self.__rawData @property def folderID(self) -> FolderID: """ The folder the message will be in. """ return self.__folderID @property def instance(self) -> int: """ Instance number that is only used in multivalue properties for searching for a specific ServerID. Will otherwise be 0. """ return self.__instance @property def messageID(self) -> MessageID: """ A MessageID identifying a message in a folder identified by the folderID instance of this object. If the object pointed to is a folder, both properties will be 0. """ return self.__messageID
Memory