# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# 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 mobius
import binascii

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Generic dataholder
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class dataholder (object):
  pass

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Entry
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Entry (object):

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief initialize object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __init__ (self):
    self.flag_paused = None
    self.name = None
    self.path = None
    self.comment = None
    self.db_time = None
    self.start_time = None
    self.elapsed = None

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief get data as UTF-8 string (sanitize non UTF-8 strings)
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def get_utf8_string (data):
  try:
    s = unicode (data, 'utf-8')
  except UnicodeDecodeError, e:
    s = unicode (data, 'iso-8859-1', errors='ignore')

  return s.encode ('utf-8')

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Decode PBTHash_XXX.dat file
# @see BitTorrentDb_load - BitTorrent/BitTorrentDlDb.pas (line 88)
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def decode (f):
  reader = f.new_reader ()
  if not reader:
    return

  # check version
  decoder = mobius.decoder.data_decoder (reader)
  version = decoder.get_uint8 ()
  
  if version != 1:
    return

  # create entry object
  entry = Entry ()
  entry.db_version = version
  entry.hash_sha1 = binascii.hexlify (decoder.get_bytearray_by_size (20))
  entry.pbthash_path = f.path.replace ('/', '\\')
  entry.pbthash_creation_time = f.creation_time
  entry.pbthash_last_modification_time = f.last_modification_time

  # decode state
  # @see BitTorrent/BitTorrentUtils (line 76)
  # 0 - processing
  state = decoder.get_uint8 ()
  entry.flag_paused = (state == 1)
  entry.flag_seeding = (state == 2)
  
  # get info
  entry.size = decoder.get_uint64_le ()
  entry.piece_length = decoder.get_uint32_le ()
  entry.bytes_downloaded = decoder.get_uint64_le ()
  entry.bytes_uploaded = decoder.get_uint64_le ()
  entry.num_pieces = decoder.get_uint32_le ()
  
  # get pieces
  # @see BitTorrentDb_load - BitTorrent/BitTorrentDlDb.pas (line 163)
  entry.pieces = []

  for i in xrange (entry.num_pieces):
    piece = dataholder ()
    piece.idx = i
    piece.hash_sha1 = binascii.hexlify (decoder.get_bytearray_by_size (20))
    piece.flag_checked = decoder.get_uint8 () == 1
    entry.pieces.append (piece)

  # decode num_files and path
  # @see BitTorrentDb_load - BitTorrent/BitTorrentDlDb.pas (line 193)
  entry.num_files = decoder.get_uint32_le ()
  siz = decoder.get_uint16_le ()
  name = decoder.get_string_by_size (siz)

  if '\\' in name:
    entry.path = name
    entry.name = name.rsplit ('\\')[-1]
  else:
    entry.name = name
    entry.path = None # @todo vars_global.my_torrentFolder + '\\' + name

  # decode file list
  # @see BitTorrentDb_load - BitTorrent/BitTorrentDlDb.pas (line 211)
  entry.files = []

  for i in xrange (entry.num_files):
    fl = dataholder ()
    fl.idx = i
    fl.size = decoder.get_uint64_le ()
    fl.last_modification_time = None

    siz = decoder.get_uint16_le ()

    if siz == 0:
      fl.last_modification_time = decoder.get_unix_datetime ()
      siz = decoder.get_uint16_le ()

    fl.name = decoder.get_string_by_size (siz)
    
    # @see BitTorrentDb_load - BitTorrent/BitTorrentDlDb.pas (line 241)
    if entry.num_files == 1:
      fl.path = entry.path
    else:
      fl.path = entry.path + '\\' + fl.name

    fl.name = fl.name.rsplit ('\\')[-1]
    entry.files.append (fl)
  
  # decode tags
  # @see BitTorrentDb_load - BitTorrent/BitTorrentDlDb.pas (line 296)
  pos = decoder.tell ()
  size = decoder.get_size ()
  entry.trackers = []

  while pos < size - 2:
    tag_id = decoder.get_uint8 ()
    tag_size = decoder.get_uint16_le ()
    pos = pos + 3 + tag_size
    
    if tag_id == 1:
      entry.name = get_utf8_string (decoder.get_string_by_size (tag_size))

    elif tag_id in (2, 6):
      tracker = get_utf8_string (decoder.get_string_by_size (tag_size))
      entry.trackers.append (tracker)

    elif tag_id == 3:
      entry.comment = get_utf8_string (decoder.get_string_by_size (tag_size))
      
    elif tag_id == 4:
      entry.db_time = decoder.get_unix_datetime ()

    elif tag_id == 5:
      entry.start_time = decoder.get_unix_datetime ()
      
    elif tag_id == 7:
      entry.elapsed = decoder.get_uint32_le ()

  return entry
