Skip to content

Commit 9414ee2

Browse files
committed
fix: properly account for q= when parting the accept header
1 parent 55a9412 commit 9414ee2

File tree

3 files changed

+38
-7
lines changed

3 files changed

+38
-7
lines changed

falcon/app_helpers.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,12 @@ def default_serialize_error(req: Request, resp: Response, exception: HTTPError)
291291
resp: Instance of ``falcon.Response``
292292
exception: Instance of ``falcon.HTTPError``
293293
"""
294-
preferred = req.client_prefers((MEDIA_XML, 'text/xml', MEDIA_JSON))
294+
predefined = [MEDIA_XML, 'text/xml', MEDIA_JSON]
295+
media_handlers = [mt for mt in resp.options.media_handlers if mt not in predefined]
296+
# NOTE(caselit) add all the registered before the predefined ones. This ensures that
297+
# in case of equal match the last one (json) is selected and that the q= is taken
298+
# into consideration when selecting the media
299+
preferred = req.client_prefers(media_handlers + predefined)
295300

296301
if preferred is None:
297302
# NOTE(kgriffs): See if the client expects a custom media
@@ -311,10 +316,6 @@ def default_serialize_error(req: Request, resp: Response, exception: HTTPError)
311316
preferred = MEDIA_JSON
312317
elif '+xml' in accept:
313318
preferred = MEDIA_XML
314-
else:
315-
# NOTE(caselit): if nothing else matchers try using the media handlers
316-
# registered in the response
317-
preferred = req.client_prefers(resp.options.media_handlers)
318319

319320
if preferred is not None:
320321
handler, _, _ = resp.options.media_handlers._resolve(

falcon/request.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
Mapping,
2929
Optional,
3030
overload,
31-
Sequence,
3231
TextIO,
3332
Tuple,
3433
Type,

tests/test_httperror.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -962,7 +962,7 @@ def test_kw_only(self):
962962
XML = (MEDIA_XML, MEDIA_XML, XML_CONTENT)
963963
CUSTOM_XML = ('custom/any+xml', MEDIA_XML, XML_CONTENT)
964964

965-
YAML = (MEDIA_YAML, MEDIA_YAML, (b'title: 410 Gone!'))
965+
YAML = (MEDIA_YAML, MEDIA_YAML, b'title: 410 Gone!')
966966
ASYNC_ONLY = ('application/only_async', 'application/only_async', b'this is async')
967967
ASYNC_WITH_SYNC = (
968968
'application/async_with_sync',
@@ -1040,3 +1040,34 @@ def test_json_async_only_error(self, util):
10401040
client = testing.TestClient(app)
10411041
with pytest.raises(NotImplementedError, match='requires the sync interface'):
10421042
client.simulate_get()
1043+
1044+
def test_add_xml_handler(self, client):
1045+
client.app.resp_options.media_handlers[MEDIA_XML] = FakeYamlMediaHandler()
1046+
res = client.simulate_get(headers={'Accept': 'application/xhtml+xml'})
1047+
assert res.content_type == MEDIA_XML
1048+
assert res.content == YAML[-1]
1049+
1050+
@pytest.mark.parametrize(
1051+
'accept, content_type',
1052+
[
1053+
(
1054+
# firefox
1055+
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,'
1056+
'image/webp,image/png,image/svg+xml,*/*;q=0.8',
1057+
MEDIA_XML,
1058+
),
1059+
(
1060+
# safari / chrome
1061+
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,'
1062+
'image/apng,*/*;q=0.8',
1063+
MEDIA_XML,
1064+
),
1065+
('text/html, application/xhtml+xml, image/jxr, */*', MEDIA_JSON), # edge
1066+
(f'text/html,{MEDIA_YAML};q=0.8,*/*;q=0.7', MEDIA_YAML),
1067+
(f'text/html,{MEDIA_YAML};q=0.8,{MEDIA_JSON};q=0.8', MEDIA_JSON),
1068+
],
1069+
)
1070+
def test_hard_content_types(self, client, accept, content_type):
1071+
client.app.resp_options.media_handlers[MEDIA_YAML] = FakeYamlMediaHandler()
1072+
res = client.simulate_get(headers={'Accept': accept})
1073+
assert res.content_type == content_type

0 commit comments

Comments
 (0)