From f669581a9527afb0d2325f9845a86715c0ba365d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 17 Sep 2019 09:20:56 +0300 Subject: [PATCH] bpo-38185: Fixed case-insensitive string comparison in sqlite3.Row indexing. (GH-16190) --- Lib/sqlite3/test/factory.py | 25 +++++--- .../2019-09-16-19-12-57.bpo-38185.zYWppY.rst | 1 + Modules/_sqlite/row.c | 62 +++++++++---------- 3 files changed, 50 insertions(+), 38 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-09-16-19-12-57.bpo-38185.zYWppY.rst diff --git a/Lib/sqlite3/test/factory.py b/Lib/sqlite3/test/factory.py index f103211ed9f..95dd24bdfad 100644 --- a/Lib/sqlite3/test/factory.py +++ b/Lib/sqlite3/test/factory.py @@ -98,16 +98,14 @@ class RowFactoryTests(unittest.TestCase): def CheckSqliteRowIndex(self): self.con.row_factory = sqlite.Row - row = self.con.execute("select 1 as a, 2 as b").fetchone() + row = self.con.execute("select 1 as a_1, 2 as b").fetchone() self.assertIsInstance(row, sqlite.Row) - col1, col2 = row["a"], row["b"] - self.assertEqual(col1, 1, "by name: wrong result for column 'a'") - self.assertEqual(col2, 2, "by name: wrong result for column 'a'") + self.assertEqual(row["a_1"], 1, "by name: wrong result for column 'a_1'") + self.assertEqual(row["b"], 2, "by name: wrong result for column 'b'") - col1, col2 = row["A"], row["B"] - self.assertEqual(col1, 1, "by name: wrong result for column 'A'") - self.assertEqual(col2, 2, "by name: wrong result for column 'B'") + self.assertEqual(row["A_1"], 1, "by name: wrong result for column 'A_1'") + self.assertEqual(row["B"], 2, "by name: wrong result for column 'B'") self.assertEqual(row[0], 1, "by index: wrong result for column 0") self.assertEqual(row[1], 2, "by index: wrong result for column 1") @@ -116,6 +114,10 @@ class RowFactoryTests(unittest.TestCase): with self.assertRaises(IndexError): row['c'] + with self.assertRaises(IndexError): + row['a_\x11'] + with self.assertRaises(IndexError): + row['a\x7f1'] with self.assertRaises(IndexError): row[2] with self.assertRaises(IndexError): @@ -123,6 +125,15 @@ class RowFactoryTests(unittest.TestCase): with self.assertRaises(IndexError): row[2**1000] + def CheckSqliteRowIndexUnicode(self): + self.con.row_factory = sqlite.Row + row = self.con.execute("select 1 as \xff").fetchone() + self.assertEqual(row["\xff"], 1) + with self.assertRaises(IndexError): + row['\u0178'] + with self.assertRaises(IndexError): + row['\xdf'] + def CheckSqliteRowSlice(self): # A sqlite.Row can be sliced like a list. self.con.row_factory = sqlite.Row diff --git a/Misc/NEWS.d/next/Library/2019-09-16-19-12-57.bpo-38185.zYWppY.rst b/Misc/NEWS.d/next/Library/2019-09-16-19-12-57.bpo-38185.zYWppY.rst new file mode 100644 index 00000000000..2260db68e8c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-09-16-19-12-57.bpo-38185.zYWppY.rst @@ -0,0 +1 @@ +Fixed case-insensitive string comparison in :class:`sqlite3.Row` indexing. diff --git a/Modules/_sqlite/row.c b/Modules/_sqlite/row.c index 758518a8ff8..4b47108278a 100644 --- a/Modules/_sqlite/row.c +++ b/Modules/_sqlite/row.c @@ -76,16 +76,38 @@ PyObject* pysqlite_row_item(pysqlite_Row* self, Py_ssize_t idx) return item; } +static int +equal_ignore_case(PyObject *left, PyObject *right) +{ + int eq = PyObject_RichCompareBool(left, right, Py_EQ); + if (eq) { /* equal or error */ + return eq; + } + if (!PyUnicode_Check(left) || !PyUnicode_Check(right)) { + return 0; + } + if (!PyUnicode_IS_ASCII(left) || !PyUnicode_IS_ASCII(right)) { + return 0; + } + + Py_ssize_t len = PyUnicode_GET_LENGTH(left); + if (PyUnicode_GET_LENGTH(right) != len) { + return 0; + } + const Py_UCS1 *p1 = PyUnicode_1BYTE_DATA(left); + const Py_UCS1 *p2 = PyUnicode_1BYTE_DATA(right); + for (; len; len--, p1++, p2++) { + if (Py_TOLOWER(*p1) != Py_TOLOWER(*p2)) { + return 0; + } + } + return 1; +} + PyObject* pysqlite_row_subscript(pysqlite_Row* self, PyObject* idx) { Py_ssize_t _idx; - const char *key; Py_ssize_t nitems, i; - const char *compare_key; - - const char *p1; - const char *p2; - PyObject* item; if (PyLong_Check(idx)) { @@ -98,44 +120,22 @@ PyObject* pysqlite_row_subscript(pysqlite_Row* self, PyObject* idx) Py_XINCREF(item); return item; } else if (PyUnicode_Check(idx)) { - key = PyUnicode_AsUTF8(idx); - if (key == NULL) - return NULL; - nitems = PyTuple_Size(self->description); for (i = 0; i < nitems; i++) { PyObject *obj; obj = PyTuple_GET_ITEM(self->description, i); obj = PyTuple_GET_ITEM(obj, 0); - compare_key = PyUnicode_AsUTF8(obj); - if (!compare_key) { + int eq = equal_ignore_case(idx, obj); + if (eq < 0) { return NULL; } - - p1 = key; - p2 = compare_key; - - while (1) { - if ((*p1 == (char)0) || (*p2 == (char)0)) { - break; - } - - if ((*p1 | 0x20) != (*p2 | 0x20)) { - break; - } - - p1++; - p2++; - } - - if ((*p1 == (char)0) && (*p2 == (char)0)) { + if (eq) { /* found item */ item = PyTuple_GetItem(self->data, i); Py_INCREF(item); return item; } - } PyErr_SetString(PyExc_IndexError, "No item with that key");