### VARIOUS IMPORTS STARTS ###

import argparse
from concurrent.futures import ThreadPoolExecutor
import csv
import datetime
import signal 
import sys
import threading
import os
import concurrent.futures
import json
import multiprocessing
from itertools import product
from tqdm import tqdm
from pytimedinput import timedKey
from traceback import format_exc, print_exc
from res.tee import Tee
from time import sleep
from itertools import cycle
from multiprocessing import Event, Queue, Process
from seleniumwire.undetected_chromedriver.v2 import Chrome, ChromeOptions
from seleniumwire.utils import decode
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoSuchWindowException
from selenium.common.exceptions import WebDriverException
from selenium.common.exceptions import NoAlertPresentException
from http.client import RemoteDisconnected
from urllib3.exceptions import ProtocolError
from urllib3.exceptions import MaxRetryError
from bs4 import BeautifulSoup as bs
from urllib.parse import urlparse
from colorama import Fore

### VARIOUS IMPORTS ENDS ###

# Defining color contants #
GREEN = Fore.GREEN
YELLOW = Fore.YELLOW
RED = Fore.RED
RESET = Fore.RESET

### PRINTING BANNER ###
print(f"""{GREEN}
##################################################################################################################################################################################
##################################################################################################################################################################################
##                                                                                                                                                                             ###
## {YELLOW}██████╗░██████╗░░█████╗░░██╗░░░░░░░██╗░██████╗███████╗██████╗░░░░░░░██████╗░██████╗░██╗░░░██╗████████╗███████╗██████╗░                                                       
{GREEN}## {YELLOW}██╔══██╗██╔══██╗██╔══██╗░██║░░██╗░░██║██╔════╝██╔════╝██╔══██╗░░░░░░██╔══██╗██╔══██╗██║░░░██║╚══██╔══╝██╔════╝██╔══██╗⠀⠀⠀⠀{RED}⠿⠿⠿⠿⠿⢿⣿⣿⣿⣿⣿⣿⡇⢰⣶⣶⣶⣶⣶⣶⣾⣷⣾⣷⣶⠀{YELLOW}. . . . . . . . . . . . . . . 
{GREEN}## {YELLOW}██████╦╝██████╔╝██║░░██║░╚██╗████╗██╔╝╚█████╗░█████╗░░██████╔╝█████╗██████╦╝██████╔╝██║░░░██║░░░██║░░░█████╗░░██████╔╝⠀{RED}⣶⣶⣶⣶⣶⣶⣶⡆⢸⣿⣿⣿⣿⣿⣿⡇⠸⠿⠿⠿⠿⠿⠿⢿⡿⢿⡿⠿⠀{YELLOW}. . . . .  . . . . . . . . . . 
{GREEN}## {YELLOW}██╔══██╗██╔══██╗██║░░██║░░████╔═████║░░╚═══██╗██╔══╝░░██╔══██╗╚════╝██╔══██╗██╔══██╗██║░░░██║░░░██║░░░██╔══╝░░██╔══██╗⠀{RED}⣿⣿⣿⣿⣿⣿⣿⡇⠘⠛⢻⠟⠛⣿⠛⠃⠀⠀⠀⠀⠀⠀⠀⠈⠁⠀⠁⠀⠀                  
{GREEN}## {YELLOW}██████╦╝██║░░██║╚█████╔╝░░╚██╔╝░╚██╔╝░██████╔╝███████╗██║░░██║░░░░░░██████╦╝██║░░██║╚██████╔╝░░░██║░░░███████╗██║░░██║⠀{RED}⠿⠿⠿⠿⠿⠿⠿⠇⠀⠀⣸⠀⢰⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀                  
{GREEN}## {YELLOW}╚═════╝░╚═╝░░╚═╝░╚════╝░░░░╚═╝░░░╚═╝░░╚═════╝░╚══════╝╚═╝░░╚═╝░░░░░░╚═════╝░╚═╝░░╚═╝░╚═════╝░░░░╚═╝░░░╚══════╝╚═╝░░╚═╝⠀⠀⠀{RED}⣆⠀⢶⡆⠀⠀⠀⢀⡟⠀⣼⡇                                 
{GREEN}##                                                                                                                       ⠀⠀⠀{RED}⢹⣄⠘⣷⡀⠀⢀⡼⠁⣰⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀                  
{GREEN}##                                                                                                                       ⠀⠀⠀⠀{RED}⠙⠦⡈⠻⢶⣿⣥⡾⠋ {YELLOW}by Jafar Pathan & NetSquare Team v1.0 
{GREEN}##                                                                                                                           {YELLOW}An Advance Browser Automated Web Form Fuzzing Tool{GREEN}### 
{GREEN}##################################################################################################################################################################################
{GREEN}##################################################################################################################################################################################{RESET}""", flush=True)


### DEFINING AND PARSING COMMAND LINE ARGUMENTS START ###
# Getting argument parser to parse and process arguments
argParser = argparse.ArgumentParser(description="BrowserBruter is a python3 script, utilizing power of selenium and selenium-wire to automate fuzzing of various input fields of webpages to test their security against malicious inputs. For contact and more information about project please visit https://github.com/netsquare/BrowserBruter",formatter_class=argparse.RawTextHelpFormatter)
# Defining the epilog message which will be displayed whith help message
argParser.epilog = f'''
Usage Examples:
	1. Fuzz on login page
	   {YELLOW}python3 BrowserBruter.py -e username,password -p sqli.txt -t http://owasp.com/login -b loginButton{RESET}
	
	2. Fuzz on login page with csrf enabled
	   {YELLOW}python3 BrowserBruter.py -e username,password -p sqli.txt -t http://owasp.com/login -b loginButton --avoid csrfToken{RESET}
	
	3. Fuzz on registration page with csrf enabled no output printed on console
	   {YELLOW}python3 BrowserBruter.py -e name,age,address,phone -p payloads.txt -t http://dvwa.com/register -b register --avoid _token --silent{RESET}
	
	4. Fuzz on 3rd form of registration page with csrf enabled no output printed on console
	   {YELLOW}python3 BrowserBruter.py -e name,age,address,phone -p payloads.txt -t http://dvwa.com/register -b register --avoid _token --silent --form 3{RESET}
	
	5. Fuzz on registration page with csrf and two cookies 1)'difficulty' and 2)'hint'
	   {YELLOW}python3 BrowserBruter.py -e name,age,address,phone -p payloads.txt -t http://dvwa.com/register -b register --cookie difficulty:high:dvwa.com hint:no:dvwa.com --avoid _token{RESET}
	
	6. Fuzz on registration page with csrf and two cookies 1)'difficulty' and 2)'hint' and sent them forcefully on each request becuase the initiali cookies might be overridden by new cookies values
	   {YELLOW}python3 BrowserBruter.py -e name,age,address,phone -p payloads.txt -t http://dvwa.com/register -b register --cookie difficulty:high:dvwa.com hint:no:dvwa.com --avoid _token --forceCookie{RESET}
	
	7. Fuzz on 3rd form of registration page with csrf and two cookies 1)'difficulty' and 2)'hint' and sent them forcefully on each request and remove session data and cookie after each request-response cycle [this is useful against Authentication pages when you don't want redirection in case of successful login]
	   {YELLOW}python3 BrowserBruter.py -e name,age,address,phone -p payloads.txt -t http://dvwa.com/register -b register --cookie difficulty:high:dvwa.com hint:no:dvwa.com --avoid _token --form 3 --forceCookie --removeSession{RESET}
	
	8. Fuzz on 3rd form of registration page with csrf and two cookies 1)'difficulty' and 2)'hint' and sent them forcefully on each request and remove session data and cookie after each request-response cycle and run browser in headless mode
	   {YELLOW}python3 BrowserBruter.py -e name,age,address,phone -p payloads.txt -t http://dvwa.com/register -b register --cookie difficulty:high:dvwa.com hint:no:dvwa.com --avoid _token --form 3 --forceCookie --removeSession --headless{RESET}
	
	9. Fuzz on 3rd form of registration page with csrf and two cookies 1)'difficulty' and 2)'hint' and sent them forcefully on each request and remove session data and cookie after each request-response cycle and run browser in headless mode and run 5 instances of browser parallely
	   {YELLOW}python3 BrowserBruter.py -e name,age,address,phone -p payloads.txt -t http://dvwa.com/register -b register --cookie difficulty:high:dvwa.com hint:no:dvwa.com --avoid _token --form 3 --forceCookie --removeSession --headless --threads 5{RESET}
   
       10. Fuzz CheckBox for example '<input type="checkbox" name="hobbies" value="reading" /> <input type="checkbox" name="hobbies" value="writing" />'
	   {YELLOW}python3 BrowserBruter.py -e hobbies -p paylods.txt -t http://dvwa.com/register -b register{RESET}
   
       11. Fuzz Radio Button for example '<input type="radio" name="yesno" id="yes" value="yes" required/> <input type="radio" name="yesno" id="no" value="no" required/>'
	   {YELLOW}python3 BrowserBruter.py -e yesno -p payloads.txt -t http://dvwa.com/register -b register {RESET}
	 OR
	   {YELLOW}python3 BrowserBruter.py -e no -p payloads.txt -t http://dvwa.com/register -b register{RESET}
   
       12. Fuzz CSRF token + don't overwrite it while fuzzing other fields
           {YELLOW}python3 BrowserBruter.py -e csrfToken,username,password -p payloads.txt -t http://dvwa.com/login -b login --avoid csrfToken{RESET}
   
       13. Fuzz <select> element - for example <select name="selectElement" required> <option value="">Select an option</option> <option value="option1">Option 1</option> </select>
           {YELLOW}python3 BrowserBruter.py -e selectElement -p payloads.txt -t http://dvwa.com/selection -b submit{RESET}
   
       14. Fuzz <textarea> element - for example <textarea name="textareaElement" placeholder="Enter text" required></textarea>
           {YELLOW}python3 BrowserBruter.py -e textareaElement -p payloads.txt -t http://dvwa.com/registration -b submit{RESET}
   
       15. Fuzz colorpicker, datepicker, timepicker - for example <input type="color" name="colorElement" required/> <input type="date" name="dateElement" required/> <input type="time" name="timeElement" required/>
           {YELLOW}python3 BrowserBruter.py -e colorElement,dateElement,timeElement -p payloads.txt -t http://localhost/ -b submit{RESET}

       16. Define API endpoint in scope for proper report generation and logging.
           {YELLOW}python3 BrowserBruter.py -e username,password -b button -t http://net-square.com/login -p payloads.txt --scope api.net-square.com,dev.api.net-square.com{RESET}

       17. Add Authorization header with bearer token for valid authorization.
           {YELLOW}python3 BrowserBruter.py -e username,password -b button -t http://net-square.com/login -p payloads.txt --headers "Auth: 123","Auth1: Bearer emluamFjb2Rlcgo=" {RESET}
	   
       18. Provide custom values for each form field types, content of the file should be in JSON format and it should contain all of the field types, see example values.json for better understanding.
           {YELLOW}python3 BrowserBruter.py -e username,password -b button -t http://net-square.com/login -p payloads.txt --values fields.json" {RESET}
	   
       19. Provide various fields (elements) to avoid being fuzzed or overwritten by BrowserBruter, set two cookies, force reuse of these cookies, reset session data each time. 
           {YELLOW}python3 BrowserBruter.py -e ip -b Submit -p payloads.txt -t http://example.com/vulnerabilities/exec/ --cookie PHPSESSID:jtq7r9fbgf90h2qm9915qk6551:example.com security:low:example.com  --forceCookie --removeSession --avoid help_button,source_button,user_token{RESET}
       
       20. Pause BrowserBruter on startup to manually login and manually set cookies, press ENTER two times to continue.
           {YELLOW}python3 BrowserBruter.py -e ip -b Submit -p payloads.txt -t http://example.com/vulnerabilities/exec/ --avoid help_button,source_button,user_token --pause
 {RESET}
       21. Pause the BrowserBruter on each iteration of fuzzing, so user can manually perform any task, complete captcha before BrowserBruter fuzzes the form, this will happen for each attempt to fuzz, so it will take a lot of time and user has to press ENTER two times to continue.
           {YELLOW}python3 BrowserBruter.py -e textarea,select,yesno,hobbies,phone,data,time,calendar,color --avoid _csrf -b submit -p payloads.txt -t http://localhost:3000/ --threads 5 --values values.json --iterative{RESET}

	'''
