From: Mike Bayer Date: Sun, 24 Nov 2013 23:11:37 +0000 (-0500) Subject: - The :func:`.create_engine` routine and the related X-Git-Tag: rel_0_9_0~97 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2800e34710672b408fa4a7bdd6d58d63a7128f04;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - The :func:`.create_engine` routine and the related :func:`.make_url` function **no longer URL encode the password**. Database passwords that include characters like spaces, plus signs and anything else should now represent these characters directly, without any URL escaping. [ticket:2873] --- diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index f0d58f205a..571f11b2f9 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -14,6 +14,21 @@ .. changelog:: :version: 0.9.0b2 + .. change:: + :tags: bug, engine + :tickets: 2873 + + The :func:`.create_engine` routine and the related + :func:`.make_url` function **no longer URL encode the password**. + Database passwords that include characters like spaces, plus signs + and anything else should now represent these characters directly, + without any URL escaping. + + .. seealso:: + + :ref:`migration_2873` + + .. change:: :tags: bug, orm :tickets: 2872 diff --git a/doc/build/changelog/migration_09.rst b/doc/build/changelog/migration_09.rst index 4e9112f871..2d490e9122 100644 --- a/doc/build/changelog/migration_09.rst +++ b/doc/build/changelog/migration_09.rst @@ -9,7 +9,7 @@ What's New in SQLAlchemy 0.9? and SQLAlchemy version 0.9, which is expected for release in late 2013. - Document last updated: October 23, 2013 + Document last updated: November 24, 2013 Introduction ============ @@ -18,9 +18,8 @@ This guide introduces what's new in SQLAlchemy version 0.9, and also documents changes which affect users migrating their applications from the 0.8 series of SQLAlchemy to 0.9. -Version 0.9 is a faster-than-usual push from version 0.8, -featuring a more versatile codebase with regards to modern -Python versions. See :ref:`behavioral_changes_09` for +Please carefully review +:ref:`behavioral_changes_orm_09` and :ref:`behavioral_changes_core_09` for potentially backwards-incompatible changes. Platform Support @@ -47,7 +46,7 @@ in both Python 2 and Python 3 environments. :ticket:`2161` -.. _behavioral_changes_09: +.. _behavioral_changes_orm_09: Behavioral Changes - ORM ======================== @@ -468,10 +467,44 @@ This is a small change demonstrated as follows:: :ticket:`2787` +.. _behavioral_changes_core_09: Behavioral Changes - Core ========================= +.. _migration_2873: + +The "password" portion of a ``create_engine()`` URL is no longer URL encoded +---------------------------------------------------------------------------- + +For whatever reason, the Python function ``unquote_plus()`` was applied to the +"password" field of a URL, likely as a means of allowing the usage of escapes +(e.g. "%2F" or similar) to be used, and perhaps as some way of allowing spaces +to be present. However, this is not complaint with `RFC 1738 `_ +which has no reserved characters within the password field and does not specify +URL quoting - so the quote_plus routines are **no longer applied** to the password +field. + +Examples of URLs with characters such as colons, @ symbols, spaces, and plus signs +include:: + + # password: "pass word + other:words" + dbtype://user:pass word + other:words@host/dbname + + # password: "apples%2Foranges" + dbtype://username:apples%2Foranges@hostspec/database + + # password: "apples@oranges@@" + dbtype://username:apples@oranges@@@hostspec/database + + # password: '', username is "username@" + dbtype://username@:@hostspec/database + + +:ticket:`2873` + + + .. _migration_2850: A bindparam() construct with no type gets upgraded via copy when a type is available diff --git a/lib/sqlalchemy/engine/url.py b/lib/sqlalchemy/engine/url.py index 8f84ab0394..28c15299e5 100644 --- a/lib/sqlalchemy/engine/url.py +++ b/lib/sqlalchemy/engine/url.py @@ -67,8 +67,7 @@ class URL(object): if self.username is not None: s += self.username if self.password is not None: - s += ':' + ('***' if hide_password - else util.quote_plus(self.password)) + s += ':' + ('***' if hide_password else self.password) s += "@" if self.host is not None: if ':' in self.host: @@ -170,7 +169,7 @@ def _parse_rfc1738_args(name): (?P[\w\+]+):// (?: (?P[^:/]*) - (?::(?P[^/]*))? + (?::(?P.*))? @)? (?: (?: @@ -195,10 +194,6 @@ def _parse_rfc1738_args(name): query = None components['query'] = query - if components['password'] is not None: - components['password'] = \ - util.unquote_plus(components['password']) - ipv4host = components.pop('ipv4host') ipv6host = components.pop('ipv6host') components['host'] = ipv4host or ipv6host diff --git a/test/engine/test_parseconnect.py b/test/engine/test_parseconnect.py index 07ce96d5e0..d1ffe426d6 100644 --- a/test/engine/test_parseconnect.py +++ b/test/engine/test_parseconnect.py @@ -31,7 +31,7 @@ class ParseConnectTest(fixtures.TestBase): 'dbtype://', 'dbtype://username:password@/database', 'dbtype:////usr/local/_xtest@example.com/members.db', - 'dbtype://username:apples%2Foranges@hostspec/database', + 'dbtype://username:apples/oranges@hostspec/database', 'dbtype://username:password@[2001:da8:2004:1000:202:116:160:90]/database?foo=bar', 'dbtype://username:password@[2001:da8:2004:1000:202:116:160:90]:80/database?foo=bar' ): @@ -49,6 +49,28 @@ class ParseConnectTest(fixtures.TestBase): 'E:/work/src/LEM/db/hello.db', None), u.database eq_(str(u), text) + def test_rfc1738_password(self): + u = url.make_url("dbtype://user:pass word + other:words@host/dbname") + eq_(u.password, "pass word + other:words") + eq_(str(u), "dbtype://user:pass word + other:words@host/dbname") + + u = url.make_url('dbtype://username:apples%2Foranges@hostspec/database') + eq_(u.password, "apples%2Foranges") + eq_(str(u), 'dbtype://username:apples%2Foranges@hostspec/database') + + u = url.make_url('dbtype://username:apples@oranges@@@hostspec/database') + eq_(u.password, "apples@oranges@@") + eq_(str(u), 'dbtype://username:apples@oranges@@@hostspec/database') + + u = url.make_url('dbtype://username@:@hostspec/database') + eq_(u.password, '') + eq_(u.username, "username@") + eq_(str(u), 'dbtype://username@:@hostspec/database') + + u = url.make_url('dbtype://username:pass/word@hostspec/database') + eq_(u.password, 'pass/word') + eq_(str(u), 'dbtype://username:pass/word@hostspec/database') + class DialectImportTest(fixtures.TestBase): def test_import_base_dialects(self):