#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""pentext_id - Identify findings from report.

This script is part of the PenText framework
                           https://pentext.org

   Copyright (C)      2017 Radically Open Security
                           https://www.radicallyopensecurity.com

                   Author: Peter Mosmans

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
"""


from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

import argparse
import logging
import os
import sys
import textwrap

try:
    from lxml import etree
except ImportError as exception:
    print('[-] This script needs lxml',
          file=sys.stderr)
    print("Install lxml with: sudo pip install lxml", file=sys.stderr)
    sys.exit(-1)


VERSION = '0.2.1'


class LogFormatter(logging.Formatter):
    """
    Class to format log messages based on their type.
    """
    FORMATS = {logging.DEBUG: "[d] %(message)s",
               logging.INFO: "[*] %(message)s",
               logging.ERROR: "[-] %(message)s",
               logging.CRITICAL: "[-] FATAL: %(message)s",
               'DEFAULT': "%(message)s"}

    def format(self, record):
        self._fmt = self.FORMATS.get(record.levelno, self.FORMATS['DEFAULT'])
        return logging.Formatter.format(self, record)


def parse_arguments(banner):
    """
    Parse and return command line arguments.
    """
    parser = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description=textwrap.dedent(banner + '''\
pentext_id - Identify findings based on their unique id, filename, or location
in the report

Copyright (C) 2017 Peter Mosmans [Radically Open Security]

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.'''))
    parser.add_argument('target', nargs='?', type=str,
                        help="""[PARAMETER] can be TODO""")
    parser.add_argument('--debug', action='store_true',
                        help='Show debug information')
    parser.add_argument('-v', '--verbose', action='store_true',
                        help='Be more verbose')
    parser.add_argument('--finding', action='store', type=int,
                        help='Convert finding filename ID to finding ID')
    parser.add_argument('--id', action='store', type=int,
                        help='Show filename of a finding ID')
    parser.add_argument('--source', action='store',
                        default='source/report.xml',
                        help='Set source file (default: source/report.xml)')
    args = parser.parse_args()
    return args


def setup_logging(args):
    """
    Set up loghandlers according to options.
    """
    logger = logging.getLogger()
    logger.setLevel(0)
    console = logging.StreamHandler(stream=sys.stdout)
    console.setFormatter(LogFormatter())
    if args.debug:
        console.setLevel(logging.DEBUG)
    elif args.verbose:
        console.setLevel(logging.INFO)
    else:
        console.setLevel(logging.ERROR)
    logger.addHandler(console)


def locate_finding(findings, args):
    """
    Show id corresponding to a finding.
    """
    try:
        for i, href in enumerate(findings, start=1):
            if 'findings/f{0}-'.format(args.finding) in href or \
               'findings/{0}-'.format(args.finding) in href:
                print("{0:2d} {1}".format(i, href))
    except IndexError:
        logging.error('Finding %s could not be located', args.id)


def locate_id(findings, args):
    """
    Show finding corresponding to an identifier
    """
    try:
        print("{0:2d} {1}".format(args.id, findings[args.id - 1]))
    except IndexError:
        logging.error('Finding %s could not be located', args.id)


def main():
    """
    Main program loop.
    """
    banner = 'pentext_id version {0}'.format(VERSION)
    args = parse_arguments(banner)
    setup_logging(args)
    logging.info('%s starting', banner)
    # read document
    filename = args.source
    if not os.path.isfile(filename):
        logging.error('Could not open %s', filename)
        sys.exit(-1)
    findings = []
    try:
        # Parse document into an ElementTree
        tree = etree.parse(filename, etree.XMLParser(strip_cdata=False))
        # Read finding code
        code = tree.getroot().attrib['findingCode']
        # Grab all elements from the findings section
        for elements in tree.xpath('//section[@id="findings"]'):
            # Iterate through all elements, looking for the findings
            for element in elements:
                if 'href' in element.attrib:
                    findings.append(element.attrib['href'])
    except (etree.ParseError, IOError) as exception:
        logging.warning('Validating %s failed: %s', filename, exception)
    if args.id:
        locate_id(findings, args)
    elif args.finding:
        locate_finding(findings, args)
    else:
        for i, href in enumerate(findings, start=1):
            print("{0}-{1:03d} {2}".format(code, i, href))


if __name__ == "__main__":
    main()
