]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Improve escaping in pysqlcipher
authorFederico Caselli <cfederico87@gmail.com>
Mon, 13 Apr 2026 20:26:48 +0000 (22:26 +0200)
committerFederico Caselli <cfederico87@gmail.com>
Mon, 13 Apr 2026 20:26:48 +0000 (22:26 +0200)
Escape key and pragma values when utilizing the pysqlcipher dialect.

Fixes: #13230
Change-Id: I7583577a3e00e2f2986e50f32136a9ef005eb28a

doc/build/changelog/unreleased_20/13230.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/sqlite/pysqlcipher.py
test/dialect/sqlite/test_dialect.py

diff --git a/doc/build/changelog/unreleased_20/13230.rst b/doc/build/changelog/unreleased_20/13230.rst
new file mode 100644 (file)
index 0000000..a9cb60e
--- /dev/null
@@ -0,0 +1,5 @@
+.. change::
+    :tags: bug, sqlite
+    :tickets: 13230
+
+    Escape key and pragma values when utilizing the pysqlcipher dialect.
index 1a4c251a47698e14428ff852d5c6b8ed1a5bd37d..7f2c3d4b796921669f928c89077a94a22dae86c7 100644 (file)
@@ -131,16 +131,20 @@ class SQLiteDialect_pysqlcipher(SQLiteDialect_pysqlite):
         # pull the info we need from the URL early.  Even though URL
         # is immutable, we don't want any in-place changes to the URL
         # to affect things
-        passphrase = url.password or ""
-        url_query = dict(url.query)
+        ip = self.identifier_preparer
+        passphrase = ip.quote_identifier(url.password or "")
+        query_pragmas = {
+            prag: ip.quote_identifier(url.query[prag])
+            for prag in self.pragmas
+            if url.query.get(prag) is not None
+        }
 
         def on_connect(conn):
             cursor = conn.cursor()
-            cursor.execute('pragma key="%s"' % passphrase)
-            for prag in self.pragmas:
-                value = url_query.get(prag, None)
-                if value is not None:
-                    cursor.execute('pragma %s="%s"' % (prag, value))
+            cursor.execute(f"pragma key={passphrase}")
+            for prag, value in query_pragmas.items():
+                cursor.execute(f"pragma {prag}={value}")
+            print(query_pragmas)
             cursor.close()
 
             if super_on_connect:
index 27392fc0790d5894b4d6144c5956ef6756231159..5deb799ea74702933897ec3a38d21732a9f99c40 100644 (file)
@@ -1,6 +1,7 @@
 """SQLite-specific tests."""
 
 import os
+from tempfile import mkstemp
 
 from sqlalchemy import and_
 from sqlalchemy import Column
@@ -197,12 +198,35 @@ class DialectTest(
     @testing.only_on("sqlite+pysqlcipher")
     def test_pysqlcipher_connects(self):
         """test #6586"""
-        str_url = str(testing.db.url)
+        f, name = mkstemp()
+        str_url = str(testing.db.url.set(database=name))
         e = create_engine(str_url)
 
         with e.connect() as conn:
             eq_(conn.scalar(text("select 1")), 1)
 
+        e.dispose()
+        os.close(f)
+        os.unlink(name)
+
+    @testing.only_on("sqlite+pysqlcipher")
+    def test_pysqlcipher_escaped(self):
+        """test #6586"""
+        f, name = mkstemp()
+        new_url = testing.db.url.set(
+            password='tes"ting',
+            database=name,
+            query={"cipher": 'aes-"select 1', "kdf_iter": "64000"},
+        )
+        e = create_engine(new_url)
+
+        with e.connect() as conn:
+            eq_(conn.scalar(text("select 1")), 1)
+
+        e.dispose()
+        os.close(f)
+        os.unlink(name)
+
     @testing.provide_metadata
     def test_extra_reserved_words(self, connection):
         """Tests reserved words in identifiers.