diff --git a/volatility/cli/text_renderer.py b/volatility/cli/text_renderer.py index 749b84922..b428e3ee5 100644 --- a/volatility/cli/text_renderer.py +++ b/volatility/cli/text_renderer.py @@ -20,6 +20,8 @@ import datetime import logging +import random +import string import sys from typing import Callable, Any @@ -115,9 +117,6 @@ class QuickTextRenderer(interfaces.renderers.Renderer): 'default': Optional(lambda x: "{}".format(x)) } - def __init__(self, options = None) -> None: - super().__init__(options) - def get_render_options(self): pass @@ -155,3 +154,60 @@ class QuickTextRenderer(interfaces.renderers.Renderer): grid.populate(visitor, outfd) outfd.write("\n") + + +class PrettyTextRenderer(interfaces.renderers.Renderer): + type_renderers = QuickTextRenderer.type_renderers + + def get_render_options(self): + pass + + def render(self, grid: interfaces.renderers.TreeGrid) -> None: + """ + Renders each column immediately to stdout. + + This does not format each line's width appropriately, it merely tab separates each field + + Args: + grid: The TreeGrid object to render + + """ + # TODO: Docstrings + # TODO: Improve text output + outfd = sys.stdout + + outfd.write("Formatting...\r") + + display_alignment = ">" + column_separator = " | " + + tree_indent_column = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(20)) + max_column_widths = dict([(column, len(column.name)) for column in grid.columns]) + + def visitor(node, accumulator): + # Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case + max_column_widths[tree_indent_column] = max(max_column_widths.get(tree_indent_column, 0), node.path_depth) + line = {} + for column in grid.columns: + renderer = self.type_renderers.get(column.type, self.type_renderers['default']) + data = renderer(node.values[column.index]) + max_column_widths[column] = max(max_column_widths.get(column, 0), len(data)) + line[column] = data + accumulator.append((node.path_depth, line)) + return accumulator + + final_output = [] + grid.populate(visitor, final_output) + + # Always align the tree to the left + format_string = ["{0:<" + str(max_column_widths[tree_indent_column]) + "s}"] + for column in grid.columns: + format_string.append("{" + str(column.index + 1) + ":" + display_alignment + + str(max_column_widths[column]) + "s}") + + format_string = column_separator.join(format_string) + "\n" + + column_titles = [""] + [column.name for column in grid.columns] + outfd.write(format_string.format(*column_titles)) + for (depth, line) in final_output: + outfd.write(format_string.format("*" * depth, *[line[x] for x in line])) diff --git a/volatility/framework/interfaces/renderers.py b/volatility/framework/interfaces/renderers.py index 4e9e80699..eddb52993 100644 --- a/volatility/framework/interfaces/renderers.py +++ b/volatility/framework/interfaces/renderers.py @@ -34,7 +34,7 @@ RenderOption = Any class Renderer(metaclass = ABCMeta): """Class that defines the interface that all output renderers must support""" - def __init__(self, options: List[RenderOption]) -> None: + def __init__(self, options: Optional[List[RenderOption]] = None) -> None: """Accepts an options object to configure the renderers""" # FIXME: Once the config option objects are in place, put the _type_check in place