Some patching of dbwire

This commit is contained in:
Miroslav Štampar
2026-08-13 13:01:43 +02:00
parent d8dbaee0df
commit e9f8180614
6 changed files with 102 additions and 7 deletions
+12 -2
View File
@@ -132,6 +132,10 @@ class _Reader(object):
v = struct.unpack_from(">d", self._buf, self._off)[0]; self._off += 8; return v
def raw(self, n):
# a slice past the end returns short data *silently*, which would hand a truncated value to the
# caller (the fixed-width readers above raise struct.error instead)
if n < 0 or n > self.remaining():
raise InterfaceError("CAS response too short (wanted %d of %d bytes)" % (n, self.remaining()))
v = self._buf[self._off:self._off + n]; self._off += n; return bytes(v)
def skip(self, n):
@@ -371,9 +375,15 @@ class Connection(object):
reader.byte() # is_updatable
columns = self._parse_columns(reader, reader.int())
# args: handle, flag, max_col_size, max_row, binds, fetch_flag, auto_commit, forward_only_cursor,
# cache_time, query_timeout. auto_commit makes the CAS worker commit the statement and end the
# transaction - without it DML is rolled back when the connection drops, whatever _open() asked
# SET_DB_PARAMETER for. It must stay off for a SELECT: ending the transaction there invalidates the
# request handle the paged _fetch_remaining() still reads from.
select = stmt_type == _STMT_SELECT
exec_writer = (_Writer(_FC_EXECUTE).arg_int(handle).arg_byte(0).arg_int(0).arg_int(0)
.arg_null().arg_byte(1 if stmt_type == _STMT_SELECT else 0)
.arg_byte(0).arg_byte(1).arg_cache_time().arg_int(0))
.arg_null().arg_byte(1 if select else 0)
.arg_byte(0 if select else 1).arg_byte(1).arg_cache_time().arg_int(0))
reader = self._call(exec_writer)
total = reader.int()
+6
View File
@@ -142,6 +142,8 @@ _GDS_INTEGRITY = frozenset((335544838, 335544879, 335544880, 335544466, 33554466
_GDS_DATA = frozenset((335544321,))
_GDS_WARNING = 335544434
_MAX_MESSAGE_LENGTH = 0x40000000 # cap on a wire-supplied length, to bound a hostile/corrupt stream
# SRP-6a group used by Firebird (fixed 1024-bit prime, generator 2)
_SRP_N = int("E67D2E994B2F900C3F41F08F5BB2627ED0D49EE1FE767A52EFCD565CD6E768812C3E1E9CE8F0A8BEA6CB13CD29DDE"
"BF7A96D4A93B55D488DF099A15C89DCB0640738EB2CBDD9A8F7BAB561AB1B0DC1C6CDABF303264A08D1BCA932D1F"
@@ -306,6 +308,10 @@ class _Wire(object):
return buf
def recv(self, n, align=False):
# every length here comes off the wire (response buffers, status strings, per-value lengths): a
# negative one would silently return short data, a huge one would read until memory ran out
if n < 0 or n > _MAX_MESSAGE_LENGTH:
raise InterfaceError("invalid Firebird length (%d)" % n)
total = n + ((4 - n % 4) % 4) if align else n
data = self._recv_raw(total)
if self._rc:
+9 -3
View File
@@ -27,6 +27,7 @@ from extra.dbwire import keepalive
from extra.dbwire import ProgrammingError
_MAX_BLOCK = 0xffff >> 1
_MAX_MESSAGE_LENGTH = 0x40000000 # cap on a (re-assembled) response, to bound a hostile/corrupt stream
def _recvn(sock, n):
buf = b""
@@ -41,14 +42,19 @@ def _recvn(sock, n):
return buf
def _getblock(sock):
out = b""
# the block length is a 15-bit field, so bounding IT is pointless - a peer that never sets the last-flag
# simply streams blocks forever. Bound the accumulated response instead (as the other wire modules do).
chunks, total = [], 0
while True:
(header,) = struct.unpack("<H", _recvn(sock, 2))
length, last = header >> 1, header & 1
out += _recvn(sock, length)
total += length
if total > _MAX_MESSAGE_LENGTH:
raise InterfaceError("backend message too large (%d bytes)" % total)
chunks.append(_recvn(sock, length))
if last:
break
return out.decode("utf-8", "replace")
return b"".join(chunks).decode("utf-8", "replace")
def _putblock(sock, text):
data = text.encode("utf-8")
+1 -1
View File
@@ -321,9 +321,9 @@ def connect(host=None, port=5432, user=None, password=None, database=None, conne
for key, value in (("user", user or ""), ("database", database or user or ""), ("client_encoding", "UTF8")):
params += key.encode("ascii") + b"\x00" + ("%s" % value).encode("utf-8") + b"\x00"
params += b"\x00"
_send(sock, b"", struct.pack("!I", _PROTOCOL_VERSION) + params)
try:
_send(sock, b"", struct.pack("!I", _PROTOCOL_VERSION) + params) # StartupMessage
_authenticate(sock, user, password)
while True: # drain until ReadyForQuery (ParameterStatus/BackendKeyData/NoticeResponse)
mtype, payload = _read_message(sock)