We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[bug] Conan client fails when server charset is specified.
I'm experimenting with implementing a conan server. While testing said server I ran into problems because my server fully qualifies the "Content-Type" HTTP header to "application/json; charset=utf-8". The conan client though errors when talking to that server with this error: `Response from remote is not json, but 'application/json; charset=utf-8'.`. JSON content requires utf-8 encoding and charset, which makes the addition in the content-type of the charset optional by the server. But regardless of what the charset is, utf-8 or ascii, clients need to handle there being a charset being specified. Which the conan client doesn't appear to be doing.

### Environment Details (include every applicable attribute)
  * Operating System+version: 5.11.14-1-MANJARO #1 SMP PREEMPT Wed Apr 14 08:25:29 UTC 2021 x86_64 GNU/Linux
  * Compiler+version: NA
  * Conan version: 1.35.1
  * Python version: 3.9.3

[bug] Conan client fails when server charset is specified.
I'm experimenting with implementing a conan server. While testing said server I ran into problems because my server fully qualifies the "Content-Type" HTTP header to "application/json; charset=utf-8". The conan client though errors when talking to that server with this error: `Response from remote is not json, but 'application/json; charset=utf-8'.`. JSON content requires utf-8 encoding and charset, which makes the addition in the content-type of the charset optional by the server. But regardless of what the charset is, utf-8 or ascii, clients need to handle there being a charset being specified. Which the conan client doesn't appear to be doing.

