summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/websockets/handlers
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/websockets/handlers
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/websockets/handlers')
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/basic_auth_wsh.py26
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/delayed-passive-close_wsh.py27
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/echo-cookie_wsh.py12
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/echo-query_v13_wsh.py11
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/echo-query_wsh.py9
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/echo_close_data_wsh.py20
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/echo_exit_wsh.py19
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/echo_raw_wsh.py16
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/echo_wsh.py36
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/empty-message_wsh.py13
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/handshake_no_extensions_wsh.py9
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/handshake_no_protocol_wsh.py8
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/handshake_protocol_wsh.py7
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/handshake_sleep_2_wsh.py9
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/invalid_wsh.py8
-rw-r--r--testing/web-platform/tests/websockets/handlers/msg_channel_wsh.py234
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/origin_wsh.py11
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/protocol_array_wsh.py14
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/protocol_wsh.py12
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/receive-backpressure_wsh.py14
-rw-r--r--testing/web-platform/tests/websockets/handlers/receive-many-with-backpressure_wsh.py23
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/referrer_wsh.py12
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/send-backpressure_wsh.py39
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/set-cookie-secure_wsh.py11
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/set-cookie_http_wsh.py11
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/set-cookie_wsh.py11
-rw-r--r--testing/web-platform/tests/websockets/handlers/set-cookies-samesite_wsh.py25
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/simple_handshake_wsh.py35
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/sleep_10_v13_wsh.py15
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/stash_responder_blocking_wsh.py45
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/stash_responder_wsh.py45
-rwxr-xr-xtesting/web-platform/tests/websockets/handlers/wrong_accept_key_wsh.py19
32 files changed, 806 insertions, 0 deletions
diff --git a/testing/web-platform/tests/websockets/handlers/basic_auth_wsh.py b/testing/web-platform/tests/websockets/handlers/basic_auth_wsh.py
new file mode 100755
index 0000000000..72e920a1d8
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/basic_auth_wsh.py
@@ -0,0 +1,26 @@
+#!/usr/bin/python
+
+"""A WebSocket handler that enforces basic HTTP authentication. Username is
+'foo' and password is 'bar'."""
+
+
+from mod_pywebsocket.handshake import AbortedByUserException
+
+
+def web_socket_do_extra_handshake(request):
+ authorization = request.headers_in.get('authorization')
+ if authorization is None or authorization != 'Basic Zm9vOmJhcg==':
+ if request.protocol == "HTTP/2":
+ request.status = 401
+ request.headers_out["Content-Length"] = "0"
+ request.headers_out['www-authenticate'] = 'Basic realm="camelot"'
+ else:
+ request.connection.write(b'HTTP/1.1 401 Unauthorized\x0d\x0a'
+ b'Content-Length: 0\x0d\x0a'
+ b'WWW-Authenticate: Basic realm="camelot"\x0d\x0a'
+ b'\x0d\x0a')
+ raise AbortedByUserException('Abort the connection')
+
+
+def web_socket_transfer_data(request):
+ pass
diff --git a/testing/web-platform/tests/websockets/handlers/delayed-passive-close_wsh.py b/testing/web-platform/tests/websockets/handlers/delayed-passive-close_wsh.py
new file mode 100755
index 0000000000..7d55b88ecc
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/delayed-passive-close_wsh.py
@@ -0,0 +1,27 @@
+#!/usr/bin/python
+from mod_pywebsocket import common
+import time
+
+def web_socket_do_extra_handshake(request):
+ pass
+
+
+def web_socket_transfer_data(request):
+ # Wait for the close frame to arrive.
+ request.ws_stream.receive_message()
+
+
+def web_socket_passive_closing_handshake(request):
+ # Echo close status code and reason
+ code, reason = request.ws_close_code, request.ws_close_reason
+
+ # No status received is a reserved pseudo code representing an empty code,
+ # so echo back an empty code in this case.
+ if code == common.STATUS_NO_STATUS_RECEIVED:
+ code = None
+
+ # The browser may error the connection if the closing handshake takes too
+ # long, but hopefully no browser will have a timeout this short.
+ time.sleep(1)
+
+ return code, reason
diff --git a/testing/web-platform/tests/websockets/handlers/echo-cookie_wsh.py b/testing/web-platform/tests/websockets/handlers/echo-cookie_wsh.py
new file mode 100755
index 0000000000..98620b6552
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/echo-cookie_wsh.py
@@ -0,0 +1,12 @@
+#!/usr/bin/python
+
+from mod_pywebsocket import msgutil
+
+def web_socket_do_extra_handshake(request):
+ request.ws_cookie = request.headers_in.get('cookie')
+
+def web_socket_transfer_data(request):
+ if request.ws_cookie is not None:
+ msgutil.send_message(request, request.ws_cookie)
+ else:
+ msgutil.send_message(request, '(none)')
diff --git a/testing/web-platform/tests/websockets/handlers/echo-query_v13_wsh.py b/testing/web-platform/tests/websockets/handlers/echo-query_v13_wsh.py
new file mode 100755
index 0000000000..d670e6e660
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/echo-query_v13_wsh.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+
+from mod_pywebsocket import msgutil
+
+def web_socket_do_extra_handshake(request):
+ pass
+
+def web_socket_transfer_data(request):
+ while True:
+ msgutil.send_message(request, request.unparsed_uri.split('?')[1] or '')
+ return
diff --git a/testing/web-platform/tests/websockets/handlers/echo-query_wsh.py b/testing/web-platform/tests/websockets/handlers/echo-query_wsh.py
new file mode 100755
index 0000000000..3921913495
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/echo-query_wsh.py
@@ -0,0 +1,9 @@
+#!/usr/bin/python
+
+from mod_pywebsocket import msgutil
+
+def web_socket_do_extra_handshake(request):
+ pass # Always accept.
+
+def web_socket_transfer_data(request):
+ msgutil.send_message(request, request.unparsed_uri.split('?', 1)[1] or '')
diff --git a/testing/web-platform/tests/websockets/handlers/echo_close_data_wsh.py b/testing/web-platform/tests/websockets/handlers/echo_close_data_wsh.py
new file mode 100755
index 0000000000..31ffcbb849
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/echo_close_data_wsh.py
@@ -0,0 +1,20 @@
+#!/usr/bin/python
+
+_GOODBYE_MESSAGE = u'Goodbye'
+
+def web_socket_do_extra_handshake(request):
+ # This example handler accepts any request. See origin_check_wsh.py for how
+ # to reject access from untrusted scripts based on origin value.
+
+ pass # Always accept.
+
+
+def web_socket_transfer_data(request):
+ while True:
+ line = request.ws_stream.receive_message()
+ if line is None:
+ return
+ if isinstance(line, str):
+ if line == _GOODBYE_MESSAGE:
+ return
+ request.ws_stream.send_message(line, binary=False)
diff --git a/testing/web-platform/tests/websockets/handlers/echo_exit_wsh.py b/testing/web-platform/tests/websockets/handlers/echo_exit_wsh.py
new file mode 100755
index 0000000000..8f6f7f854e
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/echo_exit_wsh.py
@@ -0,0 +1,19 @@
+#!/usr/bin/python
+
+_GOODBYE_MESSAGE = u'Goodbye'
+
+def web_socket_do_extra_handshake(request):
+ # This example handler accepts any request. See origin_check_wsh.py for how
+ # to reject access from untrusted scripts based on origin value.
+
+ pass # Always accept.
+
+
+def web_socket_transfer_data(request):
+ while True:
+ line = request.ws_stream.receive_message()
+ if line is None:
+ return
+ if isinstance(line, str):
+ if line == _GOODBYE_MESSAGE:
+ return
diff --git a/testing/web-platform/tests/websockets/handlers/echo_raw_wsh.py b/testing/web-platform/tests/websockets/handlers/echo_raw_wsh.py
new file mode 100755
index 0000000000..e1fc26608f
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/echo_raw_wsh.py
@@ -0,0 +1,16 @@
+#!/usr/bin/python
+
+from mod_pywebsocket import msgutil
+
+
+def web_socket_do_extra_handshake(request):
+ pass # Always accept.
+
+def web_socket_transfer_data(request):
+ while True:
+ line = msgutil.receive_message(request)
+ if line == b'exit':
+ return
+
+ if line is not None:
+ request.connection.write(line)
diff --git a/testing/web-platform/tests/websockets/handlers/echo_wsh.py b/testing/web-platform/tests/websockets/handlers/echo_wsh.py
new file mode 100755
index 0000000000..7367b70af1
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/echo_wsh.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+
+from mod_pywebsocket import common
+
+_GOODBYE_MESSAGE = u'Goodbye'
+
+def web_socket_do_extra_handshake(request):
+ # This example handler accepts any request. See origin_check_wsh.py for how
+ # to reject access from untrusted scripts based on origin value.
+ if request.ws_requested_protocols:
+ if "echo" in request.ws_requested_protocols:
+ request.ws_protocol = "echo"
+
+
+def web_socket_transfer_data(request):
+ while True:
+ line = request.ws_stream.receive_message()
+ if line is None:
+ return
+ if isinstance(line, str):
+ request.ws_stream.send_message(line, binary=False)
+ if line == _GOODBYE_MESSAGE:
+ return
+ else:
+ request.ws_stream.send_message(line, binary=True)
+
+def web_socket_passive_closing_handshake(request):
+ # Echo close status code and reason
+ code, reason = request.ws_close_code, request.ws_close_reason
+
+ # No status received is a reserved pseudo code representing an empty code,
+ # so echo back an empty code in this case.
+ if code == common.STATUS_NO_STATUS_RECEIVED:
+ code = None
+
+ return code, reason
diff --git a/testing/web-platform/tests/websockets/handlers/empty-message_wsh.py b/testing/web-platform/tests/websockets/handlers/empty-message_wsh.py
new file mode 100755
index 0000000000..0eb107f0b1
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/empty-message_wsh.py
@@ -0,0 +1,13 @@
+#!/usr/bin/python
+
+from mod_pywebsocket import msgutil
+
+def web_socket_do_extra_handshake(request):
+ pass # Always accept.
+
+def web_socket_transfer_data(request):
+ line = msgutil.receive_message(request)
+ if line == "":
+ msgutil.send_message(request, 'pass')
+ else:
+ msgutil.send_message(request, 'fail')
diff --git a/testing/web-platform/tests/websockets/handlers/handshake_no_extensions_wsh.py b/testing/web-platform/tests/websockets/handlers/handshake_no_extensions_wsh.py
new file mode 100755
index 0000000000..0d0f0a8b73
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/handshake_no_extensions_wsh.py
@@ -0,0 +1,9 @@
+#!/usr/bin/python
+
+
+def web_socket_do_extra_handshake(request):
+ request.ws_extension_processors = []
+
+
+def web_socket_transfer_data(request):
+ pass
diff --git a/testing/web-platform/tests/websockets/handlers/handshake_no_protocol_wsh.py b/testing/web-platform/tests/websockets/handlers/handshake_no_protocol_wsh.py
new file mode 100755
index 0000000000..ffc2ae80ef
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/handshake_no_protocol_wsh.py
@@ -0,0 +1,8 @@
+#!/usr/bin/python
+
+def web_socket_do_extra_handshake(request):
+ # Trick pywebsocket into believing no subprotocol was requested.
+ request.ws_requested_protocols = None
+
+def web_socket_transfer_data(request):
+ pass
diff --git a/testing/web-platform/tests/websockets/handlers/handshake_protocol_wsh.py b/testing/web-platform/tests/websockets/handlers/handshake_protocol_wsh.py
new file mode 100755
index 0000000000..2ca20c052d
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/handshake_protocol_wsh.py
@@ -0,0 +1,7 @@
+#!/usr/bin/python
+
+def web_socket_do_extra_handshake(request):
+ request.ws_protocol = 'foobar'
+
+def web_socket_transfer_data(request):
+ pass \ No newline at end of file
diff --git a/testing/web-platform/tests/websockets/handlers/handshake_sleep_2_wsh.py b/testing/web-platform/tests/websockets/handlers/handshake_sleep_2_wsh.py
new file mode 100755
index 0000000000..78de7c7659
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/handshake_sleep_2_wsh.py
@@ -0,0 +1,9 @@
+#!/usr/bin/python
+
+import time
+
+def web_socket_do_extra_handshake(request):
+ time.sleep(2)
+
+def web_socket_transfer_data(request):
+ pass
diff --git a/testing/web-platform/tests/websockets/handlers/invalid_wsh.py b/testing/web-platform/tests/websockets/handlers/invalid_wsh.py
new file mode 100755
index 0000000000..4bfc3ce4e7
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/invalid_wsh.py
@@ -0,0 +1,8 @@
+#!/usr/bin/python
+
+def web_socket_do_extra_handshake(request):
+ request.connection.write(b"FOO BAR BAZ\r\n\r\n")
+
+
+def web_socket_transfer_data(request):
+ pass
diff --git a/testing/web-platform/tests/websockets/handlers/msg_channel_wsh.py b/testing/web-platform/tests/websockets/handlers/msg_channel_wsh.py
new file mode 100644
index 0000000000..7a66646f2b
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/msg_channel_wsh.py
@@ -0,0 +1,234 @@
+#!/usr/bin/python
+import json
+import logging
+import urllib
+import threading
+import traceback
+from queue import Empty
+
+from mod_pywebsocket import stream, msgutil
+from wptserve import stash as stashmod
+
+logger = logging.getLogger()
+
+address, authkey = stashmod.load_env_config()
+stash = stashmod.Stash("msg_channel", address=address, authkey=authkey)
+
+# Backend for websocket based channels.
+#
+# Each socket connection has a uuid identifying the channel and a
+# direction which is either "read" or "write". There can be only 1
+# "read" channel per uuid, but multiple "write" channels
+# (i.e. multiple producer, single consumer).
+#
+# The websocket connection URL contains the uuid and the direction as
+# named query parameters.
+#
+# Channels are backed by a queue which is stored in the stash (one
+# queue per uuid).
+#
+# The representation of a queue in the stash is a tuple (queue,
+# has_reader, writer_count). The first field is the queue itself, the
+# latter are effectively reference counts for reader channels (which
+# is zero or one, represented by a bool) and writer channels. Once
+# both counts drop to zero the queue can be deleted.
+#
+# Entries on the queue itself are formed of (command, data) pairs. The
+# command can be either "close", signalling the socket is closing and
+# the reference count on the channel should be decremented, or
+# "message", which indicates a message.
+
+
+def log(uuid, msg, level="debug"):
+ msg = f"{uuid}: {msg}"
+ getattr(logger, level)(msg)
+
+
+def web_socket_do_extra_handshake(request):
+ return
+
+
+def web_socket_transfer_data(request):
+ """Handle opening a websocket connection."""
+
+ uuid, direction = parse_request(request)
+ log(uuid, f"Got web_socket_transfer_data {direction}")
+
+ # Get or create the relevant queue from the stash and update the refcount
+ with stash.lock:
+ value = stash.take(uuid)
+ if value is None:
+ queue = stash.get_queue()
+ if direction == "read":
+ has_reader = True
+ writer_count = 0
+ else:
+ has_reader = False
+ writer_count = 1
+ else:
+ queue, has_reader, writer_count = value
+ if direction == "read":
+ if has_reader:
+ raise ValueError("Tried to start multiple readers for the same queue")
+ has_reader = True
+ else:
+ writer_count += 1
+
+ stash.put(uuid, (queue, has_reader, writer_count))
+
+ if direction == "read":
+ run_read(request, uuid, queue)
+ elif direction == "write":
+ run_write(request, uuid, queue)
+
+ log(uuid, f"transfer_data loop exited {direction}")
+ close_channel(uuid, direction)
+
+
+def web_socket_passive_closing_handshake(request):
+ """Handle a client initiated close.
+
+ When the client closes a reader, put a message in the message
+ queue indicating the close. For a writer we don't need special
+ handling here because receive_message in run_read will return an
+ empty message in this case, so that loop will exit on its own.
+ """
+ uuid, direction = parse_request(request)
+ log(uuid, f"Got web_socket_passive_closing_handshake {direction}")
+
+ if direction == "read":
+ with stash.lock:
+ data = stash.take(uuid)
+ stash.put(uuid, data)
+ if data is not None:
+ queue = data[0]
+ queue.put(("close", None))
+
+ return request.ws_close_code, request.ws_close_reason
+
+
+def parse_request(request):
+ query = request.unparsed_uri.split('?')[1]
+ GET = dict(urllib.parse.parse_qsl(query))
+ uuid = GET["uuid"]
+ direction = GET["direction"]
+ return uuid, direction
+
+
+def wait_for_close(request, uuid, queue):
+ """Listen for messages on the socket for a read connection to a channel."""
+ closed = False
+ while not closed:
+ try:
+ msg = request.ws_stream.receive_message()
+ if msg is None:
+ break
+ try:
+ cmd, data = json.loads(msg)
+ except ValueError:
+ cmd = None
+ if cmd == "close":
+ closed = True
+ log(uuid, "Got client initiated close")
+ else:
+ log(uuid, f"Unexpected message on read socket {msg}", "warning")
+ except Exception:
+ if not (request.server_terminated or request.client_terminated):
+ log(uuid, f"Got exception in wait_for_close\n{traceback.format_exc()}")
+ closed = True
+
+ if not request.server_terminated:
+ queue.put(("close", None))
+
+
+def run_read(request, uuid, queue):
+ """Main loop for a read-type connection.
+
+ This mostly just listens on the queue for new messages of the
+ form (message, data). Supported messages are:
+ message - Send `data` on the WebSocket
+ close - Close the reader queue
+
+ In addition there's a thread that listens for messages on the
+ socket itself. Typically this socket shouldn't recieve any
+ messages, but it can recieve an explicit "close" message,
+ indicating the socket should be disconnected.
+ """
+
+ close_thread = threading.Thread(target=wait_for_close, args=(request, uuid, queue), daemon=True)
+ close_thread.start()
+
+ while True:
+ try:
+ data = queue.get(True, 1)
+ except Empty:
+ if request.server_terminated or request.client_terminated:
+ break
+ else:
+ cmd, body = data
+ log(uuid, f"queue.get ({cmd}, {body})")
+ if cmd == "close":
+ break
+ if cmd == "message":
+ msgutil.send_message(request, json.dumps(body))
+ else:
+ log(uuid, f"Unknown queue command {cmd}", level="warning")
+
+
+def run_write(request, uuid, queue):
+ """Main loop for a write-type connection.
+
+ Messages coming over the socket have the format (command, data).
+ The recognised commands are:
+ message - Send the message `data` over the channel.
+ disconnectReader - Close the reader connection for this channel.
+ delete - Force-delete the entire channel and the underlying queue.
+ """
+ while True:
+ msg = request.ws_stream.receive_message()
+ if msg is None:
+ break
+ cmd, body = json.loads(msg)
+ if cmd == "disconnectReader":
+ queue.put(("close", None))
+ elif cmd == "message":
+ log(uuid, f"queue.put ({cmd}, {body})")
+ queue.put((cmd, body))
+ elif cmd == "delete":
+ close_channel(uuid, None)
+
+
+def close_channel(uuid, direction):
+ """Update the channel state in the stash when closing a connection
+
+ This updates the stash entry, including refcounts, once a
+ connection to a channel is closed.
+
+ Params:
+ uuid - the UUID of the channel being closed.
+ direction - "read" if a read connection was closed, "write" if a
+ write connection was closed, None to remove the
+ underlying queue from the stash entirely.
+
+ """
+ log(uuid, f"Got close_channel {direction}")
+ with stash.lock:
+ data = stash.take(uuid)
+ if data is None:
+ log(uuid, "Message queue already deleted")
+ return
+ if direction is None:
+ # Return without replacing the channel in the stash
+ log(uuid, "Force deleting message queue")
+ return
+ queue, has_reader, writer_count = data
+ if direction == "read":
+ has_reader = False
+ else:
+ writer_count -= 1
+
+ if has_reader or writer_count > 0 or not queue.empty():
+ log(uuid, f"Updating refcount {has_reader}, {writer_count}")
+ stash.put(uuid, (queue, has_reader, writer_count))
+ else:
+ log(uuid, "Deleting message queue")
diff --git a/testing/web-platform/tests/websockets/handlers/origin_wsh.py b/testing/web-platform/tests/websockets/handlers/origin_wsh.py
new file mode 100755
index 0000000000..ce5f3a7f6a
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/origin_wsh.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+
+from mod_pywebsocket import msgutil
+
+
+def web_socket_do_extra_handshake(request):
+ pass # Always accept.
+
+
+def web_socket_transfer_data(request):
+ msgutil.send_message(request, request.ws_origin)
diff --git a/testing/web-platform/tests/websockets/handlers/protocol_array_wsh.py b/testing/web-platform/tests/websockets/handlers/protocol_array_wsh.py
new file mode 100755
index 0000000000..be24ee01fd
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/protocol_array_wsh.py
@@ -0,0 +1,14 @@
+#!/usr/bin/python
+
+from mod_pywebsocket import msgutil
+
+def web_socket_do_extra_handshake(request):
+ line = request.headers_in.get('sec-websocket-protocol')
+ request.ws_protocol = line.split(',', 1)[0]
+
+#pass
+
+def web_socket_transfer_data(request):
+ while True:
+ msgutil.send_message(request, request.ws_protocol)
+ return
diff --git a/testing/web-platform/tests/websockets/handlers/protocol_wsh.py b/testing/web-platform/tests/websockets/handlers/protocol_wsh.py
new file mode 100755
index 0000000000..10bdf33205
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/protocol_wsh.py
@@ -0,0 +1,12 @@
+#!/usr/bin/python
+
+from mod_pywebsocket import msgutil
+
+def web_socket_do_extra_handshake(request):
+ request.ws_protocol = request.headers_in.get('sec-websocket-protocol')
+#pass
+
+def web_socket_transfer_data(request):
+ while True:
+ msgutil.send_message(request, request.ws_protocol)
+ return
diff --git a/testing/web-platform/tests/websockets/handlers/receive-backpressure_wsh.py b/testing/web-platform/tests/websockets/handlers/receive-backpressure_wsh.py
new file mode 100755
index 0000000000..9c2e4709fd
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/receive-backpressure_wsh.py
@@ -0,0 +1,14 @@
+#!/usr/bin/python
+
+import time
+
+
+def web_socket_do_extra_handshake(request):
+ # Turn off permessage-deflate, otherwise it shrinks our 8MB buffer to 8KB.
+ request.ws_extension_processors = []
+
+
+def web_socket_transfer_data(request):
+ # Wait two seconds to cause backpressure.
+ time.sleep(2);
+ request.ws_stream.receive_message()
diff --git a/testing/web-platform/tests/websockets/handlers/receive-many-with-backpressure_wsh.py b/testing/web-platform/tests/websockets/handlers/receive-many-with-backpressure_wsh.py
new file mode 100644
index 0000000000..8e35beebdf
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/receive-many-with-backpressure_wsh.py
@@ -0,0 +1,23 @@
+# Sleep to build backpressure, receive messages, and send back their length.
+# Used by send-many-64K-messages-with-backpressure.any.js.
+
+
+import time
+
+
+def web_socket_do_extra_handshake(request):
+ # Compression will interfere with backpressure, so disable the
+ # permessage-delate extension.
+ request.ws_extension_processors = []
+
+
+def web_socket_transfer_data(request):
+ while True:
+ # Don't read the message immediately, so backpressure can build.
+ time.sleep(0.1)
+ line = request.ws_stream.receive_message()
+ if line is None:
+ return
+ # Send back the size of the message as acknowledgement that it was
+ # received.
+ request.ws_stream.send_message(str(len(line)), binary=False)
diff --git a/testing/web-platform/tests/websockets/handlers/referrer_wsh.py b/testing/web-platform/tests/websockets/handlers/referrer_wsh.py
new file mode 100755
index 0000000000..9df652dc3c
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/referrer_wsh.py
@@ -0,0 +1,12 @@
+#!/usr/bin/python
+
+from mod_pywebsocket import msgutil
+
+def web_socket_do_extra_handshake(request):
+ pass
+
+def web_socket_transfer_data(request):
+ referrer = request.headers_in.get("referer")
+ if referrer is None:
+ referrer = "MISSING AS PER FETCH"
+ msgutil.send_message(request, referrer)
diff --git a/testing/web-platform/tests/websockets/handlers/send-backpressure_wsh.py b/testing/web-platform/tests/websockets/handlers/send-backpressure_wsh.py
new file mode 100755
index 0000000000..d3288d0e85
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/send-backpressure_wsh.py
@@ -0,0 +1,39 @@
+#!/usr/bin/python
+
+import time
+
+# The amount of internal buffering a WebSocket connection has is not
+# standardised, and varies depending upon the OS. Setting this number too small
+# will result in false negatives, as the entire message gets buffered. Setting
+# this number too large will result in false positives, when it takes more than
+# 2 seconds to transmit the message anyway. This number was arrived at by
+# trial-and-error.
+MESSAGE_SIZE = 1024 * 1024
+
+# With Windows 10 and Python 3, the OS will buffer an entire message in memory
+# and return from send() immediately, even if it is very large. To work around
+# this problem, send multiple messages.
+MESSAGE_COUNT = 16
+
+
+def web_socket_do_extra_handshake(request):
+ # Turn off permessage-deflate, otherwise it shrinks our big message to a
+ # tiny message.
+ request.ws_extension_processors = []
+
+
+def web_socket_transfer_data(request):
+ # Send empty message to fill the ReadableStream queue
+ request.ws_stream.send_message(b'', binary=True)
+
+ # TODO(ricea@chromium.org): Use time.perf_counter() when migration to python
+ # 3 is complete. time.time() can go backwards.
+ start_time = time.time()
+
+ # The large messages that will be blocked by backpressure.
+ for i in range(MESSAGE_COUNT):
+ request.ws_stream.send_message(b' ' * MESSAGE_SIZE, binary=True)
+
+ # Report the time taken to send the large message.
+ request.ws_stream.send_message(str(time.time() - start_time),
+ binary=False)
diff --git a/testing/web-platform/tests/websockets/handlers/set-cookie-secure_wsh.py b/testing/web-platform/tests/websockets/handlers/set-cookie-secure_wsh.py
new file mode 100755
index 0000000000..052a882084
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/set-cookie-secure_wsh.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+import urllib
+
+
+def web_socket_do_extra_handshake(request):
+ url_parts = urllib.parse.urlsplit(request.uri)
+ request.extra_headers.append(('Set-Cookie', 'ws_test_'+(url_parts.query or '')+'=test; Secure; Path=/'))
+
+def web_socket_transfer_data(request):
+ # Expect close() from user agent.
+ request.ws_stream.receive_message()
diff --git a/testing/web-platform/tests/websockets/handlers/set-cookie_http_wsh.py b/testing/web-platform/tests/websockets/handlers/set-cookie_http_wsh.py
new file mode 100755
index 0000000000..5331091964
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/set-cookie_http_wsh.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+
+import urllib
+
+def web_socket_do_extra_handshake(request):
+ url_parts = urllib.parse.urlsplit(request.uri)
+ request.extra_headers.append(('Set-Cookie', 'ws_test_'+(url_parts.query or '')+'=test; Path=/; HttpOnly\x0D\x0ASec-WebSocket-Origin: '+request.ws_origin))
+
+def web_socket_transfer_data(request):
+ # Expect close from user agent.
+ request.ws_stream.receive_message()
diff --git a/testing/web-platform/tests/websockets/handlers/set-cookie_wsh.py b/testing/web-platform/tests/websockets/handlers/set-cookie_wsh.py
new file mode 100755
index 0000000000..5fe3ad9c57
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/set-cookie_wsh.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+import urllib
+
+
+def web_socket_do_extra_handshake(request):
+ url_parts = urllib.parse.urlsplit(request.uri)
+ request.extra_headers.append(('Set-Cookie', 'ws_test_'+(url_parts.query or '')+'=test; Path=/'))
+
+def web_socket_transfer_data(request):
+ # Expect close from user agent.
+ request.ws_stream.receive_message()
diff --git a/testing/web-platform/tests/websockets/handlers/set-cookies-samesite_wsh.py b/testing/web-platform/tests/websockets/handlers/set-cookies-samesite_wsh.py
new file mode 100644
index 0000000000..59f0a4a598
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/set-cookies-samesite_wsh.py
@@ -0,0 +1,25 @@
+import urllib
+
+
+def web_socket_do_extra_handshake(request):
+ url_parts = urllib.parse.urlsplit(request.uri)
+ max_age = ""
+ if "clear" in url_parts.query:
+ max_age = "; Max-Age=0"
+ value = "1"
+ if "value" in url_parts.query:
+ value = urllib.parse.parse_qs(url_parts.query)["value"][0]
+ cookies = [
+ "samesite-unspecified={}; Path=/".format(value) + max_age,
+ "samesite-lax={}; Path=/; SameSite=Lax".format(value) + max_age,
+ "samesite-strict={}; Path=/; SameSite=Strict".format(value) + max_age,
+ # SameSite=None cookies must be Secure.
+ "samesite-none={}; Path=/; SameSite=None; Secure".format(value) + max_age
+ ]
+ for cookie in cookies:
+ request.extra_headers.append(("Set-Cookie", cookie))
+
+
+def web_socket_transfer_data(request):
+ # Expect close() from user agent.
+ request.ws_stream.receive_message()
diff --git a/testing/web-platform/tests/websockets/handlers/simple_handshake_wsh.py b/testing/web-platform/tests/websockets/handlers/simple_handshake_wsh.py
new file mode 100755
index 0000000000..ad466877fe
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/simple_handshake_wsh.py
@@ -0,0 +1,35 @@
+#!/usr/bin/python
+
+from mod_pywebsocket import common, stream
+from mod_pywebsocket.handshake import AbortedByUserException, hybi
+
+
+def web_socket_do_extra_handshake(request):
+ # Send simple response header. This test implements the handshake manually,
+ # so that we can send the header in the same packet as the close frame.
+ msg = (b'HTTP/1.1 101 Switching Protocols:\x0D\x0A'
+ b'Connection: Upgrade\x0D\x0A'
+ b'Upgrade: WebSocket\x0D\x0A'
+ b'Set-Cookie: ws_test=test\x0D\x0A'
+ b'Sec-WebSocket-Origin: %s\x0D\x0A'
+ b'Sec-WebSocket-Accept: %s\x0D\x0A\x0D\x0A') % (request.ws_origin.encode(
+ 'UTF-8'), hybi.compute_accept_from_unicode(request.headers_in.get(common.SEC_WEBSOCKET_KEY_HEADER)))
+ # Create a clean close frame.
+ close_body = stream.create_closing_handshake_body(1001, 'PASS')
+ close_frame = stream.create_close_frame(close_body)
+ # Concatenate the header and the close frame and write them to the socket.
+ request.connection.write(msg + close_frame)
+ # Wait for the responding close frame from the user agent. It's not possible
+ # to use the stream methods at this point because the stream hasn't been
+ # established from pywebsocket's point of view. Instead just read the
+ # correct number of bytes.
+ # Warning: reading the wrong number of bytes here will make the test
+ # flaky.
+ MASK_LENGTH = 4
+ request.connection.read(len(close_frame) + MASK_LENGTH)
+ # Close the socket without pywebsocket sending its own handshake response.
+ raise AbortedByUserException('Abort the connection')
+
+
+def web_socket_transfer_data(request):
+ pass
diff --git a/testing/web-platform/tests/websockets/handlers/sleep_10_v13_wsh.py b/testing/web-platform/tests/websockets/handlers/sleep_10_v13_wsh.py
new file mode 100755
index 0000000000..bdef2f2afd
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/sleep_10_v13_wsh.py
@@ -0,0 +1,15 @@
+#!/usr/bin/python
+
+import sys, urllib, time
+from mod_pywebsocket import msgutil
+
+def web_socket_do_extra_handshake(request):
+ time.sleep(10)
+ return
+
+def web_socket_transfer_data(request):
+ while True:
+ line = msgutil.receive_message(request)
+ if line == 'Goodbye':
+ return
+ request.ws_stream.send_message(line, binary=False)
diff --git a/testing/web-platform/tests/websockets/handlers/stash_responder_blocking_wsh.py b/testing/web-platform/tests/websockets/handlers/stash_responder_blocking_wsh.py
new file mode 100755
index 0000000000..10ecdfe0da
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/stash_responder_blocking_wsh.py
@@ -0,0 +1,45 @@
+#!/usr/bin/python
+import json
+import threading
+import wptserve.stash
+from mod_pywebsocket import msgutil
+
+address, authkey = wptserve.stash.load_env_config()
+path = "/stash_responder_blocking"
+stash = wptserve.stash.Stash(path, address=address, authkey=authkey)
+cv = threading.Condition()
+
+def handle_set(key, value):
+ with cv:
+ stash.put(key, value)
+ cv.notify_all()
+
+def handle_get(key):
+ with cv:
+ while True:
+ value = stash.take(key)
+ if value is not None:
+ return value
+ cv.wait()
+
+def web_socket_do_extra_handshake(request):
+ pass
+
+def web_socket_transfer_data(request):
+ line = request.ws_stream.receive_message()
+
+ query = json.loads(line)
+ action = query["action"]
+ key = query["key"]
+
+ if action == "set":
+ value = query["value"]
+ handle_set(key, value)
+ response = {}
+ elif action == "get":
+ value = handle_get(key)
+ response = {"value": value}
+ else:
+ response = {}
+
+ msgutil.send_message(request, json.dumps(response))
diff --git a/testing/web-platform/tests/websockets/handlers/stash_responder_wsh.py b/testing/web-platform/tests/websockets/handlers/stash_responder_wsh.py
new file mode 100755
index 0000000000..d18ad3bc96
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/stash_responder_wsh.py
@@ -0,0 +1,45 @@
+#!/usr/bin/python
+import json
+import urllib
+from mod_pywebsocket import msgutil
+from wptserve import stash
+
+address, authkey = stash.load_env_config()
+stash = stash.Stash("/stash_responder", address=address, authkey=authkey)
+
+def web_socket_do_extra_handshake(request):
+ return
+
+def web_socket_transfer_data(request):
+ while True:
+ line = request.ws_stream.receive_message()
+ if line == "echo":
+ query = request.unparsed_uri.split('?')[1]
+ GET = dict(urllib.parse.parse_qsl(query))
+
+ # TODO(kristijanburnik): This code should be reused from
+ # /mixed-content/generic/expect.py or implemented more generally
+ # for other tests.
+ path = GET.get("path", request.unparsed_uri.split('?')[0])
+ key = GET["key"]
+ action = GET["action"]
+
+ if action == "put":
+ value = GET["value"]
+ stash.take(key=key, path=path)
+ stash.put(key=key, value=value, path=path)
+ response_data = json.dumps({"status": "success", "result": key})
+ elif action == "purge":
+ value = stash.take(key=key, path=path)
+ response_data = json.dumps({"status": "success", "result": value})
+ elif action == "take":
+ value = stash.take(key=key, path=path)
+ if value is None:
+ status = "allowed"
+ else:
+ status = "blocked"
+ response_data = json.dumps({"status": status, "result": value})
+
+ msgutil.send_message(request, response_data)
+
+ return
diff --git a/testing/web-platform/tests/websockets/handlers/wrong_accept_key_wsh.py b/testing/web-platform/tests/websockets/handlers/wrong_accept_key_wsh.py
new file mode 100755
index 0000000000..43240e1afb
--- /dev/null
+++ b/testing/web-platform/tests/websockets/handlers/wrong_accept_key_wsh.py
@@ -0,0 +1,19 @@
+#!/usr/bin/python
+
+import sys, urllib, time
+
+
+def web_socket_do_extra_handshake(request):
+ msg = (b'HTTP/1.1 101 Switching Protocols:\x0D\x0A'
+ b'Connection: Upgrade\x0D\x0A'
+ b'Upgrade: WebSocket\x0D\x0A'
+ b'Sec-WebSocket-Origin: %s\x0D\x0A'
+ b'Sec-WebSocket-Accept: thisisawrongacceptkey\x0D\x0A\x0D\x0A') % request.ws_origin.encode('UTF-8')
+ request.connection.write(msg)
+ return
+
+
+def web_socket_transfer_data(request):
+ while True:
+ request.ws_stream.send_message('test', binary=False)
+ return