
* 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>
111 lines
3.6 KiB
Python
111 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import email.utils
|
|
import unicodedata
|
|
|
|
from typing import NamedTuple
|
|
from urllib.parse import unquote
|
|
|
|
from sanic.headers import parse_content_header
|
|
from sanic.log import logger
|
|
|
|
from .parameters import RequestParameters
|
|
|
|
|
|
class File(NamedTuple):
|
|
"""
|
|
Model for defining a file. It is a ``namedtuple``, therefore you can
|
|
iterate over the object, or access the parameters by name.
|
|
|
|
:param type: The mimetype, defaults to text/plain
|
|
:param body: Bytes of the file
|
|
:param name: The filename
|
|
"""
|
|
|
|
type: str
|
|
body: bytes
|
|
name: str
|
|
|
|
|
|
def parse_multipart_form(body, boundary):
|
|
"""
|
|
Parse a request body and returns fields and files
|
|
|
|
:param body: bytes request body
|
|
:param boundary: bytes multipart boundary
|
|
:return: fields (RequestParameters), files (RequestParameters)
|
|
"""
|
|
files = {}
|
|
fields = {}
|
|
|
|
form_parts = body.split(boundary)
|
|
for form_part in form_parts[1:-1]:
|
|
file_name = None
|
|
content_type = "text/plain"
|
|
content_charset = "utf-8"
|
|
field_name = None
|
|
line_index = 2
|
|
line_end_index = 0
|
|
while not line_end_index == -1:
|
|
line_end_index = form_part.find(b"\r\n", line_index)
|
|
form_line = form_part[line_index:line_end_index].decode("utf-8")
|
|
line_index = line_end_index + 2
|
|
|
|
if not form_line:
|
|
break
|
|
|
|
colon_index = form_line.index(":")
|
|
idx = colon_index + 2
|
|
form_header_field = form_line[0:colon_index].lower()
|
|
form_header_value, form_parameters = parse_content_header(
|
|
form_line[idx:]
|
|
)
|
|
|
|
if form_header_field == "content-disposition":
|
|
field_name = form_parameters.get("name")
|
|
file_name = form_parameters.get("filename")
|
|
|
|
# non-ASCII filenames in RFC2231, "filename*" format
|
|
if file_name is None and form_parameters.get("filename*"):
|
|
encoding, _, value = email.utils.decode_rfc2231(
|
|
form_parameters["filename*"]
|
|
)
|
|
file_name = unquote(value, encoding=encoding)
|
|
|
|
# Normalize to NFC (Apple MacOS/iOS send NFD)
|
|
# Notes:
|
|
# - No effect for Windows, Linux or Android clients which
|
|
# already send NFC
|
|
# - Python open() is tricky (creates files in NFC no matter
|
|
# which form you use)
|
|
if file_name is not None:
|
|
file_name = unicodedata.normalize("NFC", file_name)
|
|
|
|
elif form_header_field == "content-type":
|
|
content_type = form_header_value
|
|
content_charset = form_parameters.get("charset", "utf-8")
|
|
|
|
if field_name:
|
|
post_data = form_part[line_index:-4]
|
|
if file_name is None:
|
|
value = post_data.decode(content_charset)
|
|
if field_name in fields:
|
|
fields[field_name].append(value)
|
|
else:
|
|
fields[field_name] = [value]
|
|
else:
|
|
form_file = File(
|
|
type=content_type, name=file_name, body=post_data
|
|
)
|
|
if field_name in files:
|
|
files[field_name].append(form_file)
|
|
else:
|
|
files[field_name] = [form_file]
|
|
else:
|
|
logger.debug(
|
|
"Form-data field does not have a 'name' parameter "
|
|
"in the Content-Disposition header"
|
|
)
|
|
|
|
return RequestParameters(fields), RequestParameters(files)
|