"""SQLite parser plugin for Mozilla Firefox cookies database files."""

from dfdatetime import posix_time as dfdatetime_posix_time

from plaso.containers import events
from plaso.lib import cookie_plugins_helper
from plaso.parsers import sqlite
from plaso.parsers.sqlite_plugins import interface


class FirefoxCookieEventData(events.EventData):
    """Firefox Cookie event data.

    Attributes:
      access_time (dfdatetime.DateTimeValues): date and time the cookie was last
          accessed.
      cookie_name (str): name field of the cookie.
      creation_time (dfdatetime.DateTimeValues): date and time the cookie was created.
      data (str): cookie data.
      expiration_time (dfdatetime.DateTimeValues): date and time the cookie expires.
      httponly (bool): True if the cookie cannot be accessed through client side script.
      hostname (str): name of the host or domain the cookie is tied to.
      offset (str): identifier of the row, from which the event data was extracted.
      path (str): URI of the page that set the cookie.
      query (str): SQL query that was used to obtain the event data.
      secure (bool): True if the cookie should only be transmitted over a secure
          channel.
    """

    DATA_TYPE = "firefox:cookie:entry"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.access_time = None
        self.cookie_name = None
        self.creation_time = None
        self.data = None
        self.expiration_time = None
        self.hostname = None
        self.httponly = None
        self.offset = None
        self.path = None
        self.query = None
        self.secure = None
        self.url = None


class BaseFirefoxCookiePlugin(
    interface.SQLitePlugin, cookie_plugins_helper.CookiePluginsHelper
):
    """Shared functionality for a Mozilla Firefox cookies SQLite parser plugin."""

    def _GetPosixTimeDateTimeRowValue(self, query_hash, row, value_name):
        """Retrieves a POSIX time (in seconds) date and time value from the row.

        Args:
          query_hash (int): hash of the query, that uniquely identifies the query that
              produced the row.
          row (sqlite3.Row): row.
          value_name (str): name of the value.

        Returns:
          dfdatetime.PosixTime: date and time value or None if not available.
        """
        timestamp = self._GetRowValue(query_hash, row, value_name)
        # Note that pysqlite3 can return an empty string for a NULL value.
        # Also see: https://github.com/log2timeline/plaso/issues/4961
        if timestamp is None or not isinstance(timestamp, int):
            return None

        return dfdatetime_posix_time.PosixTime(timestamp=timestamp)

    def ParseCookieRow(self, parser_mediator, query, row, **unused_kwargs):
        """Parses a cookie 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.
        """
        query_hash = hash(query)

        cookie_data = self._GetRowValue(query_hash, row, "value")
        cookie_name = self._GetRowValue(query_hash, row, "name")

        hostname = self._GetRowValue(query_hash, row, "host")
        if hostname.startswith("."):
            hostname = hostname[1:]

        is_secure = bool(self._GetRowValue(query_hash, row, "isSecure"))
        if is_secure:
            url_scheme = "https"
        else:
            url_scheme = "http"

        path = self._GetRowValue(query_hash, row, "path")
        url = f"{url_scheme:s}://{hostname:s}{path:s}"

        event_data = FirefoxCookieEventData()
        event_data.access_time = self._GetPosixTimeInMicrosecondsRowValue(
            query_hash, row, "lastAccessed"
        )
        event_data.cookie_name = cookie_name
        event_data.creation_time = self._GetPosixTimeInMicrosecondsRowValue(
            query_hash, row, "creationTime"
        )
        event_data.data = cookie_data
        event_data.hostname = hostname
        event_data.httponly = bool(self._GetRowValue(query_hash, row, "isHttpOnly"))
        event_data.offset = self._GetRowValue(query_hash, row, "id")
        event_data.path = path
        event_data.query = query
        event_data.secure = is_secure
        event_data.url = url

        # Expiry time (nsCookieService::GetExpiry in
        # netwerk/cookie/nsCookieService.cpp).
        # It's calculated as the difference between the server time and the time
        # the server wants the cookie to expire and adding that difference to the
        # client time. This localizes the client time regardless of whether or not
        # the TZ environment variable was set on the client.

        event_data.expiration_time = self._GetPosixTimeDateTimeRowValue(
            query_hash, row, "expiry"
        )
        parser_mediator.ProduceEventData(event_data)

        self._ParseCookie(parser_mediator, cookie_name, cookie_data, url)


class FirefoxCookie2Plugin(BaseFirefoxCookiePlugin):
    """SQLite parser plugin for Mozilla Firefox cookies schema 2 databases.

    Also see:
      https://hg-edge.mozilla.org/mozilla-central/file/349a2f003529/netwerk/cookie/nsCookie.h
    """

    NAME = "firefox_2_cookies"
    DATA_FORMAT = "Mozilla Firefox cookies SQLite database file version 2"

    REQUIRED_STRUCTURE = {
        "moz_cookies": frozenset(
            [
                "baseDomain",
                "creationTime",
                "expiry",
                "host",
                "id",
                "isHttpOnly",
                "isSecure",
                "lastAccessed",
                "name",
                "path",
                "value",
            ]
        )
    }

    QUERIES = [
        (
            (
                "SELECT id, baseDomain, name, value, host, path, expiry, "
                "lastAccessed, creationTime, isSecure, isHttpOnly FROM moz_cookies"
            ),
            "ParseCookieRow",
        )
    ]

    SCHEMAS = [
        {
            "moz_cookies": (
                "CREATE TABLE moz_cookies (id INTEGER PRIMARY KEY, baseDomain TEXT, "
                "appId INTEGER DEFAULT 0, inBrowserElement INTEGER DEFAULT 0, name "
                "TEXT, value TEXT, host TEXT, path TEXT, expiry INTEGER, "
                "lastAccessed INTEGER, creationTime INTEGER, isSecure INTEGER, "
                "isHttpOnly INTEGER, CONSTRAINT moz_uniqueid UNIQUE (name, host, "
                "path, appId, inBrowserElement))"
            )
        }
    ]


class FirefoxCookie10Plugin(BaseFirefoxCookiePlugin):
    """SQLite parser plugin for Mozilla Firefox cookies schema 10 databases.

    In schema 10 baseDomain was removed.
    """

    NAME = "firefox_10_cookies"
    DATA_FORMAT = "Mozilla Firefox cookies SQLite database file version 10"

    REQUIRED_STRUCTURE = {
        "moz_cookies": frozenset(
            [
                "id",
                "name",
                "value",
                "host",
                "path",
                "expiry",
                "lastAccessed",
                "creationTime",
                "isSecure",
                "isHttpOnly",
            ]
        )
    }

    QUERIES = [
        (
            (
                "SELECT id, name, value, host, path, expiry, "
                "lastAccessed, creationTime, isSecure, isHttpOnly FROM moz_cookies"
            ),
            "ParseCookieRow",
        )
    ]

    SCHEMAS = [
        {
            "moz_cookies": (
                "CREATE TABLE moz_cookies (id INTEGER PRIMARY KEY, "
                "appId INTEGER DEFAULT 0, inBrowserElement INTEGER DEFAULT 0, name "
                "TEXT, value TEXT, host TEXT, path TEXT, expiry INTEGER, "
                "lastAccessed INTEGER, creationTime INTEGER, isSecure INTEGER, "
                "isHttpOnly INTEGER, CONSTRAINT moz_uniqueid UNIQUE (name, host, "
                "path, appId, inBrowserElement))"
            )
        }
    ]


sqlite.SQLiteParser.RegisterPlugins([FirefoxCookie2Plugin, FirefoxCookie10Plugin])
