Example #1
0
    def testInitialize(self):
        """Tests the __init__ function."""
        definitions_file = self._GetTestFilePath(['integer.yaml'])
        with open(definitions_file, 'rb') as file_object:
            yaml_definition = file_object.read()

        factory = fabric.DataTypeFabric(yaml_definition=yaml_definition)
        self.assertIsNotNone(factory)
Example #2
0
    def _ReadDefinitionFile(self, path):
        """Reads a dtFabric definition file.

    Args:
      path (str): path of the dtFabric definition file.

    Returns:
      dtfabric.DataTypeFabric: data type fabric which contains the data format
          data type maps of the data type definition, such as a structure, that
          can be mapped onto binary data or None if no path is provided.
    """
        if not path:
            return None

        with open(path, 'rb') as file_object:
            definition = file_object.read()

        return dtfabric_fabric.DataTypeFabric(yaml_definition=definition)
    def _ReadDefinitionFile(self, filename):
        """Reads a dtFabric definition file.

    Args:
      filename (str): name of the dtFabric definition file.

    Returns:
      dtfabric.DataTypeFabric: data type fabric which contains the data format
          data type maps of the data type definition, such as a structure, that
          can be mapped onto binary data or None if no filename is provided.
    """
        if not filename:
            return None

        path = os.path.join(self._DEFINITION_FILES_PATH, filename)
        with open(path, 'rb') as file_object:
            definition = file_object.read()

        return dtfabric_fabric.DataTypeFabric(yaml_definition=definition)
Example #4
0
class TestBinaryDataFormat(data_format.BinaryDataFormat):
    """Binary data format for testing."""

    _DEFINITION = b"""\
name: uint32
type: integer
attributes:
  format: unsigned
  size: 4
  units: bytes
---
name: point3d
type: structure
attributes:
  byte_order: little-endian
members:
- name: x
  data_type: uint32
- name: y
  data_type: uint32
- name: z
  data_type: uint32
---
name: shape3d
type: structure
attributes:
  byte_order: little-endian
members:
- name: number_of_points
  data_type: uint32
- name: points
  type: sequence
  element_data_type: point3d
  number_of_elements: shape3d.number_of_points
"""

    _FABRIC = dtfabric_fabric.DataTypeFabric(yaml_definition=_DEFINITION)

    POINT3D = _FABRIC.CreateDataTypeMap('point3d')

    POINT3D_SIZE = POINT3D.GetByteSize()

    SHAPE3D = _FABRIC.CreateDataTypeMap('shape3d')
