mirror of
https://github.com/django/django.git
synced 2025-10-27 07:36:08 +00:00
Fixed #36411 -- Made HttpRequest.get_preferred_type() consider media type parameters.
HttpRequest.get_preferred_type() did not account for parameters in Accept header media types (e.g., "text/vcard; version=3.0"). This caused incorrect content negotiation when multiple types differed only by parameters, reducing specificity as per RFC 7231 section 5.3.2 (https://datatracker.ietf.org/doc/html/rfc7231.html#section-5.3.2). This fix updates get_preferred_type() to treat media types with parameters as distinct, allowing more precise and standards-compliant matching. Thanks to magicfelix for the report, and to David Sanders and Sarah Boyce for the reviews.
This commit is contained in:
@@ -93,8 +93,12 @@ class HttpRequest:
|
||||
"""Return a list of MediaType instances, in order of preference."""
|
||||
header_value = self.headers.get("Accept", "*/*")
|
||||
return sorted(
|
||||
(MediaType(token) for token in header_value.split(",") if token.strip()),
|
||||
key=operator.attrgetter("quality", "specificity"),
|
||||
(
|
||||
media_type
|
||||
for token in header_value.split(",")
|
||||
if token.strip() and (media_type := MediaType(token)).quality != 0
|
||||
),
|
||||
key=operator.attrgetter("specificity", "quality"),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
@@ -102,11 +106,12 @@ class HttpRequest:
|
||||
"""
|
||||
Return the preferred MediaType instance which matches the given media type.
|
||||
"""
|
||||
media_type = MediaType(media_type)
|
||||
return next(
|
||||
(
|
||||
accepted_type
|
||||
for accepted_type in self.accepted_types
|
||||
if accepted_type.match(media_type)
|
||||
if media_type.match(accepted_type)
|
||||
),
|
||||
None,
|
||||
)
|
||||
@@ -689,13 +694,13 @@ class QueryDict(MultiValueDict):
|
||||
|
||||
class MediaType:
|
||||
def __init__(self, media_type_raw_line):
|
||||
full_type, self.params = parse_header_parameters(
|
||||
full_type, self._params = parse_header_parameters(
|
||||
media_type_raw_line if media_type_raw_line else ""
|
||||
)
|
||||
self.main_type, _, self.sub_type = full_type.partition("/")
|
||||
|
||||
def __str__(self):
|
||||
params_str = "".join("; %s=%s" % (k, v) for k, v in self.params.items())
|
||||
params_str = "".join("; %s=%s" % (k, v) for k, v in self._params.items())
|
||||
return "%s%s%s" % (
|
||||
self.main_type,
|
||||
("/%s" % self.sub_type) if self.sub_type else "",
|
||||
@@ -705,23 +710,45 @@ class MediaType:
|
||||
def __repr__(self):
|
||||
return "<%s: %s>" % (self.__class__.__qualname__, self)
|
||||
|
||||
@property
|
||||
def is_all_types(self):
|
||||
return self.main_type == "*" and self.sub_type == "*"
|
||||
@cached_property
|
||||
def params(self):
|
||||
params = self._params.copy()
|
||||
params.pop("q", None)
|
||||
return params
|
||||
|
||||
def match(self, other):
|
||||
if self.is_all_types:
|
||||
return True
|
||||
other = MediaType(other)
|
||||
return self.main_type == other.main_type and self.sub_type in {
|
||||
"*",
|
||||
other.sub_type,
|
||||
}
|
||||
if not other:
|
||||
return False
|
||||
|
||||
if not isinstance(other, MediaType):
|
||||
other = MediaType(other)
|
||||
|
||||
main_types = [self.main_type, other.main_type]
|
||||
sub_types = [self.sub_type, other.sub_type]
|
||||
|
||||
# Main types and sub types must be defined.
|
||||
if not all((*main_types, *sub_types)):
|
||||
return False
|
||||
|
||||
# Main types must match or one be "*", same for sub types.
|
||||
for this_type, other_type in (main_types, sub_types):
|
||||
if this_type != other_type and this_type != "*" and other_type != "*":
|
||||
return False
|
||||
|
||||
if bool(self.params) == bool(other.params):
|
||||
# If both have params or neither have params, they must be identical.
|
||||
result = self.params == other.params
|
||||
else:
|
||||
# If self has params and other does not, it's a match.
|
||||
# If other has params and self does not, don't match.
|
||||
result = bool(self.params or not other.params)
|
||||
|
||||
return result
|
||||
|
||||
@cached_property
|
||||
def quality(self):
|
||||
try:
|
||||
quality = float(self.params.get("q", 1))
|
||||
quality = float(self._params.get("q", 1))
|
||||
except ValueError:
|
||||
# Discard invalid values.
|
||||
return 1
|
||||
@@ -741,7 +768,7 @@ class MediaType:
|
||||
return 0
|
||||
elif self.sub_type == "*":
|
||||
return 1
|
||||
elif self.quality == 1:
|
||||
elif not self.params:
|
||||
return 2
|
||||
return 3
|
||||
|
||||
|
||||
Reference in New Issue
Block a user