# -*- coding: utf-8 -*-
"""This file contains a parser for the Google Drive snapshots.
The Google Drive snapshots are stored in SQLite database files named
snapshot.db.
"""
from __future__ import unicode_literals
from dfdatetime import posix_time as dfdatetime_posix_time
from plaso.containers import events
from plaso.containers import time_events
from plaso.lib import definitions
from plaso.parsers import sqlite
from plaso.parsers.sqlite_plugins import interface
[docs]class GoogleDriveSnapshotCloudEntryEventData(events.EventData):
"""Google Drive snapshot cloud entry event data.
Attributes:
doc_type (int): document type.
path (str): path of the file.
shared (bool): True if the file is shared, False if the file is private.
size (int): size of the file.
url (str): URL of the file.
"""
DATA_TYPE = 'gdrive:snapshot:cloud_entry'
def __init__(self):
"""Initializes event data."""
super(GoogleDriveSnapshotCloudEntryEventData, self).__init__(
data_type=self.DATA_TYPE)
self.document_type = None
self.path = None
self.shared = None
self.size = None
self.url = None
[docs]class GoogleDriveSnapshotLocalEntryEventData(events.EventData):
"""Google Drive snapshot local entry event data.
Attributes:
path (str): path of the file.
size (int): size of the file.
"""
DATA_TYPE = 'gdrive:snapshot:local_entry'
def __init__(self):
"""Initializes event data."""
super(GoogleDriveSnapshotLocalEntryEventData, self).__init__(
data_type=self.DATA_TYPE)
self.path = None
self.size = None
[docs]class GoogleDrivePlugin(interface.SQLitePlugin):
"""SQLite plugin for Google Drive snapshot.db files."""
NAME = 'google_drive'
DESCRIPTION = 'Parser for Google Drive SQLite database files.'
# Define the needed queries.
QUERIES = [
(('SELECT cloud_entry.resource_id, cloud_entry.filename, '
'cloud_entry.modified, cloud_entry.created, cloud_entry.size, '
'cloud_entry.doc_type, cloud_entry.shared, cloud_entry.checksum, '
'cloud_entry.url, cloud_relations.parent_resource_id '
'FROM cloud_entry, cloud_relations '
'WHERE cloud_relations.child_resource_id = cloud_entry.resource_id '
'AND cloud_entry.modified IS NOT NULL;'),
'ParseCloudEntryRow'),
(('SELECT inode_number, filename, modified, checksum, size '
'FROM local_entry WHERE modified IS NOT NULL;'),
'ParseLocalEntryRow')]
# The required tables.
REQUIRED_TABLES = frozenset([
'cloud_entry', 'cloud_relations', 'local_entry', 'local_relations',
'mapping', 'overlay_status'])
SCHEMAS = [{
'cloud_entry': (
'CREATE TABLE cloud_entry (resource_id TEXT, filename TEXT, '
'modified INTEGER, created INTEGER, acl_role INTEGER, doc_type '
'INTEGER, removed INTEGER, url TEXT, size INTEGER, checksum TEXT, '
'shared INTEGER, PRIMARY KEY (resource_id))'),
'cloud_relations': (
'CREATE TABLE cloud_relations (child_resource_id TEXT, '
'parent_resource_id TEXT, UNIQUE (child_resource_id, '
'parent_resource_id), FOREIGN KEY (child_resource_id) REFERENCES '
'cloud_entry(resource_id), FOREIGN KEY (parent_resource_id) '
'REFERENCES cloud_entry(resource_id))'),
'local_entry': (
'CREATE TABLE local_entry (inode_number INTEGER, filename TEXT, '
'modified INTEGER, checksum TEXT, size INTEGER, PRIMARY KEY '
'(inode_number))'),
'local_relations': (
'CREATE TABLE local_relations (child_inode_number INTEGER, '
'parent_inode_number INTEGER, UNIQUE (child_inode_number), FOREIGN '
'KEY (parent_inode_number) REFERENCES local_entry(inode_number), '
'FOREIGN KEY (child_inode_number) REFERENCES '
'local_entry(inode_number))'),
'mapping': (
'CREATE TABLE mapping (inode_number INTEGER, resource_id TEXT, '
'UNIQUE (inode_number), FOREIGN KEY (inode_number) REFERENCES '
'local_entry(inode_number), FOREIGN KEY (resource_id) REFERENCES '
'cloud_entry(resource_id))'),
'overlay_status': (
'CREATE TABLE overlay_status (path TEXT, overlay_status INTEGER, '
'PRIMARY KEY (path))')}]
# Queries used to build cache.
LOCAL_PATH_CACHE_QUERY = (
'SELECT local_relations.child_inode_number, '
'local_relations.parent_inode_number, local_entry.filename '
'FROM local_relations, local_entry '
'WHERE local_relations.child_inode_number = local_entry.inode_number')
CLOUD_PATH_CACHE_QUERY = (
'SELECT cloud_entry.filename, cloud_entry.resource_id, '
'cloud_relations.parent_resource_id AS parent '
'FROM cloud_entry, cloud_relations '
'WHERE cloud_entry.doc_type = 0 '
'AND cloud_entry.resource_id = cloud_relations.child_resource_id')
[docs] def GetLocalPath(self, inode, cache, database):
"""Return local path for a given inode.
Args:
inode (int): inode number for the file.
cache (SQLiteCache): cache.
database (SQLiteDatabase): database.
Returns:
str: full path, including the filename of the given inode value.
"""
local_path = cache.GetResults('local_path')
if not local_path:
results = database.Query(self.LOCAL_PATH_CACHE_QUERY)
cache.CacheQueryResults(
results, 'local_path', 'child_inode_number',
('parent_inode_number', 'filename'))
local_path = cache.GetResults('local_path')
parent, path = local_path.get(inode, [None, None])
# TODO: Read the local_sync_root from the sync_config.db and use that
# for a root value.
root_value = '%local_sync_root%/'
if not path:
return root_value
paths = []
while path:
paths.append(path)
parent, path = local_path.get(parent, [None, None])
if not paths:
return root_value
# Paths are built top level to root so we need to reverse the list to
# represent them in the traditional order.
paths.reverse()
return root_value + '/'.join(paths)
[docs] def GetCloudPath(self, resource_id, cache, database):
"""Return cloud path given a resource id.
Args:
resource_id (str): resource identifier for the file.
cache (SQLiteCache): cache.
database (SQLiteDatabase): database.
Returns:
str: full path to the resource value.
"""
cloud_path = cache.GetResults('cloud_path')
if not cloud_path:
results = database.Query(self.CLOUD_PATH_CACHE_QUERY)
cache.CacheQueryResults(
results, 'cloud_path', 'resource_id', ('filename', 'parent'))
cloud_path = cache.GetResults('cloud_path')
if resource_id == 'folder:root':
return '/'
paths = []
parent_path, parent_id = cloud_path.get(resource_id, ['', ''])
while parent_path:
if parent_path == 'folder:root':
break
paths.append(parent_path)
parent_path, parent_id = cloud_path.get(parent_id, ['', ''])
if not paths:
return '/'
# Paths are built top level to root so we need to reverse the list to
# represent them in the traditional order.
paths.reverse()
return '/{0:s}/'.format('/'.join(paths))
# pylint 1.9.3 wants a docstring for kwargs, but this is not useful to add.
# pylint: disable=missing-param-doc
[docs] def ParseCloudEntryRow(
self, parser_mediator, query, row, cache=None, database=None,
**unused_kwargs):
"""Parses a cloud entry 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.
database (SQLiteDatabase): database.
"""
query_hash = hash(query)
parent_resource_id = self._GetRowValue(
query_hash, row, 'parent_resource_id')
filename = self._GetRowValue(query_hash, row, 'filename')
cloud_path = self.GetCloudPath(parent_resource_id, cache, database)
cloud_filename = '{0:s}{1:s}'.format(cloud_path, filename)
event_data = GoogleDriveSnapshotCloudEntryEventData()
event_data.document_type = self._GetRowValue(query_hash, row, 'doc_type')
event_data.path = cloud_filename
event_data.query = query
event_data.shared = bool(self._GetRowValue(query_hash, row, 'shared'))
event_data.size = self._GetRowValue(query_hash, row, 'size')
event_data.url = self._GetRowValue(query_hash, row, 'url')
timestamp = self._GetRowValue(query_hash, row, 'modified')
date_time = dfdatetime_posix_time.PosixTime(timestamp=timestamp)
event = time_events.DateTimeValuesEvent(
date_time, definitions.TIME_DESCRIPTION_MODIFICATION)
parser_mediator.ProduceEventWithEventData(event, event_data)
timestamp = self._GetRowValue(query_hash, row, 'created')
if timestamp:
date_time = dfdatetime_posix_time.PosixTime(timestamp=timestamp)
event = time_events.DateTimeValuesEvent(
date_time, definitions.TIME_DESCRIPTION_CREATION)
parser_mediator.ProduceEventWithEventData(event, event_data)
# pylint 1.9.3 wants a docstring for kwargs, but this is not useful to add.
# pylint: disable=missing-param-doc
[docs] def ParseLocalEntryRow(
self, parser_mediator, query, row, cache=None, database=None,
**unused_kwargs):
"""Parses a local entry 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 (Optional[SQLiteCache]): cache.
database (Optional[SQLiteDatabase]): database.
"""
query_hash = hash(query)
inode_number = self._GetRowValue(query_hash, row, 'inode_number')
local_path = self.GetLocalPath(inode_number, cache, database)
event_data = GoogleDriveSnapshotLocalEntryEventData()
event_data.path = local_path
event_data.query = query
event_data.size = self._GetRowValue(query_hash, row, 'size')
timestamp = self._GetRowValue(query_hash, row, 'modified')
date_time = dfdatetime_posix_time.PosixTime(timestamp=timestamp)
event = time_events.DateTimeValuesEvent(
date_time, definitions.TIME_DESCRIPTION_MODIFICATION)
parser_mediator.ProduceEventWithEventData(event, event_data)
sqlite.SQLiteParser.RegisterPlugin(GoogleDrivePlugin)