"""Parser for Windows NT shell items."""
import pyfwsi
from dfdatetime import fat_date_time as dfdatetime_fat_date_time
from plaso.containers import windows_events
from plaso.helpers.windows import shell_folders
from plaso.lib import definitions
[docs]
class ShellItemsParser:
"""Parses for Windows NT shell items."""
NAME = "shell_items"
_PATH_ESCAPE_CHARACTERS = {"\\": "\\\\"}
_PATH_ESCAPE_CHARACTERS.update(definitions.NON_PRINTABLE_CHARACTERS)
[docs]
def __init__(self, origin):
"""Initializes the parser.
Args:
origin (str): origin of the event.
"""
super().__init__()
self._origin = origin
self._path_escape_characters = str.maketrans(self._PATH_ESCAPE_CHARACTERS)
self._path_segments = []
def _GetDateTime(self, fat_date_time):
"""Retrieves the date and time from a FAT date time.
Args:
fat_date_time (int): FAT date time.
Returns:
dfdatetime.DateTimeValues: date and time or None if not set.
"""
if not fat_date_time:
return None
return dfdatetime_fat_date_time.FATDateTime(fat_date_time=fat_date_time)
def _GetSanitizedPathString(self, path):
"""Retrieves a sanitize path string.
Args:
path (str): path.
Returns:
str: sanitized path string.
"""
if not path:
return None
return path.translate(self._path_escape_characters)
def _ParseShellItem(self, parser_mediator, shell_item):
"""Parses a shell item.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers
and other components, such as storage and dfVFS.
shell_item (pyfwsi.item): shell item.
"""
path_segment = self._ParseShellItemPathSegment(shell_item)
self._path_segments.append(path_segment)
# TODO: generate event_data for non file_entry shell items.
if isinstance(shell_item, pyfwsi.file_entry):
event_data = windows_events.WindowsShellItemFileEntryEventData()
event_data.modification_time = self._GetDateTime(
shell_item.get_modification_time_as_integer()
)
event_data.name = self._GetSanitizedPathString(shell_item.name)
event_data.origin = self._origin
event_data.shell_item_path = self.CopyToPath()
number_of_event_data = 0
for extension_block in shell_item.extension_blocks:
if isinstance(extension_block, pyfwsi.file_entry_extension):
file_reference = extension_block.file_reference
if file_reference:
mft_entry_number = file_reference & 0xFFFFFFFFFFFF
sequence_number = file_reference >> 48
file_reference = f"{mft_entry_number:d}-{sequence_number:d}"
event_data.access_time = self._GetDateTime(
extension_block.get_access_time_as_integer()
)
event_data.creation_time = self._GetDateTime(
extension_block.get_creation_time_as_integer()
)
event_data.file_reference = file_reference
event_data.localized_name = extension_block.localized_name
event_data.long_name = self._GetSanitizedPathString(
extension_block.long_name
)
# TODO: change to generate an event_data for each extension block.
if (
event_data.access_time
or event_data.creation_time
or event_data.modification_time
):
parser_mediator.ProduceEventData(event_data)
number_of_event_data += 1
# TODO: change to generate an event_data for each shell item.
if not number_of_event_data and event_data.modification_time:
parser_mediator.ProduceEventData(event_data)
def _ParseShellItemPathSegment(self, shell_item):
"""Parses a shell item path segment.
Args:
shell_item (pyfwsi.item): shell item.
Returns:
str: shell item path segment.
"""
path_segment = None
if isinstance(shell_item, pyfwsi.control_panel_category):
# TODO: map identifier to human readable string.
path_segment = f"<Control panel category: {shell_item.identifier:d}>"
elif isinstance(shell_item, pyfwsi.control_panel_item):
# TODO: map identifier to human readable string.
path_segment = f"<Control panel item: {shell_item.identifier:s}>"
elif isinstance(shell_item, pyfwsi.file_entry):
long_name = ""
for extension_block in shell_item.extension_blocks:
if isinstance(extension_block, pyfwsi.file_entry_extension):
long_name = self._GetSanitizedPathString(extension_block.long_name)
if long_name:
path_segment = long_name
elif shell_item.name:
path_segment = self._GetSanitizedPathString(shell_item.name)
elif isinstance(shell_item, pyfwsi.network_location):
if shell_item.location:
path_segment = shell_item.location
elif isinstance(shell_item, pyfwsi.root_folder):
description = shell_folders.WindowsShellFoldersHelper.GetDescription(
shell_item.shell_folder_identifier
)
if description:
path_segment = description
else:
path_segment = f"{{{shell_item.shell_folder_identifier:s}}}"
path_segment = f"<{path_segment}>"
elif isinstance(shell_item, pyfwsi.users_property_view):
path_segment = "<Users property view>"
elif isinstance(shell_item, pyfwsi.volume):
if shell_item.name:
path_segment = self._GetSanitizedPathString(shell_item.name)
elif shell_item.identifier:
path_segment = f"{{{shell_item.identifier:s}}}"
if path_segment is None:
path_segment = f"<UNKNOWN: 0x{shell_item.class_type:02x}>"
return path_segment
[docs]
def CopyToPath(self):
"""Copies the shell items to a path.
Returns:
str: converted shell item list path or None.
"""
number_of_path_segments = len(self._path_segments)
if number_of_path_segments == 0:
return None
strings = [self._path_segments[0]]
number_of_path_segments -= 1
for path_segment in self._path_segments[1:]:
# Remove a trailing \ except for the last path segment.
if path_segment.endswith("\\\\") and number_of_path_segments > 1:
path_segment = path_segment[:-2]
if (path_segment.startswith("<") and path_segment.endswith(">")) or len(
strings
) == 1:
strings.append(f" {path_segment:s}")
elif path_segment.startswith("\\"):
strings.append(f"{path_segment:s}")
else:
strings.append(f"\\\\{path_segment:s}")
number_of_path_segments -= 1
return "".join(strings)
[docs]
def GetUpperPathSegment(self):
"""Retrieves the upper shell item path segment.
Returns:
str: shell item path segment or "N/A".
"""
if not self._path_segments:
return "N/A"
return self._path_segments[-1]
[docs]
def ParseByteStream(
self, parser_mediator, byte_stream, parent_path_segments=None, codepage="cp1252"
):
"""Parses the shell items from the byte stream.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers
and other components, such as storage and dfVFS.
byte_stream (bytes): shell items data.
parent_path_segments (Optional[list[str]]): parent shell item path
segments.
codepage (Optional[str]): byte stream codepage.
"""
if parent_path_segments and isinstance(parent_path_segments, list):
self._path_segments = list(parent_path_segments)
else:
self._path_segments = []
shell_item_list = pyfwsi.item_list()
parser_mediator.AppendToParserChain(self.NAME)
try:
shell_item_list.copy_from_byte_stream(byte_stream, ascii_codepage=codepage)
for shell_item in iter(shell_item_list.items):
self._ParseShellItem(parser_mediator, shell_item)
finally:
parser_mediator.PopFromParserChain()