# Adding various command line arguments
argsRequired = argParser.add_argument_group("required")
argsRequired.add_argument("-t","--target",help="Target's url: http://www.net-square.com/index.php")
argParser.add_argument("-e","--elements", help="Enter input fields in comma separated values.")
argParser.add_argument("--elements-payloads",help="pitchfork and clusterbomb")
argParser.add_argument("-p","--payloads",help="/path/to/payload/file.", metavar="FILENAME")
argsRequired.add_argument("-b","--button",help="Button element which will submit form data.")
argsRequired.add_argument("--attack",help="select attack mode",type=int)
argParser.add_argument("--proxy",help="Set proxy to route traffic to, for example give IP:PORT of Burpsuite to send traffic to burpsuite.",metavar="http://proxyaddress:port/",default="")
argParser.add_argument("--scope",help="Comma-separated list of in-scope domains.")
argParser.add_argument("--avoid",help="Input fields and other elements to left untouched, BrowserBruter will avoid them, also useful to avoid csrf field.")
argParser.add_argument("--headers", help=f"Comma-separated list of custom headers.")
argParser.add_argument("--delay",help="Delay between each fuzz attempt.",metavar="0.2", type=float, default=0.2)
argParser.add_argument("--cookie",help="Use it to define cookies to be used while sending initial request, cookies should be in name:value:domain format.", metavar="name:value:domain", nargs="+")
argParser.add_argument("--forceCookie",help="Use this switch to force setting of cookies given as argument using --cookie flag regardless of cookies being sent by server.",action="store_true")
argParser.add_argument("--removeSession",help="Use this switch to remove session data and cookies after each request-response cycle.", action="store_true")
argParser.add_argument("--verbose",help="Use this switch to enable HTTP request/response output being printed on console and STDLOG file.", action="store_true",default=False)
argParser.add_argument("-F","--form",help="Specify the form number to fuzz.", type=int)
argParser.add_argument("--headless",help="Use this switch to run browser in headless mode (No GUI).", action="store_true")
argParser.add_argument("-T","--threads",help="Specifies number of browsers instances to be run, max value is 5, default is 1, lower the instances slower the fuzzing process, more instances - faster fuzzing process.",default=1, type=int)
argParser.add_argument("--values", help="Path to User-configurable attribute values file")
argParser.add_argument("--pause", help="Pause the BrowserBruter and spawn Browser instances on startup, press ENTER to resume",action="store_true",default=False)
argParser.add_argument("--iterative",help="Pause the BrowserBruter before fuzzing any element at each payload and wait for user to continue",action="store_true",default=False)
argParser.add_argument("--replace-file", metavar="FILENAME", help="Replace the content of a file in HTTP responses")
argParser.add_argument("--replace-file-url",metavar="http://address/filename",help="provide url of file to be replace")
argParser.add_argument("--load-static-media",help="This switch tells BrowserBruter to load audio, video and image (.png, .img, .ico, .mp4, .gif, .mp3 etc) files. By default it discards these files to save time and load pages faster.",action="store_true",default=False)
argParser.add_argument("--javascript",help="run javascript")
argParser.add_argument("--replace-code",help="replaces the code in response body with the code provided by user")
# Getting the arguments in args variable
args = argParser.parse_args()

# Check if all required arguments are given and threads are not more than 5
#if args.payloads is None or args.target is None or args.elements is None or args.button is None or args.attack is None:
#	print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: Please Enter all required arguments --target, --paylods, --elements, --button, --attack\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
#	sys.exit(0)
#elif args.threads > 5 or args.threads < 0:
#	print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: Value of threads must less than 6 and more than 0\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
#	sys.exit(0)
# if forceCookie argument is present withouth --cookie option then throw error 
#elif args.forceCookie:
#	if args.cookie is None:
#		print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: You can not use --forceCookie without --cookie option\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
#		sys.exit(0)

### DEFINING AND PARSING COMMAND LINE ARGUMENTS ENDS ###

### DEFINING AND ASSIGNING GLOBAL VARIABLES STARTS ###	

# Getting hostname from target for filtering the output this will work as one kind scope for filtering output to be stored in report
target_url = urlparse(args.target)
hostname = target_url.hostname
# Get the scope hostnames from the command-line arguments
scope_hostnames = args.scope.split(',') if args.scope else []

# Getting time when script started to name the final report
start_time = datetime.datetime.now()
start_time = start_time.strftime("%Y-%m-%d_%H-%M-%S")

# Set verbosity level
#verbosity = args.verbosity

# Replace the response body with the content of the file provided by the user
if args.replace_file:
	if not args.replace_file_url:	
		print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: The --replace-file arguments requires --replace-file-url argument\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
		sys.exit(0)
	try:
		with open(args.replace_file, 'rb') as file:
			response_content = file.read()
	except FileNotFoundError as e:
		print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: The specified replacement file '{args.replace_file}' does not exist.\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
		sys.exit(0)
	

# Setting flag which indicates threads to run or stop
terminate = False
# Pause event will be used to pause the threads when user presses the ENTER KEY
pause_event = Event()
# Set Pause event if --pause flag is set
if args.pause:
	pause_event.set()

# Get replacement code
if args.replace_code:
	to_be_replaced, to_be_replaced_with = args.replace_code.split(',')
	to_be_replaced = to_be_replaced.encode('utf-8')
	to_be_replaced_with = to_be_replaced_with.encode('utf-8')


# Abort request with following extensions
forbidden_extensions = ('.ico', '.png', '.img', '.jpg', '.svg', '.jpeg', '.jfif', '.pjpeg', '.pjp', '.gif', '.apng', '.avif', '.webp', '.bmp', '.cur', '.tif', '.tiff', '.mp3', '.mp4', '.avi', '.mkv', '.webm', 'ogv')

payloads = []
elements_payloads = {}
if args.attack in (1, 2):
    # Get the elements to be fuzzed
    elements = args.elements.split(',')
    # Get Payloads from file and store them in a list
    try:
        with open(args.payloads, "r") as payload_file:
            for line in payload_file:
                line = line.strip()
                payloads.append(line)
    except FileNotFoundError:
        print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: The specified payloads file '{args.payloads}' does not exist.\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
        sys.exit(0)
elif args.attack in (3, 4):
    try:
        for element_payload in args.elements_payloads.split(','):
            element, payload_file_path = element_payload.split(':')
            # Initialize the list if it doesn't exist
            elements_payloads.setdefault(element, [])
            with open(payload_file_path, 'r') as payload_file:
                for line in payload_file:
                    line = line.strip()
                    elements_payloads[element].append(line)
        if args.attack == 3:
            # Check if all lists have the same length
            if len(set(len(payloads) for payloads in elements_payloads.values())) > 1:
                # Find the maximum length of lists in the dictionary
                max_length = max(len(payloads) for payloads in elements_payloads.values())

                # Loop through each element in the dictionary and extend the lists to match the maximum length
                for element in elements_payloads:
                    current_length = len(elements_payloads[element])
                    if current_length < max_length:
                        # Calculate how many elements are needed to reach the maximum length
                        needed_elements = max_length - current_length

                        # Duplicate the list and append elements from the original list until the desired length is reached
                        extended_list = elements_payloads[element].copy()
                        while len(extended_list) < max_length:
                            extended_list += elements_payloads[element]

                        # Trim the list to the maximum length
                        extended_list = extended_list[:max_length]

                        # Update the dictionary with the extended list
                        elements_payloads[element] = extended_list
    except FileNotFoundError:
        print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: The specified payloads file '{args.payloads}' does not exist.\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
        sys.exit(0)


# Creating Reports directory in current directory to store reports
os.makedirs("BrowserBruter_Reports",exist_ok=True)
os.makedirs(f"BrowserBruter_Reports/{hostname}/{start_time}",exist_ok=True)
# Creating directory for selenium to store request-responses.
#os.makedirs("selenium_storage",exist_ok=True)

# Non targeted input field value, will update this to allow user specify them, please contribute to add more types and let us know https://github.com/netsquare/BrowserBruter/issues
attribute_values = {
	# This allows BrowserBruter to put default valid values into field which are not being fuzzed
	# For example BrowserBruter will send 0123456789 in fields which has type tel and which are not being fuzzed
	"text":"text",
	"number":"1234567890",
	"password":"P2$$@@wrd0!@#JJJ",
	"email":"email@email.com",
	"url":"http://localhost.xyz/",
	"file":"name.txt",
	"tel":"9123456780",
	"date":"2023-06-17",
	"datetime":"2023-07-17T10:30",
	"time":"10:30",
	"month":"2023-07",
	"week":"2023-W25",
	"color":"#ff0000",
	"range":"50"
}
# if user has provided values for above, then override them with user's values
if args.values:
    try:
        with open(args.values, 'r') as values_file:
            attribute_values = json.load(values_file)
    except FileNotFoundError:
        print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: The specified values file '{args.values}' does not exist.\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
        sys.exit(0)
    except json.JSONDecodeError:
        print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: Invalid JSON format in the specified values file '{args.values}'.\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
        sys.exit(0)

# Create a lock for threads to print STDOUT
#print_lock = threading.Lock()

# Create a lock for threads to log errors in Error.txt
#log_error_lock = threading.Lock()




### DEFINING AND ASSIGNING GLOBAL VARIABLES ENDS ###

### FUNCTIONS STARTS ###

# Function to get browser driver specific to the running os
#def get_driver_path():
#    os_name = platform.system().lower()
    
#    if os_name == 'linux':
#        return 'res/Drivers/linux/geckodriver' if args.firefox else 'res/Drivers/linux/chromedriver'
#    elif os_name == 'windows':
#        return 'res/Drivers/Win/chromedriver.exe' if args.firefox else 'res/Drivers/Win/geckodriver.exe'
#    elif os_name == 'darwin':  # Mac then use the installed firefox or chrome, no driver support as of now
#        return 'darwin'
#    else:
#        print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: Unsupported operating system: {os_name}\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
#        sys.exit(0)

# Thread safe printing 
#def print_message(message):
    # Acquire the lock before printing
 #   with print_lock:
  #      print(message, flush=True)
        
