#!/usr/bin/env python
#    This file is part of python-evtx.
#
#   Copyright 2012, 2013 Willi Ballenthin <william.ballenthin@mandiant.com>
#                    while at Mandiant <http://www.mandiant.com>
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
import Evtx.Evtx as evtx
import Evtx.Nodes as e_nodes


class EvtxFormatter(object):
    def __init__(self):
        super(EvtxFormatter, self).__init__()
        self._indent_stack = []
        self._indent_unit = "  "

    def _indent(self):
        self._indent_stack.append(self._indent_unit)

    def _dedent(self):
        if len(self._indent_stack) > 0:
            self._indent_stack = self._indent_stack[:-1]

    def save_indent(self):
        return self._indent_stack[:]

    def restore_indent(self, indent):
        self._indent_stack = indent

    def _l(self, s):
        return "".join(self._indent_stack) + s

    def format_header(self, fh):
        yield self._l("File header")
        self._indent()
        yield self._l("magic: %s" % (fh.magic()))
        for num_field in [
            "oldest_chunk",
            "current_chunk_number",
            "next_record_number",
            "header_size",
            "minor_version",
            "major_version",
            "header_chunk_size",
            "chunk_count",
            "flags",
            "checksum",
        ]:
            yield self._l("%s: %s" % (num_field, hex(getattr(fh, num_field)())))

        yield self._l("verify: %s" % (fh.verify()))
        yield self._l("dirty: %s" % (fh.is_dirty()))
        yield self._l("full: %s" % (fh.is_full()))

        for chunk in fh.chunks():
            for line in self.format_chunk(chunk):
                yield line
        self._dedent()

    def format_chunk(self, chunk):
        yield self._l("Chunk")
        self._indent()
        yield self._l("offset: %s" % (hex(chunk.offset())))
        yield self._l("magic: %s" % (chunk.magic()))

        for num_field in [
            "file_first_record_number",
            "file_last_record_number",
            "log_first_record_number",
            "log_last_record_number",
            "header_size",
            "last_record_offset",
            "next_record_offset",
            "data_checksum",
            "header_checksum",
        ]:
            yield self._l("%s: %s" % (num_field, hex(getattr(chunk, num_field)())))

        yield self._l("verify: %s" % (chunk.verify()))
        yield self._l("templates: %d" % (len(chunk.templates())))

        for record in chunk.records():
            for line in self.format_record(record):
                yield line
        self._dedent()

    def format_record(self, record):
        yield self._l("Record")
        self._indent()
        yield self._l("offset: %s" % (hex(record.offset())))
        yield self._l("magic: %s" % (hex(record.magic())))
        yield self._l("size: %s" % (hex(record.size())))
        yield self._l("number: %s" % (hex(record.record_num())))
        yield self._l("timestamp: %s" % (record.timestamp()))
        yield self._l("verify: %s" % (record.verify()))

        try:
            s = self.save_indent()
            for line in self.format_node(record, record.root()):
                yield line
        except Exception as e:
            self.restore_indent(s)
            yield "ERROR: " + str(e)
        self._dedent()

    def _format_node_name(self, record, node, extra=None):
        """
        note: this doesn't yield, it returns
        """
        line = ""
        if extra is not None:
            line = "%s(offset=%s, %s)" % (node.__class__.__name__, hex(node.offset() - record.offset()), extra)
        else:
            line = "%s(offset=%s)" % (node.__class__.__name__, hex(node.offset() - record.offset()))

        if isinstance(node, e_nodes.VariantTypeNode):
            line += " --> %s" % (node.string())
        if isinstance(node, e_nodes.OpenStartElementNode):
            line += " --> %s" % (node.tag_name())
        if isinstance(node, e_nodes.AttributeNode):
            line += " --> %s" % (node.attribute_name().string())
        return line

    def format_node(self, record, node):
        extra = None
        if isinstance(node, e_nodes.TemplateInstanceNode) and node.is_resident_template():
            extra = "resident=True, length=%s" % (hex(node.template().data_length()))
        elif isinstance(node, e_nodes.TemplateInstanceNode):
            extra = "resident=False"
        yield self._l(self._format_node_name(record, node, extra=extra))

        if isinstance(node, e_nodes.BXmlTypeNode):
            self._indent()
            for line in self.format_node(record, node._root):
                yield line
            self._dedent()
        elif isinstance(node, e_nodes.TemplateInstanceNode) and node.is_resident_template():
            self._indent()
            for line in self.format_node(record, node.template()):
                yield line
            self._dedent()

        self._indent()
        for child in node.children():
            for line in self.format_node(record, child):
                yield line
        self._dedent()

        if isinstance(node, e_nodes.RootNode):
            ofs = node.tag_and_children_length()
            yield self._l("Substitutions(offset=%s)" % (hex(node.offset() - record.offset() + ofs)))
            self._indent()

            for sub in node.substitutions():
                for line in self.format_node(record, sub):
                    yield line
            self._dedent()


def main():
    import argparse

    parser = argparse.ArgumentParser(description="Dump the structure of an EVTX file.")
    parser.add_argument("evtx", type=str, help="Path to the Windows EVTX event log file")
    args = parser.parse_args()

    with evtx.Evtx(args.evtx) as log:
        formatter = EvtxFormatter()
        for line in formatter.format_header(log.get_file_header()):
            print(line)


if __name__ == "__main__":
    main()
