Compare commits
	
		
			16 Commits
		
	
	
		
			breaking-c
			...
			accept-enh
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | fd2e4819d1 | ||
|   | 0e024b46d9 | ||
|   | eae58e5d2a | ||
|   | 6472a69fbf | ||
|   | 2e2231919c | ||
|   | 8da10a9c0c | ||
|   | ec25581262 | ||
|   | b8ae4285a4 | ||
|   | c0ca55530e | ||
|   | 52ecbb9dc7 | ||
|   | 3ef99568a5 | ||
|   | dfe2148333 | ||
|   | 7909f673e5 | ||
|   | e35286e332 | ||
|   | 8eeb1c20dc | ||
|   | 43c9a0a49b | 
							
								
								
									
										292
									
								
								sanic/headers.py
									
									
									
									
									
								
							
							
						
						
									
										292
									
								
								sanic/headers.py
									
									
									
									
									
								
							| @@ -35,141 +35,96 @@ _host_re = re.compile( | |||||||
|  |  | ||||||
| def parse_arg_as_accept(f): | def parse_arg_as_accept(f): | ||||||
|     def func(self, other, *args, **kwargs): |     def func(self, other, *args, **kwargs): | ||||||
|         if not isinstance(other, Accept) and other: |         if not isinstance(other, MediaType) and other: | ||||||
|             other = Accept.parse(other) |             other = MediaType._parse(other) | ||||||
|         return f(self, other, *args, **kwargs) |         return f(self, other, *args, **kwargs) | ||||||
|  |  | ||||||
|     return func |     return func | ||||||
|  |  | ||||||
|  |  | ||||||
| class MediaType(str): | class MediaType: | ||||||
|     def __new__(cls, value: str): |     """A media type, as used in the Accept header.""" | ||||||
|         return str.__new__(cls, value) |  | ||||||
|  |  | ||||||
|     def __init__(self, value: str) -> None: |  | ||||||
|         self.value = value |  | ||||||
|         self.is_wildcard = self.check_if_wildcard(value) |  | ||||||
|  |  | ||||||
|     def __eq__(self, other): |  | ||||||
|         if self.is_wildcard: |  | ||||||
|             return True |  | ||||||
|  |  | ||||||
|         if self.match(other): |  | ||||||
|             return True |  | ||||||
|  |  | ||||||
|         other_is_wildcard = ( |  | ||||||
|             other.is_wildcard |  | ||||||
|             if isinstance(other, MediaType) |  | ||||||
|             else self.check_if_wildcard(other) |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         return other_is_wildcard |  | ||||||
|  |  | ||||||
|     def match(self, other): |  | ||||||
|         other_value = other.value if isinstance(other, MediaType) else other |  | ||||||
|         return self.value == other_value |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def check_if_wildcard(value): |  | ||||||
|         return value == "*" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Accept(str): |  | ||||||
|     def __new__(cls, value: str, *args, **kwargs): |  | ||||||
|         return str.__new__(cls, value) |  | ||||||
|  |  | ||||||
|     def __init__( |     def __init__( | ||||||
|         self, |         self, | ||||||
|         value: str, |         type_: str, | ||||||
|         type_: MediaType, |         subtype: str, | ||||||
|         subtype: MediaType, |         **params: str, | ||||||
|         *, |  | ||||||
|         q: str = "1.0", |  | ||||||
|         **kwargs: str, |  | ||||||
|     ): |     ): | ||||||
|         qvalue = float(q) |  | ||||||
|         if qvalue > 1 or qvalue < 0: |  | ||||||
|             raise InvalidHeader( |  | ||||||
|                 f"Accept header qvalue must be between 0 and 1, not: {qvalue}" |  | ||||||
|             ) |  | ||||||
|         self.value = value |  | ||||||
|         self.type_ = type_ |         self.type_ = type_ | ||||||
|         self.subtype = subtype |         self.subtype = subtype | ||||||
|         self.qvalue = qvalue |         self.q = float(params.get("q", "1.0")) | ||||||
|         self.params = kwargs |         self.params = params | ||||||
|  |         self.mime = f"{type_}/{subtype}" | ||||||
|  |  | ||||||
|     def _compare(self, other, method): |     def __repr__(self): | ||||||
|         try: |         return self.mime + "".join(f";{k}={v}" for k, v in self.params.items()) | ||||||
|             return method(self.qvalue, other.qvalue) |  | ||||||
|         except (AttributeError, TypeError): |     def __eq__(self, other): | ||||||
|  |         """Check for mime (str or MediaType) identical type/subtype.""" | ||||||
|  |         if isinstance(other, str): | ||||||
|  |             return self.mime == other | ||||||
|  |         if isinstance(other, MediaType): | ||||||
|  |             return self.mime == other.mime | ||||||
|         return NotImplemented |         return NotImplemented | ||||||
|  |  | ||||||
|     @parse_arg_as_accept |  | ||||||
|     def __lt__(self, other: Union[str, Accept]): |  | ||||||
|         return self._compare(other, lambda s, o: s < o) |  | ||||||
|  |  | ||||||
|     @parse_arg_as_accept |  | ||||||
|     def __le__(self, other: Union[str, Accept]): |  | ||||||
|         return self._compare(other, lambda s, o: s <= o) |  | ||||||
|  |  | ||||||
|     @parse_arg_as_accept |  | ||||||
|     def __eq__(self, other: Union[str, Accept]):  # type: ignore |  | ||||||
|         return self._compare(other, lambda s, o: s == o) |  | ||||||
|  |  | ||||||
|     @parse_arg_as_accept |  | ||||||
|     def __ge__(self, other: Union[str, Accept]): |  | ||||||
|         return self._compare(other, lambda s, o: s >= o) |  | ||||||
|  |  | ||||||
|     @parse_arg_as_accept |  | ||||||
|     def __gt__(self, other: Union[str, Accept]): |  | ||||||
|         return self._compare(other, lambda s, o: s > o) |  | ||||||
|  |  | ||||||
|     @parse_arg_as_accept |  | ||||||
|     def __ne__(self, other: Union[str, Accept]):  # type: ignore |  | ||||||
|         return self._compare(other, lambda s, o: s != o) |  | ||||||
|  |  | ||||||
|     @parse_arg_as_accept |  | ||||||
|     def match( |     def match( | ||||||
|         self, |         self, | ||||||
|         other, |         mime: str, | ||||||
|         *, |         allow_type_wildcard=True, | ||||||
|         allow_type_wildcard: bool = True, |         allow_subtype_wildcard=True, | ||||||
|         allow_subtype_wildcard: bool = True, |     ) -> Optional[MediaType]: | ||||||
|     ) -> bool: |         """Check if this media type matches the given mime type/subtype. | ||||||
|         type_match = ( |  | ||||||
|             self.type_ == other.type_ |         Wildcards are supported both ways on both type and subtype. | ||||||
|             if allow_type_wildcard |  | ||||||
|             else ( |         Note:  Use the `==` operator instead to check for literal matches | ||||||
|                 self.type_.match(other.type_) |         without expanding wildcards. | ||||||
|                 and not self.type_.is_wildcard |  | ||||||
|                 and not other.type_.is_wildcard |         @param media_type: A type/subtype string to match. | ||||||
|  |         @return `self` if the media types are compatible, else `None` | ||||||
|  |         """ | ||||||
|  |         mt = MediaType._parse(mime) | ||||||
|  |         return ( | ||||||
|  |             self | ||||||
|  |             if ( | ||||||
|  |                 # Subtype match | ||||||
|  |                 (self.subtype in (mt.subtype, "*") or mt.subtype == "*") | ||||||
|  |                 # Type match | ||||||
|  |                 and (self.type_ in (mt.type_, "*") or mt.type_ == "*") | ||||||
|  |                 # Allow disabling wildcards (backwards compatibility with tests) | ||||||
|  |                 and ( | ||||||
|  |                     allow_type_wildcard | ||||||
|  |                     or self.type_ != "*" | ||||||
|  |                     and mt.type_ != "*" | ||||||
|  |                 ) | ||||||
|  |                 and ( | ||||||
|  |                     allow_subtype_wildcard | ||||||
|  |                     or self.subtype != "*" | ||||||
|  |                     and mt.subtype != "*" | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|         subtype_match = ( |             else None | ||||||
|             self.subtype == other.subtype |  | ||||||
|             if allow_subtype_wildcard |  | ||||||
|             else ( |  | ||||||
|                 self.subtype.match(other.subtype) |  | ||||||
|                 and not self.subtype.is_wildcard |  | ||||||
|                 and not other.subtype.is_wildcard |  | ||||||
|             ) |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         return type_match and subtype_match |     @property | ||||||
|  |     def has_wildcard(self) -> bool: | ||||||
|  |         """Return True if this media type has a wildcard in it.""" | ||||||
|  |         return "*" in (self.subtype, self.type_) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def is_wildcard(self) -> bool: | ||||||
|  |         """Return True if this is the wildcard `*/*`""" | ||||||
|  |         return self.type_ == "*" and self.subtype == "*" | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def parse(cls, raw: str) -> Accept: |     def _parse(cls, mime_with_params: str) -> MediaType: | ||||||
|         invalid = False |         mtype = mime_with_params.strip() | ||||||
|         mtype = raw.strip() |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|         media, *raw_params = mtype.split(";") |         media, *raw_params = mtype.split(";") | ||||||
|             type_, subtype = media.split("/") |         type_, subtype = media.split("/", 1) | ||||||
|         except ValueError: |         if not type_ or not subtype: | ||||||
|             invalid = True |             raise ValueError(f"Invalid media type: {mtype}") | ||||||
|  |  | ||||||
|         if invalid or not type_ or not subtype: |  | ||||||
|             raise InvalidHeader(f"Header contains invalid Accept value: {raw}") |  | ||||||
|  |  | ||||||
|         params = dict( |         params = dict( | ||||||
|             [ |             [ | ||||||
| @@ -178,28 +133,79 @@ class Accept(str): | |||||||
|             ] |             ] | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         return cls(mtype, MediaType(type_), MediaType(subtype), **params) |         return cls(type_.lstrip(), subtype.rstrip(), **params) | ||||||
|  |  | ||||||
|  |  | ||||||
| class AcceptContainer(list): | class Matched(str): | ||||||
|     def __contains__(self, o: object) -> bool: |     """A matching result of a MIME string against a MediaType.""" | ||||||
|         return any(item.match(o) for item in self) |  | ||||||
|  |  | ||||||
|     def match( |     def __new__(cls, mime: str, m: Optional[MediaType]): | ||||||
|         self, |         return super().__new__(cls, mime) | ||||||
|         o: object, |  | ||||||
|         *, |     def __init__(self, mime: str, m: Optional[MediaType]): | ||||||
|         allow_type_wildcard: bool = True, |         self.m = m | ||||||
|         allow_subtype_wildcard: bool = True, |  | ||||||
|     ) -> bool: |     def __repr__(self): | ||||||
|         return any( |         return f"<{self} matched {self.m}>" if self else "<no match>" | ||||||
|             item.match( |  | ||||||
|                 o, |  | ||||||
|                 allow_type_wildcard=allow_type_wildcard, | class AcceptList(list): | ||||||
|                 allow_subtype_wildcard=allow_subtype_wildcard, |     """A list of media types, as used in the Accept header. | ||||||
|             ) |  | ||||||
|             for item in self |     The Accept header entries are listed in order of preference, starting | ||||||
|  |     with the most preferred. This class is a list of `MediaType` objects, | ||||||
|  |     that encapsulate also the q value or any other parameters. | ||||||
|  |  | ||||||
|  |     Two separate methods are provided for searching the list: | ||||||
|  |     - 'match' for finding the most preferred match (wildcards supported) | ||||||
|  |     -  operator 'in' for checking explicit matches (wildcards as literals) | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def match(self, *mimes: str) -> Matched: | ||||||
|  |         """Find a media type accepted by the client. | ||||||
|  |  | ||||||
|  |         This method can be used to find which of the media types requested by | ||||||
|  |         the client is most preferred against the ones given as arguments. | ||||||
|  |  | ||||||
|  |         The ordering of preference is set by: | ||||||
|  |         1. The q values on the Accept header, and those being equal, | ||||||
|  |         2. The order of the arguments (first is most preferred), and | ||||||
|  |         3. The first matching entry on the Accept header. | ||||||
|  |  | ||||||
|  |         Wildcards are matched both ways. A match is usually found, as the | ||||||
|  |         Accept headers typically include `*/*`, in particular if the header | ||||||
|  |         is missing, is not manually set, or if the client is a browser. | ||||||
|  |  | ||||||
|  |         Note: the returned object behaves as a string of the mime argument | ||||||
|  |         that matched, and is empty/falsy if no match was found. The matched | ||||||
|  |         header entry `MediaType` or `None` is available as the `m` attribute. | ||||||
|  |  | ||||||
|  |         @param mimes: Any MIME types to search for in order of preference. | ||||||
|  |         @return A match object with the mime string and the MediaType object. | ||||||
|  |         """ | ||||||
|  |         l = sorted( | ||||||
|  |             [ | ||||||
|  |                 (-acc.q, i, j, mime, acc)  # Sort by -q, i, j | ||||||
|  |                 for j, acc in enumerate(self) | ||||||
|  |                 for i, mime in enumerate(mimes) | ||||||
|  |                 if acc.match(mime) | ||||||
|  |             ] | ||||||
|         ) |         ) | ||||||
|  |         return Matched(*(l[0][3:] if l else ("", None))) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def parse_accept(accept: str) -> AcceptList: | ||||||
|  |     """Parse an Accept header and order the acceptable media types in | ||||||
|  |     accorsing to RFC 7231, s. 5.3.2 | ||||||
|  |     https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 | ||||||
|  |     """ | ||||||
|  |     if not accept: | ||||||
|  |         return AcceptList() | ||||||
|  |     try: | ||||||
|  |         a = [MediaType._parse(mtype) for mtype in accept.split(",")] | ||||||
|  |         return AcceptList(sorted(a, key=lambda mtype: -mtype.q)) | ||||||
|  |     except ValueError: | ||||||
|  |         raise InvalidHeader(f"Invalid header value in Accept: {accept}") | ||||||
|  |  | ||||||
|  |  | ||||||
| def parse_content_header(value: str) -> Tuple[str, Options]: | def parse_content_header(value: str) -> Tuple[str, Options]: | ||||||
| @@ -368,34 +374,6 @@ def format_http1_response(status: int, headers: HeaderBytesIterable) -> bytes: | |||||||
|     return ret |     return ret | ||||||
|  |  | ||||||
|  |  | ||||||
| def _sort_accept_value(accept: Accept): |  | ||||||
|     return ( |  | ||||||
|         accept.qvalue, |  | ||||||
|         len(accept.params), |  | ||||||
|         accept.subtype != "*", |  | ||||||
|         accept.type_ != "*", |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def parse_accept(accept: str) -> AcceptContainer: |  | ||||||
|     """Parse an Accept header and order the acceptable media types in |  | ||||||
|     accorsing to RFC 7231, s. 5.3.2 |  | ||||||
|     https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 |  | ||||||
|     """ |  | ||||||
|     media_types = accept.split(",") |  | ||||||
|     accept_list: List[Accept] = [] |  | ||||||
|  |  | ||||||
|     for mtype in media_types: |  | ||||||
|         if not mtype: |  | ||||||
|             continue |  | ||||||
|  |  | ||||||
|         accept_list.append(Accept.parse(mtype)) |  | ||||||
|  |  | ||||||
|     return AcceptContainer( |  | ||||||
|         sorted(accept_list, key=_sort_accept_value, reverse=True) |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def parse_credentials( | def parse_credentials( | ||||||
|     header: Optional[str], |     header: Optional[str], | ||||||
|     prefixes: Union[List, Tuple, Set] = None, |     prefixes: Union[List, Tuple, Set] = None, | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ from sanic.constants import ( | |||||||
| ) | ) | ||||||
| from sanic.exceptions import BadRequest, BadURL, ServerError | from sanic.exceptions import BadRequest, BadURL, ServerError | ||||||
| from sanic.headers import ( | from sanic.headers import ( | ||||||
|     AcceptContainer, |     AcceptList, | ||||||
|     Options, |     Options, | ||||||
|     parse_accept, |     parse_accept, | ||||||
|     parse_content_header, |     parse_content_header, | ||||||
| @@ -167,7 +167,7 @@ class Request: | |||||||
|         self.conn_info: Optional[ConnInfo] = None |         self.conn_info: Optional[ConnInfo] = None | ||||||
|         self.ctx = SimpleNamespace() |         self.ctx = SimpleNamespace() | ||||||
|         self.parsed_forwarded: Optional[Options] = None |         self.parsed_forwarded: Optional[Options] = None | ||||||
|         self.parsed_accept: Optional[AcceptContainer] = None |         self.parsed_accept: Optional[AcceptList] = None | ||||||
|         self.parsed_credentials: Optional[Credentials] = None |         self.parsed_credentials: Optional[Credentials] = None | ||||||
|         self.parsed_json = None |         self.parsed_json = None | ||||||
|         self.parsed_form: Optional[RequestParameters] = None |         self.parsed_form: Optional[RequestParameters] = None | ||||||
| @@ -499,7 +499,7 @@ class Request: | |||||||
|         return self.parsed_json |         return self.parsed_json | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def accept(self) -> AcceptContainer: |     def accept(self) -> AcceptList: | ||||||
|         """ |         """ | ||||||
|         :return: The ``Accept`` header parsed |         :return: The ``Accept`` header parsed | ||||||
|         :rtype: AcceptContainer |         :rtype: AcceptContainer | ||||||
|   | |||||||
| @@ -185,30 +185,22 @@ def test_request_line(app): | |||||||
|  |  | ||||||
|     assert request.request_line == b"GET / HTTP/1.1" |     assert request.request_line == b"GET / HTTP/1.1" | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
|     "raw", |     "raw", | ||||||
|     ( |     ( | ||||||
|         "show/first, show/second", |         "text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8", | ||||||
|         "show/*, show/first", |         "application/xml;q=0.9, */*;q=0.8, text/html, application/xhtml+xml", | ||||||
|         "*/*, show/first", |         "foo/bar;q=0.9, */*;q=0.8, text/html=0.8, text/plain, application/xhtml+xml", | ||||||
|         "*/*, show/*", |  | ||||||
|         "other/*; q=0.1, show/*; q=0.2", |  | ||||||
|         "show/first; q=0.5, show/second; q=0.5", |  | ||||||
|         "show/first; foo=bar, show/second; foo=bar", |  | ||||||
|         "show/second, show/first; foo=bar", |  | ||||||
|         "show/second; q=0.5, show/first; foo=bar; q=0.5", |  | ||||||
|         "show/second; q=0.5, show/first; q=1.0", |  | ||||||
|         "show/first, show/second; q=1.0", |  | ||||||
|     ), |  | ||||||
| ) |  | ||||||
| def test_parse_accept_ordered_okay(raw): |  | ||||||
|     ordered = headers.parse_accept(raw) |  | ||||||
|     expected_subtype = ( |  | ||||||
|         "*" if all(q.subtype.is_wildcard for q in ordered) else "first" |  | ||||||
|     ) |     ) | ||||||
|     assert ordered[0].type_ == "show" | ) | ||||||
|     assert ordered[0].subtype == expected_subtype | def test_accept_ordering(raw): | ||||||
|  |     """Should sort by q but also be stable.""" | ||||||
|  |     accept = headers.parse_accept(raw) | ||||||
|  |     assert accept[0].type_ == "text" | ||||||
|  |     raw1 =  ", ".join(str(a) for a in accept) | ||||||
|  |     accept = headers.parse_accept(raw1) | ||||||
|  |     raw2 =  ", ".join(str(a) for a in accept) | ||||||
|  |     assert raw1 == raw2 | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
| @@ -225,40 +217,27 @@ def test_bad_accept(raw): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_empty_accept(): | def test_empty_accept(): | ||||||
|     assert headers.parse_accept("") == [] |     a = headers.parse_accept("") | ||||||
|  |     assert a == [] | ||||||
|  |     assert not a.match("*/*") | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_wildcard_accept_set_ok(): | def test_wildcard_accept_set_ok(): | ||||||
|     accept = headers.parse_accept("*/*")[0] |     accept = headers.parse_accept("*/*")[0] | ||||||
|     assert accept.type_.is_wildcard |     assert accept.is_wildcard | ||||||
|     assert accept.subtype.is_wildcard |     assert accept.has_wildcard | ||||||
|  |  | ||||||
|  |     accept = headers.parse_accept("foo/*")[0] | ||||||
|  |     assert not accept.is_wildcard | ||||||
|  |     assert accept.has_wildcard | ||||||
|  |  | ||||||
|  |     accept = headers.parse_accept("*/bar")[0] | ||||||
|  |     assert not accept.is_wildcard | ||||||
|  |     assert accept.has_wildcard | ||||||
|  |  | ||||||
|     accept = headers.parse_accept("foo/bar")[0] |     accept = headers.parse_accept("foo/bar")[0] | ||||||
|     assert not accept.type_.is_wildcard |     assert not accept.is_wildcard | ||||||
|     assert not accept.subtype.is_wildcard |     assert not accept.has_wildcard | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_accept_parsed_against_str(): |  | ||||||
|     accept = headers.Accept.parse("foo/bar") |  | ||||||
|     assert accept > "foo/bar; q=0.1" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_media_type_equality(): |  | ||||||
|     assert headers.MediaType("foo") == headers.MediaType("foo") == "foo" |  | ||||||
|     assert headers.MediaType("foo") == headers.MediaType("*") == "*" |  | ||||||
|     assert headers.MediaType("foo") != headers.MediaType("bar") |  | ||||||
|     assert headers.MediaType("foo") != "bar" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_media_type_matching(): |  | ||||||
|     assert headers.MediaType("foo").match(headers.MediaType("foo")) |  | ||||||
|     assert headers.MediaType("foo").match("foo") |  | ||||||
|  |  | ||||||
|     assert not headers.MediaType("foo").match(headers.MediaType("*")) |  | ||||||
|     assert not headers.MediaType("foo").match("*") |  | ||||||
|  |  | ||||||
|     assert not headers.MediaType("foo").match(headers.MediaType("bar")) |  | ||||||
|     assert not headers.MediaType("foo").match("bar") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
| @@ -266,87 +245,52 @@ def test_media_type_matching(): | |||||||
|     ( |     ( | ||||||
|         # ALLOW BOTH |         # ALLOW BOTH | ||||||
|         ("foo/bar", "foo/bar", True, True, True), |         ("foo/bar", "foo/bar", True, True, True), | ||||||
|         ("foo/bar", headers.Accept.parse("foo/bar"), True, True, True), |  | ||||||
|         ("foo/bar", "foo/*", True, True, True), |         ("foo/bar", "foo/*", True, True, True), | ||||||
|         ("foo/bar", headers.Accept.parse("foo/*"), True, True, True), |  | ||||||
|         ("foo/bar", "*/*", True, True, True), |         ("foo/bar", "*/*", True, True, True), | ||||||
|         ("foo/bar", headers.Accept.parse("*/*"), True, True, True), |  | ||||||
|         ("foo/*", "foo/bar", True, True, True), |         ("foo/*", "foo/bar", True, True, True), | ||||||
|         ("foo/*", headers.Accept.parse("foo/bar"), True, True, True), |  | ||||||
|         ("foo/*", "foo/*", True, True, True), |         ("foo/*", "foo/*", True, True, True), | ||||||
|         ("foo/*", headers.Accept.parse("foo/*"), True, True, True), |  | ||||||
|         ("foo/*", "*/*", True, True, True), |         ("foo/*", "*/*", True, True, True), | ||||||
|         ("foo/*", headers.Accept.parse("*/*"), True, True, True), |  | ||||||
|         ("*/*", "foo/bar", True, True, True), |         ("*/*", "foo/bar", True, True, True), | ||||||
|         ("*/*", headers.Accept.parse("foo/bar"), True, True, True), |  | ||||||
|         ("*/*", "foo/*", True, True, True), |         ("*/*", "foo/*", True, True, True), | ||||||
|         ("*/*", headers.Accept.parse("foo/*"), True, True, True), |  | ||||||
|         ("*/*", "*/*", True, True, True), |         ("*/*", "*/*", True, True, True), | ||||||
|         ("*/*", headers.Accept.parse("*/*"), True, True, True), |  | ||||||
|         # ALLOW TYPE |         # ALLOW TYPE | ||||||
|         ("foo/bar", "foo/bar", True, True, False), |         ("foo/bar", "foo/bar", True, True, False), | ||||||
|         ("foo/bar", headers.Accept.parse("foo/bar"), True, True, False), |  | ||||||
|         ("foo/bar", "foo/*", False, True, False), |         ("foo/bar", "foo/*", False, True, False), | ||||||
|         ("foo/bar", headers.Accept.parse("foo/*"), False, True, False), |  | ||||||
|         ("foo/bar", "*/*", False, True, False), |         ("foo/bar", "*/*", False, True, False), | ||||||
|         ("foo/bar", headers.Accept.parse("*/*"), False, True, False), |  | ||||||
|         ("foo/*", "foo/bar", False, True, False), |         ("foo/*", "foo/bar", False, True, False), | ||||||
|         ("foo/*", headers.Accept.parse("foo/bar"), False, True, False), |  | ||||||
|         ("foo/*", "foo/*", False, True, False), |         ("foo/*", "foo/*", False, True, False), | ||||||
|         ("foo/*", headers.Accept.parse("foo/*"), False, True, False), |  | ||||||
|         ("foo/*", "*/*", False, True, False), |         ("foo/*", "*/*", False, True, False), | ||||||
|         ("foo/*", headers.Accept.parse("*/*"), False, True, False), |  | ||||||
|         ("*/*", "foo/bar", False, True, False), |         ("*/*", "foo/bar", False, True, False), | ||||||
|         ("*/*", headers.Accept.parse("foo/bar"), False, True, False), |  | ||||||
|         ("*/*", "foo/*", False, True, False), |         ("*/*", "foo/*", False, True, False), | ||||||
|         ("*/*", headers.Accept.parse("foo/*"), False, True, False), |  | ||||||
|         ("*/*", "*/*", False, True, False), |         ("*/*", "*/*", False, True, False), | ||||||
|         ("*/*", headers.Accept.parse("*/*"), False, True, False), |  | ||||||
|         # ALLOW SUBTYPE |         # ALLOW SUBTYPE | ||||||
|         ("foo/bar", "foo/bar", True, False, True), |         ("foo/bar", "foo/bar", True, False, True), | ||||||
|         ("foo/bar", headers.Accept.parse("foo/bar"), True, False, True), |  | ||||||
|         ("foo/bar", "foo/*", True, False, True), |         ("foo/bar", "foo/*", True, False, True), | ||||||
|         ("foo/bar", headers.Accept.parse("foo/*"), True, False, True), |  | ||||||
|         ("foo/bar", "*/*", False, False, True), |         ("foo/bar", "*/*", False, False, True), | ||||||
|         ("foo/bar", headers.Accept.parse("*/*"), False, False, True), |  | ||||||
|         ("foo/*", "foo/bar", True, False, True), |         ("foo/*", "foo/bar", True, False, True), | ||||||
|         ("foo/*", headers.Accept.parse("foo/bar"), True, False, True), |  | ||||||
|         ("foo/*", "foo/*", True, False, True), |         ("foo/*", "foo/*", True, False, True), | ||||||
|         ("foo/*", headers.Accept.parse("foo/*"), True, False, True), |  | ||||||
|         ("foo/*", "*/*", False, False, True), |         ("foo/*", "*/*", False, False, True), | ||||||
|         ("foo/*", headers.Accept.parse("*/*"), False, False, True), |  | ||||||
|         ("*/*", "foo/bar", False, False, True), |         ("*/*", "foo/bar", False, False, True), | ||||||
|         ("*/*", headers.Accept.parse("foo/bar"), False, False, True), |  | ||||||
|         ("*/*", "foo/*", False, False, True), |         ("*/*", "foo/*", False, False, True), | ||||||
|         ("*/*", headers.Accept.parse("foo/*"), False, False, True), |  | ||||||
|         ("*/*", "*/*", False, False, True), |         ("*/*", "*/*", False, False, True), | ||||||
|         ("*/*", headers.Accept.parse("*/*"), False, False, True), |  | ||||||
|     ), |     ), | ||||||
| ) | ) | ||||||
| def test_accept_matching(value, other, outcome, allow_type, allow_subtype): | def test_accept_matching(value, other, outcome, allow_type, allow_subtype): | ||||||
|     assert ( |     assert ( | ||||||
|         headers.Accept.parse(value).match( |         bool(headers.MediaType._parse(value).match( | ||||||
|             other, |             other, | ||||||
|             allow_type_wildcard=allow_type, |             allow_type_wildcard=allow_type, | ||||||
|             allow_subtype_wildcard=allow_subtype, |             allow_subtype_wildcard=allow_subtype, | ||||||
|         ) |         )) | ||||||
|         is outcome |         is outcome | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("value", ("foo/bar", "foo/*", "*/*")) |  | ||||||
| def test_value_in_accept(value): |  | ||||||
|     acceptable = headers.parse_accept(value) |  | ||||||
|     assert "foo/bar" in acceptable |  | ||||||
|     assert "foo/*" in acceptable |  | ||||||
|     assert "*/*" in acceptable |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("value", ("foo/bar", "foo/*")) | @pytest.mark.parametrize("value", ("foo/bar", "foo/*")) | ||||||
| def test_value_not_in_accept(value): | def test_value_not_in_accept(value): | ||||||
|     acceptable = headers.parse_accept(value) |     acceptable = headers.parse_accept(value) | ||||||
|     assert "no/match" not in acceptable |     assert "*/*" not in acceptable | ||||||
|     assert "no/*" not in acceptable |     assert "*/bar" not in acceptable | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
| @@ -355,16 +299,22 @@ def test_value_not_in_accept(value): | |||||||
|         ( |         ( | ||||||
|             "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",  # noqa: E501 |             "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",  # noqa: E501 | ||||||
|             [ |             [ | ||||||
|                 "text/html", |                 ("text/html", 1.0), | ||||||
|                 "application/xhtml+xml", |                 ("application/xhtml+xml", 1.0), | ||||||
|                 "image/avif", |                 ("image/avif", 1.0), | ||||||
|                 "image/webp", |                 ("image/webp", 1.0), | ||||||
|                 "application/xml;q=0.9", |                 ("application/xml", 0.9), | ||||||
|                 "*/*;q=0.8", |                 ("*/*", 0.8), | ||||||
|             ], |             ], | ||||||
|         ), |         ), | ||||||
|     ), |     ), | ||||||
| ) | ) | ||||||
| def test_browser_headers(header, expected): | def test_browser_headers(header, expected): | ||||||
|  |     mimes = [e[0] for e in expected] | ||||||
|  |     qs = [e[1] for e in expected] | ||||||
|     request = Request(b"/", {"accept": header}, "1.1", "GET", None, None) |     request = Request(b"/", {"accept": header}, "1.1", "GET", None, None) | ||||||
|     assert request.accept == expected |     assert request.accept == mimes | ||||||
|  |     for a, m, q in zip(request.accept, mimes, qs): | ||||||
|  |         assert a == m | ||||||
|  |         assert a.str == m | ||||||
|  |         assert a.q == q | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user