Source code for plaso.parsers.sqlite_plugins.imessage

"""SQLite parser plugin for MacOS and iOS iMessage database files."""

from dfdatetime import cocoa_time as dfdatetime_cocoa_time
from dfdatetime import definitions as dfdatetime_definitions

from plaso.containers import events
from plaso.lib import definitions
from plaso.parsers import sqlite
from plaso.parsers.sqlite_plugins import interface


[docs] class IMessageEventData(events.EventData): """iMessage and SMS event data. Attributes: attachment_location (str): location of the attachment. client_version (int): client version. creation_time (dfdatetime.DateTimeValues): date and time the message was created. imessage_id (str): mobile number or email address the message was sent to or received from. message_type (int): value to indicate the message was sent (1) or received (0). offset (str): identifier of the row, from which the event data was extracted. query (str): SQL query that was used to obtain the event data. read_receipt (bool): True if the message read receipt was received. service (str): service, which is either SMS or iMessage. text (str): content of the message. """ DATA_TYPE = "imessage:event:chat"
[docs] def __init__(self): """Initializes event data.""" super().__init__(data_type=self.DATA_TYPE) self.attachment_location = None self.client_version = None self.creation_time = None self.imessage_id = None self.message_type = None self.offset = None self.query = None self.read_receipt = None self.service = None self.text = None
[docs] class IMessagePlugin(interface.SQLitePlugin): """SQLite parser plugin for MacOS and iOS iMessage database files. The iMessage database file is typically stored in chat.db or sms.db. """ NAME = "imessage" DATA_FORMAT = "MacOS and iOS iMessage database (chat.db, sms.db) file" REQUIRED_STRUCTURE = { "_SqliteDatabaseProperties": frozenset(["key", "value"]), "message": frozenset( ["date", "ROWID", "is_read", "is_from_me", "service", "text", "handle_id"] ), "handle": frozenset(["id", "ROWID"]), "attachment": frozenset(["filename", "ROWID"]), "message_attachment_join": frozenset(["message_id", "attachment_id"]), } QUERIES = [ ( "SELECT message.date, message.ROWID, handle.id AS imessage_id, " "message.is_read AS read_receipt, message.is_from_me AS message_type, " "message.service, attachment.filename AS attachment_location, " "message.text FROM message JOIN handle " "ON handle.ROWID = message.handle_id " "LEFT OUTER JOIN message_attachment_join AS maj " "ON message.ROWID = maj.message_id LEFT OUTER JOIN attachment " "ON maj.attachment_id = attachment.ROWID", "ParseMessageRow", ) ] _CLIENT_VERSION_QUERY = ( "SELECT key, value FROM _SqliteDatabaseProperties " 'WHERE key = "_ClientVersion"' ) def _GetClientVersion(self, cache, database): """Retrieves the client version. Args: cache (SQLiteCache): cache. database (SQLiteDatabase): database. Returns: int: client version or None if the client version cannot be determined. """ cache_results = cache.GetResults("client_version") if not cache_results: query_result = database.Query(self._CLIENT_VERSION_QUERY) cache.CacheQueryResults(query_result, "client_version", "key", ("value",)) cache_results = cache.GetResults("client_version") client_version = cache_results.get("_ClientVersion", [])[0] if isinstance(client_version, str): try: client_version = int(client_version, 10) cache_results["_ClientVersion"] = [client_version] except (ValueError, TypeError): return None return client_version def _GetDateTimeRowValue(self, query_hash, row, value_name): """Retrieves a date and time value from the row. Args: query_hash (int): hash of the query, that uniquely identifies the query that produced the row. row (sqlite3.Row): row. value_name (str): name of the value. Returns: dfdatetime.CocoaTime: date and time value or None if not available. """ timestamp = self._GetRowValue(query_hash, row, value_name) if timestamp is None: return None precision = dfdatetime_definitions.PRECISION_1_SECOND # In current versions the timestamp is stored in nanoseconds. # Note that a Cocoa timestamp of 1000000000 is somewhere in 2032 and # the timestamp apprears to have been changes around 2017. if ( timestamp < -definitions.NANOSECONDS_PER_SECOND or timestamp > definitions.NANOSECONDS_PER_SECOND ): timestamp /= definitions.NANOSECONDS_PER_SECOND precision = dfdatetime_definitions.PRECISION_1_NANOSECOND return dfdatetime_cocoa_time.CocoaTime(precision=precision, timestamp=timestamp)
[docs] def ParseMessageRow( self, parser_mediator, query, row, cache=None, database=None, **unused_kwargs ): """Parses a message row. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfVFS. query (str): query that created the row. row (sqlite3.Row): row. cache (SQLiteCache): cache which contains cached results from querying the visits and urls tables. database (Optional[SQLiteDatabase]): database. """ query_hash = hash(query) event_data = IMessageEventData() event_data.attachment_location = self._GetRowValue( query_hash, row, "attachment_location" ) event_data.client_version = self._GetClientVersion(cache, database) event_data.creation_time = self._GetDateTimeRowValue(query_hash, row, "date") event_data.imessage_id = self._GetRowValue(query_hash, row, "imessage_id") event_data.message_type = self._GetRowValue(query_hash, row, "message_type") event_data.offset = self._GetRowValue(query_hash, row, "ROWID") event_data.query = query event_data.read_receipt = self._GetRowValue(query_hash, row, "read_receipt") event_data.service = self._GetRowValue(query_hash, row, "service") event_data.text = self._GetRowValue(query_hash, row, "text") parser_mediator.ProduceEventData(event_data)
sqlite.SQLiteParser.RegisterPlugin(IMessagePlugin)