Source code for plaso.parsers.spotlight_storedb

# -*- coding: utf-8 -*-
"""Parser for Apple Spotlight store database files."""

from collections import abc as collections
import zlib

from dfdatetime import cocoa_time as dfdatetime_cocoa_time
from dfdatetime import posix_time as dfdatetime_posix_time
from dfdatetime import semantic_time as dfdatetime_semantic_time
from dtfabric import errors as dtfabric_errors
from dtfabric.runtime import data_maps as dtfabric_data_maps

from plaso.containers import events
from plaso.containers import time_events
from plaso.lib import definitions
from plaso.lib import errors
from plaso.lib import specification
from plaso.parsers import dtfabric_parser
from plaso.parsers import manager


[docs]class SpotlightStoreMetadataItemEventData(events.EventData): """Apple Spotlight store database metadata item event data. Attributes: content_type (str): content type of the corresponding file (system) entry (kMDItemContentType). file_name (str): name of the corresponding file (system) entry (_kMDItemFileName). file_system_identifier (int): file system identifier, for example the catalog node identifier (CNID) on HFS. kind (str): item kind (kMDItemKind). parent_file_system_identifier (int): file system identifier of the parent. """ DATA_TYPE = 'spotlight:metadata_item' def __init__(self): """Initializes event data.""" super(SpotlightStoreMetadataItemEventData, self).__init__( data_type=self.DATA_TYPE) self.content_type = None self.file_name = None self.file_system_identifier = None self.parent_file_system_identifier = None
[docs]class SpotlightStoreMetadataAttribute(object): """Metadata attribute. Attributes: key (str): key or name of the metadata attribute. property_type (int): metadata attribute property type. value (object): metadata attribute value. value_type (int): metadata attribute value type. """ def __init__(self): """Initializes a metadata attribute.""" super(SpotlightStoreMetadataAttribute, self).__init__() self.key = None self.property_type = None self.value = None self.value_type = None
[docs]class SpotlightStoreMetadataItem(object): """Metadata item. Attributes: attributes (dict[str, SpotlightStoreMetadataAttribute]): metadata attributes. data_size (int): size of the record data. flags (int): record flags. identifier (int): file (system) entry identifier. item_identifier (int): item identifier. last_update_time (int): last update time. parent_identifier (int): parent file (system) entry identifier. """ def __init__(self): """Initializes a record.""" super(SpotlightStoreMetadataItem, self).__init__() self.attributes = {} self.data_size = 0 self.flags = 0 self.identifier = 0 self.item_identifier = 0 self.last_update_time = 0 self.parent_identifier = 0
[docs]class SpotlightStoreDatabaseParser(dtfabric_parser.DtFabricBaseParser): """Parser for Apple Spotlight store database (store.db) files.""" NAME = 'spotlight_storedb' DATA_FORMAT = 'Apple Spotlight store database (store.db) file' _DEFINITION_FILE = 'spotlight_storedb.yaml' # Names of metadata attributes that contain date and time values. _DATE_TIME_METADATA_ATTRIBUTES = [ ('_kMDItemContentChangeDate', definitions.TIME_DESCRIPTION_MODIFICATION), ('_kMDItemCreationDate', definitions.TIME_DESCRIPTION_CREATION), ('_kTimeMachineNewestSnapshot', definitions.TIME_DESCRIPTION_BACKUP), ('_kTimeMachineOldestSnapshot', definitions.TIME_DESCRIPTION_BACKUP), ('kMDItemAppStorePurchaseDate', definitions.TIME_DESCRIPTION_PURCHASED), ('kMDItemAttributeChangeDate', definitions.TIME_DESCRIPTION_CHANGE), ('kMDItemContentCreationDate', definitions.TIME_DESCRIPTION_CREATION), ('kMDItemContentModificationDate', definitions.TIME_DESCRIPTION_MODIFICATION), ('kMDItemDateAdded', definitions.TIME_DESCRIPTION_ADDED), ('kMDItemDownloadedDate', definitions.TIME_DESCRIPTION_FILE_DOWNLOADED), ('kMDItemLastUsedDate', definitions.TIME_DESCRIPTION_LAST_USED), ('kMDItemUsedDates', definitions.TIME_DESCRIPTION_LAST_USED)] # TODO: add more attribute values. # Names of metadata attributes that contain values to set as event data. _EVENT_DATA_METADATA_ATTRIBUTES = [ ('_kMDItemFileName', 'file_name'), ('kMDItemContentType', 'content_type'), ('kMDItemKind', 'kind')] def __init__(self): """Initializes a Apple Spotlight store database parser.""" super(SpotlightStoreDatabaseParser, self).__init__() self._map_values = [] self._metadata_lists = {} self._metadata_localized_strings = {} self._metadata_types = {} self._metadata_values = {} def _ParseMetadataItem(self, parser_mediator, metadata_item): """Parses an Apple Spotlight store metadata item. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. metadata_item (SpotlightStoreMetadataItem): a metadata item. """ event_data = SpotlightStoreMetadataItemEventData() # TODO: for identifier 1 extract and process kMDStoreProperties plist # Identifier 1 is used for volume metadata. if metadata_item.identifier > 1: event_data.file_system_identifier = metadata_item.identifier event_data.parent_file_system_identifier = metadata_item.parent_identifier for metadata_attribute_name, attribute_name in ( self._EVENT_DATA_METADATA_ATTRIBUTES): metadata_attribute = metadata_item.attributes.get( metadata_attribute_name, None) if metadata_attribute: setattr(event_data, attribute_name, metadata_attribute.value) for metadata_attribute_name, timestamp_description in ( self._DATE_TIME_METADATA_ATTRIBUTES): metadata_attribute = metadata_item.attributes.get( metadata_attribute_name, None) if metadata_attribute and metadata_attribute.value: if isinstance(metadata_attribute.value, collections.Sequence): timestamps = metadata_attribute.value else: timestamps = [metadata_attribute.value] for timestamp in timestamps: date_time = dfdatetime_cocoa_time.CocoaTime(timestamp=timestamp) event = time_events.DateTimeValuesEvent( date_time, timestamp_description) parser_mediator.ProduceEventWithEventData(event, event_data) if metadata_item.last_update_time == 0: date_time = dfdatetime_semantic_time.NotSet() else: date_time = dfdatetime_posix_time.PosixTimeInMicroseconds( timestamp=metadata_item.last_update_time) event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_UPDATE) parser_mediator.ProduceEventWithEventData(event, event_data) def _ParseRecord(self, parser_mediator, page_data, page_data_offset): """Parses a record for its metadata item. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. page_data (bytes): page data. page_data_offset (int): offset of the page value relative to the start of the page data. Returns: int: number of bytes read. Raises: ParseError: if the record cannot be read. """ metadata_item, bytes_read = self._ReadRecordHeader( page_data[page_data_offset:], page_data_offset) page_data_offset += bytes_read record_data_offset = bytes_read metadata_type_index = 0 try: while record_data_offset < metadata_item.data_size: relative_metadata_type_index, bytes_read = ( self._ReadVariableSizeInteger(page_data[page_data_offset:])) page_data_offset += bytes_read record_data_offset += bytes_read metadata_type_index += relative_metadata_type_index metadata_type = self._metadata_types.get(metadata_type_index, None) metadata_attribute, bytes_read = self._ReadMetadataAttribute( metadata_type, page_data[page_data_offset:]) page_data_offset += bytes_read record_data_offset += bytes_read metadata_item.attributes[metadata_attribute.key] = metadata_attribute self._ParseMetadataItem(parser_mediator, metadata_item) except errors.ParseError as exception: parser_mediator.ProduceExtractionWarning( 'unable to read metadata item: 0x{0:2x} with error: {1!s}'.format( metadata_item.identifier, exception)) return 4 + metadata_item.data_size def _ParseRecordPageValues(self, parser_mediator, page_data): """Parses the record page values. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. page_data (bytes): page data. Raises: ParseError: if the property page values cannot be read. """ page_data_offset = 0 page_data_size = len(page_data) while page_data_offset < page_data_size: bytes_read = self._ParseRecord( parser_mediator, page_data, page_data_offset) page_data_offset += bytes_read def _ReadFileHeader(self, file_object): """Reads the file header. Args: file_object (file): file-like object. Returns: spotlight_store_db_file_header: file header. Raises: ParseError: if the file header cannot be read. """ data_type_map = self._GetDataTypeMap('spotlight_store_db_file_header') file_header, _ = self._ReadStructureFromFileObject( file_object, 0, data_type_map) return file_header def _ReadIndexPageValues(self, page_header, page_data, property_table): """Reads the index page values. Args: page_header (spotlight_store_db_property_page_header): page header. page_data (bytes): page data. property_table (dict[int, object]): property table in which to store the property page values. Raises: ParseError: if the property page values cannot be read. """ data_type_map = self._GetDataTypeMap('spotlight_store_db_property_value81') index_values_data_type_map = self._GetDataTypeMap( 'spotlight_store_db_index_values') page_data_offset = 12 page_data_size = page_header.used_page_size - 20 page_value_index = 0 while page_data_offset < page_data_size: try: property_value = data_type_map.MapByteStream( page_data[page_data_offset:]) except dtfabric_errors.MappingError as exception: raise errors.ParseError(( 'Unable to map property value data at offset: 0x{0:08x} with ' 'error: {1!s}').format(page_data_offset, exception)) page_value_size = 4 index_size, bytes_read = self._ReadVariableSizeInteger( page_data[page_data_offset + page_value_size:]) _, padding_size = divmod(index_size, 4) page_value_size += bytes_read + padding_size index_size -= padding_size context = dtfabric_data_maps.DataTypeMapContext(values={ 'index_size': index_size}) try: index_values = index_values_data_type_map.MapByteStream( page_data[page_data_offset + page_value_size:], context=context) except dtfabric_errors.MappingError as exception: raise errors.ParseError(( 'Unable to parse index data at offset: 0x{0:08x} with error: ' '{1:s}').format(page_data_offset + page_value_size, exception)) page_value_size += index_size values_list = [] for metadata_value_index in index_values: metadata_value = self._metadata_values.get(metadata_value_index, None) value_string = getattr(metadata_value, 'value_name', '') values_list.append(value_string) setattr(property_value, 'values_list', values_list) property_table[property_value.table_index] = property_value page_data_offset += page_value_size page_value_index += 1 def _ReadMapPage(self, file_object, file_offset): """Reads a map page. Args: file_object (file): file-like object. file_offset (int): file offset. Returns: spotlight_store_db_map_page_header: page header. Raises: ParseError: if the map page cannot be read. """ data_type_map = self._GetDataTypeMap('spotlight_store_db_map_page_header') page_header, page_header_size = self._ReadStructureFromFileObject( file_object, file_offset, data_type_map) file_offset += page_header_size self._ReadMapPageValues(page_header, file_object, file_offset) return page_header def _ReadMapPages(self, file_object, map_offset, map_size): """Reads the map pages. Args: file_object (file): file-like object. map_offset (int): map offset. map_size (int): map size. Raises: ParseError: if the map pages cannot be read. """ map_end_offset = map_offset + map_size while map_offset < map_end_offset: page_header = self._ReadMapPage(file_object, map_offset) map_offset += page_header.page_size def _ReadMapPageValues(self, page_header, file_object, file_offset): """Reads the map page values. Args: page_header (spotlight_store_db_map_page_header): page header. file_object (file): file-like object. file_offset (int): file offset. Raises: ParseError: if the map page values cannot be read. """ data_type_map = self._GetDataTypeMap('spotlight_store_db_map_page_value') for _ in range(page_header.number_of_map_values): map_value, map_value_size = self._ReadStructureFromFileObject( file_object, file_offset, data_type_map) self._map_values.append(map_value) file_offset += map_value_size def _ReadMetadataAttribute(self, metadata_type, data): """Reads a metadata attribute. Args: metadata_type (spotlight_store_db_property_value11): metadata type property value. data (bytes): data. Returns: tuple[SpotlightStoreMetadataAttribute, int]: metadata attribute and number of bytes read. """ value_type = getattr(metadata_type, 'value_type', None) if value_type is None: return None, 0 key_name = getattr(metadata_type, 'key_name', None) property_type = getattr(metadata_type, 'property_type', None) if key_name == 'kMDStoreAccumulatedSizes': bytes_read = 4 * 16 value = data[:bytes_read] elif value_type in (0x00, 0x02, 0x06): value, bytes_read = self._ReadVariableSizeInteger(data) elif value_type == 0x07: value, bytes_read = self._ReadMetadataAttributeVariableSizeIntegerValue( property_type, data) elif value_type == 0x08: value, bytes_read = self._ReadMetadataAttributeByteValue( property_type, data) elif value_type == 0x09: value, bytes_read = self._ReadMetadataAttributeFloat32Value( property_type, data) elif value_type in (0x0a, 0x0c): value, bytes_read = self._ReadMetadataAttributeFloat64Value( property_type, data) elif value_type == 0x0b: value, bytes_read = self._ReadMetadataAttributeStringValue( property_type, data) elif value_type == 0x0e: data_size, bytes_read = self._ReadVariableSizeInteger(data) value = data[bytes_read:bytes_read + data_size] bytes_read += data_size # TODO: decode binary data e.g. UUID elif value_type == 0x0f: value, bytes_read = self._ReadMetadataAttributeReferenceValue( property_type, data) else: # TODO: value type 0x01, 0x03, 0x04, 0x05, 0x0d value, bytes_read = None, 0 metadata_attribute = SpotlightStoreMetadataAttribute() metadata_attribute.key = getattr(metadata_type, 'key_name', None) metadata_attribute.property_type = property_type metadata_attribute.value = value metadata_attribute.value_type = value_type return metadata_attribute, bytes_read def _ReadMetadataAttributeByteValue(self, property_type, data): """Reads a metadata attribute byte value. Args: property_type (int): metadata attribute property type. data (bytes): data. Returns: tuple[object, int]: value and number of bytes read. Raises: ParseError: if the metadata attribute byte value cannot be read. """ if property_type & 0x02 == 0x00: data_size, bytes_read = 1, 0 else: data_size, bytes_read = self._ReadVariableSizeInteger(data) data_type_map = self._GetDataTypeMap('array_of_byte') context = dtfabric_data_maps.DataTypeMapContext(values={ 'elements_data_size': data_size}) try: array_of_values = data_type_map.MapByteStream( data[bytes_read:bytes_read + data_size], context=context) except dtfabric_errors.MappingError as exception: raise errors.ParseError( 'Unable to parse array of byte values with error: {0!s}'.format( exception)) if bytes_read == 0: value = array_of_values[0] else: value = array_of_values bytes_read += data_size return value, bytes_read def _ReadMetadataAttributeFloat32Value(self, property_type, data): """Reads a metadata attribute 32-bit floating-point value. Args: property_type (int): metadata attribute property type. data (bytes): data. Returns: tuple[object, int]: value and number of bytes read. Raises: ParseError: if the metadata attribute 32-bit floating-point value cannot be read. """ if property_type & 0x02 == 0x00: data_size, bytes_read = 4, 0 else: data_size, bytes_read = self._ReadVariableSizeInteger(data) data_type_map = self._GetDataTypeMap('array_of_float32') context = dtfabric_data_maps.DataTypeMapContext(values={ 'elements_data_size': data_size}) try: array_of_values = data_type_map.MapByteStream( data[bytes_read:bytes_read + data_size], context=context) except dtfabric_errors.MappingError as exception: raise errors.ParseError(( 'Unable to parse array of 32-bit floating-point values with error: ' '{0!s}').format(exception)) if bytes_read == 0: value = array_of_values[0] else: value = array_of_values bytes_read += data_size return value, bytes_read def _ReadMetadataAttributeFloat64Value(self, property_type, data): """Reads a metadata attribute 64-bit floating-point value. Args: property_type (int): metadata attribute property type. data (bytes): data. Returns: tuple[object, int]: value and number of bytes read. Raises: ParseError: if the metadata attribute 64-bit floating-point value cannot be read. """ if property_type & 0x02 == 0x00: data_size, bytes_read = 8, 0 else: data_size, bytes_read = self._ReadVariableSizeInteger(data) data_type_map = self._GetDataTypeMap('array_of_float64') context = dtfabric_data_maps.DataTypeMapContext(values={ 'elements_data_size': data_size}) try: array_of_values = data_type_map.MapByteStream( data[bytes_read:bytes_read + data_size], context=context) except dtfabric_errors.MappingError as exception: raise errors.ParseError(( 'Unable to parse array of 64-bit floating-point values with error: ' '{0!s}').format(exception)) if bytes_read == 0: value = array_of_values[0] else: value = array_of_values bytes_read += data_size return value, bytes_read def _ReadMetadataAttributeReferenceValue(self, property_type, data): """Reads a metadata attribute reference value. Args: property_type (int): metadata attribute property type. data (bytes): data. Returns: tuple[object, int]: value and number of bytes read. Raises: ParseError: if the metadata attribute reference value cannot be read. """ table_index, bytes_read = self._ReadVariableSizeInteger(data) if property_type & 0x03 == 0x03: metadata_localized_strings = self._metadata_localized_strings.get( table_index, None) value_list = getattr(metadata_localized_strings, 'values_list', []) value = '(null)' if value_list: value = value_list[0] if '\x16\x02' in value: value = value.split('\x16\x02')[0] elif property_type & 0x03 == 0x02: metadata_list = self._metadata_lists.get(table_index, None) value = getattr(metadata_list, 'values_list', []) else: metadata_value = self._metadata_values.get(table_index, None) value = getattr(metadata_value, 'value_name', '(null)') return value, bytes_read def _ReadMetadataAttributeStringValue(self, property_type, data): """Reads a metadata attribute string value. Args: property_type (int): metadata attribute property type. data (bytes): data. Returns: tuple[object, int]: value and number of bytes read. Raises: ParseError: if the metadata attribute string value cannot be read. """ data_size, bytes_read = self._ReadVariableSizeInteger(data) data_type_map = self._GetDataTypeMap('array_of_cstring') context = dtfabric_data_maps.DataTypeMapContext(values={ 'elements_data_size': data_size}) try: array_of_values = data_type_map.MapByteStream( data[bytes_read:bytes_read + data_size], context=context) except dtfabric_errors.MappingError as exception: raise errors.ParseError( 'Unable to parse array of string values with error: {0!s}'.format( exception)) if property_type & 0x03 == 0x03: value = array_of_values[0] if '\x16\x02' in value: value = value.split('\x16\x02')[0] elif property_type & 0x03 == 0x02: value = array_of_values else: value = array_of_values[0] bytes_read += data_size return value, bytes_read def _ReadMetadataAttributeVariableSizeIntegerValue(self, property_type, data): """Reads a metadata attribute variable size integer value. Args: property_type (int): metadata attribute property type. data (bytes): data. Returns: tuple[object, int]: value and number of bytes read. """ if property_type & 0x02 == 0x00: return self._ReadVariableSizeInteger(data) data_size, bytes_read = self._ReadVariableSizeInteger(data) array_of_values = [] data_offset = 0 while data_offset < data_size: integer_value, integer_value_size = self._ReadVariableSizeInteger( data[data_offset:data_size]) data_offset += integer_value_size array_of_values.append(integer_value) bytes_read += data_size return array_of_values, bytes_read def _ReadPropertyPage(self, file_object, file_offset, property_table): """Reads a property page. Args: file_object (file): file-like object. file_offset (int): file offset. property_table (dict[int, object]): property table in which to store the property page values. Returns: tuple[spotlight_store_db_property_page_header, int]: page header and next property page block number. Raises: ParseError: if the property page cannot be read. """ page_header, bytes_read = self._ReadPropertyPageHeader( file_object, file_offset) if page_header.property_table_type not in ( 0x00000011, 0x00000021, 0x00000041, 0x00000081): raise errors.ParseError( 'Unsupported property table type: 0x{0:08x}'.format( page_header.property_table_type)) page_data = file_object.read(page_header.page_size - bytes_read) data_type_map = self._GetDataTypeMap( 'spotlight_store_db_property_values_header') file_offset += bytes_read page_values_header = self._ReadStructureFromByteStream( page_data, file_offset, data_type_map) if page_header.property_table_type in (0x00000011, 0x00000021): self._ReadPropertyPageValues(page_header, page_data, property_table) elif page_header.property_table_type == 0x00000081: self._ReadIndexPageValues(page_header, page_data, property_table) return page_header, page_values_header.next_block_number def _ReadPropertyPageHeader(self, file_object, file_offset): """Reads a property page header. Args: file_object (file): file-like object. file_offset (int): file offset. Returns: tuple[spotlight_store_db_property_page_header, int]: page header and next property page block number. Raises: ParseError: if the property page header cannot be read. """ data_type_map = self._GetDataTypeMap( 'spotlight_store_db_property_page_header') return self._ReadStructureFromFileObject( file_object, file_offset, data_type_map) def _ReadPropertyPages(self, file_object, block_number, property_table): """Reads the property pages. Args: file_object (file): file-like object. block_number (int): block number. property_table (dict[int, object]): property table in which to store the property page values. Raises: ParseError: if the property pages cannot be read. """ file_offset = block_number * 0x1000 while file_offset != 0: _, next_block_number = self._ReadPropertyPage( file_object, file_offset, property_table) file_offset = next_block_number * 0x1000 def _ReadPropertyPageValues(self, page_header, page_data, property_table): """Reads the property page values. Args: page_header (spotlight_store_db_property_page_header): page header. page_data (bytes): page data. property_table (dict[int, object]): property table in which to store the property page values. Raises: ParseError: if the property page values cannot be read. """ if page_header.property_table_type == 0x00000011: data_type_map = self._GetDataTypeMap( 'spotlight_store_db_property_value11') elif page_header.property_table_type == 0x00000021: data_type_map = self._GetDataTypeMap( 'spotlight_store_db_property_value21') page_data_offset = 12 page_data_size = page_header.used_page_size - 20 page_value_index = 0 while page_data_offset < page_data_size: context = dtfabric_data_maps.DataTypeMapContext() try: property_value = data_type_map.MapByteStream( page_data[page_data_offset:], context=context) except dtfabric_errors.MappingError as exception: raise errors.ParseError(( 'Unable to map property value data at offset: 0x{0:08x} with ' 'error: {1!s}').format(page_data_offset, exception)) property_table[property_value.table_index] = property_value page_data_offset += context.byte_size page_value_index += 1 def _ReadRecordHeader(self, data, page_data_offset): """Reads a record header. Args: data (bytes): data. page_data_offset (int): offset of the page value relative to the start of the page data. Returns: tuple[SpotlightStoreMetadataItem, int]: metadata item and number of bytes read. Raises: ParseError: if the record page cannot be read. """ data_type_map = self._GetDataTypeMap('spotlight_store_db_record') context = dtfabric_data_maps.DataTypeMapContext() try: record = data_type_map.MapByteStream(data, context=context) except dtfabric_errors.MappingError as exception: raise errors.ParseError(( 'Unable to map record at offset: 0x{0:08x} with error: ' '{1!s}').format(page_data_offset, exception)) data_offset = context.byte_size identifier, bytes_read = self._ReadVariableSizeInteger(data[data_offset:]) data_offset += bytes_read flags = data[data_offset] data_offset += 1 value_names = ['item_identifier', 'parent_identifier', 'last_update_time'] values, bytes_read = self._ReadVariableSizeIntegers( data[data_offset:], value_names) data_offset += bytes_read metadata_item = SpotlightStoreMetadataItem() metadata_item.data_size = record.data_size metadata_item.flags = flags metadata_item.identifier = identifier metadata_item.item_identifier = values.get('item_identifier') metadata_item.last_update_time = values.get('last_update_time') metadata_item.parent_identifier = values.get('parent_identifier') return metadata_item, data_offset def _ReadRecordPage(self, file_object, file_offset): """Reads a record page. Args: file_object (file): file-like object. file_offset (int): file offset. Returns: tuple[spotlight_store_db_property_page_header, bytes]: page header and page data. Raises: ParseError: if the property page cannot be read. """ page_header, bytes_read = self._ReadPropertyPageHeader( file_object, file_offset) if page_header.property_table_type != 0x00000009: raise errors.ParseError( 'Unsupported property table type: 0x{0:08x}'.format( page_header.property_table_type)) page_data = file_object.read(page_header.page_size - bytes_read) if page_header.uncompressed_page_size > 0: # TODO: add support for other compression types. if page_data[0] != 0x78: raise errors.ParseError('Unsupported compression type') page_data = zlib.decompress(page_data) return page_header, page_data def _ReadVariableSizeInteger(self, data): """Reads a variable size integer. Args: data (bytes): data. Returns: tuple[int, int]: integer value and number of bytes read. """ byte_value = data[0] bytes_read = 1 number_of_additional_bytes = 0 for bitmask in (0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff): if byte_value & bitmask != bitmask: break number_of_additional_bytes += 1 if number_of_additional_bytes > 4: byte_value = 0 elif number_of_additional_bytes > 0: byte_value &= bitmask ^ 0xff integer_value = int(byte_value) while number_of_additional_bytes > 0: integer_value <<= 8 integer_value += int(data[bytes_read]) bytes_read += 1 number_of_additional_bytes -= 1 return integer_value, bytes_read def _ReadVariableSizeIntegers(self, data, names): """Reads variable size integers. Args: data (bytes): data. names (list[str]): names to identify the integer values. Returns: tuple[dict[str, int], int]: integer values per name and number of bytes read. """ values = {} data_offset = 0 for name in names: integer_value, bytes_read = self._ReadVariableSizeInteger( data[data_offset:]) data_offset += bytes_read values[name] = integer_value return values, data_offset
[docs] @classmethod def GetFormatSpecification(cls): """Retrieves the format specification. Returns: FormatSpecification: format specification. """ format_specification = specification.FormatSpecification(cls.NAME) format_specification.AddNewSignature(b'8tsd', offset=0) return format_specification
[docs] def ParseFileObject(self, parser_mediator, file_object): """Parses an Apple Spotlight store database file-like object. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. file_object (dfvfs.FileIO): a file-like object. Raises: UnableToParseFile: when the file cannot be parsed. """ try: file_header = self._ReadFileHeader(file_object) except (ValueError, errors.ParseError): raise errors.UnableToParseFile('Unable to parse file header.') try: self._map_values = [] self._ReadMapPages( file_object, file_header.map_offset, file_header.map_size) self._metadata_types = {} self._ReadPropertyPages( file_object, file_header.metadata_types_block_number, self._metadata_types) self._metadata_values = {} self._ReadPropertyPages( file_object, file_header.metadata_values_block_number, self._metadata_values) # Note that the content of this property page is currently unknown. self._ReadPropertyPages( file_object, file_header.unknown_values41_block_number, {}) self._metadata_lists = {} self._ReadPropertyPages( file_object, file_header.metadata_lists_block_number, self._metadata_lists) self._metadata_localized_strings = {} self._ReadPropertyPages( file_object, file_header.metadata_localized_strings_block_number, self._metadata_localized_strings) except errors.ParseError as exception: parser_mediator.ProduceExtractionWarning( 'unable to read store database with error: {0!s}'.format( exception)) return for map_value in self._map_values: file_offset = map_value.block_number * 0x1000 try: _, page_data = self._ReadRecordPage(file_object, file_offset) self._ParseRecordPageValues(parser_mediator, page_data) except errors.ParseError as exception: parser_mediator.ProduceExtractionWarning(( 'unable to read record page at offset: 0x{0:08x} with error: ' '{1!s}').format(file_offset, exception)) continue
manager.ParsersManager.RegisterParser(SpotlightStoreDatabaseParser)