]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Quote URL tokens with semicolons for pyodbc, adodbapi
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 10 Nov 2016 22:08:06 +0000 (17:08 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 11 Nov 2016 18:30:25 +0000 (13:30 -0500)
Fixed bug in pyodbc dialect (as well as in the mostly non-working
adodbapi dialect) whereby a semicolon present in the password
or username fields could be interpreted as a separator for another
token; the values are now quoted when semicolons are present.

Change-Id: I5f99fd8db53ebf8e805e7d9d60bc09b8f1af603f
Fixes: #3762
doc/build/changelog/changelog_10.rst
lib/sqlalchemy/connectors/pyodbc.py
lib/sqlalchemy/dialects/mssql/adodbapi.py
test/dialect/mssql/test_engine.py

index b0a36080d9929104a1d712be7557eb599f0c3c1e..0eac6c1b1c973326ed5e83f8fb7afc07639fc8d6 100644 (file)
         fail to refresh when mapper properties or other ORM constructs were
         added to the mapper/class after these  accessors were first called.
 
+    .. change:: 3762
+        :tags: bug, mssql
+        :tickets: 3762
+        :versions: 1.1.4
+
+        Fixed bug in pyodbc dialect (as well as in the mostly non-working
+        adodbapi dialect) whereby a semicolon present in the password
+        or username fields could be interpreted as a separator for another
+        token; the values are now quoted when semicolons are present.
+
 .. changelog::
     :version: 1.0.15
     :released: September 1, 2016
index 1811229d70a304e8a4e6f11fcebabd373d23b425..6478de582c119de37fb5c91da1ce6c0565f40fce 100644 (file)
@@ -55,6 +55,7 @@ class PyODBCConnector(Connector):
         opts.update(url.query)
 
         keys = opts
+
         query = url.query
 
         connect_args = {}
@@ -65,6 +66,15 @@ class PyODBCConnector(Connector):
         if 'odbc_connect' in keys:
             connectors = [util.unquote_plus(keys.pop('odbc_connect'))]
         else:
+            def check_quote(token):
+                if ";" in str(token):
+                    token = "'%s'" % token
+                return token
+
+            keys = dict(
+                (k, check_quote(v)) for k, v in keys.items()
+            )
+
             dsn_connection = 'dsn' in keys or \
                 ('host' in keys and 'database' not in keys)
             if dsn_connection:
@@ -107,6 +117,7 @@ class PyODBCConnector(Connector):
                                   keys.pop("odbc_autotranslate"))
 
             connectors.extend(['%s=%s' % (k, v) for k, v in keys.items()])
+
         return [[";".join(connectors)], connect_args]
 
     def is_disconnect(self, e, connection, cursor):
index 60fa25d348657e39d77beec6b2818f52635c4fbf..a85ce5a57f2e568b0f39a5f11ff73674bb8dc877 100644 (file)
@@ -56,7 +56,14 @@ class MSDialect_adodbapi(MSDialect):
     )
 
     def create_connect_args(self, url):
-        keys = url.query
+        def check_quote(token):
+            if ";" in str(token):
+                token = "'%s'" % token
+            return token
+
+        keys = dict(
+            (k, check_quote(v)) for k, v in url.query.items()
+        )
 
         connectors = ["Provider=SQLOLEDB"]
         if 'port' in keys:
index 929afc8f9bcc0d3747bce6203c3a64d9af08d4ca..39562e0db660f621fcd42eadb109d35d30cd30f3 100644 (file)
@@ -2,7 +2,7 @@
 from sqlalchemy.testing import eq_, engines
 from sqlalchemy import *
 from sqlalchemy import exc
-from sqlalchemy.dialects.mssql import pyodbc, pymssql
+from sqlalchemy.dialects.mssql import pyodbc, pymssql, adodbapi
 from sqlalchemy.engine import url
 from sqlalchemy.testing import fixtures
 from sqlalchemy import testing
@@ -123,6 +123,49 @@ class ParseConnectTest(fixtures.TestBase):
         eq_([['DRIVER={SQL Server};Server=hostspec;Database=database;UI'
             'D=username;PWD=password'], {}], connection)
 
+    def test_pyodbc_token_injection(self):
+        token1 = "someuser%3BPORT%3D50001"
+        token2 = "somepw%3BPORT%3D50001"
+        token3 = "somehost%3BPORT%3D50001"
+        token4 = "somedb%3BPORT%3D50001"
+
+        u = url.make_url(
+            'mssql+pyodbc://%s:%s@%s/%s?driver=foob' % (
+                token1, token2, token3, token4
+            )
+        )
+        dialect = pyodbc.dialect()
+        connection = dialect.create_connect_args(u)
+        eq_(
+            [[
+                "DRIVER={foob};Server=somehost%3BPORT%3D50001;"
+                "Database=somedb%3BPORT%3D50001;UID='someuser;PORT=50001';"
+                "PWD='somepw;PORT=50001'"], {}],
+            connection
+        )
+
+    def test_adodbapi_token_injection(self):
+        token1 = "someuser%3BPORT%3D50001"
+        token2 = "somepw%3BPORT%3D50001"
+        token3 = "somehost%3BPORT%3D50001"
+        token4 = "someport%3BPORT%3D50001"
+
+        # this URL format is all wrong
+        u = url.make_url(
+            'mssql+adodbapi://@/?user=%s&password=%s&host=%s&port=%s' % (
+                token1, token2, token3, token4
+            )
+        )
+        dialect = adodbapi.dialect()
+        connection = dialect.create_connect_args(u)
+        eq_(
+            [["Provider=SQLOLEDB;"
+              "Data Source='somehost;PORT=50001', 'someport;PORT=50001';"
+              "Initial Catalog=None;User Id='someuser;PORT=50001';"
+              "Password='somepw;PORT=50001'"], {}],
+            connection
+        )
+
     def test_pymssql_port_setting(self):
         dialect = pymssql.dialect()