From 7f6d38273ea5f52666acb10ad732f0b562aa3c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=A9zard?= Date: Thu, 13 Apr 2017 15:10:40 +0200 Subject: [PATCH] Unquote user:password in hosts (#569) Before turning them into an authentication header, the user or password supplied in hosts must be URL decoded. The RFC https://tools.ietf.org/html/rfc3986 describes userinfo like: userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) and python documentation about urlparse says: The components are not broken up in smaller parts (for example, the network location is a single string), and % escapes are not expanded. It is the caller job to unquote them before using them. For instance, a character like "]" cannot be supplied in a host or urllib will incorrectly interpret it using ipv6 syntax. Fix #568 --- elasticsearch/client/__init__.py | 5 +++-- elasticsearch/compat.py | 4 ++-- test_elasticsearch/test_client/__init__.py | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/elasticsearch/client/__init__.py b/elasticsearch/client/__init__.py index c735b70a..488cf8be 100644 --- a/elasticsearch/client/__init__.py +++ b/elasticsearch/client/__init__.py @@ -3,7 +3,7 @@ import logging from ..transport import Transport from ..exceptions import TransportError -from ..compat import string_types, urlparse +from ..compat import string_types, urlparse, unquote from .indices import IndicesClient from .ingest import IngestClient from .cluster import ClusterClient @@ -49,7 +49,8 @@ def _normalize_hosts(hosts): h['scheme'] = parsed_url.scheme if parsed_url.username or parsed_url.password: - h['http_auth'] = '%s:%s' % (parsed_url.username, parsed_url.password) + h['http_auth'] = '%s:%s' % (unquote(parsed_url.username), + unquote(parsed_url.password)) if parsed_url.path and parsed_url.path != '/': h['url_prefix'] = parsed_url.path diff --git a/elasticsearch/compat.py b/elasticsearch/compat.py index deee3c52..a5b615d2 100644 --- a/elasticsearch/compat.py +++ b/elasticsearch/compat.py @@ -4,10 +4,10 @@ PY2 = sys.version_info[0] == 2 if PY2: string_types = basestring, - from urllib import quote_plus, urlencode + from urllib import quote_plus, urlencode, unquote from urlparse import urlparse from itertools import imap as map else: string_types = str, bytes - from urllib.parse import quote_plus, urlencode, urlparse + from urllib.parse import quote_plus, urlencode, urlparse, unquote map = map diff --git a/test_elasticsearch/test_client/__init__.py b/test_elasticsearch/test_client/__init__.py index 4bf2978c..ec01145e 100644 --- a/test_elasticsearch/test_client/__init__.py +++ b/test_elasticsearch/test_client/__init__.py @@ -13,8 +13,8 @@ class TestNormalizeHosts(TestCase): def test_strings_are_parsed_for_port_and_user(self): self.assertEquals( - [{"host": "elastic.co", "port": 42}, {"host": "elastic.co", "http_auth": "user:secret"}], - _normalize_hosts(["elastic.co:42", "user:secret@elastic.co"]) + [{"host": "elastic.co", "port": 42}, {"host": "elastic.co", "http_auth": "user:secre]"}], + _normalize_hosts(["elastic.co:42", "user:secre%5D@elastic.co"]) ) def test_strings_are_parsed_for_scheme(self):