add cookie support

This commit is contained in:
Joe Kaufeld 2024-08-19 11:13:32 -04:00
parent 14e7ad72ee
commit 62f3d650bc
6 changed files with 94 additions and 3 deletions

View File

@ -1,3 +1,5 @@
from datetime import datetime, timedelta
from spiderweb.decorators import csrf_exempt from spiderweb.decorators import csrf_exempt
from spiderweb.main import SpiderwebRouter from spiderweb.main import SpiderwebRouter
from spiderweb.exceptions import ServerError from spiderweb.exceptions import ServerError
@ -67,6 +69,21 @@ def form(request):
else: else:
return TemplateResponse(request, "form.html") return TemplateResponse(request, "form.html")
@app.route("/cookies")
def cookies(request):
print("request.COOKIES: ", request.COOKIES)
resp = HttpResponse(body="COOKIES! NOM NOM NOM")
resp.set_cookie(name='nom', value="everyonelovescookies")
resp.set_cookie(name="nom2", value="seriouslycookies")
resp.set_cookie(
name="nom3",
value="yumyum",
partitioned=True,
expires=datetime.utcnow()+timedelta(seconds=10),
max_age=15
)
return resp
if __name__ == "__main__": if __name__ == "__main__":
# can also add routes like this: # can also add routes like this:

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "spiderweb" name = "spiderweb"
version = "0.8.0" version = "0.9.0"
description = "A small web framework, just big enough to hold your average spider." description = "A small web framework, just big enough to hold your average spider."
authors = ["Joe Kaufeld <opensource@joekaufeld.com>"] authors = ["Joe Kaufeld <opensource@joekaufeld.com>"]
readme = "README.md" readme = "README.md"

View File

@ -1,3 +1,6 @@
DEFAULT_ALLOWED_METHODS = ["GET"] DEFAULT_ALLOWED_METHODS = ["GET"]
DEFAULT_ENCODING = "ISO-8859-1" DEFAULT_ENCODING = "ISO-8859-1"
__version__ = "0.8.0" __version__ = "0.9.0"
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
REGEX_COOKIE_NAME = r'^[a-zA-Z0-9\s\(\)<>@,;:\/\\\[\]\?=\{\}\"\t]*$'

View File

@ -92,7 +92,13 @@ class SpiderwebRouter(
def fire_response(self, start_response, request: Request, resp: HttpResponse): def fire_response(self, start_response, request: Request, resp: HttpResponse):
try: try:
status = get_http_status_by_code(resp.status_code) status = get_http_status_by_code(resp.status_code)
cookies = []
if "Set-Cookie" in resp.headers:
cookies = resp.headers['Set-Cookie']
del resp.headers['Set-Cookie']
headers = list(resp.headers.items()) headers = list(resp.headers.items())
for c in cookies:
headers.append(("Set-Cookie", c))
start_response(status, headers) start_response(status, headers)

View File

@ -26,9 +26,11 @@ class Request:
self.GET = {} self.GET = {}
self.POST = {} self.POST = {}
self.META = {} self.META = {}
self.COOKIES = {}
self.populate_headers() self.populate_headers()
self.populate_meta() self.populate_meta()
self.populate_cookies()
content_length = int(self.headers.get("CONTENT_LENGTH") or 0) content_length = int(self.headers.get("CONTENT_LENGTH") or 0)
if content_length: if content_length:
@ -63,6 +65,10 @@ class Request:
for f in fields: for f in fields:
self.META[f] = self.environ.get(f) self.META[f] = self.environ.get(f)
def populate_cookies(self) -> None:
if cookies := self.environ.get("HTTP_COOKIE"):
self.COOKIES = {l.split("=")[0]: l.split("=")[1] for l in cookies.split("; ")}
def json(self): def json(self):
return json.loads(self.content) return json.loads(self.content)

View File

@ -1,10 +1,12 @@
import datetime import datetime
import json import json
import re
from typing import Any from typing import Any
import urllib.parse
import mimetypes import mimetypes
from wsgiref.util import FileWrapper from wsgiref.util import FileWrapper
from spiderweb.constants import DEFAULT_ENCODING from spiderweb.constants import REGEX_COOKIE_NAME
from spiderweb.exceptions import GeneralException from spiderweb.exceptions import GeneralException
from spiderweb.request import Request from spiderweb.request import Request
@ -36,6 +38,63 @@ class HttpResponse:
def __str__(self): def __str__(self):
return self.body return self.body
def set_cookie(
self,
name: str,
value: str,
domain: str=None,
expires: datetime.datetime = None,
http_only: bool=None,
max_age: int=None,
partitioned: bool=None,
path: str=None,
secure: bool=False,
same_site: str=None
):
if not bool(re.match(REGEX_COOKIE_NAME, name)):
url = "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes"
raise GeneralException(
f"Cookie name has illegal characters. See {url} for information on"
f" allowed characters."
)
additions = {}
booleans = []
if domain:
additions["Domain"] = domain
if expires:
additions["Expires"] = expires.strftime("%a, %d %b %Y %H:%M:%S GMT")
if max_age:
additions["Max-Age"] = int(max_age)
if path:
additions["Path"] = path
if same_site:
valid_values = ["strict", "lax", "none"]
if same_site.lower() not in valid_values:
raise GeneralException(
f"Invalid value {same_site} for `same_site` cookie attribute. Valid"
f" options are 'strict', 'lax', or 'none'."
)
additions["SameSite"] = same_site.title()
if http_only:
booleans.append("HttpOnly")
if partitioned:
booleans.append("Partitioned")
if secure:
booleans.append("Secure")
attrs = [f"{k}={v}" for k, v in additions.items()]
attrs += booleans
attrs = [urllib.parse.quote_plus(value)] + attrs
cookie = f"{name}={'; '.join(attrs)}"
if "Set-Cookie" in self.headers:
self.headers["Set-Cookie"].append(cookie)
else:
self.headers["Set-Cookie"] = [cookie]
def render(self) -> str: def render(self) -> str:
return str(self.body) return str(self.body)