From 23fa10bb6f08e991d238644e9d95a04bcc290655 Mon Sep 17 00:00:00 2001 From: Nidhi Jaju Date: Mon, 6 Sep 2021 17:06:47 -0700 Subject: [PATCH] Add CONNECT response WPTs for WebTransport This CL adds 7 test cases: - establish WebTransport connection with different status codes - echo the CONNECT request headers - setting a cookie in a CONNECT request, and then making sure the "cookie" header is not echoed back The echo-request-headers server handler is also added in this CL, which sends the CONNECT request headers over a unidirectional stream. Bug: 1201569 Change-Id: I30d8092af42dc2505cadbc15bf0573f8815758bd Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3134980 Commit-Queue: Nidhi Jaju Reviewed-by: Kenichi Ishibashi Reviewed-by: Yutaka Hirano Reviewed-by: Adam Rice Cr-Commit-Position: refs/heads/main@{#918651} --- webtransport/connect.sub.any.js | 73 +++++++++++++++++++ webtransport/handlers/echo-request-headers.py | 11 +++ .../webtransport-test-helpers.sub.js | 49 +++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 webtransport/connect.sub.any.js create mode 100644 webtransport/handlers/echo-request-headers.py create mode 100644 webtransport/resources/webtransport-test-helpers.sub.js diff --git a/webtransport/connect.sub.any.js b/webtransport/connect.sub.any.js new file mode 100644 index 00000000000000..9d2e2123467213 --- /dev/null +++ b/webtransport/connect.sub.any.js @@ -0,0 +1,73 @@ +// META: global=window,worker +// META: script=/common/get-host-info.sub.js +// META: script=resources/webtransport-test-helpers.sub.js + +promise_test(async t => { + const wt = new WebTransport(webtransport_url('custom-response.py?:status=200')); + await wt.ready; +}, 'WebTransport connection succeeds with status code 200'); + +promise_test(async t => { + const wt = new WebTransport(webtransport_url('custom-response.py?:status=204')); + await wt.ready; +}, 'WebTransport connection succeeds with status code 204'); + +promise_test(async t => { + const wt = new WebTransport(webtransport_url('custom-response.py?:status=301')); + await promise_rejects_js(t, TypeError, wt.ready, 'ready promise shoud be rejected'); + await promise_rejects_js(t, TypeError, wt.closed, 'closed promise should be rejected'); +}, 'WebTransport connection fails with status code 301'); + +promise_test(async t => { + const wt = new WebTransport(webtransport_url('custom-response.py?:status=401')); + await promise_rejects_js(t, TypeError, wt.ready, 'ready promise should be rejected'); + await promise_rejects_js(t, TypeError, wt.closed, 'closed promise should be rejected'); +}, 'WebTransport connection fails with status code 401'); + +promise_test(async t => { + const wt = new WebTransport(webtransport_url('custom-response.py?:status=404')); + await promise_rejects_js(t, TypeError, wt.ready, 'ready promise should be rejected'); + await promise_rejects_js(t, TypeError, wt.closed, 'closed promise should be rejected'); +}, 'WebTransport connection fails with status code 404'); + +promise_test(async t => { + // Create WebTransport session. + const wt = new WebTransport(webtransport_url('echo-request-headers.py')); + await wt.ready; + + // Read incoming unidirectional stream for echoed request headers. + const streams = await wt.incomingUnidirectionalStreams; + + const stream_reader = streams.getReader(); + const { value: recv_stream } = await stream_reader.read(); + stream_reader.releaseLock(); + + const request_headers = await read_stream_as_json(recv_stream); + + // Check the standard request headers. + check_and_remove_standard_headers(request_headers); +}, 'Echo back request headers'); + +promise_test(async t => { + // Create WebTransport session, and attach "Set-Cookie: foo=bar" to the response of + // the handshake. + const encodedSetCookie = encodeURIComponent('foo=bar'); + let wt = new WebTransport(webtransport_url('custom-response.py?set-cookie=' + encodedSetCookie)); + await wt.ready; + + wt = new WebTransport(webtransport_url('echo-request-headers.py')); + await wt.ready; + + // Read incoming unidirectional stream for echoed request headers. + const streams = await wt.incomingUnidirectionalStreams; + + const stream_reader = streams.getReader(); + const { value: recv_stream } = await stream_reader.read(); + stream_reader.releaseLock(); + + const request_headers = await read_stream_as_json(recv_stream); + + // Check cookie header is not echoed back. + check_and_remove_standard_headers(request_headers); + assert_equals(request_headers['cookie'], undefined); +}, 'Cookie header is not echoed back'); diff --git a/webtransport/handlers/echo-request-headers.py b/webtransport/handlers/echo-request-headers.py new file mode 100644 index 00000000000000..122d6f06019e32 --- /dev/null +++ b/webtransport/handlers/echo-request-headers.py @@ -0,0 +1,11 @@ +import json + + +def session_established(session): + headers = {} + for name, value in session.request_headers: + headers[name.decode('utf-8')] = value.decode('utf-8') + + stream_id = session.create_unidirectional_stream() + data = json.dumps(headers).encode('utf-8') + session.send_stream_data(stream_id, data, end_stream=True) diff --git a/webtransport/resources/webtransport-test-helpers.sub.js b/webtransport/resources/webtransport-test-helpers.sub.js new file mode 100644 index 00000000000000..90b1f083579e4e --- /dev/null +++ b/webtransport/resources/webtransport-test-helpers.sub.js @@ -0,0 +1,49 @@ +// The file including this must also include /common/get-host-info.sub.js to +// pick up the necessary constants. + +const HOST = get_host_info().ORIGINAL_HOST; +const PORT = '{{ports[webtransport-h3][0]}}'; +const BASE = `https://${HOST}:${PORT}`; + +// Create URL for WebTransport session. +function webtransport_url(handler) { + return `${BASE}/webtransport/handlers/${handler}`; +} + +// Decode all chunks in a given ReadableStream. +async function read_stream_as_json(stream) { + const decoder = new TextDecoderStream('utf-8'); + const decode_stream = stream.readable.pipeThrough(decoder); + const reader = decode_stream.getReader(); + + let chunks = ''; + while (true) { + const {value: chunk, done} = await reader.read(); + if (done) { + break; + } + chunks += chunk; + } + reader.releaseLock(); + + return JSON.parse(chunks); +} + +// Check the standard request headers and delete them, leaving any "unique" +// headers to check in the test. +function check_and_remove_standard_headers(headers) { + assert_equals(headers[':scheme'], 'https'); + delete headers[':scheme']; + assert_equals(headers[':method'], 'CONNECT'); + delete headers[':method']; + assert_equals(headers[':authority'], `${HOST}:${PORT}`); + delete headers[':authority']; + assert_equals(headers[':path'], '/webtransport/handlers/echo-request-headers.py'); + delete headers[':path']; + assert_equals(headers[':protocol'], 'webtransport'); + delete headers[':protocol']; + assert_equals(headers['origin'], `${get_host_info().ORIGIN}`); + delete headers['origin']; + assert_equals(headers['datagram-flow-id'], '0'); + delete headers['datagram-flow-id']; +}