def slice_dict_for_threads(input_dict, num_threads):
    result = []

    for i in range(num_threads):
        thread_dict = {}
        for key, values in input_dict.items():
            total_elements = len(values)
            elements_per_thread = total_elements // num_threads
            start_index = i * elements_per_thread
            end_index = (i + 1) * elements_per_thread if i < num_threads - 1 else total_elements
            thread_dict[key] = values[start_index:end_index]

        result.append(thread_dict)

    return result

def read_file(file_path):
    try:
        with open(file_path,'r') as file:
              return file.read().splitlines()
    except FileNotFoundError as e:
        print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: The specified payloads file '{file_path}' does not exist.\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
        sys.exit(0)

# Function to Handle CTRL+C
def signal_handler(signal, frame):
	print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: CTRL+C pressed. Waiting for remaining request/response to stop. Exiting...\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
	# Set the global termination flag to True so all threads can stop gracefully
	global terminate
	terminate = True

# Function to run in a separate thread to pause and resume the browserbruter if user presses enter key
def pause_resume():
    global pause_event
    try:
        while not terminate:
            userText, timeout = timedKey(prompt="", timeout=-1, resetOnInput=True)
            if not (timeout):
                pause_event.set()  # Set the pause event
                print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nWARNING: BROWSERBRUTER IS PAUSED\nPress ENTER to resumse\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                k = input()				
                pause_event.clear()  # Clear the pause event 
                print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nWARNING: Resuming BROWSERBRUTER\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
        #else:
         #   print("timeout",end="{RESET}",flush=True,)
         #   sleep(1)
    except KeyboardInterrupt as e:
        signal_handler(signal.SIGINT, None)
        sleep(3)
        log_error(format_exc())
        print(f"{RED}CTRL+C{RESET}", flush=True)
        sys.exit(0)

# Function to get BrowserOptions
def get_browser_options():
	options = ChromeOptions()
	options.add_argument("--disable-ipc-flooding-protection")
	options.add_argument("--disable-xss-auditor")
	options.add_argument("--disable-bundled-ppapi-flash")
	options.add_argument("--disable-plugins-discovery")
	options.add_argument("--disable-default-apps")
	options.add_argument("--disable-prerender-local-predictor")
	options.add_argument("--disable-sync")
	options.add_argument("--disable-breakpad")	
	options.add_argument("--disable-crash-reporter")
	options.add_argument("--disk-cache-size=0")
	options.add_argument("--disable-settings-window")
	options.add_argument("--disable-notifications")
	options.add_argument("--disable-speech-api")
	options.add_argument("--disable-file-system")
	options.add_argument("--disable-presentation-api")
	options.add_argument("--disable-permissions-api")
	options.add_argument("--disable-new-zip-unpacker")
	options.add_argument("--disable-media-session-api")
	options.add_argument("--no-experiments")
	options.add_argument("--no-events")
	options.add_argument("--no-first-run")
	options.add_argument("--no-default-browser-check")
	options.add_argument("--no-pings")
	options.add_argument("--no-service-autorun")
	options.add_argument("--media-cache-size=0")
	options.add_argument("--use-fake-device-for-media-stream")
	options.add_argument("--dbus-stub")
	options.add_argument("--disable-background-networking")
	options.add_argument("--disable-features=ChromeWhatsNewUI,HttpsUpgrades")
	options.add_argument("--proxy-bypass-list=0.0.0.0")
	options.add_argument('--user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.5993.90 Safari/537.36"')
	options.add_argument("--ignore-certificate-errors")
	# add below option to prevent use of shared memory
	options.add_argument('--disable-dev-shm-usage')
	# adding below to hide that browser is automatically controlled which can be detected by some websites
	options.add_argument('--disable-blink-features=AutomationControlled')
	if args.headless:
		options.add_argument('--headless')
#	driver_path = get_driver_path() # Get driver path according to specific os
#	if driver_path != "darwin": # If running os is mac use pre-installed browser, do not use provided driver
#			options.add_argument(f'--executable-path={get_driver_path()}')
	return options

# Function to add cookies into selenium session
def add_cookies(driver):
	try:
		for cookie_arg in args.cookie:
			# Get the cookie data
			name, value, domain = cookie_arg.split(":")
			# Create cookie dictionary as selenium requires them in dictionary format
			cookie_dict = {
				"name": name,
				"value": value,
				"domain": domain
			}
			#if args.firefox:
			#	driver.execute_script(f'document.cookie="{cookie_dict["name"]}={cookie_dict["value"]}";')
			#else:
			driver.add_cookie(cookie_dict)  

	except ValueError as e:
		sleep(2)
		log_error(format_exc())
		print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: You have entered arguments in invalid format, please read help message for valid formate of passing cookies. Closing the Fuzzing process\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
		driver.quit()
		sys.exit(0)
	
# Function to intercept requests
def intercept_request(request):
	try:
		#print("Request Path:", request.path)
		#print("Is Forbidden:", any(extension in request.path.lower() for extension in forbidden_extensions))

		if any(extension in request.path.lower() for extension in forbidden_extensions):
			request.abort()
			
	except Exception as e:
		log_error(format_exc())

# Function to modify response
def replace_response_content(request, response):
    try:
		
        if args.replace_code: 
            if to_be_replaced in request.response.body:
                #print(f"\n\n{YELLOW}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: Replacing code {to_be_replaced} -> {to_be_replaced_with}\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                new_response_body = request.response.body.replace(to_be_replaced,to_be_replaced_with)
                request.response.body = new_response_body

        # Check if the request URL matches the filename provided by the user
        if args.replace_file and args.replace_file_url == request.url: #and args.replace_file in request.path:
            #print(f"\n\n{YELLOW}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: Replacing response file -> {args.replace_file}\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
            
           # sleep(1)
            

            #print(f"Original response body length: {len(response.body)}")
            #print(f"New response body length: {len(response_content)}")

            # Update the response content using replace method
            response.body = response_content
            #print(response.body)

            #print(f"Updated response body length: {len(response.body)}")

            # Update the Content-Length header to reflect the new body length
            #request.response.headers['Content-Length'] = str(len(response_content))
        #else:
            #request.abort()
    except Exception as e:
        log_error(format_exc())

# Function to Remove attributes this is created to reduce code in attempt function
def remove_attributes(driver, field):
	driver.execute_script("arguments[0].removeAttribute('pattern');",field)
	driver.execute_script("arguments[0].removeAttribute('min');",field)
	driver.execute_script("arguments[0].removeAttribute('max');",field)
	driver.execute_script("arguments[0].removeAttribute('maxlength');",field)
	driver.execute_script("arguments[0].removeAttribute('minlength');",field)
	driver.execute_script("arguments[0].removeAttribute('readonly');",field)

# Function to log errors 
def log_error(error):
	try:
		#with log_error_lock:
		with open("logs/Error.txt","a") as log:
			error_time = datetime.datetime.now()
			error_time = error_time.strftime("%Y-%m-%d_%H-%M-%S")
			log.write(f"\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n")
			log.write(f"Error Time - {error_time}")
			log.write("\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n")
			log.write(error)
	except:
		print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: An unknown error has been occured, Please open issue request at https://github.com/netsquare/BrowserBruter/issues and paste above message there, we are glad to help\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)

# Funtion to Generate filename for each differnt thread
def get_filename():
	# Getting current date and time to name the output file accrodingly
	current_datetime = datetime.datetime.now()
	formatted_datetime = current_datetime.strftime("%Y-%m-%d_%H-%M-%S")
	filename = [f"BrowserBruter_Reports/{hostname}/{start_time}/{hostname}-{formatted_datetime}.csv",f"BrowserBruter_Reports/{hostname}/{start_time}/{hostname }-{formatted_datetime}.txt"]
	return filename

