Skip to content

Commit 8f010dc

Browse files
author
Erlend Egeberg Aasland
authored
bpo-44822: Don't truncate strs with embedded NULL chars returned by sqlite3 UDF callbacks (GH-27588)
1 parent 3e4cb7f commit 8f010dc

File tree

3 files changed

+41
-3
lines changed

3 files changed

+41
-3
lines changed

Lib/sqlite3/test/userfunctions.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ def wrapper(self, *args, **kwargs):
5353

5454
def func_returntext():
5555
return "foo"
56+
def func_returntextwithnull():
57+
return "1\x002"
5658
def func_returnunicode():
5759
return "bar"
5860
def func_returnint():
@@ -163,11 +165,21 @@ def step(self, val):
163165
def finalize(self):
164166
return self.val
165167

168+
class AggrText:
169+
def __init__(self):
170+
self.txt = ""
171+
def step(self, txt):
172+
self.txt = self.txt + txt
173+
def finalize(self):
174+
return self.txt
175+
176+
166177
class FunctionTests(unittest.TestCase):
167178
def setUp(self):
168179
self.con = sqlite.connect(":memory:")
169180

170181
self.con.create_function("returntext", 0, func_returntext)
182+
self.con.create_function("returntextwithnull", 0, func_returntextwithnull)
171183
self.con.create_function("returnunicode", 0, func_returnunicode)
172184
self.con.create_function("returnint", 0, func_returnint)
173185
self.con.create_function("returnfloat", 0, func_returnfloat)
@@ -211,6 +223,12 @@ def test_func_return_text(self):
211223
self.assertEqual(type(val), str)
212224
self.assertEqual(val, "foo")
213225

226+
def test_func_return_text_with_null_char(self):
227+
cur = self.con.cursor()
228+
res = cur.execute("select returntextwithnull()").fetchone()[0]
229+
self.assertEqual(type(res), str)
230+
self.assertEqual(res, "1\x002")
231+
214232
def test_func_return_unicode(self):
215233
cur = self.con.cursor()
216234
cur.execute("select returnunicode()")
@@ -390,6 +408,7 @@ def setUp(self):
390408
self.con.create_aggregate("checkType", 2, AggrCheckType)
391409
self.con.create_aggregate("checkTypes", -1, AggrCheckTypes)
392410
self.con.create_aggregate("mysum", 1, AggrSum)
411+
self.con.create_aggregate("aggtxt", 1, AggrText)
393412

394413
def tearDown(self):
395414
#self.cur.close()
@@ -486,6 +505,15 @@ def test_aggr_no_match(self):
486505
val = cur.fetchone()[0]
487506
self.assertIsNone(val)
488507

508+
def test_aggr_text(self):
509+
cur = self.con.cursor()
510+
for txt in ["foo", "1\x002"]:
511+
with self.subTest(txt=txt):
512+
cur.execute("select aggtxt(?) from test", (txt,))
513+
val = cur.fetchone()[0]
514+
self.assertEqual(val, txt)
515+
516+
489517
class AuthorizerTests(unittest.TestCase):
490518
@staticmethod
491519
def authorizer_cb(action, arg1, arg2, dbname, source):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:mod:`sqlite3` user-defined functions and aggregators returning
2+
:class:`strings <str>` with embedded NUL characters are no longer
3+
truncated. Patch by Erlend E. Aasland.

Modules/_sqlite/connection.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -519,10 +519,17 @@ _pysqlite_set_result(sqlite3_context* context, PyObject* py_val)
519519
} else if (PyFloat_Check(py_val)) {
520520
sqlite3_result_double(context, PyFloat_AsDouble(py_val));
521521
} else if (PyUnicode_Check(py_val)) {
522-
const char *str = PyUnicode_AsUTF8(py_val);
523-
if (str == NULL)
522+
Py_ssize_t sz;
523+
const char *str = PyUnicode_AsUTF8AndSize(py_val, &sz);
524+
if (str == NULL) {
524525
return -1;
525-
sqlite3_result_text(context, str, -1, SQLITE_TRANSIENT);
526+
}
527+
if (sz > INT_MAX) {
528+
PyErr_SetString(PyExc_OverflowError,
529+
"string is longer than INT_MAX bytes");
530+
return -1;
531+
}
532+
sqlite3_result_text(context, str, (int)sz, SQLITE_TRANSIENT);
526533
} else if (PyObject_CheckBuffer(py_val)) {
527534
Py_buffer view;
528535
if (PyObject_GetBuffer(py_val, &view, PyBUF_SIMPLE) != 0) {

0 commit comments

Comments
 (0)