#!/usr/bin/python

# dfir_ntfs: an NTFS/FAT parser for digital forensics & incident response
# (c) Maxim Suhanov

import sys
import csv
import datetime
from dfir_ntfs.addons import FAT, exFAT

def custom_warn(msg):
	print(msg)

FAT.WARN_FUNC = custom_warn

def format_tz_item(tz):
	if tz is None:
		return '?'

	if tz == 0:
		return '0'

	if tz > 0:
		return '+{}'.format(tz * 15)

	return '-{}'.format(tz * -15)

def format_tz(mtz, atz, ctz):
	return '{}/{}/{}'.format(format_tz_item(mtz), format_tz_item(atz), format_tz_item(ctz))

def format_timestamp(timestamp):
	if timestamp is None:
		return 'N/A'

	if type(timestamp) is datetime.date:
		return timestamp.strftime('%Y-%m-%d')

	return timestamp.strftime('%Y-%m-%d %H:%M:%S.%f')

def format_boolean(boolean):
	if boolean is None:
		return '?'

	if boolean:
		return 'Y'

	return 'N'

def print_usage():
	print('Extract information from FAT12/16/32 & exFAT file systems')
	print('')
	print('Usage:')
	print(' fat_parser <input file (raw image)> <volume offset (in bytes)> <output file (CSV)>')
	print('')
	print('Warning:')
	print(' In the resulting CSV file, the allocation status of a file (or a directory)')
	print(' is specified based on its directory entry. A deleted directory can contain')
	print(' files and subdirectories marked as allocated (while they are not).')

if len(sys.argv) != 4:
	print_usage()
	sys.exit(0)

input_file = open(sys.argv[1], 'rb')
try:
	input_offset = int(sys.argv[2])
except ValueError:
	print_usage()
	sys.exit(0)

output_file = open(sys.argv[3], 'w', newline = '', encoding = 'utf-8')
csv_writer = csv.writer(output_file, dialect = 'excel')

try:
	fs = FAT.FileSystemParser(input_file, input_offset)
except FAT.FileSystemException:
	is_exfat = True
else:
	is_exfat = False

if is_exfat:
	fs = exFAT.FileSystemParser(input_file, input_offset)

if not is_exfat:
	# FAT12/16/32.

	found_label = False

	csv_writer.writerow(['Is deleted', 'Is directory', 'Path', 'M timestamp', 'A timestamp', 'C/CH timestamp', 'File size', 'Is encrypted', 'Short name (Python bytes)'])
	for item in fs.walk():
		if type(item) is FAT.OrphanLongEntry:
			csv_writer.writerow(['?', '?', item.long_name_partial + ' (Partial Name)', '?', '?', '?', '?', '?', '?'])
			continue

		if (not found_label) and (not item.is_deleted) and FAT.IsVolumeLabel(item.attributes):
			found_label = True
			path = item.short_name + ' (Volume Label)'
			csv_writer.writerow([format_boolean(item.is_deleted), format_boolean(item.is_directory), path, format_timestamp(item.mtime), format_timestamp(item.atime), format_timestamp(item.ctime), item.size, format_boolean(item.is_encrypted), item.short_name_raw])
			continue

		path = item.short_name
		if item.long_name is not None:
			path = item.long_name

		csv_writer.writerow([format_boolean(item.is_deleted), format_boolean(item.is_directory), path, format_timestamp(item.mtime), format_timestamp(item.atime), format_timestamp(item.ctime), item.size, format_boolean(item.is_encrypted), item.short_name_raw])
else:
	# exFAT.

	csv_writer.writerow(['Is deleted', 'Is directory', 'Path', 'M timestamp', 'A timestamp', 'C timestamp', 'M/A/C TZ (minutes)', 'File size', 'Is encrypted'])
	for item in fs.walk():
		if type(item) is exFAT.VolumeLabelEntry:
			label = item.volume_label + ' (Volume Label)'
			csv_writer.writerow(['N', 'N', label, '?', '?', '?', '?/?/?', '0', 'N'])
			continue

		if type(item) is exFAT.OrphanEntry:
			csv_writer.writerow(['?', '?', item.name_partial + ' (Partial Name)', '?', '?', '?', '?/?/?', '?', '?'])
			continue

		path = item.name

		csv_writer.writerow([format_boolean(item.is_deleted), format_boolean(item.is_directory), path, format_timestamp(item.mtime), format_timestamp(item.atime), format_timestamp(item.ctime), format_tz(item.mtz, item.atz, item.ctz), item.size, format_boolean(item.is_encrypted)])

input_file.close()
output_file.close()
