Skip to content

Commit

Permalink
Add convenience methods for cookie creation and deletion (#2706)
Browse files Browse the repository at this point in the history
* Add convenience methods for cookie creation and deletion

* Restore del

* Backwards compat, forward thinking

* Add delitem deprecation notice

* Add get full deprecation notice

* Add deprecation docstring

* Better deprecation docstring

* Add has_cookie

* Same defaults

* Better deprecation message

* Accessor annotations

* make pretty

* parse cookies

* Revert quote translator

* make pretty

* make pretty

* Add unit tests

* Make pretty

* Fix unit tests

* Directly include unquote

* Add some more unit tests

* Move modules into their own dir

* make pretty

* cleanup test imports

* Add test for cookie accessor

* Make test consistent

* Remove file

* Remove additional escaping

* Add header style getattr for hyphens

* Add test for cookie accessor with hyphens

* Add new translator

* Parametrize test_request_with_duplicate_cookie_key

* make pretty

* Add deprecation of direct cookie encoding

* Speedup Cookie creation

* Implement prefixes on delete_cookie

* typing changes

* Add passthru functions on response objects for setting cookies

* Add test for passthru

---------

Co-authored-by: L. Kärkkäinen <98187+Tronic@users.noreply.github.com>
  • Loading branch information
ahopkins and Tronic committed Mar 21, 2023
1 parent 61aa16f commit 1a63b9b
Show file tree
Hide file tree
Showing 13 changed files with 1,318 additions and 316 deletions.
156 changes: 0 additions & 156 deletions sanic/cookies.py

This file was deleted.

4 changes: 4 additions & 0 deletions sanic/cookies/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .response import Cookie, CookieJar


__all__ = ("Cookie", "CookieJar")
118 changes: 118 additions & 0 deletions sanic/cookies/request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import re

from typing import Any, Dict, List, Optional

from sanic.cookies.response import Cookie
from sanic.log import deprecation
from sanic.request.parameters import RequestParameters


COOKIE_NAME_RESERVED_CHARS = re.compile(
'[\x00-\x1F\x7F-\xFF()<>@,;:\\\\"/[\\]?={} \x09]'
)
OCTAL_PATTERN = re.compile(r"\\[0-3][0-7][0-7]")
QUOTE_PATTERN = re.compile(r"[\\].")


def _unquote(str): # no cov
if str is None or len(str) < 2:
return str
if str[0] != '"' or str[-1] != '"':
return str

str = str[1:-1]

i = 0
n = len(str)
res = []
while 0 <= i < n:
o_match = OCTAL_PATTERN.search(str, i)
q_match = QUOTE_PATTERN.search(str, i)
if not o_match and not q_match:
res.append(str[i:])
break
# else:
j = k = -1
if o_match:
j = o_match.start(0)
if q_match:
k = q_match.start(0)
if q_match and (not o_match or k < j):
res.append(str[i:k])
res.append(str[k + 1])
i = k + 2
else:
res.append(str[i:j])
res.append(chr(int(str[j + 1 : j + 4], 8))) # noqa: E203
i = j + 4
return "".join(res)


def parse_cookie(raw: str):
cookies: Dict[str, List] = {}

for token in raw.split(";"):
name, __, value = token.partition("=")
name = name.strip()
value = value.strip()

if not name:
continue

if COOKIE_NAME_RESERVED_CHARS.search(name): # no cov
continue

if len(value) > 2 and value[0] == '"' and value[-1] == '"': # no cov
value = _unquote(value)

if name in cookies:
cookies[name].append(value)
else:
cookies[name] = [value]

return cookies


class CookieRequestParameters(RequestParameters):
def __getitem__(self, key: str) -> Optional[str]:
deprecation(
f"You are accessing cookie key '{key}', which is currently in "
"compat mode returning a single cookie value. Starting in v24.3 "
"accessing a cookie value like this will return a list of values. "
"To avoid this behavior and continue accessing a single value, "
f"please upgrade from request.cookies['{key}'] to "
f"request.cookies.get('{key}'). See more details: ___.",
24.3,
)
try:
value = self._get_prefixed_cookie(key)
except KeyError:
value = super().__getitem__(key)
return value[0]

def __getattr__(self, key: str) -> str:
if key.startswith("_"):
return self.__getattribute__(key)
key = key.rstrip("_").replace("_", "-")
return str(self.get(key, ""))

def get(self, name: str, default: Optional[Any] = None) -> Optional[Any]:
try:
return self._get_prefixed_cookie(name)[0]
except KeyError:
return super().get(name, default)

def getlist(
self, name: str, default: Optional[Any] = None
) -> Optional[Any]:
try:
return self._get_prefixed_cookie(name)
except KeyError:
return super().getlist(name, default)

def _get_prefixed_cookie(self, name: str) -> Any:
getitem = super().__getitem__
try:
return getitem(f"{Cookie.HOST_PREFIX}{name}")
except KeyError:
return getitem(f"{Cookie.SECURE_PREFIX}{name}")

0 comments on commit 1a63b9b

Please sign in to comment.