### Environment Details (include every applicable attribute)
  * Operating System+version: 5.11.14-1-MANJARO #1 SMP PREEMPT Wed Apr 14 08:25:29 UTC 2021 x86_64 GNU/Linux
  * Compiler+version: NA
  * Conan version: 1.35.1
  * Python version: 3.9.3


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `conans/client/rest/rest_client_common.py`
Content:
```
1 import json
2 
3 from requests.auth import AuthBase, HTTPBasicAuth
4 
5 from conans.client.rest import response_to_str
6 from conans.errors import (EXCEPTION_CODE_MAPPING, ConanException,
7                            AuthenticationException, RecipeNotFoundException,
8                            PackageNotFoundException)
9 from conans.model.ref import ConanFileReference
10 from conans.util.files import decode_text
11 from conans.util.log import logger
12 
13 
14 class JWTAuth(AuthBase):
15     """Attaches JWT Authentication to the given Request object."""
16 
17     def __init__(self, token):
18         self.token = token
19 
20     def __call__(self, request):
21         if self.token:
22             request.headers['Authorization'] = "Bearer %s" % str(self.token)
23         return request
24 
25 
26 def get_exception_from_error(error_code):
27     tmp = {v: k for k, v in EXCEPTION_CODE_MAPPING.items()  # All except NotFound
28            if k not in (RecipeNotFoundException, PackageNotFoundException)}
29     if error_code in tmp:
30         logger.debug("REST ERROR: %s" % str(tmp[error_code]))
31         return tmp[error_code]
32     else:
33         base_error = int(str(error_code)[0] + "00")
34         logger.debug("REST ERROR: %s" % str(base_error))
35         try:
36             return tmp[base_error]
37         except KeyError:
38             return None
39 
40 
41 def handle_return_deserializer(deserializer=None):
42     """Decorator for rest api methods.
43     Map exceptions and http return codes and deserialize if needed.
44 
45     deserializer: Function for deserialize values"""
46 
47     def handle_return(method):
48         def inner(*argc, **argv):
49             ret = method(*argc, **argv)
50             if ret.status_code != 200:
51                 ret.charset = "utf-8"  # To be able to access ret.text (ret.content are bytes)
52                 text = ret.text if ret.status_code != 404 else "404 Not found"
53                 raise get_exception_from_error(ret.status_code)(text)
54             return deserializer(ret.content) if deserializer else decode_text(ret.content)
55 
56         return inner
57 
58     return handle_return
59 
60 
61 class RestCommonMethods(object):
62 
63     def __init__(self, remote_url, token, custom_headers, output, requester, config, verify_ssl,
64                  artifacts_properties=None, matrix_params=False):
65         self.token = token
66         self.remote_url = remote_url
67         self.custom_headers = custom_headers
68         self._output = output
69         self.requester = requester
70         self._config = config
71         self.verify_ssl = verify_ssl
72         self._artifacts_properties = artifacts_properties
73         self._matrix_params = matrix_params
74 
75     @property
76     def auth(self):
77         return JWTAuth(self.token)
78 
79     @staticmethod
80     def _check_error_response(ret):
81         if ret.status_code == 401:
82             raise AuthenticationException("Wrong user or password")
83         # Cannot check content-type=text/html, conan server is doing it wrong
84         if not ret.ok or "html>" in str(ret.content):
85             raise ConanException("%s\n\nInvalid server response, check remote URL and "
86                                  "try again" % str(ret.content))
87 
88     def authenticate(self, user, password):
89         """Sends user + password to get:
90           - A plain response with a regular token (not supported refresh in the remote) and None
91         """
92         auth = HTTPBasicAuth(user, password)
93         url = self.router.common_authenticate()
94         logger.debug("REST: Authenticate to get access_token: %s" % url)
95         ret = self.requester.get(url, auth=auth, headers=self.custom_headers,
96                                  verify=self.verify_ssl)
97 
98         self._check_error_response(ret)
99         return decode_text(ret.content)
100 
101     def authenticate_oauth(self, user, password):
102         """Sends user + password to get:
103             - A json with an access_token and a refresh token (if supported in the remote)
104                     Artifactory >= 6.13.X
105         """
106         url = self.router.oauth_authenticate()
107         auth = HTTPBasicAuth(user, password)
108         headers = {}
109         headers.update(self.custom_headers)
110         headers["Content-type"] = "application/x-www-form-urlencoded"
111         logger.debug("REST: Authenticating with OAUTH: %s" % url)
112         ret = self.requester.post(url, auth=auth, headers=headers, verify=self.verify_ssl)
113         self._check_error_response(ret)
114 
115         data = ret.json()
116         access_token = data["access_token"]
117         refresh_token = data["refresh_token"]
118         logger.debug("REST: Obtained refresh and access tokens")
119         return access_token, refresh_token
120 
121     def refresh_token(self, token, refresh_token):
122         """Sends access_token and the refresh_token to get a pair of
123         access_token and refresh token
124 
125         Artifactory >= 6.13.X
126         """
127         url = self.router.oauth_authenticate()
128         logger.debug("REST: Refreshing Token: %s" % url)
129         headers = {}
130         headers.update(self.custom_headers)
131         headers["Content-type"] = "application/x-www-form-urlencoded"
132         payload = {'access_token': token, 'refresh_token': refresh_token,
133                    'grant_type': 'refresh_token'}
134         ret = self.requester.post(url, headers=headers, verify=self.verify_ssl, data=payload)
135         self._check_error_response(ret)
136 
137         data = ret.json()
138         if "access_token" not in data:
139             logger.debug("REST: unexpected data from server: {}".format(data))
140             raise ConanException("Error refreshing the token")
141 
142         new_access_token = data["access_token"]
143         new_refresh_token = data["refresh_token"]
144         logger.debug("REST: Obtained new refresh and access tokens")
145         return new_access_token, new_refresh_token
146 
147     @handle_return_deserializer()
148     def check_credentials(self):
149         """If token is not valid will raise AuthenticationException.
150         User will be asked for new user/pass"""
151         url = self.router.common_check_credentials()
152         logger.debug("REST: Check credentials: %s" % url)
153         ret = self.requester.get(url, auth=self.auth, headers=self.custom_headers,
154                                  verify=self.verify_ssl)
155         return ret
156 
157     def server_capabilities(self, user=None, password=None):
158         """Get information about the server: status, version, type and capabilities"""
159         url = self.router.ping()
160         logger.debug("REST: ping: %s" % url)
161         if user and password:
162             # This can happen in "conan user" cmd. Instead of empty token, use HttpBasic
163             auth = HTTPBasicAuth(user, password)
164         else:
165             auth = self.auth
166         ret = self.requester.get(url, auth=auth, headers=self.custom_headers, verify=self.verify_ssl)
167 
168         server_capabilities = ret.headers.get('X-Conan-Server-Capabilities', "")
169         if not server_capabilities and not ret.ok:
170             # Old Artifactory might return 401/403 without capabilities, we don't want
171             # to cache them #5687, so raise the exception and force authentication
172             raise get_exception_from_error(ret.status_code)(response_to_str(ret))
173 
174         return [cap.strip() for cap in server_capabilities.split(",") if cap]
175 
176     def get_json(self, url, data=None, headers=None):
177         req_headers = self.custom_headers.copy()
178         req_headers.update(headers or {})
179         if data:  # POST request
180             req_headers.update({'Content-type': 'application/json',
181                                 'Accept': 'application/json'})
182             logger.debug("REST: post: %s" % url)
183             response = self.requester.post(url, auth=self.auth, headers=req_headers,
184                                            verify=self.verify_ssl,
185                                            stream=True,
186                                            data=json.dumps(data))
187         else:
188             logger.debug("REST: get: %s" % url)
189             response = self.requester.get(url, auth=self.auth, headers=req_headers,
190                                           verify=self.verify_ssl,
191                                           stream=True)
192 
193         if response.status_code != 200:  # Error message is text
194             response.charset = "utf-8"  # To be able to access ret.text (ret.content are bytes)
195             raise get_exception_from_error(response.status_code)(response_to_str(response))
196 
197         content = decode_text(response.content)
198         content_type = response.headers.get("Content-Type")
199         if content_type != 'application/json':
200             raise ConanException("%s\n\nResponse from remote is not json, but '%s'"
201                                  % (content, content_type))
202 
203         try:  # This can fail, if some proxy returns 200 and an html message
204             result = json.loads(content)
205         except Exception:
206             raise ConanException("Remote responded with broken json: %s" % content)
207         if not isinstance(result, dict):
208             raise ConanException("Unexpected server response %s" % result)
209         return result
210 
211     def upload_recipe(self, ref, files_to_upload, deleted, retry, retry_wait):
212         if files_to_upload:
213             self._upload_recipe(ref, files_to_upload, retry, retry_wait)
214         if deleted:
215             self._remove_conanfile_files(ref, deleted)
216 
217     def get_recipe_snapshot(self, ref):
218         # this method is used only for UPLOADING, then it requires the credentials
219         # Check of credentials is done in the uploader
220         url = self.router.recipe_snapshot(ref)
221         snap = self._get_snapshot(url)
222         return snap
223 
224     def get_package_snapshot(self, pref):
225         # this method is also used to check the integrity of the package upstream
226         # while installing, so check_credentials is done in uploader.
227         url = self.router.package_snapshot(pref)
228         snap = self._get_snapshot(url)
229         return snap
230 
231     def upload_package(self, pref, files_to_upload, deleted, retry, retry_wait):
232         if files_to_upload:
233             self._upload_package(pref, files_to_upload, retry, retry_wait)
234         if deleted:
235             raise Exception("This shouldn't be happening, deleted files "
236                             "in local package present in remote: %s.\n Please, report it at "
237                             "https://github.com/conan-io/conan/issues " % str(deleted))
238 
239     def search(self, pattern=None, ignorecase=True):
240         """
241         the_files: dict with relative_path: content
242         """
243         url = self.router.search(pattern, ignorecase)
244         response = self.get_json(url)["results"]
245         return [ConanFileReference.loads(reference) for reference in response]
246 
247     def search_packages(self, ref, query):
248         """Client is filtering by the query"""
249         url = self.router.search_packages(ref, query)
250         package_infos = self.get_json(url)
251         return package_infos
252
```
--- 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/conans/client/rest/rest_client_common.py b/conans/client/rest/rest_client_common.py
--- a/conans/client/rest/rest_client_common.py
+++ b/conans/client/rest/rest_client_common.py
@@ -196,7 +196,7 @@
 
         content = decode_text(response.content)
         content_type = response.headers.get("Content-Type")
