Couple of bug fixes
Some checks failed
/ build (macos-latest, 3.8) (push) Has been cancelled
/ build (ubuntu-latest, pypy-2.7) (push) Has been cancelled
/ build (windows-latest, 3.14) (push) Has been cancelled

This commit is contained in:
Miroslav Štampar 2026-06-18 13:04:59 +02:00
parent 1404133538
commit 002a28f7f0
8 changed files with 250 additions and 14 deletions

View file

@ -84,7 +84,7 @@ b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/paylo
0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml
997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml
40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml
9d7dcbc6c5e368c44db851865ff49c791c3dee1ee62d8c02af8f8b15f4551aed data/xml/queries.xml
38882b6ceb8bca59ce8ed927abe3b8840394c56b3881371c2103e229b8795040 data/xml/queries.xml
e043101194219a2e4c8bc352f0d3a04b87e1c28b1bcd6c13f6d5d1c9e260b653 doc/ARCHITECTURE.md
0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS
ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md
@ -162,7 +162,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py
63657c00a046ca0fb28fd069407ab6305bd7b95c42f26a96ed083fd05b152252 extra/vulnserver/vulnserver.py
3abecaec1a9c59645a4821463a2d761235f7a4f763a491f188a41a083bbddd98 lib/controller/action.py
6574ed70c7fe0ac305dbc85ed7102f648b6a3f42fe2fe6b89172d69717327149 lib/controller/checks.py
72707b5bdfc757c4e5271e156178919292b991a6e7337d3dcdeffea9df6db3ea lib/controller/checks.py
dcd4adcd7a2447a624ca7927541941d25767a4581af2d762c3197dc93790f4df lib/controller/controller.py
d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py
@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch
48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py
0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py
888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py
f01361d999b0cf89b8418265c4a4962924fcc03a6b87e15b39c0836788725e85 lib/core/settings.py
997888bab1d98fb9bc2550f3ab99df966d37f38719a41a8fb767e2cd79db6c4f lib/core/settings.py
cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py
bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py
70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py
@ -246,6 +246,7 @@ aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api.
442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py
da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py
a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps.py
0fd055877e8b21d17c11447dac7f91ef1766e0b04d470c494a6d98f5249e3186 lib/utils/dialect.py
51cfab194cd5b6b24d62706fb79db86c852b9e593f4c55c15b35f175e70c9d75 lib/utils/getch.py
853c3595e1d2efc54b8bfb6ab12c55d1efc1603be266978e3a7d96d553d91a52 lib/utils/gui.py
972c5db9c9e30ac0f91c0f8d4df4531d0304e151dac99f1399c37c952ba9f935 lib/utils/har.py
@ -394,7 +395,7 @@ ba04af3683b9a6e29e8fa6b3bf436a57e59435cebb042414f2df82018d91599e plugins/dbms/m
78f1ff4b82fd4af50e1fbdb81539862f1c31258cda212b39f4a8501960f1b95e plugins/dbms/monetdb/syntax.py
236fd244f0bbc3976b389429a8176feda6c243267564c2a0eff6fc2458c1b3f9 plugins/dbms/monetdb/takeover.py
6bdc774463ac87b1bd1b6a9d5c2346b7edbf40d9848b7870a30d1eaedde4fc51 plugins/dbms/mssqlserver/connector.py
52c19e9067f22f5c386206943d1807af4c661500bf260930a5986e9a180e96c7 plugins/dbms/mssqlserver/enumeration.py
69ba678efde8335efb8a167b63143b4fb65ea19802bc3ade30c87cb979c198e4 plugins/dbms/mssqlserver/enumeration.py
67cd70b64aed27af467682ceae8e20992b6765d2374d5762efb5a4585b8a6f79 plugins/dbms/mssqlserver/filesystem.py
38ade085f9f1b227eda8c89f78e3ce869e8f430c98bef0cc7cbd2c7dcd60c24e plugins/dbms/mssqlserver/fingerprint.py
1ecde09e80d7b709a710281f4983a6831bc02ca3458ae0b97b28446d6db241b4 plugins/dbms/mssqlserver/__init__.py
@ -479,7 +480,7 @@ e2e20e4707abe9ed8b6208837332d2daa4eaca282f847412063f2484dcca8fbd plugins/dbms/v
2b2dad6ba1d344215cad11b629546eb9f259d7c996c202edf3de5ab22418787e plugins/dbms/virtuoso/takeover.py
51c44048e4b335b306f8ed1323fd78ad6935a8c0d6e9d6efe195a9a5a24e46dc plugins/generic/connector.py
a967f4ebd101c68a5dcc10ff18c882a8f44a5c3bf06613d951a739ecc3abb9b3 plugins/generic/custom.py
37351d6fb7418e3659bec5c9a6f9f181a606deae74d3bc9fb8c97f495449471f plugins/generic/databases.py
6d037861acbbabec529e10c50840820ca7b876c29c69310a571b519c3f3b72fa plugins/generic/databases.py
36b7319ac00f8fe1a33496364a76ff165ea2e66db0150f5366a45135366369ca plugins/generic/entries.py
d2de7fc135cf0db3eb4ac4a509c23ebec5250a5d8043face7f8c546a09f301b5 plugins/generic/enumeration.py
a02ac4ebc1cc488a2aa5ae07e6d0c3d5064e99ded7fd529dfa073735692f11df plugins/generic/filesystem.py
@ -581,6 +582,7 @@ a48c411fea864e6bcd6a1c7e1a35094b8cda8d15088fd9e7b0270542ae20daa9 tests/test_com
5016119bdb57094381afdca35ef29a4a6641e26e4b48a9119f1db633e6123d29 tests/test_datafiles.py
9c240d4f796e56376374d4ce46f358ceb7d48cc6a7427760c5bfb89ff01cb545 tests/test_datatypes.py
3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py
9c0a0cd0b2d52a53f75c98c60f87a022354b7c3dc4baaf3fe1e272a0af5b7f0a tests/test_dialectdbms.py
e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py
993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py
9cd5841349bc4db818658d12184929a96f7f279eff1f53ad18a54dbefbd6b276 tests/test_dump_jsonl.py

