"""Analysis plugin to gather information about Chrome extensions."""

import re

import requests

from plaso.analysis import interface
from plaso.analysis import logger
from plaso.analysis import manager
from plaso.containers import analysis_results


class ChromeExtensionPlugin(interface.AnalysisPlugin):
  """Analysis plugin to gather information about Chrome extensions."""

  NAME = 'chrome_extension'

  _SUPPORTED_EVENT_DATA_TYPES = frozenset([
      'fs:stat'])

  _TITLE_RE = re.compile(r'<title>([^<]+)</title>')
  _WEB_STORE_URL = 'https://chrome.google.com/webstore/detail/{xid}?hl=en-US'

  _REQUEST_TIMEOUT = 60

  def __init__(self):
    """Initializes an Chrome extension analysis plugin."""
    super().__init__()

    # Saved list of already looked up extensions.
    self._extensions = {}
    self._extensions_per_user = {}

  def _GetChromeWebStorePage(self, extension_identifier):
    """Retrieves the page for the extension from the Chrome store website.

    Args:
      extension_identifier (str): Chrome extension identifier.

    Returns:
      str: page content or None.
    """
    web_store_url = self._WEB_STORE_URL.format(xid=extension_identifier)
    try:
      response = requests.get(web_store_url, timeout=self._REQUEST_TIMEOUT)

    except (requests.ConnectionError, requests.HTTPError) as exception:
      logger.warning((
          f'[{self.NAME:s}] unable to retrieve URL: {web_store_url:s} with '
          f'error: {exception!s}'))
      return None

    return response.text

  def _GetPathSegmentSeparator(self, path):
    """Given a path give back the path separator as a best guess.

    Args:
      path (str): path.

    Returns:
      str: path segment separator.
    """
    if path[0] in ('\\', '/'):
      return path[0]

    if path[1:].startswith(':\\'):
      return '\\'

    backward_slash_count = path.count('\\')
    forward_slash_count = path.count('/')
    if backward_slash_count > forward_slash_count:
      return '\\'

    return '/'

  def _GetTitleFromChromeWebStore(self, extension_identifier):
    """Retrieves the name of the extension from the Chrome store website.

    Args:
      extension_identifier (str): Chrome extension identifier.

    Returns:
      str: name of the extension or None.
    """
    # Check if we have already looked this extension up.
    if extension_identifier in self._extensions:
      return self._extensions.get(extension_identifier)

    page_content = self._GetChromeWebStorePage(extension_identifier)
    if not page_content:
      logger.warning((
          f'[{self.NAME:s}] no data returned for extension identifier: '
          f'{extension_identifier:s}'))
      return None

    first_line, _, _ = page_content.partition('\n')
    match = self._TITLE_RE.search(first_line)
    name = None
    if match:
      title = match.group(1)
      if title.startswith('Chrome Web Store - '):
        name = title[19:]
      elif title.endswith('- Chrome Web Store'):
        name = title[:-19]

    if not name:
      self._extensions[extension_identifier] = None
      return None

    self._extensions[extension_identifier] = name
    return name

  def CompileReport(self, analysis_mediator):
    """Compiles an analysis report.

    Args:
      analysis_mediator (AnalysisMediator): mediates interactions between
          analysis plugins and other components, such as storage and dfVFS.

    Returns:
      AnalysisReport: analysis report.
    """
    for username, extensions in sorted(self._extensions_per_user.items()):
      for extension, extension_identifier in sorted(extensions):
        analysis_result = analysis_results.ChromeExtensionAnalysisResult(
            extension=extension, extension_identifier=extension_identifier,
            username=username)
        analysis_mediator.ProduceAnalysisResult(analysis_result)

        self._analysis_counter[username] += 1

    return super().CompileReport(analysis_mediator)

  # pylint: disable=unused-argument
  def ExamineEvent(
      self, analysis_mediator, event, event_data, event_data_stream):
    """Analyzes an event.

    Args:
      analysis_mediator (AnalysisMediator): mediates interactions between
          analysis plugins and other components, such as storage and dfVFS.
      event (EventObject): event to examine.
      event_data (EventData): event data.
      event_data_stream (EventDataStream): event data stream.
    """
    if event_data.data_type not in self._SUPPORTED_EVENT_DATA_TYPES:
      return

    filename = getattr(event_data, 'filename', None)
    if not filename:
      return

    separator = self._GetPathSegmentSeparator(filename)
    path_segments = filename.lower().split(separator)

    # Determine if we have a Chrome extension ID.
    if 'chrome' not in path_segments and 'chromium' not in path_segments:
      return

    if path_segments[-2] != 'extensions':
      return

    # TODO: use a regex to check extension identifier
    extension_identifier = path_segments[-1]
    if extension_identifier == 'Temp':
      return

    username = analysis_mediator.GetUsernameForPath(filename)

    # We still want this information in here, so that we can
    # manually deduce the username.
    if not username:
      if len(filename) > 25:
        username = f'Not found ({filename[:25]:s}...)'
      else:
        username = f'Not found ({filename:s})'

    extension_string = self._GetTitleFromChromeWebStore(extension_identifier)
    if not extension_string:
      extension_string = extension_identifier

    self._extensions_per_user.setdefault(username, [])
    extension_tuple = (extension_string, extension_identifier)
    if extension_tuple not in self._extensions_per_user[username]:
      self._extensions_per_user[username].append(extension_tuple)


manager.AnalysisPluginManager.RegisterPlugin(ChromeExtensionPlugin)
