Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Firefox download update #4749

Merged
merged 11 commits into from
Dec 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 151 additions & 1 deletion plaso/parsers/sqlite_plugins/firefox_downloads.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
"""SQLite parser plugin for Mozilla Firefox downloads database files."""

import json
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style nit: given this module is part of Python the import should come before 3rd party imports


from dfdatetime import posix_time as dfdatetime_posix_time

from plaso.containers import events
Expand Down Expand Up @@ -48,6 +50,52 @@ def __init__(self):
self.url = None


class Firefox118DownloadEventData(events.EventData):
"""Firefox download event data.

Attributes:
deleted (int): deleted state.
download_state (int): state of the download.
end_time (dfdatetime.DateTimeValues): date and time the download was
finished.
expiration (int): expiration.
flags (int): flags associated with this download
full_path (str): full path of the target of the download.
mime_type (str): mime type of the download.
name (str): name of the download.
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.
received_bytes (int): number of bytes received.
referrer (str): referrer URL of the download.
start_time (dfdatetime.DateTimeValues): date and time the download was
started.
temporary_location (str): temporary location of the download.
total_bytes (int): total number of bytes of the download.
type (int): type field.
url (str): source URL of the download.
"""

DATA_TYPE = 'firefox:downloads:download'

def __init__(self):
"""Initializes event data."""
super(Firefox118DownloadEventData, self).__init__(data_type=self.DATA_TYPE)
self.deleted = None
self.download_state = None
self.end_time = None
self.expiration = None
self.flags = None
self.full_path = None
self.name = None
self.query = None
self.received_bytes = None
self.start_time = None
self.total_bytes = None
self.type = None
self.url = None


class FirefoxDownloadsPlugin(interface.SQLitePlugin):
"""SQLite parser plugin for Mozilla Firefox downloads database files.

Expand Down Expand Up @@ -132,4 +180,106 @@ def ParseDownloadsRow(
parser_mediator.ProduceEventData(event_data)


sqlite.SQLiteParser.RegisterPlugin(FirefoxDownloadsPlugin)
class Firefox118DownloadsPlugin(interface.SQLitePlugin):
"""SQLite parser plugin for version 118 Firefox downloads database files.

The version 118 Firefox downloads database file is typically stored in:
places.sql
"""

NAME = 'firefox_118_downloads'
DATA_FORMAT = (
'Mozilla Firefox 118 downloads SQLite database (downloads.sqlite) file')

REQUIRED_STRUCTURE = {
'moz_annos': frozenset([
'id', 'place_id', 'anno_attribute_id', 'content', 'flags',
'expiration', 'type', 'dateAdded', 'lastModified']),
'moz_places': frozenset([
'id', 'title', 'url', 'last_visit_date'])}

QUERIES = [
(('SELECT annos1.content, annos2.flags, annos2.expiration, annos2.type, '
'annos2.dateAdded, annos2.lastModified, annos2.content as dest_fpath, '
'places.url, places.title, places.last_visit_date '
'from moz_annos annos1, moz_annos annos2, moz_places places '
'WHERE annos1.anno_attribute_id == annos2.anno_attribute_id+1 '
'AND annos1.place_id == annos2.place_id '
'AND annos1.place_id == places.id'),
'ParseDownloadsRow')]

SCHEMAS = [
{'moz_annos':
'CREATE TABLE moz_annos (id INTEGER PRIMARY KEY, '
'place_id INTEGER NOT NULL, anno_attribute_id INTEGER, '
'content LONGVARCHAR, flags INTEGER DEFAULT 0, '
'expiration INTEGER DEFAULT 0, type INTEGER DEFAULT 0, '
'dateAdded INTEGER DEFAULT 0, lastModified INTEGER DEFAULT 0)'},
{'moz_places':
'CREATE TABLE moz_places (id INTEGER PRIMARY KEY, url LONGVARCHAR, '
'title LONGVARCHAR, rev_host LONGVARCHAR, '
'visit_count INTEGER DEFAULT 0, hidden INTEGER DEFAULT 0 NOT NULL, '
'typed INTEGER DEFAULT 0 NOT NULL, '
'frecency INTEGER DEFAULT -1 NOT NULL, last_visit_date INTEGER, '
'guid TEXT, foreign_count INTEGER DEFAULT 0 NOT NULL, '
'url_hash INTEGER DEFAULT 0 NOT NULL , description TEXT, '
'preview_image_url TEXT, site_name TEXT, '
'origin_id INTEGER REFERENCES moz_origins(id), '
'recalc_frecency INTEGER NOT NULL DEFAULT 0, alt_frecency INTEGER, '
'recalc_alt_frecency INTEGER NOT NULL DEFAULT 0)'}]

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.PosixTimeInMicroseconds: date and time value or None if not
available.
"""
timestamp = self._GetRowValue(query_hash, row, value_name)
if timestamp is None:
return None

