"""Parser for Windows Shortcut (LNK) files."""

import uuid

import pylnk

from dfdatetime import filetime as dfdatetime_filetime

from plaso.containers import events
from plaso.containers import windows_events
from plaso.lib import definitions
from plaso.lib import specification
from plaso.parsers import interface
from plaso.parsers import manager
from plaso.parsers.shared import shell_items


class WinLnkLinkEventData(events.EventData):
  """Windows Shortcut (LNK) link event data.

  Attributes:
    access_time (dfdatetime.DateTimeValues): file entry last access date
        and time.
    birth_droid_file_identifier (str): distributed link tracking birth droid
        file identifier.
    birth_droid_volume_identifier (str): distributed link tracking birth droid
        volume identifier.
    command_line_arguments (str): command line arguments.
    creation_time (dfdatetime.DateTimeValues): file entry creation date
        and time.
    description (str): description of the linked item.
    drive_serial_number (int): drive serial number where the linked item
        resides.
    drive_type (str): drive type where the linked item resided.
    droid_file_identifier (str): distributed link tracking droid file
        identifier.
    droid_volume_identifier (str): distributed link tracking droid volume
        identifier.
    env_var_location (str): environment variables location.
    file_attribute_flags (int): file attribute flags of the linked item.
    file_size (int): size of the linked item.
    icon_location (str): icon location.
    link_target (str): shell item list of the link target.
    local_path (str): local path of the linked item.
    modification_time (dfdatetime.DateTimeValues): file entry last modification
        date and time.
    network_path (str): local path of the linked item.
    relative_path (str): relative path.
    volume_label (str): volume label where the linked item resided.
    working_directory (str): working directory.
  """

  DATA_TYPE = 'windows:lnk:link'

  def __init__(self):
    """Initializes event data."""
    super().__init__(data_type=self.DATA_TYPE)
    self.access_time = None
    self.birth_droid_file_identifier = None
    self.birth_droid_volume_identifier = None
    self.command_line_arguments = None
    self.creation_time = None
    self.description = None
    self.drive_serial_number = None
    self.drive_type = None
    self.droid_file_identifier = None
    self.droid_volume_identifier = None
    self.env_var_location = None
    self.file_attribute_flags = None
    self.file_size = None
    self.icon_location = None
    self.link_target = None
    self.local_path = None
    self.modification_time = None
    self.network_path = None
    self.relative_path = None
    self.volume_label = None
    self.working_directory = None


