# -*- coding: utf-8 -*-
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#
#  Author: Mauro Soria

from optparse import OptionParser, OptionGroup

from lib.core.settings import (
    AUTHENTICATION_TYPES,
    SCRIPT_PATH,
    VERSION,
)
from lib.utils.file import FileUtils


def parse_arguments():
    usage = "Usage: %prog [-u|--url] target [-e|--extensions] extensions [options]"
    parser = OptionParser(usage, version=f"dirsearch v{VERSION}")

    # Mandatory arguments
    mandatory = OptionGroup(parser, "Mandatory")
    mandatory.add_option(
        "-u",
        "--url",
        action="append",
        dest="urls",
        metavar="URL",
        help="Target URL(s), can use multiple flags",
    )
    mandatory.add_option(
        "-l",
        "--url-file",
        action="store",
        dest="url_file",
        metavar="PATH",
        help="URL list file",
    )
    mandatory.add_option(
        "--stdin", action="store_true", dest="stdin_urls", help="Read URL(s) from STDIN"
    )
    mandatory.add_option("--cidr", action="store", dest="cidr", help="Target CIDR")
    mandatory.add_option(
        "--raw",
        action="store",
        dest="raw_file",
        metavar="PATH",
        help="Load raw HTTP request from file (use `--scheme` flag to set the scheme)",
    )
    mandatory.add_option(
        "-s", "--session", action="store", dest="session_file", help="Session file"
    )
    mandatory.add_option(
        "--config",
        action="store",
        dest="config",
        metavar="PATH",
        help="Full path to config file, see 'config.ini' for example (Default: config.ini)",
        default=FileUtils.build_path(SCRIPT_PATH, "config.ini"),
    )

    # Dictionary Settings
    dictionary = OptionGroup(parser, "Dictionary Settings")
    dictionary.add_option(
        "-w",
        "--wordlists",
        action="store",
        dest="wordlists",
        help="Customize wordlists (separated by commas)",
    )
    dictionary.add_option(
        "-e",
        "--extensions",
        action="store",
        dest="extensions",
        help="Extension list separated by commas (e.g. php,asp)",
    )
    dictionary.add_option(
        "-f",
        "--force-extensions",
        action="store_true",
        dest="force_extensions",
        help="Add extensions to the end of every wordlist entry. By default dirsearch only replaces the %EXT% keyword with extensions",
    )
    dictionary.add_option(
        "-O",
        "--overwrite-extensions",
        action="store_true",
        dest="overwrite_extensions",
        help="Overwrite other extensions in the wordlist with your extensions (selected via `-e`)",
    )
    dictionary.add_option(
        "--exclude-extensions",
        action="store",
        dest="exclude_extensions",
        metavar="EXTENSIONS",
        help="Exclude extension list separated by commas (e.g. asp,jsp)",
    )
    dictionary.add_option(
        "--remove-extensions",
        action="store_true",
        dest="remove_extensions",
        help="Remove extensions in all paths (e.g. admin.php -> admin)",
    )
    dictionary.add_option(
        "--prefixes",
        action="store",
        dest="prefixes",
        help="Add custom prefixes to all wordlist entries (separated by commas)",
    )
    dictionary.add_option(
        "--suffixes",
        action="store",
        dest="suffixes",
        help="Add custom suffixes to all wordlist entries, ignore directories (separated by commas)",
    )
    dictionary.add_option(
        "-U",
        "--uppercase",
        action="store_true",
        dest="uppercase",
        help="Uppercase wordlist",
    )
    dictionary.add_option(
        "-L",
        "--lowercase",
        action="store_true",
        dest="lowercase",
        help="Lowercase wordlist",
    )
    dictionary.add_option(
        "-C",
        "--capital",
        action="store_true",
        dest="capitalization",
        help="Capital wordlist",
    )

    # Optional Settings
    general = OptionGroup(parser, "General Settings")
    general.add_option(
        "-t",
        "--threads",
        action="store",
        type="int",
        dest="thread_count",
        metavar="THREADS",
        help="Number of threads",
    )
    general.add_option(
        "-r",
        "--recursive",
        action="store_true",
        dest="recursive",
        help="Brute-force recursively",
    )
    general.add_option(
        "--deep-recursive",
        action="store_true",
        dest="deep_recursive",
        help="Perform recursive scan on every directory depth (e.g. api/users -> api/)",
    )
    general.add_option(
        "--force-recursive",
        action="store_true",
        dest="force_recursive",
        help="Do recursive brute-force for every found path, not only directories",
    )
    general.add_option(
        "-R",
        "--max-recursion-depth",
        action="store",
        type="int",
        dest="recursion_depth",
        metavar="DEPTH",
        help="Maximum recursion depth",
    )
    general.add_option(
        "--recursion-status",
        action="store",
        dest="recursion_status_codes",
        metavar="CODES",
        help="Valid status codes to perform recursive scan, support ranges (separated by commas)",
    )
    general.add_option(
        "--subdirs",
        action="store",
        dest="subdirs",
        metavar="SUBDIRS",
        help="Scan sub-directories of the given URL[s] (separated by commas)",
    )
    general.add_option(
        "--exclude-subdirs",
        action="store",
        dest="exclude_subdirs",
        metavar="SUBDIRS",
        help="Exclude the following subdirectories during recursive scan (separated by commas)",
    )
    general.add_option(
        "-i",
        "--include-status",
        action="store",
        dest="include_status_codes",
        metavar="CODES",
        help="Include status codes, separated by commas, support ranges (e.g. 200,300-399)",
    )
    general.add_option(
        "-x",
        "--exclude-status",
        action="store",
        dest="exclude_status_codes",
        metavar="CODES",
        help="Exclude status codes, separated by commas, support ranges (e.g. 301,500-599)",
    )
    general.add_option(
        "--exclude-sizes",
        action="store",
        dest="exclude_sizes",
        metavar="SIZES",
        help="Exclude responses by sizes, separated by commas (e.g. 0B,4KB)",
    )
    general.add_option(
        "--exclude-text",
        action="append",
        dest="exclude_texts",
        metavar="TEXTS",
        help="Exclude responses by text, can use multiple flags",
    )
    general.add_option(
        "--exclude-regex",
        action="store",
        dest="exclude_regex",
        metavar="REGEX",
        help="Exclude responses by regular expression",
    )
    general.add_option(
        "--exclude-redirect",
        action="store",
        dest="exclude_redirect",
        metavar="STRING",
        help="Exclude responses if this regex (or text) matches redirect URL (e.g. '/index.html')",
    )
    general.add_option(
        "--exclude-response",
        action="store",
        dest="exclude_response",
        metavar="PATH",
        help="Exclude responses similar to response of this page, path as input (e.g. 404.html)",
    )
    general.add_option(
        "--skip-on-status",
        action="store",
        dest="skip_on_status",
        metavar="CODES",
        help="Skip target whenever hit one of these status codes, separated by commas, support ranges",
    )
    general.add_option(
        "--min-response-size",
        action="store",
        type="int",
        dest="minimum_response_size",
        help="Minimum response length",
        metavar="LENGTH",
        default=0,
    )
    general.add_option(
        "--max-response-size",
        action="store",
        type="int",
        dest="maximum_response_size",
        help="Maximum response length",
        metavar="LENGTH",
        default=0,
    )
    general.add_option(
        "--max-time",
        action="store",
        type="int",
        dest="max_time",
        metavar="SECONDS",
        help="Maximum runtime for the scan",
    )
    general.add_option(
        "--exit-on-error",
        action="store_true",
        dest="exit_on_error",
        help="Exit whenever an error occurs",
    )

    # Request Settings
    request = OptionGroup(parser, "Request Settings")
    request.add_option(
        "-m",
        "--http-method",
        action="store",
        dest="http_method",
        metavar="METHOD",
        help="HTTP method (default: GET)",
    )
    request.add_option(
        "-d", "--data", action="store", dest="data", help="HTTP request data"
    )
    request.add_option(
        "--data-file",
        action="store",
        dest="data_file",
        metavar="PATH",
        help="File contains HTTP request data"
    )
    request.add_option(
        "-H",
        "--header",
        action="append",
        dest="headers",
        help="HTTP request header, can use multiple flags",
    )
    request.add_option(
        "--header-file",
        dest="header_file",
        metavar="PATH",
        help="File contains HTTP request headers",
    )
    request.add_option(
        "-F",
        "--follow-redirects",
        action="store_true",
        dest="follow_redirects",
        help="Follow HTTP redirects",
    )
    request.add_option(
        "--random-agent",
        action="store_true",
        dest="random_agents",
        help="Choose a random User-Agent for each request",
    )
    request.add_option(
        "--auth",
        action="store",
        dest="auth",
        metavar="CREDENTIAL",
        help="Authentication credential (e.g. user:password or bearer token)",
    )
    request.add_option(
        "--auth-type",
        action="store",
        dest="auth_type",
        metavar="TYPE",
        help=f"Authentication type ({', '.join(AUTHENTICATION_TYPES)})",
    )
    request.add_option(
        "--cert-file",
        action="store",
        dest="cert_file",
        metavar="PATH",
        help="File contains client-side certificate",
    )
    request.add_option(
        "--key-file",
        action="store",
        dest="key_file",
        metavar="PATH",
        help="File contains client-side certificate private key (unencrypted)",
    )
    request.add_option("--user-agent", action="store", dest="user_agent")
    request.add_option("--cookie", action="store", dest="cookie")

    # Connection Settings
    connection = OptionGroup(parser, "Connection Settings")
    connection.add_option(
        "--timeout",
        action="store",
        type="float",
        dest="timeout",
        help="Connection timeout",
    )
    connection.add_option(
        "--delay",
        action="store",
        type="float",
        dest="delay",
        help="Delay between requests",
    )
    connection.add_option(
        "--proxy",
        action="append",
        dest="proxies",
        metavar="PROXY",
        help="Proxy URL (HTTP/SOCKS), can use multiple flags",
    )
    connection.add_option(
        "--proxy-file",
        action="store",
        dest="proxy_file",
        metavar="PATH",
        help="File contains proxy servers",
    )
    connection.add_option(
        "--proxy-auth",
        action="store",
        dest="proxy_auth",
        metavar="CREDENTIAL",
        help="Proxy authentication credential",
    )
    connection.add_option(
        "--replay-proxy",
        action="store",
        dest="replay_proxy",
        metavar="PROXY",
        help="Proxy to replay with found paths",
    )
    connection.add_option(
        "--tor", action="store_true", dest="tor", help="Use Tor network as proxy"
    )
    connection.add_option(
        "--scheme",
        action="store",
        dest="scheme",
        metavar="SCHEME",
        help="Scheme for raw request or if there is no scheme in the URL (Default: auto-detect)",
    )
    connection.add_option(
        "--max-rate",
        action="store",
        type="int",
        dest="max_rate",
        metavar="RATE",
        help="Max requests per second",
    )
    connection.add_option(
        "--retries",
        action="store",
        type="int",
        dest="max_retries",
        metavar="RETRIES",
        help="Number of retries for failed requests",
    )
    connection.add_option("--ip", action="store", dest="ip", help="Server IP address")

    # Advanced Settings
    advanced = OptionGroup(parser, "Advanced Settings")
    advanced.add_option(
        "--crawl",
        action="store_true",
        dest="crawl",
        help="Crawl for new paths in responses"
    )

    # View Settings
    view = OptionGroup(parser, "View Settings")
    view.add_option(
        "--full-url",
        action="store_true",
        dest="full_url",
        help="Full URLs in the output (enabled automatically in quiet mode)",
    )
    view.add_option(
        "--redirects-history",
        action="store_true",
        dest="redirects_history",
        help="Show redirects history",
    )
    view.add_option(
        "--no-color", action="store_false", dest="color", help="No colored output"
    )
    view.add_option(
        "-q", "--quiet-mode", action="store_true", dest="quiet", help="Quiet mode"
    )

    # Output Settings
    output = OptionGroup(parser, "Output Settings")
    output.add_option(
        "-o",
        "--output",
        action="store",
        dest="output_file",
        metavar="PATH",
        help="Output file",
    )
    output.add_option(
        "--format",
        action="store",
        dest="output_format",
        metavar="FORMAT",
        help="Report format (Available: simple, plain, json, xml, md, csv, html, sqlite)",
    )
    output.add_option(
        "--log", action="store", dest="log_file", metavar="PATH", help="Log file"
    )

    parser.add_option_group(mandatory)
    parser.add_option_group(dictionary)
    parser.add_option_group(general)
    parser.add_option_group(request)
    parser.add_option_group(connection)
    parser.add_option_group(advanced)
    parser.add_option_group(view)
    parser.add_option_group(output)
    options, _ = parser.parse_args()

    return options