View file

@ -1321,7 +1321,7 @@
</dbms>
<dbms value="ClickHouse">
<cast query="CAST(%s AS String)"/>
<cast query="CAST(%s AS Nullable(String))"/>
<length query="length(%s)"/>
<isnull query="ifNull(%s, '')"/>
<delimiter query="||"/>

View file

@ -100,6 +100,7 @@ from lib.request.connect import Connect as Request
from lib.request.comparison import comparison
from lib.request.inject import checkBooleanExpression
from lib.request.templates import getPageTemplate
from lib.utils.dialect import dialectCheckDbms
from lib.techniques.union.test import unionTest
from lib.techniques.union.use import configUnion
from thirdparty import six
@ -149,6 +150,13 @@ def checkSqlInjection(place, parameter, value):
if not Backend.getIdentifiedDbms() and kb.heuristicDbms is None and not kb.droppingRequests:
kb.heuristicDbms = heuristicCheckDbms(injection)
# keyword-free fallback: heuristicCheckDbms() above uses SELECT/quote payloads
# and is skipped when the WAF/IPS is dropping requests; the operator-dialect
# probes carry no SELECT/quote/schema name, so they can still narrow the DBMS in
# that case (or when it was inconclusive), using the now-calibrated boolean oracle
if not Backend.getIdentifiedDbms() and kb.heuristicDbms is None:
kb.heuristicDbms = dialectCheckDbms(injection)
# If the DBMS has already been fingerprinted (via DBMS-specific
# error message, simple heuristic check or via DBMS-specific
# payload), ask the user to limit the tests to the fingerprinted

View file

