"""Helper for generating bodyfile entries."""
from dfdatetime import definitions as dfdatetime_definitions
from dfvfs.lib import definitions as dfvfs_definitions
from dfvfs.vfs import ntfs_attribute as dfvfs_ntfs_attribute
from dfimagetools import definitions
[docs]
class BodyfileGenerator:
"""Bodyfile generator."""
_ESCAPE_CHARACTERS = {"/": "\\/", ":": "\\:", "\\": "\\\\", "|": "\\|"}
_ESCAPE_CHARACTERS.update(definitions.NON_PRINTABLE_CHARACTERS)
_FILE_TYPES = {
0x1000: "p",
0x2000: "c",
0x4000: "d",
0x6000: "b",
0xA000: "l",
0xC000: "s",
}
_FILE_ATTRIBUTE_READONLY = 1
_FILE_ATTRIBUTE_HIDDEN = 2
_FILE_ATTRIBUTE_SYSTEM = 4
_TIMESTAMP_FORMAT_STRINGS = {
dfdatetime_definitions.PRECISION_1_NANOSECOND: "{0:d}.{1:09d}",
dfdatetime_definitions.PRECISION_10_NANOSECONDS: "{0:d}.{1:08d}",
dfdatetime_definitions.PRECISION_100_NANOSECONDS: "{0:d}.{1:07d}",
dfdatetime_definitions.PRECISION_1_MICROSECOND: "{0:d}.{1:06d}",
dfdatetime_definitions.PRECISION_10_MICROSECONDS: "{0:d}.{1:05d}",
dfdatetime_definitions.PRECISION_100_MICROSECONDS: "{0:d}.{1:04d}",
dfdatetime_definitions.PRECISION_1_MILLISECOND: "{0:d}.{1:03d}",
dfdatetime_definitions.PRECISION_10_MILLISECONDS: "{0:d}.{1:02d}",
dfdatetime_definitions.PRECISION_100_MILLISECONDS: "{0:d}.{1:01d}",
}
[docs]
def __init__(self):
"""Initializes a bodyfile generator."""
super().__init__()
self._escape_characters = str.maketrans(self._ESCAPE_CHARACTERS)
self._root_file_entry_identifier = None
def _GetFileAttributeFlagsString(self, file_type, file_attribute_flags):
"""Retrieves a bodyfile string representation of file attributes flags.
Args:
file_type (str): bodyfile file type identifier.
file_attribute_flags (int): file attribute flags.
Returns:
str: bodyfile representation of the file attributes flags.
"""
string_parts = [file_type, "r", "w", "x", "r", "w", "x", "r", "w", "x"]
if (
file_attribute_flags & self._FILE_ATTRIBUTE_READONLY
or file_attribute_flags & self._FILE_ATTRIBUTE_SYSTEM
):
string_parts[2] = "-"
string_parts[5] = "-"
string_parts[8] = "-"
return "".join(string_parts)
def _GetModeString(self, mode):
"""Retrieves a bodyfile string representation of a mode.
Args:
mode (int): mode.
Returns:
str: bodyfile representation of the mode.
"""
string_parts = 10 * ["-"]
if mode & 0x0001:
string_parts[9] = "x"
if mode & 0x0002:
string_parts[8] = "w"
if mode & 0x0004:
string_parts[7] = "r"
if mode & 0x0008:
string_parts[6] = "x"
if mode & 0x0010:
string_parts[5] = "w"
if mode & 0x0020:
string_parts[4] = "r"
if mode & 0x0040:
string_parts[3] = "x"
if mode & 0x0080:
string_parts[2] = "w"
if mode & 0x0100:
string_parts[1] = "r"
string_parts[0] = self._FILE_TYPES.get(mode & 0xF000, "-")
return "".join(string_parts)
def _GetTimestamp(self, date_time):
"""Retrieves a bodyfile timestamp representation of a date time value.
Args:
date_time (dfdatetime.DateTimeValues): date time value.
Returns:
str: bodyfile timestamp representation of the date time value.
"""
if not date_time or date_time.timestamp == 0:
return ""
posix_timestamp, fraction_of_second = (
date_time.CopyToPosixTimestampWithFractionOfSecond()
)
format_string = self._TIMESTAMP_FORMAT_STRINGS.get(date_time.precision, "{0:d}")
return format_string.format(posix_timestamp, fraction_of_second)
[docs]
def GetEntries(self, file_entry, path_segments):
"""Retrieves bodyfile entry representations of a file entry.
Args:
file_entry (dfvfs.FileEntry): file entry.
path_segments (str): path segments of the full path of the file entry.
Yields:
str: bodyfile entry.
"""
file_attribute_flags = None
parent_file_reference = None
if file_entry.type_indicator == dfvfs_definitions.TYPE_INDICATOR_FAT:
fsfat_file_entry = file_entry.GetFATFileEntry()
file_attribute_flags = fsfat_file_entry.file_attribute_flags
elif file_entry.type_indicator == dfvfs_definitions.TYPE_INDICATOR_NTFS:
fsntfs_file_entry = file_entry.GetNTFSFileEntry()
file_attribute_flags = fsntfs_file_entry.file_attribute_flags
mft_attribute_index = getattr(file_entry.path_spec, "mft_attribute", None)
if mft_attribute_index is not None:
parent_file_reference = (
fsntfs_file_entry.get_parent_file_reference_by_attribute_index(
mft_attribute_index
)
)
stat_attribute = file_entry.GetStatAttribute()
if stat_attribute.inode_number is None:
inode_string = ""
elif file_entry.type_indicator == dfvfs_definitions.TYPE_INDICATOR_FAT:
inode_string = f"0x{stat_attribute.inode_number:x}"
elif file_entry.type_indicator == dfvfs_definitions.TYPE_INDICATOR_NTFS:
mft_entry_number = stat_attribute.inode_number & 0xFFFFFFFFFFFF
mft_sequence_number = stat_attribute.inode_number >> 48
inode_string = f"{mft_entry_number:d}-{mft_sequence_number:d}"
else:
inode_string = f"{stat_attribute.inode_number:d}"
if file_entry.type_indicator not in (
dfvfs_definitions.TYPE_INDICATOR_FAT,
dfvfs_definitions.TYPE_INDICATOR_NTFS,
):
mode = getattr(stat_attribute, "mode", None) or 0
mode_string = self._GetModeString(mode)
else:
if file_entry.entry_type == dfvfs_definitions.FILE_ENTRY_TYPE_DIRECTORY:
file_type = "d"
elif file_entry.entry_type == dfvfs_definitions.FILE_ENTRY_TYPE_LINK:
file_type = "l"
else:
file_type = "-"
if file_attribute_flags is None:
mode_string = "".join([file_type] + (3 * ["rwx"]))
else:
mode_string = self._GetFileAttributeFlagsString(
file_type, file_attribute_flags
)
owner_identifier = ""
if stat_attribute.owner_identifier is not None:
owner_identifier = str(stat_attribute.owner_identifier)
group_identifier = ""
if stat_attribute.group_identifier is not None:
group_identifier = str(stat_attribute.group_identifier)
access_time = self._GetTimestamp(file_entry.access_time)
creation_time = self._GetTimestamp(file_entry.creation_time)
change_time = self._GetTimestamp(file_entry.change_time)
modification_time = self._GetTimestamp(file_entry.modification_time)
# TODO: add support to calculate MD5
md5_string = "0"
path_segments = [
(segment or "").translate(self._escape_characters)
for segment in path_segments
]
file_entry_name_value = "/".join(path_segments) or "/"
if file_entry.IsRoot() and not file_entry_name_value.endswith("/"):
file_entry_name_value = f"{file_entry_name_value:s}/"
if not file_entry.link:
name_value = file_entry_name_value
else:
if file_entry.type_indicator == dfvfs_definitions.TYPE_INDICATOR_NTFS:
path_segments = file_entry.link.split("\\")
link_target = "/".join(
[path_segments[0]]
+ [
segment.translate(self._escape_characters)
for segment in path_segments[1:]
if segment and segment != "."
]
)
else:
path_segments = file_entry.link.split("/")
link_target = "/".join(
[
segment.translate(self._escape_characters)
for index, segment in enumerate(path_segments)
if (index == 0 or segment) and segment != "."
]
)
name_value = " -> ".join([file_entry_name_value, link_target])
size = str(file_entry.size)
yield "|".join(
[
md5_string,
name_value,
inode_string,
mode_string,
owner_identifier,
group_identifier,
size,
access_time,
modification_time,
change_time,
creation_time,
]
)
for data_stream in file_entry.data_streams:
if data_stream.name:
data_stream_name = data_stream.name.translate(self._escape_characters)
data_stream_name_value = ":".join(
[file_entry_name_value, data_stream_name]
)
data_stream_size = str(data_stream.size)
yield "|".join(
[
md5_string,
data_stream_name_value,
inode_string,
mode_string,
owner_identifier,
group_identifier,
data_stream_size,
access_time,
modification_time,
change_time,
creation_time,
]
)
for attribute in file_entry.attributes:
if isinstance(attribute, dfvfs_ntfs_attribute.FileNameNTFSAttribute):
if (
attribute.name == file_entry.name
and attribute.parent_file_reference == parent_file_reference
):
attribute_name_value = " ".join(
[file_entry_name_value, "($FILE_NAME)"]
)
access_time = self._GetTimestamp(attribute.access_time)
creation_time = self._GetTimestamp(attribute.creation_time)
change_time = self._GetTimestamp(attribute.entry_modification_time)
modification_time = self._GetTimestamp(attribute.modification_time)
yield "|".join(
[
md5_string,
attribute_name_value,
inode_string,
mode_string,
owner_identifier,
group_identifier,
size,
access_time,
modification_time,
change_time,
creation_time,
]
)