aboutsummaryrefslogtreecommitdiff
path: root/venv/lib/python3.8/site-packages/werkzeug/sansio/http.py
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.8/site-packages/werkzeug/sansio/http.py')
-rw-r--r--venv/lib/python3.8/site-packages/werkzeug/sansio/http.py171
1 files changed, 171 insertions, 0 deletions
diff --git a/venv/lib/python3.8/site-packages/werkzeug/sansio/http.py b/venv/lib/python3.8/site-packages/werkzeug/sansio/http.py
new file mode 100644
index 0000000..b2b8877
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/werkzeug/sansio/http.py
@@ -0,0 +1,171 @@
+from __future__ import annotations
+
+import re
+import typing as t
+from datetime import datetime
+
+from .._internal import _dt_as_utc
+from ..http import generate_etag
+from ..http import parse_date
+from ..http import parse_etags
+from ..http import parse_if_range_header
+from ..http import unquote_etag
+
+_etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)')
+
+
+def is_resource_modified(
+ http_range: str | None = None,
+ http_if_range: str | None = None,
+ http_if_modified_since: str | None = None,
+ http_if_none_match: str | None = None,
+ http_if_match: str | None = None,
+ etag: str | None = None,
+ data: bytes | None = None,
+ last_modified: datetime | str | None = None,
+ ignore_if_range: bool = True,
+) -> bool:
+ """Convenience method for conditional requests.
+ :param http_range: Range HTTP header
+ :param http_if_range: If-Range HTTP header
+ :param http_if_modified_since: If-Modified-Since HTTP header
+ :param http_if_none_match: If-None-Match HTTP header
+ :param http_if_match: If-Match HTTP header
+ :param etag: the etag for the response for comparison.
+ :param data: or alternatively the data of the response to automatically
+ generate an etag using :func:`generate_etag`.
+ :param last_modified: an optional date of the last modification.
+ :param ignore_if_range: If `False`, `If-Range` header will be taken into
+ account.
+ :return: `True` if the resource was modified, otherwise `False`.
+
+ .. versionadded:: 2.2
+ """
+ if etag is None and data is not None:
+ etag = generate_etag(data)
+ elif data is not None:
+ raise TypeError("both data and etag given")
+
+ unmodified = False
+ if isinstance(last_modified, str):
+ last_modified = parse_date(last_modified)
+
+ # HTTP doesn't use microsecond, remove it to avoid false positive
+ # comparisons. Mark naive datetimes as UTC.
+ if last_modified is not None:
+ last_modified = _dt_as_utc(last_modified.replace(microsecond=0))
+
+ if_range = None
+ if not ignore_if_range and http_range is not None:
+ # https://tools.ietf.org/html/rfc7233#section-3.2
+ # A server MUST ignore an If-Range header field received in a request
+ # that does not contain a Range header field.
+ if_range = parse_if_range_header(http_if_range)
+
+ if if_range is not None and if_range.date is not None:
+ modified_since: datetime | None = if_range.date
+ else:
+ modified_since = parse_date(http_if_modified_since)
+
+ if modified_since and last_modified and last_modified <= modified_since:
+ unmodified = True
+
+ if etag:
+ etag, _ = unquote_etag(etag)
+ etag = t.cast(str, etag)
+
+ if if_range is not None and if_range.etag is not None:
+ unmodified = parse_etags(if_range.etag).contains(etag)
+ else:
+ if_none_match = parse_etags(http_if_none_match)
+ if if_none_match:
+ # https://tools.ietf.org/html/rfc7232#section-3.2
+ # "A recipient MUST use the weak comparison function when comparing
+ # entity-tags for If-None-Match"
+ unmodified = if_none_match.contains_weak(etag)
+
+ # https://tools.ietf.org/html/rfc7232#section-3.1
+ # "Origin server MUST use the strong comparison function when
+ # comparing entity-tags for If-Match"
+ if_match = parse_etags(http_if_match)
+ if if_match:
+ unmodified = not if_match.is_strong(etag)
+
+ return not unmodified
+
+
+_cookie_re = re.compile(
+ r"""
+ ([^=;]*)
+ (?:\s*=\s*
+ (
+ "(?:[^\\"]|\\.)*"
+ |
+ .*?
+ )
+ )?
+ \s*;\s*
+ """,
+ flags=re.ASCII | re.VERBOSE,
+)
+_cookie_unslash_re = re.compile(rb"\\([0-3][0-7]{2}|.)")
+
+
+def _cookie_unslash_replace(m: t.Match[bytes]) -> bytes:
+ v = m.group(1)
+
+ if len(v) == 1:
+ return v
+
+ return int(v, 8).to_bytes(1, "big")
+
+
+def parse_cookie(
+ cookie: str | None = None,
+ cls: type[ds.MultiDict[str, str]] | None = None,
+) -> ds.MultiDict[str, str]:
+ """Parse a cookie from a string.
+
+ The same key can be provided multiple times, the values are stored
+ in-order. The default :class:`MultiDict` will have the first value
+ first, and all values can be retrieved with
+ :meth:`MultiDict.getlist`.
+
+ :param cookie: The cookie header as a string.
+ :param cls: A dict-like class to store the parsed cookies in.
+ Defaults to :class:`MultiDict`.
+
+ .. versionchanged:: 3.0
+ Passing bytes, and the ``charset`` and ``errors`` parameters, were removed.
+
+ .. versionadded:: 2.2
+ """
+ if cls is None:
+ cls = t.cast("type[ds.MultiDict[str, str]]", ds.MultiDict)
+
+ if not cookie:
+ return cls()
+
+ cookie = f"{cookie};"
+ out = []
+
+ for ck, cv in _cookie_re.findall(cookie):
+ ck = ck.strip()
+ cv = cv.strip()
+
+ if not ck:
+ continue
+
+ if len(cv) >= 2 and cv[0] == cv[-1] == '"':
+ # Work with bytes here, since a UTF-8 character could be multiple bytes.
+ cv = _cookie_unslash_re.sub(
+ _cookie_unslash_replace, cv[1:-1].encode()
+ ).decode(errors="replace")
+
+ out.append((ck, cv))
+
+ return cls(out)
+
+
+# circular dependencies
+from .. import datastructures as ds