@ -20,7 +20,7 @@ from lib.core.enums import OS
from thirdparty import six
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.10.6.120"
VERSION = "1.10.6.121"
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)
@ -306,7 +306,7 @@ FIREBIRD_SYSTEM_DBS = ("RDB$BACKUP_HISTORY", "RDB$CHARACTER_SETS", "RDB$CHECK_CO
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_LOB")
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")

120
lib/utils/dialect.py Normal file
View file

@ -0,0 +1,120 @@
#!/usr/bin/env python
"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission
"""
from lib.core.common import Backend
from lib.core.common import popValue
from lib.core.common import pushValue
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.enums import DBMS
from lib.request.inject import checkBooleanExpression
# Operator-dialect probes for a keyword-free back-end DBMS heuristic.
#
# Each probe is an arithmetic identity that holds only in the dialect(s) noted, using operator
# *semantics* alone - no SQL keywords, functions, quotes or schema names. It complements
# heuristicCheckDbms() (which uses (SELECT 'x')='x' string round-trips): the dialect probes carry
# no SELECT/quote, so they can narrow the back-end DBMS where those are dropped (e.g. a
# keyword-matching WAF/IPS, or when kb.droppingRequests has it skipped entirely).
#
# Each probe is evaluated through checkBooleanExpression(), i.e. as an appended boolean
# (... AND (<probe>)), which yields a clean true/false from the comparison oracle. (A value-position
# variant - replacing the value with id=2^0 etc. - was prototyped and rejected: those probes land on
# OTHER valid rows, which sqlmap's fuzzy page comparison conflates with the anchor row, producing
# false positives. See PROVE_DESIGN.md.)
#
# Truth table measured on a live OWASP-CRS platform across 11 engines (MySQL, MariaDB/TiDB,
# PostgreSQL, CockroachDB, Microsoft SQL Server, SQLite, Firebird, ClickHouse, H2, HSQLDB, Derby);
# only the zero-false-positive rules are kept (see _classify). With anchor value 2:
#
# * 2^0=2 -> '^' is bitwise XOR (MySQL/MSSQL: 2^0=2) vs exponentiation (PostgreSQL: 2^0=1) vs
# no such operator (SQLite/Oracle/... -> error, so false)
# * 2^3=8 -> '^' is exponentiation (PostgreSQL/CockroachDB: 2^3=8) - false for XOR dialects
# (2^3=1) and erroring dialects; a positive PostgreSQL-family marker. CAVEAT:
# '^'=exponentiation is not strictly unique to PostgreSQL - MS Access/Jet and DuckDB
# also use it (neither on the platform), so this can read as PostgreSQL there.
# * 5/2=2 -> integer division (PostgreSQL/MSSQL/SQLite) vs real division (MySQL/Oracle: 2.5)
# * 2|0=2 -> a bitwise OR operator exists (absent in Firebird/Oracle/ClickHouse/H2)
DIALECT_PROBES = (
("xor", "2^0=2"),
("pgpow", "2^3=8"),
("intdiv", "5/2=2"),
("bitor", "2|0=2"),
)
def _classify(signature):
"""
Maps a measured (xor, pgpow, intdiv, bitor) operator-dialect signature to a back-end
DBMS, or returns None when the signature does not *uniquely* identify a major DBMS (so
detection proceeds unchanged - the heuristic never wrong-foots the scan).
Rules below are the subset of the measured 11-engine truth table that maps with zero
false positives. Engines whose operator profile is not distinctive enough (Oracle's
all-false signature, which a minimal engine like ClickHouse/H2/Firebird/HSQLDB/Derby or
a fully WAF-blocked channel also produces) deliberately fall through to None:
>>> _classify((True, False, False, True)) # MySQL / MariaDB / TiDB
'MySQL'
>>> _classify((True, False, True, True)) # Microsoft SQL Server
'Microsoft SQL Server'
>>> _classify((False, True, True, True)) # PostgreSQL
'PostgreSQL'
>>> _classify((False, True, False, True)) # CockroachDB (pgwire) -> PostgreSQL family
'PostgreSQL'
>>> _classify((False, False, True, True)) # SQLite
'SQLite'
>>> _classify((False, False, True, False)) is None # Firebird/HSQLDB/Derby/H2 -> no prior
True
>>> _classify((False, False, False, False)) is None # all-false (Oracle/ClickHouse/blocked) -> no prior
True
"""
xor, pgpow, intdiv, bitor = signature
if pgpow: # '^' is exponentiation -> PostgreSQL family
return DBMS.PGSQL
if xor and intdiv: # '^' is XOR AND integer division -> SQL Server
return DBMS.MSSQL
if xor and not intdiv: # '^' is XOR AND real division -> MySQL family
return DBMS.MYSQL
if not xor and intdiv and bitor: # no '^', integer division, bitwise '|' -> SQLite
return DBMS.SQLITE
return None
def dialectCheckDbms(injection):
"""
Keyword-free back-end DBMS heuristic via operator-dialect differentials, evaluated through the
given (boolean-capable) injection. Complements heuristicCheckDbms() - which is skipped when the
WAF/IPS is dropping requests and otherwise relies on SELECT/quote payloads - because every probe
here is built from operator semantics alone. Returns the DBMS name or None; an ambiguous or
WAF-blocked channel yields None, leaving the scan unchanged.
"""
retVal = None
if conf.skipHeuristics:
return retVal
pushValue(kb.injection)
kb.injection = injection
try:
# channel sanity: a tautology must read TRUE and a contradiction FALSE, otherwise the
# boolean oracle is unreliable and the all-false signature (Oracle-like) would be meaningless
if checkBooleanExpression("2=2") and not checkBooleanExpression("2=3"):
signature = tuple(bool(checkBooleanExpression(expr)) for _, expr in DIALECT_PROBES)
retVal = _classify(signature)
finally:
kb.injection = popValue()
if retVal and not Backend.getIdentifiedDbms():
infoMsg = "heuristic (dialect) test shows that the back-end DBMS could be '%s'" % retVal
logger.info(infoMsg)
return retVal

View file

@ -93,7 +93,7 @@ class Enumeration(GenericEnumeration):
if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
for db in dbs:
if conf.excludeSysDbs and db in self.excludeDbsList:
if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList:
infoMsg = "skipping system database '%s'" % db
singleTimeLogMessage(infoMsg)
continue
@ -116,7 +116,7 @@ class Enumeration(GenericEnumeration):
if not kb.data.cachedTables and isInferenceAvailable() and not conf.direct:
for db in dbs:
if conf.excludeSysDbs and db in self.excludeDbsList:
if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList:
infoMsg = "skipping system database '%s'" % db
singleTimeLogMessage(infoMsg)
continue
@ -206,7 +206,7 @@ class Enumeration(GenericEnumeration):
for db in foundTbls.keys():
db = safeSQLIdentificatorNaming(db)
if conf.excludeSysDbs and db in self.excludeDbsList:
if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList:
infoMsg = "skipping system database '%s'" % db
singleTimeLogMessage(infoMsg)
continue
@ -343,7 +343,7 @@ class Enumeration(GenericEnumeration):
for db in (_ for _ in dbs if _):
db = safeSQLIdentificatorNaming(db)
if conf.excludeSysDbs and db in self.excludeDbsList:
if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList:
continue
if conf.exclude and re.search(conf.exclude, db, re.I) is not None:

View file

@ -304,7 +304,7 @@ class Databases(object):
if conf.excludeSysDbs:
infoMsg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(db) for db in self.excludeDbsList))
logger.info(infoMsg)
query += " IN (%s)" % ','.join("'%s'" % unsafeSQLIdentificatorNaming(db) for db in sorted(dbs) if db not in self.excludeDbsList)
query += " IN (%s)" % ','.join("'%s'" % unsafeSQLIdentificatorNaming(db) for db in sorted(dbs) if unsafeSQLIdentificatorNaming(db) not in self.excludeDbsList)
else:
query += " IN (%s)" % ','.join("'%s'" % unsafeSQLIdentificatorNaming(db) for db in sorted(dbs))
@ -356,7 +356,7 @@ class Databases(object):
if not kb.data.cachedTables and isInferenceAvailable() and not conf.direct:
for db in dbs:
if conf.excludeSysDbs and db in self.excludeDbsList:
if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList:
infoMsg = "skipping system database '%s'" % unsafeSQLIdentificatorNaming(db)
logger.info(infoMsg)
continue

106
tests/test_dialectdbms.py Normal file
View file

@ -0,0 +1,106 @@
#!/usr/bin/env python
"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission
Operator-dialect DBMS heuristic (lib/utils/dialect.py). These lock in the empirical truth
table: the (xor, intdiv, pgcast, bitor) operator signatures measured across 11 live engines
on an OWASP-CRS test platform, asserting that _classify() maps each to the expected back-end
DBMS - and, just as importantly, that the engines whose signatures collide or are ambiguous
map to None (no prior), so the heuristic never wrong-foots detection. The end-to-end behaviour
(the probes producing these signatures through a real boolean injection) is exercised against
the live platform, not here.
"""
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _testutils import bootstrap
bootstrap()
import lib.utils.dialect as dialect
from lib.core.data import kb
from lib.core.enums import DBMS
from lib.utils.dialect import _classify
from lib.utils.dialect import dialectCheckDbms
# measured 2026-06 across the sqli-platform (boolean form "id=2 AND <probe>", anchor value 2);
# signature = (2^0=2, 2^3=8, 5/2=2, 2|0=2)
MEASURED = {
"mysql": ((True, False, False, True), DBMS.MYSQL),
"tidb": ((True, False, False, True), DBMS.MYSQL), # MySQL wire-compatible
"mssql": ((True, False, True, True), DBMS.MSSQL),
"postgres": ((False, True, True, True), DBMS.PGSQL),
"cockroach": ((False, True, False, True), DBMS.PGSQL), # pgwire (exponent '^', decimal division)
"sqlite": ((False, False, True, True), DBMS.SQLITE),
# not distinctive enough -> deliberately no prior (operators alone can't safely separate these)
"firebird": ((False, False, True, False), None),
"hsqldb": ((False, False, True, False), None), # collides with firebird/derby/h2
"derby": ((False, False, True, False), None),
"h2": ((False, False, True, False), None),
"clickhouse": ((False, False, False, False), None), # all-error, like Oracle/broken channel
}
class TestDialectClassification(unittest.TestCase):
def test_measured_engines_map_as_expected(self):
for engine, (signature, expected) in MEASURED.items():
self.assertEqual(_classify(signature), expected, "engine %r misclassified" % engine)
def test_no_false_positive_across_measured_set(self):
# ambiguous engines must not borrow a major-DBMS identity; concrete ones must stay in range
for engine, (signature, expected) in MEASURED.items():
result = _classify(signature)
if expected is None:
self.assertIsNone(result, "ambiguous engine %r leaked a DBMS prior" % engine)
else:
self.assertIn(result, (DBMS.MYSQL, DBMS.MSSQL, DBMS.PGSQL, DBMS.SQLITE, DBMS.ORACLE))
def test_all_error_signature_yields_no_prior(self):
# an all-error signature (Oracle, ClickHouse, or simply a WAF-blocked channel) is not
# distinctive enough - it must NOT be guessed as any DBMS
self.assertIsNone(_classify((False, False, False, False)))
def test_pgpow_dominates_as_postgres_marker(self):
# exponentiation '^' is a positive PostgreSQL-family marker regardless of division flavour
self.assertEqual(_classify((False, True, True, True)), DBMS.PGSQL)
self.assertEqual(_classify((False, True, False, True)), DBMS.PGSQL)
class TestDialectCheckDbmsGuard(unittest.TestCase):
"""dialectCheckDbms() end-to-end with a mocked boolean oracle: correct DBMS on a good
channel, and None (no prior) whenever the channel is unreliable - the safety contract."""
def _run(self, truth):
# truth: {expression: bool} simulating checkBooleanExpression through a confirmed injection
orig = dialect.checkBooleanExpression
dialect.checkBooleanExpression = lambda expr, **kwargs: bool(truth.get(expr, False))
saved = kb.get("injection")
try:
return dialectCheckDbms(object()) # the injection arg is only stashed, never inspected here
finally:
dialect.checkBooleanExpression = orig
kb.injection = saved
def test_identifies_mysql_on_good_channel(self):
truth = {"2=2": True, "2=3": False, "2^0=2": True, "2^3=8": False, "5/2=2": False, "2|0=2": True}
self.assertEqual(self._run(truth), DBMS.MYSQL)
def test_identifies_postgres_on_good_channel(self):
truth = {"2=2": True, "2=3": False, "2^0=2": False, "2^3=8": True, "5/2=2": True, "2|0=2": True}
self.assertEqual(self._run(truth), DBMS.PGSQL)
def test_none_on_blocked_channel(self):
# everything blocked/false -> the tautology 2=2 reads False -> sanity fails -> None
self.assertIsNone(self._run({}))
def test_none_on_static_channel(self):
# a static page reads everything True, so the contradiction 2=3 is True -> sanity fails -> None
self.assertIsNone(self._run({"2=2": True, "2=3": True, "2^0=2": True, "2^3=8": True, "5/2=2": True, "2|0=2": True}))
if __name__ == "__main__":
unittest.main(verbosity=2)