# Function to Generate Final Report
def generate_final_report():
    print(f"\n\n{YELLOW}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: Generating Final Report\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
    directory = f"BrowserBruter_Reports/{hostname}/{start_time}"
    final_report = f"BrowserBruter_Reports/{hostname}/{start_time}/{hostname}-{start_time}.csv"
    try:
        processed_payloads = f"BrowserBruter_Reports/{hostname}/{start_time}/Processed_Payloads.txt"
        all_threads_files = [file for file in os.listdir(directory) if file.endswith('.csv')]
        all_processed_payloads_files = [file for file in os.listdir(directory) if file.endswith('.txt')]
        global payloads
        # Initialize index counter
        index_counter = 0

        # Merge other files into a single final report
        with open(final_report, 'w', newline='') as final:
            writer = csv.writer(final)

            # Insert column names or, in other words, headings
            writer.writerow(['Index', 'Request Time', 'Selected', 'Payload', 'Web Page Before', 'Method', 'URL', 'Request Headers', 'Request Body', 'Response Time', 'Cycle Time MilliSeconds', 'Response Status Code', 'Response Reason', 'Response Headers', 'Response Body', 'Response Body Length', 'Web Page After'])
            # Iterate over each CSV file
            for csv_file in all_threads_files:
                file_path = os.path.join(directory, csv_file)

                # Open the current CSV file in 'r' mode
                with open(file_path, 'r') as infile:
                    reader = csv.reader(infile)

                    # Read and write the rows to the output file
                    for row in reader:
                        # Insert the index value at the beginning of each row
                        row.insert(0, index_counter)
                        writer.writerow(row)

                        # Increment the index counter
                        index_counter += 1

                # Delete the current CSV file
                os.remove(file_path)

        if args.attack in (1, 2):
            # Merge the processed payloads file into one; logic is the same as above
            with open(processed_payloads, 'w', newline='') as final_processed_payloads:
                for one_threads_processed_payloads in all_processed_payloads_files:
                    file_path = os.path.join(directory, one_threads_processed_payloads)

                    with open(file_path, 'r') as infile:
                        for i in infile:
                            final_processed_payloads.write(i)
                            # keeping track of remaining payloads by removing processed payloads from payload[] list
                            payloads.remove(i.strip())
                    # Delete the current thread's processed payloads file
                    os.remove(file_path)
                     # Storing remaining payloads in a separate file
            remaining_payloads = os.path.join(directory, "Remaining_Payloads.txt")
            with open(remaining_payloads, 'w', newline='') as remaining_payloads_file:
                for payload in payloads:
                    remaining_payloads_file.write(payload + '\n')
        #else:
            # Read data from the processed.txt file
        #   with open(all_processed_payloads_files[0], 'r') as processed_file:
        #         processed_data = [line.strip() for line in processed_file]

            # Loop through each element in the dictionary
        #    for element in elements_payloads:
            # Remove matching elements from the list
        #        elements_payloads[element] = [item for item in elements_payloads[element] if item not in processed_data]

            # Save the updated dictionary
        #    with open('remaining_payloads.txt', 'w') as output_file:
        #        for element, payloads in elements_payloads.items():
        #            output_file.write(f"{element}: {', '.join(payloads)}\n")
            
            print(f"\n\n{YELLOW}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: Remaining Payloads (if any) have been stored -> {remaining_payloads}\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
        print(f"\n\n{YELLOW}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: Processed Payloads (if any) have been stored -> {processed_payloads}\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
        print(f"\n\n{YELLOW}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: Report Generated -> {final_report}\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
    except FileNotFoundError:
        sys.exit(0)


# Function to Run Single Browser instance
def run_browser_instance(payloads, instance_number):
    try:
        # Spawing an instance of browser
        # Assigning browser options
        # Checking whether user has to run fuzzing on firefox or chrome and assigning browser options
        options = get_browser_options()
        driver = Chrome(options=options, seleniumwire_options={'proxy': {'http': args.proxy, 'https': args.proxy}} if args.proxy else {})
        # webdriver.Firefox(options=options,seleniumwire_options={'proxy': { 'http': args.proxy, 'https': args.proxy } } if args.firefox else undetected_chromedriver.Chrome(options=options,seleniumwire_options={'proxy': { 'http': args.proxy, 'https': args.proxy } } if args.proxy else {})

        # Set scope
        # driver.scopes = [hostname]

        # Set request interceptor
        if not args.load_static_media:
            driver.request_interceptor = intercept_request

        if args.replace_code or args.replace_file:
            # Intercept HTTP responses to modify the http response body if required
            driver.response_interceptor = replace_response_content

        # If cookies are provided assign them to session
        if args.cookie:
            # first visit the domain so Chrome does not trow InvalidCookieDomainException
            driver.get(args.target)
            add_cookies(driver)

        # Set custom headers
        if args.headers:
            custom_headers = {}
            try:
                # Split the raw string into headers and set each one
                for header in args.headers.split(','):
                    key, value = map(str.strip, header.split(':'))
                    custom_headers[key] = value
                # Update header_overrides with all custom headers
                driver.header_overrides = custom_headers
            except ValueError:
                print(
                    f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: Error setting headers. Please provide headers in valid format.\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                driver.quit()
                sys.exit(0)

        # Initialize report file and processed payload file
        this_threads_files = get_filename()
        if args.attack == 1:
            sniper(payloads, elements, instance_number, this_threads_files, driver)
        elif args.attack == 2:
            battering_ram(payloads, elements, instance_number, this_threads_files, driver)
        elif args.attack == 3:
            pitchfork(payloads,instance_number,this_threads_files,driver)
        elif args.attack == 4:
            attempt_clusterbomb_fuzz(payloads,driver,this_threads_files,instance_number)

        # Handle the exceptions which are specific to this thread and do not affect other threads
    except NoSuchWindowException as e:
        sleep(0.5)
        log_error(format_exc())
        print(
            f"\n\n{YELLOW}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: Browser's window has been closed, closing the BrowserBruter, check error log if this is unintentional\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
    except RemoteDisconnected as e:
        sleep(0.7)
        log_error(format_exc())
        # This exception can be arrived when the user closes the browser window
        print(
            f"\n\n{YELLOW}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: Browser's window has been closed or Remote connection lost, check error log if this is unintentional\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
    except ProtocolError as e:
        sleep(0.9)
        log_error(format_exc())
        # This exception can be arrived when the user closes the browser window
        print(
            f"\n\n{YELLOW}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: Browser's window has been closed or Remote connection lost, check error log if this is unintentional\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
    except MaxRetryError as e:
        sleep(0.3)
        error = format_exc()
        log_error(error)
        # This exception can be arrived when the user closes the browser window
        print(
            f"\n\n{YELLOW}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: Browser's window has been closed or Browsers has reached maximum retries, if you have closed BrowserBruter ignore this else report the issue, check error log if this is unintentional\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
    except WebDriverException as e:
        sleep(1)
        log_error(format_exc())
        # This exception can be arrived when the user closes the browser window
        print(
            f"\n\n{YELLOW}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: Browser's window has been closed or Remote connection lost, check error log if this is unintentional\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
    except SystemExit as e:
        sleep(1)
        log_error(format_exc())
    except:
        sleep(3.5)
        log_error(format_exc())
        # Print Traceback
        print_exc()
        # Ask the user to send this to GitHub
        print(
            f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: An unknown error has been occurred, Please open pull request at https://github.com/netsquare/BrowserBruter/issues and paste above message there, we are glad to help\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
    finally:
        # close the specific thread's driver
        try:
            if driver is not None:
                driver.quit()
        except UnboundLocalError:
            sys.exit(0)

def pitchfork(elements_payloads,instance_number,this_threads_files,driver):
    # Determine the number of iterations based on the maximum length of test data files
    num_iterations = max(len(payload_list) for payload_list in elements_payloads.values())
    progress_bar = tqdm(total=num_iterations, desc=f"Fuzzing Progress for Browser -> {instance_number}", unit="iteration", dynamic_ncols=True, colour='blue')
    processed_payloads = []
    for i in range(num_iterations):
        #fuzz_values = {}
        #for element, payload_list in elements_payloads.items():
            #index = i % len(payload_list)
            #fuzz_values[element] = payload_list[index]

            # Fuzz until terminate flag is not set, if it is set then exit and close the browser
        if not terminate:
            # If the iterative option is set, stop and wait for the user to continue
            if args.iterative:
                pause_event.set()
            # If the pause event is set, then pause the fuzzing process
            if pause_event.is_set():
                while pause_event.is_set():
                    sleep(1)

            attempt_pitchfork_fuzz(elements_payloads, i, driver, this_threads_files[0])
            sleep(args.delay)
            for element, payload_list in elements_payloads.items():
                processed_payloads.append(payload_list[i])
            # Update the progress bar
            progress_bar.update(1)

        # Add the processed payload into the file to keep track of processed payloads by this thread
        with open(this_threads_files[1], 'w', newline='') as processed_payload_file:
            processed_payload_file.write(str(processed_payloads) + '\n')

    # Close the progress bar
    progress_bar.close()

  
		
def attempt_pitchfork_fuzz(elements_payloads,index,driver,this_threads_files):
    #print(list(elements_payloads.items())[index])
    payloads = []
    for element, payload_list in elements_payloads.items():
        payloads.append(payload_list[index])
    print(payloads)


        
    #print(elements)
    #print(payloads)
    # If --forceCookie is set then set the initial cookies
    if args.forceCookie:
        add_cookies(driver)

    # Go to the target website
    driver.get(args.target)

    # Execute custom javascript code
    if args.javascript:
        driver.execute_script(args.javascript)
        
    # Handle alert if it is present
    try:
        alert = driver.switch_to.alert
        alert.accept()
    except NoAlertPresentException:
        pass
    except Exception as e:
        log_error(format_exc())
        print_exc()
        print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: An unknown error has occurred. Please open a pull request at https://github.com/netsquare/BrowserBruter/issues and paste the above message there; we are glad to help\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")

    # Clear previous requests
    del driver.requests

    # Wait for body to be loaded in case of slow response
    wait = WebDriverWait(driver, 10)
    wait.until(EC.presence_of_all_elements_located(("xpath", '//body')))

    # Get web page content before making request
    webpage_before = driver.page_source

    # Get the input field of form to be filled
    # If the user has specified the form number 
    if args.form:
        input_fields = driver.find_elements(By.XPATH, f"//form[{args.form}]//input")
        select_fields = driver.find_elements(By.XPATH, f"//form[{args.form}]//select")
        textarea_fields = driver.find_elements(By.XPATH, f"//form[{args.form}]//textarea")
        input_fields.extend(select_fields)
        input_fields.extend(textarea_fields)
    else:
        input_fields = driver.find_elements(By.XPATH, "//input")
        select_fields = driver.find_elements(By.XPATH, "//select")
        textarea_fields = driver.find_elements(By.XPATH, "//textarea")
        input_fields.extend(select_fields)
        input_fields.extend(textarea_fields)

    # Check if the form with the specified number exists or not
    if not input_fields:
        print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: Form with the specified number does not exist. Please verify the form number. Closing the BrowserBruter\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
        driver.quit()
        sys.exit(0)

    # Remove CSRF and other elements to be excluded to avoid overwriting it
    if args.avoid:
        list_of_elements_to_avoid = args.avoid.split(',')
        input_fields = [
            field for field in input_fields
            if field.get_attribute("name") not in list_of_elements_to_avoid and
            field.get_attribute("id") not in list_of_elements_to_avoid and
            field.get_attribute("type") not in ["button", "submit"]
        ]

    for field in input_fields:
        # Fill other fields with valid inputs
        # Taking field value appropriate to its type
        fieldType = field.get_attribute("type")
        if fieldType == "checkbox" or fieldType == "radio":
            driver.execute_script("arguments[0].checked = true;", field)
        # Adding support to handle <select> tags
        elif field.tag_name == "select":
            # Select the second option
            options = field.find_elements(By.TAG_NAME, "option")
            # if there are two options in <select>, choose the second one as the first option can be empty, for example, <option value="">choose country</option><option value="india">india</option>
            if len(options) == 1:
                remove_attributes(driver, options[0])
                # Set payload to the first option
                #driver.execute_script("arguments[0].setAttribute('value', arguments[1]);", options[0], payload)
                options[0].click()
            else:
                remove_attributes(driver, options[1])
                options[1].click()
            
        elif field.tag_name == "textarea":
            remove_attributes(driver, field)
            field.clear()  # Clear any existing value
            field.send_keys("randomTextAreaValue")
        else:
            # Removing attributes that can conflict
            remove_attributes(driver, field)
            # Filling the predefined values
            fieldValue = attribute_values.get(fieldType, "defaultValue")
            driver.execute_script("arguments[0].setAttribute('value', arguments[1]);", field, fieldValue)

    # Fill the target field being fuzzed with the current payload
    # Finding the element either by id, name, or class
    #print(payloads)
    #print(element)
    for element, payload_list in elements_payloads.items():
        #for index, payload in enumerate(payload_list):
            #print("payload"+payload)
        try:
            element_being_fuzzed = driver.find_element(By.ID, element)
        except NoSuchElementException:
            try:
                element_being_fuzzed = driver.find_element(By.NAME, element)
            except NoSuchElementException:
                try:
                    element_being_fuzzed = driver.find_element(By.CLASS_NAME, element)
                except NoSuchElementException as e:
                    sleep(1.2)
                    log_error(format_exc())
                    print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: Specified element {element} is not found. Please verify the name of the element. For more information, check Error.txt\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                    driver.quit()
                    sys.exit(0)
       # print(element)
        #print(f"Debug: element={element}, index={index}")
        # Check if the element that is being fuzzed is <select> or not, if it is <select>, then set payload to its first <option> tag and mark it as the selected option
        if element_being_fuzzed.tag_name == "select":
            options = element_being_fuzzed.find_elements(By.TAG_NAME, "option")
            # if there are two options in <select>, choose the second one as the first option can be empty, for example, <option value="">choose country</option><option value="india">india</option>
            if options:
                remove_attributes(driver, options[0])
                # Set payload to the first option
                driver.execute_script("arguments[0].setAttribute('value', arguments[1]);", options[0], payload_list[index])
                print(payload_list[index])
                options[0].click()

        elif element_being_fuzzed.tag_name == "textarea":
            remove_attributes(driver, element_being_fuzzed)
            field.clear()  # Clear any existing value
            field.send_keys(payload_list[index])
            #print(payload)
        else:
            # Removing attributes that can conflict
            remove_attributes(driver, element_being_fuzzed)

            # Setting the payload
            driver.execute_script("arguments[0].setAttribute('type','text');", element_being_fuzzed)
            driver.execute_script("arguments[0].setAttribute('value',arguments[1]);", element_being_fuzzed, payload_list[index])
            #print(payload)


    # Press the button
    try:
        driver.find_element(By.ID, args.button).click()
    except NoSuchElementException:
        try:
            driver.find_element(By.NAME, args.button).click()
        except NoSuchElementException:
            try:
                driver.find_element(By.CLASS_NAME, args.button).click()
            except NoSuchElementException as e:
                sleep(1.7)
                log_error(format_exc())
                print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: Button element {args.button} is not found to press. Please verify the id or name of the button element. For more information, check Error.txt\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                driver.quit()
                sys.exit(0)

    # Getting the current time to log it into the report
    request_datetime = datetime.datetime.now()
    requestTime = request_datetime.strftime("%H-%M-%S")

    # Wait for all requests to be completed
    wait = WebDriverWait(driver, 10)
    wait.until(EC.presence_of_all_elements_located(("xpath", '//body')))

    # Get web page content after making the response
    webpage_after = driver.page_source

    # Note the response time to log it into the report
    response_datetime = datetime.datetime.now()
    responseTime = response_datetime.strftime("%H-%M-%S")

    # Calculating the difference between request - response time in milliseconds
    cycle_time = response_datetime - request_datetime
    cycle_time_in_milliseconds = int(cycle_time.total_seconds() * 1000)

    with open(this_threads_files, 'a', newline='') as report:
        # Filtering requests that are in scope
        captured_requests = driver.requests
        filtered_requests = [single_request for single_request in captured_requests if
                             (hostname in single_request.url or scope_hostnames)]
        filtered_requests = [single_request for single_request in filtered_requests if all(extension not in single_request.url for extension in forbidden_extensions)]
		
        writer = csv.writer(report)
        for request in filtered_requests:
            request_body = decode(request.body, "utf-8")
            # Check whether the output should be printed on the console or not
            if args.verbose: 
                # Print the request
                print(f'\n{GREEN}---------------------Single Request/Response Cycle-------------------')
                print(f"Fuzzing - " + str(list(elements_payloads.keys())))
                print(f"Payload - " + str(payloads))
                print(f'----------------------REQUEST---------------------{RESET}')
                print('Time - ' + requestTime + '\n', request.method, request.url)
                # Print in a new line
                print(request.headers, request_body)
                # Print the response
                print(f'{GREEN}----------------------RESPONSE--------------------{RESET}')
                if request.response:
                    print(
                        'Time - ' + responseTime + '\n',
                        request.response.status_code,
                        request.response.reason
                    )
                print(request.response.headers)
                raw = decode(request.response.body, request.response.headers.get('Content-Encoding', 'identity'))
                # Using BeautifulSoup4 to reformat the HTML content
                soup = bs(raw, features="html.parser")
                print(soup.prettify())
                print(
                    f"{GREEN}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\nTIME: {cycle_time_in_milliseconds} MiliSeconds ({cycle_time})\n\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                # After printing on the display, write it to the report
                row = [value if value else ' ' for value in
                       [requestTime, str(list(elements_payloads.keys())), str(payloads), webpage_before, request.method, request.url, request.headers,
                        request_body, responseTime, cycle_time_in_milliseconds, request.response.status_code,
                        request.response.reason, request.response.headers, soup.prettify(),
                        len(request.response.body), webpage_after]]
                writer.writerow(row)
            else:
                # Store the logs in the report file
                raw = decode(request.response.body,
                             request.response.headers.get('Content-Encoding', 'identity'))
                soup = bs(raw, features="html.parser")
                # Substituting blank or no values with N/A then writing it to the file
                row = [value if value else ' ' for value in
                       [requestTime, str(list(elements_payloads.keys())), str(payloads), webpage_before, request.method, request.url, request.headers,
                        request_body, responseTime, cycle_time_in_milliseconds, request.response.status_code,
                        request.response.reason, request.response.headers, soup.prettify(),
                        len(request.response.body), webpage_after]]
                writer.writerow(row)

    # Clear the cookies and other data
    if args.removeSession:
        driver.delete_all_cookies()



def sniper(payloads,elements,instance_number,this_threads_files,driver):
	progress_bar = tqdm(total=len(payloads) * len(elements), desc=f"Fuzzing Progress for Browser -> {instance_number}", unit="iteration",dynamic_ncols=True,colour='blue')

		# Start the Fuzzing process
	for i in range(len(payloads)):
		for j in range(len(elements)):
			# Fuzz until terminate flag is not set, if it set then exit and close browser
			if not terminate:
				# if iterative option is set stop and wait for user to continue
				if args.iterative:
					pause_event.set()
				# if pause event is set then pause the fuzzing process
				if pause_event.is_set():
					while pause_event.is_set():
						sleep(1)
				attempt_sniper_fuzz(elements[j],payloads[i], driver, this_threads_files[0])
				sleep(args.delay)
				# Update the progress bar
				progress_bar.update(1)
			j += 1
		# Add the processed payload into the file to keep track of processed payloads by this thread
		with open(this_threads_files[1],'a',newline='') as processed_payload_file:
			processed_payload_file.write(payloads[i]+'\n')
		i+=i
	#print(f"\n\n{YELLOW}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: Fuzzing completed for Browser Instance number: {instance_number}\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
	progress_bar.close()
	
def battering_ram(payloads,elements,instance_number,this_threads_files,driver):
	progress_bar = tqdm(total=len(payloads), desc=f"Fuzzing Progress for Browser -> {instance_number}", unit="iteration",dynamic_ncols=True,colour='blue')

	# Start the Fuzzing process
	for i in range(len(payloads)):
	#	for j in range(len(elements)):
		# Fuzz until terminate flag is not set, if it set then exit and close browser
		if not terminate:
		# if iterative option is set stop and wait for user to continue
			if args.iterative:
				pause_event.set()
				# if pause event is set then pause the fuzzing process
			if pause_event.is_set():
				while pause_event.is_set():
					sleep(1)
			attempt_battering_ram_fuzz(elements,payloads[i], driver, this_threads_files[0])
			sleep(args.delay)
					# Update the progress bar
			progress_bar.update(1)
	#			j += 1
		# Add the processed payload into the file to keep track of processed payloads by this thread
		with open(this_threads_files[1],'a',newline='') as processed_payload_file:
			processed_payload_file.write(payloads[i]+'\n')
		i+=i
	#print(f"\n\n{YELLOW}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nINFO: Fuzzing completed for Browser Instance number: {instance_number}\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
	progress_bar.close()
	
# Function to attempt to fuzz battering ram
def attempt_battering_ram_fuzz(elements, payload, driver, this_threads_files):

    # If --forceCookie is set then set the initial cookies
    if args.forceCookie:
        add_cookies(driver)

    # Go to the target website
    driver.get(args.target)

    # Execute custom javascript code
    if args.javascript:
        driver.execute_script(args.javascript)
        
    # Handle alert if it is present
    try:
        alert = driver.switch_to.alert
        alert.accept()
    except NoAlertPresentException:
        pass
    except Exception as e:
        log_error(format_exc())
        print_exc()
        print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: An unknown error has occurred. Please open a pull request at https://github.com/netsquare/BrowserBruter/issues and paste the above message there; we are glad to help\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")

    # Clear previous requests
    del driver.requests

    # Wait for body to be loaded in case of slow response
    wait = WebDriverWait(driver, 10)
    wait.until(EC.presence_of_all_elements_located(("xpath", '//body')))

    # Get web page content before making request
    webpage_before = driver.page_source

    # Get the input field of form to be filled
    # If the user has specified the form number 
    if args.form:
        input_fields = driver.find_elements(By.XPATH, f"//form[{args.form}]//input")
        select_fields = driver.find_elements(By.XPATH, f"//form[{args.form}]//select")
        textarea_fields = driver.find_elements(By.XPATH, f"//form[{args.form}]//textarea")
        input_fields.extend(select_fields)
        input_fields.extend(textarea_fields)
    else:
        input_fields = driver.find_elements(By.XPATH, "//input")
        select_fields = driver.find_elements(By.XPATH, "//select")
        textarea_fields = driver.find_elements(By.XPATH, "//textarea")
        input_fields.extend(select_fields)
        input_fields.extend(textarea_fields)

    # Check if the form with the specified number exists or not
    if not input_fields:
        print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: Form with the specified number does not exist. Please verify the form number. Closing the BrowserBruter\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
        driver.quit()
        sys.exit(0)

    # Remove CSRF and other elements to be excluded to avoid overwriting it
    if args.avoid:
        list_of_elements_to_avoid = args.avoid.split(',')
        input_fields = [
            field for field in input_fields
            if field.get_attribute("name") not in list_of_elements_to_avoid and
            field.get_attribute("id") not in list_of_elements_to_avoid and
            field.get_attribute("type") not in ["button", "submit"]
        ]

    for field in input_fields:
        # Fill other fields with valid inputs
        # Taking field value appropriate to its type
        fieldType = field.get_attribute("type")
        if fieldType == "checkbox" or fieldType == "radio":
            driver.execute_script("arguments[0].checked = true;", field)
        # Adding support to handle <select> tags
        elif field.tag_name == "select":
            # Select the second option
            options = field.find_elements(By.TAG_NAME, "option")
            # if there are two options in <select>, choose the second one as the first option can be empty, for example, <option value="">choose country</option><option value="india">india</option>
            if len(options) == 1:
                remove_attributes(driver, options[0])
                # Set payload to the first option
                #driver.execute_script("arguments[0].setAttribute('value', arguments[1]);", options[0], payload)
                options[0].click()
            else:
                remove_attributes(driver, options[1])
                options[1].click()
        elif field.tag_name == "textarea":
            remove_attributes(driver, field)
            field.clear()  # Clear any existing value
            field.send_keys("randomTextAreaValue")
        else:
            # Removing attributes that can conflict
            remove_attributes(driver, field)
            # Filling the predefined values
            fieldValue = attribute_values.get(fieldType, "defaultValue")
            driver.execute_script("arguments[0].setAttribute('value', arguments[1]);", field, fieldValue)

    # Fill the target field being fuzzed with the current payload
    # Finding the element either by id, name, or class
    for element in elements:
        try:
            element_being_fuzzed = driver.find_element(By.ID, element)
        except NoSuchElementException:
            try:
                element_being_fuzzed = driver.find_element(By.NAME, element)
            except NoSuchElementException:
                try:
                    element_being_fuzzed = driver.find_element(By.CLASS_NAME, element)
                except NoSuchElementException as e:
                    sleep(1.2)
                    log_error(format_exc())
                    print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: Specified element {element} is not found. Please verify the name of the element. For more information, check Error.txt\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                    driver.quit()
                    sys.exit(0)

        # Check if the element that is being fuzzed is <select> or not, if it is <select>, then set payload to its first <option> tag and mark it as the selected option
        if element_being_fuzzed.tag_name == "select":
            options = element_being_fuzzed.find_elements(By.TAG_NAME, "option")
            # if there are two options in <select>, choose the second one as the first option can be empty, for example, <option value="">choose country</option><option value="india">india</option>
            if options:
                remove_attributes(driver, options[0])
                # Set payload to the first option
                driver.execute_script("arguments[0].setAttribute('value', arguments[1]);", options[0], payload)
                options[0].click()

        elif element_being_fuzzed.tag_name == "textarea":
            remove_attributes(driver, element_being_fuzzed)
            field.clear()  # Clear any existing value
            field.send_keys(payload)
        else:
            # Removing attributes that can conflict
            remove_attributes(driver, element_being_fuzzed)
            # Setting the payload
            driver.execute_script("arguments[0].setAttribute('type','text');", element_being_fuzzed)
            driver.execute_script("arguments[0].setAttribute('value',arguments[1]);", element_being_fuzzed, payload)

    # Press the button
    try:
        driver.find_element(By.ID, args.button).click()
    except NoSuchElementException:
        try:
            driver.find_element(By.NAME, args.button).click()
        except NoSuchElementException:
            try:
                driver.find_element(By.CLASS_NAME, args.button).click()
            except NoSuchElementException as e:
                sleep(1.7)
                log_error(format_exc())
                print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: Button element {args.button} is not found to press. Please verify the id or name of the button element. For more information, check Error.txt\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                driver.quit()
                sys.exit(0)

    # Getting the current time to log it into the report
    request_datetime = datetime.datetime.now()
    requestTime = request_datetime.strftime("%H-%M-%S")

    # Wait for all requests to be completed
    wait = WebDriverWait(driver, 10)
    wait.until(EC.presence_of_all_elements_located(("xpath", '//body')))

    # Get web page content after making the response
    webpage_after = driver.page_source

    # Note the response time to log it into the report
    response_datetime = datetime.datetime.now()
    responseTime = response_datetime.strftime("%H-%M-%S")

    # Calculating the difference between request - response time in milliseconds
    cycle_time = response_datetime - request_datetime
    cycle_time_in_milliseconds = int(cycle_time.total_seconds() * 1000)

    with open(this_threads_files, 'a', newline='') as report:
        # Filtering requests that are in scope
        captured_requests = driver.requests
        filtered_requests = [single_request for single_request in captured_requests if
                             (hostname in single_request.url or scope_hostnames)]
        filtered_requests = [single_request for single_request in filtered_requests if all(extension not in single_request.url for extension in forbidden_extensions)]
		
        writer = csv.writer(report)
        for request in filtered_requests:
            request_body = decode(request.body, "utf-8")
            # Check whether the output should be printed on the console or not
            if args.verbose: 
                # Print the request
                print(f'\n{GREEN}---------------------Single Request/Response Cycle-------------------')
                print(f"Fuzzing - " + element)
                print(f"Payload - " + payload)
                print(f'----------------------REQUEST---------------------{RESET}')
                print('Time - ' + requestTime + '\n', request.method, request.url)
                # Print in a new line
                print(request.headers, request_body)
                # Print the response
                print(f'{GREEN}----------------------RESPONSE--------------------{RESET}')
                if request.response:
                    print(
                        'Time - ' + responseTime + '\n',
                        request.response.status_code,
                        request.response.reason
                    )
                print(request.response.headers)
                raw = decode(request.response.body, request.response.headers.get('Content-Encoding', 'identity'))
                # Using BeautifulSoup4 to reformat the HTML content
                soup = bs(raw, features="html.parser")
                print(soup.prettify())
                print(
                    f"{GREEN}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\nTIME: {cycle_time_in_milliseconds} MiliSeconds ({cycle_time})\n\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                # After printing on the display, write it to the report
                row = [value if value else ' ' for value in
                       [requestTime, element, payload, webpage_before, request.method, request.url, request.headers,
                        request_body, responseTime, cycle_time_in_milliseconds, request.response.status_code,
                        request.response.reason, request.response.headers, soup.prettify(),
                        len(request.response.body), webpage_after]]
                writer.writerow(row)
            else:
                # Store the logs in the report file
                raw = decode(request.response.body,
                             request.response.headers.get('Content-Encoding', 'identity'))
                soup = bs(raw, features="html.parser")
                # Substituting blank or no values with N/A then writing it to the file
                row = [value if value else ' ' for value in
                       [requestTime, element, payload, webpage_before, request.method, request.url, request.headers,
                        request_body, responseTime, cycle_time_in_milliseconds, request.response.status_code,
                        request.response.reason, request.response.headers, soup.prettify(),
                        len(request.response.body), webpage_after]]
                writer.writerow(row)

    # Clear the cookies and other data
    if args.removeSession:
        driver.delete_all_cookies()


# Function to attempt a single request-response cycle with payload
def attempt_sniper_fuzz(element, payload, driver, this_threads_files):

    # If --forceCookie is set then set the initial cookies
    if args.forceCookie:
        add_cookies(driver)

    # Go to the target website
    driver.get(args.target)

    # Execute custom javascript code
    if args.javascript:
        driver.execute_script(args.javascript)
        
    # Handle alert if it is present
    try:
        alert = driver.switch_to.alert
        alert.accept()
    except NoAlertPresentException:
        pass
    except Exception as e:
        log_error(format_exc())
        print_exc()
        print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: An unknown error has occurred. Please open a pull request at https://github.com/netsquare/BrowserBruter/issues and paste the above message there; we are glad to help\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")

    # Clear previous requests
    del driver.requests

    # Wait for body to be loaded in case of slow response
    wait = WebDriverWait(driver, 10)
    wait.until(EC.presence_of_all_elements_located(("xpath", '//body')))

    # Get web page content before making request
    webpage_before = driver.page_source

    # Get the input field of form to be filled
    # If the user has specified the form number 
    if args.form:
        input_fields = driver.find_elements(By.XPATH, f"//form[{args.form}]//input")
        select_fields = driver.find_elements(By.XPATH, f"//form[{args.form}]//select")
        textarea_fields = driver.find_elements(By.XPATH, f"//form[{args.form}]//textarea")
        input_fields.extend(select_fields)
        input_fields.extend(textarea_fields)
    else:
        input_fields = driver.find_elements(By.XPATH, "//input")
        select_fields = driver.find_elements(By.XPATH, "//select")
        textarea_fields = driver.find_elements(By.XPATH, "//textarea")
        input_fields.extend(select_fields)
        input_fields.extend(textarea_fields)

    # Check if the form with the specified number exists or not
    if not input_fields:
        print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: Form with the specified number does not exist. Please verify the form number. Closing the BrowserBruter\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
        driver.quit()
        sys.exit(0)

    # Remove CSRF and other elements to be excluded to avoid overwriting it
    if args.avoid:
        list_of_elements_to_avoid = args.avoid.split(',')
        input_fields = [
            field for field in input_fields
            if field.get_attribute("name") not in list_of_elements_to_avoid and
            field.get_attribute("id") not in list_of_elements_to_avoid and
            field.get_attribute("type") not in ["button", "submit"]
        ]

    for field in input_fields:
        # Fill other fields with valid inputs
        # Taking field value appropriate to its type
        fieldType = field.get_attribute("type")
        if fieldType == "checkbox" or fieldType == "radio":
            driver.execute_script("arguments[0].checked = true;", field)
        # Adding support to handle <select> tags
        elif field.tag_name == "select":
            # Select the second option
            options = field.find_elements(By.TAG_NAME, "option")
            # if there are two options in <select>, choose the second one as the first option can be empty, for example, <option value="">choose country</option><option value="india">india</option>
            if len(options) == 1:
                remove_attributes(driver, options[0])
                # Set payload to the first option
                #driver.execute_script("arguments[0].setAttribute('value', arguments[1]);", options[0], payload)
                options[0].click()
            else:
                remove_attributes(driver,options[1])
                options[1].click()
        elif field.tag_name == "textarea":
            remove_attributes(driver, field)
            field.clear()  # Clear any existing value
            field.send_keys("randomTextAreaValue")
        else:
            # Removing attributes that can conflict
            remove_attributes(driver, field)
            # Filling the predefined values
            fieldValue = attribute_values.get(fieldType, "defaultValue")
            driver.execute_script("arguments[0].setAttribute('value', arguments[1]);", field, fieldValue)

    # Fill the target field being fuzzed with the current payload
    # Finding the element either by id, name, or class
    try:
        element_being_fuzzed = driver.find_element(By.ID, element)
    except NoSuchElementException:
        try:
            element_being_fuzzed = driver.find_element(By.NAME, element)
        except NoSuchElementException:
            try:
                element_being_fuzzed = driver.find_element(By.CLASS_NAME, element)
            except NoSuchElementException as e:
                sleep(1.2)
                log_error(format_exc())
                print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: Specified element {element} is not found. Please verify the name of the element. For more information, check Error.txt\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                driver.quit()
                sys.exit(0)

    # Check if the element that is being fuzzed is <select> or not, if it is <select>, then set payload to its first <option> tag and mark it as the selected option
    if element_being_fuzzed.tag_name == "select":
        options = element_being_fuzzed.find_elements(By.TAG_NAME, "option")
        # if there are two options in <select>, choose the second one as the first option can be empty, for example, <option value="">choose country</option><option value="india">india</option>
        if options:
            remove_attributes(driver, options[0])
            # Set payload to the first option
            driver.execute_script("arguments[0].setAttribute('value', arguments[1]);", options[0], payload)
            options[0].click()

    elif element_being_fuzzed.tag_name == "textarea":
        remove_attributes(driver, element_being_fuzzed)
        field.clear()  # Clear any existing value
        field.send_keys(payload)
    else:
        # Removing attributes that can conflict
        remove_attributes(driver, element_being_fuzzed)
        # Setting the payload
        driver.execute_script("arguments[0].setAttribute('type','text');", element_being_fuzzed)
        driver.execute_script("arguments[0].setAttribute('value',arguments[1]);", element_being_fuzzed, payload)

    # Press the button
    try:
        driver.find_element(By.ID, args.button).click()
    except NoSuchElementException:
        try:
            driver.find_element(By.NAME, args.button).click()
        except NoSuchElementException:
            try:
                driver.find_element(By.CLASS_NAME, args.button).click()
            except NoSuchElementException as e:
                sleep(1.7)
                log_error(format_exc())
                print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: Button element {args.button} is not found to press. Please verify the id or name of the button element. For more information, check Error.txt\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                driver.quit()
                sys.exit(0)

    # Getting the current time to log it into the report
    request_datetime = datetime.datetime.now()
    requestTime = request_datetime.strftime("%H-%M-%S")

    # Wait for all requests to be completed
    wait = WebDriverWait(driver, 10)
    wait.until(EC.presence_of_all_elements_located(("xpath", '//body')))

    # Get web page content after making the response
    webpage_after = driver.page_source

    # Note the response time to log it into the report
    response_datetime = datetime.datetime.now()
    responseTime = response_datetime.strftime("%H-%M-%S")

    # Calculating the difference between request - response time in milliseconds
    cycle_time = response_datetime - request_datetime
    cycle_time_in_milliseconds = int(cycle_time.total_seconds() * 1000)

    with open(this_threads_files, 'a', newline='') as report:
        # Filtering requests that are in scope
        captured_requests = driver.requests
        filtered_requests = [single_request for single_request in captured_requests if
                             (hostname in single_request.url or scope_hostnames)]
        filtered_requests = [single_request for single_request in filtered_requests if all(extension not in single_request.url for extension in forbidden_extensions)]
		
        writer = csv.writer(report)
        for request in filtered_requests:
            request_body = decode(request.body, "utf-8")
            # Check whether the output should be printed on the console or not
            if args.verbose: 
                # Print the request
                print(f'\n{GREEN}---------------------Single Request/Response Cycle-------------------')
                print(f"Fuzzing - " + element)
                print(f"Payload - " + payload)
                print(f'----------------------REQUEST---------------------{RESET}')
                print('Time - ' + requestTime + '\n', request.method, request.url)
                # Print in a new line
                print(request.headers, request_body)
                # Print the response
                print(f'{GREEN}----------------------RESPONSE--------------------{RESET}')
                if request.response:
                    print(
                        'Time - ' + responseTime + '\n',
                        request.response.status_code,
                        request.response.reason
                    )
                print(request.response.headers)
                raw = decode(request.response.body, request.response.headers.get('Content-Encoding', 'identity'))
                # Using BeautifulSoup4 to reformat the HTML content
                soup = bs(raw, features="html.parser")
                print(soup.prettify())
                print(
                    f"{GREEN}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\nTIME: {cycle_time_in_milliseconds} MiliSeconds ({cycle_time})\n\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                # After printing on the display, write it to the report
                row = [value if value else ' ' for value in
                       [requestTime, element, payload, webpage_before, request.method, request.url, request.headers,
                        request_body, responseTime, cycle_time_in_milliseconds, request.response.status_code,
                        request.response.reason, request.response.headers, soup.prettify(),
                        len(request.response.body), webpage_after]]
                writer.writerow(row)
            else:
                # Store the logs in the report file
                raw = decode(request.response.body,
                             request.response.headers.get('Content-Encoding', 'identity'))
                soup = bs(raw, features="html.parser")
                # Substituting blank or no values with N/A then writing it to the file
                row = [value if value else ' ' for value in
                       [requestTime, element, payload, webpage_before, request.method, request.url, request.headers,
                        request_body, responseTime, cycle_time_in_milliseconds, request.response.status_code,
                        request.response.reason, request.response.headers, soup.prettify(),
                        len(request.response.body), webpage_after]]
                writer.writerow(row)

    # Clear the cookies and other data
    if args.removeSession:
        driver.delete_all_cookies()

#def clusterbomb(elements_payloads,instance_number,this_threads_files,driver):
    # Determine the number of iterations based on the maximum length of test data files
#    num_iterations = max(len(payload_list) for payload_list in elements_payloads.values())
#    progress_bar = tqdm(total=num_iterations, desc=f"Fuzzing Progress for Browser -> {instance_number}", unit="iteration", dynamic_ncols=True, colour='blue')
#    processed_payloads = []
#    for i in range(num_iterations):
        #fuzz_values = {}
        #for element, payload_list in elements_payloads.items():
            #index = i % len(payload_list)
            #fuzz_values[element] = payload_list[index]

            # Fuzz until terminate flag is not set, if it is set then exit and close the browser
#        if not terminate:
            # If the iterative option is set, stop and wait for the user to continue
#            if args.iterative:
#                pause_event.set()
            # If the pause event is set, then pause the fuzzing process
#            if pause_event.is_set():
#                while pause_event.is_set():
#                    sleep(1)

#            attempt_pitchfork_fuzz(elements_payloads, i, driver, this_threads_files[0])
#            sleep(args.delay)
#            for element, payload_list in elements_payloads.items():
#                processed_payloads.append(payload_list[i])
            # Update the progress bar
#            progress_bar.update(1)

        # Add the processed payload into the file to keep track of processed payloads by this thread
#        with open(this_threads_files[1], 'w', newline='') as processed_payload_file:
#            processed_payload_file.write(str(processed_payloads) + '\n')

    # Close the progress bar
#    progress_bar.close()

def worker(payloads_combinations, driver, this_threads_files, instance_number, start_index, end_index):
    try:
        for i in range(start_index, end_index):
            payloads = payloads_combinations[i]

            # Your existing code for attempt_clusterbomb_fuzz
            attempt_clusterbomb_fuzz(payloads, driver, this_threads_files, instance_number)
    except Exception as e:
        print(f"Error in worker: {e}")
  
def attempt_clusterbomb_fuzz(payloads_combinations, driver,this_threads_files, instance_number):#index, driver, this_threads_files):
    try:
        # Create a list to store all combinations of payloads
        #payloads_combinations = elements_payloads
        #elements = elements_payloads.keys()
        progress_bar = tqdm(total=len(payloads_combinations), desc=f"Fuzzing Progress for Browser -> {instance_number}", unit="iteration",dynamic_ncols=True,colour='blue')

        # Iterate through each combination
        for payloads in payloads_combinations:
            if not terminate:
                # If the iterative option is set, stop and wait for the user to continue
                if args.iterative:
                    pause_event.set()
                # If the pause event is set, then pause the fuzzing process
                if pause_event.is_set():
                    while pause_event.is_set():
                        sleep(1)
            #print(payloads)

                # If --forceCookie is set then set the initial cookies
                if args.forceCookie:
                    add_cookies(driver)

                # Go to the target website
                driver.get(args.target)

                # Execute custom javascript code
                if args.javascript:
                    driver.execute_script(args.javascript)

                # Handle alert if it is present
                try:
                    alert = driver.switch_to.alert
                    alert.accept()
                except NoAlertPresentException:
                    pass
                except Exception as e:
                    log_error(format_exc())
                    print_exc()
                    print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: An unknown error has occurred. Please open a pull request at https://github.com/netsquare/BrowserBruter/issues and paste the above message there; we are glad to help\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")

                # Clear previous requests
                del driver.requests

                # Wait for body to be loaded in case of slow response
                wait = WebDriverWait(driver, 10)
                wait.until(EC.presence_of_all_elements_located(("xpath", '//body')))

                # Get web page content before making request
                webpage_before = driver.page_source

                # Get the input field of the form to be filled
                # If the user has specified the form number
                if args.form:
                    input_fields = driver.find_elements(By.XPATH, f"//form[{args.form}]//input")
                    select_fields = driver.find_elements(By.XPATH, f"//form[{args.form}]//select")
                    textarea_fields = driver.find_elements(By.XPATH, f"//form[{args.form}]//textarea")
                    input_fields.extend(select_fields)
                    input_fields.extend(textarea_fields)
                else:
                    input_fields = driver.find_elements(By.XPATH, "//input")
                    select_fields = driver.find_elements(By.XPATH, "//select")
                    textarea_fields = driver.find_elements(By.XPATH, "//textarea")
                    input_fields.extend(select_fields)
                    input_fields.extend(textarea_fields)

                # Check if the form with the specified number exists or not
                if not input_fields:
                    print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nERROR: Form with the specified number does not exist. Please verify the form number. Closing the BrowserBruter\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                    driver.quit()
                    sys.exit(0)

                # Remove CSRF and other elements to be excluded to avoid overwriting it
                if args.avoid:
                    list_of_elements_to_avoid = args.avoid.split(',')
                    input_fields = [
                        field for field in input_fields
                        if field.get_attribute("name") not in list_of_elements_to_avoid and
                        field.get_attribute("id") not in list_of_elements_to_avoid and
                        field.get_attribute("type") not in ["button", "submit"]
                    ]

                for field in input_fields:
                    # Fill other fields with valid inputs
                    # Taking field value appropriate to its type
                    fieldType = field.get_attribute("type")
                    if fieldType == "checkbox" or fieldType == "radio":
                        driver.execute_script("arguments[0].checked = true;", field)
                    # Adding support to handle <select> tags
                    elif field.tag_name == "select":
                        # Select the second option
                        options = field.find_elements(By.TAG_NAME, "option")
                        # if there are two options in <select>, choose the second one as the first option can be empty, for example, <option value="">choose country</option><option value="india">india</option>
                        if len(options) == 1:
                            remove_attributes(driver, options[0])
                            # Set payload to the first option
                            options[0].click()
                        else:
                            remove_attributes(driver, options[1])
                            options[1].click()

                    elif field.tag_name == "textarea":
                        remove_attributes(driver, field)
                        field.clear()  # Clear any existing value
                        field.send_keys("randomTextAreaValue")
                    else:
                        # Removing attributes that can conflict
                        remove_attributes(driver, field)
                        # Filling the predefined values
                        fieldValue = attribute_values.get(fieldType, "defaultValue")
                        driver.execute_script("arguments[0].setAttribute('value', arguments[1]);", field, fieldValue)

                # Iterate through each element and set its value
                for element, payload in zip(elements_payloads.keys(), payloads):
                    # Find the element to be fuzzed
                    try:
                        element_being_fuzzed = driver.find_element(By.ID, element)
                    except NoSuchElementException:
                        try:
                            element_being_fuzzed = driver.find_element(By.NAME, element)
                        except NoSuchElementException:
                            try:
                                element_being_fuzzed = driver.find_element(By.CLASS_NAME, element)
                            except NoSuchElementException as e:
                                sleep(1.2)
                                log_error(format_exc())
                                print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: Specified element {element} is not found. Please verify the name of the element. For more information, check Error.txt\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                                driver.quit()
                                sys.exit(0)

                    # Check if the element that is being fuzzed is <select> or not, if it is <select>, then set payload to its first <option> tag and mark it as the selected option
                    if element_being_fuzzed.tag_name == "select":
                        options = element_being_fuzzed.find_elements(By.TAG_NAME, "option")
                        # if there are two options in <select>, choose the second one as the first option can be empty, for example, <option value="">choose country</option><option value="india">india</option>
                        if options:
                            remove_attributes(driver, options[0])
                            # Set payload to the first option
                            driver.execute_script("arguments[0].setAttribute('value', arguments[1]);", options[0], payload)
                            options[0].click()

                    elif element_being_fuzzed.tag_name == "textarea":
                        remove_attributes(driver, element_being_fuzzed)
                        field.clear()  # Clear any existing value
                        field.send_keys(payload)
                        #print(payload)
                    else:
                        # Removing attributes that can conflict
                        remove_attributes(driver, element_being_fuzzed)

                        # Setting the payload
                        driver.execute_script("arguments[0].setAttribute('type','text');", element_being_fuzzed)
                        driver.execute_script("arguments[0].setAttribute('value',arguments[1]);", element_being_fuzzed, payload)
                        #print(payload)

                # Press the button
                try:
                    driver.find_element(By.ID, args.button).click()
                except NoSuchElementException:
                    try:
                        driver.find_element(By.NAME, args.button).click()
                    except NoSuchElementException:
                        try:
                            driver.find_element(By.CLASS_NAME, args.button).click()
                        except NoSuchElementException as e:
                            sleep(1.7)
                            log_error(format_exc())
                            print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: Button element {args.button} is not found to press. Please verify the id or name of the button element. For more information, check Error.txt\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                            driver.quit()
                            sys.exit(0)
                # update progress bar
                progress_bar.update(1)

                # Getting the current time to log it into the report
                request_datetime = datetime.datetime.now()
                requestTime = request_datetime.strftime("%H-%M-%S")

                # Wait for all requests to be completed
                wait = WebDriverWait(driver, 10)
                wait.until(EC.presence_of_all_elements_located(("xpath", '//body')))

                # Get web page content after making the response
                webpage_after = driver.page_source

                # Note the response time to log it into the report
                response_datetime = datetime.datetime.now()
                responseTime = response_datetime.strftime("%H-%M-%S")

                # Calculating the difference between request - response time in milliseconds
                cycle_time = response_datetime - request_datetime
                cycle_time_in_milliseconds = int(cycle_time.total_seconds() * 1000)

                with open(this_threads_files[0], 'a', newline='') as report:
                    # Filtering requests that are in scope
                    captured_requests = driver.requests
                    filtered_requests = [single_request for single_request in captured_requests if
                                        (hostname in single_request.url or scope_hostnames)]
                    filtered_requests = [single_request for single_request in filtered_requests if all(
                        extension not in single_request.url for extension in forbidden_extensions)]

                    writer = csv.writer(report)
                    for request in filtered_requests:
                        request_body = decode(request.body, "utf-8")
                        # Check whether the output should be printed on the console or not
                        if args.verbose:
                            # Print the request
                            print(
                                f'\n{GREEN}---------------------Single Request/Response Cycle-------------------')
                            print(f"Fuzzing - " + str(list(elements_payloads.keys())))
                            print(f"Payload - " + str(payloads))
                            print(f'----------------------REQUEST---------------------{RESET}')
                            print('Time - ' + requestTime + '\n', request.method, request.url)
                            # Print in a new line
                            print(request.headers, request_body)
                            # Print the response
                            print(f'{GREEN}----------------------RESPONSE--------------------{RESET}')
                            if request.response:
                                print(
                                    'Time - ' + responseTime + '\n',
                                    request.response.status_code,
                                    request.response.reason
                                )
                            print(request.response.headers)
                            raw = decode(request.response.body, request.response.headers.get('Content-Encoding', 'identity'))
                            # Using BeautifulSoup4 to reformat the HTML content
                            soup = bs(raw, features="html.parser")
                            print(soup.prettify())
                            print(
                                f"{GREEN}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\nTIME: {cycle_time_in_milliseconds} MiliSeconds ({cycle_time})\n\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}")
                            # After printing on the display, write it to the report
                            row = [value if value else ' ' for value in
                                   [requestTime, str(list(elements_payloads.keys())), str(payloads), webpage_before,
                                    request.method, request.url, request.headers,
                                    request_body, responseTime, cycle_time_in_milliseconds, request.response.status_code,
                                    request.response.reason, request.response.headers, soup.prettify(),
                                    len(request.response.body), webpage_after]]
                            writer.writerow(row)
                        else:
                            # Store the logs in the report file
                            raw = decode(request.response.body,
                                         request.response.headers.get('Content-Encoding', 'identity'))
                            soup = bs(raw, features="html.parser")
                            # Substituting blank or no values with N/A then writing it to the file
                            row = [value if value else ' ' for value in
                                   [requestTime, str(list(elements_payloads.keys())), str(payloads), webpage_before,
                                    request.method, request.url, request.headers,
                                    request_body, responseTime, cycle_time_in_milliseconds, request.response.status_code,
                                    request.response.reason, request.response.headers, soup.prettify(),
                                    len(request.response.body), webpage_after]]
                            writer.writerow(row)
                # Add the processed payload into the file to keep track of processed payloads by this thread
                with open(this_threads_files[1], 'w', newline='') as processed_payload_file:
                    processed_payload_file.write(str(payloads) + '\n')


                # Clear the cookies and other data
                if args.removeSession:
                    driver.delete_all_cookies()
        # Close the progress bar
        progress_bar.close()
    except KeyboardInterrupt:
        signal_handler(signal.SIGINT, None)
        sleep(3)
        log_error(format_exc())
        print(f"{RED}CTRL+C{RESET}", flush=True)
        sys.exit(0)
### VARIOUS FUNCTIONS ENDS ###
        
def slice_combinations_for_threads(payload_combinations, num_threads):
    # Calculate the number of combinations per thread
    combinations_per_thread = len(payload_combinations) // num_threads
    extra_combinations = len(payload_combinations) % num_threads

    # Initialize a list to store sliced combinations
    sliced_combinations = []

    # Iterate over the threads
    start = 0
    for i in range(num_threads):
        end = start + combinations_per_thread
        # Distribute extra combinations to the first few threads
        if i < extra_combinations:
            end += 1

        # Slice the combinations for the current thread
        sliced_combinations.append(payload_combinations[start:end])

        # Update the starting index for the next thread
        start = end

    return sliced_combinations


### MAIN EXECUTION BLOCK STARTS ###
# Checking if script is running directly
if __name__ == "__main__":
    try:
        # Redirect stdout to the Tee class, the tee class redirects STDOUT to STDOUT and the log file
        log_file = 'logs/BrowserBruterSTDOUT.txt'  # This file stores console output
        tee_instance = Tee(log_file)
        sys.stdout = tee_instance

       
        # Get the number of threads or in other words number of browsers instances to use
        num_threads = args.threads

        # Dividing payloads among threads and running the threads
        # Check the number of threads specified in the command line arguments
        #num_threads = min(args.threads, 5)

        # Create and start the keyboard listener thread which will pause and resume the BrowserBruter
        # queue = Queue()
        keyboard_thread = threading.Thread(target=pause_resume,daemon=True)  # Process(target=pause_resume, args=(queue,))
        keyboard_thread.start()

         # Create and start the threads
        threads = []
        start = 0
        if args.attack in (1,2):

            # Divide the payload data equally among the threads
            payloads_per_thread = len(payloads) // num_threads
            extra_payloads = len(payloads) % num_threads

            for i in range(num_threads):
                try:
                    end = start + payloads_per_thread
                    # Distribute extra payloads to the first few threads
                    if i < extra_payloads:
                        end += 1
                    # Extract the payloads for the current thread
                    thread_payloads = payloads[start:end]
                    # Create a thread with the target function and arguments
                    thread = threading.Thread(target=run_browser_instance, args=(thread_payloads,i)) #elements, i))
                    # Start the thread
                    thread.start()
                    # Add the thread to the list of threads
                    threads.append(thread)
                    # Update the starting index for the next thread
                    start = end
                    # Sleep for 7 seconds for proper resource management
                    sleep(7)
                except KeyboardInterrupt as e:
                    signal_handler(signal.SIGINT, None)
                    sleep(3)
                    log_error(format_exc())
                    print(f"{RED}CTRL+C{RESET}", flush=True)
                    sys.exit(0)
        elif args.attack == 3:
            threaded_dicts = slice_dict_for_threads(elements_payloads, num_threads)
            # Get the elements and payloads
            # elements_payloads = process_input(args.elements_payloads)
            # payloads_per_thread = len(next(iter(elements_payloads.values()))) // num_threads
            # extra_payloads = len(next(iter(elements_payloads.values()))) % num_threads

            # Iterat   e through the elements in the dictionary
            #for element, values in elements_payloads.items():
            #    # Calculate payloads per thread for the current element
            #    payloads_per_thread = len(values) // num_threads
            #    extra_payloads = len(values) % num_threads

            #for i in range(num_threads):
            try:
                    # Calculate start and end indices for the current thread
                    
             

                for i, thread_dict in enumerate(threaded_dicts, start=1):
                    #print(f"Output for Thread {i}")
                    #print(f"element {thread_dict}\n")
                        # Create a thread with the target function and arguments
                    thread = threading.Thread(target=run_browser_instance, args=(thread_dict, i))
                    thread.start()
                    threads.append(thread)
                    # Sleep for 7 seconds for proper resource management
                    sleep(7)
            except KeyboardInterrupt as e:
                signal_handler(signal.SIGINT, None)
                sleep(3)
                log_error(format_exc())
                print(f"{RED}CTRL+C{RESET}", flush=True)
                sys.exit(0)

        elif args.attack == 4:
            # Create a list to store all combinations of payloads
            payloads_combinations = list(product(*elements_payloads.values()))

            # Split the workload equally among threads
            chunk_size = len(payloads_combinations) // num_threads
            chunks = [(i * chunk_size, (i + 1) * chunk_size) for i in range(num_threads - 1)]
            chunks.append(((num_threads - 1) * chunk_size, len(payloads_combinations)))

            # Create and start the threads
            #threads = []
            for instance_number, (start, end) in enumerate(chunks):
                #print(f"Output for Thread {instance_number + 1}")
                thread_combinations = payloads_combinations[start:end]
                # Create a thread with the target function and arguments
                thread = threading.Thread(target=run_browser_instance, args=(thread_combinations, instance_number))
                # Start the thread
                thread.start()
                threads.append(thread)
                # Sleep for 7 seconds for proper resource management
                sleep(7)
            
            # Create a list to store all combinations of payloads
            #payloads_combinations = list(product(*elements_payloads.values()))

            # Get the sliced combinations
            #threaded_combinations = slice_combinations_for_threads(payloads_combinations, num_threads)

            # Create and start the threads
            #threads = []
            #for instance_number, combinations in enumerate(threaded_combinations):
            #    print(f"Output for Thread {instance_number + 1}")
            #    print(f"Combinations: {combinations}\n")
        
                # Create a thread with the target function and arguments
            #    thread = threading.Thread(target=run_browser_instance, args=(combinations, instance_number))
            #    thread.start()
            #    threads.append(thread)
                # Sleep for 7 seconds for proper resource management
            #    sleep(7)
            # Step 3: Generate combinations and test each combination
            #for combination in product(*elements_payloads.values()):
                # Iterate over each element in the dictionary and its corresponding combination value
            #    for element, value in zip(elements_payloads.keys(), combination):
                    # set_element_value(element, value)
            #        print(element, value)
            #threads = []
            #for i, combination in enumerate(product(*elements_payloads.values()), start=1):
                #print(f"Output for Thread {i}")
                #print(combination)
                #print(len(elements_payloads))
                #thread = threading.Thread(target=run_browser_instance, args=(i, combination, len(elements_payloads)))
                #thread.start()
                #threads.append(thread)
                #sleep(7)
            # Create a list to store all combinations of payloads
            #payloads_combinations = list(product(*elements_payloads.values()))

            ## Split the workload equally among threads
            #chunk_size = len(payloads_combinations) // num_threads
            #chunks = [(i * chunk_size, (i + 1) * chunk_size) for i in range(num_threads - 1)]
            #chunks.append(((num_threads - 1) * chunk_size, len(payloads_combinations)))
             # Use ThreadPoolExecutor to parallelize the execution
            #with ThreadPoolExecutor(max_workers=num_threads) as executor:
            #     futures = [executor.submit(worker, payloads_combinations start, end) for start, end in chunks]
                 # Wait for all threads to complete
             #    concurrent.futures.wait(futures)
            #for instance_number, (start, end) in enumerate(chunks):
                #thread = threading.Thread(target=run_browser_instance, args=(payloads_combinations[start:end], instance_number))
                #threads.append(thread)
                #thread.start()

            #run_browser_instance(elements_payloads,0)

        
        # Wait for all threads to finish
        for thread in threads:
            try:
                thread.join()
            except KeyboardInterrupt:
                log_error(format_exc())
        # Set the terminate flag so the keyboard thread stops
        terminate = True

    except KeyboardInterrupt:
        signal_handler(signal.SIGINT, None)
        sleep(3)
        log_error(format_exc())
        print(f"{RED}CTRL+C{RESET}", flush=True)
        sys.exit(0)
    except SystemExit:
        sleep(1.4)
        log_error(format_exc())
        print("", flush=True)
    except:
        sleep(3.3)
        log_error(format_exc())
        # Print Traceback
        print_exc()
        # Ask the user to send this to GitHub
        print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: An unknown error has occurred, Please open a pull request at https://github.com/netsquare/BrowserBruter/issues and paste the above message there, we are glad to help\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)
    finally:
        # Generate the report
        generate_final_report()
        sys.stdout = sys.__stdout__
        sys.exit(0)
        # Wait for the thread responsible for pause-resume mechanism to stop
        # keyboard_thread.terminate()
        #keyboard_thread.join()
        # Reset sys.stdout to the console at the end of your script
        
else:
    print(f"\n\n{RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nError: Please run the script again using python3 BrowserBruter.py, closing the BrowserBruter\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++{RESET}", flush=True)

### MAIN EXECUTION BLOCK ENDS ###