Source code for extract_msg.attachments.custom_att_handler.outlook_image_dib

from __future__ import annotations


__all__ = [
    'OutlookImage',
]


import struct

from typing import Optional, TYPE_CHECKING

from . import registerHandler
from .custom_handler import CustomAttachmentHandler
from ...enums import DVAspect, InsecureFeatures
from ...exceptions import DependencyError, SecurityError


if TYPE_CHECKING:
    from ..attachment_base import AttachmentBase

_ST_OLE = struct.Struct('<IIIII')
_ST_MAILSTREAM = struct.Struct('<III')


[docs]class OutlookImageDIB(CustomAttachmentHandler): """ Custom handler for a special attachment type, a Device Independent Bitmap stored in a way special to Outlook. """ def __init__(self, attachment: AttachmentBase): super().__init__(attachment) # First we need to get the mailstream. stream = self.getStream('\x03MailStream') if not stream: raise ValueError('MailStream could not be found.') if len(stream) != 12: raise ValueError('MailStream is the wrong length.') # Next get the bitmap data. self.__data = self.getStream('CONTENTS') if not self.__data: raise ValueError('Bitmap data could not be read for Outlook signature.') # Get the OLE data. oleStream = self.getStream('\x01Ole') if not oleStream: raise ValueError('OLE stream could not be found.') # While I have only seen this stream be one length, it could in theory # be more than one length. So long as it is *at least* 20 bytes, we # call it valid. if len(oleStream) < 20: raise ValueError('OLE stream is too short.') # Unpack and verify the OLE stream. vals = _ST_OLE.unpack(oleStream[:20]) # Check the version magic. if vals[0] != 0x2000001: raise ValueError('OLE stream has wrong version magic.') # Check the reserved bytes. if vals[3] != 0: raise ValueError('OLE stream has non-zero reserved int.') # Unpack the mailstream and create the HTML tag. vals = _ST_MAILSTREAM.unpack(stream) self.__dvaspect = DVAspect(vals[0]) self.__x = vals[1] self.__y = vals[2] # Convert to twips for RTF. self.__xtwips = int(round(self.__x / 1.7639)) self.__ytwips = int(round(self.__y / 1.7639))
[docs] @classmethod def isCorrectHandler(cls, attachment: AttachmentBase) -> bool: if attachment.clsid != '00000316-0000-0000-C000-000000000046': return False # Check for the required streams. if not attachment.exists('__substg1.0_3701000D/CONTENTS'): return False if not attachment.exists('__substg1.0_3701000D/\x01Ole'): return False if not attachment.exists('__substg1.0_3701000D/\x03MailStream'): return False return True
[docs] def generateRtf(self) -> Optional[bytes]: """ Generates the RTF to inject in place of the \\objattph tag. If this function should do nothing, returns ``None``. :raises DependencyError: PIL or Pillow could not be found. """ if InsecureFeatures.PIL_IMAGE_PARSING not in self.attachment.msg.insecureFeatures: raise SecurityError('Generating the RTF for a custom attachment requires the insecure feature PIL_IMAGE_PARSING.') try: import PIL.Image except ImportError: raise DependencyError('PIL or Pillow is required for inserting an Outlook Image into the body.') # First, convert the bitmap into a PNG so we can insert it into the # body. import io # Note, use self.data instead of self.__data to allow support for # extensions. with PIL.Image.open(io.BytesIO(self.data)) as img: out = io.BytesIO() img.save(out, 'PNG') hexData = out.getvalue().hex() inject = '{\\*\\shppict\n{\\pict\\picscalex100\\picscaley100' inject += f'\\picw{img.width}\\pich{img.height}' inject += f'\\picwgoal{self.__xtwips}\\pichgoal{self.__ytwips}\n' inject += '\\pngblip ' + hexData + '}}' return inject.encode()
@property def data(self) -> bytes: return self.__data @property def name(self) -> str: # Try to get the name from the attachment. If that fails, name it based # on the number. if not (name := self.attachment.name): name = f'attachment {int(self.attachment.dir[-8:], 16)}' return name + '.bmp' @property def obj(self) -> bytes: return self.data
registerHandler(OutlookImageDIB)