Import from old repository
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2014 The Plaso Project Authors.
|
||||
# Please see the AUTHORS file for details on individual authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""This file contains an import statement for each OLECF plugin."""
|
||||
|
||||
from plaso.parsers.olecf_plugins import automatic_destinations
|
||||
from plaso.parsers.olecf_plugins import default
|
||||
from plaso.parsers.olecf_plugins import summary
|
||||
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2014 The Plaso Project Authors.
|
||||
# Please see the AUTHORS file for details on individual authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""Plugin to parse .automaticDestinations-ms OLECF files."""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
import construct
|
||||
|
||||
from plaso.events import time_events
|
||||
from plaso.lib import binary
|
||||
from plaso.lib import errors
|
||||
from plaso.lib import eventdata
|
||||
from plaso.parsers import olecf
|
||||
from plaso.parsers import winlnk
|
||||
from plaso.parsers.olecf_plugins import interface
|
||||
|
||||
|
||||
class AutomaticDestinationsDestListEntryEvent(time_events.FiletimeEvent):
|
||||
"""Convenience class for an .automaticDestinations-ms DestList entry event."""
|
||||
|
||||
DATA_TYPE = 'olecf:dest_list:entry'
|
||||
|
||||
def __init__(
|
||||
self, timestamp, timestamp_description, entry_offset, dest_list_entry):
|
||||
"""Initializes the event object.
|
||||
|
||||
Args:
|
||||
timestamp: The FILETIME value for the timestamp.
|
||||
timestamp_description: The usage string for the timestamp value.
|
||||
entry_offset: The offset of the DestList entry relative to the start of
|
||||
the DestList stream.
|
||||
dest_list_entry: The DestList entry (instance of construct.Struct).
|
||||
"""
|
||||
super(AutomaticDestinationsDestListEntryEvent, self).__init__(
|
||||
timestamp, timestamp_description)
|
||||
|
||||
self.offset = entry_offset
|
||||
self.entry_number = dest_list_entry.entry_number
|
||||
|
||||
self.hostname = binary.ByteStreamCopyToString(
|
||||
dest_list_entry.hostname, codepage='ascii')
|
||||
self.path = binary.Ut16StreamCopyToString(dest_list_entry.path)
|
||||
self.pin_status = dest_list_entry.pin_status
|
||||
|
||||
self.droid_volume_identifier = binary.ByteStreamCopyToGuid(
|
||||
dest_list_entry.droid_volume_identifier)
|
||||
self.droid_file_identifier = binary.ByteStreamCopyToGuid(
|
||||
dest_list_entry.droid_file_identifier)
|
||||
self.birth_droid_volume_identifier = binary.ByteStreamCopyToGuid(
|
||||
dest_list_entry.birth_droid_volume_identifier)
|
||||
self.birth_droid_file_identifier = binary.ByteStreamCopyToGuid(
|
||||
dest_list_entry.birth_droid_file_identifier)
|
||||
|
||||
|
||||
class AutomaticDestinationsOlecfPlugin(interface.OlecfPlugin):
|
||||
"""Plugin that parses an .automaticDestinations-ms OLECF file."""
|
||||
|
||||
NAME = 'olecf_automatic_destinations'
|
||||
DESCRIPTION = u'Parser for *.automaticDestinations-ms OLECF files.'
|
||||
|
||||
REQUIRED_ITEMS = frozenset([u'DestList'])
|
||||
|
||||
_RE_LNK_ITEM_NAME = re.compile(r'^[1-9a-f][0-9a-f]*$')
|
||||
|
||||
# We cannot use the parser registry here since winlnk could be disabled.
|
||||
# TODO: see if there is a more elegant solution for this.
|
||||
_WINLNK_PARSER = winlnk.WinLnkParser()
|
||||
|
||||
_DEST_LIST_STREAM_HEADER = construct.Struct(
|
||||
'dest_list_stream_header',
|
||||
construct.ULInt32('unknown1'),
|
||||
construct.ULInt32('number_of_entries'),
|
||||
construct.ULInt32('number_of_pinned_entries'),
|
||||
construct.LFloat32('unknown2'),
|
||||
construct.ULInt32('last_entry_number'),
|
||||
construct.Padding(4),
|
||||
construct.ULInt32('last_revision_number'),
|
||||
construct.Padding(4))
|
||||
|
||||
_DEST_LIST_STREAM_HEADER_SIZE = _DEST_LIST_STREAM_HEADER.sizeof()
|
||||
|
||||
# Using Construct's utf-16 encoding here will create strings with their
|
||||
# end-of-string characters exposed. Instead the strings are read as
|
||||
# binary strings and converted using ReadUtf16().
|
||||
_DEST_LIST_STREAM_ENTRY = construct.Struct(
|
||||
'dest_list_stream_entry',
|
||||
construct.ULInt64('unknown1'),
|
||||
construct.Array(16, construct.Byte('droid_volume_identifier')),
|
||||
construct.Array(16, construct.Byte('droid_file_identifier')),
|
||||
construct.Array(16, construct.Byte('birth_droid_volume_identifier')),
|
||||
construct.Array(16, construct.Byte('birth_droid_file_identifier')),
|
||||
construct.String('hostname', 16),
|
||||
construct.ULInt32('entry_number'),
|
||||
construct.ULInt32('unknown2'),
|
||||
construct.LFloat32('unknown3'),
|
||||
construct.ULInt64('last_modification_time'),
|
||||
construct.ULInt32('pin_status'),
|
||||
construct.ULInt16('path_size'),
|
||||
construct.String('path', lambda ctx: ctx.path_size * 2))
|
||||
|
||||
def ParseDestList(
|
||||
self, parser_context, file_entry=None, parser_chain=None,
|
||||
olecf_item=None):
|
||||
"""Parses the DestList OLECF item.
|
||||
|
||||
Args:
|
||||
parser_context: A parser context object (instance of ParserContext).
|
||||
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
|
||||
The default is None.
|
||||
parser_chain: Optional string containing the parsing chain up to this
|
||||
point. The default is None.
|
||||
olecf_item: An optional OLECF item (instance of pyolecf.item).
|
||||
"""
|
||||
if not olecf_item:
|
||||
return
|
||||
|
||||
try:
|
||||
header = self._DEST_LIST_STREAM_HEADER.parse_stream(olecf_item)
|
||||
except (IOError, construct.FieldError) as exception:
|
||||
raise errors.UnableToParseFile(
|
||||
u'Unable to parse DestList header with error: {0:s}'.format(
|
||||
exception))
|
||||
|
||||
if header.unknown1 != 1:
|
||||
# TODO: add format debugging notes to parser context.
|
||||
logging.debug(u'[{0:s}] unknown1 value: {1:d}.'.format(
|
||||
self.NAME, header.unknown1))
|
||||
|
||||
entry_offset = olecf_item.get_offset()
|
||||
while entry_offset < olecf_item.size:
|
||||
try:
|
||||
entry = self._DEST_LIST_STREAM_ENTRY.parse_stream(olecf_item)
|
||||
except (IOError, construct.FieldError) as exception:
|
||||
raise errors.UnableToParseFile(
|
||||
u'Unable to parse DestList entry with error: {0:s}'.format(
|
||||
exception))
|
||||
|
||||
if not entry:
|
||||
break
|
||||
|
||||
event_object = AutomaticDestinationsDestListEntryEvent(
|
||||
entry.last_modification_time,
|
||||
eventdata.EventTimestamp.MODIFICATION_TIME, entry_offset, entry)
|
||||
parser_context.ProduceEvent(
|
||||
event_object, parser_chain=parser_chain, file_entry=file_entry)
|
||||
|
||||
entry_offset = olecf_item.get_offset()
|
||||
|
||||
def ParseItems(
|
||||
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
|
||||
**unused_kwargs):
|
||||
"""Parses OLECF items.
|
||||
|
||||
Args:
|
||||
parser_context: A parser context object (instance of ParserContext).
|
||||
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
|
||||
The default is None.
|
||||
parser_chain: Optional string containing the parsing chain up to this
|
||||
point. The default is None.
|
||||
root_item: Optional root item of the OLECF file. The default is None.
|
||||
|
||||
Raises:
|
||||
ValueError: If the root_item is not set.
|
||||
"""
|
||||
if root_item is None:
|
||||
raise ValueError(u'Root item not set.')
|
||||
|
||||
for item in root_item.sub_items:
|
||||
if item.name == u'DestList':
|
||||
self.ParseDestList(
|
||||
parser_context, file_entry=file_entry, parser_chain=parser_chain,
|
||||
olecf_item=item)
|
||||
|
||||
elif self._RE_LNK_ITEM_NAME.match(item.name):
|
||||
if file_entry:
|
||||
display_name = u'{0:s} # {1:s}'.format(
|
||||
parser_context.GetDisplayName(file_entry), item.name)
|
||||
else:
|
||||
display_name = u'# {0:s}'.format(item.name)
|
||||
|
||||
self._WINLNK_PARSER.ParseFileObject(
|
||||
parser_context, item, file_entry=file_entry,
|
||||
parser_chain=parser_chain, display_name=display_name)
|
||||
|
||||
# TODO: check for trailing data?
|
||||
|
||||
|
||||
olecf.OleCfParser.RegisterPlugin(AutomaticDestinationsOlecfPlugin)
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2014 The Plaso Project Authors.
|
||||
# Please see the AUTHORS file for details on individual authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""Tests for the .automaticDestinations-ms OLECF file plugin."""
|
||||
|
||||
import unittest
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from plaso.formatters import olecf as olecf_formatter
|
||||
from plaso.lib import eventdata
|
||||
from plaso.lib import timelib_test
|
||||
from plaso.parsers.olecf_plugins import automatic_destinations
|
||||
from plaso.parsers.olecf_plugins import test_lib
|
||||
|
||||
|
||||
class TestAutomaticDestinationsOlecfPlugin(test_lib.OleCfPluginTestCase):
|
||||
"""Tests for the .automaticDestinations-ms OLECF file plugin."""
|
||||
|
||||
def setUp(self):
|
||||
"""Sets up the needed objects used throughout the test."""
|
||||
self._plugin = automatic_destinations.AutomaticDestinationsOlecfPlugin()
|
||||
|
||||
def testProcess(self):
|
||||
"""Tests the Process function."""
|
||||
test_file = self._GetTestFilePath([
|
||||
u'1b4dd67f29cb1962.automaticDestinations-ms'])
|
||||
event_queue_consumer = self._ParseOleCfFileWithPlugin(
|
||||
test_file, self._plugin)
|
||||
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
|
||||
|
||||
self.assertEquals(len(event_objects), 44)
|
||||
|
||||
# Check a AutomaticDestinationsDestListEntryEvent.
|
||||
event_object = event_objects[3]
|
||||
|
||||
self.assertEquals(event_object.offset, 32)
|
||||
|
||||
self.assertEquals(
|
||||
event_object.timestamp_desc, eventdata.EventTimestamp.MODIFICATION_TIME)
|
||||
|
||||
expected_timestamp = timelib_test.CopyStringToTimestamp(
|
||||
'2012-04-01 13:52:38.997538')
|
||||
self.assertEquals(event_object.timestamp, expected_timestamp)
|
||||
|
||||
expected_msg = (
|
||||
u'Entry: 11 '
|
||||
u'Pin status: Unpinned '
|
||||
u'Hostname: wks-win764bitb '
|
||||
u'Path: C:\\Users\\nfury\\Pictures\\The SHIELD '
|
||||
u'Droid volume identifier: {cf6619c2-66a8-44a6-8849-1582fcd3a338} '
|
||||
u'Droid file identifier: {63eea867-7b85-11e1-8950-005056a50b40} '
|
||||
u'Birth droid volume identifier: '
|
||||
u'{cf6619c2-66a8-44a6-8849-1582fcd3a338} '
|
||||
u'Birth droid file identifier: {63eea867-7b85-11e1-8950-005056a50b40}')
|
||||
|
||||
expected_msg_short = (
|
||||
u'Entry: 11 '
|
||||
u'Pin status: Unpinned '
|
||||
u'Path: C:\\Users\\nfury\\Pictures\\The SHIELD')
|
||||
|
||||
self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short)
|
||||
|
||||
# Check a WinLnkLinkEvent.
|
||||
event_object = event_objects[1]
|
||||
|
||||
expected_timestamp = timelib_test.CopyStringToTimestamp(
|
||||
'2010-11-10 07:51:16.749125')
|
||||
self.assertEquals(event_object.timestamp, expected_timestamp)
|
||||
|
||||
expected_msg = (
|
||||
u'File size: 3545 '
|
||||
u'File attribute flags: 0x00002020 '
|
||||
u'Drive type: 3 '
|
||||
u'Drive serial number: 0x24ba718b '
|
||||
u'Local path: C:\\Users\\nfury\\AppData\\Roaming\\Microsoft\\Windows\\'
|
||||
u'Libraries\\Documents.library-ms '
|
||||
u'Link target: [Users Libraries, UNKNOWN: 0x00]')
|
||||
|
||||
expected_msg_short = (
|
||||
u'C:\\Users\\nfury\\AppData\\Roaming\\Microsoft\\Windows\\Libraries\\'
|
||||
u'Documents.library-ms')
|
||||
|
||||
self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 The Plaso Project Authors.
|
||||
# Please see the AUTHORS file for details on individual authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""The default plugin for parsing OLE Compound Files (OLECF)."""
|
||||
|
||||
from plaso.events import time_events
|
||||
from plaso.lib import eventdata
|
||||
from plaso.parsers import olecf
|
||||
from plaso.parsers.olecf_plugins import interface
|
||||
|
||||
|
||||
class OleCfItemEvent(time_events.FiletimeEvent):
|
||||
"""Convenience class for an OLECF item event."""
|
||||
|
||||
DATA_TYPE = 'olecf:item'
|
||||
|
||||
def __init__(self, timestamp, usage, olecf_item):
|
||||
"""Initializes the event.
|
||||
|
||||
Args:
|
||||
timestamp: The FILETIME timestamp value.
|
||||
usage: A string describing the timestamp value.
|
||||
olecf_item: The OLECF item (pyolecf.item).
|
||||
"""
|
||||
super(OleCfItemEvent, self).__init__(timestamp, usage)
|
||||
|
||||
# TODO: need a better way to express the original location of the
|
||||
# original data.
|
||||
self.offset = 0
|
||||
|
||||
self.name = olecf_item.name
|
||||
# TODO: have pyolecf return the item type here.
|
||||
# self.type = olecf_item.type
|
||||
self.size = olecf_item.size
|
||||
|
||||
|
||||
class DefaultOleCFPlugin(interface.OlecfPlugin):
|
||||
"""Class to define the default OLECF file plugin."""
|
||||
|
||||
NAME = 'olecf_default'
|
||||
DESCRIPTION = u'Parser for a generic OLECF item.'
|
||||
|
||||
def _ParseItem(
|
||||
self, parser_context, file_entry=None, parser_chain=None,
|
||||
olecf_item=None):
|
||||
"""Parses an OLECF item.
|
||||
|
||||
Args:
|
||||
parser_context: A parser context object (instance of ParserContext).
|
||||
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
|
||||
The default is None.
|
||||
parser_chain: Optional string containing the parsing chain up to this
|
||||
point. The default is None.
|
||||
olecf_item: An optional OLECF item (instance of pyolecf.item).
|
||||
|
||||
Returns:
|
||||
A boolean value indicating if an event object was produced.
|
||||
"""
|
||||
event_object = None
|
||||
result = False
|
||||
|
||||
creation_time, modification_time = self.GetTimestamps(olecf_item)
|
||||
|
||||
if creation_time:
|
||||
event_object = OleCfItemEvent(
|
||||
creation_time, eventdata.EventTimestamp.CREATION_TIME,
|
||||
olecf_item)
|
||||
parser_context.ProduceEvent(
|
||||
event_object, parser_chain=parser_chain, file_entry=file_entry)
|
||||
|
||||
if modification_time:
|
||||
event_object = OleCfItemEvent(
|
||||
modification_time, eventdata.EventTimestamp.MODIFICATION_TIME,
|
||||
olecf_item)
|
||||
parser_context.ProduceEvent(
|
||||
event_object, parser_chain=parser_chain, file_entry=file_entry)
|
||||
|
||||
if event_object:
|
||||
result = True
|
||||
|
||||
for sub_item in olecf_item.sub_items:
|
||||
if self._ParseItem(
|
||||
parser_context, file_entry=file_entry, parser_chain=parser_chain,
|
||||
olecf_item=sub_item):
|
||||
result = True
|
||||
|
||||
return result
|
||||
|
||||
def ParseItems(
|
||||
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
|
||||
**unused_kwargs):
|
||||
"""Parses OLECF items.
|
||||
|
||||
Args:
|
||||
parser_context: A parser context object (instance of ParserContext).
|
||||
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
|
||||
The default is None.
|
||||
parser_chain: Optional string containing the parsing chain up to this
|
||||
point. The default is None.
|
||||
root_item: Optional root item of the OLECF file. The default is None.
|
||||
"""
|
||||
if not self._ParseItem(
|
||||
parser_context, file_entry=file_entry, parser_chain=parser_chain,
|
||||
olecf_item=root_item):
|
||||
# If no event object was produced, produce at least one for
|
||||
# the root item.
|
||||
event_object = OleCfItemEvent(
|
||||
0, eventdata.EventTimestamp.CREATION_TIME, root_item)
|
||||
parser_context.ProduceEvent(
|
||||
event_object, parser_chain=parser_chain, file_entry=file_entry)
|
||||
|
||||
def Process(
|
||||
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
|
||||
item_names=None, **kwargs):
|
||||
"""Determine if this is the right plugin for this OLECF file.
|
||||
|
||||
This function takes a list of sub items found in the root of a
|
||||
OLECF file and compares that to a list of required items defined
|
||||
in this plugin.
|
||||
|
||||
Args:
|
||||
parser_context: A parser context object (instance of ParserContext).
|
||||
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
|
||||
The default is None.
|
||||
parser_chain: Optional string containing the parsing chain up to this
|
||||
point. The default is None.
|
||||
root_item: Optional root item of the OLECF file. The default is None.
|
||||
item_names: Optional list of all items discovered in the root.
|
||||
The default is None.
|
||||
|
||||
Raises:
|
||||
errors.WrongPlugin: If the set of required items is not a subset
|
||||
of the available items.
|
||||
ValueError: If the root_item or items are not set.
|
||||
"""
|
||||
if root_item is None or item_names is None:
|
||||
raise ValueError(u'Root item or items are not set.')
|
||||
|
||||
# Add ourselves to the parser chain, which will be used in all subsequent
|
||||
# event creation in this parser.
|
||||
parser_chain = self._BuildParserChain(parser_chain)
|
||||
|
||||
self.ParseItems(
|
||||
parser_context, file_entry=file_entry, parser_chain=parser_chain,
|
||||
root_item=root_item)
|
||||
|
||||
|
||||
olecf.OleCfParser.RegisterPlugin(DefaultOleCFPlugin)
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 The Plaso Project Authors.
|
||||
# Please see the AUTHORS file for details on individual authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""Tests for the OLE Compound File (OLECF) default plugin."""
|
||||
|
||||
import unittest
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from plaso.formatters import olecf as olecf_formatter
|
||||
from plaso.lib import eventdata
|
||||
from plaso.lib import timelib_test
|
||||
from plaso.parsers.olecf_plugins import default
|
||||
from plaso.parsers.olecf_plugins import test_lib
|
||||
|
||||
|
||||
class TestDefaultPluginOleCf(test_lib.OleCfPluginTestCase):
|
||||
"""Tests for the OLECF default plugin."""
|
||||
|
||||
def setUp(self):
|
||||
"""Sets up the needed objects used throughout the test."""
|
||||
self._plugin = default.DefaultOleCFPlugin()
|
||||
|
||||
def testProcess(self):
|
||||
"""Tests the Process function."""
|
||||
test_file = self._GetTestFilePath(['Document.doc'])
|
||||
event_queue_consumer = self._ParseOleCfFileWithPlugin(
|
||||
test_file, self._plugin)
|
||||
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
|
||||
|
||||
self.assertEquals(len(event_objects), 5)
|
||||
|
||||
# Check the Root Entry event.
|
||||
event_object = event_objects[0]
|
||||
|
||||
self.assertEquals(event_object.name, u'Root Entry')
|
||||
|
||||
self.assertEquals(
|
||||
event_object.timestamp_desc, eventdata.EventTimestamp.MODIFICATION_TIME)
|
||||
|
||||
expected_timestamp = timelib_test.CopyStringToTimestamp(
|
||||
'2013-05-16 02:29:49.795')
|
||||
self.assertEquals(event_object.timestamp, expected_timestamp)
|
||||
|
||||
expected_string = (
|
||||
u'Name: Root Entry')
|
||||
|
||||
self._TestGetMessageStrings(event_object, expected_string, expected_string)
|
||||
|
||||
# Check one other entry.
|
||||
event_object = event_objects[1]
|
||||
|
||||
expected_string = u'Name: MsoDataStore'
|
||||
self._TestGetMessageStrings(event_object, expected_string, expected_string)
|
||||
|
||||
expected_timestamp = timelib_test.CopyStringToTimestamp(
|
||||
'2013-05-16 02:29:49.704')
|
||||
self.assertEquals(event_object.timestamp, expected_timestamp)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2014 The Plaso Project Authors.
|
||||
# Please see the AUTHORS file for details on individual authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""This file contains the necessary interface for OLECF plugins."""
|
||||
|
||||
import abc
|
||||
import logging
|
||||
|
||||
from plaso.lib import errors
|
||||
from plaso.parsers import plugins
|
||||
|
||||
|
||||
class OlecfPlugin(plugins.BasePlugin):
|
||||
"""An OLECF plugin for Plaso."""
|
||||
|
||||
NAME = 'olecf'
|
||||
|
||||
# List of tables that should be present in the database, for verification.
|
||||
REQUIRED_ITEMS = frozenset([])
|
||||
|
||||
def GetTimestamps(self, olecf_item):
|
||||
"""Takes an OLECF object and returns extracted timestamps.
|
||||
|
||||
Args:
|
||||
olecf_item: A OLECF item (instance of pyolecf.item).
|
||||
|
||||
Returns:
|
||||
A tuple of two timestamps: created and modified.
|
||||
"""
|
||||
if not olecf_item:
|
||||
return None, None
|
||||
|
||||
try:
|
||||
creation_time = olecf_item.get_creation_time_as_integer()
|
||||
except OverflowError as exception:
|
||||
logging.warning(
|
||||
u'Unable to read the creation time with error: {0:s}'.format(
|
||||
exception))
|
||||
creation_time = 0
|
||||
|
||||
try:
|
||||
modification_time = olecf_item.get_modification_time_as_integer()
|
||||
except OverflowError as exception:
|
||||
logging.warning(
|
||||
u'Unable to read the modification time with error: {0:s}'.format(
|
||||
exception))
|
||||
modification_time = 0
|
||||
|
||||
# If no useful events, return early.
|
||||
if not creation_time and not modification_time:
|
||||
return None, None
|
||||
|
||||
# Office template documents sometimes contain a creation time
|
||||
# of -1 (0xffffffffffffffff).
|
||||
if creation_time == 0xffffffffffffffffL:
|
||||
creation_time = 0
|
||||
|
||||
return creation_time, modification_time
|
||||
|
||||
@abc.abstractmethod
|
||||
def ParseItems(
|
||||
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
|
||||
items=None, **kwargs):
|
||||
"""Parses OLECF items.
|
||||
|
||||
Args:
|
||||
parser_context: A parser context object (instance of ParserContext).
|
||||
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
|
||||
The default is None.
|
||||
parser_chain: Optional string containing the parsing chain up to this
|
||||
point. The default is None.
|
||||
root_item: Optional root item of the OLECF file. The default is None.
|
||||
item_names: Optional list of all items discovered in the root.
|
||||
The default is None.
|
||||
"""
|
||||
|
||||
def Process(
|
||||
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
|
||||
item_names=None, **kwargs):
|
||||
"""Determine if this is the right plugin for this OLECF file.
|
||||
|
||||
This function takes a list of sub items found in the root of a
|
||||
OLECF file and compares that to a list of required items defined
|
||||
in this plugin.
|
||||
|
||||
Args:
|
||||
parser_context: A parser context object (instance of ParserContext).
|
||||
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
|
||||
The default is None.
|
||||
parser_chain: Optional string containing the parsing chain up to this
|
||||
point. The default is None.
|
||||
root_item: Optional root item of the OLECF file. The default is None.
|
||||
item_names: Optional list of all items discovered in the root.
|
||||
The default is None.
|
||||
|
||||
Raises:
|
||||
errors.WrongPlugin: If the set of required items is not a subset
|
||||
of the available items.
|
||||
ValueError: If the root_item or items are not set.
|
||||
"""
|
||||
if root_item is None or item_names is None:
|
||||
raise ValueError(u'Root item or items are not set.')
|
||||
|
||||
if not frozenset(item_names) >= self.REQUIRED_ITEMS:
|
||||
raise errors.WrongPlugin(
|
||||
u'Not the correct items for: {0:s}'.format(self.NAME))
|
||||
|
||||
# This will raise if unhandled keyword arguments are passed.
|
||||
super(OlecfPlugin, self).Process(parser_context, **kwargs)
|
||||
|
||||
# Add ourselves to the parser chain, which will be used in all subsequent
|
||||
# event creation in this parser.
|
||||
parser_chain = self._BuildParserChain(parser_chain)
|
||||
|
||||
items = []
|
||||
for item_string in self.REQUIRED_ITEMS:
|
||||
item = root_item.get_sub_item_by_name(item_string)
|
||||
|
||||
if item:
|
||||
items.append(item)
|
||||
|
||||
self.ParseItems(
|
||||
parser_context, file_entry=file_entry, parser_chain=parser_chain,
|
||||
root_item=root_item, items=items)
|
||||
|
||||
|
||||
class OleDefinitions(object):
|
||||
"""Convenience class for OLE definitions."""
|
||||
|
||||
VT_I2 = 0x0002
|
||||
VT_I4 = 0x0003
|
||||
VT_BOOL = 0x000b
|
||||
VT_LPSTR = 0x001e
|
||||
VT_LPWSTR = 0x001e
|
||||
VT_FILETIME = 0x0040
|
||||
VT_CF = 0x0047
|
||||
@@ -0,0 +1,433 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2014 The Plaso Project Authors.
|
||||
# Please see the AUTHORS file for details on individual authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""Plugin to parse the OLECF summary/document summary information items."""
|
||||
|
||||
from plaso.events import time_events
|
||||
from plaso.lib import eventdata
|
||||
from plaso.parsers import olecf
|
||||
from plaso.parsers.olecf_plugins import interface
|
||||
|
||||
|
||||
class OleCfSummaryInfoEvent(time_events.FiletimeEvent):
|
||||
"""Convenience class for an OLECF Summary info event."""
|
||||
|
||||
DATA_TYPE = 'olecf:summary_info'
|
||||
|
||||
def __init__(self, timestamp, usage, attributes):
|
||||
"""Initializes the event.
|
||||
|
||||
Args:
|
||||
timestamp: The FILETIME timestamp value.
|
||||
usage: The usage string, describing the timestamp value.
|
||||
attributes: A dict object containing all extracted attributes.
|
||||
"""
|
||||
super(OleCfSummaryInfoEvent, self).__init__(
|
||||
timestamp, usage)
|
||||
|
||||
self.name = u'Summary Information'
|
||||
|
||||
for attribute_name, attribute_value in attributes.iteritems():
|
||||
setattr(self, attribute_name, attribute_value)
|
||||
|
||||
|
||||
# TODO: Move this class to a higher level (to the interface)
|
||||
# so the these functions can be shared by other plugins.
|
||||
class OleCfSummaryInfo(object):
|
||||
"""An OLECF Summary Info object."""
|
||||
|
||||
_CLASS_IDENTIFIER = 'f29f85e0-4ff9-1068-ab91-08002b27b3d9'
|
||||
|
||||
_PROPERTY_NAMES_INT32 = {
|
||||
0x000e: 'number_of_pages', # PIDSI_PAGECOUNT
|
||||
0x000f: 'number_of_words', # PIDSI_WORDCOUNT
|
||||
0x0010: 'number_of_characters', # PIDSI_CHARCOUNT
|
||||
0x0013: 'security', # PIDSI_SECURITY
|
||||
}
|
||||
|
||||
_PROPERTY_NAMES_STRING = {
|
||||
0x0002: 'title', # PIDSI_TITLE
|
||||
0x0003: 'subject', # PIDSI_SUBJECT
|
||||
0x0004: 'author', # PIDSI_AUTHOR
|
||||
0x0005: 'keywords', # PIDSI_KEYWORDS
|
||||
0x0006: 'comments', # PIDSI_COMMENTS
|
||||
0x0007: 'template', # PIDSI_TEMPLATE
|
||||
0x0008: 'last_saved_by', # PIDSI_LASTAUTHOR
|
||||
0x0009: 'revision_number', # PIDSI_REVNUMBER
|
||||
0x0012: 'application', # PIDSI_APPNAME
|
||||
}
|
||||
|
||||
PIDSI_CODEPAGE = 0x0001
|
||||
PIDSI_EDITTIME = 0x000a
|
||||
PIDSI_LASTPRINTED = 0x000b
|
||||
PIDSI_CREATE_DTM = 0x000c
|
||||
PIDSI_LASTSAVE_DTM = 0x000d
|
||||
PIDSI_THUMBNAIL = 0x0011
|
||||
|
||||
def __init__(self, olecf_item):
|
||||
"""Initialize the OLECF summary object.
|
||||
|
||||
Args:
|
||||
olecf_item: The OLECF item (instance of pyolecf.property_set_stream).
|
||||
"""
|
||||
super(OleCfSummaryInfo, self).__init__()
|
||||
self.attributes = {}
|
||||
self.events = []
|
||||
|
||||
self._InitFromPropertySet(olecf_item.set)
|
||||
|
||||
def _InitFromPropertySet(self, property_set):
|
||||
"""Initializes the object from a property set.
|
||||
|
||||
Args:
|
||||
property_set: The OLECF property set (pyolecf.property_set).
|
||||
"""
|
||||
# Combine the values of multiple property sections
|
||||
# but do not override properties that are already set.
|
||||
for property_section in property_set.sections:
|
||||
if property_section.class_identifier != self._CLASS_IDENTIFIER:
|
||||
continue
|
||||
for property_value in property_section.properties:
|
||||
self._InitFromPropertyValue(property_value)
|
||||
|
||||
def _InitFromPropertyValue(self, property_value):
|
||||
"""Initializes the object from a property value.
|
||||
|
||||
Args:
|
||||
property_value: The OLECF property value (pyolecf.property_value).
|
||||
"""
|
||||
if property_value.type == interface.OleDefinitions.VT_I2:
|
||||
self._InitFromPropertyValueTypeInt16(property_value)
|
||||
|
||||
elif property_value.type == interface.OleDefinitions.VT_I4:
|
||||
self._InitFromPropertyValueTypeInt32(property_value)
|
||||
|
||||
elif (property_value.type == interface.OleDefinitions.VT_LPSTR or
|
||||
property_value.type == interface.OleDefinitions.VT_LPWSTR):
|
||||
self._InitFromPropertyValueTypeString(property_value)
|
||||
|
||||
elif property_value.type == interface.OleDefinitions.VT_FILETIME:
|
||||
self._InitFromPropertyValueTypeFiletime(property_value)
|
||||
|
||||
def _InitFromPropertyValueTypeInt16(self, property_value):
|
||||
"""Initializes the object from a 16-bit int type property value.
|
||||
|
||||
Args:
|
||||
property_value: The OLECF property value (pyolecf.property_value
|
||||
of type VT_I2).
|
||||
"""
|
||||
if property_value.identifier == self.PIDSI_CODEPAGE:
|
||||
# TODO: can the codepage vary per property section?
|
||||
# And is it needed to interpret the ASCII strings?
|
||||
# codepage = property_value.data_as_integer
|
||||
pass
|
||||
|
||||
def _InitFromPropertyValueTypeInt32(self, property_value):
|
||||
"""Initializes the object from a 32-bit int type property value.
|
||||
|
||||
Args:
|
||||
property_value: The OLECF property value (pyolecf.property_value
|
||||
of type VT_I4).
|
||||
"""
|
||||
property_name = self._PROPERTY_NAMES_INT32.get(
|
||||
property_value.identifier, None)
|
||||
|
||||
if property_name and not property_name in self.attributes:
|
||||
self.attributes[property_name] = property_value.data_as_integer
|
||||
|
||||
def _InitFromPropertyValueTypeString(self, property_value):
|
||||
"""Initializes the object from a string type property value.
|
||||
|
||||
Args:
|
||||
property_value: The OLECF property value (pyolecf.property_value
|
||||
of type VT_LPSTR or VT_LPWSTR).
|
||||
"""
|
||||
property_name = self._PROPERTY_NAMES_STRING.get(
|
||||
property_value.identifier, None)
|
||||
|
||||
if property_name and not property_name in self.attributes:
|
||||
self.attributes[property_name] = property_value.data_as_string
|
||||
|
||||
def _InitFromPropertyValueTypeFiletime(self, property_value):
|
||||
"""Initializes the object from a filetime type property value.
|
||||
|
||||
Args:
|
||||
property_value: The OLECF property value (pyolecf.property_value
|
||||
of type VT_FILETIME).
|
||||
"""
|
||||
if property_value.identifier == self.PIDSI_LASTPRINTED:
|
||||
self.events.append(
|
||||
(property_value.data_as_integer, 'Document Last Printed Time'))
|
||||
|
||||
elif property_value.identifier == self.PIDSI_CREATE_DTM:
|
||||
self.events.append(
|
||||
(property_value.data_as_integer, 'Document Creation Time'))
|
||||
|
||||
elif property_value.identifier == self.PIDSI_LASTSAVE_DTM:
|
||||
self.events.append(
|
||||
(property_value.data_as_integer, 'Document Last Save Time'))
|
||||
|
||||
elif property_value.identifier == self.PIDSI_EDITTIME:
|
||||
# property_name = 'total_edit_time'
|
||||
# TODO: handle duration.
|
||||
pass
|
||||
|
||||
|
||||
class OleCfDocumentSummaryInfoEvent(time_events.FiletimeEvent):
|
||||
"""Convenience class for an OLECF Document Summary info event."""
|
||||
|
||||
DATA_TYPE = 'olecf:document_summary_info'
|
||||
|
||||
_CLASS_IDENTIFIER = 'd5cdd502-2e9c-101b-9397-08002b2cf9ae'
|
||||
|
||||
_PROPERTY_NAMES_BOOL = {
|
||||
0x0013: 'shared_document', # PIDDSI_SHAREDDOC
|
||||
}
|
||||
|
||||
_PROPERTY_NAMES_INT32 = {
|
||||
0x0004: 'number_of_bytes', # PIDDSI_BYTECOUNT
|
||||
0x0005: 'number_of_lines', # PIDDSI_LINECOUNT
|
||||
0x0006: 'number_of_paragraphs', # PIDDSI_PARCOUNT
|
||||
0x0007: 'number_of_slides', # PIDDSI_SLIDECOUNT
|
||||
0x0008: 'number_of_notes', # PIDDSI_NOTECOUNT
|
||||
0x0009: 'number_of_hidden_slides', # PIDDSI_HIDDENCOUNT
|
||||
0x000a: 'number_of_clips', # PIDDSI_MMCLIPCOUNT
|
||||
0x0011: 'number_of_characters_with_white_space', # PIDDSI_CCHWITHSPACES
|
||||
0x0017: 'application_version', # PIDDSI_VERSION
|
||||
}
|
||||
|
||||
_PROPERTY_NAMES_STRING = {
|
||||
0x000e: 'manager', # PIDDSI_MANAGER
|
||||
0x000f: 'company', # PIDDSI_COMPANY
|
||||
0x001a: 'content_type', # PIDDSI_CONTENTTYPE
|
||||
0x001b: 'content_status', # PIDDSI_CONTENTSTATUS
|
||||
0x001c: 'language', # PIDDSI_LANGUAGE
|
||||
0x001d: 'document_version', # PIDDSI_DOCVERSION
|
||||
}
|
||||
|
||||
PIDDSI_CODEPAGE = 0x0001
|
||||
PIDDSI_CATEGORY = 0x0002
|
||||
PIDDSI_PRESFORMAT = 0x0003
|
||||
PIDDSI_SCALE = 0x000b
|
||||
PIDDSI_HEADINGPAIR = 0x000c
|
||||
PIDDSI_DOCPARTS = 0x000d
|
||||
PIDDSI_LINKSDIRTY = 0x0010
|
||||
PIDDSI_VERSION = 0x0017
|
||||
|
||||
def __init__(self, timestamp, usage, olecf_item):
|
||||
"""Initializes the event.
|
||||
|
||||
Args:
|
||||
timestamp: The FILETIME timestamp value.
|
||||
usage: The usage string, describing the timestamp value.
|
||||
olecf_item: The OLECF item (pyolecf.property_set_stream).
|
||||
"""
|
||||
super(OleCfDocumentSummaryInfoEvent, self).__init__(
|
||||
timestamp, usage)
|
||||
|
||||
self.name = u'Document Summary Information'
|
||||
|
||||
self._InitFromPropertySet(olecf_item.set)
|
||||
|
||||
def _InitFromPropertySet(self, property_set):
|
||||
"""Initializes the event from a property set.
|
||||
|
||||
Args:
|
||||
property_set: The OLECF property set (pyolecf.property_set).
|
||||
"""
|
||||
# Combine the values of multiple property sections
|
||||
# but do not override properties that are already set.
|
||||
for property_section in property_set.sections:
|
||||
if property_section.class_identifier != self._CLASS_IDENTIFIER:
|
||||
continue
|
||||
for property_value in property_section.properties:
|
||||
self._InitFromPropertyValue(property_value)
|
||||
|
||||
def _InitFromPropertyValue(self, property_value):
|
||||
"""Initializes the event from a property value.
|
||||
|
||||
Args:
|
||||
property_value: The OLECF property value (pyolecf.property_value).
|
||||
"""
|
||||
if property_value.type == interface.OleDefinitions.VT_I2:
|
||||
self._InitFromPropertyValueTypeInt16(property_value)
|
||||
|
||||
elif property_value.type == interface.OleDefinitions.VT_I4:
|
||||
self._InitFromPropertyValueTypeInt32(property_value)
|
||||
|
||||
elif property_value.type == interface.OleDefinitions.VT_BOOL:
|
||||
self._InitFromPropertyValueTypeBool(property_value)
|
||||
|
||||
elif (property_value.type == interface.OleDefinitions.VT_LPSTR or
|
||||
property_value.type == interface.OleDefinitions.VT_LPWSTR):
|
||||
self._InitFromPropertyValueTypeString(property_value)
|
||||
|
||||
def _InitFromPropertyValueTypeInt16(self, property_value):
|
||||
"""Initializes the event from a 16-bit int type property value.
|
||||
|
||||
Args:
|
||||
property_value: The OLECF property value (pyolecf.property_value
|
||||
of type VT_I2).
|
||||
"""
|
||||
if property_value.identifier == self.PIDDSI_CODEPAGE:
|
||||
# TODO: can the codepage vary per property section?
|
||||
# And is it needed to interpret the ASCII strings?
|
||||
# codepage = property_value.data_as_integer
|
||||
pass
|
||||
|
||||
def _InitFromPropertyValueTypeInt32(self, property_value):
|
||||
"""Initializes the event from a 32-bit int type property value.
|
||||
|
||||
Args:
|
||||
property_value: The OLECF property value (pyolecf.property_value
|
||||
of type VT_I4).
|
||||
"""
|
||||
property_name = self._PROPERTY_NAMES_INT32.get(
|
||||
property_value.identifier, None)
|
||||
|
||||
# The application version consists of 2 16-bit values that make up
|
||||
# the version number. Where the upper 16-bit is the major number
|
||||
# and the lower 16-bit the minor number.
|
||||
if property_value.identifier == self.PIDDSI_VERSION:
|
||||
application_version = property_value.data_as_integer
|
||||
setattr(self, property_name, u'{0:d}.{1:d}'.format(
|
||||
application_version >> 16, application_version & 0xffff))
|
||||
|
||||
elif property_name and not hasattr(self, property_name):
|
||||
setattr(self, property_name, property_value.data_as_integer)
|
||||
|
||||
def _InitFromPropertyValueTypeBool(self, property_value):
|
||||
"""Initializes the event from a boolean type property value.
|
||||
|
||||
Args:
|
||||
property_value: The OLECF property value (pyolecf.property_value
|
||||
of type VT_BOOL).
|
||||
"""
|
||||
property_name = self._PROPERTY_NAMES_BOOL.get(
|
||||
property_value.identifier, None)
|
||||
|
||||
if property_name and not hasattr(self, property_name):
|
||||
setattr(self, property_name, property_value.data_as_boolean)
|
||||
|
||||
def _InitFromPropertyValueTypeString(self, property_value):
|
||||
"""Initializes the event from a string type property value.
|
||||
|
||||
Args:
|
||||
property_value: The OLECF property value (pyolecf.property_value
|
||||
of type VT_LPSTR or VT_LPWSTR).
|
||||
"""
|
||||
property_name = self._PROPERTY_NAMES_STRING.get(
|
||||
property_value.identifier, None)
|
||||
|
||||
if property_name and not hasattr(self, property_name):
|
||||
setattr(self, property_name, property_value.data_as_string)
|
||||
|
||||
|
||||
class DocumentSummaryOlecfPlugin(interface.OlecfPlugin):
|
||||
"""Plugin that parses DocumentSummaryInformation item from an OLECF file."""
|
||||
|
||||
NAME = 'olecf_document_summary'
|
||||
DESCRIPTION = u'Parser for a DocumentSummaryInformation OLECF stream.'
|
||||
|
||||
# pylint: disable=anomalous-backslash-in-string
|
||||
REQUIRED_ITEMS = frozenset([u'\005DocumentSummaryInformation'])
|
||||
|
||||
def ParseItems(
|
||||
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
|
||||
items=None, **unused_kwargs):
|
||||
"""Parses a document summary information OLECF item.
|
||||
|
||||
Args:
|
||||
parser_context: A parser context object (instance of ParserContext).
|
||||
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
|
||||
The default is None.
|
||||
parser_chain: Optional string containing the parsing chain up to this
|
||||
point. The default is None.
|
||||
root_item: Optional root item of the OLECF file. The default is None.
|
||||
item_names: Optional list of all items discovered in the root.
|
||||
The default is None.
|
||||
"""
|
||||
root_creation_time, root_modification_time = self.GetTimestamps(root_item)
|
||||
|
||||
for item in items:
|
||||
if root_creation_time:
|
||||
event_object = OleCfDocumentSummaryInfoEvent(
|
||||
root_creation_time, eventdata.EventTimestamp.CREATION_TIME, item)
|
||||
parser_context.ProduceEvent(
|
||||
event_object, parser_chain=parser_chain, file_entry=file_entry)
|
||||
|
||||
if root_modification_time:
|
||||
event_object = OleCfDocumentSummaryInfoEvent(
|
||||
root_modification_time, eventdata.EventTimestamp.MODIFICATION_TIME,
|
||||
item)
|
||||
parser_context.ProduceEvent(
|
||||
event_object, parser_chain=parser_chain, file_entry=file_entry)
|
||||
|
||||
|
||||
class SummaryInfoOlecfPlugin(interface.OlecfPlugin):
|
||||
"""Plugin that parses the SummaryInformation item from an OLECF file."""
|
||||
|
||||
NAME = 'olecf_summary'
|
||||
DESCRIPTION = u'Parser for a SummaryInformation OLECF stream.'
|
||||
|
||||
# pylint: disable=anomalous-backslash-in-string
|
||||
REQUIRED_ITEMS = frozenset([u'\005SummaryInformation'])
|
||||
|
||||
def ParseItems(
|
||||
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
|
||||
items=None, **unused_kwargs):
|
||||
"""Parses a summary information OLECF item.
|
||||
|
||||
Args:
|
||||
parser_context: A parser context object (instance of ParserContext).
|
||||
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
|
||||
The default is None.
|
||||
parser_chain: Optional string containing the parsing chain up to this
|
||||
point. The default is None.
|
||||
root_item: Optional root item of the OLECF file. The default is None.
|
||||
item_names: Optional list of all items discovered in the root.
|
||||
The default is None.
|
||||
"""
|
||||
root_creation_time, root_modification_time = self.GetTimestamps(root_item)
|
||||
|
||||
for item in items:
|
||||
summary_information_object = OleCfSummaryInfo(item)
|
||||
|
||||
for timestamp, timestamp_description in summary_information_object.events:
|
||||
event_object = OleCfSummaryInfoEvent(
|
||||
timestamp, timestamp_description,
|
||||
summary_information_object.attributes)
|
||||
parser_context.ProduceEvent(
|
||||
event_object, parser_chain=parser_chain, file_entry=file_entry)
|
||||
|
||||
if root_creation_time:
|
||||
event_object = OleCfSummaryInfoEvent(
|
||||
root_creation_time, eventdata.EventTimestamp.CREATION_TIME,
|
||||
summary_information_object.attributes)
|
||||
parser_context.ProduceEvent(
|
||||
event_object, parser_chain=parser_chain, file_entry=file_entry)
|
||||
|
||||
if root_modification_time:
|
||||
event_object = OleCfSummaryInfoEvent(
|
||||
root_modification_time, eventdata.EventTimestamp.MODIFICATION_TIME,
|
||||
summary_information_object.attributes)
|
||||
parser_context.ProduceEvent(
|
||||
event_object, parser_chain=parser_chain, file_entry=file_entry)
|
||||
|
||||
|
||||
olecf.OleCfParser.RegisterPlugins(
|
||||
[DocumentSummaryOlecfPlugin, SummaryInfoOlecfPlugin])
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 The Plaso Project Authors.
|
||||
# Please see the AUTHORS file for details on individual authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""Tests for the OLE Compound File summary and document summary plugins."""
|
||||
|
||||
import unittest
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from plaso.formatters import olecf as olecf_formatter
|
||||
from plaso.lib import timelib_test
|
||||
from plaso.parsers.olecf_plugins import summary
|
||||
from plaso.parsers.olecf_plugins import test_lib
|
||||
|
||||
|
||||
class TestSummaryInfoOlecfPlugin(test_lib.OleCfPluginTestCase):
|
||||
"""Tests for the OLECF summary information plugin."""
|
||||
|
||||
def setUp(self):
|
||||
"""Sets up the needed objects used throughout the test."""
|
||||
self._summary_plugin = summary.SummaryInfoOlecfPlugin()
|
||||
self._test_file = self._GetTestFilePath(['Document.doc'])
|
||||
|
||||
def testProcess(self):
|
||||
"""Tests the Process function on a SummaryInformation stream."""
|
||||
event_queue_consumer = self._ParseOleCfFileWithPlugin(
|
||||
self._test_file, self._summary_plugin)
|
||||
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
|
||||
|
||||
# There is one summary info stream with three event objects.
|
||||
self.assertEquals(len(event_objects), 3)
|
||||
|
||||
event_object = event_objects[0]
|
||||
self.assertEquals(event_object.name, u'Summary Information')
|
||||
|
||||
self.assertEquals(event_object.title, u'Table of Context')
|
||||
self.assertEquals(event_object.author, u'DAVID NIDES')
|
||||
self.assertEquals(event_object.template, u'Normal.dotm')
|
||||
self.assertEquals(event_object.last_saved_by, u'Nides')
|
||||
self.assertEquals(event_object.revision_number, u'4')
|
||||
self.assertEquals(event_object.number_of_characters, 18)
|
||||
self.assertEquals(event_object.application, u'Microsoft Office Word')
|
||||
self.assertEquals(event_object.security, 0)
|
||||
|
||||
self.assertEquals(event_object.timestamp_desc, u'Document Creation Time')
|
||||
expected_timestamp = timelib_test.CopyStringToTimestamp(
|
||||
'2012-12-10 18:38:00')
|
||||
self.assertEquals(event_object.timestamp, expected_timestamp)
|
||||
|
||||
expected_msg = (
|
||||
u'Title: Table of Context '
|
||||
u'Author: DAVID NIDES '
|
||||
u'Template: Normal.dotm '
|
||||
u'Revision number: 4 '
|
||||
u'Last saved by: Nides '
|
||||
u'Number of pages: 1 '
|
||||
u'Number of words: 3 '
|
||||
u'Number of characters: 18 '
|
||||
u'Application: Microsoft Office Word '
|
||||
u'Security: 0')
|
||||
|
||||
expected_msg_short = (
|
||||
u'Title: Table of Context '
|
||||
u'Author: DAVID NIDES '
|
||||
u'Revision number: 4')
|
||||
|
||||
# TODO: add support for:
|
||||
# u'Total edit time (secs): 0 '
|
||||
|
||||
self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short)
|
||||
|
||||
|
||||
class TestDocumentSummaryInfoOlecfPlugin(test_lib.OleCfPluginTestCase):
|
||||
"""Tests for the OLECF document summary information plugin."""
|
||||
|
||||
def setUp(self):
|
||||
"""Sets up the needed objects used throughout the test."""
|
||||
self._document_summary_plugin = summary.DocumentSummaryOlecfPlugin()
|
||||
self._test_file = self._GetTestFilePath(['Document.doc'])
|
||||
|
||||
def testProcess(self):
|
||||
"""Tests the Process function on a DocumentSummaryInformation stream."""
|
||||
event_queue_consumer = self._ParseOleCfFileWithPlugin(
|
||||
self._test_file, self._document_summary_plugin)
|
||||
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
|
||||
|
||||
# There should only be one summary info stream with one event.
|
||||
self.assertEquals(len(event_objects), 1)
|
||||
|
||||
event_object = event_objects[0]
|
||||
self.assertEquals(event_object.name, u'Document Summary Information')
|
||||
|
||||
self.assertEquals(event_object.number_of_lines, 1)
|
||||
self.assertEquals(event_object.number_of_paragraphs, 1)
|
||||
self.assertEquals(event_object.company, u'KPMG')
|
||||
self.assertFalse(event_object.shared_document)
|
||||
self.assertEquals(event_object.application_version, u'14.0')
|
||||
|
||||
# TODO: add support for:
|
||||
# self.assertEquals(event_object.is_shared, False)
|
||||
|
||||
expected_msg = (
|
||||
u'Number of lines: 1 '
|
||||
u'Number of paragraphs: 1 '
|
||||
u'Company: KPMG '
|
||||
u'Shared document: False '
|
||||
u'Application version: 14.0')
|
||||
|
||||
expected_msg_short = (
|
||||
u'Company: KPMG')
|
||||
|
||||
self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2014 The Plaso Project Authors.
|
||||
# Please see the AUTHORS file for details on individual authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the 'License');
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""OLECF plugin related functions and classes for testing."""
|
||||
|
||||
from dfvfs.lib import definitions
|
||||
from dfvfs.path import factory as path_spec_factory
|
||||
from dfvfs.resolver import resolver as path_spec_resolver
|
||||
|
||||
import pyolecf
|
||||
|
||||
from plaso.engine import single_process
|
||||
from plaso.parsers import test_lib
|
||||
|
||||
|
||||
class OleCfPluginTestCase(test_lib.ParserTestCase):
|
||||
"""The unit test case for OLE CF based plugins."""
|
||||
|
||||
def _OpenOleCfFile(self, path, codepage='cp1252'):
|
||||
"""Opens an OLE compound file and returns back a pyolecf.file object.
|
||||
|
||||
Args:
|
||||
path: The path to the OLE CF test file.
|
||||
codepate: Optional codepage. The default is cp1252.
|
||||
"""
|
||||
path_spec = path_spec_factory.Factory.NewPathSpec(
|
||||
definitions.TYPE_INDICATOR_OS, location=path)
|
||||
file_entry = path_spec_resolver.Resolver.OpenFileEntry(path_spec)
|
||||
|
||||
file_object = file_entry.GetFileObject()
|
||||
olecf_file = pyolecf.file()
|
||||
olecf_file.set_ascii_codepage(codepage)
|
||||
|
||||
olecf_file.open_file_object(file_object)
|
||||
|
||||
return olecf_file
|
||||
|
||||
def _ParseOleCfFileWithPlugin(
|
||||
self, path, plugin_object, knowledge_base_values=None):
|
||||
"""Parses a file as an OLE compound file and returns an event generator.
|
||||
|
||||
Args:
|
||||
path: The path to the OLE CF test file.
|
||||
plugin_object: The plugin object that is used to extract an event
|
||||
generator.
|
||||
knowledge_base_values: optional dict containing the knowledge base
|
||||
values. The default is None.
|
||||
|
||||
Returns:
|
||||
An event object queue consumer object (instance of
|
||||
TestEventObjectQueueConsumer).
|
||||
"""
|
||||
event_queue = single_process.SingleProcessQueue()
|
||||
event_queue_consumer = test_lib.TestEventObjectQueueConsumer(event_queue)
|
||||
|
||||
parse_error_queue = single_process.SingleProcessQueue()
|
||||
|
||||
parser_context = self._GetParserContext(
|
||||
event_queue, parse_error_queue,
|
||||
knowledge_base_values=knowledge_base_values)
|
||||
olecf_file = self._OpenOleCfFile(path)
|
||||
|
||||
# Get a list of all root items from the OLE CF file.
|
||||
root_item = olecf_file.root_item
|
||||
item_names = [item.name for item in root_item.sub_items]
|
||||
|
||||
plugin_object.Process(
|
||||
parser_context, root_item=root_item, item_names=item_names)
|
||||
|
||||
return event_queue_consumer
|
||||
Reference in New Issue
Block a user