From: Mike Bayer Date: Thu, 10 Nov 2016 22:08:06 +0000 (-0500) Subject: Quote URL tokens with semicolons for pyodbc, adodbapi X-Git-Tag: rel_1_1_4~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5fba7db9be7a03076d50051fb84dade31d55262e;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Quote URL tokens with semicolons for pyodbc, adodbapi 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 --- diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index b0a36080d9..0eac6c1b1c 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -106,6 +106,16 @@ 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 diff --git a/lib/sqlalchemy/connectors/pyodbc.py b/lib/sqlalchemy/connectors/pyodbc.py index 1811229d70..6478de582c 100644 --- a/lib/sqlalchemy/connectors/pyodbc.py +++ b/lib/sqlalchemy/connectors/pyodbc.py @@ -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): diff --git a/lib/sqlalchemy/dialects/mssql/adodbapi.py b/lib/sqlalchemy/dialects/mssql/adodbapi.py index 60fa25d348..a85ce5a57f 100644 --- a/lib/sqlalchemy/dialects/mssql/adodbapi.py +++ b/lib/sqlalchemy/dialects/mssql/adodbapi.py @@ -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: diff --git a/test/dialect/mssql/test_engine.py b/test/dialect/mssql/test_engine.py index 929afc8f9b..39562e0db6 100644 --- a/test/dialect/mssql/test_engine.py +++ b/test/dialect/mssql/test_engine.py @@ -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()