From 43a97878ce14b72f0981164f87f2e35e14151312 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:22:09 +0200 Subject: Adding upstream version 110.0.1. Signed-off-by: Daniel Baumann --- .../tests/websockets/handlers/basic_auth_wsh.py | 26 +++ .../handlers/delayed-passive-close_wsh.py | 27 +++ .../tests/websockets/handlers/echo-cookie_wsh.py | 12 ++ .../websockets/handlers/echo-query_v13_wsh.py | 11 + .../tests/websockets/handlers/echo-query_wsh.py | 9 + .../websockets/handlers/echo_close_data_wsh.py | 20 ++ .../tests/websockets/handlers/echo_exit_wsh.py | 19 ++ .../tests/websockets/handlers/echo_raw_wsh.py | 16 ++ .../tests/websockets/handlers/echo_wsh.py | 36 ++++ .../tests/websockets/handlers/empty-message_wsh.py | 13 ++ .../handlers/handshake_no_extensions_wsh.py | 9 + .../handlers/handshake_no_protocol_wsh.py | 8 + .../websockets/handlers/handshake_protocol_wsh.py | 7 + .../websockets/handlers/handshake_sleep_2_wsh.py | 9 + .../tests/websockets/handlers/invalid_wsh.py | 8 + .../tests/websockets/handlers/msg_channel_wsh.py | 234 +++++++++++++++++++++ .../tests/websockets/handlers/origin_wsh.py | 11 + .../websockets/handlers/protocol_array_wsh.py | 14 ++ .../tests/websockets/handlers/protocol_wsh.py | 12 ++ .../handlers/receive-backpressure_wsh.py | 14 ++ .../tests/websockets/handlers/referrer_wsh.py | 12 ++ .../websockets/handlers/send-backpressure_wsh.py | 39 ++++ .../websockets/handlers/set-cookie-secure_wsh.py | 11 + .../websockets/handlers/set-cookie_http_wsh.py | 11 + .../tests/websockets/handlers/set-cookie_wsh.py | 11 + .../handlers/set-cookies-samesite_wsh.py | 25 +++ .../websockets/handlers/simple_handshake_wsh.py | 35 +++ .../tests/websockets/handlers/sleep_10_v13_wsh.py | 24 +++ .../handlers/stash_responder_blocking_wsh.py | 45 ++++ .../websockets/handlers/stash_responder_wsh.py | 45 ++++ .../websockets/handlers/wrong_accept_key_wsh.py | 19 ++ 31 files changed, 792 insertions(+) create mode 100755 testing/web-platform/tests/websockets/handlers/basic_auth_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/delayed-passive-close_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/echo-cookie_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/echo-query_v13_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/echo-query_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/echo_close_data_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/echo_exit_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/echo_raw_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/echo_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/empty-message_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/handshake_no_extensions_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/handshake_no_protocol_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/handshake_protocol_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/handshake_sleep_2_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/invalid_wsh.py create mode 100644 testing/web-platform/tests/websockets/handlers/msg_channel_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/origin_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/protocol_array_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/protocol_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/receive-backpressure_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/referrer_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/send-backpressure_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/set-cookie-secure_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/set-cookie_http_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/set-cookie_wsh.py create mode 100644 testing/web-platform/tests/websockets/handlers/set-cookies-samesite_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/simple_handshake_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/sleep_10_v13_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/stash_responder_blocking_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/stash_responder_wsh.py create mode 100755 testing/web-platform/tests/websockets/handlers/wrong_accept_key_wsh.py (limited to 'testing/web-platform/tests/websockets/handlers') 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/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..b0f1ddeb9a --- /dev/null +++ b/testing/web-platform/tests/websockets/handlers/sleep_10_v13_wsh.py @@ -0,0 +1,24 @@ +#!/usr/bin/python + +import sys, urllib, time +from mod_pywebsocket import msgutil + +def web_socket_do_extra_handshake(request): + request.connection.write(b'x') + time.sleep(2) + request.connection.write(b'x') + time.sleep(2) + request.connection.write(b'x') + time.sleep(2) + request.connection.write(b'x') + time.sleep(2) + request.connection.write(b'x') + time.sleep(2) + 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 -- cgit v1.2.3