-        if content_type != 'application/json':
+        if content_type != 'application/json' and content_type != 'application/json; charset=utf-8':
             raise ConanException("%s\n\nResponse from remote is not json, but '%s'"
                                  % (content, content_type))

{"golden_diff": "diff --git a/conans/client/rest/rest_client_common.py b/conans/client/rest/rest_client_common.py\n--- a/conans/client/rest/rest_client_common.py\n+++ b/conans/client/rest/rest_client_common.py\n@@ -196,7 +196,7 @@\n \n         content = decode_text(response.content)\n         content_type = response.headers.get(\"Content-Type\")\n-        if content_type != 'application/json':\n+        if content_type != 'application/json' and content_type != 'application/json; charset=utf-8':\n             raise ConanException(\"%s\\n\\nResponse from remote is not json, but '%s'\"\n                                  % (content, content_type))\n", "issue": "[bug] Conan client fails when server charset is specified.\nI'm experimenting with implementing a conan server. While testing said server I ran into problems because my server fully qualifies the \"Content-Type\" HTTP header to \"application/json; charset=utf-8\". The conan client though errors when talking to that server with this error: `Response from remote is not json, but 'application/json; charset=utf-8'.`. JSON content requires utf-8 encoding and charset, which makes the addition in the content-type of the charset optional by the server. But regardless of what the charset is, utf-8 or ascii, clients need to handle there being a charset being specified. Which the conan client doesn't appear to be doing.\r\n\r\n### Environment Details (include every applicable attribute)\r\n  * Operating System+version: 5.11.14-1-MANJARO #1 SMP PREEMPT Wed Apr 14 08:25:29 UTC 2021 x86_64 GNU/Linux\r\n  * Compiler+version: NA\r\n  * Conan version: 1.35.1\r\n  * Python version: 3.9.3\r\n\n[bug] Conan client fails when server charset is specified.\nI'm experimenting with implementing a conan server. While testing said server I ran into problems because my server fully qualifies the \"Content-Type\" HTTP header to \"application/json; charset=utf-8\". The conan client though errors when talking to that server with this error: `Response from remote is not json, but 'application/json; charset=utf-8'.`. JSON content requires utf-8 encoding and charset, which makes the addition in the content-type of the charset optional by the server. But regardless of what the charset is, utf-8 or ascii, clients need to handle there being a charset being specified. Which the conan client doesn't appear to be doing.\r\n\r\n### Environment Details (include every applicable attribute)\r\n  * Operating System+version: 5.11.14-1-MANJARO #1 SMP PREEMPT Wed Apr 14 08:25:29 UTC 2021 x86_64 GNU/Linux\r\n  * Compiler+version: NA\r\n  * Conan version: 1.35.1\r\n  * Python version: 3.9.3\r\n\n", "before_files": [{"content": "import json\n\nfrom requests.auth import AuthBase, HTTPBasicAuth\n\nfrom conans.client.rest import response_to_str\nfrom conans.errors import (EXCEPTION_CODE_MAPPING, ConanException,\n                           AuthenticationException, RecipeNotFoundException,\n                           PackageNotFoundException)\nfrom conans.model.ref import ConanFileReference\nfrom conans.util.files import decode_text\nfrom conans.util.log import logger\n\n\nclass JWTAuth(AuthBase):\n    \"\"\"Attaches JWT Authentication to the given Request object.\"\"\"\n\n    def __init__(self, token):\n        self.token = token\n\n    def __call__(self, request):\n        if self.token:\n            request.headers['Authorization'] = \"Bearer %s\" % str(self.token)\n        return request\n\n\ndef get_exception_from_error(error_code):\n    tmp = {v: k for k, v in EXCEPTION_CODE_MAPPING.items()  # All except NotFound\n           if k not in (RecipeNotFoundException, PackageNotFoundException)}\n    if error_code in tmp:\n        logger.debug(\"REST ERROR: %s\" % str(tmp[error_code]))\n        return tmp[error_code]\n    else:\n        base_error = int(str(error_code)[0] + \"00\")\n        logger.debug(\"REST ERROR: %s\" % str(base_error))\n        try:\n            return tmp[base_error]\n        except KeyError:\n            return None\n\n\ndef handle_return_deserializer(deserializer=None):\n    \"\"\"Decorator for rest api methods.\n    Map exceptions and http return codes and deserialize if needed.\n\n    deserializer: Function for deserialize values\"\"\"\n\n    def handle_return(method):\n        def inner(*argc, **argv):\n            ret = method(*argc, **argv)\n            if ret.status_code != 200:\n                ret.charset = \"utf-8\"  # To be able to access ret.text (ret.content are bytes)\n                text = ret.text if ret.status_code != 404 else \"404 Not found\"\n                raise get_exception_from_error(ret.status_code)(text)\n            return deserializer(ret.content) if deserializer else decode_text(ret.content)\n\n        return inner\n\n    return handle_return\n\n\nclass RestCommonMethods(object):\n\n    def __init__(self, remote_url, token, custom_headers, output, requester, config, verify_ssl,\n                 artifacts_properties=None, matrix_params=False):\n        self.token = token\n        self.remote_url = remote_url\n        self.custom_headers = custom_headers\n        self._output = output\n        self.requester = requester\n        self._config = config\n        self.verify_ssl = verify_ssl\n        self._artifacts_properties = artifacts_properties\n        self._matrix_params = matrix_params\n\n    @property\n    def auth(self):\n        return JWTAuth(self.token)\n\n    @staticmethod\n    def _check_error_response(ret):\n        if ret.status_code == 401:\n            raise AuthenticationException(\"Wrong user or password\")\n        # Cannot check content-type=text/html, conan server is doing it wrong\n        if not ret.ok or \"html>\" in str(ret.content):\n            raise ConanException(\"%s\\n\\nInvalid server response, check remote URL and \"\n                                 \"try again\" % str(ret.content))\n\n    def authenticate(self, user, password):\n        \"\"\"Sends user + password to get:\n          - A plain response with a regular token (not supported refresh in the remote) and None\n        \"\"\"\n        auth = HTTPBasicAuth(user, password)\n        url = self.router.common_authenticate()\n        logger.debug(\"REST: Authenticate to get access_token: %s\" % url)\n        ret = self.requester.get(url, auth=auth, headers=self.custom_headers,\n                                 verify=self.verify_ssl)\n\n        self._check_error_response(ret)\n        return decode_text(ret.content)\n\n    def authenticate_oauth(self, user, password):\n        \"\"\"Sends user + password to get:\n            - A json with an access_token and a refresh token (if supported in the remote)\n                    Artifactory >= 6.13.X\n        \"\"\"\n        url = self.router.oauth_authenticate()\n        auth = HTTPBasicAuth(user, password)\n        headers = {}\n        headers.update(self.custom_headers)\n        headers[\"Content-type\"] = \"application/x-www-form-urlencoded\"\n        logger.debug(\"REST: Authenticating with OAUTH: %s\" % url)\n        ret = self.requester.post(url, auth=auth, headers=headers, verify=self.verify_ssl)\n        self._check_error_response(ret)\n\n        data = ret.json()\n        access_token = data[\"access_token\"]\n        refresh_token = data[\"refresh_token\"]\n        logger.debug(\"REST: Obtained refresh and access tokens\")\n        return access_token, refresh_token\n\n    def refresh_token(self, token, refresh_token):\n        \"\"\"Sends access_token and the refresh_token to get a pair of\n        access_token and refresh token\n\n        Artifactory >= 6.13.X\n        \"\"\"\n        url = self.router.oauth_authenticate()\n        logger.debug(\"REST: Refreshing Token: %s\" % url)\n        headers = {}\n        headers.update(self.custom_headers)\n        headers[\"Content-type\"] = \"application/x-www-form-urlencoded\"\n        payload = {'access_token': token, 'refresh_token': refresh_token,\n                   'grant_type': 'refresh_token'}\n        ret = self.requester.post(url, headers=headers, verify=self.verify_ssl, data=payload)\n        self._check_error_response(ret)\n\n        data = ret.json()\n        if \"access_token\" not in data:\n            logger.debug(\"REST: unexpected data from server: {}\".format(data))\n            raise ConanException(\"Error refreshing the token\")\n\n        new_access_token = data[\"access_token\"]\n        new_refresh_token = data[\"refresh_token\"]\n        logger.debug(\"REST: Obtained new refresh and access tokens\")\n        return new_access_token, new_refresh_token\n\n    @handle_return_deserializer()\n    def check_credentials(self):\n        \"\"\"If token is not valid will raise AuthenticationException.\n        User will be asked for new user/pass\"\"\"\n        url = self.router.common_check_credentials()\n        logger.debug(\"REST: Check credentials: %s\" % url)\n        ret = self.requester.get(url, auth=self.auth, headers=self.custom_headers,\n                                 verify=self.verify_ssl)\n        return ret\n\n    def server_capabilities(self, user=None, password=None):\n        \"\"\"Get information about the server: status, version, type and capabilities\"\"\"\n        url = self.router.ping()\n        logger.debug(\"REST: ping: %s\" % url)\n        if user and password:\n            # This can happen in \"conan user\" cmd. Instead of empty token, use HttpBasic\n            auth = HTTPBasicAuth(user, password)\n        else:\n            auth = self.auth\n        ret = self.requester.get(url, auth=auth, headers=self.custom_headers, verify=self.verify_ssl)\n\n        server_capabilities = ret.headers.get('X-Conan-Server-Capabilities', \"\")\n        if not server_capabilities and not ret.ok:\n            # Old Artifactory might return 401/403 without capabilities, we don't want\n            # to cache them #5687, so raise the exception and force authentication\n            raise get_exception_from_error(ret.status_code)(response_to_str(ret))\n\n        return [cap.strip() for cap in server_capabilities.split(\",\") if cap]\n\n    def get_json(self, url, data=None, headers=None):\n        req_headers = self.custom_headers.copy()\n        req_headers.update(headers or {})\n        if data:  # POST request\n            req_headers.update({'Content-type': 'application/json',\n                                'Accept': 'application/json'})\n            logger.debug(\"REST: post: %s\" % url)\n            response = self.requester.post(url, auth=self.auth, headers=req_headers,\n                                           verify=self.verify_ssl,\n                                           stream=True,\n                                           data=json.dumps(data))\n        else:\n            logger.debug(\"REST: get: %s\" % url)\n            response = self.requester.get(url, auth=self.auth, headers=req_headers,\n                                          verify=self.verify_ssl,\n                                          stream=True)\n\n        if response.status_code != 200:  # Error message is text\n            response.charset = \"utf-8\"  # To be able to access ret.text (ret.content are bytes)\n            raise get_exception_from_error(response.status_code)(response_to_str(response))\n\n        content = decode_text(response.content)\n        content_type = response.headers.get(\"Content-Type\")\n        if content_type != 'application/json':\n            raise ConanException(\"%s\\n\\nResponse from remote is not json, but '%s'\"\n                                 % (content, content_type))\n\n        try:  # This can fail, if some proxy returns 200 and an html message\n            result = json.loads(content)\n        except Exception:\n            raise ConanException(\"Remote responded with broken json: %s\" % content)\n        if not isinstance(result, dict):\n            raise ConanException(\"Unexpected server response %s\" % result)\n        return result\n\n    def upload_recipe(self, ref, files_to_upload, deleted, retry, retry_wait):\n        if files_to_upload:\n            self._upload_recipe(ref, files_to_upload, retry, retry_wait)\n        if deleted:\n            self._remove_conanfile_files(ref, deleted)\n\n    def get_recipe_snapshot(self, ref):\n        # this method is used only for UPLOADING, then it requires the credentials\n        # Check of credentials is done in the uploader\n        url = self.router.recipe_snapshot(ref)\n        snap = self._get_snapshot(url)\n        return snap\n\n    def get_package_snapshot(self, pref):\n        # this method is also used to check the integrity of the package upstream\n        # while installing, so check_credentials is done in uploader.\n        url = self.router.package_snapshot(pref)\n        snap = self._get_snapshot(url)\n        return snap\n\n    def upload_package(self, pref, files_to_upload, deleted, retry, retry_wait):\n        if files_to_upload:\n            self._upload_package(pref, files_to_upload, retry, retry_wait)\n        if deleted:\n            raise Exception(\"This shouldn't be happening, deleted files \"\n                            \"in local package present in remote: %s.\\n Please, report it at \"\n                            \"https://github.com/conan-io/conan/issues \" % str(deleted))\n\n    def search(self, pattern=None, ignorecase=True):\n        \"\"\"\n        the_files: dict with relative_path: content\n        \"\"\"\n        url = self.router.search(pattern, ignorecase)\n        response = self.get_json(url)[\"results\"]\n        return [ConanFileReference.loads(reference) for reference in response]\n\n    def search_packages(self, ref, query):\n        \"\"\"Client is filtering by the query\"\"\"\n        url = self.router.search_packages(ref, query)\n        package_infos = self.get_json(url)\n        return package_infos\n", "path": "conans/client/rest/rest_client_common.py"}], "after_files": [{"content": "import json\n\nfrom requests.auth import AuthBase, HTTPBasicAuth\n\nfrom conans.client.rest import response_to_str\nfrom conans.errors import (EXCEPTION_CODE_MAPPING, ConanException,\n                           AuthenticationException, RecipeNotFoundException,\n                           PackageNotFoundException)\nfrom conans.model.ref import ConanFileReference\nfrom conans.util.files import decode_text\nfrom conans.util.log import logger\n\n\nclass JWTAuth(AuthBase):\n    \"\"\"Attaches JWT Authentication to the given Request object.\"\"\"\n\n    def __init__(self, token):\n        self.token = token\n\n    def __call__(self, request):\n        if self.token:\n            request.headers['Authorization'] = \"Bearer %s\" % str(self.token)\n        return request\n\n\ndef get_exception_from_error(error_code):\n    tmp = {v: k for k, v in EXCEPTION_CODE_MAPPING.items()  # All except NotFound\n           if k not in (RecipeNotFoundException, PackageNotFoundException)}\n    if error_code in tmp:\n        logger.debug(\"REST ERROR: %s\" % str(tmp[error_code]))\n        return tmp[error_code]\n    else:\n        base_error = int(str(error_code)[0] + \"00\")\n        logger.debug(\"REST ERROR: %s\" % str(base_error))\n        try:\n            return tmp[base_error]\n        except KeyError:\n            return None\n\n\ndef handle_return_deserializer(deserializer=None):\n    \"\"\"Decorator for rest api methods.\n    Map exceptions and http return codes and deserialize if needed.\n\n    deserializer: Function for deserialize values\"\"\"\n\n    def handle_return(method):\n        def inner(*argc, **argv):\n            ret = method(*argc, **argv)\n            if ret.status_code != 200:\n                ret.charset = \"utf-8\"  # To be able to access ret.text (ret.content are bytes)\n                text = ret.text if ret.status_code != 404 else \"404 Not found\"\n                raise get_exception_from_error(ret.status_code)(text)\n            return deserializer(ret.content) if deserializer else decode_text(ret.content)\n\n        return inner\n\n    return handle_return\n\n\nclass RestCommonMethods(object):\n\n    def __init__(self, remote_url, token, custom_headers, output, requester, config, verify_ssl,\n                 artifacts_properties=None, matrix_params=False):\n        self.token = token\n        self.remote_url = remote_url\n        self.custom_headers = custom_headers\n        self._output = output\n        self.requester = requester\n        self._config = config\n        self.verify_ssl = verify_ssl\n        self._artifacts_properties = artifacts_properties\n        self._matrix_params = matrix_params\n\n    @property\n    def auth(self):\n        return JWTAuth(self.token)\n\n    @staticmethod\n    def _check_error_response(ret):\n        if ret.status_code == 401:\n            raise AuthenticationException(\"Wrong user or password\")\n        # Cannot check content-type=text/html, conan server is doing it wrong\n        if not ret.ok or \"html>\" in str(ret.content):\n            raise ConanException(\"%s\\n\\nInvalid server response, check remote URL and \"\n                                 \"try again\" % str(ret.content))\n\n    def authenticate(self, user, password):\n        \"\"\"Sends user + password to get:\n          - A plain response with a regular token (not supported refresh in the remote) and None\n        \"\"\"\n        auth = HTTPBasicAuth(user, password)\n        url = self.router.common_authenticate()\n        logger.debug(\"REST: Authenticate to get access_token: %s\" % url)\n        ret = self.requester.get(url, auth=auth, headers=self.custom_headers,\n                                 verify=self.verify_ssl)\n\n        self._check_error_response(ret)\n        return decode_text(ret.content)\n\n    def authenticate_oauth(self, user, password):\n        \"\"\"Sends user + password to get:\n            - A json with an access_token and a refresh token (if supported in the remote)\n                    Artifactory >= 6.13.X\n        \"\"\"\n        url = self.router.oauth_authenticate()\n        auth = HTTPBasicAuth(user, password)\n        headers = {}\n        headers.update(self.custom_headers)\n        headers[\"Content-type\"] = \"application/x-www-form-urlencoded\"\n        logger.debug(\"REST: Authenticating with OAUTH: %s\" % url)\n        ret = self.requester.post(url, auth=auth, headers=headers, verify=self.verify_ssl)\n        self._check_error_response(ret)\n\n        data = ret.json()\n        access_token = data[\"access_token\"]\n        refresh_token = data[\"refresh_token\"]\n        logger.debug(\"REST: Obtained refresh and access tokens\")\n        return access_token, refresh_token\n\n    def refresh_token(self, token, refresh_token):\n        \"\"\"Sends access_token and the refresh_token to get a pair of\n        access_token and refresh token\n\n        Artifactory >= 6.13.X\n        \"\"\"\n        url = self.router.oauth_authenticate()\n        logger.debug(\"REST: Refreshing Token: %s\" % url)\n        headers = {}\n        headers.update(self.custom_headers)\n        headers[\"Content-type\"] = \"application/x-www-form-urlencoded\"\n        payload = {'access_token': token, 'refresh_token': refresh_token,\n                   'grant_type': 'refresh_token'}\n        ret = self.requester.post(url, headers=headers, verify=self.verify_ssl, data=payload)\n        self._check_error_response(ret)\n\n        data = ret.json()\n        if \"access_token\" not in data:\n            logger.debug(\"REST: unexpected data from server: {}\".format(data))\n            raise ConanException(\"Error refreshing the token\")\n\n        new_access_token = data[\"access_token\"]\n        new_refresh_token = data[\"refresh_token\"]\n        logger.debug(\"REST: Obtained new refresh and access tokens\")\n        return new_access_token, new_refresh_token\n\n    @handle_return_deserializer()\n    def check_credentials(self):\n        \"\"\"If token is not valid will raise AuthenticationException.\n        User will be asked for new user/pass\"\"\"\n        url = self.router.common_check_credentials()\n        logger.debug(\"REST: Check credentials: %s\" % url)\n        ret = self.requester.get(url, auth=self.auth, headers=self.custom_headers,\n                                 verify=self.verify_ssl)\n        return ret\n\n    def server_capabilities(self, user=None, password=None):\n        \"\"\"Get information about the server: status, version, type and capabilities\"\"\"\n        url = self.router.ping()\n        logger.debug(\"REST: ping: %s\" % url)\n        if user and password:\n            # This can happen in \"conan user\" cmd. Instead of empty token, use HttpBasic\n            auth = HTTPBasicAuth(user, password)\n        else:\n            auth = self.auth\n        ret = self.requester.get(url, auth=auth, headers=self.custom_headers, verify=self.verify_ssl)\n\n        server_capabilities = ret.headers.get('X-Conan-Server-Capabilities', \"\")\n        if not server_capabilities and not ret.ok:\n            # Old Artifactory might return 401/403 without capabilities, we don't want\n            # to cache them #5687, so raise the exception and force authentication\n            raise get_exception_from_error(ret.status_code)(response_to_str(ret))\n\n        return [cap.strip() for cap in server_capabilities.split(\",\") if cap]\n\n    def get_json(self, url, data=None, headers=None):\n        req_headers = self.custom_headers.copy()\n        req_headers.update(headers or {})\n        if data:  # POST request\n            req_headers.update({'Content-type': 'application/json',\n                                'Accept': 'application/json'})\n            logger.debug(\"REST: post: %s\" % url)\n            response = self.requester.post(url, auth=self.auth, headers=req_headers,\n                                           verify=self.verify_ssl,\n                                           stream=True,\n                                           data=json.dumps(data))\n        else:\n            logger.debug(\"REST: get: %s\" % url)\n            response = self.requester.get(url, auth=self.auth, headers=req_headers,\n                                          verify=self.verify_ssl,\n                                          stream=True)\n\n        if response.status_code != 200:  # Error message is text\n            response.charset = \"utf-8\"  # To be able to access ret.text (ret.content are bytes)\n            raise get_exception_from_error(response.status_code)(response_to_str(response))\n\n        content = decode_text(response.content)\n        content_type = response.headers.get(\"Content-Type\")\n        if content_type != 'application/json' and content_type != 'application/json; charset=utf-8':\n            raise ConanException(\"%s\\n\\nResponse from remote is not json, but '%s'\"\n                                 % (content, content_type))\n\n        try:  # This can fail, if some proxy returns 200 and an html message\n            result = json.loads(content)\n        except Exception:\n            raise ConanException(\"Remote responded with broken json: %s\" % content)\n        if not isinstance(result, dict):\n            raise ConanException(\"Unexpected server response %s\" % result)\n        return result\n\n    def upload_recipe(self, ref, files_to_upload, deleted, retry, retry_wait):\n        if files_to_upload:\n            self._upload_recipe(ref, files_to_upload, retry, retry_wait)\n        if deleted:\n            self._remove_conanfile_files(ref, deleted)\n\n    def get_recipe_snapshot(self, ref):\n        # this method is used only for UPLOADING, then it requires the credentials\n        # Check of credentials is done in the uploader\n        url = self.router.recipe_snapshot(ref)\n        snap = self._get_snapshot(url)\n        return snap\n\n    def get_package_snapshot(self, pref):\n        # this method is also used to check the integrity of the package upstream\n        # while installing, so check_credentials is done in uploader.\n        url = self.router.package_snapshot(pref)\n        snap = self._get_snapshot(url)\n        return snap\n\n    def upload_package(self, pref, files_to_upload, deleted, retry, retry_wait):\n        if files_to_upload:\n            self._upload_package(pref, files_to_upload, retry, retry_wait)\n        if deleted:\n            raise Exception(\"This shouldn't be happening, deleted files \"\n                            \"in local package present in remote: %s.\\n Please, report it at \"\n                            \"https://github.com/conan-io/conan/issues \" % str(deleted))\n\n    def search(self, pattern=None, ignorecase=True):\n        \"\"\"\n        the_files: dict with relative_path: content\n        \"\"\"\n        url = self.router.search(pattern, ignorecase)\n        response = self.get_json(url)[\"results\"]\n        return [ConanFileReference.loads(reference) for reference in response]\n\n    def search_packages(self, ref, query):\n        \"\"\"Client is filtering by the query\"\"\"\n        url = self.router.search_packages(ref, query)\n        package_infos = self.get_json(url)\n        return package_infos\n", "path": "conans/client/rest/rest_client_common.py"}]}