send charset again in content-type when returning json

There are quite a few applications out there that will use some local
encoding when the charset is not explicitly given.
This commit is contained in:
Sarah Hoffmann 2023-08-23 20:55:57 +02:00
parent 517a0cb673
commit a9edd57fe2
2 changed files with 19 additions and 18 deletions

View File

@ -25,17 +25,18 @@ from nominatim.api.v1.format import dispatch as formatting
from nominatim.api.v1.format import RawDataList from nominatim.api.v1.format import RawDataList
from nominatim.api.v1 import helpers from nominatim.api.v1 import helpers
CONTENT_TYPE = { CONTENT_TEXT = 'text/plain; charset=utf-8'
'text': 'text/plain; charset=utf-8', CONTENT_XML = 'text/xml; charset=utf-8'
'xml': 'text/xml; charset=utf-8', CONTENT_HTML = 'text/html; charset=utf-8'
'debug': 'text/html; charset=utf-8' CONTENT_JSON = 'application/json; charset=utf-8'
}
CONTENT_TYPE = {'text': CONTENT_TEXT, 'xml': CONTENT_XML, 'debug': CONTENT_HTML}
class ASGIAdaptor(abc.ABC): class ASGIAdaptor(abc.ABC):
""" Adapter class for the different ASGI frameworks. """ Adapter class for the different ASGI frameworks.
Wraps functionality over concrete requests and responses. Wraps functionality over concrete requests and responses.
""" """
content_type: str = 'text/plain; charset=utf-8' content_type: str = CONTENT_TEXT
@abc.abstractmethod @abc.abstractmethod
def get(self, name: str, default: Optional[str] = None) -> Optional[str]: def get(self, name: str, default: Optional[str] = None) -> Optional[str]:
@ -85,13 +86,13 @@ class ASGIAdaptor(abc.ABC):
""" Create a response from the given output. Wraps a JSONP function """ Create a response from the given output. Wraps a JSONP function
around the response, if necessary. around the response, if necessary.
""" """
if self.content_type == 'application/json' and status == 200: if self.content_type == CONTENT_JSON and status == 200:
jsonp = self.get('json_callback') jsonp = self.get('json_callback')
if jsonp is not None: if jsonp is not None:
if any(not part.isidentifier() for part in jsonp.split('.')): if any(not part.isidentifier() for part in jsonp.split('.')):
self.raise_error('Invalid json_callback value') self.raise_error('Invalid json_callback value')
output = f"{jsonp}({output})" output = f"{jsonp}({output})"
self.content_type = 'application/javascript' self.content_type = 'application/javascript; charset=utf-8'
return self.create_response(status, output, num_results) return self.create_response(status, output, num_results)
@ -101,16 +102,16 @@ class ASGIAdaptor(abc.ABC):
message. The message will be formatted according to the message. The message will be formatted according to the
output format chosen by the request. output format chosen by the request.
""" """
if self.content_type == 'text/xml; charset=utf-8': if self.content_type == CONTENT_XML:
msg = f"""<?xml version="1.0" encoding="UTF-8" ?> msg = f"""<?xml version="1.0" encoding="UTF-8" ?>
<error> <error>
<code>{status}</code> <code>{status}</code>
<message>{msg}</message> <message>{msg}</message>
</error> </error>
""" """
elif self.content_type == 'application/json': elif self.content_type == CONTENT_JSON:
msg = f"""{{"error":{{"code":{status},"message":"{msg}"}}}}""" msg = f"""{{"error":{{"code":{status},"message":"{msg}"}}}}"""
elif self.content_type == 'text/html; charset=utf-8': elif self.content_type == CONTENT_HTML:
loglib.log().section('Execution error') loglib.log().section('Execution error')
loglib.log().var_dump('Status', status) loglib.log().var_dump('Status', status)
loglib.log().var_dump('Message', msg) loglib.log().var_dump('Message', msg)
@ -204,7 +205,7 @@ class ASGIAdaptor(abc.ABC):
""" """
if self.get_bool('debug', False): if self.get_bool('debug', False):
loglib.set_log_output('html') loglib.set_log_output('html')
self.content_type = 'text/html; charset=utf-8' self.content_type = CONTENT_HTML
return True return True
return False return False
@ -234,7 +235,7 @@ class ASGIAdaptor(abc.ABC):
self.raise_error("Parameter 'format' must be one of: " + self.raise_error("Parameter 'format' must be one of: " +
', '.join(formatting.list_formats(result_type))) ', '.join(formatting.list_formats(result_type)))
self.content_type = CONTENT_TYPE.get(fmt, 'application/json') self.content_type = CONTENT_TYPE.get(fmt, CONTENT_JSON)
return fmt return fmt

View File

@ -67,7 +67,7 @@ def test_adaptor_parse_format_use_configured():
adaptor = FakeAdaptor(params={'format': 'json'}) adaptor = FakeAdaptor(params={'format': 'json'})
assert adaptor.parse_format(napi.StatusResult, 'text') == 'json' assert adaptor.parse_format(napi.StatusResult, 'text') == 'json'
assert adaptor.content_type == 'application/json' assert adaptor.content_type == 'application/json; charset=utf-8'
def test_adaptor_parse_format_invalid_value(): def test_adaptor_parse_format_invalid_value():
@ -132,7 +132,7 @@ class TestAdaptorRaiseError:
def test_json(self): def test_json(self):
self.adaptor.content_type = 'application/json' self.adaptor.content_type = 'application/json; charset=utf-8'
err = self.run_raise_error('TEST', 501) err = self.run_raise_error('TEST', 501)
@ -189,7 +189,7 @@ def test_build_response_with_status():
assert isinstance(resp, FakeResponse) assert isinstance(resp, FakeResponse)
assert resp.status == 404 assert resp.status == 404
assert resp.output == 'stuff\nmore stuff' assert resp.output == 'stuff\nmore stuff'
assert resp.content_type == 'application/json' assert resp.content_type == 'application/json; charset=utf-8'
def test_build_response_jsonp_with_json(): def test_build_response_jsonp_with_json():
@ -201,7 +201,7 @@ def test_build_response_jsonp_with_json():
assert isinstance(resp, FakeResponse) assert isinstance(resp, FakeResponse)
assert resp.status == 200 assert resp.status == 200
assert resp.output == 'test.func({})' assert resp.output == 'test.func({})'
assert resp.content_type == 'application/javascript' assert resp.content_type == 'application/javascript; charset=utf-8'
def test_build_response_jsonp_without_json(): def test_build_response_jsonp_without_json():
@ -270,7 +270,7 @@ class TestStatusEndpoint:
assert isinstance(resp, FakeResponse) assert isinstance(resp, FakeResponse)
assert resp.status == 200 assert resp.status == 200
assert resp.content_type == 'application/json' assert resp.content_type == 'application/json; charset=utf-8'
@pytest.mark.asyncio @pytest.mark.asyncio