Added support for raw_path in scope. (#268)

As per https://github.com/django/asgiref/pull/92

Required valid URI path fragments to be used in tests:
- Test case must ensure paths are correctly quoted before calling
   run_daphne_request() & co.

Co-authored-by: Carlton Gibson <carlton.gibson@noumenal.es>
This commit is contained in:
Simon Willison 2019-07-03 11:22:03 -07:00 committed by Carlton Gibson
parent ffd949f2ce
commit 333f4644d1
5 changed files with 31 additions and 12 deletions

View File

@ -171,6 +171,7 @@ class WebRequest(http.Request):
), ),
"method": self.method.decode("ascii"), "method": self.method.decode("ascii"),
"path": unquote(self.path.decode("ascii")), "path": unquote(self.path.decode("ascii")),
"raw_path": self.path,
"root_path": self.root_path, "root_path": self.root_path,
"scheme": self.client_scheme, "scheme": self.client_scheme,
"query_string": self.query_string, "query_string": self.query_string,

View File

@ -75,6 +75,7 @@ class WebSocketProtocol(WebSocketServerProtocol):
{ {
"type": "websocket", "type": "websocket",
"path": unquote(self.path.decode("ascii")), "path": unquote(self.path.decode("ascii")),
"raw_path": self.path,
"headers": self.clean_headers, "headers": self.clean_headers,
"query_string": self._raw_query_string, # Passed by HTTP protocol "query_string": self._raw_query_string, # Passed by HTTP protocol
"client": self.client_addr, "client": self.client_addr,

View File

@ -32,8 +32,6 @@ class DaphneTestCase(unittest.TestCase):
# Send it the request. We have to do this the long way to allow # Send it the request. We have to do this the long way to allow
# duplicate headers. # duplicate headers.
conn = HTTPConnection(test_app.host, test_app.port, timeout=timeout) conn = HTTPConnection(test_app.host, test_app.port, timeout=timeout)
# Make sure path is urlquoted and add any params
path = parse.quote(path)
if params: if params:
path += "?" + parse.urlencode(params, doseq=True) path += "?" + parse.urlencode(params, doseq=True)
conn.putrequest(method, path, skip_accept_encoding=True, skip_host=True) conn.putrequest(method, path, skip_accept_encoding=True, skip_host=True)
@ -128,8 +126,6 @@ class DaphneTestCase(unittest.TestCase):
# Send it the request. We have to do this the long way to allow # Send it the request. We have to do this the long way to allow
# duplicate headers. # duplicate headers.
conn = HTTPConnection(test_app.host, test_app.port, timeout=timeout) conn = HTTPConnection(test_app.host, test_app.port, timeout=timeout)
# Make sure path is urlquoted and add any params
path = parse.quote(path)
if params: if params:
path += "?" + parse.urlencode(params, doseq=True) path += "?" + parse.urlencode(params, doseq=True)
conn.putrequest("GET", path, skip_accept_encoding=True, skip_host=True) conn.putrequest("GET", path, skip_accept_encoding=True, skip_host=True)
@ -247,12 +243,11 @@ class DaphneTestCase(unittest.TestCase):
# Assert that no other keys are present # Assert that no other keys are present
self.assertEqual(set(), present_keys - required_keys - optional_keys) self.assertEqual(set(), present_keys - required_keys - optional_keys)
def assert_valid_path(self, path, request_path): def assert_valid_path(self, path):
""" """
Checks the path is valid and already url-decoded. Checks the path is valid and already url-decoded.
""" """
self.assertIsInstance(path, str) self.assertIsInstance(path, str)
self.assertEqual(path, request_path)
# Assert that it's already url decoded # Assert that it's already url decoded
self.assertEqual(path, parse.unquote(path)) self.assertEqual(path, parse.unquote(path))

View File

@ -28,6 +28,7 @@ class TestHTTPRequest(DaphneTestCase):
"http_version", "http_version",
"method", "method",
"path", "path",
"raw_path",
"query_string", "query_string",
"headers", "headers",
}, },
@ -40,7 +41,7 @@ class TestHTTPRequest(DaphneTestCase):
self.assertIsInstance(scope["method"], str) self.assertIsInstance(scope["method"], str)
self.assertEqual(scope["method"], method.upper()) self.assertEqual(scope["method"], method.upper())
# Path # Path
self.assert_valid_path(scope["path"], path) self.assert_valid_path(scope["path"])
# HTTP version # HTTP version
self.assertIn(scope["http_version"], ["1.0", "1.1", "1.2"]) self.assertIn(scope["http_version"], ["1.0", "1.1", "1.2"])
# Scheme # Scheme
@ -134,13 +135,21 @@ class TestHTTPRequest(DaphneTestCase):
self.assert_valid_http_scope(scope, "POST", request_path) self.assert_valid_http_scope(scope, "POST", request_path)
self.assert_valid_http_request_message(messages[0], body=request_body) self.assert_valid_http_request_message(messages[0], body=request_body)
def test_raw_path(self):
"""
Tests that /foo%2Fbar produces raw_path and a decoded path
"""
scope, _ = self.run_daphne_request("GET", "/foo%2Fbar")
self.assertEqual(scope["path"], "/foo/bar")
self.assertEqual(scope["raw_path"], b"/foo%2Fbar")
@given(request_headers=http_strategies.headers()) @given(request_headers=http_strategies.headers())
@settings(max_examples=5, deadline=5000) @settings(max_examples=5, deadline=5000)
def test_headers(self, request_headers): def test_headers(self, request_headers):
""" """
Tests that HTTP header fields are handled as specified Tests that HTTP header fields are handled as specified
""" """
request_path = "/te st-à/" request_path = parse.quote("/te st-à/")
scope, messages = self.run_daphne_request( scope, messages = self.run_daphne_request(
"OPTIONS", request_path, headers=request_headers "OPTIONS", request_path, headers=request_headers
) )
@ -160,7 +169,7 @@ class TestHTTPRequest(DaphneTestCase):
header_name = request_headers[0][0] header_name = request_headers[0][0]
duplicated_headers = [(header_name, header[1]) for header in request_headers] duplicated_headers = [(header_name, header[1]) for header in request_headers]
# Run the request # Run the request
request_path = "/te st-à/" request_path = parse.quote("/te st-à/")
scope, messages = self.run_daphne_request( scope, messages = self.run_daphne_request(
"OPTIONS", request_path, headers=duplicated_headers "OPTIONS", request_path, headers=duplicated_headers
) )

