from __future__ import annotations
__all__ = [
'OLEPresentationStream',
]
from typing import List, Optional, Union
from ._helpers import BytesReader
from .cfoas import ClipboardFormatOrAnsiString
from ..constants import st
from .dv_target_device import DVTargetDevice
from ..enums import ADVF, ClipboardFormat, DVAspect
from .toc_entry import TOCEntry
class OLEPresentationStream:
"""
[MS-OLEDS] OLEPresentationStream.
"""
def __init__(self, data: bytes):
reader = BytesReader(data)
acf = self.__ansiClipboardFormat = ClipboardFormatOrAnsiString(reader)
# Validate the structure based on the documentation.
if acf.markerOrLength == 0:
raise ValueError('Invalid OLEPresentationStream (MarkerOrLength is 0).')
if acf.clipboardFormat is ClipboardFormat.CF_BITMAP:
raise ValueError('Invalid OLEPresentationStream (Format is CF_BITMAP).')
if 0x201 < acf.markerOrLength < 0xFFFFFFFE:
raise ValueError('Invalid OLEPresentationStream (ANSI length was more than 0x201).')
targetDeviceSize = reader.readUnsignedInt()
if targetDeviceSize < 0x4:
raise ValueError('Invalid OLEPresentationStream (TargetDeviceSize was less than 4).')
if targetDeviceSize > 0x4:
# Read the TargetDevice field.
self.__targetDevice = DVTargetDevice(reader.read(targetDeviceSize))
else:
self.__targetDevice = None
self.__aspect = reader.readUnsignedInt()
self.__lindex = reader.readUnsignedInt()
self.__advf = reader.readUnsignedInt()
# Reserved1.
self.__reserved1 = reader.read(4)
self.__width = reader.readUnsignedInt()
self.__height = reader.readUnsignedInt()
size = reader.readUnsignedInt()
self.__data = reader.read(size)
if acf.clipboardFormat is ClipboardFormat.CF_METAFILEPICT:
self.__reserved2 = reader.read(18)
else:
self.__reserved2 = b'\x00' * 18.
self.__tocSignature = reader.readUnsignedInt()
self.__tocEntries = []
if self.__tocSignature == 0x494E414E: # b'NANI' in little endian.
for _ in range(reader.readUnsignedInt()):
self.__tocEntries.append(TOCEntry(reader))
def __bytes__(self) -> bytes:
return self.toBytes()
def toBytes(self) -> bytes:
ret = bytes(self.__ansiClipboardFormat)
if self.__targetDevice is None:
ret += b'\x04\x00\x00\x00'
else:
dvData = bytes(self.__targetDevice)
ret += st.ST_LE_UI32.pack(len(dvData))
ret += dvData
ret += st.ST_LE_UI32.pack(self.__aspect)
ret += st.ST_LE_UI32.pack(self.__lindex)
ret += st.ST_LE_UI32.pack(self.__advf)
ret += self.__reserved1
ret += st.ST_LE_UI32.pack(self.__width)
ret += st.ST_LE_UI32.pack(self.__height)
ret += st.ST_LE_UI32.pack(len(self.__data)) + self.__data
if self.reserved2: # Shortcut since this property has protection.
ret += self.__reserved2
if self.__tocSignature == 0x494E414E:
ret += st.ST_LE_UI32.pack(len(self.__tocEntries))
for entry in self.__tocEntries:
ret += bytes(entry)
else:
ret += b'\x00\x00\x00\x00'
return ret
@property
def advf(self) -> Union[int, ADVF]:
"""
An implementation specific hint on how to render the presentation data
on screen. May be ignored on processing.
"""
return self.__advf
@advf.setter
def advf(self, val: Union[int, ADVF]) -> None:
if not isinstance(val, int):
raise TypeError(':property advf: must be an int.')
if val < 0:
raise ValueError(':property advf: must be positive.')
if val > 0xFFFFFFFF:
raise ValueError(':property advf: cannot be greater than 0xFFFFFFFF.')
self.__advf = val
@property
def ansiClipboardFormat(self) -> ClipboardFormatOrAnsiString:
return self.__ansiClipboardFormat
@property
def aspect(self) -> Union[int, DVAspect]:
"""
An implementation specific hint on how to render the presentation data
on screen. May be ignored on processing.
"""
return self.__aspect
@aspect.setter
def aspect(self, val: Union[int, DVAspect]) -> None:
if not isinstance(val, int):
raise TypeError(':property aspect: must be an int.')
if val < 0:
raise ValueError(':property aspect: must be positive.')
if val > 0xFFFFFFFF:
raise ValueError(':property aspect: cannot be greater than 0xFFFFFFFF.')
self.__aspect = val
@property
def data(self) -> bytes:
"""
The presentation data. The form of this data depends on :property clipboardFormat: of :property ansiClipboardFormat:.
"""
return self.__data
@data.setter
def data(self, val: bytes) -> None:
if not isinstance(val, bytes):
raise TypeError(':property data: must be bytes.')
self.__data = val
@property
def height(self) -> int:
"""
The height, in pixels, of the presentation data.
"""
return self.__height
@height.setter
def height(self, val: int) -> None:
if not isinstance(val, int):
raise TypeError(':property height: must be an int.')
if val < 0:
raise ValueError(':property height: must be positive.')
if val > 0xFFFFFFFF:
raise ValueError(':property height: cannot be greater than 0xFFFFFFFF.')
self.__height = val
@property
def lindex(self) -> int:
"""
An implementation specific hint on how to render the presentation data on screen. May be ignored on processing.
"""
return self.__lindex
@lindex.setter
def lindex(self, val: int) -> None:
if not isinstance(val, int):
raise TypeError(':property lindex: must be an int.')
if val < 0:
raise ValueError(':property lindex: must be positive.')
if val > 0xFFFFFFFF:
raise ValueError(':property lindex: cannot be greater than 0xFFFFFFFF.')
self.__lindex = val
@property
def reserved1(self) -> bytes:
"""
4 bytes that can contain any arbitrary data. Must be *exactly* 4 bytes
when setting.
"""
return self.__reserved1
@reserved1.setter
def reserved1(self, val: bytes) -> None:
if not isinstance(val, bytes):
raise TypeError(':property reserved1: must by bytes.')
if len(val) != 4:
raise ValueError(':property reserved1: must be exactly 4 bytes.')
self.__reserved1 = val
@property
def reserved2(self) -> Optional[bytes]:
"""
Optional additional data that is only set if the clipboard format of
:property ansiClipboardFormat: is ``CF_METAFILEPICT``.
Getting this will automatically correct the value retrieved based on
the clipboard format, but will *not* modify the underlying data.
Must be *exactly* 18 bytes when setting.
"""
if self.__ansiClipboardFormat.clipboardFormat is not ClipboardFormat.CF_METAFILEPICT:
return None
return self.__reserved2
@reserved2.setter
def reserved2(self, val: bytes) -> None:
if self.__ansiClipboardFormat.clipboardFormat is not ClipboardFormat.CF_METAFILEPICT:
raise ValueError(':property reserved2: cannot be set if the clipboard format (from :property ansiClipboardFormat:) is not CF_METAFILEPICT.')
if not isinstance(val, bytes):
raise TypeError(':property reserved2: must by bytes.')
if len(val) != 18:
raise ValueError(':property reserved2: must be exactly 18 bytes.')
self.__reserved2 = val
@property
def targetDevice(self) -> Optional[DVTargetDevice]:
return self.__targetDevice
@targetDevice.setter
def targetDevice(self, val: Optional[DVTargetDevice]) -> None:
if val is not None and not isinstance(val, DVTargetDevice):
raise TypeError(':property targetDevice: must be None or a DVTargetDevice.')
self.__targetDevice = val
@property
def tocEntries(self) -> List[TOCEntry]:
"""
A list of TOCEntry structures. If :property tocSignature: is not set to
``0x494E414E``, accessing this value will clear the list.
:returns: A direct reference to the list, allowing for modification.
This class WILL NOT change this reference over the lifetime of the
object.
"""
if self.__tocSignature != 0x494E414E:
self.__tocEntries.clear()
return self.__tocEntries
@property
def tocSignature(self) -> int:
"""
If this field does not contain ``0x494E414E``, then
: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.
Setting this to a value other than ``0x494E414E`` will clear the list
immediately.
"""
return self.__tocSignature
@tocSignature.setter
def tocSignature(self, val: int) -> None:
if not isinstance(val, int):
raise TypeError(':property tocSignature: must be an int.')
if val < 0:
raise ValueError(':property tocSignature: must be positive.')
if val > 0xFFFFFFFF:
raise ValueError(':property tocSignature: cannot be greater than 0xFFFFFFFF.')
if val != 0x494E414E:
self.__tocEntries.clear()
self.__tocSignature = val
@property
def width(self) -> int:
"""
The width, in pixels, of the presentation data.
"""
return self.__width
@width.setter
def width(self, val: int) -> None:
if not isinstance(val, int):
raise TypeError(':property width: must be an int.')
if val < 0:
raise ValueError(':property width: must be positive.')
if val > 0xFFFFFFFF:
raise ValueError(':property width: cannot be greater than 0xFFFFFFFF.')
self.__width = val