Patches for dbwire

This commit is contained in:
Miroslav Štampar 2026-07-12 10:59:49 +02:00
parent 921870ccf0
commit 2acc8a5540
5 changed files with 32 additions and 10 deletions

View file

@ -14,6 +14,7 @@ backslash escaping and \\N for NULL). Covers ClickHouse and its HTTP-compatible
"""
import base64
import socket
try:
from urllib.request import Request, urlopen # Python 3
@ -29,14 +30,14 @@ def _unescape(value):
return None
if "\\" not in value:
return value
out, it = [], iter(range(len(value)))
out = []
i = 0
n = len(value)
while i < n:
ch = value[i]
if ch == "\\" and i + 1 < n:
nxt = value[i + 1]
out.append({"t": "\t", "n": "\n", "r": "\r", "0": "\0", "\\": "\\", "'": "'"}.get(nxt, nxt))
out.append({"t": "\t", "n": "\n", "r": "\r", "0": "\0", "b": "\b", "f": "\f", "a": "\a", "v": "\v", "\\": "\\", "'": "'"}.get(nxt, nxt))
i += 2
else:
out.append(ch)
@ -103,6 +104,8 @@ class Connection(object):
raise ProgrammingError("(remote) %s" % ex.read().decode("utf-8", "replace").strip())
except URLError as ex:
raise OperationalError("(remote) %s" % ex)
except (socket.timeout, socket.error) as ex:
raise OperationalError("(remote) %s" % ex)
if not body:
return None, []

View file

@ -23,7 +23,6 @@ import os
import socket
import struct
from extra.dbwire import DatabaseError
from extra.dbwire import DataError
from extra.dbwire import IntegrityError
from extra.dbwire import InterfaceError

View file

@ -14,7 +14,7 @@ headers are sent so the same client works against Presto and Trino.
import base64
import json
import time
import socket
try:
from urllib.request import Request, urlopen # Python 3
@ -27,6 +27,20 @@ from extra.dbwire import NotSupportedError
from extra.dbwire import OperationalError
from extra.dbwire import ProgrammingError
def _convert(value, coltype):
# normalize Presto/Trino JSON cells for sqlmap: VARBINARY arrives base64-encoded (decode to bytes so
# direct()'s binary handling hex-encodes it), ARRAY/MAP/ROW arrive as JSON structures (serialize to text)
if value is None:
return value
if coltype.startswith("varbinary"):
try:
return base64.b64decode(value)
except Exception:
return value
if isinstance(value, (list, dict)):
return json.dumps(value)
return value
class Cursor(object):
def __init__(self, connection):
self.connection = connection
@ -65,9 +79,13 @@ class Connection(object):
self._headers = {"Content-Type": "text/plain"}
for prefix in ("X-Presto-", "X-Trino-"):
self._headers[prefix + "User"] = user or "sqlmap"
self._headers[prefix + "Catalog"] = catalog or ""
self._headers[prefix + "Schema"] = schema or "default"
self._headers[prefix + "Source"] = "dbwire"
# only send Catalog/Schema when supplied: a Schema without a Catalog makes Trino reject every
# request ("Schema is set but catalog is not"), so never force a "default" schema
if catalog:
self._headers[prefix + "Catalog"] = catalog
if schema:
self._headers[prefix + "Schema"] = schema
if password:
token = base64.b64encode(("%s:%s" % (user or "", password)).encode("utf-8")).decode("ascii")
self._headers["Authorization"] = "Basic %s" % token
@ -92,6 +110,8 @@ class Connection(object):
raise ProgrammingError("(remote) HTTP %s: %s" % (ex.code, ex.read().decode("utf-8", "replace")[:200]))
except URLError as ex:
raise OperationalError("(remote) %s" % ex)
except (socket.timeout, socket.error) as ex:
raise OperationalError("(remote) %s" % ex)
try:
return json.loads(body)
except ValueError as ex:
@ -99,15 +119,16 @@ class Connection(object):
def _query(self, query):
page = self._request(self._statement_url, data=query)
columns, rows = None, []
columns, rows, types = None, [], []
while True:
if page.get("error"):
message = page["error"].get("message", "unknown error")
raise ProgrammingError("(remote) %s" % message)
if page.get("columns") and columns is None:
columns = [(c.get("name"), c.get("type"), None, None, None, None, None) for c in page["columns"]]
types = [(c.get("type") or "") for c in page["columns"]]
for row in page.get("data") or []:
rows.append(tuple(row))
rows.append(tuple(_convert(v, types[i] if i < len(types) else "") for i, v in enumerate(row)))
next_uri = page.get("nextUri")
if not next_uri:
break

View file

@ -355,7 +355,6 @@ def _parse_tokens(sock, login=False):
rows.append(tuple(row))
elif token == 0xaa: # ERROR
(tlen,) = struct.unpack("<H", data[off:off + 2]); off += 2
number = struct.unpack("<i", data[off:off + 4])[0]
msg_off = off + 4 + 1 + 1 # number(4) state(1) class(1)
(mlen,) = struct.unpack("<H", data[msg_off:msg_off + 2])
error = data[msg_off + 2:msg_off + 2 + mlen * 2].decode("utf-16-le", "replace")

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.7.85"
VERSION = "1.10.7.86"
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)