View File

@ -24,14 +24,14 @@ class TestWebsocket(DaphneTestCase):
""" """
# Check overall keys # Check overall keys
self.assert_key_sets( self.assert_key_sets(
required_keys={"type", "path", "query_string", "headers"}, required_keys={"type", "path", "raw_path", "query_string", "headers"},
optional_keys={"scheme", "root_path", "client", "server", "subprotocols"}, optional_keys={"scheme", "root_path", "client", "server", "subprotocols"},
actual_keys=scope.keys(), actual_keys=scope.keys(),
) )
# Check that it is the right type # Check that it is the right type
self.assertEqual(scope["type"], "websocket") self.assertEqual(scope["type"], "websocket")
# Path # Path
self.assert_valid_path(scope["path"], path) self.assert_valid_path(scope["path"])
# Scheme # Scheme
self.assertIn(scope.get("scheme", "ws"), ["ws", "wss"]) self.assertIn(scope.get("scheme", "ws"), ["ws", "wss"])
if scheme: if scheme:
@ -161,7 +161,7 @@ class TestWebsocket(DaphneTestCase):
test_app.add_send_messages([{"type": "websocket.accept"}]) test_app.add_send_messages([{"type": "websocket.accept"}])
self.websocket_handshake( self.websocket_handshake(
test_app, test_app,
path=request_path, path=parse.quote(request_path),
params=request_params, params=request_params,
headers=request_headers, headers=request_headers,
) )
@ -172,6 +172,19 @@ class TestWebsocket(DaphneTestCase):
) )
self.assert_valid_websocket_connect_message(messages[0]) self.assert_valid_websocket_connect_message(messages[0])
def test_raw_path(self):
"""
Tests that /foo%2Fbar produces raw_path and a decoded path
"""
with DaphneTestingInstance() as test_app:
test_app.add_send_messages([{"type": "websocket.accept"}])
self.websocket_handshake(test_app, path="/foo%2Fbar")
# Validate the scope and messages we got
scope, _ = test_app.get_received()
self.assertEqual(scope["path"], "/foo/bar")
self.assertEqual(scope["raw_path"], b"/foo%2Fbar")
def test_text_frames(self): def test_text_frames(self):
""" """
Tests we can send and receive text frames. Tests we can send and receive text frames.