Example #5
0
class DtFabricBaseParserTest(test_lib.BaseTestCase):
    """Shared functionality for dtFabric-based data format parsers tests."""

    # pylint: disable=protected-access

    _DATA_TYPE_FABRIC_DEFINITION = b"""\
name: uint32
type: integer
attributes:
  format: unsigned
  size: 4
  units: bytes
---
name: point3d
type: structure
attributes:
  byte_order: little-endian
members:
- name: x
  data_type: uint32
- name: y
  data_type: uint32
- name: z
  data_type: uint32
---
name: shape3d
type: structure
attributes:
  byte_order: little-endian
members:
- name: number_of_points
  data_type: uint32
- name: points
  type: sequence
  element_data_type: point3d
  number_of_elements: shape3d.number_of_points
"""

    _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric(
        yaml_definition=_DATA_TYPE_FABRIC_DEFINITION)

    _POINT3D = _DATA_TYPE_FABRIC.CreateDataTypeMap('point3d')

    _POINT3D_SIZE = _POINT3D.GetByteSize()

    _SHAPE3D = _DATA_TYPE_FABRIC.CreateDataTypeMap('shape3d')

    # TODO: add tests for _GetDataTypeMap

    def testReadData(self):
        """Tests the _ReadData function."""
        parser = dtfabric_parser.DtFabricBaseParser()

        file_object = io.BytesIO(
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')

        parser._ReadData(file_object, 0, self._POINT3D_SIZE)

        # Test with missing file-like object.
        with self.assertRaises(ValueError):
            parser._ReadData(None, 0, self._POINT3D_SIZE)

        # Test with file-like object with insufficient data.
        file_object = io.BytesIO(
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00')

        with self.assertRaises(errors.ParseError):
            parser._ReadData(file_object, 0, self._POINT3D_SIZE)

        # Test with file-like object that raises an IOError.
        file_object = ErrorBytesIO(
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')

        with self.assertRaises(errors.ParseError):
            parser._ReadData(file_object, 0, self._POINT3D_SIZE)

    # TODO: add tests for _ReadDefinitionFile

    def testReadStructureFromByteStream(self):
        """Tests the _ReadStructureFromByteStream function."""
        parser = dtfabric_parser.DtFabricBaseParser()

        parser._ReadStructureFromByteStream(
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0,
            self._POINT3D)

        # Test with missing byte stream.
        with self.assertRaises(ValueError):
            parser._ReadStructureFromByteStream(None, 0, self._POINT3D)

        # Test with missing data map type.
        with self.assertRaises(ValueError):
            parser._ReadStructureFromByteStream(
                b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, None)

        # Test with data type map that raises an dtfabric.MappingError.
        data_type_map = ErrorDataTypeMap(None)

        with self.assertRaises(errors.ParseError):
            parser._ReadStructureFromByteStream(
                b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0,
                data_type_map)

    def testReadStructureFromFileObject(self):
        """Tests the _ReadStructureFromFileObject function."""
        parser = dtfabric_parser.DtFabricBaseParser()

        file_object = io.BytesIO(
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')

        parser._ReadStructureFromFileObject(file_object, 0, self._POINT3D)

        file_object = io.BytesIO(
            b'\x03\x00\x00\x00'
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
            b'\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00'
            b'\x06\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00')

        parser._ReadStructureFromFileObject(file_object, 0, self._SHAPE3D)
Example #6
0
class CPIOArchiveFile(data_format.DataFormat):
    """CPIO archive file.

  Attributes:
    file_format (str): CPIO file format.
  """
    _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join(os.path.dirname(__file__),
                                                     'cpio.yaml')

    with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object:
        _DATA_TYPE_FABRIC_DEFINITION = file_object.read()

    _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric(
        yaml_definition=_DATA_TYPE_FABRIC_DEFINITION)

    _CPIO_BINARY_BIG_ENDIAN_FILE_ENTRY = _DATA_TYPE_FABRIC.CreateDataTypeMap(
        'cpio_binary_big_endian_file_entry')

    _CPIO_BINARY_BIG_ENDIAN_FILE_ENTRY_SIZE = (
        _CPIO_BINARY_BIG_ENDIAN_FILE_ENTRY.GetByteSize())

    _CPIO_BINARY_LITTLE_ENDIAN_FILE_ENTRY = _DATA_TYPE_FABRIC.CreateDataTypeMap(
        'cpio_binary_little_endian_file_entry')

    _CPIO_BINARY_LITTLE_ENDIAN_FILE_ENTRY_SIZE = (
        _CPIO_BINARY_LITTLE_ENDIAN_FILE_ENTRY.GetByteSize())

    _CPIO_PORTABLE_ASCII_FILE_ENTRY = _DATA_TYPE_FABRIC.CreateDataTypeMap(
        'cpio_portable_ascii_file_entry')

    _CPIO_PORTABLE_ASCII_FILE_ENTRY_SIZE = (
        _CPIO_PORTABLE_ASCII_FILE_ENTRY.GetByteSize())

    _CPIO_NEW_ASCII_FILE_ENTRY = _DATA_TYPE_FABRIC.CreateDataTypeMap(
        'cpio_new_ascii_file_entry')

    _CPIO_NEW_ASCII_FILE_ENTRY_SIZE = _CPIO_NEW_ASCII_FILE_ENTRY.GetByteSize()

    _CPIO_SIGNATURE_BINARY_BIG_ENDIAN = b'\x71\xc7'
    _CPIO_SIGNATURE_BINARY_LITTLE_ENDIAN = b'\xc7\x71'
    _CPIO_SIGNATURE_PORTABLE_ASCII = b'070707'
    _CPIO_SIGNATURE_NEW_ASCII = b'070701'
    _CPIO_SIGNATURE_NEW_ASCII_WITH_CHECKSUM = b'070702'

    _CPIO_ATTRIBUTE_NAMES_ODC = ('device_number', 'inode_number', 'mode',
                                 'user_identifier', 'group_identifier',
                                 'number_of_links', 'special_device_number',
                                 'modification_time', 'path_size', 'file_size')

    _CPIO_ATTRIBUTE_NAMES_CRC = ('inode_number', 'mode', 'user_identifier',
                                 'group_identifier', 'number_of_links',
                                 'modification_time', 'path_size', 'file_size',
                                 'device_major_number', 'device_minor_number',
                                 'special_device_major_number',
                                 'special_device_minor_number', 'checksum')

    def __init__(self, encoding='utf-8'):
        """Initializes a CPIO archive file.

    Args:
      encoding (str): encoding of paths within the archive file.
    """
        super(CPIOArchiveFile, self).__init__()
        self._encoding = encoding
        self._file_entries = None
        self._file_object = None
        self._file_size = 0

        self.file_format = None

    @property
    def encoding(self):
        """str: encoding of paths within the archive file."""
        return self._encoding

    def _ReadFileEntry(self, file_object, file_offset):
        """Reads a file entry.

    Args:
      file_object (FileIO): file-like object.
      file_offset (int): offset of the data relative from the start of
          the file-like object.

    Returns:
      CPIOArchiveFileEntry: a file entry.

    Raises:
      FileFormatError: if the file entry cannot be read.
    """
        if self.file_format == 'bin-big-endian':
            data_type_map = self._CPIO_BINARY_BIG_ENDIAN_FILE_ENTRY
            file_entry_data_size = self._CPIO_BINARY_BIG_ENDIAN_FILE_ENTRY_SIZE
        elif self.file_format == 'bin-little-endian':
            data_type_map = self._CPIO_BINARY_LITTLE_ENDIAN_FILE_ENTRY
            file_entry_data_size = self._CPIO_BINARY_LITTLE_ENDIAN_FILE_ENTRY_SIZE
        elif self.file_format == 'odc':
            data_type_map = self._CPIO_PORTABLE_ASCII_FILE_ENTRY
            file_entry_data_size = self._CPIO_PORTABLE_ASCII_FILE_ENTRY_SIZE
        elif self.file_format in ('crc', 'newc'):
            data_type_map = self._CPIO_NEW_ASCII_FILE_ENTRY
            file_entry_data_size = self._CPIO_NEW_ASCII_FILE_ENTRY_SIZE

        file_entry = self._ReadStructure(file_object, file_offset,
                                         file_entry_data_size, data_type_map,
                                         'file entry')

        file_offset += file_entry_data_size

        if self.file_format in ('bin-big-endian', 'bin-little-endian'):
            file_entry.modification_time = (
                (file_entry.modification_time.upper << 16)
                | file_entry.modification_time.lower)

            file_entry.file_size = ((file_entry.file_size.upper << 16)
                                    | file_entry.file_size.lower)

        if self.file_format == 'odc':
            for attribute_name in self._CPIO_ATTRIBUTE_NAMES_ODC:
                value = getattr(file_entry, attribute_name, None)
                try:
                    value = int(value, 8)
                except ValueError:
                    raise errors.FileFormatError(
                        'Unable to convert attribute: {0:s} into an integer'.
                        format(attribute_name))

                value = setattr(file_entry, attribute_name, value)

        elif self.file_format in ('crc', 'newc'):
            for attribute_name in self._CPIO_ATTRIBUTE_NAMES_CRC:
                value = getattr(file_entry, attribute_name, None)
                try:
                    value = int(value, 16)
                except ValueError:
                    raise errors.FileFormatError(
                        'Unable to convert attribute: {0:s} into an integer'.
                        format(attribute_name))

                value = setattr(file_entry, attribute_name, value)

        path_data = file_object.read(file_entry.path_size)

        file_offset += file_entry.path_size

        path = path_data.decode(self._encoding)
        path, _, _ = path.partition('\x00')

        if self.file_format in ('bin-big-endian', 'bin-little-endian'):
            padding_size = file_offset % 2
            if padding_size > 0:
                padding_size = 2 - padding_size

        elif self.file_format == 'odc':
            padding_size = 0

        elif self.file_format in ('crc', 'newc'):
            padding_size = file_offset % 4
            if padding_size > 0:
                padding_size = 4 - padding_size

        file_offset += padding_size

        archive_file_entry = CPIOArchiveFileEntry()

        archive_file_entry.data_offset = file_offset
        archive_file_entry.data_size = file_entry.file_size
        archive_file_entry.group_identifier = file_entry.group_identifier
        archive_file_entry.inode_number = file_entry.inode_number
        archive_file_entry.modification_time = file_entry.modification_time
        archive_file_entry.path = path
        archive_file_entry.mode = file_entry.mode
        archive_file_entry.size = (file_entry_data_size +
                                   file_entry.path_size + padding_size +
                                   file_entry.file_size)
        archive_file_entry.user_identifier = file_entry.user_identifier

        file_offset += file_entry.file_size

        if self.file_format in ('bin-big-endian', 'bin-little-endian'):
            padding_size = file_offset % 2
            if padding_size > 0:
                padding_size = 2 - padding_size

        elif self.file_format == 'odc':
            padding_size = 0

        elif self.file_format in ('crc', 'newc'):
            padding_size = file_offset % 4
            if padding_size > 0:
                padding_size = 4 - padding_size

        if padding_size > 0:
            archive_file_entry.size += padding_size

        return archive_file_entry

    def _ReadFileEntries(self, file_object):
        """Reads the file entries from the cpio archive.

    Args:
      file_object (FileIO): file-like object.
    """
        self._file_entries = {}

        file_offset = 0
        while file_offset < self._file_size or self._file_size == 0:
            file_entry = self._ReadFileEntry(file_object, file_offset)
            file_offset += file_entry.size
            if file_entry.path == 'TRAILER!!!':
                break

            if file_entry.path in self._file_entries:
                # TODO: alert on file entries with duplicate paths?
                continue

            self._file_entries[file_entry.path] = file_entry

    def Close(self):
        """Closes the CPIO archive file."""
        self._file_entries = None
        self._file_object = None
        self._file_size = None

    def FileEntryExistsByPath(self, path):
        """Determines if file entry for a specific path exists.

    Returns:
      bool: True if the file entry exists.
    """
        if self._file_entries is None:
            return False

        return path in self._file_entries

    def GetFileEntries(self, path_prefix=''):
        """Retrieves the file entries.

    Args:
      path_prefix (str): path prefix.

    Yields:
      CPIOArchiveFileEntry: a CPIO archive file entry.
    """
        if self._file_entries:
            for path, file_entry in self._file_entries.items():
                if path.startswith(path_prefix):
                    yield file_entry

    def GetFileEntryByPath(self, path):
        """Retrieves a file entry for a specific path.

    Returns:
      CPIOArchiveFileEntry: a CPIO archive file entry or None if not available.
    """
        if not self._file_entries:
            return None
        return self._file_entries.get(path, None)

    def Open(self, file_object):
        """Opens the CPIO archive file.

    Args:
      file_object (FileIO): a file-like object.

    Raises:
      IOError: if the file format signature is not supported.
      OSError: if the file format signature is not supported.
    """
        file_object.seek(0, os.SEEK_SET)
        signature_data = file_object.read(6)

        self.file_format = None
        if len(signature_data) > 2:
            if signature_data[:2] == self._CPIO_SIGNATURE_BINARY_BIG_ENDIAN:
                self.file_format = 'bin-big-endian'
            elif signature_data[:
                                2] == self._CPIO_SIGNATURE_BINARY_LITTLE_ENDIAN:
                self.file_format = 'bin-little-endian'
            elif signature_data == self._CPIO_SIGNATURE_PORTABLE_ASCII:
                self.file_format = 'odc'
            elif signature_data == self._CPIO_SIGNATURE_NEW_ASCII:
                self.file_format = 'newc'
            elif signature_data == self._CPIO_SIGNATURE_NEW_ASCII_WITH_CHECKSUM:
                self.file_format = 'crc'

        if self.file_format is None:
            raise IOError('Unsupported CPIO format.')

        self._file_object = file_object
        self._file_size = file_object.get_size()

        self._ReadFileEntries(self._file_object)

    def ReadDataAtOffset(self, file_offset, size):
        """Reads a byte string from the file-like object at a specific offset.

    Args:
      file_offset (int): file offset.
      size (int): number of bytes to read.

    Returns:
      bytes: data read.

    Raises:
      IOError: if the read failed.
      OSError: if the read failed.
    """
        self._file_object.seek(file_offset, os.SEEK_SET)
        return self._file_object.read(size)
Example #7
0
class EMFFile(data_format.BinaryDataFile):
    """Enhanced Metafile Format (EMF) file."""

    FILE_TYPE = 'Windows Enhanced Metafile'

    _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join(os.path.dirname(__file__),
                                                     'emf.yaml')

    with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object:
        _DATA_TYPE_FABRIC_DEFINITION = file_object.read()

    _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric(
        yaml_definition=_DATA_TYPE_FABRIC_DEFINITION)

    _RECORD_TYPE = _DATA_TYPE_FABRIC.CreateDataTypeMap('emf_record_type')

    _FILE_HEADER = _DATA_TYPE_FABRIC.CreateDataTypeMap('emf_file_header')

    _FILE_HEADER_SIZE = _FILE_HEADER.GetByteSize()

    _RECORD_HEADER = _DATA_TYPE_FABRIC.CreateDataTypeMap('emf_record_header')

    _RECORD_HEADER_SIZE = _RECORD_HEADER.GetByteSize()

    _EMF_SIGNATURE = b'FME\x20'

    # Here None represents that the record has no additional data.
    _EMF_RECORD_DATA_STRUCT_TYPES = {
        0x0018: _DATA_TYPE_FABRIC.CreateDataTypeMap('emf_settextcolor'),
        0x0025: _DATA_TYPE_FABRIC.CreateDataTypeMap('emf_selectobject')
    }

    _EMF_STOCK_OBJECT = _DATA_TYPE_FABRIC.CreateDataTypeMap('emf_stock_object')

    def _DebugPrintFileHeader(self, file_header):
        """Prints file header debug information.

    Args:
      file_header (emf_file_header): file header.
    """
        record_type_string = self._RECORD_TYPE.GetName(file_header.record_type)
        value_string = '0x{0:04x} ({1:s})'.format(
            file_header.record_type, record_type_string or 'UNKNOWN')
        self._DebugPrintValue('Record type', value_string)

        value_string = '{0:d}'.format(file_header.record_size)
        self._DebugPrintValue('Record size', value_string)

        value_string = '0x{0:04x}'.format(file_header.signature)
        self._DebugPrintValue('Signature', value_string)

        value_string = '0x{0:04x}'.format(file_header.format_version)
        self._DebugPrintValue('Format version', value_string)

        value_string = '{0:d}'.format(file_header.file_size)
        self._DebugPrintValue('File size', value_string)

        value_string = '{0:d}'.format(file_header.number_of_records)
        self._DebugPrintValue('Number of records', value_string)

        value_string = '{0:d}'.format(file_header.number_of_handles)
        self._DebugPrintValue('Number of handles', value_string)

        value_string = '0x{0:04x}'.format(file_header.unknown1)
        self._DebugPrintValue('Unknown (reserved)', value_string)

        value_string = '{0:d}'.format(file_header.description_string_size)
        self._DebugPrintValue('Description string size', value_string)

        value_string = '0x{0:04x}'.format(
            file_header.description_string_offset)
        self._DebugPrintValue('Description string offset', value_string)

        self._DebugPrintText('\n')

    def _DebugPrintRecordHeader(self, record_header):
        """Prints record header debug information.

    Args:
      record_header (emf_record_header): record header.
    """
        record_type_string = self._RECORD_TYPE.GetName(
            record_header.record_type)
        value_string = '0x{0:04x} ({1:s})'.format(
            record_header.record_type, record_type_string or 'UNKNOWN')
        self._DebugPrintValue('Record type', value_string)

        value_string = '{0:d}'.format(record_header.record_size)
        self._DebugPrintValue('Record size', value_string)

        self._DebugPrintText('\n')

    def _ReadFileHeader(self, file_object):
        """Reads a file header.

    Args:
      file_object (file): file-like object.

    Raises:
      ParseError: if the file header cannot be read.
    """
        file_offset = file_object.tell()
        file_header = self._ReadStructure(file_object, file_offset,
                                          self._FILE_HEADER_SIZE,
                                          self._FILE_HEADER, 'file header')

        if self._debug:
            self._DebugPrintFileHeader(file_header)

        # TODO: check record type
        # TODO: check record size
        # TODO: check signature

    def _ReadRecord(self, file_object, file_offset):
        """Reads a record.

    Args:
      file_object (file): file-like object.
      file_offset (int): offset of the data relative to the start of
          the file-like object.

    Raises:
      ParseError: if the record cannot be read.
    """
        record_header = self._ReadStructure(file_object, file_offset,
                                            self._RECORD_HEADER_SIZE,
                                            self._RECORD_HEADER,
                                            'record header')

        if self._debug:
            self._DebugPrintRecordHeader(record_header)

        data_offset = file_offset + self._RECORD_HEADER_SIZE
        data_size = record_header.record_size - self._RECORD_HEADER_SIZE

        if self._debug:
            self._ReadRecordData(file_object, record_header.record_type,
                                 data_size)

        return Record(record_header.record_type, record_header.record_size,
                      data_offset, data_size)

    def _ReadRecordData(self, file_object, record_type, data_size):
        """Reads a record.

    Args:
      file_object (file): file-like object.
      record_type (int): record type.
      data_size (int): size of the record data.

    Raises:
      ParseError: if the record data cannot be read.
    """
        record_data = file_object.read(data_size)

        if self._debug and data_size > 0:
            self._DebugPrintData('Record data', record_data)

        # TODO: use lookup dict with callback.
        data_type_map = self._EMF_RECORD_DATA_STRUCT_TYPES.get(
            record_type, None)
        if not data_type_map:
            return

        try:
            record = data_type_map.MapByteStream(record_data)
        except dtfabric_errors.MappingError as exception:
            raise errors.ParseError(
                ('Unable to parse record data with error: {0:s}'
                 ).format(exception))

        if self._debug:
            if record_type == 0x0018:
                value_string = '0x{0:04x}'.format(record.color)
                self._DebugPrintValue('Color', value_string)

            elif record_type == 0x0025:
                stock_object_string = self._EMF_STOCK_OBJECT.GetName(
                    record.object_identifier)

                if stock_object_string:
                    value_string = '0x{0:08x} ({1:s})'.format(
                        record.object_identifier, stock_object_string)
                else:
                    value_string = '0x{0:08x}'.format(record.object_identifier)

                self._DebugPrintValue('Object identifier', value_string)

            self._DebugPrintText('\n')

    def ReadFileObject(self, file_object):
        """Reads a Enhanced Metafile Format (EMF) file-like object.

    Args:
      file_object (file): file-like object.
    """
        self._ReadFileHeader(file_object)

        file_offset = file_object.tell()
        while file_offset < self._file_size:
            record = self._ReadRecord(file_object, file_offset)

            file_offset += record.size
Example #8
0
class WMFFile(data_format.BinaryDataFile):
    """Windows Metafile Format (WMF) file."""

    FILE_TYPE = 'Windows Metafile'

    _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join(os.path.dirname(__file__),
                                                     'wmf.yaml')

    with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object:
        _DATA_TYPE_FABRIC_DEFINITION = file_object.read()

    # https://msdn.microsoft.com/en-us/library/cc250370.aspx

    # TODO: merge with YAML file
    _TODO = b'\n'.join([
        b'---',
        b'name: wmf_record',
        b'type: structure',
        b'description: WMF record',
        b'urls: ["https://msdn.microsoft.com/en-us/library/cc250387.aspx"]',
        b'attributes:',
        b'  byte_order: little-endian',
        b'members:',
        b'- name: record_size',
        b'  aliases: [RecordSize]',
        b'  description: Size of the record as number of 16-bit values',
        b'  data_type: uint32',
        # TODO: link to wmf_record_type
        b'- name: record_type',
        b'  aliases: [RecordFunction]',
        b'  data_type: uint16',
        b'- name: record_data',
        b'  aliases: [rdParam]',
        b'  type: stream',
        b'  element_data_type: byte',
        b'  element_data_size: (wmf_record.record_size * 2) - 6',
        b'---',
        b'name: wmf_restoredc_record',
        b'type: structure',
        b'description: Restores the playback device context',
        b'urls: ["https://msdn.microsoft.com/en-us/library/cc250469.aspx"]',
        b'attributes:',
        b'  byte_order: little-endian',
        b'members:',
        b'- name: record_size',
        b'  data_type: uint32',
        b'- name: record_type',
        b'  data_type: uint16',
        # TODO: or support wmf_record_type.META_RESTOREDC
        b'  value: 0x0127',
        b'- name: number_of_saved_device_context',
        b'  data_type: uint16',
        b'---',
        b'name: wmf_setmapmode_record',
        b'type: structure',
        b'description: Defines the mapping mode',
        b'urls: ["https://msdn.microsoft.com/en-us/library/cc250483.aspx"]',
        b'attributes:',
        b'  byte_order: little-endian',
        b'members:',
        b'- name: record_size',
        b'  data_type: uint32',
        b'- name: record_type',
        b'  data_type: uint16',
        # TODO: or support wmf_record_type.META_SETMAPMODE
        b'  value: 0x0103',
        b'- name: map_mode',
        b'  data_type: uint16',
        # TODO: map to wmf_map_mode
    ])

    _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric(
        yaml_definition=_DATA_TYPE_FABRIC_DEFINITION)

    _HEADER = _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_header')

    _HEADER_SIZE = _HEADER.GetByteSize()

    _PLACEABLE = _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_placeable')

    _PLACEABLE_SIZE = _PLACEABLE.GetByteSize()

    _RECORD_TYPE = _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_record_type')

    _RECORD_HEADER = _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_record_header')

    _RECORD_HEADER_SIZE = _RECORD_HEADER.GetByteSize()

    _WMF_PLACEABLE_SIGNATURE = b'\xd7\xcd\xc6\x9a'

    _MAP_MODE = _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_map_mode')

    _STRETCH_MODE = _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_stretch_mode')

    # record_size == ((record_type >> 8) + 3)
    # DIB: https://msdn.microsoft.com/en-us/library/cc250593.aspx

    # Here None represents that the record has no additional data.
    _WMF_RECORD_DATA_STRUCT_TYPES = {
        0x0000: None,
        0x001e: None,
        0x0103: _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_setmapmode'),
        0x0107: _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_setstretchbltmode'),
        0x0127: _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_restoredc'),
        0x020b: _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_setwindoworg'),
        0x020c: _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_setwindowext'),
        0x0b41: _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_dibstretchblt')
    }

    # Reverse Polish wmf_raster_operation_code
    _WMF_RASTER_OPERATIONS = {
        0x00000042: 'BLACKNESS',
        0x00010289: 'DPSOO',
        0x00020C89: 'DPSON',
        0x000300AA: 'PSO',
        0x00040C88: 'SDPON',
        0x000500A9: 'DPO',
        0x00060865: 'PDSXNO',
        0x000702C5: 'PDSAO',
        0x00080F08: 'SDPNA',
        0x00090245: 'PDSXO',
        0x000A0329: 'DPN',
        0x000B0B2A: 'PSDNAO',
        0x000C0324: 'SPN',
        0x000D0B25: 'PDSNAO',
        0x000E08A5: 'PDSONO',
        0x000F0001: 'P',
        0x00100C85: 'PDSON',
        0x001100A6: 'NOTSRCERAS',
        0x00120868: 'SDPXNO',
        0x001302C8: 'SDPAO',
        0x00140869: 'DPSXNO',
        0x001502C9: 'DPSAO',
        0x00165CCA: 'PSDPSANAX',
        0x00171D54: 'SSPXDSXAX',
        0x00180D59: 'SPXPDX',
        0x00191CC8: 'SDPSANAX',
        0x001A06C5: 'PDSPAO',
        0x001B0768: 'SDPSXAX',
        0x001C06CA: 'PSDPAO',
        0x001D0766: 'DSPDXAX',
        0x001E01A5: 'PDSO',
        0x001F0385: 'PDSOA',
        0x00200F09: 'DPSNA',
        0x00210248: 'SDPXO',
        0x00220326: 'DSN',
        0x00230B24: 'SPDNAO',
        0x00240D55: 'SPXDSX',
        0x00251CC5: 'PDSPANAX',
        0x002606C8: 'SDPSAO',
        0x00271868: 'SDPSXNOX',
        0x00280369: 'DPSXA',
        0x002916CA: 'PSDPSAOXXN',
        0x002A0CC9: 'DPSANA',
        0x002B1D58: 'SSPXPDXAXN',
        0x002C0784: 'SPDSOAX',
        0x002D060A: 'PSDNOX',
        0x002E064A: 'PSDPXOX',
        0x002F0E2A: 'PSDNOAN',
        0x0030032A: 'PSNA',
        0x00310B28: 'SDPNAON',
        0x00320688: 'SDPSOOX',
        0x00330008: 'NOTSRCCOPY',
        0x003406C4: 'SPDSAOX',
        0x00351864: 'SPDSXNOX',
        0x003601A8: 'SDPOX',
        0x00370388: 'SDPOAN',
        0x0038078A: 'PSDPOAX',
        0x00390604: 'SPDNOX',
        0x003A0644: 'SPDSXOX',
        0x003B0E24: 'SPDNOAN',
        0x003C004A: 'PSX',
        0x003D18A4: 'SPDSONOX',
        0x003E1B24: 'SPDSNAOX',
        0x003F00EA: 'PSAN',
        0x00400F0A: 'PSDNAA',
        0x00410249: 'DPSXON',
        0x00420D5D: 'SDXPDXA',
        0x00431CC4: 'SPDSANAXN',
        0x00440328: 'SRCERASE',
        0x00450B29: 'DPSNAON',
        0x004606C6: 'DSPDAOX',
        0x0047076A: 'PSDPXAXN',
        0x00480368: 'SDPXA',
        0x004916C5: 'PDSPDAOXXN',
        0x004A0789: 'DPSDOAX',
        0x004B0605: 'PDSNOX',
        0x004C0CC8: 'SDPANA',
        0x004D1954: 'SSPXDSXOXN',
        0x004E0645: 'PDSPXOX',
        0x004F0E25: 'PDSNOAN',
        0x00500325: 'PDNA',
        0x00510B26: 'DSPNAON',
        0x005206C9: 'DPSDAOX',
        0x00530764: 'SPDSXAXN',
        0x005408A9: 'DPSONON',
        0x00550009: 'DSTINVERT',
        0x005601A9: 'DPSOX',
        0x00570389: 'DPSOAN',
        0x00580785: 'PDSPOAX',
        0x00590609: 'DPSNOX',
        0x005A0049: 'PATINVERT',
        0x005B18A9: 'DPSDONOX',
        0x005C0649: 'DPSDXOX',
        0x005D0E29: 'DPSNOAN',
        0x005E1B29: 'DPSDNAOX',
        0x005F00E9: 'DPAN',
        0x00600365: 'PDSXA',
        0x006116C6: 'DSPDSAOXXN',
        0x00620786: 'DSPDOAX',
        0x00630608: 'SDPNOX',
        0x00640788: 'SDPSOAX',
        0x00650606: 'DSPNOX',
        0x00660046: 'SRCINVERT',
        0x006718A8: 'SDPSONOX',
        0x006858A6: 'DSPDSONOXXN',
        0x00690145: 'PDSXXN',
        0x006A01E9: 'DPSAX',
        0x006B178A: 'PSDPSOAXXN',
        0x006C01E8: 'SDPAX',
        0x006D1785: 'PDSPDOAXXN',
        0x006E1E28: 'SDPSNOAX',
        0x006F0C65: 'PDXNAN',
        0x00700CC5: 'PDSANA',
        0x00711D5C: 'SSDXPDXAXN',
        0x00720648: 'SDPSXOX',
        0x00730E28: 'SDPNOAN',
        0x00740646: 'DSPDXOX',
        0x00750E26: 'DSPNOAN',
        0x00761B28: 'SDPSNAOX',
        0x007700E6: 'DSAN',
        0x007801E5: 'PDSAX',
        0x00791786: 'DSPDSOAXXN',
        0x007A1E29: 'DPSDNOAX',
        0x007B0C68: 'SDPXNAN',
        0x007C1E24: 'SPDSNOAX',
        0x007D0C69: 'DPSXNAN',
        0x007E0955: 'SPXDSXO',
        0x007F03C9: 'DPSAAN',
        0x008003E9: 'DPSAA',
        0x00810975: 'SPXDSXON',
        0x00820C49: 'DPSXNA',
        0x00831E04: 'SPDSNOAXN',
        0x00840C48: 'SDPXNA',
        0x00851E05: 'PDSPNOAXN',
        0x008617A6: 'DSPDSOAXX',
        0x008701C5: 'PDSAXN',
        0x008800C6: 'SRCAND',
        0x00891B08: 'SDPSNAOXN',
        0x008A0E06: 'DSPNOA',
        0x008B0666: 'DSPDXOXN',
        0x008C0E08: 'SDPNOA',
        0x008D0668: 'SDPSXOXN',
        0x008E1D7C: 'SSDXPDXAX',
        0x008F0CE5: 'PDSANAN',
        0x00900C45: 'PDSXNA',
        0x00911E08: 'SDPSNOAXN',
        0x009217A9: 'DPSDPOAXX',
        0x009301C4: 'SPDAXN',
        0x009417AA: 'PSDPSOAXX',
        0x009501C9: 'DPSAXN',
        0x00960169: 'DPSXX',
        0x0097588A: 'PSDPSONOXX',
        0x00981888: 'SDPSONOXN',
        0x00990066: 'DSXN',
        0x009A0709: 'DPSNAX',
        0x009B07A8: 'SDPSOAXN',
        0x009C0704: 'SPDNAX',
        0x009D07A6: 'DSPDOAXN',
        0x009E16E6: 'DSPDSAOXX',
        0x009F0345: 'PDSXAN',
        0x00A000C9: 'DPA',
        0x00A11B05: 'PDSPNAOXN',
        0x00A20E09: 'DPSNOA',
        0x00A30669: 'DPSDXOXN',
        0x00A41885: 'PDSPONOXN',
        0x00A50065: 'PDXN',
        0x00A60706: 'DSPNAX',
        0x00A707A5: 'PDSPOAXN',
        0x00A803A9: 'DPSOA',
        0x00A90189: 'DPSOXN',
        0x00AA0029: 'D',
        0x00AB0889: 'DPSONO',
        0x00AC0744: 'SPDSXAX',
        0x00AD06E9: 'DPSDAOXN',
        0x00AE0B06: 'DSPNAO',
        0x00AF0229: 'DPNO',
        0x00B00E05: 'PDSNOA',
        0x00B10665: 'PDSPXOXN',
        0x00B21974: 'SSPXDSXOX',
        0x00B30CE8: 'SDPANAN',
        0x00B4070A: 'PSDNAX',
        0x00B507A9: 'DPSDOAXN',
        0x00B616E9: 'DPSDPAOXX',
        0x00B70348: 'SDPXAN',
        0x00B8074A: 'PSDPXAX',
        0x00B906E6: 'DSPDAOXN',
        0x00BA0B09: 'DPSNAO',
        0x00BB0226: 'MERGEPAINT',
        0x00BC1CE4: 'SPDSANAX',
        0x00BD0D7D: 'SDXPDXAN',
        0x00BE0269: 'DPSXO',
        0x00BF08C9: 'DPSANO',
        0x00C000CA: 'MERGECOPY',
        0x00C11B04: 'SPDSNAOXN',
        0x00C21884: 'SPDSONOXN',
        0x00C3006A: 'PSXN',
        0x00C40E04: 'SPDNOA',
        0x00C50664: 'SPDSXOXN',
        0x00C60708: 'SDPNAX',
        0x00C707AA: 'PSDPOAXN',
        0x00C803A8: 'SDPOA',
        0x00C90184: 'SPDOXN',
        0x00CA0749: 'DPSDXAX',
        0x00CB06E4: 'SPDSAOXN',
        0x00CC0020: 'SRCCOPY',
        0x00CD0888: 'SDPONO',
        0x00CE0B08: 'SDPNAO',
        0x00CF0224: 'SPNO',
        0x00D00E0A: 'PSDNOA',
        0x00D1066A: 'PSDPXOXN',
        0x00D20705: 'PDSNAX',
        0x00D307A4: 'SPDSOAXN',
        0x00D41D78: 'SSPXPDXAX',
        0x00D50CE9: 'DPSANAN',
        0x00D616EA: 'PSDPSAOXX',
        0x00D70349: 'DPSXAN',
        0x00D80745: 'PDSPXAX',
        0x00D906E8: 'SDPSAOXN',
        0x00DA1CE9: 'DPSDANAX',
        0x00DB0D75: 'SPXDSXAN',
        0x00DC0B04: 'SPDNAO',
        0x00DD0228: 'SDNO',
        0x00DE0268: 'SDPXO',
        0x00DF08C8: 'SDPANO',
        0x00E003A5: 'PDSOA',
        0x00E10185: 'PDSOXN',
        0x00E20746: 'DSPDXAX',
        0x00E306EA: 'PSDPAOXN',
        0x00E40748: 'SDPSXAX',
        0x00E506E5: 'PDSPAOXN',
        0x00E61CE8: 'SDPSANAX',
        0x00E70D79: 'SPXPDXAN',
        0x00E81D74: 'SSPXDSXAX',
        0x00E95CE6: 'DSPDSANAXXN',
        0x00EA02E9: 'DPSAO',
        0x00EB0849: 'DPSXNO',
        0x00EC02E8: 'SDPAO',
        0x00ED0848: 'SDPXNO',
        0x00EE0086: 'SRCPAINT',
        0x00EF0A08: 'SDPNOO',
        0x00F00021: 'PATCOPY',
        0x00F10885: 'PDSONO',
        0x00F20B05: 'PDSNAO',
        0x00F3022A: 'PSNO',
        0x00F40B0A: 'PSDNAO',
        0x00F50225: 'PDNO',
        0x00F60265: 'PDSXO',
        0x00F708C5: 'PDSANO',
        0x00F802E5: 'PDSAO',
        0x00F90845: 'PDSXNO',
        0x00FA0089: 'DPO',
        0x00FB0A09: 'PATPAINT',
        0x00FC008A: 'PSO',
        0x00FD0A0A: 'PSDNOO',
        0x00FE02A9: 'DPSOO',
        0x00FF0062: 'WHITENESS'
    }

    def _DebugPrintHeader(self, file_header):
        """Prints header debug information.

    Args:
      file_header (wmf_header): file header.
    """
        value_string = '0x{0:04x}'.format(file_header.file_type)
        self._DebugPrintValue('File type', value_string)

        value_string = '{0:d}'.format(file_header.record_size)
        self._DebugPrintValue('Record size', value_string)

        value_string = '{0:d}'.format(file_header.format_version)
        self._DebugPrintValue('Format version', value_string)

        value_string = '{0:d}'.format(file_header.file_size_lower)
        self._DebugPrintValue('File size lower', value_string)

        value_string = '{0:d}'.format(file_header.file_size_upper)
        self._DebugPrintValue('File size upper', value_string)

        value_string = '{0:d}'.format(file_header.maximum_number_of_objects)
        self._DebugPrintValue('Maximum number of object', value_string)

        value_string = '{0:d}'.format(file_header.largest_record_size)
        self._DebugPrintValue('Largest record size', value_string)

        value_string = '{0:d}'.format(file_header.number_of_records)
        self._DebugPrintValue('Number of records', value_string)

        self._DebugPrintText('\n')

    def _DebugPrintPlaceable(self, placeable):
        """Prints placeable debug information.

    Args:
      placeable (wmf_placeable): placeable.
    """
        value_string = '0x{0:08x}'.format(placeable.signature)
        self._DebugPrintValue('Signature', value_string)

        value_string = '0x{0:04x}'.format(placeable.resource_handle)
        self._DebugPrintValue('Resource handle', value_string)

        self._DebugPrintData('Bounding box', placeable.bounding_box)

        value_string = '{0:d}'.format(placeable.number_of_units_per_inch)
        self._DebugPrintValue('Number of units per inch', value_string)

        value_string = '0x{0:08x}'.format(placeable.unknown1)
        self._DebugPrintValue('Unknown1', value_string)

        value_string = '0x{0:04x}'.format(placeable.checksum)
        self._DebugPrintValue('Checksum', value_string)

        self._DebugPrintText('\n')

    def _DebugPrintRecordHeader(self, record_header):
        """Prints record header debug information.

    Args:
      record_header (wmf_record_header): record header.
    """
        value_string = '{0:d} ({1:d} bytes)'.format(
            record_header.record_size, record_header.record_size * 2)
        self._DebugPrintValue('Record size', value_string)

        record_type_string = self._RECORD_TYPE.GetName(
            record_header.record_type)
        value_string = '0x{0:04x} ({1:s})'.format(
            record_header.record_type, record_type_string or 'UNKNOWN')
        self._DebugPrintValue('Record type', value_string)

        self._DebugPrintText('\n')

    def _ReadHeader(self, file_object):
        """Reads a header.

    Args:
      file_object (file): file-like object.

    Raises:
      ParseError: if the header cannot be read.
    """
        file_offset = file_object.tell()
        file_header = self._ReadStructure(file_object, file_offset,
                                          self._HEADER_SIZE, self._HEADER,
                                          'header')

        if self._debug:
            self._DebugPrintHeader(file_header)

        if file_header.file_type not in (1, 2):
            raise errors.ParseError('Unsupported file type: {0:d}'.format(
                file_header.file_type))

        if file_header.record_size != 9:
            raise errors.ParseError('Unsupported record size: {0:d}'.format(
                file_header.record_size))

    def _ReadPlaceable(self, file_object):
        """Reads a placeable.

    Args:
      file_object (file): file-like object.

    Raises:
      ParseError: if the placeable cannot be read.
    """
        file_offset = file_object.tell()
        placeable = self._ReadStructure(file_object, file_offset,
                                        self._PLACEABLE_SIZE, self._PLACEABLE,
                                        'placeable')

        if self._debug:
            self._DebugPrintPlaceable(placeable)

    def _ReadRecord(self, file_object, file_offset):
        """Reads a record.

    Args:
      file_object (file): file-like object.
      file_offset (int): offset of the data relative to the start of
          the file-like object.

    Raises:
      ParseError: if the record cannot be read.
    """
        record_header = self._ReadStructure(file_object, file_offset,
                                            self._RECORD_HEADER_SIZE,
                                            self._RECORD_HEADER,
                                            'record header')

        if self._debug:
            self._DebugPrintRecordHeader(record_header)

        record_size = record_header.record_size * 2

        data_offset = file_offset + self._RECORD_HEADER_SIZE
        data_size = record_size - self._RECORD_HEADER_SIZE

        if self._debug:
            self._ReadRecordData(file_object, record_header.record_type,
                                 data_size)

        return Record(record_header.record_type, record_size, data_offset,
                      data_size)

    def _ReadRecordData(self, file_object, record_type, data_size):
        """Reads a record.

    Args:
      file_object (file): file-like object.
      record_type (int): record type.
      data_size (int): size of the record data.

    Raises:
      ParseError: if the record cannot be read.
    """
        record_data = file_object.read(data_size)

        if self._debug and data_size > 0:
            self._DebugPrintData('Record data', record_data)

        # TODO: use lookup dict with callback.
        data_type_map = self._WMF_RECORD_DATA_STRUCT_TYPES.get(
            record_type, None)
        if not data_type_map:
            return

        try:
            record = data_type_map.MapByteStream(record_data)
        except dtfabric_errors.MappingError as exception:
            raise errors.ParseError(
                ('Unable to parse record data with error: {0:s}'
                 ).format(exception))

        if self._debug:
            if record_type == 0x0103:
                map_mode_string = self._MAP_MODE.GetName(record.map_mode)
                value_string = '0x{0:04x} ({1:s})'.format(
                    record.map_mode, map_mode_string or 'UNKNOWN')
                self._DebugPrintValue('Map mode', value_string)

            elif record_type == 0x0107:
                stretch_mode_string = self._MAP_MODE.GetName(
                    record.stretch_mode)
                value_string = '0x{0:04x} ({1:s})'.format(
                    record.stretch_mode, stretch_mode_string or 'UNKNOWN')
                self._DebugPrintValue('Stretch mode', value_string)

            elif record_type == 0x0127:
                value_string = '{0:d}'.format(
                    record.number_of_saved_device_context)
                self._DebugPrintValue('Number of saved device context',
                                      value_string)

            elif record_type in (0x020b, 0x020c):
                value_string = '{0:d}'.format(record.x_coordinate)
                self._DebugPrintValue('X coordinate', value_string)

                value_string = '{0:d}'.format(record.y_coordinate)
                self._DebugPrintValue('Y coordinate', value_string)

            elif record_type == 0x0b41:
                raster_operation_string = self._WMF_RASTER_OPERATIONS.get(
                    record.raster_operation, 'UNKNOWN')
                value_string = '0x{0:08x} ({1:s})'.format(
                    record.raster_operation, raster_operation_string)
                self._DebugPrintValue('Raster operation', value_string)

                value_string = '{0:d}'.format(record.source_height)
                self._DebugPrintValue('Source height', value_string)

                value_string = '{0:d}'.format(record.source_width)
                self._DebugPrintValue('Source width', value_string)

                value_string = '{0:d}'.format(record.source_x_coordinate)
                self._DebugPrintValue('Source X coordinate', value_string)

                value_string = '{0:d}'.format(record.source_y_coordinate)
                self._DebugPrintValue('Source Y coordinate', value_string)

                value_string = '{0:d}'.format(record.destination_height)
                self._DebugPrintValue('Destination height', value_string)

                value_string = '{0:d}'.format(record.destination_width)
                self._DebugPrintValue('Destination width', value_string)

                value_string = '{0:d}'.format(record.destination_x_coordinate)
                self._DebugPrintValue('Destination X coordinate', value_string)

                value_string = '{0:d}'.format(record.destination_y_coordinate)
                self._DebugPrintValue('Destination Y coordinate', value_string)

            self._DebugPrintText('\n')

    def ReadFileObject(self, file_object):
        """Reads a Windows Metafile Format (WMF) file-like object.

    Args:
      file_object (file): file-like object.

    Raises:
      ParseError: if the file cannot be read.
    """
        try:
            signature = file_object.read(4)
            file_object.seek(-4, os.SEEK_CUR)
        except IOError as exception:
            raise errors.ParseError(
                'Unable to read file signature with error: {0!s}'.format(
                    exception))

        if signature == self._WMF_PLACEABLE_SIGNATURE:
            self._ReadPlaceable(file_object)

        self._ReadHeader(file_object)

        file_offset = file_object.tell()
        while file_offset < self._file_size:
            record = self._ReadRecord(file_object, file_offset)

            file_offset += record.size
Example #9
0
class DtFabricHelperTest(test_lib.BaseTestCase):
  """dtFabric format definition helper mix-in tests."""

  # pylint: disable=protected-access

  _DATA_TYPE_FABRIC_DEFINITION = b"""\
name: uint32
type: integer
attributes:
  format: unsigned
  size: 4
  units: bytes
---
name: point3d
type: structure
attributes:
  byte_order: little-endian
members:
- name: x
  data_type: uint32
- name: y
  data_type: uint32
- name: z
  data_type: uint32
---
name: shape3d
type: structure
attributes:
  byte_order: little-endian
members:
- name: number_of_points
  data_type: uint32
- name: points
  type: sequence
  element_data_type: point3d
  number_of_elements: shape3d.number_of_points
"""

  _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric(
      yaml_definition=_DATA_TYPE_FABRIC_DEFINITION)

  _POINT3D = _DATA_TYPE_FABRIC.CreateDataTypeMap('point3d')

  _POINT3D_SIZE = _POINT3D.GetByteSize()

  _SHAPE3D = _DATA_TYPE_FABRIC.CreateDataTypeMap('shape3d')

  def testFormatPackedIPv4Address(self):
    """Tests the _FormatPackedIPv4Address function."""
    test_helper = dtfabric_helper.DtFabricHelper()

    ip_address = test_helper._FormatPackedIPv4Address([0xc0, 0xa8, 0xcc, 0x62])
    self.assertEqual(ip_address, '192.168.204.98')

  def testFormatPackedIPv6Address(self):
    """Tests the _FormatPackedIPv6Address function."""
    test_helper = dtfabric_helper.DtFabricHelper()

    ip_address = test_helper._FormatPackedIPv6Address([
        0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x42, 0x83, 0x29])
    self.assertEqual(ip_address, '2001:0db8:0000:0000:0000:ff00:0042:8329')

  # TODO: add tests for _GetDataTypeMap

  def testReadData(self):
    """Tests the _ReadData function."""
    test_helper = dtfabric_helper.DtFabricHelper()

    file_object = io.BytesIO(
        b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')

    test_helper._ReadData(file_object, 0, self._POINT3D_SIZE)

    # Test with missing file-like object.
    with self.assertRaises(ValueError):
      test_helper._ReadData(None, 0, self._POINT3D_SIZE)

    # Test with file-like object with insufficient data.
    file_object = io.BytesIO(
        b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00')

    with self.assertRaises(errors.ParseError):
      test_helper._ReadData(file_object, 0, self._POINT3D_SIZE)

    # Test with file-like object that raises an IOError.
    file_object = ErrorBytesIO(
        b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')

    with self.assertRaises(errors.ParseError):
      test_helper._ReadData(file_object, 0, self._POINT3D_SIZE)

  # TODO: add tests for _ReadDefinitionFile

  def testReadStructureFromByteStream(self):
    """Tests the _ReadStructureFromByteStream function."""
    test_helper = dtfabric_helper.DtFabricHelper()

    test_helper._ReadStructureFromByteStream(
        b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0,
        self._POINT3D)

    # Test with missing byte stream.
    with self.assertRaises(ValueError):
      test_helper._ReadStructureFromByteStream(None, 0, self._POINT3D)

    # Test with missing data map type.
    with self.assertRaises(ValueError):
      test_helper._ReadStructureFromByteStream(
          b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, None)

    # Test with data type map that raises an dtfabric.MappingError.
    data_type_map = ErrorDataTypeMap(None)

    with self.assertRaises(errors.ParseError):
      test_helper._ReadStructureFromByteStream(
          b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0,
          data_type_map)

  def testReadStructureFromFileObject(self):
    """Tests the _ReadStructureFromFileObject function."""
    test_helper = dtfabric_helper.DtFabricHelper()

    file_object = io.BytesIO(
        b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')

    test_helper._ReadStructureFromFileObject(file_object, 0, self._POINT3D)

    file_object = io.BytesIO(
        b'\x03\x00\x00\x00'
        b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
        b'\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00'
        b'\x06\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00')

    test_helper._ReadStructureFromFileObject(file_object, 0, self._SHAPE3D)
Example #10
0
class ChromeCacheParser(object):
    """Chrome Cache parser."""

    _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join(os.path.dirname(__file__),
                                                     'chrome_cache.yaml')

    with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object:
        _DATA_TYPE_FABRIC_DEFINITION = file_object.read()

    _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric(
        yaml_definition=_DATA_TYPE_FABRIC_DEFINITION)

    _UINT32LE = _DATA_TYPE_FABRIC.CreateDataTypeMap('uint32le')

    _UINT32LE_SIZE = _UINT32LE.GetByteSize()

    def __init__(self, debug=False, output_writer=None):
        """Initializes a Chrome Cache parser.

    Args:
      debug (Optional[bool]): True if debug information should be written.
      output_writer (Optional[OutputWriter]): output writer.
    """
        super(ChromeCacheParser, self).__init__()
        self._debug = debug
        self._output_writer = output_writer

    def ParseDirectory(self, path):
        """Parses a Chrome Cache directory.

    Args:
      path (str): path of the directory.

    Raises:
      ParseError: if the directory cannot be read.
    """
        index_file_path = os.path.join(path, 'index')
        if not os.path.exists(index_file_path):
            raise errors.ParseError(
                'Missing index file: {0:s}'.format(index_file_path))

        index_file = IndexFile(debug=self._debug,
                               output_writer=self._output_writer)
        index_file.Open(index_file_path)

        data_block_files = {}
        have_all_data_block_files = True
        for cache_address in iter(index_file.index_table.values()):
            if cache_address.filename not in data_block_files:
                data_block_file_path = os.path.join(path,
                                                    cache_address.filename)

                if not os.path.exists(data_block_file_path):
                    logging.error('Missing data block file: {0:s}'.format(
                        data_block_file_path))
                    have_all_data_block_files = False

                else:
                    data_block_file = DataBlockFile(
                        debug=self._debug, output_writer=self._output_writer)
                    data_block_file.Open(data_block_file_path)

                    data_block_files[cache_address.filename] = data_block_file

        if have_all_data_block_files:
            # TODO: read the cache entries from the data block files
            for cache_address in iter(index_file.index_table.values()):
                cache_address_chain_length = 0
                while cache_address.value != 0x00000000:
                    if cache_address_chain_length >= 64:
                        logging.error(
                            'Maximum allowed cache address chain length reached.'
                        )
                        break

                    data_file = data_block_files.get(cache_address.filename,
                                                     None)
                    if not data_file:
                        logging.warning(
                            'Cache address: 0x{0:08x} missing filename.'.
                            format(cache_address.value))
                        break

                    # print('Cache address\t: {0:s}'.format(
                    #     cache_address.GetDebugString()))
                    cache_entry = data_file.ReadCacheEntry(
                        cache_address.block_offset)

                    try:
                        cache_entry_key = cache_entry.key.decode('ascii')
                    except UnicodeDecodeError:
                        logging.warning((
                            'Unable to decode cache entry key at cache address: '
                            '0x{0:08x}. Characters that cannot be decoded will be '
                            'replaced with "?" or "\\ufffd".').format(
                                cache_address.value))
                        cache_entry_key = cache_entry.key.decode(
                            'ascii', errors='replace')

                    # TODO: print('Url\t\t: {0:s}'.format(cache_entry_key))
                    _ = cache_entry_key

                    date_string = (datetime.datetime(1601, 1, 1) +
                                   datetime.timedelta(
                                       microseconds=cache_entry.creation_time))

                    # print('Creation time\t: {0!s}'.format(date_string))

                    # print('')

                    print('{0!s}\t{1:s}'.format(date_string, cache_entry.key))

                    cache_address = cache_entry.next
                    cache_address_chain_length += 1

        for data_block_file in iter(data_block_files.values()):
            data_block_file.Close()

        index_file.Close()

        if not have_all_data_block_files:
            raise errors.ParseError('Missing data block files.')

    def ParseFile(self, path):
        """Parses a Chrome Cache file.

    Args:
      path (str): path of the file.

    Raises:
      ParseError: if the file cannot be read.
    """
        with open(path, 'rb') as file_object:
            signature_data = file_object.read(self._UINT32LE_SIZE)

            try:
                signature = self._UINT32LE.MapByteStream(signature_data)
            except dtfabric_errors.MappingError as exception:
                raise errors.ParseError(
                    'Unable to signature with error: {0!s}'.format(exception))

            if signature not in (DataBlockFile.SIGNATURE, IndexFile.SIGNATURE):
                raise errors.ParseError(
                    'Unsupported signature: 0x{0:08x}'.format(signature))

            if signature == DataBlockFile.SIGNATURE:
                chrome_cache_file = DataBlockFile(
                    debug=self._debug, output_writer=self._output_writer)

            elif signature == IndexFile.SIGNATURE:
                chrome_cache_file = IndexFile(
                    debug=self._debug, output_writer=self._output_writer)

            chrome_cache_file.ReadFileObject(file_object)
Example #11
0
class FakeWinRegistryValue(interface.WinRegistryValue):
  """Fake implementation of a Windows Registry value."""

  _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join(
      os.path.dirname(__file__), 'dtfabric.yaml')

  with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object:
    _DATA_TYPE_FABRIC_DEFINITION = file_object.read()

  _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric(
      yaml_definition=_DATA_TYPE_FABRIC_DEFINITION)

  _INT32_BIG_ENDIAN = _DATA_TYPE_FABRIC.CreateDataTypeMap('int32be')
  _INT32_LITTLE_ENDIAN = _DATA_TYPE_FABRIC.CreateDataTypeMap('int32le')
  _INT64_LITTLE_ENDIAN = _DATA_TYPE_FABRIC.CreateDataTypeMap('int64le')

  def __init__(self, name, data=b'', data_type=definitions.REG_NONE, offset=0):
    """Initializes a Windows Registry value.

    Args:
      name (str): name of the Windows Registry value.
      data (Optional[bytes]): value data.
      data_type (Optional[int]): value data type.
      offset (Optional[int]): offset of the value within the Windows Registry
          file.
    """
    super(FakeWinRegistryValue, self).__init__()
    self._data = data
    self._data_type = data_type
    self._data_size = len(data)
    self._name = name
    self._offset = offset

  @property
  def data(self):
    """bytes: value data as a byte string."""
    return self._data

  @property
  def data_type(self):
    """int: data type."""
    return self._data_type

  @property
  def name(self):
    """str: name of the value."""
    return self._name

  @property
  def offset(self):
    """int: offset of the value within the Windows Registry file."""
    return self._offset

  def GetDataAsObject(self):
    """Retrieves the data as an object.

    Returns:
      object: data as a Python type or None if not available.

    Raises:
      WinRegistryValueError: if the value data cannot be read.
    """
    if not self._data:
      return None

    if self._data_type in self._STRING_VALUE_TYPES:
      try:
        return self._data.decode('utf-16-le')

      # AttributeError is raised when self._data has no decode method.
      except AttributeError as exception:
        raise errors.WinRegistryValueError((
            'Unsupported data type: {0!s} of value: {1!s} with error: '
            '{2!s}').format(type(self._data), self._name, exception))

      except UnicodeError as exception:
        raise errors.WinRegistryValueError(
            'Unable to decode data of value: {0!s} with error: {1!s}'.format(
                self._name, exception))

    elif (self._data_type == definitions.REG_DWORD and
          self._data_size == 4):
      return self._INT32_LITTLE_ENDIAN.MapByteStream(self._data)

    elif (self._data_type == definitions.REG_DWORD_BIG_ENDIAN and
          self._data_size == 4):
      return self._INT32_BIG_ENDIAN.MapByteStream(self._data)

    elif (self._data_type == definitions.REG_QWORD and
          self._data_size == 8):
      return self._INT64_LITTLE_ENDIAN.MapByteStream(self._data)

    elif self._data_type == definitions.REG_MULTI_SZ:
      try:
        utf16_string = self._data.decode('utf-16-le')
        # TODO: evaluate the use of filter here is appropriate behavior.
        return list(filter(None, utf16_string.split('\x00')))

      # AttributeError is raised when self._data has no decode method.
      except AttributeError as exception:
        raise errors.WinRegistryValueError((
            'Unsupported data type: {0!s} of value: {1!s} with error: '
            '{2!s}').format(type(self._data), self._name, exception))

      except UnicodeError as exception:
        raise errors.WinRegistryValueError(
            'Unable to read data from value: {0!s} with error: {1!s}'.format(
                self._name, exception))

    return self._data
Example #12
0
class GzipMember(data_format.DataFormat):
    """Gzip member.

  Gzip files have no index of members, so each member must be read
  sequentially before metadata and random seeks are possible. This class
  provides caching of gzip member data during the initial read of each member.

  Attributes:
    comment (str): comment stored in the member.
    member_end_offset (int): offset to the end of the member in the parent file
        object.
    member_start_offset (int): offset to the start of the member in the parent
        file object.
    operating_system (int): type of file system on which the compression
        took place.
    original_filename (str): original filename of the uncompressed file.
    uncompressed_data_offset (int): offset of the start of the uncompressed
        data in this member relative to the whole gzip file's uncompressed data.
    uncompressed_data_size (int): total size of the data in this gzip member
        after decompression.
  """

    _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join(os.path.dirname(__file__),
                                                     'gzipfile.yaml')

    with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object:
        _DATA_TYPE_FABRIC_DEFINITION = file_object.read()

    _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric(
        yaml_definition=_DATA_TYPE_FABRIC_DEFINITION)

    _MEMBER_HEADER = _DATA_TYPE_FABRIC.CreateDataTypeMap('gzip_member_header')

    _MEMBER_HEADER_SIZE = _MEMBER_HEADER.GetByteSize()

    _MEMBER_FOOTER = _DATA_TYPE_FABRIC.CreateDataTypeMap('gzip_member_footer')

    _MEMBER_FOOTER_SIZE = _MEMBER_FOOTER.GetByteSize()

    _UINT16LE = _DATA_TYPE_FABRIC.CreateDataTypeMap('uint16le')

    _UINT16LE_SIZE = _UINT16LE.GetByteSize()

    _CSTRING = _DATA_TYPE_FABRIC.CreateDataTypeMap('cstring')

    _GZIP_SIGNATURE = 0x8b1f

    _COMPRESSION_METHOD_DEFLATE = 8

    _FLAG_FTEXT = 0x01
    _FLAG_FHCRC = 0x02
    _FLAG_FEXTRA = 0x04
    _FLAG_FNAME = 0x08
    _FLAG_FCOMMENT = 0x10

    # The maximum size of the uncompressed data cache.
    _UNCOMPRESSED_DATA_CACHE_SIZE = 2 * 1024 * 1024

    def __init__(self, file_object, member_start_offset,
                 uncompressed_data_offset):
        """Initializes a gzip member.

    Args:
      file_object (FileIO): file-like object, containing the gzip member.
      member_start_offset (int): offset to the beginning of the gzip member
          in the containing file.
      uncompressed_data_offset (int): offset of the start of the uncompressed
          data in this member relative to the whole gzip file's uncompressed
          data.
    """
        self._cache = b''
        # End offset of the cached uncompressed data of the member.
        self._cache_end_offset = None
        # Start offset of the cached uncompressed data of the member.
        self._cache_start_offset = None

        self.comment = None
        self.modification_time = None
        self.operating_system = None
        self.original_filename = None

        file_size = file_object.get_size()

        file_object.seek(member_start_offset, os.SEEK_SET)
        self._ReadMemberHeader(file_object)

        data_offset = 0
        uncompressed_data_size = 0

        compressed_data_offset = file_object.get_offset()
        decompressor_state = _GzipDecompressorState(compressed_data_offset)

        # Read the member data to determine the uncompressed data size and
        # the offset of the member footer.
        file_offset = compressed_data_offset
        while file_offset < file_size:
            data_offset += uncompressed_data_size

            decompressed_data = decompressor_state.Read(file_object)
            uncompressed_data_size += len(decompressed_data)

            # Note that unused data will be set when the decompressor reads beyond
            # the end of the compressed data stream.
            unused_data = decompressor_state.GetUnusedData()
            if unused_data:
                file_object.seek(-len(unused_data), os.SEEK_CUR)
                file_offset = file_object.get_offset()
                break

            file_offset = file_object.get_offset()

        # Do not read the the last member footer if it is missing, which is
        # a common corruption scenario.
        if file_offset < file_size:
            self._ReadStructure(file_object, file_offset,
                                self._MEMBER_FOOTER_SIZE, self._MEMBER_FOOTER,
                                'member footer')

        member_end_offset = file_object.get_offset()

        # Initialize the member with data.
        self._file_object = file_object
        self._file_object.seek(member_start_offset, os.SEEK_SET)

        # Cache uncompressed data of gzip files that fit entirely in the cache.
        if (data_offset == 0 and
                uncompressed_data_size < self._UNCOMPRESSED_DATA_CACHE_SIZE):
            self._cache = decompressed_data
            self._cache_start_offset = 0
            self._cache_end_offset = uncompressed_data_size

        # Offset to the beginning of the compressed data in the file object.
        self._compressed_data_start = compressed_data_offset
        self._decompressor_state = _GzipDecompressorState(
            compressed_data_offset)

        # Offset to the start of the member in the parent file object.
        self.member_start_offset = member_start_offset

        # Offset to the end of the member in the parent file object.
        self.member_end_offset = member_end_offset

        # Total size of the data in this gzip member after decompression.
        self.uncompressed_data_size = uncompressed_data_size

        # Offset of the start of the uncompressed data in this member relative to
        # the whole gzip file's uncompressed data.
        self.uncompressed_data_offset = uncompressed_data_offset

    def _ReadMemberHeader(self, file_object):
        """Reads a member header.

    Args:
      file_object (FileIO): file-like object to read from.

    Raises:
      FileFormatError: if the member header cannot be read.
    """
        file_offset = file_object.get_offset()
        member_header = self._ReadStructure(file_object, file_offset,
                                            self._MEMBER_HEADER_SIZE,
                                            self._MEMBER_HEADER,
                                            'member header')

        if member_header.signature != self._GZIP_SIGNATURE:
            raise errors.FileFormatError(
                'Unsupported signature: 0x{0:04x}.'.format(
                    member_header.signature))

        if member_header.compression_method != self._COMPRESSION_METHOD_DEFLATE:
            raise errors.FileFormatError(
                'Unsupported compression method: {0:d}.'.format(
                    member_header.compression_method))

        self.modification_time = member_header.modification_time
        self.operating_system = member_header.operating_system

        if member_header.flags & self._FLAG_FEXTRA:
            file_offset = file_object.get_offset()
            extra_field_data_size = self._ReadStructure(
                file_object, file_offset, self._UINT16LE_SIZE, self._UINT16LE,
                'extra field data size')

            file_object.seek(extra_field_data_size, os.SEEK_CUR)

        if member_header.flags & self._FLAG_FNAME:
            file_offset = file_object.get_offset()
            string_value = self._ReadString(file_object, file_offset,
                                            self._CSTRING, 'original filename')

            self.original_filename = string_value.rstrip('\x00')

        if member_header.flags & self._FLAG_FCOMMENT:
            file_offset = file_object.get_offset()
            string_value = self._ReadString(file_object, file_offset,
                                            self._CSTRING, 'comment')

            self.comment = string_value.rstrip('\x00')

        if member_header.flags & self._FLAG_FHCRC:
            file_object.read(2)

    def _ResetDecompressorState(self):
        """Resets the state of the internal decompression object."""
        self._decompressor_state = _GzipDecompressorState(
            self._compressed_data_start)

    def FlushCache(self):
        """Empties the cache that holds cached decompressed data."""
        self._cache = b''
        self._cache_start_offset = None
        self._cache_end_offset = None
        self._ResetDecompressorState()

    def GetCacheSize(self):
        """Determines the size of the uncompressed cached data.

    Returns:
      int: number of cached bytes.
    """
        if not self._cache_start_offset or not self._cache_end_offset:
            return 0
        return self._cache_end_offset - self._cache_start_offset

    def IsCacheFull(self):
        """Checks whether the uncompressed data cache is full.

    Returns:
      bool: True if the cache is full.
    """
        return self.GetCacheSize() >= self._UNCOMPRESSED_DATA_CACHE_SIZE

    def ReadAtOffset(self, offset, size=None):
        """Reads a byte string from the gzip member at the specified offset.

    The function will read a byte string of the specified size or
    all of the remaining data if no size was specified.

    Args:
      offset (int): offset within the uncompressed data in this member to
        read from.
      size (Optional[int]): maximum number of bytes to read, where None
          represents all remaining data, to a maximum of the uncompressed
          cache size.

    Returns:
      bytes: data read.

    Raises:
      IOError: if the read failed.
      ValueError: if a negative read size or offset is specified.
    """
        if size is not None and size < 0:
            raise ValueError('Invalid size value {0!s}'.format(size))

        if offset < 0:
            raise ValueError('Invalid offset value {0!s}'.format(offset))

        if size == 0 or offset >= self.uncompressed_data_size:
            return b''

        if self._cache_start_offset is None:
            self._LoadDataIntoCache(self._file_object, offset)

        if offset > self._cache_end_offset or offset < self._cache_start_offset:
            self.FlushCache()
            self._LoadDataIntoCache(self._file_object, offset)

        cache_offset = offset - self._cache_start_offset
        if not size:
            return self._cache[cache_offset:]

        data_end_offset = cache_offset + size

        if data_end_offset > self._cache_end_offset:
            return self._cache[cache_offset:]

        return self._cache[cache_offset:data_end_offset]

    def _LoadDataIntoCache(self, file_object, minimum_offset):
        """Reads and decompresses the data in the member.

    This function already loads as much data as possible in the cache, up to
    UNCOMPRESSED_DATA_CACHE_SIZE bytes.

    Args:
      file_object (FileIO): file-like object.
      minimum_offset (int): offset into this member's uncompressed data at
          which the cache should start.
    """
        # Decompression can only be performed from beginning to end of the stream.
        # So, if data before the current position of the decompressor in the stream
        # is required, it's necessary to throw away the current decompression
        # state and start again.
        if minimum_offset < self._decompressor_state.uncompressed_offset:
            self._ResetDecompressorState()

        while not self.IsCacheFull():
            decompressed_data = self._decompressor_state.Read(file_object)
            # Note that decompressed_data will be empty if there is no data left
            # to read and decompress.
            if not decompressed_data:
                break

            decompressed_data_length = len(decompressed_data)
            decompressed_end_offset = self._decompressor_state.uncompressed_offset
            decompressed_start_offset = (decompressed_end_offset -
                                         decompressed_data_length)

            data_to_add = decompressed_data
            added_data_start_offset = decompressed_start_offset

            if decompressed_start_offset < minimum_offset:
                data_to_add = None

            if decompressed_start_offset < minimum_offset < decompressed_end_offset:
                data_add_offset = decompressed_end_offset - minimum_offset
                data_to_add = decompressed_data[-data_add_offset:]
                added_data_start_offset = decompressed_end_offset - data_add_offset

            if not self.IsCacheFull() and data_to_add:
                self._cache = b''.join([self._cache, data_to_add])
                if self._cache_start_offset is None:
                    self._cache_start_offset = added_data_start_offset
                if self._cache_end_offset is None:
                    self._cache_end_offset = self._cache_start_offset + len(
                        data_to_add)
                else:
                    self._cache_end_offset += len(data_to_add)

            # If there's no more data in the member, the unused_data value is
            # populated in the decompressor. When this situation arises, we rewind
            # to the end of the compressed_data section.
            unused_data = self._decompressor_state.GetUnusedData()
            if unused_data:
                seek_offset = -len(unused_data)
                file_object.seek(seek_offset, os.SEEK_CUR)
                self._ResetDecompressorState()
                break
class BinaryDataFormatTest(test_lib.BaseTestCase):
    """Binary data format tests."""

    # pylint: disable=protected-access

    _DATA_TYPE_FABRIC_DEFINITION = b"""\
name: uint32
type: integer
attributes:
  format: unsigned
  size: 4
  units: bytes
---
name: point3d
type: structure
attributes:
  byte_order: little-endian
members:
- name: x
  data_type: uint32
- name: y
  data_type: uint32
- name: z
  data_type: uint32
---
name: shape3d
type: structure
attributes:
  byte_order: little-endian
members:
- name: number_of_points
  data_type: uint32
- name: points
  type: sequence
  element_data_type: point3d
  number_of_elements: shape3d.number_of_points
"""

    _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric(
        yaml_definition=_DATA_TYPE_FABRIC_DEFINITION)

    _POINT3D = _DATA_TYPE_FABRIC.CreateDataTypeMap('point3d')

    _POINT3D_SIZE = _POINT3D.GetByteSize()

    _SHAPE3D = _DATA_TYPE_FABRIC.CreateDataTypeMap('shape3d')

    def testDebugPrintData(self):
        """Tests the _DebugPrintData function."""
        output_writer = test_lib.TestOutputWriter()
        test_format = data_format.BinaryDataFormat(output_writer=output_writer)

        data = b'\x00\x01\x02\x03\x04\x05\x06'
        test_format._DebugPrintData('Description', data)

        expected_output = [
            'Description:\n',
            ('0x00000000  00 01 02 03 04 05 06                              '
             '.......\n\n')
        ]
        self.assertEqual(output_writer.output, expected_output)

    def testDebugPrintDecimalValue(self):
        """Tests the _DebugPrintDecimalValue function."""
        output_writer = test_lib.TestOutputWriter()
        test_format = data_format.BinaryDataFormat(output_writer=output_writer)

        test_format._DebugPrintDecimalValue('Description', 1)

        expected_output = ['Description\t\t\t\t\t\t\t\t: 1\n']
        self.assertEqual(output_writer.output, expected_output)

    # TODO add tests for _DebugPrintFiletimeValue
    # TODO add tests for _DebugPrintPosixTimeValue

    def testDebugPrintValue(self):
        """Tests the _DebugPrintValue function."""
        output_writer = test_lib.TestOutputWriter()
        test_format = data_format.BinaryDataFormat(output_writer=output_writer)

        test_format._DebugPrintValue('Description', 'Value')

        expected_output = ['Description\t\t\t\t\t\t\t\t: Value\n']
        self.assertEqual(output_writer.output, expected_output)

    def testDebugPrintText(self):
        """Tests the _DebugPrintText function."""
        output_writer = test_lib.TestOutputWriter()
        test_format = data_format.BinaryDataFormat(output_writer=output_writer)

        test_format._DebugPrintText('Text')

        expected_output = ['Text']
        self.assertEqual(output_writer.output, expected_output)

    def testFormatDataInHexadecimal(self):
        """Tests the _FormatDataInHexadecimal function."""
        test_format = data_format.BinaryDataFormat()

        data = b'\x00\x01\x02\x03\x04\x05\x06'
        expected_formatted_data = (
            '0x00000000  00 01 02 03 04 05 06                              '
            '.......\n'
            '\n')
        formatted_data = test_format._FormatDataInHexadecimal(data)
        self.assertEqual(formatted_data, expected_formatted_data)

        data = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09'
        expected_formatted_data = (
            '0x00000000  00 01 02 03 04 05 06 07  08 09                    '
            '..........\n'
            '\n')
        formatted_data = test_format._FormatDataInHexadecimal(data)
        self.assertEqual(formatted_data, expected_formatted_data)

        data = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
        expected_formatted_data = (
            '0x00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  '
            '................\n'
            '\n')
        formatted_data = test_format._FormatDataInHexadecimal(data)
        self.assertEqual(formatted_data, expected_formatted_data)

        data = (
            b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
            b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
            b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
        )
        expected_formatted_data = (
            '0x00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  '
            '................\n'
            '...\n'
            '0x00000020  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  '
            '................\n'
            '\n')
        formatted_data = test_format._FormatDataInHexadecimal(data)
        self.assertEqual(formatted_data, expected_formatted_data)

    def testFormatPackedIPv4Address(self):
        """Tests the _FormatPackedIPv4Address function."""
        test_format = data_format.BinaryDataFormat()

        ip_address = test_format._FormatPackedIPv4Address(
            [0xc0, 0xa8, 0xcc, 0x62])
        self.assertEqual(ip_address, '192.168.204.98')

    def testFormatPackedIPv6Address(self):
        """Tests the _FormatPackedIPv6Address function."""
        test_format = data_format.BinaryDataFormat()

        ip_address = test_format._FormatPackedIPv6Address([
            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
            0x00, 0x00, 0x42, 0x83, 0x29
        ])
        self.assertEqual(ip_address, '2001:0db8:0000:0000:0000:ff00:0042:8329')

    # TODO: add tests for _GetDataTypeMap

    def testReadData(self):
        """Tests the _ReadData function."""
        output_writer = test_lib.TestOutputWriter()
        test_format = data_format.BinaryDataFormat(debug=True,
                                                   output_writer=output_writer)

        file_object = io.BytesIO(
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')

        test_format._ReadData(file_object, 0, self._POINT3D_SIZE, 'point3d')

        # Test with missing file-like object.
        with self.assertRaises(ValueError):
            test_format._ReadData(None, 0, self._POINT3D_SIZE, 'point3d')

        # Test with file-like object with insufficient data.
        file_object = io.BytesIO(
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00')

        with self.assertRaises(errors.ParseError):
            test_format._ReadData(file_object, 0, self._POINT3D_SIZE,
                                  'point3d')

        # Test with file-like object that raises an IOError.
        file_object = ErrorBytesIO(
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')

        with self.assertRaises(errors.ParseError):
            test_format._ReadData(file_object, 0, self._POINT3D_SIZE,
                                  'point3d')

    # TODO: add tests for _ReadDefinitionFile

    def testReadStructure(self):
        """Tests the _ReadStructure function."""
        output_writer = test_lib.TestOutputWriter()
        test_format = data_format.BinaryDataFormat(debug=True,
                                                   output_writer=output_writer)

        file_object = io.BytesIO(
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')

        test_format._ReadStructure(file_object, 0, self._POINT3D_SIZE,
                                   self._POINT3D, 'point3d')

    def testReadStructureFromByteStream(self):
        """Tests the _ReadStructureFromByteStream function."""
        output_writer = test_lib.TestOutputWriter()
        test_format = data_format.BinaryDataFormat(debug=True,
                                                   output_writer=output_writer)

        test_format._ReadStructureFromByteStream(
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0,
            self._POINT3D, 'point3d')

        # Test with missing byte stream.
        with self.assertRaises(ValueError):
            test_format._ReadStructureFromByteStream(None, 0, self._POINT3D,
                                                     'point3d')

        # Test with missing data map type.
        with self.assertRaises(ValueError):
            test_format._ReadStructureFromByteStream(
                b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, None,
                'point3d')

        # Test with data type map that raises an dtfabric.MappingError.
        data_type_map = ErrorDataTypeMap(None)

        with self.assertRaises(errors.ParseError):
            test_format._ReadStructureFromByteStream(
                b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0,
                data_type_map, 'point3d')

    def testReadStructureFromFileObject(self):
        """Tests the _ReadStructureFromFileObject function."""
        output_writer = test_lib.TestOutputWriter()
        test_format = data_format.BinaryDataFormat(debug=True,
                                                   output_writer=output_writer)

        file_object = io.BytesIO(
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')

        test_format._ReadStructureFromFileObject(file_object, 0, self._POINT3D,
                                                 "point3d")

        file_object = io.BytesIO(
            b'\x03\x00\x00\x00'
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
            b'\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00'
            b'\x06\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00')

        test_format._ReadStructureFromFileObject(file_object, 0, self._SHAPE3D,
                                                 "shape3d")
Example #14
0
class WindowsTaskSchedularJobFile(data_format.BinaryDataFile):
    """Windows Task Scheduler job (.job) file."""

    _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join(os.path.dirname(__file__),
                                                     'job.yaml')

    with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object:
        _DATA_TYPE_FABRIC_DEFINITION = file_object.read()

    # TODO: add format definition.
    # https://msdn.microsoft.com/en-us/library/cc248285.aspx

    # TODO: add job signature
    # https://msdn.microsoft.com/en-us/library/cc248299.aspx

    _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric(
        yaml_definition=_DATA_TYPE_FABRIC_DEFINITION)

    _FIXED_LENGTH_DATA_SECTION = _DATA_TYPE_FABRIC.CreateDataTypeMap(
        'job_fixed_length_data_section')

    _FIXED_LENGTH_DATA_SECTION_SIZE = _FIXED_LENGTH_DATA_SECTION.GetByteSize()

    _VARIABLE_LENGTH_DATA_SECTION = _DATA_TYPE_FABRIC.CreateDataTypeMap(
        'job_variable_length_data_section')

    _DEBUG_INFO_FIXED_LENGTH_DATA_SECTION = [
        ('signature', 'Signature', '_FormatIntegerAsProductVersion'),
        ('format_version', 'Format version', '_FormatIntegerAsDecimal'),
        ('job_identifier', 'Job identifier', '_FormatUUIDAsString'),
        ('application_name_offset', 'Application name offset',
         '_FormatIntegerAsHexadecimal4'),
        ('triggers_offset', 'Triggers offset', '_FormatIntegerAsHexadecimal4'),
        ('error_retry_count', 'Error retry count', '_FormatIntegerAsDecimal'),
        ('error_retry_interval', 'Error retry interval',
         '_FormatIntegerAsIntervalInMinutes'),
        ('idle_deadline', 'Idle deadline',
         '_FormatIntegerAsIntervalInMinutes'),
        ('idle_wait', 'Idle wait', '_FormatIntegerAsIntervalInMinutes'),
        ('priority', 'Priority', '_FormatIntegerAsHexadecimal8'),
        ('maximum_run_time', 'Maximum run time',
         '_FormatIntegerAsIntervalInMilliseconds'),
        ('exit_code', 'Exit code', '_FormatIntegerAsHexadecimal8'),
        ('status', 'Status', '_FormatIntegerAsHexadecimal8'),
        ('flags', 'Flags', '_FormatIntegerAsHexadecimal8'),
        ('last_run_time', 'Last run time', '_FormatSystemTime')
    ]

    _DEBUG_INFO_TRIGGER = [
        ('size', 'Size', '_FormatIntegerAsDecimal'),
        ('reserved1', 'Reserved1', '_FormatIntegerAsHexadecimal4'),
        ('start_date', 'Start date', '_FormatDate'),
        ('end_date', 'End date', '_FormatDate'),
        ('start_time', 'Start time', '_FormatTime'),
        ('duration', 'Duration', '_FormatIntegerAsIntervalInMinutes'),
        ('interval', 'Interval', '_FormatIntegerAsIntervalInMinutes'),
        ('trigger_flags', 'Trigger flags', '_FormatIntegerAsHexadecimal8'),
        ('trigger_type', 'Trigger type', '_FormatIntegerAsHexadecimal8'),
        ('trigger_arg0', 'Trigger arg0', '_FormatIntegerAsHexadecimal4'),
        ('trigger_arg1', 'Trigger arg1', '_FormatIntegerAsHexadecimal4'),
        ('trigger_arg2', 'Trigger arg2', '_FormatIntegerAsHexadecimal4'),
        ('trigger_padding', 'Trigger padding', '_FormatIntegerAsHexadecimal4'),
        ('trigger_reserved2', 'Trigger reserved2',
         '_FormatIntegerAsHexadecimal4'),
        ('trigger_reserved3', 'Trigger reserved3',
         '_FormatIntegerAsHexadecimal4')
    ]

    _DEBUG_INFO_VARIABLE_LENGTH_DATA_SECTION = [
        ('running_instance_count', 'Running instance count',
         '_FormatIntegerAsDecimal'),
        ('application_name', 'Application name', '_FormatString'),
        ('parameters', 'Parameters', '_FormatString'),
        ('working_directory', 'Working directory', '_FormatString'),
        ('author', 'Author', '_FormatString'),
        ('comment', 'Comment', '_FormatString'),
        ('user_data', 'User data', '_FormatDataStream'),
        ('reserved_data', 'Reserved data', '_FormatDataStream'),
        ('number_of_triggers', 'Number of triggers', '_FormatIntegerAsDecimal')
    ]

    def _FormatDataStream(self, data_stream):
        """Formats a data stream structure

    Args:
      data_stream (job_reserved_data|job_user_data): data stream structure

    Returns:
      str: formatted data stream structure
    """
        # TODO: print data_stream.size on a separate line
        return self._FormatDataInHexadecimal(data_stream.stream)

    def _FormatDate(self, date):
        """Formats a date structure.

    Args:
      date (job_trigger_date): date structure.

    Returns:
      str: formatted date structure.
    """
        return '{0:04d}-{1:02d}-{2:02d}'.format(date.year, date.month,
                                                date.day_of_month)

    def _FormatIntegerAsIntervalInMilliseconds(self, integer):
        """Formats an integer as an interval in milliseconds.

    Args:
      integer (int): integer.

    Returns:
      str: integer formatted as an interval in milliseconds.
    """
        return '{0:d} milliseconds'.format(integer)

    def _FormatIntegerAsIntervalInMinutes(self, integer):
        """Formats an integer as an interval in minutes.

    Args:
      integer (int): integer.

    Returns:
      str: integer formatted as an interval in minutes.
    """
        return '{0:d} minutes'.format(integer)

    def _FormatIntegerAsProductVersion(self, integer):
        """Formats an integer as a product version.

    Args:
      integer (int): integer.

    Returns:
      str: integer formatted as a product version.
    """
        return '0x{0:04x} ({1:d}.{2:d})'.format(integer, (integer >> 8) & 0xff,
                                                integer & 0xff)

    def _FormatString(self, string):
        """Formats a string structure

    Args:
      string (job_string): string structure

    Returns:
      str: formatted string structure
    """
        # TODO: print string.number_of_characters on a separate line
        return '({0:d} bytes) {1:s}'.format(string.number_of_characters * 2,
                                            string.string)

    def _FormatSystemTime(self, systemtime):
        """Formats a SYSTEMTIME structure.

    Args:
      systemtime (system_time): SYSTEMTIME structure.

    Returns:
      str: formatted SYSTEMTIME structure.
    """
        return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:03d}'.format(
            systemtime.year, systemtime.month, systemtime.day_of_month,
            systemtime.hours, systemtime.minutes, systemtime.seconds,
            systemtime.milliseconds)

    def _FormatTime(self, time):
        """Formats a time structure.

    Args:
      time (job_trigger_time): time structure.

    Returns:
      str: formatted time structure.
    """
        return '{0:02d}:{1:02d}'.format(time.hours, time.minutes)

    def _ReadFixedLengthDataSection(self, file_object):
        """Reads the fixed-length data section.

    Args:
      file_object (file): file-like object.

    Raises:
      IOError: if the fixed-length data section cannot be read.
    """
        file_offset = file_object.tell()
        data_section = self._ReadStructure(
            file_object, file_offset, self._FIXED_LENGTH_DATA_SECTION_SIZE,
            self._FIXED_LENGTH_DATA_SECTION, 'fixed-length data section')

        if self._debug:
            self._DebugPrintStructureObject(
                data_section, self._DEBUG_INFO_FIXED_LENGTH_DATA_SECTION)

    def _ReadVariableLengthDataSection(self, file_object):
        """Reads the variable-length data section.

    Args:
      file_object (file): file-like object.

    Raises:
      IOError: if the variable-length data section cannot be read.
    """
        file_offset = file_object.tell()
        data_size = self._file_size - file_offset
        data_section = self._ReadStructure(file_object, file_offset, data_size,
                                           self._VARIABLE_LENGTH_DATA_SECTION,
                                           'variable-length data section')

        if self._debug:
            self._DebugPrintStructureObject(
                data_section, self._DEBUG_INFO_VARIABLE_LENGTH_DATA_SECTION)

            for trigger in data_section.triggers.triggers_array:
                self._DebugPrintStructureObject(trigger,
                                                self._DEBUG_INFO_TRIGGER)

    def ReadFileObject(self, file_object):
        """Reads a Windows Task Scheduler job file-like object.

    Args:
      file_object (file): file-like object.

    Raises:
      ParseError: if the file cannot be read.
    """
        self._ReadFixedLengthDataSection(file_object)
        self._ReadVariableLengthDataSection(file_object)
Example #15
0
class BinaryDataFormatTest(test_lib.BaseTestCase):
    """Binary data format tests."""

    # pylint: disable=protected-access

    _DATA_TYPE_FABRIC_DEFINITION = b"""\
name: uint32
type: integer
attributes:
  format: unsigned
  size: 4
  units: bytes
---
name: point3d
type: structure
attributes:
  byte_order: little-endian
members:
- name: x
  data_type: uint32
- name: y
  data_type: uint32
- name: z
  data_type: uint32
---
name: shape3d
type: structure
attributes:
  byte_order: little-endian
members:
- name: number_of_points
  data_type: uint32
- name: points
  type: sequence
  element_data_type: point3d
  number_of_elements: shape3d.number_of_points
"""

    _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric(
        yaml_definition=_DATA_TYPE_FABRIC_DEFINITION)

    def testDebugPrintData(self):
        """Tests the _DebugPrintData function."""
        output_writer = test_lib.TestOutputWriter()
        test_format = data_format.BinaryDataFormat(output_writer=output_writer)

        data = b'\x00\x01\x02\x03\x04\x05\x06'
        test_format._DebugPrintData('Description', data)

        expected_output = [
            'Description:\n',
            ('0x00000000  00 01 02 03 04 05 06                              '
             '.......\n\n')
        ]
        self.assertEqual(output_writer.output, expected_output)

    def testDebugPrintDecimalValue(self):
        """Tests the _DebugPrintDecimalValue function."""
        output_writer = test_lib.TestOutputWriter()
        test_format = data_format.BinaryDataFormat(output_writer=output_writer)

        test_format._DebugPrintDecimalValue('Description', 1)

        expected_output = ['Description\t\t\t\t\t\t\t\t: 1\n']
        self.assertEqual(output_writer.output, expected_output)

    # TODO add tests for _DebugPrintFiletimeValue

    def testDebugPrintValue(self):
        """Tests the _DebugPrintValue function."""
        output_writer = test_lib.TestOutputWriter()
        test_format = data_format.BinaryDataFormat(output_writer=output_writer)

        test_format._DebugPrintValue('Description', 'Value')

        expected_output = ['Description\t\t\t\t\t\t\t\t: Value\n']
        self.assertEqual(output_writer.output, expected_output)

    def testDebugPrintText(self):
        """Tests the _DebugPrintText function."""
        output_writer = test_lib.TestOutputWriter()
        test_format = data_format.BinaryDataFormat(output_writer=output_writer)

        test_format._DebugPrintText('Text')

        expected_output = ['Text']
        self.assertEqual(output_writer.output, expected_output)

    # TODO: add tests for _GetDataTypeMap
    # TODO: add tests for _ReadDefinitionFile

    def testReadStructureFromByteStream(self):
        """Tests the _ReadStructureFromByteStream function."""
        output_writer = test_lib.TestOutputWriter()
        test_format = data_format.BinaryDataFormat(debug=True,
                                                   output_writer=output_writer)

        data_type_map = self._DATA_TYPE_FABRIC.CreateDataTypeMap('point3d')

        test_format._ReadStructureFromByteStream(
            b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0,
            data_type_map, 'point3d')

        # Test with missing byte stream.
        with self.assertRaises(ValueError):
            test_format._ReadStructureFromByteStream(None, 0, data_type_map,
                                                     'point3d')

        # Test with missing data map type.
        with self.assertRaises(ValueError):
            test_format._ReadStructureFromByteStream(
                b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, None,
                'point3d')

        # Test with data type map that raises an dtfabric.MappingError.
        data_type_map = ErrorDataTypeMap(None)

        with self.assertRaises(errors.ParseError):
            test_format._ReadStructureFromByteStream(
                b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0,
                data_type_map, 'point3d')