We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
decode_auth function does not handle utf-8 logins or password
HI

I have found that the function **decode_auth** (line 96, [file](https://github.com/docker/docker-py/blob/master/docker/auth/auth.py)) fails when decoding UTF-8 passwords from the .dockercfg file, and **load_config** returning an empty config.

I have checked and docker hub can handle UTF-8 passwords, this code proves that:

``` python
# coding=utf-8
from docker import Client
cred = { 'username': <user>, 'password': <utf-8 password> }
c = Client(base_url='unix://var/run/docker.sock')
res = c.pull(repository='<private container>', tag='latest', auth_config=cred)
print(res)
```

Thank you


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `docker/auth/auth.py`
Content:
```
1 # Copyright 2013 dotCloud inc.
2 
3 #    Licensed under the Apache License, Version 2.0 (the "License");
4 #    you may not use this file except in compliance with the License.
5 #    You may obtain a copy of the License at
6 
7 #        http://www.apache.org/licenses/LICENSE-2.0
8 
9 #    Unless required by applicable law or agreed to in writing, software
10 #    distributed under the License is distributed on an "AS IS" BASIS,
11 #    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 #    See the License for the specific language governing permissions and
13 #    limitations under the License.
14 
15 import base64
16 import json
17 import logging
18 import os
19 import warnings
20 
21 import six
22 
23 from .. import constants
24 from .. import errors
25 
26 INDEX_NAME = 'index.docker.io'
27 INDEX_URL = 'https://{0}/v1/'.format(INDEX_NAME)
28 DOCKER_CONFIG_FILENAME = os.path.join('.docker', 'config.json')
29 LEGACY_DOCKER_CONFIG_FILENAME = '.dockercfg'
30 
31 log = logging.getLogger(__name__)
32 
33 
34 def resolve_repository_name(repo_name, insecure=False):
35     if insecure:
36         warnings.warn(
37             constants.INSECURE_REGISTRY_DEPRECATION_WARNING.format(
38                 'resolve_repository_name()'
39             ), DeprecationWarning
40         )
41 
42     if '://' in repo_name:
43         raise errors.InvalidRepository(
44             'Repository name cannot contain a scheme ({0})'.format(repo_name))
45     parts = repo_name.split('/', 1)
46     if '.' not in parts[0] and ':' not in parts[0] and parts[0] != 'localhost':
47         # This is a docker index repo (ex: foo/bar or ubuntu)
48         return INDEX_NAME, repo_name
49     if len(parts) < 2:
50         raise errors.InvalidRepository(
51             'Invalid repository name ({0})'.format(repo_name))
52 
53     if 'index.docker.io' in parts[0]:
54         raise errors.InvalidRepository(
55             'Invalid repository name, try "{0}" instead'.format(parts[1])
56         )
57 
58     return parts[0], parts[1]
59 
60 
61 def resolve_authconfig(authconfig, registry=None):
62     """
63     Returns the authentication data from the given auth configuration for a
64     specific registry. As with the Docker client, legacy entries in the config
65     with full URLs are stripped down to hostnames before checking for a match.
66     Returns None if no match was found.
67     """
68     # Default to the public index server
69     registry = convert_to_hostname(registry) if registry else INDEX_NAME
70     log.debug("Looking for auth entry for {0}".format(repr(registry)))
71 
72     if registry in authconfig:
73         log.debug("Found {0}".format(repr(registry)))
74         return authconfig[registry]
75 
76     for key, config in six.iteritems(authconfig):
77         if convert_to_hostname(key) == registry:
78             log.debug("Found {0}".format(repr(key)))
79             return config
80 
81     log.debug("No entry found")
82     return None
83 
84 
85 def convert_to_hostname(url):
86     return url.replace('http://', '').replace('https://', '').split('/', 1)[0]
87 
88 
89 def encode_auth(auth_info):
90     return base64.b64encode(auth_info.get('username', '') + b':' +
91                             auth_info.get('password', ''))
92 
93 
94 def decode_auth(auth):
95     if isinstance(auth, six.string_types):
96         auth = auth.encode('ascii')
97     s = base64.b64decode(auth)
98     login, pwd = s.split(b':', 1)
99     return login.decode('ascii'), pwd.decode('ascii')
100 
101 
102 def encode_header(auth):
103     auth_json = json.dumps(auth).encode('ascii')
104     return base64.urlsafe_b64encode(auth_json)
105 
106 
107 def parse_auth(entries):
108     """
109     Parses authentication entries
110 
111     Args:
112       entries: Dict of authentication entries.
113 
114     Returns:
115       Authentication registry.
116     """
117 
118     conf = {}
119     for registry, entry in six.iteritems(entries):
120         username, password = decode_auth(entry['auth'])
121         log.debug(
122             'Found entry (registry={0}, username={1})'
123             .format(repr(registry), repr(username))
124         )
125         conf[registry] = {
126             'username': username,
127             'password': password,
128             'email': entry['email'],
129             'serveraddress': registry,
130         }
131     return conf
132 
133 
134 def find_config_file(config_path=None):
135     environment_path = os.path.join(
136         os.environ.get('DOCKER_CONFIG'),
137         os.path.basename(DOCKER_CONFIG_FILENAME)
138     ) if os.environ.get('DOCKER_CONFIG') else None
139 
140     paths = [
141         config_path,  # 1
142         environment_path,  # 2
143         os.path.join(os.path.expanduser('~'), DOCKER_CONFIG_FILENAME),  # 3
144         os.path.join(
145             os.path.expanduser('~'), LEGACY_DOCKER_CONFIG_FILENAME
146         )  # 4
147     ]
148 
149     for path in paths:
150         if path and os.path.exists(path):
151             return path
152     return None
153 
154 
155 def load_config(config_path=None):
156     """
157     Loads authentication data from a Docker configuration file in the given
158     root directory or if config_path is passed use given path.
159     Lookup priority:
160         explicit config_path parameter > DOCKER_CONFIG environment variable >
161         ~/.docker/config.json > ~/.dockercfg
162     """
163 
164     config_file = find_config_file(config_path)
165 
166     if not config_file:
167         log.debug("File doesn't exist")
168         return {}
169 
170     try:
171         with open(config_file) as f:
172             data = json.load(f)
173             if data.get('auths'):
174                 log.debug("Found 'auths' section")
175                 return parse_auth(data['auths'])
176             else:
177                 log.debug("Couldn't find 'auths' section")
178                 f.seek(0)
179                 return parse_auth(json.load(f))
180     except (IOError, KeyError, ValueError) as e:
181         # Likely missing new Docker config file or it's in an
182         # unknown format, continue to attempt to read old location
183         # and format.
184         log.debug(e)
185 
186     log.debug("Attempting to parse legacy auth file format")
187     try:
188         data = []
189         with open(config_file) as f:
190             for line in f.readlines():
191                 data.append(line.strip().split(' = ')[1])
192             if len(data) < 2:
193                 # Not enough data
194                 raise errors.InvalidConfigFile(
195                     'Invalid or empty configuration file!'
196                 )
197 
198         username, password = decode_auth(data[0])
199         return {
200             INDEX_NAME: {
201                 'username': username,
202                 'password': password,
203                 'email': data[1],
204                 'serveraddress': INDEX_URL,
205             }
206         }
207     except Exception as e:
208         log.debug(e)
209         pass
210 
211     log.debug("All parsing attempts failed - returning empty config")
212     return {}
213
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/docker/auth/auth.py b/docker/auth/auth.py
--- a/docker/auth/auth.py
+++ b/docker/auth/auth.py
@@ -96,7 +96,7 @@
         auth = auth.encode('ascii')
     s = base64.b64decode(auth)
     login, pwd = s.split(b':', 1)
-    return login.decode('ascii'), pwd.decode('ascii')
+    return login.decode('utf8'), pwd.decode('utf8')
 
 
 def encode_header(auth):

{"golden_diff": "diff --git a/docker/auth/auth.py b/docker/auth/auth.py\n--- a/docker/auth/auth.py\n+++ b/docker/auth/auth.py\n@@ -96,7 +96,7 @@\n         auth = auth.encode('ascii')\n     s = base64.b64decode(auth)\n     login, pwd = s.split(b':', 1)\n-    return login.decode('ascii'), pwd.decode('ascii')\n+    return login.decode('utf8'), pwd.decode('utf8')\n \n \n def encode_header(auth):\n", "issue": "decode_auth function does not handle utf-8 logins or password\nHI\n\nI have found that the function **decode_auth** (line 96, [file](https://github.com/docker/docker-py/blob/master/docker/auth/auth.py)) fails when decoding UTF-8 passwords from the .dockercfg file, and **load_config** returning an empty config.\n\nI have checked and docker hub can handle UTF-8 passwords, this code proves that:\n\n``` python\n# coding=utf-8\nfrom docker import Client\ncred = { 'username': <user>, 'password': <utf-8 password> }\nc = Client(base_url='unix://var/run/docker.sock')\nres = c.pull(repository='<private container>', tag='latest', auth_config=cred)\nprint(res)\n```\n\nThank you\n\n", "before_files": [{"content": "# Copyright 2013 dotCloud inc.\n\n#    Licensed under the Apache License, Version 2.0 (the \"License\");\n#    you may not use this file except in compliance with the License.\n#    You may obtain a copy of the License at\n\n#        http://www.apache.org/licenses/LICENSE-2.0\n\n#    Unless required by applicable law or agreed to in writing, software\n#    distributed under the License is distributed on an \"AS IS\" BASIS,\n#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#    See the License for the specific language governing permissions and\n#    limitations under the License.\n\nimport base64\nimport json\nimport logging\nimport os\nimport warnings\n\nimport six\n\nfrom .. import constants\nfrom .. import errors\n\nINDEX_NAME = 'index.docker.io'\nINDEX_URL = 'https://{0}/v1/'.format(INDEX_NAME)\nDOCKER_CONFIG_FILENAME = os.path.join('.docker', 'config.json')\nLEGACY_DOCKER_CONFIG_FILENAME = '.dockercfg'\n\nlog = logging.getLogger(__name__)\n\n\ndef resolve_repository_name(repo_name, insecure=False):\n    if insecure:\n        warnings.warn(\n            constants.INSECURE_REGISTRY_DEPRECATION_WARNING.format(\n                'resolve_repository_name()'\n            ), DeprecationWarning\n        )\n\n    if '://' in repo_name:\n        raise errors.InvalidRepository(\n            'Repository name cannot contain a scheme ({0})'.format(repo_name))\n    parts = repo_name.split('/', 1)\n    if '.' not in parts[0] and ':' not in parts[0] and parts[0] != 'localhost':\n        # This is a docker index repo (ex: foo/bar or ubuntu)\n        return INDEX_NAME, repo_name\n    if len(parts) < 2:\n        raise errors.InvalidRepository(\n            'Invalid repository name ({0})'.format(repo_name))\n\n    if 'index.docker.io' in parts[0]:\n        raise errors.InvalidRepository(\n            'Invalid repository name, try \"{0}\" instead'.format(parts[1])\n        )\n\n    return parts[0], parts[1]\n\n\ndef resolve_authconfig(authconfig, registry=None):\n    \"\"\"\n    Returns the authentication data from the given auth configuration for a\n    specific registry. As with the Docker client, legacy entries in the config\n    with full URLs are stripped down to hostnames before checking for a match.\n    Returns None if no match was found.\n    \"\"\"\n    # Default to the public index server\n    registry = convert_to_hostname(registry) if registry else INDEX_NAME\n    log.debug(\"Looking for auth entry for {0}\".format(repr(registry)))\n\n    if registry in authconfig:\n        log.debug(\"Found {0}\".format(repr(registry)))\n        return authconfig[registry]\n\n    for key, config in six.iteritems(authconfig):\n        if convert_to_hostname(key) == registry:\n            log.debug(\"Found {0}\".format(repr(key)))\n            return config\n\n    log.debug(\"No entry found\")\n    return None\n\n\ndef convert_to_hostname(url):\n    return url.replace('http://', '').replace('https://', '').split('/', 1)[0]\n\n\ndef encode_auth(auth_info):\n    return base64.b64encode(auth_info.get('username', '') + b':' +\n                            auth_info.get('password', ''))\n\n\ndef decode_auth(auth):\n    if isinstance(auth, six.string_types):\n        auth = auth.encode('ascii')\n    s = base64.b64decode(auth)\n    login, pwd = s.split(b':', 1)\n    return login.decode('ascii'), pwd.decode('ascii')\n\n\ndef encode_header(auth):\n    auth_json = json.dumps(auth).encode('ascii')\n    return base64.urlsafe_b64encode(auth_json)\n\n\ndef parse_auth(entries):\n    \"\"\"\n    Parses authentication entries\n\n    Args:\n      entries: Dict of authentication entries.\n\n    Returns:\n      Authentication registry.\n    \"\"\"\n\n    conf = {}\n    for registry, entry in six.iteritems(entries):\n        username, password = decode_auth(entry['auth'])\n        log.debug(\n            'Found entry (registry={0}, username={1})'\n            .format(repr(registry), repr(username))\n        )\n        conf[registry] = {\n            'username': username,\n            'password': password,\n            'email': entry['email'],\n            'serveraddress': registry,\n        }\n    return conf\n\n\ndef find_config_file(config_path=None):\n    environment_path = os.path.join(\n        os.environ.get('DOCKER_CONFIG'),\n        os.path.basename(DOCKER_CONFIG_FILENAME)\n    ) if os.environ.get('DOCKER_CONFIG') else None\n\n    paths = [\n        config_path,  # 1\n        environment_path,  # 2\n        os.path.join(os.path.expanduser('~'), DOCKER_CONFIG_FILENAME),  # 3\n        os.path.join(\n            os.path.expanduser('~'), LEGACY_DOCKER_CONFIG_FILENAME\n        )  # 4\n    ]\n\n    for path in paths:\n        if path and os.path.exists(path):\n            return path\n    return None\n\n\ndef load_config(config_path=None):\n    \"\"\"\n    Loads authentication data from a Docker configuration file in the given\n    root directory or if config_path is passed use given path.\n    Lookup priority:\n        explicit config_path parameter > DOCKER_CONFIG environment variable >\n        ~/.docker/config.json > ~/.dockercfg\n    \"\"\"\n\n    config_file = find_config_file(config_path)\n\n    if not config_file:\n        log.debug(\"File doesn't exist\")\n        return {}\n\n    try:\n        with open(config_file) as f:\n            data = json.load(f)\n            if data.get('auths'):\n                log.debug(\"Found 'auths' section\")\n                return parse_auth(data['auths'])\n            else:\n                log.debug(\"Couldn't find 'auths' section\")\n                f.seek(0)\n                return parse_auth(json.load(f))\n    except (IOError, KeyError, ValueError) as e:\n        # Likely missing new Docker config file or it's in an\n        # unknown format, continue to attempt to read old location\n        # and format.\n        log.debug(e)\n\n    log.debug(\"Attempting to parse legacy auth file format\")\n    try:\n        data = []\n        with open(config_file) as f:\n            for line in f.readlines():\n                data.append(line.strip().split(' = ')[1])\n            if len(data) < 2:\n                # Not enough data\n                raise errors.InvalidConfigFile(\n                    'Invalid or empty configuration file!'\n                )\n\n        username, password = decode_auth(data[0])\n        return {\n            INDEX_NAME: {\n                'username': username,\n                'password': password,\n                'email': data[1],\n                'serveraddress': INDEX_URL,\n            }\n        }\n    except Exception as e:\n        log.debug(e)\n        pass\n\n    log.debug(\"All parsing attempts failed - returning empty config\")\n    return {}\n", "path": "docker/auth/auth.py"}], "after_files": [{"content": "# Copyright 2013 dotCloud inc.\n\n#    Licensed under the Apache License, Version 2.0 (the \"License\");\n#    you may not use this file except in compliance with the License.\n#    You may obtain a copy of the License at\n\n#        http://www.apache.org/licenses/LICENSE-2.0\n\n#    Unless required by applicable law or agreed to in writing, software\n#    distributed under the License is distributed on an \"AS IS\" BASIS,\n#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#    See the License for the specific language governing permissions and\n#    limitations under the License.\n\nimport base64\nimport json\nimport logging\nimport os\nimport warnings\n\nimport six\n\nfrom .. import constants\nfrom .. import errors\n\nINDEX_NAME = 'index.docker.io'\nINDEX_URL = 'https://{0}/v1/'.format(INDEX_NAME)\nDOCKER_CONFIG_FILENAME = os.path.join('.docker', 'config.json')\nLEGACY_DOCKER_CONFIG_FILENAME = '.dockercfg'\n\nlog = logging.getLogger(__name__)\n\n\ndef resolve_repository_name(repo_name, insecure=False):\n    if insecure:\n        warnings.warn(\n            constants.INSECURE_REGISTRY_DEPRECATION_WARNING.format(\n                'resolve_repository_name()'\n            ), DeprecationWarning\n        )\n\n    if '://' in repo_name:\n        raise errors.InvalidRepository(\n            'Repository name cannot contain a scheme ({0})'.format(repo_name))\n    parts = repo_name.split('/', 1)\n    if '.' not in parts[0] and ':' not in parts[0] and parts[0] != 'localhost':\n        # This is a docker index repo (ex: foo/bar or ubuntu)\n        return INDEX_NAME, repo_name\n    if len(parts) < 2:\n        raise errors.InvalidRepository(\n            'Invalid repository name ({0})'.format(repo_name))\n\n    if 'index.docker.io' in parts[0]:\n        raise errors.InvalidRepository(\n            'Invalid repository name, try \"{0}\" instead'.format(parts[1])\n        )\n\n    return parts[0], parts[1]\n\n\ndef resolve_authconfig(authconfig, registry=None):\n    \"\"\"\n    Returns the authentication data from the given auth configuration for a\n    specific registry. As with the Docker client, legacy entries in the config\n    with full URLs are stripped down to hostnames before checking for a match.\n    Returns None if no match was found.\n    \"\"\"\n    # Default to the public index server\n    registry = convert_to_hostname(registry) if registry else INDEX_NAME\n    log.debug(\"Looking for auth entry for {0}\".format(repr(registry)))\n\n    if registry in authconfig:\n        log.debug(\"Found {0}\".format(repr(registry)))\n        return authconfig[registry]\n\n    for key, config in six.iteritems(authconfig):\n        if convert_to_hostname(key) == registry:\n            log.debug(\"Found {0}\".format(repr(key)))\n            return config\n\n    log.debug(\"No entry found\")\n    return None\n\n\ndef convert_to_hostname(url):\n    return url.replace('http://', '').replace('https://', '').split('/', 1)[0]\n\n\ndef encode_auth(auth_info):\n    return base64.b64encode(auth_info.get('username', '') + b':' +\n                            auth_info.get('password', ''))\n\n\ndef decode_auth(auth):\n    if isinstance(auth, six.string_types):\n        auth = auth.encode('ascii')\n    s = base64.b64decode(auth)\n    login, pwd = s.split(b':', 1)\n    return login.decode('utf8'), pwd.decode('utf8')\n\n\ndef encode_header(auth):\n    auth_json = json.dumps(auth).encode('ascii')\n    return base64.urlsafe_b64encode(auth_json)\n\n\ndef parse_auth(entries):\n    \"\"\"\n    Parses authentication entries\n\n    Args:\n      entries: Dict of authentication entries.\n\n    Returns:\n      Authentication registry.\n    \"\"\"\n\n    conf = {}\n    for registry, entry in six.iteritems(entries):\n        username, password = decode_auth(entry['auth'])\n        log.debug(\n            'Found entry (registry={0}, username={1})'\n            .format(repr(registry), repr(username))\n        )\n        conf[registry] = {\n            'username': username,\n            'password': password,\n            'email': entry['email'],\n            'serveraddress': registry,\n        }\n    return conf\n\n\ndef find_config_file(config_path=None):\n    environment_path = os.path.join(\n        os.environ.get('DOCKER_CONFIG'),\n        os.path.basename(DOCKER_CONFIG_FILENAME)\n    ) if os.environ.get('DOCKER_CONFIG') else None\n\n    paths = [\n        config_path,  # 1\n        environment_path,  # 2\n        os.path.join(os.path.expanduser('~'), DOCKER_CONFIG_FILENAME),  # 3\n        os.path.join(\n            os.path.expanduser('~'), LEGACY_DOCKER_CONFIG_FILENAME\n        )  # 4\n    ]\n\n    for path in paths:\n        if path and os.path.exists(path):\n            return path\n    return None\n\n\ndef load_config(config_path=None):\n    \"\"\"\n    Loads authentication data from a Docker configuration file in the given\n    root directory or if config_path is passed use given path.\n    Lookup priority:\n        explicit config_path parameter > DOCKER_CONFIG environment variable >\n        ~/.docker/config.json > ~/.dockercfg\n    \"\"\"\n\n    config_file = find_config_file(config_path)\n\n    if not config_file:\n        log.debug(\"File doesn't exist\")\n        return {}\n\n    try:\n        with open(config_file) as f:\n            data = json.load(f)\n            if data.get('auths'):\n                log.debug(\"Found 'auths' section\")\n                return parse_auth(data['auths'])\n            else:\n                log.debug(\"Couldn't find 'auths' section\")\n                f.seek(0)\n                return parse_auth(json.load(f))\n    except (IOError, KeyError, ValueError) as e:\n        # Likely missing new Docker config file or it's in an\n        # unknown format, continue to attempt to read old location\n        # and format.\n        log.debug(e)\n\n    log.debug(\"Attempting to parse legacy auth file format\")\n    try:\n        data = []\n        with open(config_file) as f:\n            for line in f.readlines():\n                data.append(line.strip().split(' = ')[1])\n            if len(data) < 2:\n                # Not enough data\n                raise errors.InvalidConfigFile(\n                    'Invalid or empty configuration file!'\n                )\n\n        username, password = decode_auth(data[0])\n        return {\n            INDEX_NAME: {\n                'username': username,\n                'password': password,\n                'email': data[1],\n                'serveraddress': INDEX_URL,\n            }\n        }\n    except Exception as e:\n        log.debug(e)\n        pass\n\n    log.debug(\"All parsing attempts failed - returning empty config\")\n    return {}\n", "path": "docker/auth/auth.py"}]}