diff --git a/openapi_core/templating/media_types/finders.py b/openapi_core/templating/media_types/finders.py index 5c0cd4c3..1be2a022 100644 --- a/openapi_core/templating/media_types/finders.py +++ b/openapi_core/templating/media_types/finders.py @@ -1,6 +1,7 @@ """OpenAPI core templating media types finders module""" import fnmatch +import re from typing import Mapping from typing import Tuple @@ -38,12 +39,32 @@ def find(self, mimetype: str) -> MediaType: raise MediaTypeNotFound(mimetype, list(self.content.keys())) def _parse_mimetype(self, mimetype: str) -> Tuple[str, Mapping[str, str]]: - mimetype_parts = mimetype.split("; ") - mime_type = mimetype_parts[0] + mimetype_parts = mimetype.split(";") + mime_type = mimetype_parts[0].lower().rstrip() parameters = {} if len(mimetype_parts) > 1: parameters_list = ( - param_str.split("=") for param_str in mimetype_parts[1:] + self._parse_parameter(param_str) + for param_str in mimetype_parts[1:] ) parameters = dict(parameters_list) return mime_type, parameters + + def _parse_parameter(self, parameter: str) -> Tuple[str, str]: + """Parse a parameter according to RFC 9110. + + See https://www.rfc-editor.org/rfc/rfc9110.html#name-parameters + + Important points: + * parameter names are case-insensitive + * parameter values are case-sensitive + except "charset" which is case-insensitive + https://www.rfc-editor.org/rfc/rfc2046#section-4.1.2 + """ + name, value = parameter.split("=") + name = name.lower().lstrip() + # remove surrounding quotes from value + value = re.sub('^"(.*)"$', r"\1", value, count=1) + if name == "charset": + value = value.lower() + return name, value.rstrip() diff --git a/tests/unit/templating/test_media_types_finders.py b/tests/unit/templating/test_media_types_finders.py index c94ff5b6..d83cc1f1 100644 --- a/tests/unit/templating/test_media_types_finders.py +++ b/tests/unit/templating/test_media_types_finders.py @@ -21,10 +21,19 @@ def content(self, spec): def finder(self, content): return MediaTypeFinder(content) - def test_charset(self, finder, content): - mimetype = "text/html; charset=utf-8" - - mimetype, parameters, _ = finder.find(mimetype) + @pytest.mark.parametrize( + "media_type", + [ + # equivalent according to RFC 9110 + "text/html;charset=utf-8", + 'Text/HTML;Charset="utf-8"', + 'text/html; charset="utf-8"', + "text/html;charset=UTF-8", + "text/html ; charset=utf-8", + ], + ) + def test_charset(self, finder, content, media_type): + mimetype, parameters, _ = finder.find(media_type) assert mimetype == "text/*" assert parameters == {"charset": "utf-8"}