Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/sansio/http.py: 24%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import re
4import typing as t
5from datetime import datetime
7from .._internal import _dt_as_utc
8from ..http import generate_etag
9from ..http import parse_date
10from ..http import unquote_etag
12_etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)')
15def is_resource_modified(
16 http_range: str | None = None,
17 http_if_range: str | None = None,
18 http_if_modified_since: str | None = None,
19 http_if_none_match: str | None = None,
20 http_if_match: str | None = None,
21 etag: str | None = None,
22 data: bytes | None = None,
23 last_modified: datetime | str | None = None,
24 ignore_if_range: bool = True,
25) -> bool:
26 """Convenience method for conditional requests.
27 :param http_range: Range HTTP header
28 :param http_if_range: If-Range HTTP header
29 :param http_if_modified_since: If-Modified-Since HTTP header
30 :param http_if_none_match: If-None-Match HTTP header
31 :param http_if_match: If-Match HTTP header
32 :param etag: the etag for the response for comparison.
33 :param data: or alternatively the data of the response to automatically
34 generate an etag using :func:`generate_etag`.
35 :param last_modified: an optional date of the last modification.
36 :param ignore_if_range: If `False`, `If-Range` header will be taken into
37 account.
38 :return: `True` if the resource was modified, otherwise `False`.
40 .. versionadded:: 2.2
41 """
42 if etag is None and data is not None:
43 etag = generate_etag(data)
44 elif data is not None:
45 raise TypeError("both data and etag given")
47 unmodified = False
48 if isinstance(last_modified, str):
49 last_modified = parse_date(last_modified)
51 # HTTP doesn't use microsecond, remove it to avoid false positive
52 # comparisons. Mark naive datetimes as UTC.
53 if last_modified is not None:
54 last_modified = _dt_as_utc(last_modified.replace(microsecond=0))
56 if_range = None
57 if not ignore_if_range and http_range is not None:
58 # https://tools.ietf.org/html/rfc7233#section-3.2
59 # A server MUST ignore an If-Range header field received in a request
60 # that does not contain a Range header field.
61 if_range = ds.IfRange.from_header(http_if_range)
63 if if_range is not None and if_range.date is not None:
64 modified_since: datetime | None = if_range.date
65 else:
66 modified_since = parse_date(http_if_modified_since)
68 if modified_since and last_modified and last_modified <= modified_since:
69 unmodified = True
71 if etag:
72 etag, _ = unquote_etag(etag)
74 if if_range is not None and if_range.etag is not None:
75 unmodified = ds.ETags.from_header(if_range.etag).contains(etag)
76 else:
77 # https://tools.ietf.org/html/rfc7232#section-3.2
78 # "A recipient MUST use the weak comparison function when comparing
79 # entity-tags for If-None-Match"
80 if if_none_match := ds.ETags.from_header(http_if_none_match):
81 unmodified = if_none_match.contains_weak(etag)
83 # https://tools.ietf.org/html/rfc7232#section-3.1
84 # "Origin server MUST use the strong comparison function when
85 # comparing entity-tags for If-Match"
86 if if_match := ds.ETags.from_header(http_if_match):
87 unmodified = not if_match.is_strong(etag)
89 return not unmodified
92_cookie_re = re.compile(
93 r"""
94 ([^=;]*)
95 (?:\s*=\s*
96 (
97 "(?:[^\\"]|\\.)*"
98 |
99 .*?
100 )
101 )?
102 \s*;\s*
103 """,
104 flags=re.ASCII | re.VERBOSE,
105)
106_cookie_unslash_re = re.compile(rb"\\([0-3][0-7]{2}|.)")
109def _cookie_unslash_replace(m: t.Match[bytes]) -> bytes:
110 v = m.group(1)
112 if len(v) == 1:
113 return v
115 return int(v, 8).to_bytes(1, "big")
118def parse_cookie(
119 cookie: str | None = None, **kwargs: t.Any
120) -> ds.ImmutableMultiDict[str, str]:
121 """Parse cookies from a ``Cookie`` header as an :class:`.ImmutableMultiDict`.
123 :param cookie: The ``Cookie`` header.
125 .. versionchanged:: 3.2
126 The ``cls`` parameter is deprecated and will be removed in Werkzeug 3.3.
127 It will always be ``ImmutableMultiDict``.
129 .. versionchanged:: 3.0
130 Passing bytes, and the ``charset`` and ``errors`` parameters, were removed.
132 .. versionadded:: 2.2
133 """
134 if "cls" in kwargs:
135 import warnings
137 warnings.warn(
138 "The 'cls' parameter is deprecated and will be removed in Werkzeug 3.3."
139 " It will always be 'ImmutableMultiDict'.",
140 DeprecationWarning,
141 stacklevel=2,
142 )
144 cls: type[ds.ImmutableMultiDict[str, str]] = kwargs.get(
145 "cls", ds.ImmutableMultiDict
146 )
148 if not cookie:
149 return cls()
151 cookie = f"{cookie};"
152 out = []
154 for ck, cv in _cookie_re.findall(cookie):
155 ck = ck.strip()
156 cv = cv.strip()
158 if not ck:
159 continue
161 if len(cv) >= 2 and cv[0] == cv[-1] == '"':
162 # Work with bytes here, since a UTF-8 character could be multiple bytes.
163 cv = _cookie_unslash_re.sub(
164 _cookie_unslash_replace, cv[1:-1].encode()
165 ).decode(errors="replace")
167 out.append((ck, cv))
169 return cls(out)
172# circular dependencies
173from .. import datastructures as ds # noqa: E402