Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unquote connection string components properly #472

Merged
merged 1 commit into from
Oct 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions asyncpg/connect_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def _validate_port_spec(hosts, port):
return port


def _parse_hostlist(hostlist, port):
def _parse_hostlist(hostlist, port, *, unquote=False):
if ',' in hostlist:
# A comma-separated list of host addresses.
hostspecs = hostlist.split(',')
Expand Down Expand Up @@ -185,9 +185,14 @@ def _parse_hostlist(hostlist, port):
addr = hostspec
hostspec_port = ''

if unquote:
addr = urllib.parse.unquote(addr)

hosts.append(addr)
if not port:
if hostspec_port:
if unquote:
hostspec_port = urllib.parse.unquote(hostspec_port)
hostlist_ports.append(int(hostspec_port))
else:
hostlist_ports.append(default_port[i])
Expand All @@ -213,25 +218,34 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user,
'invalid DSN: scheme is expected to be either '
'"postgresql" or "postgres", got {!r}'.format(parsed.scheme))

if not host and parsed.netloc:
if parsed.netloc:
if '@' in parsed.netloc:
auth, _, hostspec = parsed.netloc.partition('@')
dsn_auth, _, dsn_hostspec = parsed.netloc.partition('@')
else:
hostspec = parsed.netloc
dsn_hostspec = parsed.netloc
dsn_auth = ''
else:
dsn_auth = dsn_hostspec = ''

if dsn_auth:
dsn_user, _, dsn_password = dsn_auth.partition(':')
else:
dsn_user = dsn_password = ''

if hostspec:
host, port = _parse_hostlist(hostspec, port)
if not host and dsn_hostspec:
host, port = _parse_hostlist(dsn_hostspec, port, unquote=True)

if parsed.path and database is None:
database = parsed.path
if database.startswith('/'):
database = database[1:]
dsn_database = parsed.path
if dsn_database.startswith('/'):
dsn_database = dsn_database[1:]
database = urllib.parse.unquote(dsn_database)

if parsed.username and user is None:
user = parsed.username
if user is None and dsn_user:
user = urllib.parse.unquote(dsn_user)

if parsed.password and password is None:
password = parsed.password
if password is None and dsn_password:
password = urllib.parse.unquote(dsn_password)

if parsed.query:
query = urllib.parse.parse_qs(parsed.query, strict_parsing=True)
Expand Down
35 changes: 35 additions & 0 deletions tests/test_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,41 @@ class TestConnectParams(tb.TestCase):
'database': 'dbname'})
},

{
'dsn': 'postgresql://us%40r:p%40ss@h%40st1,h%40st2:543%33/d%62',
'result': (
[('h@st1', 5432), ('h@st2', 5433)],
{
'user': 'us@r',
'password': 'p@ss',
'database': 'db',
}
)
},

{
'dsn': 'postgresql://user:p@ss@host/db',
'result': (
[('ss@host', 5432)],
{
'user': 'user',
'password': 'p',
'database': 'db',
}
)
},

{
'dsn': 'postgresql:///d%62?user=us%40r&host=h%40st&port=543%33',
'result': (
[('h@st', 5433)],
{
'user': 'us@r',
'database': 'db',
}
)
},

{
'dsn': 'pq:///dbname?host=/unix_sock/test&user=spam',
'error': (ValueError, 'invalid DSN')
Expand Down