mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-29 12:23:42 +03:00
Raise an exception if the libpq fails to decode bytea in hex format
This commit is contained in:
parent
c01a7edbf4
commit
894d3f653c
2
NEWS
2
NEWS
|
@ -13,6 +13,8 @@ What's new in psycopg 2.4
|
||||||
time from the backend.
|
time from the backend.
|
||||||
- The named cursors name can be an invalid identifier.
|
- The named cursors name can be an invalid identifier.
|
||||||
- 'cursor.description' is provided in named tuples if available.
|
- 'cursor.description' is provided in named tuples if available.
|
||||||
|
- Raise a clean exception instead of returning bad data when receiving bytea
|
||||||
|
in 'hex' format and the client libpq can't parse them.
|
||||||
- Connections and cursors are weakly referenceable.
|
- Connections and cursors are weakly referenceable.
|
||||||
- Added 'b' and 't' mode to large objects: write can deal with both bytes
|
- Added 'b' and 't' mode to large objects: write can deal with both bytes
|
||||||
strings and unicode; read can return either bytes strings or decoded
|
strings and unicode; read can return either bytes strings or decoded
|
||||||
|
|
|
@ -290,6 +290,9 @@ the SQL string that would be sent to the database.
|
||||||
`bytea_output`__ parameter to ``escape``, either in the server
|
`bytea_output`__ parameter to ``escape``, either in the server
|
||||||
configuration or in the client session using a query such as ``SET
|
configuration or in the client session using a query such as ``SET
|
||||||
bytea_output TO escape;`` before trying to receive binary data.
|
bytea_output TO escape;`` before trying to receive binary data.
|
||||||
|
|
||||||
|
Starting from Psycopg 2.4 this condition is detected and signaled with a
|
||||||
|
`~psycopg2.InterfaceError`.
|
||||||
|
|
||||||
.. __: http://www.postgresql.org/docs/9.0/static/datatype-binary.html
|
.. __: http://www.postgresql.org/docs/9.0/static/datatype-binary.html
|
||||||
.. __: http://www.postgresql.org/docs/9.0/static/runtime-config-client.html#GUC-BYTEA-OUTPUT
|
.. __: http://www.postgresql.org/docs/9.0/static/runtime-config-client.html#GUC-BYTEA-OUTPUT
|
||||||
|
|
|
@ -166,6 +166,19 @@ typecast_BINARY_cast(const char *s, Py_ssize_t l, PyObject *curs)
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check the escaping was successful */
|
||||||
|
if (s[0] == '\\' && s[1] == 'x' /* input encoded in hex format */
|
||||||
|
&& str[0] == 'x' /* output resulted in an 'x' */
|
||||||
|
&& s[2] != '7' && s[3] != '8') /* input wasn't really an x (0x78) */
|
||||||
|
{
|
||||||
|
PyErr_SetString(InterfaceError,
|
||||||
|
"can't receive bytea data from server >= 9.0 with the current "
|
||||||
|
"libpq client library: please update the libpq to at least 9.0 "
|
||||||
|
"or set bytea_output to 'escape' in the server config "
|
||||||
|
"or with a query");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
chunk = (chunkObject *) PyObject_New(chunkObject, &chunkType);
|
chunk = (chunkObject *) PyObject_New(chunkObject, &chunkType);
|
||||||
if (chunk == NULL) goto fail;
|
if (chunk == NULL) goto fail;
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,10 @@ class QuotingTestCase(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
res = curs.fetchone()[0].tobytes()
|
res = curs.fetchone()[0].tobytes()
|
||||||
|
|
||||||
|
if res[0] in (b('x'), ord(b('x'))) and self.conn.server_version >= 90000:
|
||||||
|
return self.skipTest(
|
||||||
|
"bytea broken with server >= 9.0, libpq < 9")
|
||||||
|
|
||||||
self.assertEqual(res, data)
|
self.assertEqual(res, data)
|
||||||
self.assert_(not self.conn.notices)
|
self.assert_(not self.conn.notices)
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,24 @@ def skip_if_no_namedtuple(f):
|
||||||
return skip_if_no_namedtuple_
|
return skip_if_no_namedtuple_
|
||||||
|
|
||||||
|
|
||||||
|
def skip_if_broken_hex_binary(f):
|
||||||
|
"""Decorator to detect libpq < 9.0 unable to parse bytea in hex format"""
|
||||||
|
def cope_with_hex_binary_(self):
|
||||||
|
from psycopg2 import InterfaceError
|
||||||
|
try:
|
||||||
|
return f(self)
|
||||||
|
except InterfaceError, e:
|
||||||
|
if '9.0' in str(e) and self.conn.server_version >= 90000:
|
||||||
|
return self.skipTest(
|
||||||
|
# FIXME: we are only assuming the libpq is older here,
|
||||||
|
# but we don't have a reliable way to detect the libpq
|
||||||
|
# version, not pre-9 at least.
|
||||||
|
"bytea broken with server >= 9.0, libpq < 9")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
return cope_with_hex_binary_
|
||||||
|
|
||||||
def skip_if_no_iobase(f):
|
def skip_if_no_iobase(f):
|
||||||
"""Skip a test if io.TextIOBase is not available."""
|
"""Skip a test if io.TextIOBase is not available."""
|
||||||
def skip_if_no_iobase_(self):
|
def skip_if_no_iobase_(self):
|
||||||
|
|
|
@ -28,7 +28,7 @@ except:
|
||||||
pass
|
pass
|
||||||
import sys
|
import sys
|
||||||
import testutils
|
import testutils
|
||||||
from testutils import unittest
|
from testutils import unittest, skip_if_broken_hex_binary
|
||||||
from testconfig import dsn
|
from testconfig import dsn
|
||||||
|
|
||||||
import psycopg2
|
import psycopg2
|
||||||
|
@ -116,6 +116,7 @@ class TypesBasicTests(unittest.TestCase):
|
||||||
s = self.execute("SELECT %s AS foo", (float("-inf"),))
|
s = self.execute("SELECT %s AS foo", (float("-inf"),))
|
||||||
self.failUnless(str(s) == "-inf", "wrong float quoting: " + str(s))
|
self.failUnless(str(s) == "-inf", "wrong float quoting: " + str(s))
|
||||||
|
|
||||||
|
@skip_if_broken_hex_binary
|
||||||
def testBinary(self):
|
def testBinary(self):
|
||||||
if sys.version_info[0] < 3:
|
if sys.version_info[0] < 3:
|
||||||
s = ''.join([chr(x) for x in range(256)])
|
s = ''.join([chr(x) for x in range(256)])
|
||||||
|
@ -142,6 +143,7 @@ class TypesBasicTests(unittest.TestCase):
|
||||||
b = psycopg2.Binary(bytes([]))
|
b = psycopg2.Binary(bytes([]))
|
||||||
self.assertEqual(str(b), "''::bytea")
|
self.assertEqual(str(b), "''::bytea")
|
||||||
|
|
||||||
|
@skip_if_broken_hex_binary
|
||||||
def testBinaryRoundTrip(self):
|
def testBinaryRoundTrip(self):
|
||||||
# test to make sure buffers returned by psycopg2 are
|
# test to make sure buffers returned by psycopg2 are
|
||||||
# understood by execute:
|
# understood by execute:
|
||||||
|
@ -189,6 +191,7 @@ class TypesBasicTests(unittest.TestCase):
|
||||||
s = self.execute("SELECT '{}'::text AS foo")
|
s = self.execute("SELECT '{}'::text AS foo")
|
||||||
self.failUnlessEqual(s, "{}")
|
self.failUnlessEqual(s, "{}")
|
||||||
|
|
||||||
|
@skip_if_broken_hex_binary
|
||||||
@testutils.skip_from_python(3)
|
@testutils.skip_from_python(3)
|
||||||
def testTypeRoundtripBuffer(self):
|
def testTypeRoundtripBuffer(self):
|
||||||
o1 = buffer("".join(map(chr, range(256))))
|
o1 = buffer("".join(map(chr, range(256))))
|
||||||
|
@ -199,14 +202,18 @@ class TypesBasicTests(unittest.TestCase):
|
||||||
o1 = buffer("")
|
o1 = buffer("")
|
||||||
o2 = self.execute("select %s;", (o1,))
|
o2 = self.execute("select %s;", (o1,))
|
||||||
self.assertEqual(type(o1), type(o2))
|
self.assertEqual(type(o1), type(o2))
|
||||||
|
self.assertEqual(str(o1), str(o2))
|
||||||
|
|
||||||
|
@skip_if_broken_hex_binary
|
||||||
@testutils.skip_from_python(3)
|
@testutils.skip_from_python(3)
|
||||||
def testTypeRoundtripBufferArray(self):
|
def testTypeRoundtripBufferArray(self):
|
||||||
o1 = buffer("".join(map(chr, range(256))))
|
o1 = buffer("".join(map(chr, range(256))))
|
||||||
o1 = [o1]
|
o1 = [o1]
|
||||||
o2 = self.execute("select %s;", (o1,))
|
o2 = self.execute("select %s;", (o1,))
|
||||||
self.assertEqual(type(o1[0]), type(o2[0]))
|
self.assertEqual(type(o1[0]), type(o2[0]))
|
||||||
|
self.assertEqual(str(o1[0]), str(o2[0]))
|
||||||
|
|
||||||
|
@skip_if_broken_hex_binary
|
||||||
@testutils.skip_before_python(3)
|
@testutils.skip_before_python(3)
|
||||||
def testTypeRoundtripBytes(self):
|
def testTypeRoundtripBytes(self):
|
||||||
o1 = bytes(range(256))
|
o1 = bytes(range(256))
|
||||||
|
@ -218,6 +225,7 @@ class TypesBasicTests(unittest.TestCase):
|
||||||
o2 = self.execute("select %s;", (o1,))
|
o2 = self.execute("select %s;", (o1,))
|
||||||
self.assertEqual(memoryview, type(o2))
|
self.assertEqual(memoryview, type(o2))
|
||||||
|
|
||||||
|
@skip_if_broken_hex_binary
|
||||||
@testutils.skip_before_python(3)
|
@testutils.skip_before_python(3)
|
||||||
def testTypeRoundtripBytesArray(self):
|
def testTypeRoundtripBytesArray(self):
|
||||||
o1 = bytes(range(256))
|
o1 = bytes(range(256))
|
||||||
|
@ -225,23 +233,32 @@ class TypesBasicTests(unittest.TestCase):
|
||||||
o2 = self.execute("select %s;", (o1,))
|
o2 = self.execute("select %s;", (o1,))
|
||||||
self.assertEqual(memoryview, type(o2[0]))
|
self.assertEqual(memoryview, type(o2[0]))
|
||||||
|
|
||||||
|
@skip_if_broken_hex_binary
|
||||||
@testutils.skip_before_python(2, 6)
|
@testutils.skip_before_python(2, 6)
|
||||||
def testAdaptBytearray(self):
|
def testAdaptBytearray(self):
|
||||||
o1 = bytearray(range(256))
|
o1 = bytearray(range(256))
|
||||||
o2 = self.execute("select %s;", (o1,))
|
o2 = self.execute("select %s;", (o1,))
|
||||||
|
|
||||||
if sys.version_info[0] < 3:
|
if sys.version_info[0] < 3:
|
||||||
self.assertEqual(buffer, type(o2))
|
self.assertEqual(buffer, type(o2))
|
||||||
else:
|
else:
|
||||||
self.assertEqual(memoryview, type(o2))
|
self.assertEqual(memoryview, type(o2))
|
||||||
|
|
||||||
|
self.assertEqual(len(o1), len(o2))
|
||||||
|
for c1, c2 in zip(o1, o2):
|
||||||
|
self.assertEqual(c1, ord(c2))
|
||||||
|
|
||||||
# Test with an empty buffer
|
# Test with an empty buffer
|
||||||
o1 = bytearray([])
|
o1 = bytearray([])
|
||||||
o2 = self.execute("select %s;", (o1,))
|
o2 = self.execute("select %s;", (o1,))
|
||||||
|
|
||||||
|
self.assertEqual(len(o2), 0)
|
||||||
if sys.version_info[0] < 3:
|
if sys.version_info[0] < 3:
|
||||||
self.assertEqual(buffer, type(o2))
|
self.assertEqual(buffer, type(o2))
|
||||||
else:
|
else:
|
||||||
self.assertEqual(memoryview, type(o2))
|
self.assertEqual(memoryview, type(o2))
|
||||||
|
|
||||||
|
@skip_if_broken_hex_binary
|
||||||
@testutils.skip_before_python(2, 7)
|
@testutils.skip_before_python(2, 7)
|
||||||
def testAdaptMemoryview(self):
|
def testAdaptMemoryview(self):
|
||||||
o1 = memoryview(bytearray(range(256)))
|
o1 = memoryview(bytearray(range(256)))
|
||||||
|
@ -259,6 +276,13 @@ class TypesBasicTests(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
self.assertEqual(memoryview, type(o2))
|
self.assertEqual(memoryview, type(o2))
|
||||||
|
|
||||||
|
def testByteaHexCheckFalsePositive(self):
|
||||||
|
# the check \x -> x to detect bad bytea decode
|
||||||
|
# may be fooled if the first char is really an 'x'
|
||||||
|
o1 = psycopg2.Binary(b('x'))
|
||||||
|
o2 = self.execute("SELECT %s::bytea AS foo", (o1,))
|
||||||
|
self.assertEqual(b('x'), o2[0])
|
||||||
|
|
||||||
|
|
||||||
class AdaptSubclassTest(unittest.TestCase):
|
class AdaptSubclassTest(unittest.TestCase):
|
||||||
def test_adapt_subtype(self):
|
def test_adapt_subtype(self):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user