class WinLnkParser(interface.FileObjectParser):
  """Windows Shortcut (LNK) file parser."""

  _INITIAL_FILE_OFFSET = None

  _PATH_ESCAPE_CHARACTERS = {'\\': '\\\\'}
  _PATH_ESCAPE_CHARACTERS.update(definitions.NON_PRINTABLE_CHARACTERS)

  NAME = 'lnk'
  DATA_FORMAT = 'Windows Shortcut (LNK) file'

  def __init__(self):
    """Initializes a Windows Shortcut (LNK) file parser."""
    super().__init__()
    self._path_escape_characters = str.maketrans(self._PATH_ESCAPE_CHARACTERS)

  def _GetDateTime(self, filetime):
    """Retrieves the date and time from a FILETIME timestamp.

    Args:
      filetime (int): FILETIME timestamp.

    Returns:
      dfdatetime.DateTimeValues: date and time or None if not set.
    """
    if not filetime:
      return None

    return dfdatetime_filetime.Filetime(timestamp=filetime)

  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)

  @classmethod
  def GetFormatSpecification(cls):
    """Retrieves the format specification.

    Returns:
      FormatSpecification: format specification.
    """
    format_specification = specification.FormatSpecification(cls.NAME)
    format_specification.AddNewSignature(
        b'\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46',
        offset=4)
    return format_specification

  def _ParseDistributedTrackingIdentifier(
      self, parser_mediator, uuid_string, origin):
    """Extracts data from a Distributed Tracking identifier.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      uuid_string (str): UUID string of the Distributed Tracking identifier.
      origin (str): origin of the event (event source).
    """
    uuid_object = uuid.UUID(uuid_string)

    if uuid_object.version == 1:
      event_data = windows_events.WindowsDistributedLinkTrackingEventData(
          uuid_object, origin)
      parser_mediator.ProduceEventData(event_data)

  def ParseFileObject(self, parser_mediator, file_object):
    """Parses a Windows Shortcut (LNK) file-like object.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      file_object (dfvfs.FileIO): file-like object.
    """
    display_name = parser_mediator.GetDisplayName()
    self.ParseFileLNKFile(parser_mediator, file_object, display_name)

  def ParseFileLNKFile(
      self, parser_mediator, file_object, display_name):
    """Parses a Windows Shortcut (LNK) file-like object.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      file_object (dfvfs.FileIO): file-like object.
      display_name (str): display name.
    """
    code_page = parser_mediator.GetCodePage()

    lnk_file = pylnk.file()
    lnk_file.set_ascii_codepage(code_page)

    try:
      lnk_file.open_file_object(file_object)
    except OSError as exception:
      parser_mediator.ProduceExtractionWarning(
          f'unable to open file with error: {exception!s}')
      return

    link_target = None
    if lnk_file.link_target_identifier_data:  # pylint: disable=using-constant-test
      # TODO: change file_entry.name to display name once it is generated
      # correctly.
      display_name = parser_mediator.GetFilename()
      shell_items_parser = shell_items.ShellItemsParser(display_name)
      shell_items_parser.ParseByteStream(
          parser_mediator, lnk_file.link_target_identifier_data,
          codepage=code_page)

      link_target = shell_items_parser.CopyToPath()

    access_time = lnk_file.get_file_access_time_as_integer()
    creation_time = lnk_file.get_file_creation_time_as_integer()
    modification_time = lnk_file.get_file_modification_time_as_integer()

    event_data = WinLnkLinkEventData()
    event_data.access_time = self._GetDateTime(access_time)
    event_data.birth_droid_file_identifier = (
        lnk_file.birth_droid_file_identifier)
    event_data.birth_droid_volume_identifier = (
        lnk_file.birth_droid_volume_identifier)
    event_data.command_line_arguments = self._GetSanitizedPathString(
        lnk_file.command_line_arguments)
    event_data.creation_time = self._GetDateTime(creation_time)
    event_data.description = self._GetSanitizedPathString(
        lnk_file.description)
    event_data.drive_serial_number = lnk_file.drive_serial_number
    event_data.drive_type = lnk_file.drive_type
    event_data.droid_file_identifier = lnk_file.droid_file_identifier
    event_data.droid_volume_identifier = lnk_file.droid_volume_identifier
    event_data.env_var_location = self._GetSanitizedPathString(
        lnk_file.environment_variables_location)
    event_data.file_attribute_flags = lnk_file.file_attribute_flags
    event_data.file_size = lnk_file.file_size
    event_data.icon_location = self._GetSanitizedPathString(
        lnk_file.icon_location)
    event_data.link_target = link_target
    event_data.local_path = self._GetSanitizedPathString(lnk_file.local_path)
    event_data.modification_time = self._GetDateTime(modification_time)
    event_data.network_path = self._GetSanitizedPathString(
        lnk_file.network_path)
    event_data.relative_path = self._GetSanitizedPathString(
        lnk_file.relative_path)
    event_data.volume_label = lnk_file.volume_label
    event_data.working_directory = self._GetSanitizedPathString(
        lnk_file.working_directory)

    parser_mediator.ProduceEventData(event_data)

    if lnk_file.droid_file_identifier:  # pylint: disable=using-constant-test
      try:
        self._ParseDistributedTrackingIdentifier(
            parser_mediator, lnk_file.droid_file_identifier, display_name)
      except (TypeError, ValueError) as exception:
        parser_mediator.ProduceExtractionWarning(
            f'unable to read droid file identifier with error: {exception!s}.')

    if (lnk_file.birth_droid_file_identifier and
        lnk_file.birth_droid_file_identifier != lnk_file.droid_file_identifier):
      try:
        self._ParseDistributedTrackingIdentifier(
            parser_mediator, lnk_file.birth_droid_file_identifier, display_name)
      except (TypeError, ValueError) as exception:
        parser_mediator.ProduceExtractionWarning(
            f'unable to read birth droid file identifier with error: '
            f'{exception!s}.')

    lnk_file.close()


manager.ParsersManager.RegisterParser(WinLnkParser)
