# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Mobius Forensic Toolkit
# Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019 Eduardo Aguiar
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
import tempfile
import os
import os.path
import decoder_cookies
import decoder_downloads
import decoder_formhistory
import decoder_places
import pymobius
#import beta.util

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Known files list
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
KFF_FILES = [
   'cookies.sqlite',
   'cookies.sqlite-shm',
   'cookies.sqlite-wal',
   'downloads.sqlite',
   'favicons.sqlite',
   'favicons.sqlite-wal',
   'formhistory.sqlite',
   'places.sqlite',
   'places.sqlite-shm',
   'places.sqlite-wal',
   'signons.sqlite'
   ]

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Firefox Profile class
# @author Eduardo Aguiar
# @see https://support.mozilla.org/en-US/kb/profiles-where-firefox-stores-user-data
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Profile (object):

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Initialize object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __init__ (self, folder):
    self.__cookies_loaded = False
    self.__downloads_loaded = False
    self.__form_history_loaded = False
    self.__history_loaded = False

    self.__cookies = []
    self.__downloads = []
    self.__form_history = []
    self.__history = []
    self.__searches = []

    # beta.util.dirtree (folder)	# development only

    self.__roaming_folder = folder
    self.path = folder.path.replace ('/', '\\')
    
    if folder.name.lower ().endswith ('.default'):
      self.name = folder.name[:-8]

    else:
      self.name = folder.name

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get cookies from cookies.sqlite
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_cookies (self):

    if not self.__cookies_loaded:
      path = self.__retrieve_roaming_file ('cookies.sqlite')

      if path:
        data = decoder_cookies.decode (path)
        self.__cookies = data.cookies
        os.remove (path)

      self.__cookies_loaded = True

    return self.__cookies

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get download history from downloads.sqlite
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_downloads (self):

    if not self.__downloads_loaded:
      path = self.__retrieve_roaming_file ('downloads.sqlite')

      if path:
        data = decoder_downloads.decode (path)
        self.__downloads = data.downloads
        os.remove (path)

      self.__downloads_loaded = True

    return self.__downloads

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get form history
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_form_history (self):

    if not self.__form_history_loaded:
      self.__load_form_history ()

    return self.__form_history

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get search history
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_searches (self):

    if not self.__form_history_loaded:
      self.__load_form_history ()

    return self.__searches

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get history entries from places.sqlite
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_history (self):

    if not self.__history_loaded:
      path = self.__retrieve_roaming_file ('places.sqlite')

      if path:
        data = decoder_places.decode (path)
        self.__history = data.history
        
        for h in self.__history:
          h.url = h.url.encode ('ascii')
          h.profile_path = self.path
          h.profile_name = self.name
          h.username = self.username

        os.remove (path)

      self.__history_loaded = True

    return self.__history

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Load form history from formhistory.sqlite
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __load_form_history (self):
    path = self.__retrieve_roaming_file ('formhistory.sqlite')

    if path:
      self.__form_history = []
      self.__searches = []

      for entry in decoder_formhistory.decode (path):

        if entry.fieldname == 'searchbar-history':
          search = pymobius.Data ()
          search.id = entry.id
          search.text = entry.value
          search.timestamp = entry.first_used_time
          self.__searches.append (search)
          
          if entry.last_used_time and entry.first_used_time != entry.last_used_time:
            search = pymobius.Data ()
            search.id = entry.id
            search.text = entry.value
            search.timestamp = entry.last_used_time
            self.__searches.append (search)

        else:
          self.__form_history.append (entry)

      os.remove (path)

    self.__form_history_loaded = True

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve file into temp directory
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_roaming_file (self, rpath):

    # retrieve file
    f = self.__roaming_folder.get_child_by_path (rpath, False)
    if not f or f.is_reallocated:
      return

    reader = f.new_reader ()
    if not reader:
      return

    # create temporary .sqlite local file
    ext = os.path.splitext (f.name)[1]
    path = tempfile.mktemp (suffix=ext)

    fp = open (path, 'w')
    fp.write (reader.read ())
    fp.close ()
    
    return path

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get known files list
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_known_files (self):

    for kff_file in KFF_FILES:
      f = self.__roaming_folder.get_child_by_name (kff_file, False)

      if f and not f.is_reallocated:
        yield f

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get any file (development only)
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_file (self, rpath, name=None):

    # try to get file
    f = self.__roaming_folder.get_child_by_path (rpath, False)
    if not f:
      return

    reader = f.new_reader ()
    if not reader:
      return

    # create temporary .sqlite local file
    name = name or os.path.basename (rpath)
    fp = open (name, 'w')
    fp.write (reader.read ())
    fp.close ()