return dfdatetime_posix_time.PosixTimeInMicroseconds(timestamp=timestamp)

def ParseDownloadsRow(self, parser_mediator, query, row, **unused_kwargs):
"""Parses a downloads 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.
"""
query_hash = hash(query)

content = self._GetRowValue(query_hash, row, 'content')
content_data = json.loads(content)

event_data = Firefox118DownloadEventData()

event_data.deleted = content_data.get('deleted', None)
event_data.download_state = content_data.get('state', None)
event_data.end_time = content_data.get('endTime', None)
event_data.expiration = self._GetRowValue(query_hash, row, 'expiration')
event_data.flags = self._GetRowValue(query_hash, row, 'flags')
event_data.full_path = self._GetRowValue(query_hash, row, 'dest_fpath')
event_data.name = self._GetRowValue(query_hash, row, 'title')
event_data.query = query
event_data.received_bytes = content_data.get('fileSize', 0)
event_data.start_time = self._GetDateTimeRowValue(
query_hash, row, 'dateAdded')
event_data.total_bytes = content_data.get('fileSize', 0)
event_data.type = self._GetRowValue(query_hash, row, 'type')
event_data.url = self._GetRowValue(query_hash, row, 'url')

parser_mediator.ProduceEventData(event_data)


sqlite.SQLiteParser.RegisterPlugins([
FirefoxDownloadsPlugin, Firefox118DownloadsPlugin])
Binary file added test_data/places118.sqlite
Binary file not shown.
40 changes: 40 additions & 0 deletions tests/parsers/sqlite_plugins/firefox_downloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,45 @@ def testProcessVersion25(self):
self.CheckEventData(event_data, expected_event_values)


def testProcessVersion118(self):
"""Tests the Process function on a Firefox Downloads database file."""
plugin = firefox_downloads.Firefox118DownloadsPlugin()
storage_writer = self._ParseDatabaseFileWithPlugin(
['places118.sqlite'], plugin)

number_of_event_data = storage_writer.GetNumberOfAttributeContainers(
'event_data')
self.assertEqual(number_of_event_data, 7)

number_of_warnings = storage_writer.GetNumberOfAttributeContainers(
'extraction_warning')
self.assertEqual(number_of_warnings, 0)

number_of_warnings = storage_writer.GetNumberOfAttributeContainers(
'recovery_warning')
self.assertEqual(number_of_warnings, 0)

expected_event_values = {
'data_type': 'firefox:downloads:download',
'download_state': 1,
'end_time': 1689722429974,
'full_path':
'file:///C:/Users/factdevteam/'
'Downloads/VSCodeUserSetup-x64-1.80.1.exe',
'name': 'VSCodeUserSetup-x64-1.80.1.exe',
'received_bytes': 93176792,
'start_time': "2023-07-18T23:20:28.452000+00:00",
'total_bytes': 93176792,
'type': 3,
'url': (
"https://az764295.vo.msecnd.net/stable/74f6148eb9ea00507ec"
"113ec51c489d6ffb4b771/VSCodeUserSetup-x64-1.80.1.exe"
)
}

event_data = storage_writer.GetAttributeContainerByIndex('event_data', 2)
self.CheckEventData(event_data, expected_event_values)


if __name__ == '__main__':
unittest.main()