Compare commits

..

2 Commits

Author SHA1 Message Date
L. Kärkkäinen
a202435283 Skip empty cookie records. Add tests. 2023-10-14 20:03:43 +01:00
L. Kärkkäinen
6d433af406 Accept bare cookies 2023-10-14 18:27:26 +00:00
3 changed files with 67 additions and 11 deletions

View File

@@ -73,12 +73,16 @@ def parse_cookie(raw: str) -> Dict[str, List[str]]:
cookies: Dict[str, List[str]] = {} cookies: Dict[str, List[str]] = {}
for token in raw.split(";"): for token in raw.split(";"):
name, __, value = token.partition("=") name, sep, value = token.partition("=")
name = name.strip() name = name.strip()
value = value.strip() value = value.strip()
if not name: # Support cookies =value or plain value with no name
continue # https://github.com/httpwg/http-extensions/issues/159
if not sep:
if not name:
continue # Empty value like ;; or a cookie header with no value
name, value = "", name
if COOKIE_NAME_RESERVED_CHARS.search(name): # no cov if COOKIE_NAME_RESERVED_CHARS.search(name): # no cov
continue continue

View File

@@ -152,20 +152,24 @@ class HTTPReceiver(Receiver, Stream):
size = len(response.body) if response.body else 0 size = len(response.body) if response.body else 0
headers = response.headers headers = response.headers
status = response.status status = response.status
want_body = (
if not has_message_body(status) and (
size size
or "content-length" in headers or "content-length" in headers
or "transfer-encoding" in headers or "transfer-encoding" in headers
) ):
headers.pop("transfer-encoding", None) # Not used with HTTP/3
if want_body and not has_message_body(status):
headers.pop("content-length", None) headers.pop("content-length", None)
headers.pop("transfer-encoding", None)
logger.warning( # no cov logger.warning( # no cov
f"Message body set in response on {self.request.path}. " f"Message body set in response on {self.request.path}. "
f"A {status} response may only have headers, no body." f"A {status} response may only have headers, no body."
) )
elif size and "content-length" not in headers: elif "content-length" not in headers:
headers["content-length"] = size if size:
headers["content-length"] = size
else:
headers["transfer-encoding"] = "chunked"
headers = [ headers = [
(b":status", str(response.status).encode()), (b":status", str(response.status).encode()),
*response.processed_headers, *response.processed_headers,
@@ -191,8 +195,18 @@ class HTTPReceiver(Receiver, Stream):
self.headers_sent = True self.headers_sent = True
self.stage = Stage.RESPONSE self.stage = Stage.RESPONSE
if self.response.body and not self.head_only:
self._send(self.response.body, False)
elif self.head_only:
self.future.cancel()
def respond(self, response: BaseHTTPResponse) -> BaseHTTPResponse: def respond(self, response: BaseHTTPResponse) -> BaseHTTPResponse:
"""Prepare response to client""" """Prepare response to client"""
logger.debug( # no cov
f"{Colors.BLUE}[respond]:{Colors.END} {response}",
extra={"verbosity": 2},
)
if self.stage is not Stage.HANDLER: if self.stage is not Stage.HANDLER:
self.stage = Stage.FAILED self.stage = Stage.FAILED
raise RuntimeError("Response already started") raise RuntimeError("Response already started")
@@ -215,14 +229,38 @@ class HTTPReceiver(Receiver, Stream):
async def send(self, data: bytes, end_stream: bool) -> None: async def send(self, data: bytes, end_stream: bool) -> None:
"""Send data to client""" """Send data to client"""
logger.debug( # no cov
f"{Colors.BLUE}[send]: {Colors.GREEN}data={data.decode()} "
f"end_stream={end_stream}{Colors.END}",
extra={"verbosity": 2},
)
self._send(data, end_stream)
def _send(self, data: bytes, end_stream: bool) -> None:
if not self.headers_sent: if not self.headers_sent:
self.send_headers() self.send_headers()
if self.stage is not Stage.RESPONSE: if self.stage is not Stage.RESPONSE:
raise ServerError(f"not ready to send: {self.stage}") raise ServerError(f"not ready to send: {self.stage}")
if data and self.head_only: # Chunked
data = b"" if (
self.response
and self.response.headers.get("transfer-encoding") == "chunked"
):
size = len(data)
if end_stream:
data = (
b"%x\r\n%b\r\n0\r\n\r\n" % (size, data)
if size
else b"0\r\n\r\n"
)
elif size:
data = b"%x\r\n%b\r\n" % (size, data)
logger.debug( # no cov
f"{Colors.BLUE}[transmitting]{Colors.END}",
extra={"verbosity": 2},
)
self.protocol.connection.send_data( self.protocol.connection.send_data(
stream_id=self.request.stream_id, stream_id=self.request.stream_id,
data=data, data=data,

View File

@@ -11,6 +11,20 @@ from sanic.cookies.request import CookieRequestParameters
from sanic.exceptions import ServerError from sanic.exceptions import ServerError
from sanic.response import text from sanic.response import text
from sanic.response.convenience import json from sanic.response.convenience import json
from sanic.cookies.request import parse_cookie
def test_request_cookies():
cdict = parse_cookie("foo=one; foo=two; abc = xyz;;bare;=bare2")
assert cdict == {
"foo": ["one", "two"],
"abc": ["xyz"],
"": ["bare", "bare2"],
}
c = CookieRequestParameters(cdict)
assert c.getlist("foo") == ["one", "two"]
assert c.getlist("abc") == ["xyz"]
assert c.getlist("") == ["bare", "bare2"]
assert c.getlist("bare") == None # [] might be sensible but we got None for now
# ------------------------------------------------------------ # # ------------------------------------------------------------ #