#!/usr/bin/python3
# -*- coding: UTF-8 -*-

import os, subprocess, socket, re, requests, errno, sys, time, json, signal, base64, hashlib, random
from pathlib import Path
from urllib.parse import urlparse

from .args import Arguments
from .util.color import Color
from .util.logger import Logger
from .util.database import Database
from .version import __version__

class Configuration(object):
    ''' Stores configuration variables and functions for Turbo Search. '''
    version = '0.0.0'

    initialized = False # Flag indicating config has been initialized
    verbose = 0
    target = ''
    ip_list = ''
    out_file = ''
    full_log = False
    cmd_line =''
    restore = ''
    threads_data = None
    restored_uri=''
    restored_paths=[]
    threads_data={}
    proxy=''
    host=''
    proxy_report_to=''
    request_method='GET'
    user_agent=''
    user_headers={}
    ipaddresses=[]
    skip_current=False
    db = None
    statsdb=False
    main_code=0
    main_length=0
    check_both=False
    base_target = False
    main_min_length = 0
    main_max_length = 0
    waf_list = {}
    waf_list_short = []

    @staticmethod
    def initialize():
        '''
            Sets up default initial configuration values.
            Also sets config values based on command-line arguments.
        '''

        # Only initialize this class once
        if Configuration.initialized:
            return
        Configuration.initialized = True

        Configuration.verbose = 0 # Verbosity level.
        Configuration.print_stack_traces = True

        # Overwrite config values with arguments (if defined)
        Configuration.load_from_arguments()


    @staticmethod
    def load_from_arguments():
        ''' Sets configuration values based on Argument.args object '''
        from .args import Arguments

        config_check = 0

        sys.argv[0] = 'webfinder'

        force_restore = any(['-R' in word for word in sys.argv])
        help = any(['-h' in word for word in sys.argv])

        if help:
            args = Arguments().args
        else:
            if not force_restore and os.path.exists("webfinder.restore"):
                ignore = any(['-I' in word for word in sys.argv])
                if not ignore:
                    Color.pl('{!} {W}Restorefile (you have 10 seconds to abort... (use option -I to skip waiting)) from a previous session found, to prevent overwriting, ./webfinder.restore')
                    time.sleep(10)
                os.remove("webfinder.restore")

            args = {}
            if os.path.exists("webfinder.restore"):
                try:
                    with open("webfinder.restore", 'r') as f:
                        restore_data = json.load(f)
                        Configuration.cmd_line = restore_data["command"]
                        Configuration.threads_data = restore_data["threads"]

                except Exception as e:
                    Color.pl('{!} {R}error: invalid restore file\r\n')
                    raise

                args = Arguments(Configuration.cmd_line).args

            else:
                args = Arguments().args
                for a in sys.argv:
                    if a != "-I":
                        Configuration.cmd_line += "%s " % a

        Color.pl('{+} {W}Startup parameters')

        Logger.pl('     {C}command line:{O} %s{W}' % Configuration.cmd_line)

        if args.target:
            from .util.tools import Tools

            Configuration.target = args.target
            if Configuration.target.endswith('/'):
                Configuration.target = Configuration.target[:-1]

            #rUri = urlparse(Configuration.target)
            #Configuration.host = rUri.netloc
            #if ':' in Configuration.host:
            #    Configuration.host = Configuration.host.split(':')[0]
            Configuration.host = Tools.get_host(Configuration.target)
            Configuration.base_target = Configuration.target.replace(Configuration.host, '{ip}')

        if args.tasks:
            Configuration.tasks = args.tasks

        if args.ip_list:
            Configuration.ip_list = args.ip_list

        if args.verbose:
            Configuration.verbose = args.verbose

        if args.out_file:
            Configuration.out_file = args.out_file

        if args.check_both:
            Configuration.check_both = args.check_both

        if args.tasks:
            Configuration.tasks = args.tasks

        if Configuration.tasks < 1:
            Configuration.tasks = 1

        if Configuration.tasks > 256:
            Configuration.tasks = 256

        if Configuration.target == '':
            config_check = 1

        if Configuration.ip_list == '':
            config_check = 1

        if config_check == 1:
            Configuration.mandatory()

        if args.full_log:
            Configuration.full_log = args.full_log

        if args.proxy:
            Configuration.proxy = args.proxy

        if args.report_to:
            Configuration.proxy_report_to = args.report_to

        if args.request_method.upper() == "POST":
            Configuration.request_method = "POST"
        elif args.request_method.upper() == "PUT":
            Configuration.request_method = "PUT"
        elif args.request_method.upper() == "OPTIONS":
            Configuration.request_method = "OPTIONS"
        else:
            Configuration.request_method = "GET"

        if args.random_agent:
            try:
                
                with open(str(Path(__file__).parent) + "/resources/user_agents.txt", 'r') as f:
                    # file opened for writing. write to it here
                    line = next(f)
                    for num, aline in enumerate(f, 2):
                        if random.randrange(num):
                            continue
                        if aline.strip("\r\n").strip() == '':
                            continue
                        Configuration.user_agent = aline.strip("\r\n").strip()
                    
            except IOError as x:
                if x.errno == errno.EACCES:
                    Color.pl('{!} {R}error: could not open ./resources/user_agents.txt {O}permission denied{R}{W}\r\n')
                    Configuration.exit_gracefully(0)
                elif x.errno == errno.EISDIR:
                    Color.pl('{!} {R}error: could not open ./resources/user_agents.txt {O}it is an directory{R}{W}\r\n')
                    Configuration.exit_gracefully(0)
                else:
                    Color.pl('{!} {R}error: could not open ./resources/user_agents.txt{W}\r\n')
                    Configuration.exit_gracefully(0)

        # get list of WAF
        try:

            with open(str(Path(__file__).parent) + "/resources/waf_headers.json", 'r') as f:
                tmp_waf_list = json.loads(f.read())

                if not isinstance(tmp_waf_list, dict):
                    raise Exception("Invalid dictionary!")

                Configuration.waf_list = {
                    k: [
                        str(n).lower()
                        for n in v
                    ]
                    for k, v in tmp_waf_list.items()
                }
                Configuration.waf_list_short = [
                    str(v).lower()
                    for k, vl in tmp_waf_list.items()
                    for v in vl
                ]

        except IOError as x:
            if x.errno == errno.EACCES:
                Color.pl('{!} {R}error: could not open ./resources/waf_headers.json {O}permission denied{R}{W}\r\n')
                Configuration.exit_gracefully(0)
            elif x.errno == errno.EISDIR:
                Color.pl('{!} {R}error: could not open ./resources/waf_headers.json {O}it is an directory{R}{W}\r\n')
                Configuration.exit_gracefully(0)
            else:
                Color.pl('{!} {R}error: could not open ./resources/waf_headers.json{W}\r\n')
                Configuration.exit_gracefully(0)
        except Exception as e:
            Color.pl('{!} {R}error: could not parse ./resources/waf_headers.json {O}%s{W}\r\n' % str(e))
            Configuration.exit_gracefully(0)

        if args.header != '':
            jData = {}
            try:
                jData=json.loads(args.header)
            except:
                Logger.pl('{!} {R}error: could not convert header value {O}%s{R} from an JSON object {W}\r\n' % (args.header))
                Configuration.exit_gracefully(0)

            Configuration.user_headers = {}
            try:
                for k in jData:
                    if isinstance(k, str):
                        if isinstance(jData[k], str):
                            if k.lower().find("user-agent") != -1:
                                Configuration.user_agent = jData[k]
                            elif k.lower().find("host") != -1:
                                pass
                            elif k.lower().find("connection") != -1:
                                pass
                            elif k.lower().find("accept") != -1:
                                pass
                            elif k.lower().find("accept-encoding") != -1:
                                pass
                            else:
                                Configuration.user_headers[k] = jData[k]
                        else:
                            raise Exception("The value of %s id not an String" % k)
                    else:
                        raise Exception("%s id not an String" % k)
            except Exception as e:
                Logger.pl('{!} {R}error: could parse header data: {R}%s{W}\r\n' % (str(e)))
                Configuration.exit_gracefully(0)

        regex = re.compile(
            r'^(?:http|ftp)s?://'  # http:// or https://
            r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'  # domain...
            r'localhost|'  # localhost...
            r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'  # ...or ip
            r'(?::\d+)?'  # optional port
            r'(?:/?|[/?]\S+)$', re.IGNORECASE)

        if re.match(regex, Configuration.target) is None:
            Color.pl('{!} {R}error: invalid target {O}%s{R}{W}\r\n' % Configuration.target)
            Configuration.exit_gracefully(0)

        if Configuration.proxy != '' and re.match(regex, Configuration.proxy) is None:
            Color.pl('{!} {R}error: invalid proxy {O}%s{R}{W}\r\n' % Configuration.proxy)
            Configuration.exit_gracefully(0)

        if Configuration.proxy_report_to != '' and re.match(regex, Configuration.proxy_report_to) is None:
            Color.pl('{!} {R}error: invalid report to proxy {O}%s{R}{W}\r\n' % Configuration.proxy_report_to)
            Configuration.exit_gracefully(0)

        if not os.path.isfile(Configuration.ip_list):
            Color.pl('{!} {R}error: word list file not found {O}%s{R}{W}\r\n' % Configuration.ip_list)
            Configuration.exit_gracefully(0)

        if Configuration.out_file != '':
            try:
                with open(Configuration.out_file, 'a') as f:
                    # file opened for writing. write to it here
                    Logger.out_file = Configuration.out_file
                    f.write(Color.sc(Configuration.get_banner()) + '\n')
                    f.write(Color.sc('{+} {W}Startup parameters') + '\n')
                    pass
            except IOError as x:
                if x.errno == errno.EACCES:
                    Color.pl('{!} {R}error: could not open output file to write {O}permission denied{R}{W}\r\n')
                    Configuration.exit_gracefully(0)
                elif x.errno == errno.EISDIR:
                    Color.pl('{!} {R}error: could not open output file to write {O}it is an directory{R}{W}\r\n')
                    Configuration.exit_gracefully(0)
                else:
                    Color.pl('{!} {R}error: could not open output file to write{W}\r\n')
                    Configuration.exit_gracefully(0)

        try:
            with open(Configuration.ip_list, 'r') as f:
                # file opened for writing. write to it here
                pass
        except IOError as x:
            if x.errno == errno.EACCES:
                Logger.pl('{!} {R}error: could not open word list file {O}permission denied{R}{W}\r\n')
                Configuration.exit_gracefully(0)
            elif x.errno == errno.EISDIR:
                Logger.pl('{!} {R}error: could not open word list file {O}it is an directory{R}{W}\r\n')
                Configuration.exit_gracefully(0)
            else:
                Logger.pl('{!} {R}error: could not open word list file {W}\r\n')
                Configuration.exit_gracefully(0)

        Logger.pl('     {C}target:{O} %s{W}' % Configuration.target)
        Logger.pl('     {C}host:{O} %s{W}' % Configuration.host)

        if Configuration.proxy != '':
            Logger.pl('     {C}Proxy:{O} %s{W}' % Configuration.proxy)

        Logger.pl('     {C}tasks:{O} %s{W}' % Configuration.tasks)

        if args.verbose:
            Logger.pl('     {C}option:{O} verbosity level %d{W}' % Configuration.verbose)

        Logger.pl('     {C}request method: {O}%s{W}' % Configuration.request_method)

        if Configuration.user_agent:
            Logger.pl('     {C}user agent: {O}%s{W}' % Configuration.user_agent)

        Logger.pl('     {C}ip address list:{O} %s{W}' % Configuration.ip_list)

        if Configuration.out_file != '':
            Logger.pl('     {C}output file:{O} %s{W}' % Configuration.out_file)

    @staticmethod
    def get_banner():
            """ Displays ASCII art of the highest caliber.  """

            Configuration.version = str(__version__)

            return '''\

       {G}Web Finder {D}v%s{W}{G} by Helvio Junior{W}
       {W}{D}automated web server finder{W}
       {C}{D}https://github.com/helviojunior/webfinder{W}

    ''' % Configuration.version


    @staticmethod
    def mandatory():
        Color.pl('{!} {R}error: missing a mandatory option ({O}-t and -ip{R}){G}, use -h help{W}\r\n')
        Configuration.exit_gracefully(0)

    @staticmethod
    def exit_gracefully(code=0):
        ''' Deletes temp and exist with the given code '''

        exit(code)


    @staticmethod
    def kill(code=0):
        ''' Deletes temp and exist with the given code '''

        os.kill(os.getpid(),signal.SIGTERM)


    @staticmethod
    def dump():
        ''' (Colorful) string representation of the configuration '''
        from .util.color import Color

        max_len = 20
        for key in Configuration.__dict__.keys():
            max_len = max(max_len, len(key))

        result  = Color.s('{W}%s  Value{W}\n' % 'Configuration Key'.ljust(max_len))
        result += Color.s('{W}%s------------------{W}\n' % ('-' * max_len))

        for (key,val) in sorted(Configuration.__dict__.items()):
            if key.startswith('__') or type(val) == staticmethod or val is None:
                continue
            result += Color.s("{G}%s {W} {C}%s{W}\n" % (key.ljust(max_len),val))
        return result

if __name__ == '__main__':
    Configuration.initialize(False)
    print(Configuration.dump())
