# -*- coding: utf-8 -*-
"""Output writer."""
import abc
from dfdatetime import fat_date_time as dfdatetime_fat_date_time
from dfdatetime import filetime as dfdatetime_filetime
from winregrc import hexdump
[docs]
class OutputWriter(object):
"""Output writer interface."""
# Note that redundant-returns-doc is broken for pylint 1.7.x
# pylint: disable=redundant-returns-doc
_HEXDUMP_CHARACTER_MAP = [
'.' if byte < 0x20 or byte > 0x7e else chr(byte) for byte in range(256)]
def _FormatDataInHexadecimal(self, data):
"""Formats data in a hexadecimal representation.
Args:
data (bytes): data.
Returns:
str: hexadecimal representation of the data.
"""
in_group = False
previous_hexadecimal_string = None
lines = []
data_size = len(data)
for block_index in range(0, data_size, 16):
data_string = data[block_index:block_index + 16]
hexadecimal_byte_values = []
printable_values = []
for byte_value in data_string:
if isinstance(byte_value, str):
byte_value = ord(byte_value)
hexadecimal_byte_values.append(f'{byte_value:02x}')
printable_value = self._HEXDUMP_CHARACTER_MAP[byte_value]
printable_values.append(printable_value)
remaining_size = 16 - len(data_string)
if remaining_size == 0:
whitespace = ''
elif remaining_size >= 8:
whitespace = ' ' * ((3 * remaining_size) - 1)
else:
whitespace = ' ' * (3 * remaining_size)
hexadecimal_string_part1 = ' '.join(hexadecimal_byte_values[0:8])
hexadecimal_string_part2 = ' '.join(hexadecimal_byte_values[8:16])
hexadecimal_string = (
f'{hexadecimal_string_part1:s} {hexadecimal_string_part2:s}'
f'{whitespace:s}')
if (previous_hexadecimal_string is not None and
previous_hexadecimal_string == hexadecimal_string and
block_index + 16 < data_size):
if not in_group:
in_group = True
lines.append('...')
else:
printable_string = ''.join(printable_values)
lines.append(
f'0x{block_index:08x} {hexadecimal_string:s} '
f'{printable_string:s}')
in_group = False
previous_hexadecimal_string = hexadecimal_string
lines.extend(['', ''])
return '\n'.join(lines)
def _FormatFATDateTimeValue(self, value):
"""Formats a FAT date time value.
Args:
value (int): FAT date time value.
Returns:
str: date time string.
"""
if not value:
date_time_string = 'Not set (0)'
else:
date_time = dfdatetime_fat_date_time.FATDateTime(fat_date_time=value)
date_time_string = date_time.CopyToDateTimeString()
if not date_time_string:
date_time_string = f'0x{value:04x}'
return date_time_string
def _FormatFiletimeValue(self, value):
"""Formats a FILETIME timestamp value.
Args:
value (int): FILETIME timestamp value.
Returns:
str: date time string.
"""
if value == 0:
date_time_string = 'Not set (0)'
elif value == 0x7fffffffffffffff:
date_time_string = 'Never (0x7fffffffffffffff)'
else:
date_time = dfdatetime_filetime.Filetime(timestamp=value)
date_time_string = date_time.CopyToDateTimeString()
if date_time_string:
date_time_string = f'{date_time_string:s} UTC'
else:
date_time_string = f'0x{value:08x}'
return date_time_string
[docs]
@abc.abstractmethod
def Close(self):
"""Closes the output writer."""
[docs]
def DebugPrintData(self, description, data):
"""Prints data for debugging.
Args:
description (str): description.
data (bytes): data.
"""
self.WriteText(f'{description:s}:\n')
value_string = self._FormatDataInHexadecimal(data)
self.WriteText(value_string)
[docs]
def DebugPrintValue(self, description, value):
"""Prints a value for debugging.
Args:
description (str): description.
value (object): value.
"""
alignment, _ = divmod(len(description), 8)
alignment_string = '\t' * (8 - alignment + 1)
self.WriteText(f'{description:s}{alignment_string:s}: {value!s}\n')
[docs]
def DebugPrintText(self, text):
"""Prints text for debugging.
Args:
text (str): text.
"""
self.WriteText(text)
[docs]
@abc.abstractmethod
def Open(self):
"""Opens the output writer.
Returns:
bool: True if successful or False if not.
"""
[docs]
@abc.abstractmethod
def WriteDebugData(self, description, data):
"""Writes data for debugging.
Args:
description (str): description.
data (bytes): data to write.
"""
[docs]
@abc.abstractmethod
def WriteIntegerValueAsDecimal(self, description, value):
"""Writes an integer value as decimal.
Args:
description (str): description.
value (int): value to write.
"""
[docs]
@abc.abstractmethod
def WriteFiletimeValue(self, description, value):
"""Writes a FILETIME timestamp value.
Args:
description (str): description.
value (str): value to write.
"""
[docs]
@abc.abstractmethod
def WriteText(self, text):
"""Writes text.
Args:
text (str): text to write.
"""
[docs]
@abc.abstractmethod
def WriteValue(self, description, value):
"""Writes a value.
Args:
description (str): description.
value (str): value to write.
"""
[docs]
class StdoutOutputWriter(OutputWriter):
"""Stdout output writer."""
[docs]
def Close(self):
"""Closes the output writer."""
return
[docs]
def Open(self):
"""Opens the output writer.
Returns:
bool: True if successful or False if not.
"""
return True
[docs]
def WriteDebugData(self, description, data):
"""Writes data for debugging.
Args:
description (str): description.
data (bytes): data.
"""
self.WriteText(description)
self.WriteText('\n')
hexdump_text = hexdump.Hexdump(data)
self.WriteText(hexdump_text)
[docs]
def WriteFiletimeValue(self, description, value):
"""Writes a FILETIME timestamp value.
Args:
description (str): description.
value (int): FILETIME timestamp value.
"""
date_time_string = self._FormatFiletimeValue(value)
self.WriteValue(description, date_time_string)
[docs]
def WriteIntegerValueAsDecimal(self, description, value):
"""Writes an integer value as decimal.
Args:
description (str): description.
value (int): integer value.
"""
self.WriteValue(description, f'{value:d}')
[docs]
def WriteText(self, text):
"""Writes text.
Args:
text (str): text to write.
"""
print(text, end='')
[docs]
def WriteValue(self, description, value):
"""Writes a value.
Args:
description (str): description.
value (object): value.
"""
description_no_tabs = description.replace('\t', ' ' * 8)
alignment, _ = divmod(len(description_no_tabs), 8)
alignment_string = '\t' * (8 - alignment + 1)
self.WriteText(f'{description:s}{alignment_string:s}: {value!s}\n')