Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/sansio/http.py: 26%
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 ..datastructures.etag import ETag
9from ..http import _generate_etag
10from ..http import parse_date
13def _is_resource_modified(
14 http_range: str | None = None,
15 http_if_range: str | None = None,
16 http_if_modified_since: str | None = None,
17 http_if_none_match: str | None = None,
18 http_if_match: str | None = None,
19 etag: str | None = None,
20 data: bytes | None = None,
21 last_modified: datetime | str | None = None,
22 ignore_if_range: bool = True,
23) -> bool:
24 """Convenience method for conditional requests.
25 :param http_range: Range HTTP header
26 :param http_if_range: If-Range HTTP header
27 :param http_if_modified_since: If-Modified-Since HTTP header
28 :param http_if_none_match: If-None-Match HTTP header
29 :param http_if_match: If-Match HTTP header
30 :param etag: the ETag for the response for comparison.
31 :param data: or alternatively the data of the response to automatically
32 generate an ETag using :func:`generate_etag`.
33 :param last_modified: an optional date of the last modification.
34 :param ignore_if_range: If `False`, `If-Range` header will be taken into
35 account.
36 :return: `True` if the resource was modified, otherwise `False`.
38 .. deprecated:: 3.2
39 Will be removed in Werkzeug 4.0. ``Response.apply_conditions`` and
40 ``apply_range`` perform their own checks.
42 .. versionadded:: 2.2
43 """
44 if etag is None and data is not None:
45 etag = f'"{_generate_etag(data)}"'
46 elif data is not None:
47 raise TypeError("Only one of 'data' or 'etag` may be given.")
49 unmodified = False
50 if isinstance(last_modified, str):
51 last_modified = parse_date(last_modified)
53 # HTTP doesn't use microsecond, remove it to avoid false positive
54 # comparisons. Mark naive datetimes as UTC.
55 if last_modified is not None:
56 last_modified = _dt_as_utc(last_modified.replace(microsecond=0))
58 if_range = None
59 if not ignore_if_range and http_range is not None:
60 # https://tools.ietf.org/html/rfc7233#section-3.2
61 # A server MUST ignore an If-Range header field received in a request
62 # that does not contain a Range header field.
63 if_range = ds.IfRange.from_header(http_if_range)
65 if if_range is not None and if_range.date is not None:
66 modified_since: datetime | None = if_range.date
67 else:
68 modified_since = parse_date(http_if_modified_since)
70 if modified_since and last_modified and last_modified <= modified_since:
71 unmodified = True
73 if etag:
74 if (parsed_etag := ETag.from_header(etag)) is None:
75 unmodified = False
76 elif if_range is not None and if_range.etag is not None:
77 unmodified = if_range.etag == parsed_etag.value
78 else:
79 # https://tools.ietf.org/html/rfc7232#section-3.2
80 # "A recipient MUST use the weak comparison function when comparing
81 # entity-tags for If-None-Match"
82 if if_none_match := ds.ETagSet.from_header(http_if_none_match):
83 unmodified = if_none_match.contains_weak(parsed_etag.value)
85 # https://tools.ietf.org/html/rfc7232#section-3.1
86 # "Origin server MUST use the strong comparison function when
87 # comparing entity-tags for If-Match"
88 if if_match := ds.ETagSet.from_header(http_if_match):
89 unmodified = not if_match.contains_strong(parsed_etag.value)
91 return not unmodified
94_cookie_re = re.compile(
95 r"""
96 [ \t]* # ignore leading space
97 ([^ \t=";]+) # key
98 (?:[ \t]*=[ \t]* # optional =value, ignoring invalid space
99 (
100 "(?:[^\\"]|\\.)*" # quoted value with backslash escapes
101 |
102 [^ \t";]* # token value
103 )
104 )?
105 [ \t]* # ignore trailing space
106 (?:;|\Z) # only if followed by semicolon or end
107 """,
108 flags=re.ASCII | re.VERBOSE,
109)
110_cookie_unslash_re = re.compile(rb"\\([0-3][0-7]{2}|.)")
113def _cookie_unslash_replace(m: t.Match[bytes]) -> bytes:
114 v = m.group(1)
116 if len(v) == 1:
117 return v
119 return int(v, 8).to_bytes(1, "big")
122def parse_cookie(
123 cookie: str | None = None, **kwargs: t.Any
124) -> ds.ImmutableMultiDict[str, str]:
125 """Parse cookies from a ``Cookie`` header as an :class:`.ImmutableMultiDict`.
127 :param cookie: The ``Cookie`` header.
129 .. versionchanged:: 3.2
130 The ``cls`` parameter is deprecated and will be removed in Werkzeug 4.0.
131 It will always be ``ImmutableMultiDict``.
133 .. versionchanged:: 3.0
134 Passing bytes, and the ``charset`` and ``errors`` parameters, were removed.
136 .. versionadded:: 2.2
137 """
138 if "cls" in kwargs:
139 import warnings
141 warnings.warn(
142 "The 'cls' parameter is deprecated and will be removed in Werkzeug 4.0."
143 " It will always be 'ImmutableMultiDict'.",
144 DeprecationWarning,
145 stacklevel=2,
146 )
148 cls: type[ds.ImmutableMultiDict[str, str]] = kwargs.get(
149 "cls", ds.ImmutableMultiDict
150 )
152 if not cookie:
153 return cls()
155 out = []
156 pos = 0
158 while True:
159 if (m := _cookie_re.match(cookie, pos)) is None:
160 # Skip invalid chars until the next semicolon.
161 if (pos := cookie.find(";", pos) + 1) == 0:
162 break
164 continue
166 ck, cv = m.groups()
167 pos = m.end()
169 if cv is None:
170 cv = ""
172 if cv.startswith('"') and cv.endswith('"'):
173 # Work with bytes here, since a UTF-8 character could be multiple bytes.
174 cv = _cookie_unslash_re.sub(
175 _cookie_unslash_replace, cv[1:-1].encode()
176 ).decode(errors="replace")
178 out.append((ck, cv))
180 return cls(out)
183# circular dependencies
184from .. import datastructures as ds # noqa: E402
186if not t.TYPE_CHECKING:
188 def __getattr__(name: str) -> t.Any:
189 import warnings
191 if name == "is_resource_modified":
192 warnings.warn(
193 "'is_resource_modified' is deprecated and will be removed in"
194 " Werkzeug 4.0. 'Response.apply_conditions' and 'apply_range'"
195 " perform their own checks.",
196 DeprecationWarning,
197 stacklevel=2,
198 )
199 return _is_resource_modified
201 raise AttributeError(name)