Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/sansio/http.py: 35%
75 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-09 06:08 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-09 06:08 +0000
1from __future__ import annotations
3import re
4import typing as t
5import warnings
6from datetime import datetime
8from .._internal import _dt_as_utc
9from ..http import generate_etag
10from ..http import parse_date
11from ..http import parse_etags
12from ..http import parse_if_range_header
13from ..http import unquote_etag
15_etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)')
18def is_resource_modified(
19 http_range: str | None = None,
20 http_if_range: str | None = None,
21 http_if_modified_since: str | None = None,
22 http_if_none_match: str | None = None,
23 http_if_match: str | None = None,
24 etag: str | None = None,
25 data: bytes | None = None,
26 last_modified: datetime | str | None = None,
27 ignore_if_range: bool = True,
28) -> bool:
29 """Convenience method for conditional requests.
30 :param http_range: Range HTTP header
31 :param http_if_range: If-Range HTTP header
32 :param http_if_modified_since: If-Modified-Since HTTP header
33 :param http_if_none_match: If-None-Match HTTP header
34 :param http_if_match: If-Match HTTP header
35 :param etag: the etag for the response for comparison.
36 :param data: or alternatively the data of the response to automatically
37 generate an etag using :func:`generate_etag`.
38 :param last_modified: an optional date of the last modification.
39 :param ignore_if_range: If `False`, `If-Range` header will be taken into
40 account.
41 :return: `True` if the resource was modified, otherwise `False`.
43 .. versionadded:: 2.2
44 """
45 if etag is None and data is not None:
46 etag = generate_etag(data)
47 elif data is not None:
48 raise TypeError("both data and etag given")
50 unmodified = False
51 if isinstance(last_modified, str):
52 last_modified = parse_date(last_modified)
54 # HTTP doesn't use microsecond, remove it to avoid false positive
55 # comparisons. Mark naive datetimes as UTC.
56 if last_modified is not None:
57 last_modified = _dt_as_utc(last_modified.replace(microsecond=0))
59 if_range = None
60 if not ignore_if_range and http_range is not None:
61 # https://tools.ietf.org/html/rfc7233#section-3.2
62 # A server MUST ignore an If-Range header field received in a request
63 # that does not contain a Range header field.
64 if_range = parse_if_range_header(http_if_range)
66 if if_range is not None and if_range.date is not None:
67 modified_since: datetime | None = if_range.date
68 else:
69 modified_since = parse_date(http_if_modified_since)
71 if modified_since and last_modified and last_modified <= modified_since:
72 unmodified = True
74 if etag:
75 etag, _ = unquote_etag(etag)
76 etag = t.cast(str, etag)
78 if if_range is not None and if_range.etag is not None:
79 unmodified = parse_etags(if_range.etag).contains(etag)
80 else:
81 if_none_match = parse_etags(http_if_none_match)
82 if if_none_match:
83 # https://tools.ietf.org/html/rfc7232#section-3.2
84 # "A recipient MUST use the weak comparison function when comparing
85 # entity-tags for If-None-Match"
86 unmodified = if_none_match.contains_weak(etag)
88 # https://tools.ietf.org/html/rfc7232#section-3.1
89 # "Origin server MUST use the strong comparison function when
90 # comparing entity-tags for If-Match"
91 if_match = parse_etags(http_if_match)
92 if if_match:
93 unmodified = not if_match.is_strong(etag)
95 return not unmodified
98_cookie_re = re.compile(
99 r"""
100 ([^=;]*)
101 (?:\s*=\s*
102 (
103 "(?:[^\\"]|\\.)*"
104 |
105 .*?
106 )
107 )?
108 \s*;\s*
109 """,
110 flags=re.ASCII | re.VERBOSE,
111)
112_cookie_unslash_re = re.compile(rb"\\([0-3][0-7]{2}|.)")
115def _cookie_unslash_replace(m: t.Match[bytes]) -> bytes:
116 v = m.group(1)
118 if len(v) == 1:
119 return v
121 return int(v, 8).to_bytes(1, "big")
124def parse_cookie(
125 cookie: str | None = None,
126 charset: str | None = None,
127 errors: str | None = None,
128 cls: type[ds.MultiDict] | None = None,
129) -> ds.MultiDict[str, str]:
130 """Parse a cookie from a string.
132 The same key can be provided multiple times, the values are stored
133 in-order. The default :class:`MultiDict` will have the first value
134 first, and all values can be retrieved with
135 :meth:`MultiDict.getlist`.
137 :param cookie: The cookie header as a string.
138 :param cls: A dict-like class to store the parsed cookies in.
139 Defaults to :class:`MultiDict`.
141 .. versionchanged:: 2.3
142 Passing bytes, and the ``charset`` and ``errors`` parameters, are deprecated and
143 will be removed in Werkzeug 3.0.
145 .. versionadded:: 2.2
146 """
147 if cls is None:
148 cls = ds.MultiDict
150 if isinstance(cookie, bytes):
151 warnings.warn(
152 "The 'cookie' parameter must be a string. Passing bytes is deprecated and"
153 " will not be supported in Werkzeug 3.0.",
154 DeprecationWarning,
155 stacklevel=2,
156 )
157 cookie = cookie.decode()
159 if charset is not None:
160 warnings.warn(
161 "The 'charset' parameter is deprecated and will be removed in Werkzeug 3.0",
162 DeprecationWarning,
163 stacklevel=2,
164 )
165 else:
166 charset = "utf-8"
168 if errors is not None:
169 warnings.warn(
170 "The 'errors' parameter is deprecated and will be removed in Werkzeug 3.0",
171 DeprecationWarning,
172 stacklevel=2,
173 )
174 else:
175 errors = "replace"
177 if not cookie:
178 return cls()
180 cookie = f"{cookie};"
181 out = []
183 for ck, cv in _cookie_re.findall(cookie):
184 ck = ck.strip()
185 cv = cv.strip()
187 if not ck:
188 continue
190 if len(cv) >= 2 and cv[0] == cv[-1] == '"':
191 # Work with bytes here, since a UTF-8 character could be multiple bytes.
192 cv = _cookie_unslash_re.sub(
193 _cookie_unslash_replace, cv[1:-1].encode()
194 ).decode(charset, errors)
196 out.append((ck, cv))
198 return cls(out)
201# circular dependencies
202from .. import datastructures as ds