From 5a55ae1b13497d1ae78ff01596f4342dce16d9be Mon Sep 17 00:00:00 2001 From: WPT Sync Bot Date: Tue, 25 Feb 2020 08:19:47 +0000 Subject: [PATCH] Update web-platform-tests to revision 2be2d7e3abcde5baded3448b85d0bb88e58d3cf7 --- ...ern.image.nonexistent-but-loading.html.ini | 4 + .../2d.pattern.image.nonexistent.html.ini | 4 + .../2d.pattern.image.nosrc.html.ini | 4 + .../2d.pattern.svgimage.nonexistent.html.ini | 4 + .../2d.pattern.svgimage.zeroheight.html.ini | 4 + .../2d.pattern.svgimage.zerowidth.html.ini | 4 + .../url/url-in-tags-revoke.window.js.ini | 4 +- tests/wpt/metadata/MANIFEST.json | 1370 ++++++----- .../WebCryptoAPI/idlharness.https.any.js.ini | 237 +- .../idlharness.tentative.https.window.js.ini | 24 +- .../CSS2/floats/hit-test-floats-002.html.ini | 4 - ....html.ini => hit-test-floats-004.html.ini} | 2 +- .../css/css-fonts/idlharness.html.ini | 3 + .../text-decoration-skip-ink-005.html.ini | 2 + .../text-decoration-skip-ink.html.ini | 3 + .../transform-scale-hittest.html.ini | 3 - ...on-from-ua-to-blocking-stylesheet.html.ini | 2 +- .../minmax-number-computed.html.ini | 6 + .../css/cssom-view/CaretPosition-001.html.ini | 4 + ...ryList-addListener-removeListener.html.ini | 3 + .../elementsFromPoint-iframes.html.ini | 3 - .../elementsFromPoint-invalid-cases.html.ini | 4 - .../css/cssom-view/idlharness.html.ini | 190 ++ .../metadata/css/cssom/idlharness.html.ini | 45 + .../metadata/css/geometry/interfaces.html.ini | 31 + .../css/geometry/interfaces.worker.js.ini | 31 + tests/wpt/metadata/dom/idlharness.any.js.ini | 15 + .../wpt/metadata/dom/idlharness.window.js.ini | 119 + ...ia-attribute-reflection.tentative.html.ini | 3 + .../api/cors/data-url-shared-worker.html.ini | 10 + .../fetch/api/cors/data-url-worker.html.ini | 4 + .../metadata/fetch/api/idlharness.any.js.ini | 12 + .../fetch/content-type/response.window.js.ini | 17 +- .../nosniff/parsing-nosniff.window.js.ini | 6 + ...mbedded-credentials.tentative.sub.html.ini | 3 +- .../fullscreen/idlharness.window.js.ini | 3 + .../traverse_the_history_1.html.ini | 4 - .../traverse_the_history_4.html.ini | 4 + .../traverse_the_history_5.html.ini | 4 + .../origin/origin-of-data-document.html.ini | 4 + ...ocument_domain_access_details.sub.html.ini | 25 +- ...main_feature_policy.tentative.sub.html.ini | 4 + .../sandbox-disallow-same-origin.html.ini | 4 + .../html/dom/idlharness.https.html.ini | 178 ++ .../html/dom/idlharness.worker.js.ini | 108 +- .../blob-data.https.html.ini | 26 + .../skip-document-with-fragment.html.ini | 4 + .../supported-elements.html.ini | 12 +- .../img-empty-alt-replaced.html.ini | 4 + ...rame_sandbox_popups_nonescaping-3.html.ini | 2 +- .../script-onerror-insertion-point-2.html.ini | 2 + .../webaudio/idlharness.https.window.js.ini | 165 ++ .../audiobuffersource-multi-channels.html.ini | 3 + .../sub-sample-buffer-stitching.html.ini | 6 + .../wpt/metadata/webgl/idlharness.any.js.ini | 24 + .../broadcastchannel/basics.html.ini | 4 + .../webmessaging/without-ports/017.html.ini | 5 - .../webmessaging/without-ports/018.html.ini | 5 - .../websockets/constructor/009.html.ini | 5 + .../websockets/constructor/022.html.ini | 5 + .../metadata/websockets/cookies/005.html.ini | 1 + .../003-sets-origin.worker.js.ini | 2 + .../websockets/opening-handshake/003.html.ini | 5 + .../websockets/opening-handshake/005.html.ini | 5 + .../metadata/webvr/idlharness.https.html.ini | 3 + .../dom-overlay/ar_dom_overlay.https.html.ini | 18 + .../events_session_squeeze.https.html.ini | 5 + .../webxr/idlharness.https.window.js.ini | 3 + .../semantics/multiple-workers/005.html.ini | 1 + ...t-sync-default-feature-policy.sub.html.ini | 4 + .../cross-origin-objects.html.ini | 4 + ...pattern.image.nonexistent-but-loading.html | 32 + .../2d.pattern.image.nonexistent.html | 29 + .../2d.pattern.image.nosrc.html | 30 + .../2d.pattern.image.zeroheight.html | 29 + .../2d.pattern.image.zerowidth.html | 29 + .../2d.pattern.svgimage.nonexistent.html | 29 + .../2d.pattern.svgimage.zeroheight.html | 29 + .../2d.pattern.svgimage.zerowidth.html | 29 + .../2dcontext/tools/gentestutils.py | 6 + .../2dcontext/tools/tests2d.yaml | 76 +- .../async-interfaces.https.html | 2 +- ...sync-navigator-clipboard-basics.https.html | 2 +- .../async-raw-write-read.tentative.https.html | 2 +- ...c-write-blobs-read-blobs-manual.https.html | 2 +- ...c-write-image-read-image-manual.https.html | 2 +- .../clipboard-events-synthetic.html | 2 +- .../clipboard-apis/clipboard-item.https.html | 2 +- .../clipboard-on-detached-iframe.https.html | 2 +- ...read-on-detaching-iframe-manual.https.html | 2 +- ...rite-on-detaching-iframe-manual.https.html | 2 +- ...-read-on-detached-iframe-manual.https.html | 2 +- ...dText-on-detached-iframe-manual.https.html | 2 +- .../events/copy-event-manual.html | 2 +- .../events/cut-event-manual.html | 2 +- .../events/paste-event-manual.html | 2 +- .../async-write-read-manual.https.html | 2 +- .../async-write-readText-manual.https.html | 2 +- .../async-writeText-read-manual.https.html | 2 +- ...async-writeText-readText-manual.https.html | 2 +- .../nonce-hiding/nonces.html | 2 +- .../nonce-hiding/script-nonces-hidden.html | 6 +- .../svgscript-nonces-hidden-meta.sub.html | 4 +- .../nonce-hiding/svgscript-nonces-hidden.html | 2 +- .../css-flexbox/padding-overflow-crash.html | 10 + .../percentage-heights-004-ref.html | 8 +- .../css-flexbox/percentage-heights-004.html | 11 +- .../css/css-page/page-size-011.xht | 20 + .../css/css-page/page-size-012.xht | 20 + .../text-decoration-skip-ink-005-notref.html | 22 + .../text-decoration-skip-ink-005.html | 27 + .../text-decoration-skip-ink.html | 4 +- .../css-values/minmax-number-computed.html | 4 + .../cssom/CSSStyleSheet-constructable.html | 37 +- .../attribute-case/syntax.html | 2 +- .../check-for-references.sh | 2 +- .../contain/reftest.list | 7 - .../multicol3/reftest.list | 6 - .../mozilla-central-reftests/sync-tests.sh | 2 +- .../custom-element-registry/define.html | 10 +- .../reactions/with-exceptions.html | 6 +- ...ic-markup-insertion-counter-construct.html | 10 +- ...ic-markup-insertion-counter-reactions.html | 10 +- .../docs/writing-tests/testharness-api.md | 6 +- .../events/scrolling/overscroll-deltas.html | 4 +- .../dom/nodes/Document-createElement.html | 5 +- .../dom/nodes/Document-createElementNS.html | 2 +- .../dom/nodes/Element-matches.js | 10 +- .../dom/nodes/Node-removeChild.html | 6 +- .../dom/nodes/ParentNode-querySelector-All.js | 12 +- .../aria-attribute-reflection.tentative.html | 11 + .../dom/ranges/Range-cloneContents.html | 10 +- .../dom/ranges/Range-extractContents.html | 10 +- .../dom/ranges/Range-insertNode.html | 2 +- .../dom/ranges/Range-surroundContents.html | 2 +- .../serviceworker-intercepted.https.html | 8 +- .../fetch/api/cors/data-url-iframe.html | 58 + .../api/cors/data-url-shared-worker.html | 53 + .../fetch/api/cors/data-url-worker.html | 50 + .../forced-colors-mode-18-ref.html | 36 + .../forced-colors-mode-18.html | 33 + .../forced-colors-mode-18.tentative-ref.html | 28 - .../forced-colors-mode-18.tentative.html | 27 - .../forced-colors-mode-26-ref.html | 15 + .../forced-colors-mode-26.html | 20 + .../forced-colors-mode-26.tentative-ref.html | 16 - .../forced-colors-mode-26.tentative.html | 23 - ...hor-fragment-form-submit-longfragment.html | 29 + .../anchor-fragment-form-submit-withpath.html | 35 + .../anchor-fragment-form-submit.html | 29 + .../navigating-across-documents/form.html | 5 + .../history/the-history-interface/001.html | 6 +- .../history/the-history-interface/002.html | 6 +- .../document_domain_setter.html | 3 +- .../none.https.html | 19 + .../require-corp.https.html | 79 + .../navigate-require-corp-same-site.sub.html | 29 + ...te-require-corp-same-site.sub.html.headers | 2 + .../shared-array-buffers/blob-data.https.html | 131 ++ .../blob-data.https.html.headers | 2 + .../img-empty-alt-replaced.html | 23 + .../the-input-element/maxlength-number.html | 19 + ...t-exception-vs-return-origin.sub.window.js | 44 +- .../bailout-exception-vs-return-xml.window.js | 4 +- ...e-effects-same-origin-domain.sub.window.js | 2 +- .../bailout-side-effects-xml.window.js | 11 +- ...-document-open-same-origin-domain.sub.html | 10 +- .../context-creation-offscreen.html | 2 +- .../interfaces/css-font-loading.idl | 15 +- .../interfaces/cssom-view.idl | 4 +- .../interfaces/resize-observer.idl | 7 +- .../interfaces/scroll-animations.idl | 4 +- .../interfaces/visual-viewport.idl | 1 + .../interfaces/web-bluetooth.idl | 3 +- .../interfaces/webaudio.idl | 4 - tests/wpt/web-platform-tests/lint.whitelist | 1 + ...o-disconnect-removes-observed-types.any.js | 19 + .../po-observe-repeated-type.any.js | 17 + .../portals/about-blank-cannot-host.html | 1 + .../portals/csp/frame-src.sub.html | 4 +- .../history-manipulation-inside-portal.html | 6 + ...ment-event-handler-content-attributes.html | 1 + .../portals/portal-activate-data.html | 1 + .../portals/portal-activate-event.html | 3 + .../portals/portal-non-http-navigation.html | 3 + .../portals/portal-onload-event.html | 1 + ...rtals-activate-empty-browsing-context.html | 2 + .../portals-activate-inside-iframe.html | 1 + .../portals-activate-inside-portal.html | 1 + .../portals-activate-no-browsing-context.html | 1 + .../portals/portals-activate-resolution.html | 1 + .../portals/portals-activate-twice.html | 2 + .../portals/portals-adopt-predecessor.html | 7 + .../portals-cross-origin-load.sub.html | 1 + .../portals/portals-focus.sub.html | 1 + .../portals/portals-host-exposure.sub.html | 1 + .../portals-host-hidden-after-activation.html | 1 + .../portals-host-post-message.sub.html | 4 + .../portals-navigate-after-adoption.html | 1 + .../portals/portals-nested.html | 1 + .../portals/portals-post-message.sub.html | 4 + .../portals-referrer-inherit-header.html | 2 +- .../portals-referrer-inherit-meta.html | 2 +- .../portals/portals-referrer.html | 6 +- .../portals/portals-rendering.html | 31 +- .../portals/portals-repeated-activate.html | 3 +- .../portals-set-src-after-activate.html | 1 + .../portals/predecessor-fires-unload.html | 2 + .../portals/xfo/portals-xfo-deny.sub.html | 2 + .../resize-observer/observe.html | 133 ++ .../wpt/web-platform-tests/resources/LICENSE | 30 - .../resources/chromium/webxr-test.js | 25 + .../resources/idlharness.js | 58 +- .../test/tests/functional/api-tests-1.html | 51 +- .../unit/IdlInterfaceMember/toString.html | 36 + .../resources/testharness.js | 93 +- .../scroll-animations/scroll-animation.html | 24 + .../scroll-to-text-fragment-target.html | 6 +- .../scroll-to-text-fragment.html | 6 + .../detached-context.https.html | 2 + .../fetch-event-handled.https.html | 82 + .../service-worker/multipart-image.https.html | 5 +- .../resources/fetch-event-handled-worker.js | 41 + .../shadow-dom/untriaged/LICENSE | 107 - .../web-platform-tests/tools/localpaths.py | 2 +- .../tools/pywebsocket/CONTRIBUTING | 11 - .../tools/pywebsocket/README | 17 - .../tools/pywebsocket/README.md | 8 - .../tools/pywebsocket/example/cookie_wsh.py | 32 - .../tools/pywebsocket/example/echo_client.py | 1129 --------- .../pywebsocket/example/fetch_benchmark.html | 163 -- .../pywebsocket/example/fetch_benchmark.js | 225 -- .../fetch_performance_test_iframe.html | 6 - .../example/performance_test_iframe.html | 6 - .../example/performance_test_iframe.js | 66 - .../pywebsocket/example/pywebsocket.conf | 42 - .../tools/pywebsocket/example/util_main.js | 65 - .../tools/pywebsocket/example/util_worker.js | 20 - .../pywebsocket/example/xhr_benchmark.html | 212 -- .../pywebsocket/example/xhr_benchmark.js | 292 --- .../pywebsocket/example/xhr_event_logger.html | 123 - .../example/xhr_performance_test_iframe.html | 6 - .../mod_pywebsocket/_stream_base.py | 181 -- .../mod_pywebsocket/_stream_hixie75.py | 229 -- .../mod_pywebsocket/handshake/hybi00.py | 293 --- .../mod_pywebsocket/headerparserhandler.py | 257 -- .../tools/pywebsocket/mod_pywebsocket/mux.py | 1885 --------------- .../pywebsocket/mod_pywebsocket/standalone.py | 1208 ---------- .../pywebsocket/mod_pywebsocket/stream.py | 56 - .../mod_pywebsocket/xhr_benchmark_handler.py | 121 - .../pywebsocket/test/client_for_testing.py | 1100 --------- .../test/mux_client_for_testing.py | 690 ------ .../pywebsocket/test/test_handshake_hybi00.py | 516 ---- .../tools/pywebsocket/test/test_msgutil.py | 1347 ----------- .../tools/pywebsocket/test/test_mux.py | 2088 ----------------- .../pywebsocket/test/test_stream_hixie75.py | 59 - .../web-platform-tests/tools/serve/serve.py | 14 +- .../third_party/pywebsocket3/.travis.yml | 17 + .../third_party/pywebsocket3/CONTRIBUTING | 30 + .../pywebsocket3}/LICENSE | 2 +- .../pywebsocket3}/MANIFEST.in | 0 .../tools/third_party/pywebsocket3/README.md | 36 + .../example/abort_handshake_wsh.py | 2 +- .../pywebsocket3}/example/abort_wsh.py | 2 +- .../example/arraybuffer_benchmark.html | 0 .../pywebsocket3}/example/bench_wsh.py | 7 +- .../pywebsocket3}/example/benchmark.html | 2 +- .../pywebsocket3}/example/benchmark.js | 32 +- .../example/benchmark_helper_wsh.py | 19 +- .../pywebsocket3}/example/close_wsh.py | 1 + .../pywebsocket3}/example/console.html | 0 .../pywebsocket3/example/cookie_wsh.py} | 40 +- .../pywebsocket3/example/echo_client.py | 696 ++++++ .../pywebsocket3}/example/echo_noext_wsh.py | 3 +- .../pywebsocket3}/example/echo_wsh.py | 3 +- .../pywebsocket3}/example/handler_map.txt | 0 .../pywebsocket3}/example/hsts_wsh.py | 0 .../example/internal_error_wsh.py | 2 +- .../pywebsocket3}/example/origin_check_wsh.py | 0 .../example/performance_test_iframe.html} | 47 +- .../example/performance_test_iframe.js | 86 + .../pywebsocket3}/example/special_headers.cgi | 2 - .../pywebsocket3}/example/util.js | 8 +- .../pywebsocket3/example/util_main.js | 89 + .../pywebsocket3/example/util_worker.js | 44 + .../pywebsocket3}/mod_pywebsocket/__init__.py | 90 +- .../mod_pywebsocket/_stream_exceptions.py} | 71 +- .../pywebsocket3}/mod_pywebsocket/common.py | 45 +- .../pywebsocket3}/mod_pywebsocket/dispatch.py | 82 +- .../mod_pywebsocket/extensions.py | 395 +--- .../mod_pywebsocket/fast_masking.i | 0 .../mod_pywebsocket/handshake/__init__.py | 19 +- .../mod_pywebsocket/handshake/_base.py | 21 +- .../mod_pywebsocket/handshake/hybi.py | 179 +- .../mod_pywebsocket/http_header_util.py | 27 +- .../mod_pywebsocket/memorizingfile.py | 13 +- .../pywebsocket3}/mod_pywebsocket/msgutil.py | 25 +- .../mod_pywebsocket/request_handler.py | 319 +++ .../mod_pywebsocket/server_util.py | 87 + .../mod_pywebsocket/standalone.py | 478 ++++ .../pywebsocket3/mod_pywebsocket/stream.py} | 306 ++- .../pywebsocket3}/mod_pywebsocket/util.py | 119 +- .../mod_pywebsocket/websocket_server.py | 285 +++ .../pywebsocket3}/setup.py | 54 +- .../pywebsocket3}/test/__init__.py | 0 .../pywebsocket3}/test/cert/cacert.pem | 0 .../pywebsocket3}/test/cert/cert.pem | 0 .../pywebsocket3}/test/cert/client_cert.p12 | Bin .../pywebsocket3}/test/cert/key.pem | 0 .../pywebsocket3/test/client_for_testing.py | 719 ++++++ .../pywebsocket3}/test/mock.py | 68 +- .../pywebsocket3}/test/run_all.py | 30 +- .../pywebsocket3}/test/set_sys_path.py | 6 +- .../pywebsocket3}/test/test_dispatch.py | 198 +- .../pywebsocket3}/test/test_endtoend.py | 449 ++-- .../pywebsocket3}/test/test_extensions.py | 95 +- .../pywebsocket3}/test/test_handshake.py | 76 +- .../pywebsocket3}/test/test_handshake_hybi.py | 265 +-- .../test/test_http_header_util.py | 23 +- .../pywebsocket3}/test/test_memorizingfile.py | 24 +- .../pywebsocket3}/test/test_mock.py | 56 +- .../pywebsocket3/test/test_msgutil.py | 916 ++++++++ .../pywebsocket3}/test/test_stream.py | 29 +- .../pywebsocket3}/test/test_util.py | 123 +- .../pywebsocket3}/test/testdata/README | 0 .../testdata/handlers/abort_by_user_wsh.py | 1 - .../test/testdata/handlers/blank_wsh.py | 1 - .../testdata/handlers/origin_check_wsh.py | 5 +- .../handlers/sub/exception_in_transfer_wsh.py | 2 - .../handlers/sub/no_wsh_at_the_end.py | 8 +- .../testdata/handlers/sub/non_callable_wsh.py | 4 - .../test/testdata/handlers/sub/plain_wsh.py | 5 +- .../handlers/sub/wrong_handshake_sig_wsh.py | 8 +- .../handlers/sub/wrong_transfer_sig_wsh.py | 8 +- .../pywebsocket3}/test/testdata/hello.pl | 0 .../tools/webdriver/webdriver/client.py | 4 +- .../tools/webdriver/webdriver/transport.py | 20 +- .../tools/wptrunner/LICENSE | 30 - .../wptrunner/wptrunner/executors/base.py | 12 +- .../wptrunner/executors/executorwebdriver.py | 12 +- .../tools/wptrunner/wptrunner/update/sync.py | 41 - .../web-platform-tests/tools/wptserve/LICENSE | 30 - .../tests/functional/test_response.py | 14 +- .../web-nfc/NDEFRecord_constructor.https.html | 97 +- ...ecredential-badargs-attestation.https.html | 4 +- ...dential-badargs-authnrselection.https.html | 27 +- ...atecredential-badargs-challenge.https.html | 4 +- .../createcredential-badargs-rp.https.html | 4 +- .../createcredential-badargs-user.https.html | 2 + ...tecredential-excludecredentials.https.html | 2 + .../createcredential-extensions.https.html | 2 + .../createcredential-passing.https.html | 4 +- ...eatecredential-pubkeycredparams.https.html | 12 +- .../createcredential-timeout.https.html | 71 +- .../getcredential-badargs-rpid.https.html | 4 +- ...ential-badargs-userverification.https.html | 6 +- .../getcredential-extensions.https.html | 2 + .../webauthn/getcredential-passing.https.html | 4 +- .../webauthn/getcredential-timeout.https.html | 64 +- .../web-platform-tests/webauthn/helpers.js | 64 +- .../webauthn/securecontext.http.html | 56 +- .../webauthn/securecontext.https.html | 30 +- .../webmessaging/broadcastchannel/basics.html | 16 +- .../webmessaging/postMessage_Document.htm | 2 +- .../webmessaging/postMessage_Function.htm | 2 +- .../postMessage_dup_transfer_objects.htm | 2 +- .../postMessage_invalid_targetOrigin.htm | 2 +- ...RTCPeerConnection-onnegotiationneeded.html | 22 + ...nection-setRemoteDescription-rollback.html | 15 + .../webrtc/protocol/ice-ufragpwd.html | 55 + .../handlers/simple_handshake_wsh.py | 2 +- .../dom-overlay/ar_dom_overlay.https.html | 202 +- .../webxr/events_session_squeeze.https.html | 137 ++ .../workers/Worker-constructor-proto.any.js | 4 +- .../xhr/open-url-multi-window-2.htm | 9 +- .../xhr/open-url-multi-window-3.htm | 7 +- .../xhr/open-url-multi-window-5.htm | 8 +- 377 files changed, 9772 insertions(+), 15950 deletions(-) create mode 100644 tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.image.nonexistent-but-loading.html.ini create mode 100644 tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.image.nonexistent.html.ini create mode 100644 tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.image.nosrc.html.ini create mode 100644 tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.nonexistent.html.ini create mode 100644 tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zeroheight.html.ini create mode 100644 tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zerowidth.html.ini delete mode 100644 tests/wpt/metadata/css/CSS2/floats/hit-test-floats-002.html.ini rename tests/wpt/metadata/css/CSS2/floats/{hit-test-floats-003.html.ini => hit-test-floats-004.html.ini} (67%) create mode 100644 tests/wpt/metadata/css/css-text-decor/text-decoration-skip-ink-005.html.ini create mode 100644 tests/wpt/metadata/css/cssom-view/CaretPosition-001.html.ini delete mode 100644 tests/wpt/metadata/css/cssom-view/elementsFromPoint-invalid-cases.html.ini create mode 100644 tests/wpt/metadata/fetch/api/cors/data-url-shared-worker.html.ini create mode 100644 tests/wpt/metadata/fetch/api/cors/data-url-worker.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/traverse_the_history_1.html.ini create mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/traverse_the_history_4.html.ini create mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/traverse_the_history_5.html.ini create mode 100644 tests/wpt/metadata/html/browsers/origin/origin-of-data-document.html.ini create mode 100644 tests/wpt/metadata/html/browsers/sandboxing/sandbox-disallow-same-origin.html.ini create mode 100644 tests/wpt/metadata/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html.ini create mode 100644 tests/wpt/metadata/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-empty-alt-replaced.html.ini create mode 100644 tests/wpt/metadata/html/semantics/scripting-1/the-script-element/script-onerror-insertion-point-2.html.ini create mode 100644 tests/wpt/metadata/webmessaging/broadcastchannel/basics.html.ini delete mode 100644 tests/wpt/metadata/webmessaging/without-ports/017.html.ini delete mode 100644 tests/wpt/metadata/webmessaging/without-ports/018.html.ini create mode 100644 tests/wpt/metadata/websockets/constructor/009.html.ini create mode 100644 tests/wpt/metadata/websockets/constructor/022.html.ini create mode 100644 tests/wpt/metadata/websockets/opening-handshake/003-sets-origin.worker.js.ini create mode 100644 tests/wpt/metadata/websockets/opening-handshake/003.html.ini create mode 100644 tests/wpt/metadata/websockets/opening-handshake/005.html.ini create mode 100644 tests/wpt/metadata/webxr/events_session_squeeze.https.html.ini create mode 100644 tests/wpt/mozilla/meta/mozilla/cross-origin-objects/cross-origin-objects.html.ini create mode 100644 tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.nonexistent-but-loading.html create mode 100644 tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.nonexistent.html create mode 100644 tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.nosrc.html create mode 100644 tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.zeroheight.html create mode 100644 tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.zerowidth.html create mode 100644 tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.nonexistent.html create mode 100644 tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zeroheight.html create mode 100644 tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zerowidth.html create mode 100644 tests/wpt/web-platform-tests/css/css-flexbox/padding-overflow-crash.html create mode 100644 tests/wpt/web-platform-tests/css/css-page/page-size-011.xht create mode 100644 tests/wpt/web-platform-tests/css/css-page/page-size-012.xht create mode 100644 tests/wpt/web-platform-tests/css/css-text-decor/reference/text-decoration-skip-ink-005-notref.html create mode 100644 tests/wpt/web-platform-tests/css/css-text-decor/text-decoration-skip-ink-005.html create mode 100644 tests/wpt/web-platform-tests/fetch/api/cors/data-url-iframe.html create mode 100644 tests/wpt/web-platform-tests/fetch/api/cors/data-url-shared-worker.html create mode 100644 tests/wpt/web-platform-tests/fetch/api/cors/data-url-worker.html create mode 100644 tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18-ref.html create mode 100644 tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18.html delete mode 100644 tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18.tentative-ref.html delete mode 100644 tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18.tentative.html create mode 100644 tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26-ref.html create mode 100644 tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26.html delete mode 100644 tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26.tentative-ref.html delete mode 100644 tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26.tentative.html create mode 100644 tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-longfragment.html create mode 100644 tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-withpath.html create mode 100644 tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit.html create mode 100644 tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/form.html create mode 100644 tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html create mode 100644 tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html.headers create mode 100644 tests/wpt/web-platform-tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html create mode 100644 tests/wpt/web-platform-tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html.headers create mode 100644 tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-empty-alt-replaced.html create mode 100644 tests/wpt/web-platform-tests/html/semantics/forms/the-input-element/maxlength-number.html create mode 100644 tests/wpt/web-platform-tests/performance-timeline/po-disconnect-removes-observed-types.any.js create mode 100644 tests/wpt/web-platform-tests/performance-timeline/po-observe-repeated-type.any.js delete mode 100644 tests/wpt/web-platform-tests/resources/LICENSE create mode 100644 tests/wpt/web-platform-tests/resources/test/tests/unit/IdlInterfaceMember/toString.html create mode 100644 tests/wpt/web-platform-tests/service-workers/service-worker/fetch-event-handled.https.html create mode 100644 tests/wpt/web-platform-tests/service-workers/service-worker/resources/fetch-event-handled-worker.js delete mode 100644 tests/wpt/web-platform-tests/shadow-dom/untriaged/LICENSE delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/CONTRIBUTING delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/README delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/README.md delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/example/cookie_wsh.py delete mode 100755 tests/wpt/web-platform-tests/tools/pywebsocket/example/echo_client.py delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/example/fetch_benchmark.html delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/example/fetch_benchmark.js delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/example/fetch_performance_test_iframe.html delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/example/performance_test_iframe.html delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/example/performance_test_iframe.js delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/example/pywebsocket.conf delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/example/util_main.js delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/example/util_worker.js delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_benchmark.html delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_benchmark.js delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_event_logger.html delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_performance_test_iframe.html delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/_stream_base.py delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/_stream_hixie75.py delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/handshake/hybi00.py delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/headerparserhandler.py delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/mux.py delete mode 100755 tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/standalone.py delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/stream.py delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/xhr_benchmark_handler.py delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/test/client_for_testing.py delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/test/mux_client_for_testing.py delete mode 100755 tests/wpt/web-platform-tests/tools/pywebsocket/test/test_handshake_hybi00.py delete mode 100755 tests/wpt/web-platform-tests/tools/pywebsocket/test/test_msgutil.py delete mode 100644 tests/wpt/web-platform-tests/tools/pywebsocket/test/test_mux.py delete mode 100755 tests/wpt/web-platform-tests/tools/pywebsocket/test/test_stream_hixie75.py create mode 100644 tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/.travis.yml create mode 100644 tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/CONTRIBUTING rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/LICENSE (98%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/MANIFEST.in (100%) create mode 100644 tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/README.md rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/abort_handshake_wsh.py (97%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/abort_wsh.py (97%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/arraybuffer_benchmark.html (100%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/bench_wsh.py (96%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/benchmark.html (99%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/benchmark.js (75%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/benchmark_helper_wsh.py (88%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/close_wsh.py (98%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/console.html (100%) rename tests/wpt/web-platform-tests/tools/{pywebsocket/example/eventsource.cgi => third_party/pywebsocket3/example/cookie_wsh.py} (65%) mode change 100755 => 100644 create mode 100755 tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/echo_client.py rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/echo_noext_wsh.py (97%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/echo_wsh.py (97%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/handler_map.txt (100%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/hsts_wsh.py (100%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/internal_error_wsh.py (97%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/origin_check_wsh.py (100%) rename tests/wpt/web-platform-tests/tools/{pywebsocket/example/eventsource.html => third_party/pywebsocket3/example/performance_test_iframe.html} (59%) create mode 100644 tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/performance_test_iframe.js rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/special_headers.cgi (99%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/example/util.js (97%) create mode 100644 tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/util_main.js create mode 100644 tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/util_worker.js rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/mod_pywebsocket/__init__.py (63%) rename tests/wpt/web-platform-tests/tools/{pywebsocket/test/endtoend_with_external_server.py => third_party/pywebsocket3/mod_pywebsocket/_stream_exceptions.py} (52%) mode change 100755 => 100644 rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/mod_pywebsocket/common.py (88%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/mod_pywebsocket/dispatch.py (86%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/mod_pywebsocket/extensions.py (55%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/mod_pywebsocket/fast_masking.i (100%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/mod_pywebsocket/handshake/__init__.py (89%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/mod_pywebsocket/handshake/_base.py (93%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/mod_pywebsocket/handshake/hybi.py (71%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/mod_pywebsocket/http_header_util.py (92%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/mod_pywebsocket/memorizingfile.py (96%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/mod_pywebsocket/msgutil.py (93%) create mode 100644 tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/mod_pywebsocket/request_handler.py create mode 100644 tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/mod_pywebsocket/server_util.py create mode 100755 tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/mod_pywebsocket/standalone.py rename tests/wpt/web-platform-tests/tools/{pywebsocket/mod_pywebsocket/_stream_hybi.py => third_party/pywebsocket3/mod_pywebsocket/stream.py} (76%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/mod_pywebsocket/util.py (83%) create mode 100644 tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/mod_pywebsocket/websocket_server.py rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/setup.py (62%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/__init__.py (100%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/cert/cacert.pem (100%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/cert/cert.pem (100%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/cert/client_cert.p12 (100%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/cert/key.pem (100%) create mode 100644 tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/test/client_for_testing.py rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/mock.py (82%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/run_all.py (81%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/set_sys_path.py (97%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/test_dispatch.py (62%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/test_endtoend.py (57%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/test_extensions.py (69%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/test_handshake.py (78%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/test_handshake_hybi.py (62%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/test_http_header_util.py (86%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/test_memorizingfile.py (88%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/test_mock.py (76%) create mode 100755 tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/test/test_msgutil.py rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/test_stream.py (76%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/test_util.py (60%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/testdata/README (100%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/testdata/handlers/abort_by_user_wsh.py (99%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/testdata/handlers/blank_wsh.py (99%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/testdata/handlers/origin_check_wsh.py (91%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/testdata/handlers/sub/exception_in_transfer_wsh.py (99%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/testdata/handlers/sub/no_wsh_at_the_end.py (90%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/testdata/handlers/sub/non_callable_wsh.py (99%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/testdata/handlers/sub/plain_wsh.py (88%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py (90%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py (90%) rename tests/wpt/web-platform-tests/tools/{pywebsocket => third_party/pywebsocket3}/test/testdata/hello.pl (100%) delete mode 100644 tests/wpt/web-platform-tests/tools/wptrunner/LICENSE delete mode 100644 tests/wpt/web-platform-tests/tools/wptserve/LICENSE create mode 100644 tests/wpt/web-platform-tests/webrtc/protocol/ice-ufragpwd.html create mode 100644 tests/wpt/web-platform-tests/webxr/events_session_squeeze.https.html diff --git a/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.image.nonexistent-but-loading.html.ini b/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.image.nonexistent-but-loading.html.ini new file mode 100644 index 00000000000..79f39c53155 --- /dev/null +++ b/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.image.nonexistent-but-loading.html.ini @@ -0,0 +1,4 @@ +[2d.pattern.image.nonexistent-but-loading.html] + [Canvas test: 2d.pattern.image.nonexistent-but-loading] + expected: FAIL + diff --git a/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.image.nonexistent.html.ini b/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.image.nonexistent.html.ini new file mode 100644 index 00000000000..d1cbf15e597 --- /dev/null +++ b/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.image.nonexistent.html.ini @@ -0,0 +1,4 @@ +[2d.pattern.image.nonexistent.html] + [Canvas test: 2d.pattern.image.nonexistent] + expected: FAIL + diff --git a/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.image.nosrc.html.ini b/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.image.nosrc.html.ini new file mode 100644 index 00000000000..27fe75b75e7 --- /dev/null +++ b/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.image.nosrc.html.ini @@ -0,0 +1,4 @@ +[2d.pattern.image.nosrc.html] + [Canvas test: 2d.pattern.image.nosrc] + expected: FAIL + diff --git a/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.nonexistent.html.ini b/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.nonexistent.html.ini new file mode 100644 index 00000000000..c6ac9ddf165 --- /dev/null +++ b/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.nonexistent.html.ini @@ -0,0 +1,4 @@ +[2d.pattern.svgimage.nonexistent.html] + [Canvas test: 2d.pattern.svgimage.nonexistent] + expected: FAIL + diff --git a/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zeroheight.html.ini b/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zeroheight.html.ini new file mode 100644 index 00000000000..50d7ad171fb --- /dev/null +++ b/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zeroheight.html.ini @@ -0,0 +1,4 @@ +[2d.pattern.svgimage.zeroheight.html] + [Canvas test: 2d.pattern.svgimage.zeroheight] + expected: FAIL + diff --git a/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zerowidth.html.ini b/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zerowidth.html.ini new file mode 100644 index 00000000000..40ca3dd584e --- /dev/null +++ b/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zerowidth.html.ini @@ -0,0 +1,4 @@ +[2d.pattern.svgimage.zerowidth.html] + [Canvas test: 2d.pattern.svgimage.zerowidth] + expected: FAIL + diff --git a/tests/wpt/metadata/FileAPI/url/url-in-tags-revoke.window.js.ini b/tests/wpt/metadata/FileAPI/url/url-in-tags-revoke.window.js.ini index 76b398963ae..fc007d26cb3 100644 --- a/tests/wpt/metadata/FileAPI/url/url-in-tags-revoke.window.js.ini +++ b/tests/wpt/metadata/FileAPI/url/url-in-tags-revoke.window.js.ini @@ -10,10 +10,10 @@ expected: FAIL [Opening a blob URL in a noopener about:blank window immediately before revoking it works.] - expected: FAIL + expected: TIMEOUT [Opening a blob URL in a new window by clicking an tag works immediately before revoking the URL.] - expected: FAIL + expected: TIMEOUT [Fetching a blob URL immediately before revoking it works in + + + + + +

2d.pattern.image.nonexistent-but-loading

+

+ + +

Actual output:

+

FAIL (fallback content)

+ + + + diff --git a/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.nonexistent.html b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.nonexistent.html new file mode 100644 index 00000000000..c6c33b5c522 --- /dev/null +++ b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.nonexistent.html @@ -0,0 +1,29 @@ + + +Canvas test: 2d.pattern.image.nonexistent + + + + + + +

2d.pattern.image.nonexistent

+

+ + +

Actual output:

+

FAIL (fallback content)

+ + + + + diff --git a/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.nosrc.html b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.nosrc.html new file mode 100644 index 00000000000..0c275f82ac7 --- /dev/null +++ b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.nosrc.html @@ -0,0 +1,30 @@ + + +Canvas test: 2d.pattern.image.nosrc + + + + + + +

2d.pattern.image.nosrc

+

+ + +

Actual output:

+

FAIL (fallback content)

+ + + + diff --git a/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.zeroheight.html b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.zeroheight.html new file mode 100644 index 00000000000..143eec9d2cf --- /dev/null +++ b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.zeroheight.html @@ -0,0 +1,29 @@ + + +Canvas test: 2d.pattern.image.zeroheight + + + + + + +

2d.pattern.image.zeroheight

+

+ + +

Actual output:

+

FAIL (fallback content)

+ + + + + diff --git a/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.zerowidth.html b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.zerowidth.html new file mode 100644 index 00000000000..66890cfe2e0 --- /dev/null +++ b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.image.zerowidth.html @@ -0,0 +1,29 @@ + + +Canvas test: 2d.pattern.image.zerowidth + + + + + + +

2d.pattern.image.zerowidth

+

+ + +

Actual output:

+

FAIL (fallback content)

+ + + + + diff --git a/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.nonexistent.html b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.nonexistent.html new file mode 100644 index 00000000000..756c2094c96 --- /dev/null +++ b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.nonexistent.html @@ -0,0 +1,29 @@ + + +Canvas test: 2d.pattern.svgimage.nonexistent + + + + + + +

2d.pattern.svgimage.nonexistent

+

+ + +

Actual output:

+

FAIL (fallback content)

+ + + + + diff --git a/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zeroheight.html b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zeroheight.html new file mode 100644 index 00000000000..2cb1374d2db --- /dev/null +++ b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zeroheight.html @@ -0,0 +1,29 @@ + + +Canvas test: 2d.pattern.svgimage.zeroheight + + + + + + +

2d.pattern.svgimage.zeroheight

+

+ + +

Actual output:

+

FAIL (fallback content)

+ + + + + diff --git a/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zerowidth.html b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zerowidth.html new file mode 100644 index 00000000000..0b3f70ddf00 --- /dev/null +++ b/tests/wpt/web-platform-tests/2dcontext/fill-and-stroke-styles/2d.pattern.svgimage.zerowidth.html @@ -0,0 +1,29 @@ + + +Canvas test: 2d.pattern.svgimage.zerowidth + + + + + + +

2d.pattern.svgimage.zerowidth

+

+ + +

Actual output:

+

FAIL (fallback content)

+ + + + + diff --git a/tests/wpt/web-platform-tests/2dcontext/tools/gentestutils.py b/tests/wpt/web-platform-tests/2dcontext/tools/gentestutils.py index bd1e3c75768..64ad669b329 100644 --- a/tests/wpt/web-platform-tests/2dcontext/tools/gentestutils.py +++ b/tests/wpt/web-platform-tests/2dcontext/tools/gentestutils.py @@ -361,6 +361,12 @@ def genTestUtils(TESTOUTPUTDIR, IMAGEOUTPUTDIR, TEMPLATEFILE, NAME2DIRFILE, ISOF used_images[i] = 1 i = '../images/%s' % i images += '\n' % (i,id) + for i in test.get('svgimages', []): + id = i.split('/')[-1] + if '/' not in i: + used_images[i] = 1 + i = '../images/%s' % i + images += '\n' % (i,id) images = images.replace("../images/", "/images/") fonts = '' diff --git a/tests/wpt/web-platform-tests/2dcontext/tools/tests2d.yaml b/tests/wpt/web-platform-tests/2dcontext/tools/tests2d.yaml index 6b971a4b2fc..42a572995a6 100644 --- a/tests/wpt/web-platform-tests/2dcontext/tools/tests2d.yaml +++ b/tests/wpt/web-platform-tests/2dcontext/tools/tests2d.yaml @@ -2898,13 +2898,87 @@ - name: 2d.pattern.image.broken testing: - - 2d.pattern.incomplete.image + - 2d.pattern.broken.image images: - broken.png code: | var img = document.getElementById('broken.png'); @assert ctx.createPattern(img, 'repeat') === null; +- name: 2d.pattern.image.nonexistent + testing: + - 2d.pattern.nonexistent.image + images: + - no-such-image-really.png + code: | + var img = document.getElementById('no-such-image-really.png'); + @assert throws INVALID_STATE_ERR ctx.createPattern(img, 'repeat'); + +- name: 2d.pattern.svgimage.nonexistent + testing: + - 2d.pattern.nonexistent.svgimage + svgimages: + - no-such-image-really.png + code: | + var img = document.getElementById('no-such-image-really.png'); + @assert throws INVALID_STATE_ERR ctx.createPattern(img, 'repeat'); + +- name: 2d.pattern.image.nonexistent-but-loading + testing: + - 2d.pattern.nonexistent-but-loading.image + code: | + var img = document.createElement("img"); + img.src = "/images/no-such-image-really.png"; + @assert ctx.createPattern(img, 'repeat') === null; + var img = document.createElementNS("http://www.w3.org/2000/svg", "image"); + img.src = "/images/no-such-image-really.png"; + @assert ctx.createPattern(img, 'repeat') === null; + +- name: 2d.pattern.image.nosrc + testing: + - 2d.pattern.nosrc.image + code: | + var img = document.createElement("img"); + @assert ctx.createPattern(img, 'repeat') === null; + var img = document.createElementNS("http://www.w3.org/2000/svg", "image"); + @assert ctx.createPattern(img, 'repeat') === null; + +- name: 2d.pattern.image.zerowidth + testing: + - 2d.pattern.zerowidth.image + images: + - red-zerowidth.svg + code: | + var img = document.getElementById('red-zerowidth.svg'); + @assert ctx.createPattern(img, 'repeat') === null; + +- name: 2d.pattern.image.zeroheight + testing: + - 2d.pattern.zeroheight.image + images: + - red-zeroheight.svg + code: | + var img = document.getElementById('red-zeroheight.svg'); + @assert ctx.createPattern(img, 'repeat') === null; + +- name: 2d.pattern.svgimage.zerowidth + testing: + - 2d.pattern.zerowidth.svgimage + svgimages: + - red-zerowidth.svg + code: | + var img = document.getElementById('red-zerowidth.svg'); + @assert ctx.createPattern(img, 'repeat') === null; + +- name: 2d.pattern.svgimage.zeroheight + testing: + - 2d.pattern.zeroheight.svgimage + svgimages: + - red-zeroheight.svg + code: | + var img = document.getElementById('red-zeroheight.svg'); + @assert ctx.createPattern(img, 'repeat') === null; + - name: 2d.pattern.repeat.empty testing: - 2d.pattern.missing diff --git a/tests/wpt/web-platform-tests/clipboard-apis/async-interfaces.https.html b/tests/wpt/web-platform-tests/clipboard-apis/async-interfaces.https.html index 1c423e95b66..06b777fbe32 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/async-interfaces.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/async-interfaces.https.html @@ -1,4 +1,4 @@ - + Clipboard IDL test diff --git a/tests/wpt/web-platform-tests/clipboard-apis/async-navigator-clipboard-basics.https.html b/tests/wpt/web-platform-tests/clipboard-apis/async-navigator-clipboard-basics.https.html index 22358f16d96..65759186a6c 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/async-navigator-clipboard-basics.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/async-navigator-clipboard-basics.https.html @@ -1,4 +1,4 @@ - + Async Clipboard input type validation tests diff --git a/tests/wpt/web-platform-tests/clipboard-apis/async-raw-write-read.tentative.https.html b/tests/wpt/web-platform-tests/clipboard-apis/async-raw-write-read.tentative.https.html index 6cfa2dbcca7..a6889d61efc 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/async-raw-write-read.tentative.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/async-raw-write-read.tentative.https.html @@ -1,4 +1,4 @@ - + Async Clipboard raw write -> Async Clipboard raw read tests diff --git a/tests/wpt/web-platform-tests/clipboard-apis/async-write-blobs-read-blobs-manual.https.html b/tests/wpt/web-platform-tests/clipboard-apis/async-write-blobs-read-blobs-manual.https.html index b5f0f3d9dc1..f53d7f9c36e 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/async-write-blobs-read-blobs-manual.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/async-write-blobs-read-blobs-manual.https.html @@ -1,4 +1,4 @@ - + Async Clipboard write blobs -> read blobs tests diff --git a/tests/wpt/web-platform-tests/clipboard-apis/async-write-image-read-image-manual.https.html b/tests/wpt/web-platform-tests/clipboard-apis/async-write-image-read-image-manual.https.html index 6381b453abf..71d14984953 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/async-write-image-read-image-manual.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/async-write-image-read-image-manual.https.html @@ -1,4 +1,4 @@ -<!DOCTYPE html> +<!doctype html> <meta charset="utf-8"> <title> Async Clipboard write [image/png ClipboardItem] -> diff --git a/tests/wpt/web-platform-tests/clipboard-apis/clipboard-events-synthetic.html b/tests/wpt/web-platform-tests/clipboard-apis/clipboard-events-synthetic.html index fa40d8c2003..87868297526 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/clipboard-events-synthetic.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/clipboard-events-synthetic.html @@ -1,4 +1,4 @@ -<!DOCTYPE html> +<!doctype html> <title>synthetic clipboard events should not be composed diff --git a/tests/wpt/web-platform-tests/clipboard-apis/clipboard-item.https.html b/tests/wpt/web-platform-tests/clipboard-apis/clipboard-item.https.html index d9dcee76419..afec778eb99 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/clipboard-item.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/clipboard-item.https.html @@ -1,4 +1,4 @@ - + ClipboardItem tests diff --git a/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/clipboard-on-detached-iframe.https.html b/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/clipboard-on-detached-iframe.https.html index 9b43b3695af..84fce803236 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/clipboard-on-detached-iframe.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/clipboard-on-detached-iframe.https.html @@ -1,4 +1,4 @@ - + Clipboard API on detached iframe diff --git a/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/read-on-detaching-iframe-manual.https.html b/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/read-on-detaching-iframe-manual.https.html index e4a4283c900..8361ceff125 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/read-on-detaching-iframe-manual.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/read-on-detaching-iframe-manual.https.html @@ -1,4 +1,4 @@ - + navigator.clipboard read on detaching iframe diff --git a/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/write-on-detaching-iframe-manual.https.html b/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/write-on-detaching-iframe-manual.https.html index 033cc0fd9f1..4a3dbd7dc32 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/write-on-detaching-iframe-manual.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/write-on-detaching-iframe-manual.https.html @@ -1,4 +1,4 @@ - + navigator.clipboard write on detaching iframe diff --git a/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/write-read-on-detached-iframe-manual.https.html b/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/write-read-on-detached-iframe-manual.https.html index 31168092480..af12ff8d880 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/write-read-on-detached-iframe-manual.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/write-read-on-detached-iframe-manual.https.html @@ -1,4 +1,4 @@ - + navigator.clipboard read and write on detached iframe diff --git a/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/writeText-readText-on-detached-iframe-manual.https.html b/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/writeText-readText-on-detached-iframe-manual.https.html index 883e8dbd32e..c2d8be1ace2 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/writeText-readText-on-detached-iframe-manual.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/detached-iframe/writeText-readText-on-detached-iframe-manual.https.html @@ -1,4 +1,4 @@ - + navigator.clipboard readText and writeText on detached iframe diff --git a/tests/wpt/web-platform-tests/clipboard-apis/events/copy-event-manual.html b/tests/wpt/web-platform-tests/clipboard-apis/events/copy-event-manual.html index 6f687af196f..9f9f1950e7c 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/events/copy-event-manual.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/events/copy-event-manual.html @@ -1,4 +1,4 @@ - + The copy event diff --git a/tests/wpt/web-platform-tests/clipboard-apis/events/cut-event-manual.html b/tests/wpt/web-platform-tests/clipboard-apis/events/cut-event-manual.html index c5593171754..72c11ec3b98 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/events/cut-event-manual.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/events/cut-event-manual.html @@ -1,4 +1,4 @@ - + The cut event diff --git a/tests/wpt/web-platform-tests/clipboard-apis/events/paste-event-manual.html b/tests/wpt/web-platform-tests/clipboard-apis/events/paste-event-manual.html index 19e6b95c5f3..608a0d6f232 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/events/paste-event-manual.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/events/paste-event-manual.html @@ -1,4 +1,4 @@ - + The paste event diff --git a/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-write-read-manual.https.html b/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-write-read-manual.https.html index b374333ca94..14488b1e716 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-write-read-manual.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-write-read-manual.https.html @@ -1,4 +1,4 @@ - + Async Clipboard write ([text/plain ClipboardItem]) -> diff --git a/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-write-readText-manual.https.html b/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-write-readText-manual.https.html index 2d78b2f186e..c76df06a8b0 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-write-readText-manual.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-write-readText-manual.https.html @@ -1,4 +1,4 @@ -<!DOCTYPE html> +<!doctype html> <meta charset="utf-8"> <title> Async Clipboard write ([text/plain ClipboardItem]) -> readText tests diff --git a/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-writeText-read-manual.https.html b/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-writeText-read-manual.https.html index 4f9fe25bca5..e74726ec5e7 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-writeText-read-manual.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-writeText-read-manual.https.html @@ -1,4 +1,4 @@ -<!DOCTYPE html> +<!doctype html> <meta charset="utf-8"> <title> Async Clipboard writeText -> read ([text/plain ClipboardItem]) tests diff --git a/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-writeText-readText-manual.https.html b/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-writeText-readText-manual.https.html index 48e5adb51d2..d2c43f0b6f2 100644 --- a/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-writeText-readText-manual.https.html +++ b/tests/wpt/web-platform-tests/clipboard-apis/text-write-read/async-writeText-readText-manual.https.html @@ -1,4 +1,4 @@ -<!DOCTYPE html> +<!doctype html> <meta charset="utf-8"> <title>Async Clipboard writeText -> readText tests diff --git a/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/nonces.html b/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/nonces.html index e7bada96f94..b023d060323 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/nonces.html +++ b/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/nonces.html @@ -7,7 +7,7 @@ ["div", ""], ["script", ""], ["meh", "http://www.w3.org/2000/svg"], - ["svg", "http://www.w3.org/2000/svg"], , + ["svg", "http://www.w3.org/2000/svg"], ["script", "http://www.w3.org/2000/svg"]].forEach(([localName, namespace]) => { test(t => { const element = namespace === "" ? document.createElement(localName) : document.createElementNS(namespace, localName); diff --git a/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/script-nonces-hidden.html b/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/script-nonces-hidden.html index b32635ff658..d9718d904c2 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/script-nonces-hidden.html +++ b/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/script-nonces-hidden.html @@ -149,10 +149,10 @@ diff --git a/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub.html b/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub.html index 0bdf2ab8f48..870fef316ea 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub.html +++ b/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub.html @@ -49,7 +49,7 @@ test(t => { script.setAttribute('nonce', 'foo'); assert_equals(script.getAttribute('nonce'), 'foo'); - assert_equals(script.nonce, 'abc'); + assert_equals(script.nonce, 'foo'); }, "Writing 'nonce' content attribute."); // Set the IDL attribute to 'bar' @@ -77,6 +77,8 @@ innerScript.innerText = script.innerText; innerScript.nonce = 'abc'; s.appendChild(innerScript); + assert_equals(innerScript.nonce, 'abc'); + assert_equals(innerScript.getAttribute('nonce'), null, 'innerScript.getAttribute nonce'); document.body.appendChild(s); assert_equals(innerScript.nonce, 'abc'); assert_equals(innerScript.getAttribute('nonce'), null, 'innerScript.getAttribute nonce'); diff --git a/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/svgscript-nonces-hidden.html b/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/svgscript-nonces-hidden.html index d6e9379d471..a50c75b34ca 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/svgscript-nonces-hidden.html +++ b/tests/wpt/web-platform-tests/content-security-policy/nonce-hiding/svgscript-nonces-hidden.html @@ -49,7 +49,7 @@ test(t => { script.setAttribute('nonce', 'foo'); assert_equals(script.getAttribute('nonce'), 'foo'); - assert_equals(script.nonce, 'abc'); + assert_equals(script.nonce, 'foo'); }, "Writing 'nonce' content attribute."); // Set the IDL attribute to 'bar' diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/padding-overflow-crash.html b/tests/wpt/web-platform-tests/css/css-flexbox/padding-overflow-crash.html new file mode 100644 index 00000000000..fd0c333faba --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-flexbox/padding-overflow-crash.html @@ -0,0 +1,10 @@ + + + + + +

Test passes if there is a filled green square only.

+ +
+
+
diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/percentage-heights-004-ref.html b/tests/wpt/web-platform-tests/css/css-flexbox/percentage-heights-004-ref.html index ffb44a82b53..7c1e5858130 100644 --- a/tests/wpt/web-platform-tests/css/css-flexbox/percentage-heights-004-ref.html +++ b/tests/wpt/web-platform-tests/css/css-flexbox/percentage-heights-004-ref.html @@ -7,7 +7,7 @@ #outer { height: 10em; display: inline-block; - background: green; + background: tan; vertical-align: top; } @@ -18,11 +18,14 @@ #inner { width: 200px; + height: 100%; + background: green; } #outer2 { height: 10em; display: inline-block; + background: tan; } #middle2 { @@ -37,7 +40,8 @@ } -

You should not see red nor a vertical scrollbar

+

You should not see tan (except perhaps as the background of a horizontal + scrollbar), and you should not see a vertical scrollbar.

diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/percentage-heights-004.html b/tests/wpt/web-platform-tests/css/css-flexbox/percentage-heights-004.html index 4f162487a71..3fb0806b63d 100644 --- a/tests/wpt/web-platform-tests/css/css-flexbox/percentage-heights-004.html +++ b/tests/wpt/web-platform-tests/css/css-flexbox/percentage-heights-004.html @@ -9,7 +9,7 @@ #outer { height: 10em; display: inline-flex; - background: red; + background: tan; } #middle { @@ -17,8 +17,8 @@ } #inner { - height: 100%; width: 200px; + height: 100%; background: green; } @@ -26,7 +26,7 @@ height: 10em; display: inline-flex; flex-direction: column; - background: red; + background: tan; } #middle2 { @@ -35,13 +35,14 @@ } #inner2 { - height: 100%; width: 200px; + height: 100%; background: green; } -

You should not see red nor a vertical scrollbar

+

You should not see tan (except perhaps as the background of a horizontal + scrollbar), and you should not see a vertical scrollbar.

diff --git a/tests/wpt/web-platform-tests/css/css-page/page-size-011.xht b/tests/wpt/web-platform-tests/css/css-page/page-size-011.xht new file mode 100644 index 00000000000..6c9e3bcb11e --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-page/page-size-011.xht @@ -0,0 +1,20 @@ + + + + CSS Test: @page size JIS-B5 + + + + + + + +
If JIS B5 (182mm x 257mm) or larger paper is available, this content should be printed in a black box that has a width of 142mm and a height of 217mm.
+ + diff --git a/tests/wpt/web-platform-tests/css/css-page/page-size-012.xht b/tests/wpt/web-platform-tests/css/css-page/page-size-012.xht new file mode 100644 index 00000000000..0def30ae455 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-page/page-size-012.xht @@ -0,0 +1,20 @@ + + + + CSS Test: @page size JIS-B4 + + + + + + + +
If JIS B4 (257mm x 364mm) or larger paper is available, this content should be printed in a black box that has a width of 217mm and a height of 324mm.
+ + diff --git a/tests/wpt/web-platform-tests/css/css-text-decor/reference/text-decoration-skip-ink-005-notref.html b/tests/wpt/web-platform-tests/css/css-text-decor/reference/text-decoration-skip-ink-005-notref.html new file mode 100644 index 00000000000..9cbee037c0e --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-text-decor/reference/text-decoration-skip-ink-005-notref.html @@ -0,0 +1,22 @@ + + + + + Reference file for text-decoration-skip-ink test + + + +

Test passes if the underline skips the glyphs in the text below

+
中文
+ + diff --git a/tests/wpt/web-platform-tests/css/css-text-decor/text-decoration-skip-ink-005.html b/tests/wpt/web-platform-tests/css/css-text-decor/text-decoration-skip-ink-005.html new file mode 100644 index 00000000000..2d91f73e2ee --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-text-decor/text-decoration-skip-ink-005.html @@ -0,0 +1,27 @@ + + + + + Test case for text-decoration-skip-ink + + + + + + + +

Test passes if the underline skips the glyphs in the text below

+
中文
+ + diff --git a/tests/wpt/web-platform-tests/css/css-text-decor/text-decoration-skip-ink.html b/tests/wpt/web-platform-tests/css/css-text-decor/text-decoration-skip-ink.html index cd6f7a049c6..2d6a5d3671f 100644 --- a/tests/wpt/web-platform-tests/css/css-text-decor/text-decoration-skip-ink.html +++ b/tests/wpt/web-platform-tests/css/css-text-decor/text-decoration-skip-ink.html @@ -24,8 +24,10 @@ function testTextDecorationSkipInk() { "Text-decoration-skip-ink must allow setting the value auto."); assert_true(CSS.supports('text-decoration-skip-ink', "none"), "Text-decoration-skip-ink must allow setting the value none."); + assert_true(CSS.supports('text-decoration-skip-ink', "all"), + "Text-decoration-skip-ink must allow setting the value all."); }, - 'Property text-decoration-skip-ink must support values auto and none.') + 'Property text-decoration-skip-ink must support values auto, none and all.') } testTextDecorationSkipInk(); diff --git a/tests/wpt/web-platform-tests/css/css-values/minmax-number-computed.html b/tests/wpt/web-platform-tests/css/css-values/minmax-number-computed.html index c72c2766254..9a6e6142ea8 100644 --- a/tests/wpt/web-platform-tests/css/css-values/minmax-number-computed.html +++ b/tests/wpt/web-platform-tests/css/css-values/minmax-number-computed.html @@ -38,4 +38,8 @@ test_number_equals('calc(max(0.1, 0.2) * 2)', '0.4'); test_number_equals('calc(max(0.1, 0.2) / 2)', '0.1'); test_number_equals('calc(min(0.1, 0.2) + max(0.1, 0.05))', '0.2'); test_number_equals('calc(min(0.1, 0.2) - max(0.1, 0.05))', '0'); + +// Mixing floats and integers +test_number_equals('min(0, 0.5)', '0'); +test_number_equals('max(0, 0.5)', '0.5'); diff --git a/tests/wpt/web-platform-tests/css/cssom/CSSStyleSheet-constructable.html b/tests/wpt/web-platform-tests/css/cssom/CSSStyleSheet-constructable.html index d196b3965a5..539075b47ef 100644 --- a/tests/wpt/web-platform-tests/css/cssom/CSSStyleSheet-constructable.html +++ b/tests/wpt/web-platform-tests/css/cssom/CSSStyleSheet-constructable.html @@ -120,7 +120,7 @@ function createAllSheetsPromise() { const greenSheet = new CSSStyleSheet(); const redSheet = new CSSStyleSheet({media: "screen, print"}); const blueSheet = new CSSStyleSheet({title: "Blue", disabled: true}); - const whiteSheet = new CSSStyleSheet({title: "White", alternate: true}); + const whiteSheet = new CSSStyleSheet({title: "White", disabled: true}); const yellowSheet = new CSSStyleSheet({disabled: false}); const greenPromise = greenSheet.replace(greenStyleText); @@ -140,16 +140,17 @@ promise_test(() => { const yellowStyleSheet = values[4]; // Lists of style sheets can be created, assigned and read. + + // disabled stylesheets aren't applied document.adoptedStyleSheets = [whiteStyleSheet]; - // alternate stylesheets aren't applied when title != current preferable name assert_equals(getComputedStyle(greenSpan).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(redSpan).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(blueSpan).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(whiteSpan).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(yellowSpan).color, "rgb(0, 0, 0)"); + // disable dsheets don't block other styles from applying document.adoptedStyleSheets = [greenStyleSheet, blueStyleSheet]; - // disabled stylesheets aren't applied assert_equals(getComputedStyle(greenSpan).color, "rgb(0, 128, 0)"); assert_equals(getComputedStyle(redSpan).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(blueSpan).color, "rgb(0, 0, 0)"); @@ -170,6 +171,7 @@ promise_test(() => { assert_equals(getComputedStyle(blueSpan).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(whiteSpan).color, "rgb(255, 0, 0)"); assert_equals(getComputedStyle(yellowSpan).color, "rgb(255, 255, 0)"); + document.adoptedStyleSheets = []; }); }, 'Constructed style sheets can be applied on document'); @@ -241,7 +243,6 @@ test(() => { }, 'Attaching a shadow root that already has adopted stylesheets work'); test(() => { - const sheet = new CSSStyleSheet(); sheet.replaceSync(":host([red]) { color: red; } :host(.blue) { color: blue; }"); const host = document.createElement("div"); @@ -327,7 +328,8 @@ promise_test(() => { assert_equals(getComputedStyle(blueShadowSpan).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(whiteShadowSpan).color, "rgb(255, 0, 0)"); assert_equals(getComputedStyle(yellowShadowSpan).color, "rgb(0, 0, 0)"); -}); + document.adoptedStyleSheets = []; + }); }, 'Constructed stylesheet can be used and modified in multiple TreeScopes'); promise_test(() => { @@ -344,7 +346,11 @@ promise_test(() => { const plainSheet = new CSSStyleSheet(); const redStyleSheetPromise = plainSheet.replace(redStyleTexts[0]); return redStyleSheetPromise.then(function(redStyleSheet) { - assert_throws_dom('NotAllowedError', () => { iframe.contentDocument.adoptedStyleSheets = [redStyleSheet]; }); + assert_throws_dom( + 'NotAllowedError', + iframe.contentWindow.DOMException, + () => { iframe.contentDocument.adoptedStyleSheets = [redStyleSheet]; } + ); assert_equals(getComputedStyle(greenIframeSpan).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(redIframeSpan).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(blueIframeSpan).color, "rgb(0, 0, 0)"); @@ -367,9 +373,11 @@ promise_test(() => { }); }, 'Stylesheets constructed on the main Document cannot be used in iframes'); -promise_test(() => { +promise_test(async () => { const iframe = document.createElement("iframe"); + const iframeLoaded = new Promise(resolve => iframe.addEventListener("load", resolve)); document.body.appendChild(iframe); + await iframeLoaded; const thirdDiv = firstDiv.cloneNode(true); iframe.contentDocument.body.appendChild(thirdDiv); const greenIframeSpan = thirdDiv.children[0]; @@ -444,7 +452,9 @@ test(() => { iframe.contentDocument.body.appendChild(iframeDiv); assert_equals(getComputedStyle(iframeDiv).color, "rgb(0, 0, 0)"); - assert_throws_dom('NotAllowedError', () => { iframe.contentDocument.adoptedStyleSheets = [nonConstructedStyleSheet]; }); + assert_throws_dom('NotAllowedError', iframe.contentWindow.DOMException, () => { + iframe.contentDocument.adoptedStyleSheets = [nonConstructedStyleSheet]; + }); assert_equals(getComputedStyle(iframeDiv).color, "rgb(0, 0, 0)"); iframeStyle = iframe.contentDocument.createElement("style"); @@ -623,14 +633,19 @@ promise_test(() => { assert_equals(sheet.cssRules.length, 1); assert_equals(sheet.cssRules[0].cssText, import_text); assert_equals(getComputedStyle(shadowDiv).color, "rgb(255, 0, 0)"); + }).catch((reason) => { + assert_unreached(`Promise was rejected (${reason}) when it should have been resolved`); }); }, 'CSSStyleSheet.replace allows import rule inside'); promise_test(() => { const sheet = new CSSStyleSheet(); - const sheet_promise = sheet.replace("import url('not-there.css');"); - return sheet_promise.catch((reason) => { - assert_equals(reason.name, "NotAllowedError"); + const sheet_promise = sheet.replace("@import url('not-there.css');"); + + return sheet_promise.then((sheet) => { + assert_unreached("Promise was resolved when it should have been rejected"); + }).catch((reason) => { + assert_equals(reason.name, "NetworkError"); }); }, 'CSSStyleSheet.replace returns rejected promise on failed imports'); diff --git a/tests/wpt/web-platform-tests/css/selectors/attribute-selectors/attribute-case/syntax.html b/tests/wpt/web-platform-tests/css/selectors/attribute-selectors/attribute-case/syntax.html index 0eda7374053..e62c13a0340 100644 --- a/tests/wpt/web-platform-tests/css/selectors/attribute-selectors/attribute-case/syntax.html +++ b/tests/wpt/web-platform-tests/css/selectors/attribute-selectors/attribute-case/syntax.html @@ -135,7 +135,7 @@ onload = function() { assert_equals(global.getComputedStyle(elm).visibility, 'visible', 'invalid selector matched'); }, s + ' in ' + global.mode); test(function() { - assert_throws_dom("SyntaxError", function() { + assert_throws_dom("SyntaxError", global.DOMException, function() { global.document.querySelector(s); }, 'invalid selector'); }, s + ' with querySelector in ' + global.mode); diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/check-for-references.sh b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/check-for-references.sh index f5f68d5f578..adcc7dd2e7d 100755 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/check-for-references.sh +++ b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/check-for-references.sh @@ -3,7 +3,7 @@ cd "$(dirname "$0")" find . -name reftest.list | sed 's,/reftest.list$,,' | while read DIRNAME do - cat "$DIRNAME/reftest.list" | grep -v -e "^default-preferences" -e "include " | sed 's/ #.*//;s/^#.*//;s/.* == /== /;s/.* != /!= /' | grep -v "^ *$" | while read TYPE TEST REF + cat "$DIRNAME/reftest.list" | grep -v -e "^defaults" -e "include " | sed 's/ #.*//;s/^#.*//;s/.* == /== /;s/.* != /!= /' | grep -v "^ *$" | while read TYPE TEST REF do REFTYPE="" if [ "$TYPE" == "==" ] diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/reftest.list index 3dde877e231..1c5b5447c70 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/reftest.list +++ b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/reftest.list @@ -53,10 +53,3 @@ == contain-layout-ignored-cases-no-principal-box-003.html contain-layout-ignored-cases-no-principal-box-003-ref.html == contain-layout-suppress-baseline-001.html contain-layout-suppress-baseline-001-ref.html == contain-layout-suppress-baseline-002.html contain-layout-suppress-baseline-002-ref.html - -# The following lines are duplicates of other lines from further up in this -# manifest. They're listed again here so we can re-run these tests with -# column-span enabled. These lines can be removed once the pref becomes -# default-enabled (Bug 1426010). -== contain-size-multicol-002.html contain-size-multicol-002-ref.html -== contain-size-multicol-003.html contain-size-multicol-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/reftest.list index 867a23079d7..35102af64b6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/reftest.list +++ b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/reftest.list @@ -1,9 +1,3 @@ == broken-column-rule-1.html broken-column-rule-1-ref.html == moz-multicol3-column-balancing-break-inside-avoid-1.html moz-multicol3-column-balancing-break-inside-avoid-1-ref.html == multicol-height-002.xht reference/multicol-height-002.xht - -# The following lines are duplicates of other lines from further up in this -# manifest. They're listed again here so we can re-run these tests with -# column-span enabled. These lines can be removed once the pref becomes -# default-enabled (Bug 1426010). -== broken-column-rule-1.html broken-column-rule-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sync-tests.sh b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sync-tests.sh index 431ead2482e..7e3f087681e 100755 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sync-tests.sh +++ b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sync-tests.sh @@ -22,7 +22,7 @@ else fi rsync -avz --delete --filter=". ./sync-tests-filter" "$MOZTREE"/layout/reftests/w3c-css/submitted/ ./ -sed -i -e 's/^\(\(fails\|needs-focus\|random\|skip\|asserts\|slow\|require-or\|silentfail\|pref\|test-pref\|ref-pref\|fuzzy\)[^ ]* *\?\)\+//;/^default-preferences /d;s/ \?# \?\(TC: \)\?[bB]ug.*//' $(find . -name reftest.list) +sed -i -e 's/^\(\(fails\|needs-focus\|random\|skip\|asserts\|slow\|require-or\|silentfail\|pref\|test-pref\|ref-pref\|fuzzy\)[^ ]* *\?\)\+//;/^defaults /d;s/ \?# \?\(TC: \)\?[bB]ug.*//' $(find . -name reftest.list) sed -i -e 's/-moz-crisp-edges/pixelated/g' $(find . -regex ".*\.\(xht\|xhtml\|html\|css\)") git add -A . git commit -m"Sync Mozilla CSS tests as of https://hg.mozilla.org/mozilla-central/rev/$MOZREV ." -e . diff --git a/tests/wpt/web-platform-tests/custom-elements/custom-element-registry/define.html b/tests/wpt/web-platform-tests/custom-elements/custom-element-registry/define.html index 61717b2e200..2aad1c18025 100644 --- a/tests/wpt/web-platform-tests/custom-elements/custom-element-registry/define.html +++ b/tests/wpt/web-platform-tests/custom-elements/custom-element-registry/define.html @@ -105,7 +105,7 @@ }); invalidCustomElementNames.forEach(name => { test(() => { - assert_throws_dom(expectSyntaxError, () => { + assert_throws_dom(expectSyntaxError, testWindow.DOMException, () => { customElements.define(name, class {}); }); }, `Element names: defining an element named ${name} should throw a SyntaxError`); @@ -115,7 +115,7 @@ // then throw a NotSupportedError and abort these steps. test(() => { customElements.define('test-define-dup-name', class {}); - assert_throws_dom(expectNotSupportedError, () => { + assert_throws_dom(expectNotSupportedError, testWindow.DOMException, () => { customElements.define('test-define-dup-name', class {}); }); }, 'If the name is already defined, should throw a NotSupportedError'); @@ -125,7 +125,7 @@ test(() => { class TestDupConstructor {}; customElements.define('test-define-dup-constructor', TestDupConstructor); - assert_throws_dom(expectNotSupportedError, () => { + assert_throws_dom(expectNotSupportedError, testWindow.DOMException, () => { customElements.define('test-define-dup-ctor2', TestDupConstructor); }); }, 'If the constructor is already defined, should throw a NotSupportedError'); @@ -134,7 +134,7 @@ // then throw a NotSupportedError. validCustomElementNames.forEach(name => { test(() => { - assert_throws_dom(expectNotSupportedError, () => { + assert_throws_dom(expectNotSupportedError, testWindow.DOMException, () => { customElements.define('test-define-extend-valid-name', class {}, { extends: name }); }); }, `If extends is ${name}, should throw a NotSupportedError`); @@ -154,7 +154,7 @@ 'elementnametobeunknownelement', ].forEach(name => { test(() => { - assert_throws_dom(expectNotSupportedError, () => { + assert_throws_dom(expectNotSupportedError, testWindow.DOMException, () => { customElements.define('test-define-extend-' + name, class {}, { extends: name }); }); }, `If extends is ${name}, should throw a NotSupportedError`); diff --git a/tests/wpt/web-platform-tests/custom-elements/reactions/with-exceptions.html b/tests/wpt/web-platform-tests/custom-elements/reactions/with-exceptions.html index e7cdee789d4..131348b1c41 100644 --- a/tests/wpt/web-platform-tests/custom-elements/reactions/with-exceptions.html +++ b/tests/wpt/web-platform-tests/custom-elements/reactions/with-exceptions.html @@ -25,7 +25,11 @@ test_with_window((contentWindow, contentDocument) => { contentDocument.documentElement.appendChild(text); const element = contentDocument.createElement("custom-element"); contentDocument.documentElement.appendChild(element); - assert_throws_dom("HierarchyRequestError", () => text.before("", contentDocument.documentElement)); + assert_throws_dom( + "HierarchyRequestError", + contentWindow.DOMException, + () => text.before("", contentDocument.documentElement) + ); assert_true(reactionRan); }, "Reaction must run even after the exception is thrown"); diff --git a/tests/wpt/web-platform-tests/custom-elements/throw-on-dynamic-markup-insertion-counter-construct.html b/tests/wpt/web-platform-tests/custom-elements/throw-on-dynamic-markup-insertion-counter-construct.html index 85359504014..3e9254e2a50 100644 --- a/tests/wpt/web-platform-tests/custom-elements/throw-on-dynamic-markup-insertion-counter-construct.html +++ b/tests/wpt/web-platform-tests/custom-elements/throw-on-dynamic-markup-insertion-counter-construct.html @@ -45,12 +45,12 @@ promise_test(async function () { const result = await construct_custom_element_in_parser(this, (document) => document.open()); - assert_throws_dom('InvalidStateError', () => { throw result.exception; }, 'Must throw an InvalidStateError'); + assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError'); }, 'document.open() must throw an InvalidStateError when synchronously constructing a custom element'); promise_test(async function () { const result = await construct_custom_element_in_parser(this, (document) => document.open('text/html')); - assert_throws_dom('InvalidStateError', () => { throw result.exception; }, 'Must throw an InvalidStateError'); + assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError'); }, 'document.open("text/html") must throw an InvalidStateError when synchronously constructing a custom element'); // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-open-window @@ -63,19 +63,19 @@ promise_test(async function () { const result = await construct_custom_element_in_parser(this, (document) => document.close()); - assert_throws_dom('InvalidStateError', () => { throw result.exception; }, 'Must throw an InvalidStateError'); + assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError'); }, 'document.close() must throw an InvalidStateError when synchronously constructing a custom element'); promise_test(async function () { const result = await construct_custom_element_in_parser(this, (document) => document.write('some text')); - assert_throws_dom('InvalidStateError', () => { throw result.exception; }, 'Must throw an InvalidStateError'); + assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError'); assert_equals(result.document.querySelector('b'), null, 'Must not insert new content'); assert_false(result.document.documentElement.innerHTML.includes('some text'), 'Must not insert new content'); }, 'document.write must throw an InvalidStateError when synchronously constructing a custom element'); promise_test(async function () { const result = await construct_custom_element_in_parser(this, (document) => document.writeln('some text')); - assert_throws_dom('InvalidStateError', () => { throw result.exception; }, 'Must throw an InvalidStateError'); + assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError'); assert_equals(result.document.querySelector('b'), null, 'Must not insert new content'); assert_false(result.document.documentElement.innerHTML.includes('some text'), 'Must not insert new content'); }, 'document.writeln must throw an InvalidStateError when synchronously constructing a custom element'); diff --git a/tests/wpt/web-platform-tests/custom-elements/throw-on-dynamic-markup-insertion-counter-reactions.html b/tests/wpt/web-platform-tests/custom-elements/throw-on-dynamic-markup-insertion-counter-reactions.html index 49335bf9b42..e798d332e38 100644 --- a/tests/wpt/web-platform-tests/custom-elements/throw-on-dynamic-markup-insertion-counter-reactions.html +++ b/tests/wpt/web-platform-tests/custom-elements/throw-on-dynamic-markup-insertion-counter-reactions.html @@ -45,12 +45,12 @@ async function custom_element_reactions_in_parser(test, call_function) promise_test(async function () { const result = await custom_element_reactions_in_parser(this, (document) => document.open()); - assert_throws_dom('InvalidStateError', () => { throw result.exception; }, 'Must throw an InvalidStateError'); + assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError'); }, 'document.open() must throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element'); promise_test(async function () { const result = await custom_element_reactions_in_parser(this, (document) => document.open('text/html')); - assert_throws_dom('InvalidStateError', () => { throw result.exception; }, 'Must throw an InvalidStateError'); + assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError'); }, 'document.open("text/html") must throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element'); // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-open-window @@ -63,19 +63,19 @@ promise_test(async function () { promise_test(async function () { const result = await custom_element_reactions_in_parser(this, (document) => document.close()); - assert_throws_dom('InvalidStateError', () => { throw result.exception; }, 'Must throw an InvalidStateError'); + assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError'); }, 'document.close() must throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element'); promise_test(async function () { const result = await custom_element_reactions_in_parser(this, (document) => document.write('some text')); - assert_throws_dom('InvalidStateError', () => { throw result.exception; }, 'Must throw an InvalidStateError'); + assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError'); assert_equals(result.document.querySelector('b'), null, 'Must not insert new content'); assert_false(result.document.documentElement.innerHTML.includes('some text'), 'Must not insert new content'); }, 'document.write must throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element'); promise_test(async function () { const result = await custom_element_reactions_in_parser(this, (document) => document.writeln('some text')); - assert_throws_dom('InvalidStateError', () => { throw result.exception; }, 'Must throw an InvalidStateError'); + assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError'); assert_equals(result.document.querySelector('b'), null, 'Must not insert new content'); assert_false(result.document.documentElement.innerHTML.includes('some text'), 'Must not insert new content'); }, 'document.writeln must throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element'); diff --git a/tests/wpt/web-platform-tests/docs/writing-tests/testharness-api.md b/tests/wpt/web-platform-tests/docs/writing-tests/testharness-api.md index d83ebc4e676..2ee52786add 100644 --- a/tests/wpt/web-platform-tests/docs/writing-tests/testharness-api.md +++ b/tests/wpt/web-platform-tests/docs/writing-tests/testharness-api.md @@ -206,7 +206,7 @@ promise_rejects_exactly(test_object, value, promise, description) The `code`, `constructor`, and `value` arguments are equivalent to the same argument to the `assert_throws_dom`, `assert_throws_js`, and -`assert_throws_exactly` functions. +`assert_throws_exactly` functions. The `promise_rejects_dom` function can also be called with a DOMException constructor argument between the `code` and `promise` arguments, just like `assert_throws_dom`, when we want to assert that the DOMException comes from a non-default global. Here's an example where the `bar()` function returns a Promise that rejects with a TypeError: @@ -858,7 +858,7 @@ attribute attribute_name following the conditions specified by WebIDL ### `assert_readonly(object, property_name, description)` assert that property `property_name` on object is readonly -### `assert_throws_dom(code, func, description)` +### `assert_throws_dom(code, func, description)` or `assert_throws_dom(code, constructor, func, description)` `code` - the expected exception. This can take several forms: * string - asserts that the thrown exception must be a DOMException @@ -871,6 +871,8 @@ assert that property `property_name` on object is readonly `func` - a function that should throw +`constructor` - The DOMException constructor that the resulting DOMException should have as its `.constructor`. This should be used when a DOMException from a non-default global is expected to be thrown. + ### `assert_throws_js(constructor, func, description)` `constructor` - the expected exception. This is the constructor object that the exception should have as its .constructor. For example, diff --git a/tests/wpt/web-platform-tests/dom/events/scrolling/overscroll-deltas.html b/tests/wpt/web-platform-tests/dom/events/scrolling/overscroll-deltas.html index 287987c3ee2..091cfd0631b 100644 --- a/tests/wpt/web-platform-tests/dom/events/scrolling/overscroll-deltas.html +++ b/tests/wpt/web-platform-tests/dom/events/scrolling/overscroll-deltas.html @@ -47,7 +47,7 @@ function runTest() { 'Document did not receive scrollend event.'); assert_greater_than(overscrolled_y_deltas.length, 0, "There should be at least one overscroll events when overscrolling."); - assert_equals(Math.max(...overscrolled_x_deltas), 0, "The deltaX attribute must be 0 when there is no scrolling in x direction."); + assert_equals(overscrolled_x_deltas.filter(function(x){ return x!=0; }).length, 0, "The deltaX attribute must be 0 when there is no scrolling in x direction."); assert_less_than_equal(Math.max(...overscrolled_y_deltas), 0, "The deltaY attribute must be <= 0 when there is overscrolling in up direction."); assert_less_than_equal(Math.min(...overscrolled_y_deltas),-100, "The deltaY attribute must be the number of pixels overscrolled."); @@ -62,7 +62,7 @@ function runTest() { 'Document did not receive scrollend event.'); assert_greater_than(overscrolled_y_deltas.length, 0, "There should be at least one overscroll events when overscrolling."); - assert_equals(Math.max(...overscrolled_y_deltas), 0, "The deltaY attribute must be 0 when there is no scrolling in y direction."); + assert_equals(overscrolled_y_deltas.filter(function(x){ return x!=0; }).length, 0, "The deltaY attribute must be 0 when there is no scrolling in y direction."); assert_less_than_equal(Math.max(...overscrolled_x_deltas), 0, "The deltaX attribute must be <= 0 when there is overscrolling in left direction."); assert_less_than_equal(Math.min(...overscrolled_x_deltas),-100, "The deltaX attribute must be the number of pixels overscrolled."); diff --git a/tests/wpt/web-platform-tests/dom/nodes/Document-createElement.html b/tests/wpt/web-platform-tests/dom/nodes/Document-createElement.html index c0126b4f8a8..93435ac82d6 100644 --- a/tests/wpt/web-platform-tests/dom/nodes/Document-createElement.html +++ b/tests/wpt/web-platform-tests/dom/nodes/Document-createElement.html @@ -145,8 +145,9 @@ invalid.forEach(function(arg) { async_test(function(testObj) { window.addEventListener("load", function() { testObj.step(function() { - var doc = getWin(desc).document; - assert_throws_dom("InvalidCharacterError", + let win = getWin(desc); + let doc = win.document; + assert_throws_dom("InvalidCharacterError", win.DOMException, function() { doc.createElement(arg) }) }); testObj.done(); diff --git a/tests/wpt/web-platform-tests/dom/nodes/Document-createElementNS.html b/tests/wpt/web-platform-tests/dom/nodes/Document-createElementNS.html index 43cf800b4cc..5ad81043de0 100644 --- a/tests/wpt/web-platform-tests/dom/nodes/Document-createElementNS.html +++ b/tests/wpt/web-platform-tests/dom/nodes/Document-createElementNS.html @@ -50,7 +50,7 @@ function runTest(t, i, desc) { } var namespace = t[0], qualifiedName = t[1], expected = t[2] if (expected != null) { - assert_throws_dom(expected, function() { doc.createElementNS(namespace, qualifiedName) }) + assert_throws_dom(expected, doc.defaultView.DOMException, function() {doc.createElementNS(namespace, qualifiedName) }); } else { var element = doc.createElementNS(namespace, qualifiedName) assert_not_equals(element, null) diff --git a/tests/wpt/web-platform-tests/dom/nodes/Element-matches.js b/tests/wpt/web-platform-tests/dom/nodes/Element-matches.js index 9494f6f0eac..a1455c671f7 100644 --- a/tests/wpt/web-platform-tests/dom/nodes/Element-matches.js +++ b/tests/wpt/web-platform-tests/dom/nodes/Element-matches.js @@ -49,9 +49,13 @@ function runInvalidSelectorTestMatches(method, type, root, selectors) { var q = s["selector"]; test(function() { - assert_throws_dom("SyntaxError", function() { - root[method](q) - }) + assert_throws_dom( + "SyntaxError", + root.ownerDocument.defaultView.DOMException, + function() { + root[method](q) + } + ); }, type + "." + method + ": " + n + ": " + q); } } diff --git a/tests/wpt/web-platform-tests/dom/nodes/Node-removeChild.html b/tests/wpt/web-platform-tests/dom/nodes/Node-removeChild.html index a4581aadc9b..6158423359c 100644 --- a/tests/wpt/web-platform-tests/dom/nodes/Node-removeChild.html +++ b/tests/wpt/web-platform-tests/dom/nodes/Node-removeChild.html @@ -41,7 +41,11 @@ documents.forEach(function(d) { var s = doc[creator]("test") doc.body.appendChild(s) assert_equals(s.ownerDocument, doc) - assert_throws_dom("NOT_FOUND_ERR", function() { s.removeChild(doc) }) + assert_throws_dom( + "NOT_FOUND_ERR", + (doc.defaultView || self).DOMException, + function() { s.removeChild(doc) } + ); }, "Calling removeChild on a " + p + " from " + description + " with no children should throw NOT_FOUND_ERR.") } diff --git a/tests/wpt/web-platform-tests/dom/nodes/ParentNode-querySelector-All.js b/tests/wpt/web-platform-tests/dom/nodes/ParentNode-querySelector-All.js index 3d424f750b9..3c6c5031798 100644 --- a/tests/wpt/web-platform-tests/dom/nodes/ParentNode-querySelector-All.js +++ b/tests/wpt/web-platform-tests/dom/nodes/ParentNode-querySelector-All.js @@ -207,6 +207,10 @@ function runValidSelectorTest(type, root, selectors, testType, docType) { } } +function windowFor(root) { + return root.defaultView || root.ownerDocument.defaultView; +} + /* * Execute queries with the specified invalid selectors for both querySelector() and querySelectorAll() * Only run these tests when errors are expected. Don't run for valid selector tests. @@ -218,15 +222,15 @@ function runInvalidSelectorTest(type, root, selectors) { var q = s["selector"]; test(function() { - assert_throws_dom("SyntaxError", function() { + assert_throws_dom("SyntaxError", windowFor(root).DOMException, function() { root.querySelector(q) - }) + }); }, type + ".querySelector: " + n + ": " + q); test(function() { - assert_throws_dom("SyntaxError", function() { + assert_throws_dom("SyntaxError", windowFor(root).DOMException, function() { root.querySelectorAll(q) - }) + }); }, type + ".querySelectorAll: " + n + ": " + q); } } diff --git a/tests/wpt/web-platform-tests/dom/nodes/aria-attribute-reflection.tentative.html b/tests/wpt/web-platform-tests/dom/nodes/aria-attribute-reflection.tentative.html index 0a3a66bdf43..d5f4adff59f 100644 --- a/tests/wpt/web-platform-tests/dom/nodes/aria-attribute-reflection.tentative.html +++ b/tests/wpt/web-platform-tests/dom/nodes/aria-attribute-reflection.tentative.html @@ -109,6 +109,17 @@ test(function(t) {
+
+ + + diff --git a/tests/wpt/web-platform-tests/fetch/api/cors/data-url-iframe.html b/tests/wpt/web-platform-tests/fetch/api/cors/data-url-iframe.html new file mode 100644 index 00000000000..217baa3c46b --- /dev/null +++ b/tests/wpt/web-platform-tests/fetch/api/cors/data-url-iframe.html @@ -0,0 +1,58 @@ + + + + + + diff --git a/tests/wpt/web-platform-tests/fetch/api/cors/data-url-shared-worker.html b/tests/wpt/web-platform-tests/fetch/api/cors/data-url-shared-worker.html new file mode 100644 index 00000000000..d69748ab261 --- /dev/null +++ b/tests/wpt/web-platform-tests/fetch/api/cors/data-url-shared-worker.html @@ -0,0 +1,53 @@ + + + + + diff --git a/tests/wpt/web-platform-tests/fetch/api/cors/data-url-worker.html b/tests/wpt/web-platform-tests/fetch/api/cors/data-url-worker.html new file mode 100644 index 00000000000..13113e62621 --- /dev/null +++ b/tests/wpt/web-platform-tests/fetch/api/cors/data-url-worker.html @@ -0,0 +1,50 @@ + + + + + diff --git a/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18-ref.html b/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18-ref.html new file mode 100644 index 00000000000..a1701e3fdac --- /dev/null +++ b/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18-ref.html @@ -0,0 +1,36 @@ + + +Forced colors mode - svg. + Only foreignObject svg elements should have styles + overridden in forced colors mode. + + + + + + This text should have a red fill and blue stroke color in + forced colors mode. + + + +
+ This text should be orange in forced colors mode due to inheritance. +
+ + + +
+
+ + + +
+ This text should be CanvasText in forced colors mode. +
+
+
+ diff --git a/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18.html b/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18.html new file mode 100644 index 00000000000..435b9e4326f --- /dev/null +++ b/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18.html @@ -0,0 +1,33 @@ + + +Forced colors mode - svg. + Only foreignObject svg elements should have styles + overridden in forced colors mode. + + + + + + + This text should have a red fill and blue stroke color in + forced colors mode. + + + +
+ This text should be orange in forced colors mode due to inheritance. +
+ + + +
+
+ + + +
+ This text should be CanvasText in forced colors mode. +
+
+
+ diff --git a/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18.tentative-ref.html b/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18.tentative-ref.html deleted file mode 100644 index 34eba583caa..00000000000 --- a/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18.tentative-ref.html +++ /dev/null @@ -1,28 +0,0 @@ - - -Forced colors mode - fill and stroke reference. - Only text and foreignObject svg elements should have fill and stroke - overridden in forced colors mode. - - - - - - This text should have a WindowText fill and transparent stroke color in - forced colors mode. - - - -
- This text should be WindowText color in forced colors mode. -
- - - -
-
- diff --git a/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18.tentative.html b/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18.tentative.html deleted file mode 100644 index 307420af9fc..00000000000 --- a/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-18.tentative.html +++ /dev/null @@ -1,27 +0,0 @@ - - -Forced colors mode - fill and stroke. - Only text and foreignObject svg elements should have fill and stroke - overridden in forced colors mode. - - - - - - - - This text should have a WindowText fill and transparent stroke color in - forced colors mode. - - - -
- This text should be WindowText color in forced colors mode. -
- - - -
-
- diff --git a/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26-ref.html b/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26-ref.html new file mode 100644 index 00000000000..39caee40b52 --- /dev/null +++ b/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26-ref.html @@ -0,0 +1,15 @@ + + +Forced colors mode - fill/stroke. + + + + The triangle below should be currentColor when forced colors mode. + + + + diff --git a/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26.html b/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26.html new file mode 100644 index 00000000000..a9aa5901321 --- /dev/null +++ b/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26.html @@ -0,0 +1,20 @@ + + +Forced colors mode - fill/stroke. + + + + + +
+ The triangle below should be currentColor when forced colors mode. + + + +
+ diff --git a/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26.tentative-ref.html b/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26.tentative-ref.html deleted file mode 100644 index f551a434290..00000000000 --- a/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26.tentative-ref.html +++ /dev/null @@ -1,16 +0,0 @@ - - -Forced colors mode - fill/stroke inheritance. - Tests that fill and stroke colors are properly inherited from parent elements. - - - - The triangle below should be red when forced colors mode is enabled and disabled. - - - - diff --git a/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26.tentative.html b/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26.tentative.html deleted file mode 100644 index ac21cae2380..00000000000 --- a/tests/wpt/web-platform-tests/forced-colors-mode/forced-colors-mode-26.tentative.html +++ /dev/null @@ -1,23 +0,0 @@ - - -Forced colors mode - fill/stroke inheritance. - Tests that fill and stroke colors are properly inherited from parent elements. - - - - - -
- The triangle below should be red when forced colors mode is enabled and disabled. - - - - -
- diff --git a/tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-longfragment.html b/tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-longfragment.html new file mode 100644 index 00000000000..8c4c77014fc --- /dev/null +++ b/tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-longfragment.html @@ -0,0 +1,29 @@ + + + + +Anchor element with onclick form submission and href to fragment + + + + + + +
+
Test + + diff --git a/tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-withpath.html b/tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-withpath.html new file mode 100644 index 00000000000..b891623a2f5 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit-withpath.html @@ -0,0 +1,35 @@ + + + + +Anchor element with onclick form submission and href to fragment + + + + + + +
+Test + + diff --git a/tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit.html b/tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit.html new file mode 100644 index 00000000000..57e44f18085 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/anchor-fragment-form-submit.html @@ -0,0 +1,29 @@ + + + + +Anchor element with onclick form submission and href to fragment + + + + + + +
+Test + + diff --git a/tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/form.html b/tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/form.html new file mode 100644 index 00000000000..6523a82b393 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/form.html @@ -0,0 +1,5 @@ + + +form navigation diff --git a/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/001.html b/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/001.html index da93e0dafc7..b4a5fd4bf4d 100644 --- a/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/001.html +++ b/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/001.html @@ -94,7 +94,7 @@ function reportload() { assert_throws_dom('SECURITY_ERR',function () { history.pushState('','','data:text/html,'); }); }, 'pushState must not be allowed to create cross-origin URLs (data:URI)'); test(function () { - assert_throws_dom('SECURITY_ERR',function () { iframe.contentWindow.history.pushState('','','http://www.example.com/'); },iframe.contentWindow); + assert_throws_dom('SECURITY_ERR', iframe.contentWindow.DOMException, function () { iframe.contentWindow.history.pushState('','','http://www.example.com/'); }); }, 'security errors are expected to be thrown in the context of the document that owns the history object'); test(function () { iframe.contentWindow.location.hash = 'test2'; @@ -180,9 +180,9 @@ function reportload() { } }, 'pushState must be able to use an error object as data'); test(function () { - assert_throws_dom( 'DATA_CLONE_ERR', function () { + assert_throws_dom('DATA_CLONE_ERR', iframe.contentWindow.DOMException, function () { iframe.contentWindow.history.pushState(document,''); - }, iframe.contentWindow ); + }); }, 'security errors are expected to be thrown in the context of the document that owns the history object (2)'); cloneobj = { nulldata: null, diff --git a/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/002.html b/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/002.html index dba28b0471c..dcda248282f 100644 --- a/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/002.html +++ b/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/002.html @@ -99,7 +99,7 @@ function reportload() { assert_throws_dom('SECURITY_ERR',function () { history.replaceState('','','data:text/html,'); }); }, 'replaceState must not be allowed to create cross-origin URLs (data:URI)'); test(function () { - assert_throws_dom('SECURITY_ERR',function () { iframe.contentWindow.history.replaceState('','','http://www.example.com/'); },iframe.contentWindow); + assert_throws_dom('SECURITY_ERR',iframe.contentWindow.DOMException,function () { iframe.contentWindow.history.replaceState('','','http://www.example.com/'); }); }, 'security errors are expected to be thrown in the context of the document that owns the history object'); test(function () { //avoids browsers running .go synchronously when only a hash change is involved @@ -161,9 +161,9 @@ function reportload() { } }, 'replaceState must be able to use an error object as data'); test(function () { - assert_throws_dom( 'DATA_CLONE_ERR', function () { + assert_throws_dom('DATA_CLONE_ERR', iframe.contentWindow.DOMException, function () { iframe.contentWindow.history.replaceState(document,''); - }, iframe.contentWindow ); + }); }, 'security errors are expected to be thrown in the context of the document that owns the history object (2)'); cloneobj = { nulldata: null, diff --git a/tests/wpt/web-platform-tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html b/tests/wpt/web-platform-tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html index 77f438c0166..25395283411 100644 --- a/tests/wpt/web-platform-tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html +++ b/tests/wpt/web-platform-tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html @@ -61,7 +61,8 @@ assert_equals(iframe.contentWindow.location.toString(), iframe_url.toString()); // document.open checks for same-origin, not same-origin-domain, // https://github.com/whatwg/html/issues/2282 - assert_throws_dom("SecurityError", function() { iframe.contentDocument.open(); }); + assert_throws_dom("SecurityError", iframe.contentWindow.DOMException, + function() { iframe.contentDocument.open(); }); })); }, "same-origin-domain iframe"); diff --git a/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/none.https.html b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/none.https.html index 7f296e60441..e603753084c 100644 --- a/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/none.https.html +++ b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/none.https.html @@ -4,8 +4,12 @@ +
diff --git a/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/require-corp.https.html b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/require-corp.https.html index 69a81311c13..6f799b6e408 100644 --- a/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/require-corp.https.html +++ b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/require-corp.https.html @@ -7,6 +7,9 @@
diff --git a/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html new file mode 100644 index 00000000000..9a0ebe544a0 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html @@ -0,0 +1,29 @@ + + diff --git a/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html.headers b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html.headers new file mode 100644 index 00000000000..56d0ac34282 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html.headers @@ -0,0 +1,2 @@ +Cross-Origin-Embedder-Policy: require-corp +Cross-Origin-Resource-Policy: same-site diff --git a/tests/wpt/web-platform-tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html b/tests/wpt/web-platform-tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html new file mode 100644 index 00000000000..b35d8c2798a --- /dev/null +++ b/tests/wpt/web-platform-tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html @@ -0,0 +1,131 @@ + + + + +
+ diff --git a/tests/wpt/web-platform-tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html.headers b/tests/wpt/web-platform-tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html.headers new file mode 100644 index 00000000000..63b60e490f4 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html.headers @@ -0,0 +1,2 @@ +Cross-Origin-Opener-Policy: same-origin +Cross-Origin-Embedder-Policy: require-corp diff --git a/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-empty-alt-replaced.html b/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-empty-alt-replaced.html new file mode 100644 index 00000000000..3cc06d6c853 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-empty-alt-replaced.html @@ -0,0 +1,23 @@ + +Images with an empty alt attribute have an intrinsic size of zero + + + + +non-empty + + diff --git a/tests/wpt/web-platform-tests/html/semantics/forms/the-input-element/maxlength-number.html b/tests/wpt/web-platform-tests/html/semantics/forms/the-input-element/maxlength-number.html new file mode 100644 index 00000000000..1e1d9f694cf --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/forms/the-input-element/maxlength-number.html @@ -0,0 +1,19 @@ + +input type=number maxlength + + + + + + + + diff --git a/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-origin.sub.window.js b/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-origin.sub.window.js index d60be3b8d60..b20c3e3f317 100644 --- a/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-origin.sub.window.js +++ b/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-origin.sub.window.js @@ -14,9 +14,14 @@ async_test(t => { iframe.onload = t.step_func_done(() => { // Since this is called as an event handler on an element of this window, // the entry settings object is that of this browsing context. - assert_throws_dom("InvalidStateError", () => { - iframe.contentDocument.open(); - }, "opening an XML document should throw an InvalidStateError"); + assert_throws_dom( + "InvalidStateError", + iframe.contentWindow.DOMException, + () => { + iframe.contentDocument.open(); + }, + "opening an XML document should throw an InvalidStateError" + ); }); const frameURL = new URL("resources/bailout-order-xml-with-domain-frame.sub.xhtml", document.URL); frameURL.port = "{{ports[http][1]}}"; @@ -39,9 +44,14 @@ async_test(t => { // IDL algorithm in "create an element", called by "create an element for a // token" in the parser. setEntryToTopLevel(t.step_func_done(() => { - assert_throws_dom("InvalidStateError", () => { - iframe.contentDocument.open(); - }, "opening a document when the throw-on-dynamic-markup-insertion counter is incremented should throw an InvalidStateError"); + assert_throws_dom( + "InvalidStateError", + iframe.contentWindow.DOMException, + () => { + iframe.contentDocument.open(); + }, + "opening a document when the throw-on-dynamic-markup-insertion counter is incremented should throw an InvalidStateError" + ); })); }); const frameURL = new URL("resources/bailout-order-custom-element-with-domain-frame.sub.html", document.URL); @@ -60,9 +70,14 @@ async_test(t => { // "Clean up after running script" is executed when the tag is // seen by the HTML parser. setEntryToTopLevel(t.step_func_done(() => { - assert_throws_dom("SecurityError", () => { - iframe.contentDocument.open(); - }, "opening a same origin-domain (but not same origin) document should throw a SecurityError"); + assert_throws_dom( + "SecurityError", + iframe.contentWindow.DOMException, + () => { + iframe.contentDocument.open(); + }, + "opening a same origin-domain (but not same origin) document should throw a SecurityError" + ); })); }); const frameURL = new URL("resources/bailout-order-synchronous-script-with-domain-frame.sub.html", document.URL); @@ -85,9 +100,14 @@ for (const ev of ["beforeunload", "pagehide", "unload"]) { // "Clean up after running script" is called in the task that // navigates. setEntryToTopLevel(t.step_func_done(() => { - assert_throws_dom("SecurityError", () => { - iframe.contentDocument.open(); - }, "opening a same origin-domain (but not same origin) document should throw a SecurityError"); + assert_throws_dom( + "SecurityError", + iframe.contentWindow.DOMException, + () => { + iframe.contentDocument.open(); + }, + "opening a same origin-domain (but not same origin) document should throw a SecurityError" + ); })); })); iframe.src = "about:blank"; diff --git a/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-xml.window.js b/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-xml.window.js index 8b1a04fe83a..45a67f925bc 100644 --- a/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-xml.window.js +++ b/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-xml.window.js @@ -2,7 +2,7 @@ async_test(t => { const iframe = document.body.appendChild(document.createElement("iframe")); t.add_cleanup(() => { iframe.remove(); }); self.testSynchronousScript = t.step_func_done(() => { - assert_throws_dom("InvalidStateError", () => { + assert_throws_dom("InvalidStateError", iframe.contentWindow.DOMException, () => { iframe.contentDocument.open(); }, "opening an XML document should throw"); }); @@ -15,7 +15,7 @@ for (const ev of ["beforeunload", "pagehide", "unload"]) { t.add_cleanup(() => { iframe.remove(); }); iframe.addEventListener("load", t.step_func(() => { iframe.contentWindow.addEventListener(ev, t.step_func_done(() => { - assert_throws_dom("InvalidStateError", () => { + assert_throws_dom("InvalidStateError", iframe.contentWindow.DOMException, () => { iframe.contentDocument.open(); }, "opening an XML document should throw"); })); diff --git a/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-same-origin-domain.sub.window.js b/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-same-origin-domain.sub.window.js index 9adacb2a99f..f5edd7aed9c 100644 --- a/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-same-origin-domain.sub.window.js +++ b/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-same-origin-domain.sub.window.js @@ -7,7 +7,7 @@ testInIFrame("http://{{host}}:{{ports[http][1]}}/common/domain-setter.sub.html", const iframe = ctx.iframes[0]; const origURL = iframe.contentDocument.URL; assertDocumentIsReadyForSideEffectsTest(iframe.contentDocument, "same origin-domain (but not same origin) document"); - assert_throws_dom("SecurityError", () => { + assert_throws_dom("SecurityError", iframe.contentWindow.DOMException, () => { ctx.iframes[0].contentDocument.open(); }, "document.open() should throw a SecurityError on a same origin-domain (but not same origin) document"); assertOpenHasNoSideEffects(iframe.contentDocument, origURL, "same origin-domain (but not same origin) document"); diff --git a/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-xml.window.js b/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-xml.window.js index bcc4266319a..bbfc015c68e 100644 --- a/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-xml.window.js +++ b/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-xml.window.js @@ -7,9 +7,14 @@ async_test(t => { iframe.onload = t.step_func_done(() => { const origURL = iframe.contentDocument.URL; assertDocumentIsReadyForSideEffectsTest(iframe.contentDocument, "XML document"); - assert_throws_dom("InvalidStateError", () => { - iframe.contentDocument.open(); - }, "document.open() should throw on XML documents"); + assert_throws_dom( + "InvalidStateError", + iframe.contentWindow.DOMException, + () => { + iframe.contentDocument.open(); + }, + "document.open() should throw on XML documents" + ); assertOpenHasNoSideEffects(iframe.contentDocument, origURL, "XML document"); }); }, "document.open bailout should not have any side effects (XML document)"); diff --git a/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-same-origin-domain.sub.html b/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-same-origin-domain.sub.html index 83ce50d6c4d..ba4ef3bae88 100644 --- a/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-same-origin-domain.sub.html +++ b/tests/wpt/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-same-origin-domain.sub.html @@ -9,14 +9,16 @@ diff --git a/tests/wpt/web-platform-tests/imagebitmap-renderingcontext/context-creation-offscreen.html b/tests/wpt/web-platform-tests/imagebitmap-renderingcontext/context-creation-offscreen.html index 05a920cd3e6..66d4cce420a 100644 --- a/tests/wpt/web-platform-tests/imagebitmap-renderingcontext/context-creation-offscreen.html +++ b/tests/wpt/web-platform-tests/imagebitmap-renderingcontext/context-creation-offscreen.html @@ -12,7 +12,7 @@ test(function() { var ctx = canvas.getContext('bitmaprenderer'); assert_true(ctx instanceof ImageBitmapRenderingContext); assert_true("canvas" in ctx); - assert_object_equals(canvas, ctx.canvas); + assert_equals(canvas, ctx.canvas); }, "Test that canvas.getContext('bitmaprenderer') returns an instance of ImageBitmapRenderingContext and ctx.canvas on a ImageBitmapRenderingContext returns the original OffscreenCanvas"); diff --git a/tests/wpt/web-platform-tests/interfaces/css-font-loading.idl b/tests/wpt/web-platform-tests/interfaces/css-font-loading.idl index 2bbc35a2591..936ded788bc 100644 --- a/tests/wpt/web-platform-tests/interfaces/css-font-loading.idl +++ b/tests/wpt/web-platform-tests/interfaces/css-font-loading.idl @@ -18,10 +18,10 @@ dictionary FontFaceDescriptors { enum FontFaceLoadStatus { "unloaded", "loading", "loaded", "error" }; -[Constructor(CSSOMString family, (CSSOMString or BinaryData) source, - optional FontFaceDescriptors descriptors), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface FontFace { + constructor(CSSOMString family, (CSSOMString or BinaryData) source, + optional FontFaceDescriptors descriptors = {}); attribute CSSOMString family; attribute CSSOMString style; attribute CSSOMString weight; @@ -42,9 +42,9 @@ dictionary FontFaceSetLoadEventInit : EventInit { sequence fontfaces = []; }; -[Constructor(CSSOMString type, optional FontFaceSetLoadEventInit eventInitDict), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface FontFaceSetLoadEvent : Event { + constructor(CSSOMString type, optional FontFaceSetLoadEventInit eventInitDict = {}); [SameObject] readonly attribute FrozenArray fontfaces; }; @@ -52,9 +52,10 @@ enum FontFaceSetLoadStatus { "loading", "loaded" }; callback ForEachCallback = void (FontFace font, long index, FontFaceSet self); -[Exposed=(Window,Worker), - Constructor(sequence initialFaces)] +[Exposed=(Window,Worker)] interface FontFaceSet : EventTarget { + constructor(sequence initialFaces); + // FontFaceSet is Set-like! setlike; FontFaceSet add(FontFace font); diff --git a/tests/wpt/web-platform-tests/interfaces/cssom-view.idl b/tests/wpt/web-platform-tests/interfaces/cssom-view.idl index 2745ba96dc0..82517fefcc2 100644 --- a/tests/wpt/web-platform-tests/interfaces/cssom-view.idl +++ b/tests/wpt/web-platform-tests/interfaces/cssom-view.idl @@ -58,9 +58,9 @@ interface MediaQueryList : EventTarget { attribute EventHandler onchange; }; -[Exposed=Window, - Constructor(CSSOMString type, optional MediaQueryListEventInit eventInitDict = {})] +[Exposed=Window] interface MediaQueryListEvent : Event { + constructor(CSSOMString type, optional MediaQueryListEventInit eventInitDict = {}); readonly attribute CSSOMString media; readonly attribute boolean matches; }; diff --git a/tests/wpt/web-platform-tests/interfaces/resize-observer.idl b/tests/wpt/web-platform-tests/interfaces/resize-observer.idl index d53e9da55ee..448c949df9e 100644 --- a/tests/wpt/web-platform-tests/interfaces/resize-observer.idl +++ b/tests/wpt/web-platform-tests/interfaces/resize-observer.idl @@ -11,9 +11,9 @@ dictionary ResizeObserverOptions { ResizeObserverBoxOptions box = "content-box"; }; -[Exposed=(Window), - Constructor(ResizeObserverCallback callback)] +[Exposed=(Window)] interface ResizeObserver { + constructor(ResizeObserverCallback callback); void observe(Element target, optional ResizeObserverOptions options); void unobserve(Element target); void disconnect(); @@ -35,9 +35,8 @@ interface ResizeObserverSize { readonly attribute unrestricted double blockSize; }; -[Constructor(Element target) -] interface ResizeObservation { + constructor(Element target); readonly attribute Element target; readonly attribute ResizeObserverBoxOptions observedBox; readonly attribute sequence lastReportedSizes; diff --git a/tests/wpt/web-platform-tests/interfaces/scroll-animations.idl b/tests/wpt/web-platform-tests/interfaces/scroll-animations.idl index 865419c15b5..a247308e50e 100644 --- a/tests/wpt/web-platform-tests/interfaces/scroll-animations.idl +++ b/tests/wpt/web-platform-tests/interfaces/scroll-animations.idl @@ -20,9 +20,9 @@ dictionary ScrollTimelineOptions { (double or ScrollTimelineAutoKeyword) timeRange = "auto"; }; -[Exposed=Window, - Constructor(optional ScrollTimelineOptions options = {})] +[Exposed=Window] interface ScrollTimeline : AnimationTimeline { + constructor(optional ScrollTimelineOptions options = {}); readonly attribute Element scrollSource; readonly attribute ScrollDirection orientation; readonly attribute DOMString startScrollOffset; diff --git a/tests/wpt/web-platform-tests/interfaces/visual-viewport.idl b/tests/wpt/web-platform-tests/interfaces/visual-viewport.idl index 8e9316bf854..5e4b5413694 100644 --- a/tests/wpt/web-platform-tests/interfaces/visual-viewport.idl +++ b/tests/wpt/web-platform-tests/interfaces/visual-viewport.idl @@ -7,6 +7,7 @@ partial interface Window { [SameObject, Replaceable] readonly attribute VisualViewport visualViewport; }; +[Exposed=Window] interface VisualViewport : EventTarget { readonly attribute double offsetLeft; readonly attribute double offsetTop; diff --git a/tests/wpt/web-platform-tests/interfaces/web-bluetooth.idl b/tests/wpt/web-platform-tests/interfaces/web-bluetooth.idl index 65c4e20ef34..cbf09e1d7ed 100644 --- a/tests/wpt/web-platform-tests/interfaces/web-bluetooth.idl +++ b/tests/wpt/web-platform-tests/interfaces/web-bluetooth.idl @@ -30,6 +30,7 @@ interface Bluetooth : EventTarget { attribute EventHandler onavailabilitychanged; [SameObject] readonly attribute BluetoothDevice? referringDevice; + Promise> getDevices(); Promise requestDevice(optional RequestDeviceOptions options = {}); }; @@ -51,7 +52,7 @@ dictionary AllowedBluetoothDevice { // An allowedServices of "all" means all services are allowed. required (DOMString or sequence) allowedServices; }; -dictionary BluetoothPermissionData { +dictionary BluetoothPermissionStorage { required sequence allowedDevices; }; diff --git a/tests/wpt/web-platform-tests/interfaces/webaudio.idl b/tests/wpt/web-platform-tests/interfaces/webaudio.idl index 9491090337c..f0a5c7a9020 100644 --- a/tests/wpt/web-platform-tests/interfaces/webaudio.idl +++ b/tests/wpt/web-platform-tests/interfaces/webaudio.idl @@ -129,9 +129,7 @@ interface AudioBuffer { unsigned long channelNumber, optional unsigned long bufferOffset = 0); void copyToChannel (Float32Array source, - unsigned long channelNumber, - optional unsigned long bufferOffset = 0); }; @@ -198,9 +196,7 @@ interface AudioParam { AudioParam exponentialRampToValueAtTime (float value, double endTime); AudioParam setTargetAtTime (float target, double startTime, float timeConstant); AudioParam setValueCurveAtTime (sequence values, - double startTime, - double duration); AudioParam cancelScheduledValues (double cancelTime); AudioParam cancelAndHoldAtTime (double cancelTime); diff --git a/tests/wpt/web-platform-tests/lint.whitelist b/tests/wpt/web-platform-tests/lint.whitelist index a79d77f2fed..6ea2bd4ec1e 100644 --- a/tests/wpt/web-platform-tests/lint.whitelist +++ b/tests/wpt/web-platform-tests/lint.whitelist @@ -297,6 +297,7 @@ SET TIMEOUT: document-policy/font-display/font-display-document-policy-01.tentat SET TIMEOUT: html/browsers/windows/auxiliary-browsing-contexts/resources/close-opener.html SET TIMEOUT: html/cross-origin-embedder-policy/resources/navigate-none.sub.html SET TIMEOUT: html/cross-origin-embedder-policy/resources/navigate-require-corp.sub.html +SET TIMEOUT: html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html SET TIMEOUT: html/dom/documents/dom-tree-accessors/Document.currentScript.html SET TIMEOUT: html/webappapis/timers/* SET TIMEOUT: portals/history/resources/portal-harness.js diff --git a/tests/wpt/web-platform-tests/performance-timeline/po-disconnect-removes-observed-types.any.js b/tests/wpt/web-platform-tests/performance-timeline/po-disconnect-removes-observed-types.any.js new file mode 100644 index 00000000000..cac97bea075 --- /dev/null +++ b/tests/wpt/web-platform-tests/performance-timeline/po-disconnect-removes-observed-types.any.js @@ -0,0 +1,19 @@ +// META: script=performanceobservers.js + +async_test(function (t) { + const observer = new PerformanceObserver( + t.step_func(function (entryList) { + // There should be no mark entry. + checkEntries(entryList.getEntries(), + [{ entryType: "measure", name: "b"}]); + t.done(); + }) + ); + observer.observe({type: "mark"}); + // Disconnect the observer. + observer.disconnect(); + // Now, only observe measure. + observer.observe({type: "measure"}); + performance.mark("a"); + performance.measure("b"); +}, "Types observed are forgotten when disconnect() is called."); diff --git a/tests/wpt/web-platform-tests/performance-timeline/po-observe-repeated-type.any.js b/tests/wpt/web-platform-tests/performance-timeline/po-observe-repeated-type.any.js new file mode 100644 index 00000000000..2bba396a6b6 --- /dev/null +++ b/tests/wpt/web-platform-tests/performance-timeline/po-observe-repeated-type.any.js @@ -0,0 +1,17 @@ +// META: script=performanceobservers.js + +async_test(function (t) { + const observer = new PerformanceObserver( + t.step_func(function (entryList) { + checkEntries(entryList.getEntries(), + [{ entryType: "mark", name: "early"}]); + observer.disconnect(); + t.done(); + }) + ); + performance.mark("early"); + // This call will not trigger anything. + observer.observe({type: "mark"}); + // This call should override the previous call and detect the early mark. + observer.observe({type: "mark", buffered: true}); +}, "Two calls of observe() with the same 'type' cause override."); diff --git a/tests/wpt/web-platform-tests/portals/about-blank-cannot-host.html b/tests/wpt/web-platform-tests/portals/about-blank-cannot-host.html index 59858c690dc..6a721c32e68 100644 --- a/tests/wpt/web-platform-tests/portals/about-blank-cannot-host.html +++ b/tests/wpt/web-platform-tests/portals/about-blank-cannot-host.html @@ -4,6 +4,7 @@ - + + + diff --git a/tests/wpt/web-platform-tests/portals/portals-repeated-activate.html b/tests/wpt/web-platform-tests/portals/portals-repeated-activate.html index a3843dddb47..bf8d8ad4264 100644 --- a/tests/wpt/web-platform-tests/portals/portals-repeated-activate.html +++ b/tests/wpt/web-platform-tests/portals/portals-repeated-activate.html @@ -2,7 +2,8 @@ diff --git a/tests/wpt/web-platform-tests/resources/LICENSE b/tests/wpt/web-platform-tests/resources/LICENSE deleted file mode 100644 index 45896e6be2b..00000000000 --- a/tests/wpt/web-platform-tests/resources/LICENSE +++ /dev/null @@ -1,30 +0,0 @@ -W3C 3-clause BSD License - -http://www.w3.org/Consortium/Legal/2008/03-bsd-license.html - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of works must retain the original copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the original copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -* Neither the name of the W3C nor the names of its contributors may be - used to endorse or promote products derived from this work without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/wpt/web-platform-tests/resources/chromium/webxr-test.js b/tests/wpt/web-platform-tests/resources/chromium/webxr-test.js index be8be8067e9..dee05d08ff8 100644 --- a/tests/wpt/web-platform-tests/resources/chromium/webxr-test.js +++ b/tests/wpt/web-platform-tests/resources/chromium/webxr-test.js @@ -1012,6 +1012,9 @@ class MockXRInputSource { this.primary_input_clicked_ = fakeInputSourceInit.selectionClicked; } + this.primary_squeeze_pressed_ = false; + this.primary_squeeze_clicked_ = false; + this.mojo_from_input_ = null; if (fakeInputSourceInit.gripOrigin != null) { this.setGripOrigin(fakeInputSourceInit.gripOrigin); @@ -1163,6 +1166,20 @@ class MockXRInputSource { throw new Error("Unknown Button Type!"); } + // is this a 'squeeze' button? + if (buttonIndex === this.getButtonIndex('grip')) { + // squeeze + if (buttonState.pressed) { + this.primary_squeeze_pressed_ = true; + } else if (this.gamepad_.buttons[buttonIndex].pressed) { + this.primary_squeeze_clicked_ = true; + this.primary_squeeze_pressed_ = false; + } else { + this.primary_squeeze_clicked_ = false; + this.primary_squeeze_pressed_ = false; + } + } + this.gamepad_.buttons[buttonIndex].pressed = buttonState.pressed; this.gamepad_.buttons[buttonIndex].touched = buttonState.touched; this.gamepad_.buttons[buttonIndex].value = buttonState.pressedValue; @@ -1181,10 +1198,17 @@ class MockXRInputSource { input_state.primaryInputPressed = this.primary_input_pressed_; input_state.primaryInputClicked = this.primary_input_clicked_; + + input_state.primarySqueezePressed = this.primary_squeeze_pressed_; + input_state.primarySqueezeClicked = this.primary_squeeze_clicked_; // Setting the input source's "clicked" state should generate one "select" // event. Reset the input value to prevent it from continuously generating // events. this.primary_input_clicked_ = false; + // Setting the input source's "clicked" state should generate one "squeeze" + // event. Reset the input value to prevent it from continuously generating + // events. + this.primary_squeeze_clicked_ = false; input_state.mojoFromInput = this.mojo_from_input_; @@ -1233,6 +1257,7 @@ class MockXRInputSource { // Pointer data for DOM Overlay, set by setOverlayPointerPosition() if (this.overlay_pointer_position_) { input_state.overlayPointerPosition = this.overlay_pointer_position_; + this.overlay_pointer_position_ = null; } return input_state; diff --git a/tests/wpt/web-platform-tests/resources/idlharness.js b/tests/wpt/web-platform-tests/resources/idlharness.js index 18b11ec506d..ff40847a1fc 100644 --- a/tests/wpt/web-platform-tests/resources/idlharness.js +++ b/tests/wpt/web-platform-tests/resources/idlharness.js @@ -2406,10 +2406,7 @@ IdlInterface.prototype.test_member_operation = function(member) if (!shouldRunSubTest(this.name)) { return; } - var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: operation " + member.name + - "(" + member.arguments.map( - function(m) {return m.idlType.idlType; } ).join(", ") - +")"); + var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: operation " + member); a_test.step(function() { // This function tests WebIDL as of 2015-12-29. @@ -2883,11 +2880,6 @@ IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expect || member.type == "operation") && member.name) { - var described_name = member.name; - if (member.type == "operation") - { - described_name += "(" + member.arguments.map(arg => arg.idlType.idlType).join(", ") + ")"; - } subsetTestByKey(this.name, test, function() { assert_equals(exception, null, "Unexpected exception when evaluating object"); @@ -2927,16 +2919,17 @@ IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expect assert_equals(typeof obj[member.name], "function"); } } - }.bind(this), this.name + " interface: " + desc + ' must inherit property "' + described_name + '" with the proper type'); + }.bind(this), this.name + " interface: " + desc + ' must inherit property "' + member + '" with the proper type'); } // TODO: This is wrong if there are multiple operations with the same // identifier. // TODO: Test passing arguments of the wrong type. if (member.type == "operation" && member.name && member.arguments.length) { - var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: calling " + member.name + - "(" + member.arguments.map(function(m) { return m.idlType.idlType; }).join(", ") + - ") on " + desc + " with too few arguments must throw TypeError"); + var description = + this.name + " interface: calling " + member + " on " + desc + + " with too few arguments must throw TypeError"; + var a_test = subsetTestByKey(this.name, async_test, description); a_test.step(function() { assert_equals(exception, null, "Unexpected exception when evaluating object"); @@ -3150,6 +3143,36 @@ IdlInterfaceMember.prototype.is_to_json_regular_operation = function() { return this.type == "operation" && this.special !== "static" && this.name == "toJSON"; }; +IdlInterfaceMember.prototype.toString = function() { + function formatType(type) { + var result; + if (type.generic) { + result = type.generic + "<" + type.idlType.map(formatType).join(", ") + ">"; + } else if (type.union) { + result = "(" + type.subtype.map(formatType).join(" or ") + ")"; + } else { + result = type.idlType; + } + if (type.nullable) { + result += "?" + } + return result; + } + + if (this.type === "operation") { + var args = this.arguments.map(function(m) { + return [ + m.optional ? "optional " : "", + formatType(m.idlType), + m.variadic ? "..." : "", + ].join(""); + }).join(", "); + return this.name + "(" + args + ")"; + } + + return this.name; +} + /// Internal helper functions /// function create_suitable_object(type) { @@ -3294,17 +3317,10 @@ IdlNamespace.prototype.test_member_operation = function(member) if (!shouldRunSubTest(this.name)) { return; } - var args = member.arguments.map(function(a) { - var s = a.idlType.idlType; - if (a.variadic) { - s += '...'; - } - return s; - }).join(", "); var a_test = subsetTestByKey( this.name, async_test, - this.name + ' namespace: operation ' + member.name + '(' + args + ')'); + this.name + ' namespace: operation ' + member); a_test.step(function() { assert_own_property( self[this.name], diff --git a/tests/wpt/web-platform-tests/resources/test/tests/functional/api-tests-1.html b/tests/wpt/web-platform-tests/resources/test/tests/functional/api-tests-1.html index 05b570a6d0a..7c77afdda8e 100644 --- a/tests/wpt/web-platform-tests/resources/test/tests/functional/api-tests-1.html +++ b/tests/wpt/web-platform-tests/resources/test/tests/functional/api-tests-1.html @@ -194,6 +194,33 @@ assert_throws_dom("SyntaxError", function () {document.querySelector("")}); }, "Test throw DOM SyntaxError") + test(function() + { + var ifr = document.createElement("iframe"); + document.body.appendChild(ifr); + this.add_cleanup(() => ifr.remove()); + assert_throws_dom("SyntaxError", ifr.contentWindow.DOMException, + function () {ifr.contentDocument.querySelector("")}); + }, "Test throw DOM SyntaxError from subframe"); + + test(function() + { + var ifr = document.createElement("iframe"); + document.body.appendChild(ifr); + this.add_cleanup(() => ifr.remove()); + assert_throws_dom("SyntaxError", + function () {ifr.contentDocument.querySelector("")}); + }, "Test throw DOM SyntaxError from subframe with incorrect global expectation; expected to fail"); + + test(function() + { + var ifr = document.createElement("iframe"); + document.body.appendChild(ifr); + this.add_cleanup(() => ifr.remove()); + assert_throws_dom("SyntaxError", ifr.contentWindow.DOMException, + function () {document.querySelector("")}); + }, "Test throw DOM SyntaxError with incorrect expectation; expected to fail"); + test(function() { assert_throws_dom("SyntaxError", function () {JSON.parse("{")}); @@ -214,13 +241,15 @@ test(function() { - var e = {code:0, name:"TEST_ERR", TEST_ERR:0} + var e = {code:0, name:"TEST_ERR", TEST_ERR:0}; + e.constructor = DOMException; assert_throws_dom("TEST_ERR", function() {throw e}); }, "Test assert_throws_dom with non-DOM-exception expected to Fail"); test(function() { - var e = {code: DOMException.SYNTAX_ERR, name:"SyntaxError"} + var e = {code: DOMException.SYNTAX_ERR, name:"SyntaxError"}; + e.constructor = DOMException; assert_throws_dom(DOMException.SYNTAX_ERR, function() {throw e}); }, "Test assert_throws_dom with number code value expected to Pass"); @@ -358,6 +387,24 @@ "message": null, "properties": {} }, + { + "status_string": "PASS", + "name": "Test throw DOM SyntaxError from subframe", + "message": null, + "properties": {} + }, + { + "status_string": "FAIL", + "name": "Test throw DOM SyntaxError from subframe with incorrect global expectation; expected to fail", + "message": "assert_throws_dom: function \"function () {ifr.contentDocument.querySelector(\"\")}\" threw an exception from the wrong global", + "properties": {} + }, + { + "status_string": "FAIL", + "name": "Test throw DOM SyntaxError with incorrect expectation; expected to fail", + "message": "assert_throws_dom: function \"function () {document.querySelector(\"\")}\" threw an exception from the wrong global", + "properties": {} + }, { "status_string": "FAIL", "name": "Test throw JS SyntaxError where SyntaxError DOMException expected; expected to fail", diff --git a/tests/wpt/web-platform-tests/resources/test/tests/unit/IdlInterfaceMember/toString.html b/tests/wpt/web-platform-tests/resources/test/tests/unit/IdlInterfaceMember/toString.html new file mode 100644 index 00000000000..779c7a15e6d --- /dev/null +++ b/tests/wpt/web-platform-tests/resources/test/tests/unit/IdlInterfaceMember/toString.html @@ -0,0 +1,36 @@ + + + +IdlInterfaceMember.prototype.toString() + + +
+ + + + + + + + diff --git a/tests/wpt/web-platform-tests/resources/testharness.js b/tests/wpt/web-platform-tests/resources/testharness.js index 7a0e4872899..0ec232c1d27 100644 --- a/tests/wpt/web-platform-tests/resources/testharness.js +++ b/tests/wpt/web-platform-tests/resources/testharness.js @@ -645,10 +645,42 @@ policies and contribution forms [3]. }); } - function promise_rejects_dom(test, type, promise, description) { + /** + * Assert that a Promise is rejected with the right DOMException. + * + * @param test the test argument passed to promise_test + * @param {number|string} type. See documentation for assert_throws_dom. + * + * For the remaining arguments, there are two ways of calling + * promise_rejects_dom: + * + * 1) If the DOMException is expected to come from the current global, the + * third argument should be the promise expected to reject, and a fourth, + * optional, argument is the assertion description. + * + * 2) If the DOMException is expected to come from some other global, the + * third argument should be the DOMException constructor from that global, + * the fourth argument the promise expected to reject, and the fifth, + * optional, argument the assertion description. + */ + + function promise_rejects_dom(test, type, promiseOrConstructor, descriptionOrPromise, maybeDescription) { + let constructor, promise, description; + if (typeof promiseOrConstructor === "function" && + promiseOrConstructor.name === "DOMException") { + constructor = promiseOrConstructor; + promise = descriptionOrPromise; + description = maybeDescription; + } else { + constructor = self.DOMException; + promise = promiseOrConstructor; + description = descriptionOrPromise; + assert(maybeDescription === undefined, + "Too many args pased to no-constructor version of promise_rejects_dom"); + } return promise.then(test.unreached_func("Should have rejected: " + description)).catch(function(e) { - assert_throws_dom_impl(type, function() { throw e }, - description, "promise_rejects_dom"); + assert_throws_dom_impl(type, function() { throw e }, description, + "promise_rejects_dom", constructor); }); } @@ -1541,20 +1573,44 @@ policies and contribution forms [3]. * either be an exception name (e.g. "HierarchyRequestError", * "WrongDocumentError") or the name of the corresponding error code * (e.g. "HIERARCHY_REQUEST_ERR", "WRONG_DOCUMENT_ERR"). - * @param {Function} func Function which should throw. - * @param {string} description Error description for the case that the error is not thrown. + * + * For the remaining arguments, there are two ways of calling + * promise_rejects_dom: + * + * 1) If the DOMException is expected to come from the current global, the + * second argument should be the function expected to throw and a third, + * optional, argument is the assertion description. + * + * 2) If the DOMException is expected to come from some other global, the + * second argument should be the DOMException constructor from that global, + * the third argument the function expected to throw, and the fourth, optional, + * argument the assertion description. */ - function assert_throws_dom(type, func, description) + function assert_throws_dom(type, funcOrConstructor, descriptionOrFunc, maybeDescription) { - assert_throws_dom_impl(type, func, description, "assert_throws_dom") + let constructor, func, description; + if (funcOrConstructor.name === "DOMException") { + constructor = funcOrConstructor; + func = descriptionOrFunc; + description = maybeDescription; + } else { + constructor = self.DOMException; + func = funcOrConstructor; + description = descriptionOrFunc; + assert(maybeDescription === undefined, + "Too many args pased to no-constructor version of assert_throws_dom"); + } + assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor) } expose(assert_throws_dom, "assert_throws_dom"); /** - * Like assert_throws_dom but allows specifying the assertion type - * (assert_throws_dom or promise_rejects_dom, in practice). + * Similar to assert_throws_dom but allows specifying the assertion type + * (assert_throws_dom or promise_rejects_dom, in practice). The + * "constructor" argument must be the DOMException constructor from the + * global we expect the exception to come from. */ - function assert_throws_dom_impl(type, func, description, assertion_type) + function assert_throws_dom_impl(type, func, description, assertion_type, constructor) { try { func.call(this); @@ -1565,6 +1621,7 @@ policies and contribution forms [3]. throw e; } + // Basic sanity-checks on the thrown exception. assert(typeof e === "object", assertion_type, description, "${func} threw ${e} with type ${type}, not an object", @@ -1678,19 +1735,21 @@ policies and contribution forms [3]. required_props.name = name; } - //We'd like to test that e instanceof the appropriate interface, - //but we can't, because we don't know what window it was created - //in. It might be an instanceof the appropriate interface on some - //unknown other window. TODO: Work around this somehow? Maybe have - //the first arg just be a DOMException with the right name instead - //of the string-or-code thing we have now? - for (var prop in required_props) { assert(prop in e && e[prop] == required_props[prop], assertion_type, description, "${func} threw ${e} that is not a DOMException " + type + ": property ${prop} is equal to ${actual}, expected ${expected}", {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]}); } + + // Check that the exception is from the right global. This check is last + // so more specific, and more informative, checks on the properties can + // happen in case a totally incorrect exception is thrown. + assert(e.constructor === constructor, + assertion_type, description, + "${func} threw an exception from the wrong global", + {func}); + } } diff --git a/tests/wpt/web-platform-tests/scroll-animations/scroll-animation.html b/tests/wpt/web-platform-tests/scroll-animations/scroll-animation.html index 7a6f8757440..62e5bb854aa 100644 --- a/tests/wpt/web-platform-tests/scroll-animations/scroll-animation.html +++ b/tests/wpt/web-platform-tests/scroll-animations/scroll-animation.html @@ -135,4 +135,28 @@ promise_test(async t => { "The start time is zero in Playing state."); }, 'Animation start and current times are correct when multiple animations' + ' are attached to the same timeline.'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + const scroller = animation.timeline.scrollSource; + // Make the scroll timeline inactive. + scroller.style.overflow = "visible"; + // Trigger layout; + scroller.scrollTop; + assert_equals(animation.timeline.currentTime, null, + "Timeline current time is null in inactive state."); + // Play the animation when the timeline is inactive. + animation.play(); + // Make the scroll timeline active. + scroller.style.overflow = "auto"; + await animation.ready; + // Ready promise is resolved as a result of the timeline becoming active. + assert_equals(animation.timeline.currentTime, 0, + "Timeline current time is resolved in active state."); + assert_equals(animation.currentTime, 0, + "Animation current time is resolved when the animation is ready."); + assert_equals(animation.startTime, 0, + "Animation start time is resolved when the animation is ready."); +}, 'Animation start and current times are correct if scroll timeline is ' + + 'activated after animation.play call.'); \ No newline at end of file diff --git a/tests/wpt/web-platform-tests/scroll-to-text-fragment/scroll-to-text-fragment-target.html b/tests/wpt/web-platform-tests/scroll-to-text-fragment/scroll-to-text-fragment-target.html index b06f3c889c1..b2be85132cb 100644 --- a/tests/wpt/web-platform-tests/scroll-to-text-fragment/scroll-to-text-fragment-target.html +++ b/tests/wpt/web-platform-tests/scroll-to-text-fragment/scroll-to-text-fragment-target.html @@ -74,7 +74,11 @@ window.onload = function() {
Element
-

This is a test page !$'()*+./:;=?@_~ &,- ネコ

+

+ This is a test page !$'()*+./:;=?@_~ &,- ネコ +
+ foo foo foo bar bar bar +

More test page text

diff --git a/tests/wpt/web-platform-tests/scroll-to-text-fragment/scroll-to-text-fragment.html b/tests/wpt/web-platform-tests/scroll-to-text-fragment/scroll-to-text-fragment.html index f0b167a9051..73931d4b0e6 100644 --- a/tests/wpt/web-platform-tests/scroll-to-text-fragment/scroll-to-text-fragment.html +++ b/tests/wpt/web-platform-tests/scroll-to-text-fragment/scroll-to-text-fragment.html @@ -61,6 +61,12 @@ let test_cases = [ expect_position: 'text', description: 'Exact text with prefix and suffix should match text' }, + // Test tricky edge case where prefix and query are equal + { + fragment: '#:~:text=foo-,foo,-bar', + expect_position: 'text', + description: 'Exact text with prefix and suffix and query equals prefix.' + }, // Test text range matching, with all combinations of context terms { fragment: '#:~:text=this,page', diff --git a/tests/wpt/web-platform-tests/service-workers/service-worker/detached-context.https.html b/tests/wpt/web-platform-tests/service-workers/service-worker/detached-context.https.html index c9015b80930..cbb818f7ac0 100644 --- a/tests/wpt/web-platform-tests/service-workers/service-worker/detached-context.https.html +++ b/tests/wpt/web-platform-tests/service-workers/service-worker/detached-context.https.html @@ -75,12 +75,14 @@ promise_test(t => { .then(() => { return with_iframe(scope); }) .then(frame => { const worker = frame.contentWindow.navigator.serviceWorker.controller; + const ctor = frame.contentWindow.DOMException; frame.remove(); assert_equals(worker.scriptURL, normalizeURL(script)); assert_equals(worker.state, 'activated'); worker.onstatechange = () => { /* empty */ }; assert_throws_dom( 'InvalidStateError', + ctor, () => { worker.postMessage(''); }, 'postMessage on a detached client should throw an exception.'); }); diff --git a/tests/wpt/web-platform-tests/service-workers/service-worker/fetch-event-handled.https.html b/tests/wpt/web-platform-tests/service-workers/service-worker/fetch-event-handled.https.html new file mode 100644 index 00000000000..2d6f6c86d87 --- /dev/null +++ b/tests/wpt/web-platform-tests/service-workers/service-worker/fetch-event-handled.https.html @@ -0,0 +1,82 @@ + + +Service Worker: FetchEvent.handled + + + + + diff --git a/tests/wpt/web-platform-tests/service-workers/service-worker/multipart-image.https.html b/tests/wpt/web-platform-tests/service-workers/service-worker/multipart-image.https.html index 98336d7d9e2..00c20d25f90 100644 --- a/tests/wpt/web-platform-tests/service-workers/service-worker/multipart-image.https.html +++ b/tests/wpt/web-platform-tests/service-workers/service-worker/multipart-image.https.html @@ -56,12 +56,13 @@ promise_test(t => { promise_test(t => { return frame.contentWindow.load_multipart_image('cross-origin-multipart-image-with-no-cors') .then(img => { - assert_throws_dom('SecurityError', () => frame.contentWindow.get_image_data(img)); + assert_throws_dom('SecurityError', frame.contentWindow.DOMException, + () => frame.contentWindow.get_image_data(img)); }); }, 'cross-origin multipart image with no-cors via SW should not be readable'); promise_test(t => { const promise = frame.contentWindow.load_multipart_image('cross-origin-multipart-image-with-cors-rejected'); - return promise_rejects_dom(t, 'NetworkError', promise); + return promise_rejects_dom(t, 'NetworkError', frame.contentWindow.DOMException, promise); }, 'cross-origin multipart image via SW with rejected CORS should fail to load'); diff --git a/tests/wpt/web-platform-tests/service-workers/service-worker/resources/fetch-event-handled-worker.js b/tests/wpt/web-platform-tests/service-workers/service-worker/resources/fetch-event-handled-worker.js new file mode 100644 index 00000000000..4af58e20d05 --- /dev/null +++ b/tests/wpt/web-platform-tests/service-workers/service-worker/resources/fetch-event-handled-worker.js @@ -0,0 +1,41 @@ +// This worker reports back the final state of FetchEvent.handled (RESOLVED or +// REJECTED) to the test. + +// Send a message to the client with the client id. +function send_message_to_client(message, clientId) { + clients.get(clientId).then((client) => { + client.postMessage(message); + }); +} + +self.addEventListener('fetch', function(event) { + const clientId = event.clientId; + try { + event.handled.then(() => { + send_message_to_client('RESOLVED', clientId); + }, () => { + send_message_to_client('REJECTED', clientId); + }); + } catch (e) { + send_message_to_client('FAILED', clientId); + return; + } + + const search = new URL(event.request.url).search; + switch (search) { + case '?respondWith-not-called': + break; + case '?respondWith-not-called-and-event-canceled': + event.preventDefault(); + break; + case '?respondWith-called-and-promise-resolved': + event.respondWith(Promise.resolve(new Response('body'))); + break; + case '?respondWith-called-and-promise-resolved-to-invalid-response': + event.respondWith(Promise.resolve('invalid response')); + break; + case '?respondWith-called-and-promise-rejected': + event.respondWith(Promise.reject(new Error('respondWith rejected'))); + break; + } +}); diff --git a/tests/wpt/web-platform-tests/shadow-dom/untriaged/LICENSE b/tests/wpt/web-platform-tests/shadow-dom/untriaged/LICENSE deleted file mode 100644 index 531fac43af2..00000000000 --- a/tests/wpt/web-platform-tests/shadow-dom/untriaged/LICENSE +++ /dev/null @@ -1,107 +0,0 @@ -Copyright 2012, Google Inc. -All rights reserved. - -Licensed under the W3C Test Suite License (the "License"); you may not -use this software except in compliance with the License. You may -obtain a copy of the License at - - http://www.w3.org/Consortium/Legal/2008/04-testsuite-license.html - -Alternatively, this software may be distributed under the terms of the -W3C 3-clause BSD License. You may obtain a copy of the W3C 3-clause -BSD License at - - http://www.w3.org/Consortium/Legal/2008/03-bsd-license.html - - -W3C Test Suite Licence - -This document, Test Suites and other documents that link to this -statement are provided by the copyright holders under the following -license: By using and/or copying this document, or the W3C document -from which this statement is linked, you (the licensee) agree that you -have read, understood, and will comply with the following terms and -conditions: - -Permission to copy, and distribute the contents of this document, or -the W3C document from which this statement is linked, in any medium -for any purpose and without fee or royalty is hereby granted, provided -that you include the following on ALL copies of the document, or -portions thereof, that you use: - - 1. A link or URL to the original W3C document. - 2. The pre-existing copyright notice of the original author, or if -it doesn't exist, a notice (hypertext is preferred, but a textual -representation is permitted) of the form: "Copyright © -[$date-of-document] World Wide Web Consortium, (Massachusetts -Institute of Technology, European Research Consortium for Informatics -and Mathematics, Keio University) and others. All Rights -Reserved. http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html" - 3. If it exists, the STATUS of the W3C document. When space -permits, inclusion of the full text of this NOTICE should be -provided. We request that authorship attribution be provided in any -software, documents, or other items or products that you create -pursuant to the implementation of the contents of this document, or -any portion thereof. - -No right to create modifications or derivatives of W3C documents is -granted pursuant to this license. However, if additional requirements -(documented in the Copyright FAQ) are satisfied, the right to create -modifications or derivatives is sometimes granted by the W3C to -individuals complying with those requirements. - -If a Test Suite distinguishes the test harness (or, framework for -navigation) and the actual tests, permission is given to remove or -alter the harness or navigation if the Test Suite in question allows -to do so. The tests themselves shall NOT be changed in any way. - -The name and trademarks of W3C and other copyright holders may NOT be -used in advertising or publicity pertaining to this document or other -documents that link to this statement without specific, written prior -permission. Title to copyright in this document will at all times -remain with copyright holders. Permission is given to use the -trademarked string W3C within claims of performance concerning W3C -Specifications or features described therein, and there only, if the -test suite so authorizes. - -THIS WORK IS PROVIDED BY W3C, MIT, ERCIM, KEIO UNIVERSITY, THE -COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL W3C, MIT, ERCIM, KEIO UNIVERSITY, THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - - -W3C 3-clause BSD License - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - * Redistributions of works must retain the original copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the original -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of the W3C nor the names of its contributors may -be used to endorse or promote products derived from this work without -specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/wpt/web-platform-tests/tools/localpaths.py b/tests/wpt/web-platform-tests/tools/localpaths.py index f6e24b486d5..ce3b41e300c 100644 --- a/tests/wpt/web-platform-tests/tools/localpaths.py +++ b/tests/wpt/web-platform-tests/tools/localpaths.py @@ -6,7 +6,7 @@ repo_root = os.path.abspath(os.path.join(here, os.pardir)) sys.path.insert(0, os.path.join(here)) sys.path.insert(0, os.path.join(here, "wptserve")) -sys.path.insert(0, os.path.join(here, "pywebsocket")) +sys.path.insert(0, os.path.join(here, "third_party", "pywebsocket3")) sys.path.insert(0, os.path.join(here, "third_party", "atomicwrites")) sys.path.insert(0, os.path.join(here, "third_party", "attrs", "src")) sys.path.insert(0, os.path.join(here, "third_party", "funcsigs")) diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/CONTRIBUTING b/tests/wpt/web-platform-tests/tools/pywebsocket/CONTRIBUTING deleted file mode 100644 index 6415f5d011f..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/CONTRIBUTING +++ /dev/null @@ -1,11 +0,0 @@ -For instructions for contributing code, please read: -https://github.com/google/pywebsocket/wiki/CodeReviewInstruction - -You must complete the Individual Contributor License Agreement. -https://cla.developers.google.com/about/google-individual -You can do this online, and it only takes a minute. - -If you are contributing on behalf of a corporation, you must fill out the -Corporate Contributor License Agreement -https://cla.developers.google.com/about/google-corporate -and send it to us as described on that page. diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/README b/tests/wpt/web-platform-tests/tools/pywebsocket/README deleted file mode 100644 index c8c758f5eef..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/README +++ /dev/null @@ -1,17 +0,0 @@ -INSTALL - -To install this package to the system, run this: -$ python setup.py build -$ sudo python setup.py install - -To install this package as a normal user, run this instead: -$ python setup.py build -$ python setup.py install --user - -LAUNCH - -To use pywebsocket as Apache module, run this to read the document: -$ pydoc mod_pywebsocket - -To use pywebsocket as standalone server, run this to read the document: -$ pydoc mod_pywebsocket.standalone diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/README.md b/tests/wpt/web-platform-tests/tools/pywebsocket/README.md deleted file mode 100644 index ae8e910a84e..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/README.md +++ /dev/null @@ -1,8 +0,0 @@ - -# pywebsocket # - -The pywebsocket project aims to provide a [WebSocket](https://tools.ietf.org/html/rfc6455) standalone server and a WebSocket extension for [Apache HTTP Server](https://httpd.apache.org/), mod\_pywebsocket. - -pywebsocket is intended for **testing** or **experimental** purposes. - -Please see [Wiki](../../wiki) for more details. diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/cookie_wsh.py b/tests/wpt/web-platform-tests/tools/pywebsocket/example/cookie_wsh.py deleted file mode 100644 index 8b327152e70..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/cookie_wsh.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2014 Google Inc. All rights reserved. -# -# Use of this source code is governed by a BSD-style -# license that can be found in the COPYING file or at -# https://developers.google.com/open-source/licenses/bsd - - -import urlparse - - -def _add_set_cookie(request, value): - request.extra_headers.append(('Set-Cookie', value)) - - -def web_socket_do_extra_handshake(request): - components = urlparse.urlparse(request.uri) - command = components[4] - - ONE_DAY_LIFE = 'Max-Age=86400' - - if command == 'set': - _add_set_cookie(request, '; '.join(['foo=bar', ONE_DAY_LIFE])) - elif command == 'set_httponly': - _add_set_cookie(request, - '; '.join(['httpOnlyFoo=bar', ONE_DAY_LIFE, 'httpOnly'])) - elif command == 'clear': - _add_set_cookie(request, 'foo=0; Max-Age=0') - _add_set_cookie(request, 'httpOnlyFoo=0; Max-Age=0') - - -def web_socket_transfer_data(request): - pass diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/echo_client.py b/tests/wpt/web-platform-tests/tools/pywebsocket/example/echo_client.py deleted file mode 100755 index 8ac740e7058..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/echo_client.py +++ /dev/null @@ -1,1129 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Simple WebSocket client named echo_client just because of historical reason. - -mod_pywebsocket directory must be in PYTHONPATH. - -Example Usage: - -# server setup - % cd $pywebsocket - % PYTHONPATH=$cwd/src python ./mod_pywebsocket/standalone.py -p 8880 \ - -d $cwd/src/example - -# run client - % PYTHONPATH=$cwd/src python ./src/example/echo_client.py -p 8880 \ - -s localhost \ - -o http://localhost -r /echo -m test - -or - -# run echo client to test IETF HyBi 00 protocol - run with --protocol-version=hybi00 -""" -from __future__ import print_function - - -import base64 -import codecs -import logging -from optparse import OptionParser -import os -import random -import re -import socket -import struct -import sys - -from mod_pywebsocket import common -from mod_pywebsocket.extensions import DeflateFrameExtensionProcessor -from mod_pywebsocket.extensions import PerMessageDeflateExtensionProcessor -from mod_pywebsocket.extensions import _PerMessageDeflateFramer -from mod_pywebsocket.extensions import _parse_window_bits -from mod_pywebsocket.stream import Stream -from mod_pywebsocket.stream import StreamHixie75 -from mod_pywebsocket.stream import StreamOptions -from mod_pywebsocket import util - - -_TIMEOUT_SEC = 10 -_UNDEFINED_PORT = -1 - -_UPGRADE_HEADER = 'Upgrade: websocket\r\n' -_UPGRADE_HEADER_HIXIE75 = 'Upgrade: WebSocket\r\n' -_CONNECTION_HEADER = 'Connection: Upgrade\r\n' - -# Special message that tells the echo server to start closing handshake -_GOODBYE_MESSAGE = 'Goodbye' - -_PROTOCOL_VERSION_HYBI13 = 'hybi13' -_PROTOCOL_VERSION_HYBI08 = 'hybi08' -_PROTOCOL_VERSION_HYBI00 = 'hybi00' -_PROTOCOL_VERSION_HIXIE75 = 'hixie75' - -# Constants for the --tls_module flag. -_TLS_BY_STANDARD_MODULE = 'ssl' -_TLS_BY_PYOPENSSL = 'pyopenssl' - -# Values used by the --tls-version flag. -_TLS_VERSION_SSL23 = 'ssl23' -_TLS_VERSION_SSL3 = 'ssl3' -_TLS_VERSION_TLS1 = 'tls1' - - -class ClientHandshakeError(Exception): - pass - - -def _build_method_line(resource): - return 'GET %s HTTP/1.1\r\n' % resource - - -def _origin_header(header, origin): - # 4.1 13. concatenation of the string "Origin:", a U+0020 SPACE character, - # and the /origin/ value, converted to ASCII lowercase, to /fields/. - return '%s: %s\r\n' % (header, origin.lower()) - - -def _format_host_header(host, port, secure): - # 4.1 9. Let /hostport/ be an empty string. - # 4.1 10. Append the /host/ value, converted to ASCII lowercase, to - # /hostport/ - hostport = host.lower() - # 4.1 11. If /secure/ is false, and /port/ is not 80, or if /secure/ - # is true, and /port/ is not 443, then append a U+003A COLON character - # (:) followed by the value of /port/, expressed as a base-ten integer, - # to /hostport/ - if ((not secure and port != common.DEFAULT_WEB_SOCKET_PORT) or - (secure and port != common.DEFAULT_WEB_SOCKET_SECURE_PORT)): - hostport += ':' + str(port) - # 4.1 12. concatenation of the string "Host:", a U+0020 SPACE - # character, and /hostport/, to /fields/. - return '%s: %s\r\n' % (common.HOST_HEADER, hostport) - - -def _receive_bytes(socket, length): - bytes = [] - remaining = length - while remaining > 0: - received_bytes = socket.recv(remaining) - if not received_bytes: - raise IOError( - 'Connection closed before receiving requested length ' - '(requested %d bytes but received only %d bytes)' % - (length, length - remaining)) - bytes.append(received_bytes) - remaining -= len(received_bytes) - return ''.join(bytes) - - -def _get_mandatory_header(fields, name): - """Gets the value of the header specified by name from fields. - - This function expects that there's only one header with the specified name - in fields. Otherwise, raises an ClientHandshakeError. - """ - - values = fields.get(name.lower()) - if values is None or len(values) == 0: - raise ClientHandshakeError( - '%s header not found: %r' % (name, values)) - if len(values) > 1: - raise ClientHandshakeError( - 'Multiple %s headers found: %r' % (name, values)) - return values[0] - - -def _validate_mandatory_header(fields, name, - expected_value, case_sensitive=False): - """Gets and validates the value of the header specified by name from - fields. - - If expected_value is specified, compares expected value and actual value - and raises an ClientHandshakeError on failure. You can specify case - sensitiveness in this comparison by case_sensitive parameter. This function - expects that there's only one header with the specified name in fields. - Otherwise, raises an ClientHandshakeError. - """ - - value = _get_mandatory_header(fields, name) - - if ((case_sensitive and value != expected_value) or - (not case_sensitive and value.lower() != expected_value.lower())): - raise ClientHandshakeError( - 'Illegal value for header %s: %r (expected) vs %r (actual)' % - (name, expected_value, value)) - - -class _TLSSocket(object): - """Wrapper for a TLS connection.""" - - def __init__(self, - raw_socket, tls_module, tls_version, disable_tls_compression): - self._logger = util.get_class_logger(self) - - if tls_module == _TLS_BY_STANDARD_MODULE: - if tls_version == _TLS_VERSION_SSL23: - version = ssl.PROTOCOL_SSLv23 - elif tls_version == _TLS_VERSION_SSL3: - version = ssl.PROTOCOL_SSLv3 - elif tls_version == _TLS_VERSION_TLS1: - version = ssl.PROTOCOL_TLSv1 - else: - raise ValueError( - 'Invalid --tls-version flag: %r' % tls_version) - - if disable_tls_compression: - raise ValueError( - '--disable-tls-compression is not available for ssl ' - 'module') - - self._tls_socket = ssl.wrap_socket(raw_socket, ssl_version=version) - - # Print cipher in use. Handshake is done on wrap_socket call. - self._logger.info("Cipher: %s", self._tls_socket.cipher()) - elif tls_module == _TLS_BY_PYOPENSSL: - if tls_version == _TLS_VERSION_SSL23: - version = OpenSSL.SSL.SSLv23_METHOD - elif tls_version == _TLS_VERSION_SSL3: - version = OpenSSL.SSL.SSLv3_METHOD - elif tls_version == _TLS_VERSION_TLS1: - version = OpenSSL.SSL.TLSv1_METHOD - else: - raise ValueError( - 'Invalid --tls-version flag: %r' % tls_version) - - context = OpenSSL.SSL.Context(version) - - if disable_tls_compression: - # OP_NO_COMPRESSION is not defined in OpenSSL module. - context.set_options(0x00020000) - - self._tls_socket = OpenSSL.SSL.Connection(context, raw_socket) - # Client mode. - self._tls_socket.set_connect_state() - self._tls_socket.setblocking(True) - - # Do handshake now (not necessary). - self._tls_socket.do_handshake() - else: - raise ValueError('No TLS support module is available') - - def send(self, data): - return self._tls_socket.write(data) - - def sendall(self, data): - return self._tls_socket.sendall(data) - - def recv(self, size=-1): - return self._tls_socket.read(size) - - def close(self): - return self._tls_socket.close() - - def getpeername(self): - return self._tls_socket.getpeername() - - -class ClientHandshakeBase(object): - """A base class for WebSocket opening handshake processors for each - protocol version. - """ - - def __init__(self): - self._logger = util.get_class_logger(self) - - def _read_fields(self): - # 4.1 32. let /fields/ be a list of name-value pairs, initially empty. - fields = {} - while True: # "Field" - # 4.1 33. let /name/ and /value/ be empty byte arrays - name = '' - value = '' - # 4.1 34. read /name/ - name = self._read_name() - if name is None: - break - # 4.1 35. read spaces - # TODO(tyoshino): Skip only one space as described in the spec. - ch = self._skip_spaces() - # 4.1 36. read /value/ - value = self._read_value(ch) - # 4.1 37. read a byte from the server - ch = _receive_bytes(self._socket, 1) - if ch != '\n': # 0x0A - raise ClientHandshakeError( - 'Expected LF but found %r while reading value %r for ' - 'header %r' % (ch, value, name)) - self._logger.debug('Received %r header', name) - # 4.1 38. append an entry to the /fields/ list that has the name - # given by the string obtained by interpreting the /name/ byte - # array as a UTF-8 stream and the value given by the string - # obtained by interpreting the /value/ byte array as a UTF-8 byte - # stream. - fields.setdefault(name, []).append(value) - # 4.1 39. return to the "Field" step above - return fields - - def _read_name(self): - # 4.1 33. let /name/ be empty byte arrays - name = '' - while True: - # 4.1 34. read a byte from the server - ch = _receive_bytes(self._socket, 1) - if ch == '\r': # 0x0D - return None - elif ch == '\n': # 0x0A - raise ClientHandshakeError( - 'Unexpected LF when reading header name %r' % name) - elif ch == ':': # 0x3A - return name - elif ch >= 'A' and ch <= 'Z': # Range 0x31 to 0x5A - ch = chr(ord(ch) + 0x20) - name += ch - else: - name += ch - - def _skip_spaces(self): - # 4.1 35. read a byte from the server - while True: - ch = _receive_bytes(self._socket, 1) - if ch == ' ': # 0x20 - continue - return ch - - def _read_value(self, ch): - # 4.1 33. let /value/ be empty byte arrays - value = '' - # 4.1 36. read a byte from server. - while True: - if ch == '\r': # 0x0D - return value - elif ch == '\n': # 0x0A - raise ClientHandshakeError( - 'Unexpected LF when reading header value %r' % value) - else: - value += ch - ch = _receive_bytes(self._socket, 1) - - -def _get_permessage_deflate_framer(extension_response): - """Validate the response and return a framer object using the parameters in - the response. This method doesn't accept the server_.* parameters. - """ - - client_max_window_bits = None - client_no_context_takeover = None - - client_max_window_bits_name = ( - PerMessageDeflateExtensionProcessor. - _CLIENT_MAX_WINDOW_BITS_PARAM) - client_no_context_takeover_name = ( - PerMessageDeflateExtensionProcessor. - _CLIENT_NO_CONTEXT_TAKEOVER_PARAM) - - # We didn't send any server_.* parameter. - # Handle those parameters as invalid if found in the response. - - for param_name, param_value in extension_response.get_parameters(): - if param_name == client_max_window_bits_name: - if client_max_window_bits is not None: - raise ClientHandshakeError( - 'Multiple %s found' % client_max_window_bits_name) - - parsed_value = _parse_window_bits(param_value) - if parsed_value is None: - raise ClientHandshakeError( - 'Bad %s: %r' % - (client_max_window_bits_name, param_value)) - client_max_window_bits = parsed_value - elif param_name == client_no_context_takeover_name: - if client_no_context_takeover is not None: - raise ClientHandshakeError( - 'Multiple %s found' % client_no_context_takeover_name) - - if param_value is not None: - raise ClientHandshakeError( - 'Bad %s: Has value %r' % - (client_no_context_takeover_name, param_value)) - client_no_context_takeover = True - - if client_no_context_takeover is None: - client_no_context_takeover = False - - return _PerMessageDeflateFramer(client_max_window_bits, - client_no_context_takeover) - - -class ClientHandshakeProcessor(ClientHandshakeBase): - """WebSocket opening handshake processor for - draft-ietf-hybi-thewebsocketprotocol-06 and later. - """ - - def __init__(self, socket, options): - super(ClientHandshakeProcessor, self).__init__() - - self._socket = socket - self._options = options - - self._logger = util.get_class_logger(self) - - def handshake(self): - """Performs opening handshake on the specified socket. - - Raises: - ClientHandshakeError: handshake failed. - """ - - request_line = _build_method_line(self._options.resource) - self._logger.debug('Client\'s opening handshake Request-Line: %r', - request_line) - self._socket.sendall(request_line) - - fields = [] - fields.append(_format_host_header( - self._options.server_host, - self._options.server_port, - self._options.use_tls)) - fields.append(_UPGRADE_HEADER) - fields.append(_CONNECTION_HEADER) - if self._options.origin is not None: - if self._options.protocol_version == _PROTOCOL_VERSION_HYBI08: - fields.append(_origin_header( - common.SEC_WEBSOCKET_ORIGIN_HEADER, - self._options.origin)) - else: - fields.append(_origin_header(common.ORIGIN_HEADER, - self._options.origin)) - - original_key = os.urandom(16) - self._key = base64.b64encode(original_key) - self._logger.debug( - '%s: %r (%s)', - common.SEC_WEBSOCKET_KEY_HEADER, - self._key, - util.hexify(original_key)) - fields.append( - '%s: %s\r\n' % (common.SEC_WEBSOCKET_KEY_HEADER, self._key)) - - if self._options.version_header > 0: - fields.append('%s: %d\r\n' % (common.SEC_WEBSOCKET_VERSION_HEADER, - self._options.version_header)) - elif self._options.protocol_version == _PROTOCOL_VERSION_HYBI08: - fields.append('%s: %d\r\n' % (common.SEC_WEBSOCKET_VERSION_HEADER, - common.VERSION_HYBI08)) - else: - fields.append('%s: %d\r\n' % (common.SEC_WEBSOCKET_VERSION_HEADER, - common.VERSION_HYBI_LATEST)) - - extensions_to_request = [] - - if self._options.deflate_frame: - extensions_to_request.append( - common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)) - - if self._options.use_permessage_deflate: - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - # Accept the client_max_window_bits extension parameter by default. - extension.add_parameter( - PerMessageDeflateExtensionProcessor. - _CLIENT_MAX_WINDOW_BITS_PARAM, - None) - extensions_to_request.append(extension) - - if len(extensions_to_request) != 0: - fields.append( - '%s: %s\r\n' % - (common.SEC_WEBSOCKET_EXTENSIONS_HEADER, - common.format_extensions(extensions_to_request))) - - for field in fields: - self._socket.sendall(field) - - self._socket.sendall('\r\n') - - self._logger.debug('Sent client\'s opening handshake headers: %r', - fields) - self._logger.debug('Start reading Status-Line') - - status_line = '' - while True: - ch = _receive_bytes(self._socket, 1) - status_line += ch - if ch == '\n': - break - - m = re.match('HTTP/\\d+\\.\\d+ (\\d\\d\\d) .*\r\n', status_line) - if m is None: - raise ClientHandshakeError( - 'Wrong status line format: %r' % status_line) - status_code = m.group(1) - if status_code != '101': - self._logger.debug('Unexpected status code %s with following ' - 'headers: %r', status_code, self._read_fields()) - raise ClientHandshakeError( - 'Expected HTTP status code 101 but found %r' % status_code) - - self._logger.debug('Received valid Status-Line') - self._logger.debug('Start reading headers until we see an empty line') - - fields = self._read_fields() - - ch = _receive_bytes(self._socket, 1) - if ch != '\n': # 0x0A - raise ClientHandshakeError( - 'Expected LF but found %r while reading value %r for header ' - 'name %r' % (ch, value, name)) - - self._logger.debug('Received an empty line') - self._logger.debug('Server\'s opening handshake headers: %r', fields) - - _validate_mandatory_header( - fields, - common.UPGRADE_HEADER, - common.WEBSOCKET_UPGRADE_TYPE, - False) - - _validate_mandatory_header( - fields, - common.CONNECTION_HEADER, - common.UPGRADE_CONNECTION_TYPE, - False) - - accept = _get_mandatory_header( - fields, common.SEC_WEBSOCKET_ACCEPT_HEADER) - - # Validate - try: - binary_accept = base64.b64decode(accept) - except TypeError: - raise HandshakeError( - 'Illegal value for header %s: %r' % - (common.SEC_WEBSOCKET_ACCEPT_HEADER, accept)) - - if len(binary_accept) != 20: - raise ClientHandshakeError( - 'Decoded value of %s is not 20-byte long' % - common.SEC_WEBSOCKET_ACCEPT_HEADER) - - self._logger.debug( - 'Response for challenge : %r (%s)', - accept, util.hexify(binary_accept)) - - binary_expected_accept = util.sha1_hash( - self._key + common.WEBSOCKET_ACCEPT_UUID).digest() - expected_accept = base64.b64encode(binary_expected_accept) - - self._logger.debug( - 'Expected response for challenge: %r (%s)', - expected_accept, util.hexify(binary_expected_accept)) - - if accept != expected_accept: - raise ClientHandshakeError( - 'Invalid %s header: %r (expected: %s)' % - (common.SEC_WEBSOCKET_ACCEPT_HEADER, accept, expected_accept)) - - deflate_frame_accepted = False - permessage_deflate_accepted = False - - extensions_header = fields.get( - common.SEC_WEBSOCKET_EXTENSIONS_HEADER.lower()) - accepted_extensions = [] - if extensions_header is not None and len(extensions_header) != 0: - accepted_extensions = common.parse_extensions(extensions_header[0]) - - # TODO(bashi): Support the new style perframe compression extension. - for extension in accepted_extensions: - extension_name = extension.name() - if (extension_name == common.DEFLATE_FRAME_EXTENSION and - self._options.deflate_frame): - deflate_frame_accepted = True - processor = DeflateFrameExtensionProcessor(extension) - unused_extension_response = processor.get_extension_response() - self._options.deflate_frame = processor - continue - elif (extension_name == common.PERMESSAGE_DEFLATE_EXTENSION and - self._options.use_permessage_deflate): - permessage_deflate_accepted = True - - framer = _get_permessage_deflate_framer(extension) - framer.set_compress_outgoing_enabled(True) - self._options.use_permessage_deflate = framer - continue - - raise ClientHandshakeError( - 'Unexpected extension %r' % extension_name) - - if (self._options.deflate_frame and not deflate_frame_accepted): - raise ClientHandshakeError( - 'Requested %s, but the server rejected it' % - common.DEFLATE_FRAME_EXTENSION) - - if (self._options.use_permessage_deflate and - not permessage_deflate_accepted): - raise ClientHandshakeError( - 'Requested %s, but the server rejected it' % - common.PERMESSAGE_DEFLATE_EXTENSION) - - # TODO(tyoshino): Handle Sec-WebSocket-Protocol - # TODO(tyoshino): Handle Cookie, etc. - - -class ClientHandshakeProcessorHybi00(ClientHandshakeBase): - """WebSocket opening handshake processor for - draft-ietf-hybi-thewebsocketprotocol-00 (equivalent to - draft-hixie-thewebsocketprotocol-76). - """ - - def __init__(self, socket, options): - super(ClientHandshakeProcessorHybi00, self).__init__() - - self._socket = socket - self._options = options - - self._logger = util.get_class_logger(self) - - if (self._options.deflate_frame or - self._options.use_permessage_deflate): - logging.critical('HyBi 00 doesn\'t support extensions.') - sys.exit(1) - - def handshake(self): - """Performs opening handshake on the specified socket. - - Raises: - ClientHandshakeError: handshake failed. - """ - - # 4.1 5. send request line. - self._socket.sendall(_build_method_line(self._options.resource)) - # 4.1 6. Let /fields/ be an empty list of strings. - fields = [] - # 4.1 7. Add the string "Upgrade: WebSocket" to /fields/. - fields.append(_UPGRADE_HEADER_HIXIE75) - # 4.1 8. Add the string "Connection: Upgrade" to /fields/. - fields.append(_CONNECTION_HEADER) - # 4.1 9-12. Add Host: field to /fields/. - fields.append(_format_host_header( - self._options.server_host, - self._options.server_port, - self._options.use_tls)) - # 4.1 13. Add Origin: field to /fields/. - if not self._options.origin: - raise ClientHandshakeError( - 'Specify the origin of the connection by --origin flag') - fields.append(_origin_header(common.ORIGIN_HEADER, - self._options.origin)) - # TODO: 4.1 14 Add Sec-WebSocket-Protocol: field to /fields/. - # TODO: 4.1 15 Add cookie headers to /fields/. - - # 4.1 16-23. Add Sec-WebSocket-Key to /fields/. - self._number1, key1 = self._generate_sec_websocket_key() - self._logger.debug('Number1: %d', self._number1) - fields.append('%s: %s\r\n' % (common.SEC_WEBSOCKET_KEY1_HEADER, key1)) - self._number2, key2 = self._generate_sec_websocket_key() - self._logger.debug('Number2: %d', self._number2) - fields.append('%s: %s\r\n' % (common.SEC_WEBSOCKET_KEY2_HEADER, key2)) - - fields.append('%s: 0\r\n' % common.SEC_WEBSOCKET_DRAFT_HEADER) - - # 4.1 24. For each string in /fields/, in a random order: send the - # string, encoded as UTF-8, followed by a UTF-8 encoded U+000D CARRIAGE - # RETURN U+000A LINE FEED character pair (CRLF). - random.shuffle(fields) - for field in fields: - self._socket.sendall(field) - # 4.1 25. send a UTF-8-encoded U+000D CARRIAGE RETURN U+000A LINE FEED - # character pair (CRLF). - self._socket.sendall('\r\n') - # 4.1 26. let /key3/ be a string consisting of eight random bytes (or - # equivalently, a random 64 bit integer encoded in a big-endian order). - self._key3 = self._generate_key3() - # 4.1 27. send /key3/ to the server. - self._socket.sendall(self._key3) - self._logger.debug( - 'Key3: %r (%s)', self._key3, util.hexify(self._key3)) - - self._logger.info('Sent handshake') - - # 4.1 28. Read bytes from the server until either the connection - # closes, or a 0x0A byte is read. let /field/ be these bytes, including - # the 0x0A bytes. - field = '' - while True: - ch = _receive_bytes(self._socket, 1) - field += ch - if ch == '\n': - break - # if /field/ is not at least seven bytes long, or if the last - # two bytes aren't 0x0D and 0x0A respectively, or if it does not - # contain at least two 0x20 bytes, then fail the WebSocket connection - # and abort these steps. - if len(field) < 7 or not field.endswith('\r\n'): - raise ClientHandshakeError('Wrong status line: %r' % field) - m = re.match('[^ ]* ([^ ]*) .*', field) - if m is None: - raise ClientHandshakeError( - 'No HTTP status code found in status line: %r' % field) - # 4.1 29. let /code/ be the substring of /field/ that starts from the - # byte after the first 0x20 byte, and ends with the byte before the - # second 0x20 byte. - code = m.group(1) - # 4.1 30. if /code/ is not three bytes long, or if any of the bytes in - # /code/ are not in the range 0x30 to 0x90, then fail the WebSocket - # connection and abort these steps. - if not re.match('[0-9][0-9][0-9]', code): - raise ClientHandshakeError( - 'HTTP status code %r is not three digit in status line: %r' % - (code, field)) - # 4.1 31. if /code/, interpreted as UTF-8, is "101", then move to the - # next step. - if code != '101': - raise ClientHandshakeError( - 'Expected HTTP status code 101 but found %r in status line: ' - '%r' % (code, field)) - # 4.1 32-39. read fields into /fields/ - fields = self._read_fields() - # 4.1 40. _Fields processing_ - # read a byte from server - ch = _receive_bytes(self._socket, 1) - if ch != '\n': # 0x0A - raise ClientHandshakeError('Expected LF but found %r' % ch) - # 4.1 41. check /fields/ - # TODO(ukai): protocol - # if the entry's name is "upgrade" - # if the value is not exactly equal to the string "WebSocket", - # then fail the WebSocket connection and abort these steps. - _validate_mandatory_header( - fields, - common.UPGRADE_HEADER, - common.WEBSOCKET_UPGRADE_TYPE_HIXIE75, - True) - # if the entry's name is "connection" - # if the value, converted to ASCII lowercase, is not exactly equal - # to the string "upgrade", then fail the WebSocket connection and - # abort these steps. - _validate_mandatory_header( - fields, - common.CONNECTION_HEADER, - common.UPGRADE_CONNECTION_TYPE, - False) - - origin = _get_mandatory_header( - fields, common.SEC_WEBSOCKET_ORIGIN_HEADER) - - location = _get_mandatory_header( - fields, common.SEC_WEBSOCKET_LOCATION_HEADER) - - # TODO(ukai): check origin, location, cookie, .. - - # 4.1 42. let /challenge/ be the concatenation of /number_1/, - # expressed as a big endian 32 bit integer, /number_2/, expressed - # as big endian 32 bit integer, and the eight bytes of /key_3/ in the - # order they were sent on the wire. - challenge = struct.pack('!I', self._number1) - challenge += struct.pack('!I', self._number2) - challenge += self._key3 - - self._logger.debug( - 'Challenge: %r (%s)', challenge, util.hexify(challenge)) - - # 4.1 43. let /expected/ be the MD5 fingerprint of /challenge/ as a - # big-endian 128 bit string. - expected = util.md5_hash(challenge).digest() - self._logger.debug( - 'Expected challenge response: %r (%s)', - expected, util.hexify(expected)) - - # 4.1 44. read sixteen bytes from the server. - # let /reply/ be those bytes. - reply = _receive_bytes(self._socket, 16) - self._logger.debug( - 'Actual challenge response: %r (%s)', reply, util.hexify(reply)) - - # 4.1 45. if /reply/ does not exactly equal /expected/, then fail - # the WebSocket connection and abort these steps. - if expected != reply: - raise ClientHandshakeError( - 'Bad challenge response: %r (expected) != %r (actual)' % - (expected, reply)) - # 4.1 46. The *WebSocket connection is established*. - - def _generate_sec_websocket_key(self): - # 4.1 16. let /spaces_n/ be a random integer from 1 to 12 inclusive. - spaces = random.randint(1, 12) - # 4.1 17. let /max_n/ be the largest integer not greater than - # 4,294,967,295 divided by /spaces_n/. - maxnum = 4294967295 / spaces - # 4.1 18. let /number_n/ be a random integer from 0 to /max_n/ - # inclusive. - number = random.randint(0, maxnum) - # 4.1 19. let /product_n/ be the result of multiplying /number_n/ and - # /spaces_n/ together. - product = number * spaces - # 4.1 20. let /key_n/ be a string consisting of /product_n/, expressed - # in base ten using the numerals in the range U+0030 DIGIT ZERO (0) to - # U+0039 DIGIT NINE (9). - key = str(product) - # 4.1 21. insert between one and twelve random characters from the - # range U+0021 to U+002F and U+003A to U+007E into /key_n/ at random - # positions. - available_chars = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1) - n = random.randint(1, 12) - for _ in xrange(n): - ch = random.choice(available_chars) - pos = random.randint(0, len(key)) - key = key[0:pos] + chr(ch) + key[pos:] - # 4.1 22. insert /spaces_n/ U+0020 SPACE characters into /key_n/ at - # random positions other than start or end of the string. - for _ in xrange(spaces): - pos = random.randint(1, len(key) - 1) - key = key[0:pos] + ' ' + key[pos:] - return number, key - - def _generate_key3(self): - # 4.1 26. let /key3/ be a string consisting of eight random bytes (or - # equivalently, a random 64 bit integer encoded in a big-endian order). - return ''.join([chr(random.randint(0, 255)) for _ in xrange(8)]) - - -class ClientConnection(object): - """A wrapper for socket object to provide the mp_conn interface. - mod_pywebsocket library is designed to be working on Apache mod_python's - mp_conn object. - """ - - def __init__(self, socket): - self._socket = socket - - def write(self, data): - self._socket.sendall(data) - - def read(self, n): - return self._socket.recv(n) - - def get_remote_addr(self): - return self._socket.getpeername() - remote_addr = property(get_remote_addr) - - -class ClientRequest(object): - """A wrapper class just to make it able to pass a socket object to - functions that expect a mp_request object. - """ - - def __init__(self, socket): - self._logger = util.get_class_logger(self) - - self._socket = socket - self.connection = ClientConnection(socket) - - -def _import_ssl(): - global ssl - try: - import ssl - return True - except ImportError: - return False - - -def _import_pyopenssl(): - global OpenSSL - try: - import OpenSSL.SSL - return True - except ImportError: - return False - - -class EchoClient(object): - """WebSocket echo client.""" - - def __init__(self, options): - self._options = options - self._socket = None - - self._logger = util.get_class_logger(self) - - def run(self): - """Run the client. - - Shake hands and then repeat sending message and receiving its echo. - """ - - self._socket = socket.socket() - self._socket.settimeout(self._options.socket_timeout) - try: - self._socket.connect((self._options.server_host, - self._options.server_port)) - if self._options.use_tls: - self._socket = _TLSSocket( - self._socket, - self._options.tls_module, - self._options.tls_version, - self._options.disable_tls_compression) - - version = self._options.protocol_version - - if (version == _PROTOCOL_VERSION_HYBI08 or - version == _PROTOCOL_VERSION_HYBI13): - self._handshake = ClientHandshakeProcessor( - self._socket, self._options) - elif version == _PROTOCOL_VERSION_HYBI00: - self._handshake = ClientHandshakeProcessorHybi00( - self._socket, self._options) - else: - raise ValueError( - 'Invalid --protocol-version flag: %r' % version) - - self._handshake.handshake() - - self._logger.info('Connection established') - - request = ClientRequest(self._socket) - - version_map = { - _PROTOCOL_VERSION_HYBI08: common.VERSION_HYBI08, - _PROTOCOL_VERSION_HYBI13: common.VERSION_HYBI13, - _PROTOCOL_VERSION_HYBI00: common.VERSION_HYBI00} - request.ws_version = version_map[version] - - if (version == _PROTOCOL_VERSION_HYBI08 or - version == _PROTOCOL_VERSION_HYBI13): - stream_option = StreamOptions() - stream_option.mask_send = True - stream_option.unmask_receive = False - - if self._options.deflate_frame is not False: - processor = self._options.deflate_frame - processor.setup_stream_options(stream_option) - - if self._options.use_permessage_deflate is not False: - framer = self._options.use_permessage_deflate - framer.setup_stream_options(stream_option) - - self._stream = Stream(request, stream_option) - elif version == _PROTOCOL_VERSION_HYBI00: - self._stream = StreamHixie75(request, True) - - for line in self._options.message.split(','): - self._stream.send_message(line) - if self._options.verbose: - print('Send: %s' % line) - try: - received = self._stream.receive_message() - - if self._options.verbose: - print('Recv: %s' % received) - except Exception as e: - if self._options.verbose: - print('Error: %s' % e) - raise - - self._do_closing_handshake() - finally: - self._socket.close() - - def _do_closing_handshake(self): - """Perform closing handshake using the specified closing frame.""" - - if self._options.message.split(',')[-1] == _GOODBYE_MESSAGE: - # requested server initiated closing handshake, so - # expecting closing handshake message from server. - self._logger.info('Wait for server-initiated closing handshake') - message = self._stream.receive_message() - if message is None: - print('Recv close') - print('Send ack') - self._logger.info( - 'Received closing handshake and sent ack') - return - print('Send close') - self._stream.close_connection() - self._logger.info('Sent closing handshake') - print('Recv ack') - self._logger.info('Received ack') - - -def main(): - sys.stdout = codecs.getwriter('utf-8')(sys.stdout) - - parser = OptionParser() - # We accept --command_line_flag style flags which is the same as Google - # gflags in addition to common --command-line-flag style flags. - parser.add_option('-s', '--server-host', '--server_host', - dest='server_host', type='string', - default='localhost', help='server host') - parser.add_option('-p', '--server-port', '--server_port', - dest='server_port', type='int', - default=_UNDEFINED_PORT, help='server port') - parser.add_option('-o', '--origin', dest='origin', type='string', - default=None, help='origin') - parser.add_option('-r', '--resource', dest='resource', type='string', - default='/echo', help='resource path') - parser.add_option('-m', '--message', dest='message', type='string', - help=('comma-separated messages to send. ' - '%s will force close the connection from server.' % - _GOODBYE_MESSAGE)) - parser.add_option('-q', '--quiet', dest='verbose', action='store_false', - default=True, help='suppress messages') - parser.add_option('-t', '--tls', dest='use_tls', action='store_true', - default=False, help='use TLS (wss://). By default, ' - 'it looks for ssl and pyOpenSSL module and uses found ' - 'one. Use --tls-module option to specify which module ' - 'to use') - parser.add_option('--tls-module', '--tls_module', dest='tls_module', - type='choice', - choices=[_TLS_BY_STANDARD_MODULE, _TLS_BY_PYOPENSSL], - help='Use ssl module if "%s" is specified. ' - 'Use pyOpenSSL module if "%s" is specified' % - (_TLS_BY_STANDARD_MODULE, _TLS_BY_PYOPENSSL)) - parser.add_option('--tls-version', '--tls_version', - dest='tls_version', - type='string', default=_TLS_VERSION_SSL23, - help='TLS/SSL version to use. One of \'' + - _TLS_VERSION_SSL23 + '\' (SSL version 2 or 3), \'' + - _TLS_VERSION_SSL3 + '\' (SSL version 3), \'' + - _TLS_VERSION_TLS1 + '\' (TLS version 1)') - parser.add_option('--disable-tls-compression', '--disable_tls_compression', - dest='disable_tls_compression', - action='store_true', default=False, - help='Disable TLS compression. Available only when ' - 'pyOpenSSL module is used.') - parser.add_option('-k', '--socket-timeout', '--socket_timeout', - dest='socket_timeout', type='int', default=_TIMEOUT_SEC, - help='Timeout(sec) for sockets') - parser.add_option('--draft75', dest='draft75', - action='store_true', default=False, - help='Obsolete option. Don\'t use this.') - parser.add_option('--protocol-version', '--protocol_version', - dest='protocol_version', - type='string', default=_PROTOCOL_VERSION_HYBI13, - help='WebSocket protocol version to use. One of \'' + - _PROTOCOL_VERSION_HYBI13 + '\', \'' + - _PROTOCOL_VERSION_HYBI08 + '\', \'' + - _PROTOCOL_VERSION_HYBI00 + '\'') - parser.add_option('--version-header', '--version_header', - dest='version_header', - type='int', default=-1, - help='Specify Sec-WebSocket-Version header value') - parser.add_option('--deflate-frame', '--deflate_frame', - dest='deflate_frame', - action='store_true', default=False, - help='Use the deflate-frame extension.') - parser.add_option('--use-permessage-deflate', '--use_permessage_deflate', - dest='use_permessage_deflate', - action='store_true', default=False, - help='Use the permessage-deflate extension.') - parser.add_option('--log-level', '--log_level', type='choice', - dest='log_level', default='warn', - choices=['debug', 'info', 'warn', 'error', 'critical'], - help='Log level.') - - (options, unused_args) = parser.parse_args() - - logging.basicConfig(level=logging.getLevelName(options.log_level.upper())) - - if options.draft75: - logging.critical('--draft75 option is obsolete.') - sys.exit(1) - - if options.protocol_version == _PROTOCOL_VERSION_HIXIE75: - logging.critical( - 'Value %s is obsolete for --protocol_version options' % - _PROTOCOL_VERSION_HIXIE75) - sys.exit(1) - - if options.use_tls: - if options.tls_module is None: - if _import_ssl(): - options.tls_module = _TLS_BY_STANDARD_MODULE - logging.debug('Using ssl module') - elif _import_pyopenssl(): - options.tls_module = _TLS_BY_PYOPENSSL - logging.debug('Using pyOpenSSL module') - else: - logging.critical( - 'TLS support requires ssl or pyOpenSSL module.') - sys.exit(1) - elif options.tls_module == _TLS_BY_STANDARD_MODULE: - if not _import_ssl(): - logging.critical('ssl module is not available') - sys.exit(1) - elif options.tls_module == _TLS_BY_PYOPENSSL: - if not _import_pyopenssl(): - logging.critical('pyOpenSSL module is not available') - sys.exit(1) - else: - logging.critical('Invalid --tls-module option: %r', - options.tls_module) - sys.exit(1) - - if (options.disable_tls_compression and - options.tls_module != _TLS_BY_PYOPENSSL): - logging.critical('You can disable TLS compression only when ' - 'pyOpenSSL module is used.') - sys.exit(1) - else: - if options.tls_module is not None: - logging.critical('Use --tls-module option only together with ' - '--use-tls option.') - sys.exit(1) - - if options.disable_tls_compression: - logging.critical('Use --disable-tls-compression only together ' - 'with --use-tls option.') - sys.exit(1) - - # Default port number depends on whether TLS is used. - if options.server_port == _UNDEFINED_PORT: - if options.use_tls: - options.server_port = common.DEFAULT_WEB_SOCKET_SECURE_PORT - else: - options.server_port = common.DEFAULT_WEB_SOCKET_PORT - - # optparse doesn't seem to handle non-ascii default values. - # Set default message here. - if not options.message: - options.message = u'Hello,\u65e5\u672c' # "Japan" in Japanese - - EchoClient(options).run() - - -if __name__ == '__main__': - main() - - -# vi:sts=4 sw=4 et diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/fetch_benchmark.html b/tests/wpt/web-platform-tests/tools/pywebsocket/example/fetch_benchmark.html deleted file mode 100644 index 2105eb67a55..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/fetch_benchmark.html +++ /dev/null @@ -1,163 +0,0 @@ - - - -Fetch API benchmark - - - - - - - -
- url prefix - - - - - -
- - - - - - - - -
- - Parameters: - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Number of fetch() requests
Number of iterations
Number of warm-up iterations
Start size
Stop threshold
Multipliers
- - Set data type - - - -
- -
- -
-
- Summary - -
- -
- Note: -
    -
  • Effect of RTT and time spent for ArrayBuffer creation in receive benchmarks are not eliminated.
  • -
  • The Stddev column shows NaN when the number of iterations is set to 1.
  • -
-
- - - diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/fetch_benchmark.js b/tests/wpt/web-platform-tests/tools/pywebsocket/example/fetch_benchmark.js deleted file mode 100644 index 13952190a8e..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/fetch_benchmark.js +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright 2015 Google Inc. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the COPYING file or at -// https://developers.google.com/open-source/licenses/bsd - -var isWorker = typeof importScripts !== "undefined"; - -if (isWorker) { - // Running on a worker - importScripts('util.js', 'util_worker.js'); -} - -// Namespace for holding globals. -var benchmark = {}; -benchmark.startTimeInMs = 0; - -var timerID = null; - -function sendBenchmarkStep(size, config, isWarmUp) { - timerID = null; - benchmark.startTimeInMs = null; - - // Prepare data. - var dataArray = []; - for (var i = 0; i < config.numFetches; ++i) { - var data = null; - if (config.dataType == 'arraybuffer' || - config.dataType == 'blob') { - data = new ArrayBuffer(size); - - fillArrayBuffer(data, 0x61); - - if (config.dataType == 'blob') { - data = new Blob([data]); - } - } else { - data = repeatString('a', size); - } - - dataArray.push(data); - } - - // Start time measuring. - benchmark.startTimeInMs = getTimeStamp(); - - // Start fetch. - var promises = []; - for (var i = 0; i < config.numFetches; ++i) { - var data = dataArray[i]; - var promise = fetch(config.prefixUrl + '_send', - {method: 'POST', body: data}) - .then(function (response) { - if (response.status != 200) { - config.addToLog('Failed (status=' + response.status + ')'); - return Promise.reject(); - } - // Check and warn if proxy is enabled. - if (response.headers.get('Via') !== null) { - config.addToLog('WARNING: proxy seems enabled.'); - } - if (config.verifyData) { - return response.text() - .then(function(text) { - if (!verifyAcknowledgement(config, text, size)) { - return Promise.reject(); - } - }); - } - }); - promises.push(promise); - } - - // Finish and report time measuring. - Promise.all(promises) - .then(function() { - if (benchmark.startTimeInMs == null) { - config.addToLog('startTimeInMs not set'); - return Promise.reject(); - } - calculateAndLogResult(config, size, benchmark.startTimeInMs, - size * config.numFetches, isWarmUp); - runNextTask(config); - }) - .catch(function(e) { - config.addToLog("ERROR: " + e); - config.notifyAbort(); - }); -} - -function receiveBenchmarkStep(size, config, isWarmUp) { - timerID = null; - benchmark.startTimeInMs = null; - - // Start time measuring. - benchmark.startTimeInMs = getTimeStamp(); - - // Start fetch. - var promises = []; - for (var i = 0; i < config.numFetches; ++i) { - var request; - if (config.methodAndCache === 'GET-NOCACHE') { - request = new Request(config.prefixUrl + '_receive_getnocache?' + size, - {method: 'GET'}); - } else if (config.methodAndCache === 'GET-CACHE') { - request = new Request(config.prefixUrl + '_receive_getcache?' + size, - {method: 'GET'}); - } else { - request = new Request(config.prefixUrl + '_receive', - {method: 'POST', body: size + ' none'}); - } - var promise = fetch(request) - .then(function(response) { - if (response.status != 200) { - config.addToLog('Failed (status=' + this.status + ')'); - return Promise.reject(); - } - // Check and warn if proxy is enabled. - if (response.headers.get('Via') !== null) { - config.addToLog('WARNING: proxy seems enabled.'); - } - if (config.dataType === 'arraybuffer') { - return response.arrayBuffer() - .then(function(arrayBuffer) { - return [arrayBuffer.byteLength, - (!config.verifyData || - verifyArrayBuffer(arrayBuffer, 0x61))]; - }); - } else if (config.dataType == 'blob') { - return response.blob() - .then(function(blob) { - return new Promise(function(resolve, reject) { - if (config.verifyData) { - verifyBlob(config, blob, 0x61, - function(receivedSize, verificationResult) { - resolve([receivedSize, verificationResult]); - }); - } else { - resolve([blob.size, true]); - } - }); - }); - } else { - return response.text() - .then(function(text) { - return [text.length, - (!config.verifyData || - text == repeatString('a', text.length))]; - }); - } - }) - .then(function(receivedSizeAndVerificationResult) { - var receivedSize = receivedSizeAndVerificationResult[0]; - var verificationResult = receivedSizeAndVerificationResult[1]; - if (receivedSize !== size) { - config.addToLog('Expected ' + size + - 'B but received ' + receivedSize + 'B'); - return Promise.reject(); - } - if (!verificationResult) { - config.addToLog('Response verification failed'); - return Promise.reject(); - } - }); - promises.push(promise); - } - - // Finish and report time measuring. - Promise.all(promises) - .then(function() { - if (benchmark.startTimeInMs == null) { - config.addToLog('startTimeInMs not set'); - return Promise.reject(); - } - calculateAndLogResult(config, size, benchmark.startTimeInMs, - size * config.numFetches, isWarmUp); - runNextTask(config); - }) - .catch(function(e) { - config.addToLog("ERROR: " + e); - config.notifyAbort(); - }); -} - - -function getConfigString(config) { - return '(' + config.dataType + - ', verifyData=' + config.verifyData + - ', ' + (isWorker ? 'Worker' : 'Main') + - ', numFetches=' + config.numFetches + - ', numIterations=' + config.numIterations + - ', numWarmUpIterations=' + config.numWarmUpIterations + - ')'; -} - -function startBenchmark(config) { - clearTimeout(timerID); - - runNextTask(config); -} - -function batchBenchmark(originalConfig) { - originalConfig.addToLog('Batch benchmark'); - - tasks = []; - clearAverageData(); - - var dataTypes = ['text', 'blob', 'arraybuffer']; - var stepFuncs = [sendBenchmarkStep, receiveBenchmarkStep]; - var names = ['Send', 'Receive']; - for (var i = 0; i < stepFuncs.length; ++i) { - for (var j = 0; j < dataTypes.length; ++j) { - var config = cloneConfig(originalConfig); - config.dataType = dataTypes[j]; - addTasks(config, stepFuncs[i]); - addResultReportingTask(config, - names[i] + ' benchmark ' + getConfigString(config)); - } - } - - startBenchmark(config); -} - -function cleanup() { -} diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/fetch_performance_test_iframe.html b/tests/wpt/web-platform-tests/tools/pywebsocket/example/fetch_performance_test_iframe.html deleted file mode 100644 index e12df2e7df4..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/fetch_performance_test_iframe.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/performance_test_iframe.html b/tests/wpt/web-platform-tests/tools/pywebsocket/example/performance_test_iframe.html deleted file mode 100644 index 78aede97a14..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/performance_test_iframe.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/performance_test_iframe.js b/tests/wpt/web-platform-tests/tools/pywebsocket/example/performance_test_iframe.js deleted file mode 100644 index 065b25bd1ff..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/performance_test_iframe.js +++ /dev/null @@ -1,66 +0,0 @@ -function perfTestAddToLog(text) { - parent.postMessage({'command': 'log', 'value': text}, '*'); -} - -function perfTestAddToSummary(text) { -} - -function perfTestMeasureValue(value) { - parent.postMessage({'command': 'measureValue', 'value': value}, '*'); -} - -function perfTestNotifyAbort() { - parent.postMessage({'command': 'notifyAbort'}, '*'); -} - -function getConfigForPerformanceTest(connectionType, dataType, async, - verifyData, numIterations, - numWarmUpIterations) { - var prefixUrl; - if (connectionType === 'WebSocket') { - prefixUrl = 'ws://' + location.host + '/benchmark_helper'; - } else { - // XHR or fetch - prefixUrl = 'http://' + location.host + '/073be001e10950692ccbf3a2ad21c245'; - } - - return { - prefixUrl: prefixUrl, - printSize: true, - numXHRs: 1, - numFetches: 1, - numSockets: 1, - // + 1 is for a warmup iteration by the Telemetry framework. - numIterations: numIterations + numWarmUpIterations + 1, - numWarmUpIterations: numWarmUpIterations, - minTotal: 10240000, - startSize: 10240000, - stopThreshold: 10240000, - multipliers: [2], - verifyData: verifyData, - dataType: dataType, - async: async, - addToLog: perfTestAddToLog, - addToSummary: perfTestAddToSummary, - measureValue: perfTestMeasureValue, - notifyAbort: perfTestNotifyAbort - }; -} - -var data; -onmessage = function(message) { - var action; - if (message.data.command === 'start') { - data = message.data; - initWorker(data.connectionType, 'http://' + location.host); - action = data.benchmarkName; - } else { - action = 'stop'; - } - - var config = getConfigForPerformanceTest(data.connectionType, data.dataType, - data.async, data.verifyData, - data.numIterations, - data.numWarmUpIterations); - doAction(config, data.isWorker, action); -}; diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/pywebsocket.conf b/tests/wpt/web-platform-tests/tools/pywebsocket/example/pywebsocket.conf deleted file mode 100644 index 335d130a5c8..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/pywebsocket.conf +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# -# Sample configuration file for apache2 -# -LogLevel debug - - PythonPath "sys.path+['/mod_pywebsocket']" - PythonOption mod_pywebsocket.handler_root /var/www - PythonOption mod_pywebsocket.handler_scan /var/www/ws - #PythonOption mod_pywebsocket.allow_draft75 On - - PythonHeaderParserHandler mod_pywebsocket.headerparserhandler - - diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/util_main.js b/tests/wpt/web-platform-tests/tools/pywebsocket/example/util_main.js deleted file mode 100644 index 471ed63fb5d..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/util_main.js +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2014 Google Inc. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the COPYING file or at -// https://developers.google.com/open-source/licenses/bsd - -// Utilities for example applications (for the main thread only). - -var logBox = null; -var queuedLog = ''; - -var summaryBox = null; - -function queueLog(log) { - queuedLog += log + '\n'; -} - -function addToLog(log) { - logBox.value += queuedLog; - queuedLog = ''; - logBox.value += log + '\n'; - logBox.scrollTop = 1000000; -} - -function addToSummary(log) { - summaryBox.value += log + '\n'; - summaryBox.scrollTop = 1000000; -} - -// value: execution time in milliseconds. -// config.measureValue is intended to be used in Performance Tests. -// Do nothing here in non-PerformanceTest. -function measureValue(value) { -} - -// config.notifyAbort is called when the benchmark failed and aborted, and -// intended to be used in Performance Tests. -// Do nothing here in non-PerformanceTest. -function notifyAbort() { -} - -function getIntFromInput(id) { - return parseInt(document.getElementById(id).value); -} - -function getStringFromRadioBox(name) { - var list = document.getElementById('benchmark_form')[name]; - for (var i = 0; i < list.length; ++i) - if (list.item(i).checked) - return list.item(i).value; - return undefined; -} -function getBoolFromCheckBox(id) { - return document.getElementById(id).checked; -} - -function getIntArrayFromInput(id) { - var strArray = document.getElementById(id).value.split(','); - return strArray.map(function(str) { return parseInt(str, 10); }); -} - -function getFloatArrayFromInput(id) { - var strArray = document.getElementById(id).value.split(','); - return strArray.map(parseFloat); -} diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/util_worker.js b/tests/wpt/web-platform-tests/tools/pywebsocket/example/util_worker.js deleted file mode 100644 index 98dcf441729..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/util_worker.js +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2014 Google Inc. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the COPYING file or at -// https://developers.google.com/open-source/licenses/bsd - -// Utilities for example applications (for the worker threads only). - -onmessage = function (message) { - var config = message.data.config; - config.addToLog = function(text) { - postMessage({type: 'addToLog', data: text}); }; - config.addToSummary = function(text) { - postMessage({type: 'addToSummary', data: text}); }; - config.measureValue = function(value) { - postMessage({type: 'measureValue', data: value}); }; - config.notifyAbort = function() { postMessage({type: 'notifyAbort'}); }; - - doAction(config, false, message.data.type); -}; diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_benchmark.html b/tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_benchmark.html deleted file mode 100644 index 800d20c9ccb..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_benchmark.html +++ /dev/null @@ -1,212 +0,0 @@ - - - - -XMLHttpRequest benchmark - - - - - - - -
- url prefix - - - - - -
- - - - - - - - -
- (Receive && Non-Worker && Sync is not supported by spec) - -
- - Parameters: - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Num XHRs
Number of iterations
Number of warm-up iterations
Start size
Stop threshold
Multipliers
- - Set data type - - - - -
- Set HTTP method and cache control - - - -
- (Cache control: receive only. Cache is valid for 10 seconds. This config controls Cache-control HTTP response header. Browsers might not cache data e.g. when it is large) -
- -
- -
-
- Summary - -
- -
- Note: -
    -
  • Effect of RTT and time spent for ArrayBuffer creation in receive benchmarks are not eliminated.
  • -
  • The Stddev column shows NaN when the number of iterations is set to 1.
  • -
-
- - - diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_benchmark.js b/tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_benchmark.js deleted file mode 100644 index 14a97e8fb83..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_benchmark.js +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2014 Google Inc. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the COPYING file or at -// https://developers.google.com/open-source/licenses/bsd - -var isWorker = typeof importScripts !== "undefined"; - -if (isWorker) { - // Running on a worker - importScripts('util.js', 'util_worker.js'); -} - -// Namespace for holding globals. -var benchmark = {}; -benchmark.startTimeInMs = 0; - -var xhrs = []; - -var timerID = null; - -function destroyAllXHRs() { - for (var i = 0; i < xhrs.length; ++i) { - xhrs[i].onreadystatechange = null; - // Abort XHRs if they are not yet DONE state. - // Calling abort() here (i.e. in onreadystatechange handler) - // causes "NetworkError" messages in DevTools in sync mode, - // even if it is after transition to DONE state. - if (xhrs[i].readyState != XMLHttpRequest.DONE) - xhrs[i].abort(); - } - xhrs = []; - // gc() might be needed for Chrome/Blob -} - -function sendBenchmarkStep(size, config, isWarmUp) { - timerID = null; - - benchmark.startTimeInMs = null; - var totalSize = 0; - var totalReplied = 0; - - var onReadyStateChangeHandler = function () { - if (this.readyState != this.DONE) { - return; - } - - if (this.status != 200) { - config.addToLog('Failed (status=' + this.status + ')'); - destroyAllXHRs(); - config.notifyAbort(); - return; - } - - if (config.verifyData && - !verifyAcknowledgement(config, this.response, size)) { - destroyAllXHRs(); - config.notifyAbort(); - return; - } - - totalReplied += size; - - if (totalReplied < totalSize) { - return; - } - - if (benchmark.startTimeInMs == null) { - config.addToLog('startTimeInMs not set'); - destroyAllXHRs(); - config.notifyAbort(); - return; - } - - // Check and warn if proxy is enabled. - if (this.getResponseHeader('Via') !== null) { - config.addToLog('WARNING: proxy seems enabled.'); - } - - calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize, - isWarmUp); - - destroyAllXHRs(); - - runNextTask(config); - }; - - for (var i = 0; i < config.numXHRs; ++i) { - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = onReadyStateChangeHandler; - xhrs.push(xhr); - } - - var dataArray = []; - - for (var i = 0; i < xhrs.length; ++i) { - var data = null; - if (config.dataType == 'arraybuffer' || - config.dataType == 'blob') { - data = new ArrayBuffer(size); - - fillArrayBuffer(data, 0x61); - - if (config.dataType == 'blob') { - data = new Blob([data]); - } - } else { - data = repeatString('a', size); - } - - dataArray.push(data); - } - - - benchmark.startTimeInMs = getTimeStamp(); - totalSize = size * xhrs.length; - - for (var i = 0; i < xhrs.length; ++i) { - var data = dataArray[i]; - var xhr = xhrs[i]; - xhr.open('POST', config.prefixUrl + '_send', config.async); - xhr.send(data); - } -} - -function receiveBenchmarkStep(size, config, isWarmUp) { - timerID = null; - - benchmark.startTimeInMs = null; - var totalSize = 0; - var totalReplied = 0; - - var checkResultAndContinue = function (bytesReceived, verificationResult) { - if (!verificationResult) { - config.addToLog('Response verification failed'); - destroyAllXHRs(); - config.notifyAbort(); - return; - } - - totalReplied += bytesReceived; - - if (totalReplied < totalSize) { - return; - } - - if (benchmark.startTimeInMs == null) { - config.addToLog('startTimeInMs not set'); - destroyAllXHRs(); - config.notifyAbort(); - return; - } - - calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize, - isWarmUp); - - destroyAllXHRs(); - - runNextTask(config); - } - - var onReadyStateChangeHandler = function () { - if (this.readyState != this.DONE) { - return; - } - - if (this.status != 200) { - config.addToLog('Failed (status=' + this.status + ')'); - destroyAllXHRs(); - config.notifyAbort(); - return; - } - - // Check and warn if proxy is enabled. - if (this.getResponseHeader('Via') !== null) { - config.addToLog('WARNING: proxy seems enabled.'); - } - - var bytesReceived = -1; - if (this.responseType == 'arraybuffer') { - bytesReceived = this.response.byteLength; - } else if (this.responseType == 'blob') { - bytesReceived = this.response.size; - } else { - bytesReceived = this.response.length; - } - if (bytesReceived != size) { - config.addToLog('Expected ' + size + - 'B but received ' + bytesReceived + 'B'); - destroyAllXHRs(); - config.notifyAbort(); - return; - } - - if (this.responseType == 'arraybuffer') { - checkResultAndContinue(bytesReceived, - !config.verifyData || verifyArrayBuffer(this.response, 0x61)); - } else if (this.responseType == 'blob') { - if (config.verifyData) - verifyBlob(config, this.response, 0x61, checkResultAndContinue); - else - checkResultAndContinue(bytesReceived, true); - } else { - checkResultAndContinue( - bytesReceived, - !config.verifyData || - this.response == repeatString('a', this.response.length)); - } - }; - - for (var i = 0; i < config.numXHRs; ++i) { - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = onReadyStateChangeHandler; - xhrs.push(xhr); - } - - benchmark.startTimeInMs = getTimeStamp(); - totalSize = size * xhrs.length; - - for (var i = 0; i < xhrs.length; ++i) { - var xhr = xhrs[i]; - if (config.methodAndCache === 'GET-NOCACHE') { - xhr.open('GET', config.prefixUrl + '_receive_getnocache?' + size, - config.async); - xhr.responseType = config.dataType; - xhr.send(); - } else if (config.methodAndCache === 'GET-CACHE') { - xhr.open('GET', config.prefixUrl + '_receive_getcache?' + size, - config.async); - xhr.responseType = config.dataType; - xhr.send(); - } else { - xhr.open('POST', config.prefixUrl + '_receive', config.async); - xhr.responseType = config.dataType; - xhr.send(size + ' none'); - } - } -} - - -function getConfigString(config) { - return '(' + config.dataType + - ', verifyData=' + config.verifyData + - ', ' + (isWorker ? 'Worker' : 'Main') + - ', ' + (config.async ? 'Async' : 'Sync') + - ', numXHRs=' + config.numXHRs + - ', numIterations=' + config.numIterations + - ', numWarmUpIterations=' + config.numWarmUpIterations + - ')'; -} - -function startBenchmark(config) { - clearTimeout(timerID); - destroyAllXHRs(); - - runNextTask(config); -} - -function batchBenchmark(originalConfig) { - originalConfig.addToLog('Batch benchmark'); - - tasks = []; - clearAverageData(); - - var dataTypes = ['text', 'blob', 'arraybuffer']; - var stepFuncs = [sendBenchmarkStep, receiveBenchmarkStep]; - var names = ['Send', 'Receive']; - var async = [true, false]; - for (var i = 0; i < stepFuncs.length; ++i) { - for (var j = 0; j < dataTypes.length; ++j) { - for (var k = 0; k < async.length; ++k) { - var config = cloneConfig(originalConfig); - config.dataType = dataTypes[j]; - config.async = async[k]; - - // Receive && Non-Worker && Sync is not supported by the spec - if (stepFuncs[i] === receiveBenchmarkStep && !isWorker && - !config.async) - continue; - - addTasks(config, stepFuncs[i]); - addResultReportingTask(config, - names[i] + ' benchmark ' + getConfigString(config)); - } - } - } - - startBenchmark(config); -} - -function cleanup() { -} diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_event_logger.html b/tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_event_logger.html deleted file mode 100644 index 50db0fbec49..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_event_logger.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - -XHR event logger - - - - - -
- Size:
- -
- -
- -
- -
- - - - diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_performance_test_iframe.html b/tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_performance_test_iframe.html deleted file mode 100644 index ab02c9ea56f..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/xhr_performance_test_iframe.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/_stream_base.py b/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/_stream_base.py deleted file mode 100644 index 21a1254ae6d..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/_stream_base.py +++ /dev/null @@ -1,181 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Base stream class. -""" - - -# Note: request.connection.write/read are used in this module, even though -# mod_python document says that they should be used only in connection -# handlers. Unfortunately, we have no other options. For example, -# request.write/read are not suitable because they don't allow direct raw bytes -# writing/reading. - - -import socket - -from mod_pywebsocket import util - - -# Exceptions - - -class ConnectionTerminatedException(Exception): - """This exception will be raised when a connection is terminated - unexpectedly. - """ - - pass - - -class InvalidFrameException(ConnectionTerminatedException): - """This exception will be raised when we received an invalid frame we - cannot parse. - """ - - pass - - -class BadOperationException(Exception): - """This exception will be raised when send_message() is called on - server-terminated connection or receive_message() is called on - client-terminated connection. - """ - - pass - - -class UnsupportedFrameException(Exception): - """This exception will be raised when we receive a frame with flag, opcode - we cannot handle. Handlers can just catch and ignore this exception and - call receive_message() again to continue processing the next frame. - """ - - pass - - -class InvalidUTF8Exception(Exception): - """This exception will be raised when we receive a text frame which - contains invalid UTF-8 strings. - """ - - pass - - -class StreamBase(object): - """Base stream class.""" - - def __init__(self, request): - """Construct an instance. - - Args: - request: mod_python request. - """ - - self._logger = util.get_class_logger(self) - - self._request = request - - def _read(self, length): - """Reads length bytes from connection. In case we catch any exception, - prepends remote address to the exception message and raise again. - - Raises: - ConnectionTerminatedException: when read returns empty string. - """ - - try: - read_bytes = self._request.connection.read(length) - if not read_bytes: - raise ConnectionTerminatedException( - 'Receiving %d byte failed. Peer (%r) closed connection' % - (length, (self._request.connection.remote_addr,))) - return read_bytes - except socket.error as e: - # Catch a socket.error. Because it's not a child class of the - # IOError prior to Python 2.6, we cannot omit this except clause. - # Use %s rather than %r for the exception to use human friendly - # format. - raise ConnectionTerminatedException( - 'Receiving %d byte failed. socket.error (%s) occurred' % - (length, e)) - except IOError as e: - # Also catch an IOError because mod_python throws it. - raise ConnectionTerminatedException( - 'Receiving %d byte failed. IOError (%s) occurred' % - (length, e)) - - def _write(self, bytes_to_write): - """Writes given bytes to connection. In case we catch any exception, - prepends remote address to the exception message and raise again. - """ - - try: - self._request.connection.write(bytes_to_write) - except Exception as e: - util.prepend_message_to_exception( - 'Failed to send message to %r: ' % - (self._request.connection.remote_addr,), - e) - raise - - def receive_bytes(self, length): - """Receives multiple bytes. Retries read when we couldn't receive the - specified amount. - - Raises: - ConnectionTerminatedException: when read returns empty string. - """ - - read_bytes = [] - while length > 0: - new_read_bytes = self._read(length) - read_bytes.append(new_read_bytes) - length -= len(new_read_bytes) - return ''.join(read_bytes) - - def _read_until(self, delim_char): - """Reads bytes until we encounter delim_char. The result will not - contain delim_char. - - Raises: - ConnectionTerminatedException: when read returns empty string. - """ - - read_bytes = [] - while True: - ch = self._read(1) - if ch == delim_char: - break - read_bytes.append(ch) - return ''.join(read_bytes) - - -# vi:sts=4 sw=4 et diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/_stream_hixie75.py b/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/_stream_hixie75.py deleted file mode 100644 index 94cf5b31ba0..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/_stream_hixie75.py +++ /dev/null @@ -1,229 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""This file provides a class for parsing/building frames of the WebSocket -protocol version HyBi 00 and Hixie 75. - -Specification: -- HyBi 00 http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 -- Hixie 75 http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 -""" - - -from mod_pywebsocket import common -from mod_pywebsocket._stream_base import BadOperationException -from mod_pywebsocket._stream_base import ConnectionTerminatedException -from mod_pywebsocket._stream_base import InvalidFrameException -from mod_pywebsocket._stream_base import StreamBase -from mod_pywebsocket._stream_base import UnsupportedFrameException -from mod_pywebsocket import util - - -class StreamHixie75(StreamBase): - """A class for parsing/building frames of the WebSocket protocol version - HyBi 00 and Hixie 75. - """ - - def __init__(self, request, enable_closing_handshake=False): - """Construct an instance. - - Args: - request: mod_python request. - enable_closing_handshake: to let StreamHixie75 perform closing - handshake as specified in HyBi 00, set - this option to True. - """ - - StreamBase.__init__(self, request) - - self._logger = util.get_class_logger(self) - - self._enable_closing_handshake = enable_closing_handshake - - self._request.client_terminated = False - self._request.server_terminated = False - - def send_message(self, message, end=True, binary=False): - """Send message. - - Args: - message: unicode string to send. - binary: not used in hixie75. - - Raises: - BadOperationException: when called on a server-terminated - connection. - """ - - if not end: - raise BadOperationException( - 'StreamHixie75 doesn\'t support send_message with end=False') - - if binary: - raise BadOperationException( - 'StreamHixie75 doesn\'t support send_message with binary=True') - - if self._request.server_terminated: - raise BadOperationException( - 'Requested send_message after sending out a closing handshake') - - self._write(''.join(['\x00', message.encode('utf-8'), '\xff'])) - - def _read_payload_length_hixie75(self): - """Reads a length header in a Hixie75 version frame with length. - - Raises: - ConnectionTerminatedException: when read returns empty string. - """ - - length = 0 - while True: - b_str = self._read(1) - b = ord(b_str) - length = length * 128 + (b & 0x7f) - if (b & 0x80) == 0: - break - return length - - def receive_message(self): - """Receive a WebSocket frame and return its payload an unicode string. - - Returns: - payload unicode string in a WebSocket frame. - - Raises: - ConnectionTerminatedException: when read returns empty - string. - BadOperationException: when called on a client-terminated - connection. - """ - - if self._request.client_terminated: - raise BadOperationException( - 'Requested receive_message after receiving a closing ' - 'handshake') - - while True: - # Read 1 byte. - # mp_conn.read will block if no bytes are available. - # Timeout is controlled by TimeOut directive of Apache. - frame_type_str = self.receive_bytes(1) - frame_type = ord(frame_type_str) - if (frame_type & 0x80) == 0x80: - # The payload length is specified in the frame. - # Read and discard. - length = self._read_payload_length_hixie75() - if length > 0: - _ = self.receive_bytes(length) - # 5.3 3. 12. if /type/ is 0xFF and /length/ is 0, then set the - # /client terminated/ flag and abort these steps. - if not self._enable_closing_handshake: - continue - - if frame_type == 0xFF and length == 0: - self._request.client_terminated = True - - if self._request.server_terminated: - self._logger.debug( - 'Received ack for server-initiated closing ' - 'handshake') - return None - - self._logger.debug( - 'Received client-initiated closing handshake') - - self._send_closing_handshake() - self._logger.debug( - 'Sent ack for client-initiated closing handshake') - return None - else: - # The payload is delimited with \xff. - bytes = self._read_until('\xff') - # The WebSocket protocol section 4.4 specifies that invalid - # characters must be replaced with U+fffd REPLACEMENT - # CHARACTER. - message = bytes.decode('utf-8', 'replace') - if frame_type == 0x00: - return message - # Discard data of other types. - - def _send_closing_handshake(self): - if not self._enable_closing_handshake: - raise BadOperationException( - 'Closing handshake is not supported in Hixie 75 protocol') - - self._request.server_terminated = True - - # 5.3 the server may decide to terminate the WebSocket connection by - # running through the following steps: - # 1. send a 0xFF byte and a 0x00 byte to the client to indicate the - # start of the closing handshake. - self._write('\xff\x00') - - def close_connection(self, unused_code='', unused_reason=''): - """Closes a WebSocket connection. - - Raises: - ConnectionTerminatedException: when closing handshake was - not successfull. - """ - - if self._request.server_terminated: - self._logger.debug( - 'Requested close_connection but server is already terminated') - return - - if not self._enable_closing_handshake: - self._request.server_terminated = True - self._logger.debug('Connection closed') - return - - self._send_closing_handshake() - self._logger.debug('Sent server-initiated closing handshake') - - # TODO(ukai): 2. wait until the /client terminated/ flag has been set, - # or until a server-defined timeout expires. - # - # For now, we expect receiving closing handshake right after sending - # out closing handshake, and if we couldn't receive non-handshake - # frame, we take it as ConnectionTerminatedException. - message = self.receive_message() - if message is not None: - raise ConnectionTerminatedException( - 'Didn\'t receive valid ack for closing handshake') - # TODO: 3. close the WebSocket connection. - # note: mod_python Connection (mp_conn) doesn't have close method. - - def send_ping(self, body): - raise BadOperationException( - 'StreamHixie75 doesn\'t support send_ping') - - -# vi:sts=4 sw=4 et diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/handshake/hybi00.py b/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/handshake/hybi00.py deleted file mode 100644 index 8757717a639..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/handshake/hybi00.py +++ /dev/null @@ -1,293 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""This file provides the opening handshake processor for the WebSocket -protocol version HyBi 00. - -Specification: -http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 -""" - - -# Note: request.connection.write/read are used in this module, even though -# mod_python document says that they should be used only in connection -# handlers. Unfortunately, we have no other options. For example, -# request.write/read are not suitable because they don't allow direct raw bytes -# writing/reading. - - -import logging -import re -import struct - -from mod_pywebsocket import common -from mod_pywebsocket.stream import StreamHixie75 -from mod_pywebsocket import util -from mod_pywebsocket.handshake._base import HandshakeException -from mod_pywebsocket.handshake._base import check_request_line -from mod_pywebsocket.handshake._base import format_header -from mod_pywebsocket.handshake._base import get_default_port -from mod_pywebsocket.handshake._base import get_mandatory_header -from mod_pywebsocket.handshake._base import parse_host_header -from mod_pywebsocket.handshake._base import validate_mandatory_header - - -_MANDATORY_HEADERS = [ - # key, expected value or None - [common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75], - [common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE], -] - - -def _validate_subprotocol(subprotocol): - """Checks if characters in subprotocol are in range between U+0020 and - U+007E. A value in the Sec-WebSocket-Protocol field need to satisfy this - requirement. - - See the Section 4.1. Opening handshake of the spec. - """ - - if not subprotocol: - raise HandshakeException('Invalid subprotocol name: empty') - - # Parameter should be in the range U+0020 to U+007E. - for c in subprotocol: - if not 0x20 <= ord(c) <= 0x7e: - raise HandshakeException( - 'Illegal character in subprotocol name: %r' % c) - - -def _check_header_lines(request, mandatory_headers): - check_request_line(request) - - # The expected field names, and the meaning of their corresponding - # values, are as follows. - # |Upgrade| and |Connection| - for key, expected_value in mandatory_headers: - validate_mandatory_header(request, key, expected_value) - - -def _build_location(request): - """Build WebSocket location for request.""" - - location_parts = [] - if request.is_https(): - location_parts.append(common.WEB_SOCKET_SECURE_SCHEME) - else: - location_parts.append(common.WEB_SOCKET_SCHEME) - location_parts.append('://') - host, port = parse_host_header(request) - connection_port = request.connection.local_addr[1] - if port != connection_port: - raise HandshakeException('Header/connection port mismatch: %d/%d' % - (port, connection_port)) - location_parts.append(host) - if (port != get_default_port(request.is_https())): - location_parts.append(':') - location_parts.append(str(port)) - location_parts.append(request.unparsed_uri) - return ''.join(location_parts) - - -class Handshaker(object): - """Opening handshake processor for the WebSocket protocol version HyBi 00. - """ - - def __init__(self, request, dispatcher): - """Construct an instance. - - Args: - request: mod_python request. - dispatcher: Dispatcher (dispatch.Dispatcher). - - Handshaker will add attributes such as ws_resource in performing - handshake. - """ - - self._logger = util.get_class_logger(self) - - self._request = request - self._dispatcher = dispatcher - - def do_handshake(self): - """Perform WebSocket Handshake. - - On _request, we set - ws_resource, ws_protocol, ws_location, ws_origin, ws_challenge, - ws_challenge_md5: WebSocket handshake information. - ws_stream: Frame generation/parsing class. - ws_version: Protocol version. - - Raises: - HandshakeException: when any error happened in parsing the opening - handshake request. - """ - - # 5.1 Reading the client's opening handshake. - # dispatcher sets it in self._request. - _check_header_lines(self._request, _MANDATORY_HEADERS) - self._set_resource() - self._set_subprotocol() - self._set_location() - self._set_origin() - self._set_challenge_response() - self._set_protocol_version() - - self._dispatcher.do_extra_handshake(self._request) - - self._send_handshake() - - def _set_resource(self): - self._request.ws_resource = self._request.uri - - def _set_subprotocol(self): - # |Sec-WebSocket-Protocol| - subprotocol = self._request.headers_in.get( - common.SEC_WEBSOCKET_PROTOCOL_HEADER) - if subprotocol is not None: - _validate_subprotocol(subprotocol) - self._request.ws_protocol = subprotocol - - def _set_location(self): - # |Host| - host = self._request.headers_in.get(common.HOST_HEADER) - if host is not None: - self._request.ws_location = _build_location(self._request) - # TODO(ukai): check host is this host. - - def _set_origin(self): - # |Origin| - origin = self._request.headers_in.get(common.ORIGIN_HEADER) - if origin is not None: - self._request.ws_origin = origin - - def _set_protocol_version(self): - # |Sec-WebSocket-Draft| - draft = self._request.headers_in.get(common.SEC_WEBSOCKET_DRAFT_HEADER) - if draft is not None and draft != '0': - raise HandshakeException('Illegal value for %s: %s' % - (common.SEC_WEBSOCKET_DRAFT_HEADER, - draft)) - - self._logger.debug('Protocol version is HyBi 00') - self._request.ws_version = common.VERSION_HYBI00 - self._request.ws_stream = StreamHixie75(self._request, True) - - def _set_challenge_response(self): - # 5.2 4-8. - self._request.ws_challenge = self._get_challenge() - # 5.2 9. let /response/ be the MD5 finterprint of /challenge/ - self._request.ws_challenge_md5 = util.md5_hash( - self._request.ws_challenge).digest() - self._logger.debug( - 'Challenge: %r (%s)', - self._request.ws_challenge, - util.hexify(self._request.ws_challenge)) - self._logger.debug( - 'Challenge response: %r (%s)', - self._request.ws_challenge_md5, - util.hexify(self._request.ws_challenge_md5)) - - def _get_key_value(self, key_field): - key_value = get_mandatory_header(self._request, key_field) - - self._logger.debug('%s: %r', key_field, key_value) - - # 5.2 4. let /key-number_n/ be the digits (characters in the range - # U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_n/, - # interpreted as a base ten integer, ignoring all other characters - # in /key_n/. - try: - key_number = int(re.sub("\\D", "", key_value)) - except: - raise HandshakeException('%s field contains no digit' % key_field) - # 5.2 5. let /spaces_n/ be the number of U+0020 SPACE characters - # in /key_n/. - spaces = re.subn(" ", "", key_value)[1] - if spaces == 0: - raise HandshakeException('%s field contains no space' % key_field) - - self._logger.debug( - '%s: Key-number is %d and number of spaces is %d', - key_field, key_number, spaces) - - # 5.2 6. if /key-number_n/ is not an integral multiple of /spaces_n/ - # then abort the WebSocket connection. - if key_number % spaces != 0: - raise HandshakeException( - '%s: Key-number (%d) is not an integral multiple of spaces ' - '(%d)' % (key_field, key_number, spaces)) - # 5.2 7. let /part_n/ be /key-number_n/ divided by /spaces_n/. - part = key_number / spaces - self._logger.debug('%s: Part is %d', key_field, part) - return part - - def _get_challenge(self): - # 5.2 4-7. - key1 = self._get_key_value(common.SEC_WEBSOCKET_KEY1_HEADER) - key2 = self._get_key_value(common.SEC_WEBSOCKET_KEY2_HEADER) - # 5.2 8. let /challenge/ be the concatenation of /part_1/, - challenge = '' - challenge += struct.pack('!I', key1) # network byteorder int - challenge += struct.pack('!I', key2) # network byteorder int - challenge += self._request.connection.read(8) - return challenge - - def _send_handshake(self): - response = [] - - # 5.2 10. send the following line. - response.append('HTTP/1.1 101 WebSocket Protocol Handshake\r\n') - - # 5.2 11. send the following fields to the client. - response.append(format_header( - common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75)) - response.append(format_header( - common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE)) - response.append(format_header( - common.SEC_WEBSOCKET_LOCATION_HEADER, self._request.ws_location)) - response.append(format_header( - common.SEC_WEBSOCKET_ORIGIN_HEADER, self._request.ws_origin)) - if self._request.ws_protocol: - response.append(format_header( - common.SEC_WEBSOCKET_PROTOCOL_HEADER, - self._request.ws_protocol)) - # 5.2 12. send two bytes 0x0D 0x0A. - response.append('\r\n') - # 5.2 13. send /response/ - response.append(self._request.ws_challenge_md5) - - raw_response = ''.join(response) - self._request.connection.write(raw_response) - self._logger.debug('Sent server\'s opening handshake: %r', - raw_response) - - -# vi:sts=4 sw=4 et diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/headerparserhandler.py b/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/headerparserhandler.py deleted file mode 100644 index 06e76385979..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/headerparserhandler.py +++ /dev/null @@ -1,257 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""PythonHeaderParserHandler for mod_pywebsocket. - -Apache HTTP Server and mod_python must be configured such that this -function is called to handle WebSocket request. -""" - - -import logging - -from mod_python import apache - -from mod_pywebsocket import common -from mod_pywebsocket import dispatch -from mod_pywebsocket import handshake -from mod_pywebsocket import util - - -# PythonOption to specify the handler root directory. -_PYOPT_HANDLER_ROOT = 'mod_pywebsocket.handler_root' - -# PythonOption to specify the handler scan directory. -# This must be a directory under the root directory. -# The default is the root directory. -_PYOPT_HANDLER_SCAN = 'mod_pywebsocket.handler_scan' - -# PythonOption to allow handlers whose canonical path is -# not under the root directory. It's disallowed by default. -# Set this option with value of 'yes' to allow. -_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT = ( - 'mod_pywebsocket.allow_handlers_outside_root_dir') -# Map from values to their meanings. 'Yes' and 'No' are allowed just for -# compatibility. -_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION = { - 'off': False, 'no': False, 'on': True, 'yes': True} - -# (Obsolete option. Ignored.) -# PythonOption to specify to allow handshake defined in Hixie 75 version -# protocol. The default is None (Off) -_PYOPT_ALLOW_DRAFT75 = 'mod_pywebsocket.allow_draft75' -# Map from values to their meanings. -_PYOPT_ALLOW_DRAFT75_DEFINITION = {'off': False, 'on': True} - - -class ApacheLogHandler(logging.Handler): - - """Wrapper logging.Handler to emit log message to apache's error.log.""" - - _LEVELS = { - logging.DEBUG: apache.APLOG_DEBUG, - logging.INFO: apache.APLOG_INFO, - logging.WARNING: apache.APLOG_WARNING, - logging.ERROR: apache.APLOG_ERR, - logging.CRITICAL: apache.APLOG_CRIT, - } - - def __init__(self, request=None): - logging.Handler.__init__(self) - self._log_error = apache.log_error - if request is not None: - self._log_error = request.log_error - - # Time and level will be printed by Apache. - self._formatter = logging.Formatter('%(name)s: %(message)s') - - def emit(self, record): - apache_level = apache.APLOG_DEBUG - if record.levelno in ApacheLogHandler._LEVELS: - apache_level = ApacheLogHandler._LEVELS[record.levelno] - - msg = self._formatter.format(record) - - # "server" parameter must be passed to have "level" parameter work. - # If only "level" parameter is passed, nothing shows up on Apache's - # log. However, at this point, we cannot get the server object of the - # virtual host which will process WebSocket requests. The only server - # object we can get here is apache.main_server. But Wherever (server - # configuration context or virtual host context) we put - # PythonHeaderParserHandler directive, apache.main_server just points - # the main server instance (not any of virtual server instance). Then, - # Apache follows LogLevel directive in the server configuration context - # to filter logs. So, we need to specify LogLevel in the server - # configuration context. Even if we specify "LogLevel debug" in the - # virtual host context which actually handles WebSocket connections, - # DEBUG level logs never show up unless "LogLevel debug" is specified - # in the server configuration context. - # - # TODO(tyoshino): Provide logging methods on request object. When - # request is mp_request object (when used together with Apache), the - # methods call request.log_error indirectly. When request is - # _StandaloneRequest, the methods call Python's logging facility which - # we create in standalone.py. - self._log_error(msg, apache_level, apache.main_server) - - -def _configure_logging(): - logger = logging.getLogger() - # Logs are filtered by Apache based on LogLevel directive in Apache - # configuration file. We must just pass logs for all levels to - # ApacheLogHandler. - logger.setLevel(logging.DEBUG) - logger.addHandler(ApacheLogHandler()) - - -_configure_logging() - -_LOGGER = logging.getLogger(__name__) - - -def _parse_option(name, value, definition): - """Return the meaning of a option value.""" - if value is None: - return False - - meaning = definition.get(value.lower()) - if meaning is None: - raise Exception('Invalid value for PythonOption %s: %r' % - (name, value)) - return meaning - - -def _create_dispatcher(): - """Initialize a dispatch.Dispatcher.""" - _LOGGER.info('Initializing Dispatcher') - - options = apache.main_server.get_options() - - handler_root = options.get(_PYOPT_HANDLER_ROOT, None) - if not handler_root: - raise Exception('PythonOption %s is not defined' % _PYOPT_HANDLER_ROOT, - apache.APLOG_ERR) - - handler_scan = options.get(_PYOPT_HANDLER_SCAN, handler_root) - - allow_handlers_outside_root = _parse_option( - _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT, - options.get(_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT), - _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION) - - dispatcher = dispatch.Dispatcher( - handler_root, handler_scan, allow_handlers_outside_root) - - for warning in dispatcher.source_warnings(): - apache.log_error( - 'mod_pywebsocket: Warning in source loading: %s' % warning, - apache.APLOG_WARNING) - - return dispatcher - - -# Initialize -_dispatcher = _create_dispatcher() - - -def headerparserhandler(request): - """Handle request. - - Args: - request: mod_python request. - - This function is named headerparserhandler because it is the default - name for a PythonHeaderParserHandler. - """ - handshake_is_done = False - try: - # Fallback to default http handler for request paths for which - # we don't have request handlers. - if not _dispatcher.get_handler_suite(request.uri): - request.log_error( - 'mod_pywebsocket: No handler for resource: %r' % request.uri, - apache.APLOG_INFO) - request.log_error( - 'mod_pywebsocket: Fallback to Apache', apache.APLOG_INFO) - return apache.DECLINED - except dispatch.DispatchException as e: - request.log_error( - 'mod_pywebsocket: Dispatch failed for error: %s' % e, - apache.APLOG_INFO) - if not handshake_is_done: - return e.status - - try: - allow_draft75 = _parse_option( - _PYOPT_ALLOW_DRAFT75, - apache.main_server.get_options().get(_PYOPT_ALLOW_DRAFT75), - _PYOPT_ALLOW_DRAFT75_DEFINITION) - - try: - handshake.do_handshake( - request, _dispatcher, allowDraft75=allow_draft75) - except handshake.VersionException as e: - request.log_error( - 'mod_pywebsocket: Handshake failed for version error: %s' % e, - apache.APLOG_INFO) - request.err_headers_out.add(common.SEC_WEBSOCKET_VERSION_HEADER, - e.supported_versions) - return apache.HTTP_BAD_REQUEST - except handshake.HandshakeException as e: - # Handshake for ws/wss failed. - # Send http response with error status. - request.log_error( - 'mod_pywebsocket: Handshake failed for error: %s' % e, - apache.APLOG_INFO) - return e.status - - handshake_is_done = True - request._dispatcher = _dispatcher - _dispatcher.transfer_data(request) - except handshake.AbortedByUserException as e: - request.log_error('mod_pywebsocket: Aborted: %s' % e, - apache.APLOG_INFO) - except Exception as e: - # DispatchException can also be thrown if something is wrong in - # pywebsocket code. It's caught here, then. - - request.log_error('mod_pywebsocket: Exception occurred: %s\n%s' % - (e, util.get_stack_trace()), - apache.APLOG_ERR) - # Unknown exceptions before handshake mean Apache must handle its - # request with another handler. - if not handshake_is_done: - return apache.DECLINED - # Set assbackwards to suppress response header generation by Apache. - request.assbackwards = 1 - return apache.DONE # Return DONE such that no other handlers are invoked. - - -# vi:sts=4 sw=4 et diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/mux.py b/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/mux.py deleted file mode 100644 index 994f8a14645..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/mux.py +++ /dev/null @@ -1,1885 +0,0 @@ -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""This file provides classes and helper functions for multiplexing extension. - -Specification: -http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-06 -""" - - -import collections -import copy -import email -import email.parser -import logging -import math -import struct -import threading -import traceback - -from mod_pywebsocket import common -from mod_pywebsocket import handshake -from mod_pywebsocket import util -from mod_pywebsocket._stream_base import BadOperationException -from mod_pywebsocket._stream_base import ConnectionTerminatedException -from mod_pywebsocket._stream_base import InvalidFrameException -from mod_pywebsocket._stream_hybi import Frame -from mod_pywebsocket._stream_hybi import Stream -from mod_pywebsocket._stream_hybi import StreamOptions -from mod_pywebsocket._stream_hybi import create_binary_frame -from mod_pywebsocket._stream_hybi import create_closing_handshake_body -from mod_pywebsocket._stream_hybi import create_header -from mod_pywebsocket._stream_hybi import create_length_header -from mod_pywebsocket._stream_hybi import parse_frame -from mod_pywebsocket.handshake import hybi - - -_CONTROL_CHANNEL_ID = 0 -_DEFAULT_CHANNEL_ID = 1 - -_MUX_OPCODE_ADD_CHANNEL_REQUEST = 0 -_MUX_OPCODE_ADD_CHANNEL_RESPONSE = 1 -_MUX_OPCODE_FLOW_CONTROL = 2 -_MUX_OPCODE_DROP_CHANNEL = 3 -_MUX_OPCODE_NEW_CHANNEL_SLOT = 4 - -_MAX_CHANNEL_ID = 2 ** 29 - 1 - -_INITIAL_NUMBER_OF_CHANNEL_SLOTS = 64 -_INITIAL_QUOTA_FOR_CLIENT = 8 * 1024 - -_HANDSHAKE_ENCODING_IDENTITY = 0 -_HANDSHAKE_ENCODING_DELTA = 1 - -# We need only these status code for now. -_HTTP_BAD_RESPONSE_MESSAGES = { - common.HTTP_STATUS_BAD_REQUEST: 'Bad Request', -} - -# DropChannel reason code -# TODO(bashi): Define all reason code defined in -05 draft. -_DROP_CODE_NORMAL_CLOSURE = 1000 - -_DROP_CODE_INVALID_ENCAPSULATING_MESSAGE = 2001 -_DROP_CODE_CHANNEL_ID_TRUNCATED = 2002 -_DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED = 2003 -_DROP_CODE_UNKNOWN_MUX_OPCODE = 2004 -_DROP_CODE_INVALID_MUX_CONTROL_BLOCK = 2005 -_DROP_CODE_CHANNEL_ALREADY_EXISTS = 2006 -_DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION = 2007 -_DROP_CODE_UNKNOWN_REQUEST_ENCODING = 2010 - -_DROP_CODE_SEND_QUOTA_VIOLATION = 3005 -_DROP_CODE_SEND_QUOTA_OVERFLOW = 3006 -_DROP_CODE_ACKNOWLEDGED = 3008 -_DROP_CODE_BAD_FRAGMENTATION = 3009 - - -class MuxUnexpectedException(Exception): - - """Exception in handling multiplexing extension.""" - - pass - - -# Temporary -class MuxNotImplementedException(Exception): - - """Raised when a flow enters unimplemented code path.""" - - pass - - -class LogicalConnectionClosedException(Exception): - - """Raised when logical connection is gracefully closed.""" - - pass - - -class PhysicalConnectionError(Exception): - - """Raised when there is a physical connection error.""" - - def __init__(self, drop_code, message=''): - super(PhysicalConnectionError, self).__init__( - 'code=%d, message=%r' % (drop_code, message)) - self.drop_code = drop_code - self.message = message - - -class LogicalChannelError(Exception): - - """Raised when there is a logical channel error.""" - - def __init__(self, channel_id, drop_code, message=''): - """Initialize the error with a status message.""" - super(LogicalChannelError, self).__init__( - 'channel_id=%d, code=%d, message=%r' % ( - channel_id, drop_code, message)) - self.channel_id = channel_id - self.drop_code = drop_code - self.message = message - - -def _encode_channel_id(channel_id): - if channel_id < 0: - raise ValueError('Channel id %d must not be negative' % channel_id) - - if channel_id < 2 ** 7: - return chr(channel_id) - if channel_id < 2 ** 14: - return struct.pack('!H', 0x8000 + channel_id) - if channel_id < 2 ** 21: - first = chr(0xc0 + (channel_id >> 16)) - return first + struct.pack('!H', channel_id & 0xffff) - if channel_id < 2 ** 29: - return struct.pack('!L', 0xe0000000 + channel_id) - - raise ValueError('Channel id %d is too large' % channel_id) - - -def _encode_number(number): - return create_length_header(number, False) - - -def _create_add_channel_response(channel_id, encoded_handshake, - encoding=0, rejected=False): - if encoding != 0 and encoding != 1: - raise ValueError('Invalid encoding %d' % encoding) - - first_byte = ((_MUX_OPCODE_ADD_CHANNEL_RESPONSE << 5) | - (rejected << 4) | encoding) - block = (chr(first_byte) + - _encode_channel_id(channel_id) + - _encode_number(len(encoded_handshake)) + - encoded_handshake) - return block - - -def _create_drop_channel(channel_id, code=None, message=''): - if len(message) > 0 and code is None: - raise ValueError('Code must be specified if message is specified') - - first_byte = _MUX_OPCODE_DROP_CHANNEL << 5 - block = chr(first_byte) + _encode_channel_id(channel_id) - if code is None: - block += _encode_number(0) # Reason size - else: - reason = struct.pack('!H', code) + message - reason_size = _encode_number(len(reason)) - block += reason_size + reason - - return block - - -def _create_flow_control(channel_id, replenished_quota): - first_byte = _MUX_OPCODE_FLOW_CONTROL << 5 - block = (chr(first_byte) + - _encode_channel_id(channel_id) + - _encode_number(replenished_quota)) - return block - - -def _create_new_channel_slot(slots, send_quota): - if slots < 0 or send_quota < 0: - raise ValueError('slots and send_quota must be non-negative.') - first_byte = _MUX_OPCODE_NEW_CHANNEL_SLOT << 5 - block = (chr(first_byte) + - _encode_number(slots) + - _encode_number(send_quota)) - return block - - -def _create_fallback_new_channel_slot(): - first_byte = (_MUX_OPCODE_NEW_CHANNEL_SLOT << 5) | 1 # Set the F flag - block = (chr(first_byte) + _encode_number(0) + _encode_number(0)) - return block - - -def _parse_request_text(request_text): - request_line, header_lines = request_text.split('\r\n', 1) - - words = request_line.split(' ') - if len(words) != 3: - raise ValueError('Bad Request-Line syntax %r' % request_line) - [command, path, version] = words - if version != 'HTTP/1.1': - raise ValueError('Bad request version %r' % version) - - # email.parser.Parser() parses RFC 2822 (RFC 822) style headers. - # RFC 6455 refers RFC 2616 for handshake parsing, and RFC 2616 refers - # RFC 822. - headers = email.parser.Parser().parsestr(header_lines) - return command, path, version, headers - - -class _ControlBlock(object): - - """A structure that holds parsing result of multiplexing control block. - - Control block specific attributes will be added by _MuxFramePayloadParser. - (e.g. encoded_handshake will be added for AddChannelRequest and - AddChannelResponse) - """ - - def __init__(self, opcode): - self.opcode = opcode - - -class _MuxFramePayloadParser(object): - - """A class that parses multiplexed frame payload.""" - - def __init__(self, payload): - self._data = payload - self._read_position = 0 - self._logger = util.get_class_logger(self) - - def read_channel_id(self): - """Read channel id. - - Raises: - ValueError: when the payload doesn't contain - valid channel id. - """ - remaining_length = len(self._data) - self._read_position - pos = self._read_position - if remaining_length == 0: - raise ValueError('Invalid channel id format') - - channel_id = ord(self._data[pos]) - channel_id_length = 1 - if channel_id & 0xe0 == 0xe0: - if remaining_length < 4: - raise ValueError('Invalid channel id format') - channel_id = struct.unpack('!L', - self._data[pos:pos+4])[0] & 0x1fffffff - channel_id_length = 4 - elif channel_id & 0xc0 == 0xc0: - if remaining_length < 3: - raise ValueError('Invalid channel id format') - channel_id = (((channel_id & 0x1f) << 16) + - struct.unpack('!H', self._data[pos+1:pos+3])[0]) - channel_id_length = 3 - elif channel_id & 0x80 == 0x80: - if remaining_length < 2: - raise ValueError('Invalid channel id format') - channel_id = struct.unpack('!H', - self._data[pos:pos+2])[0] & 0x3fff - channel_id_length = 2 - self._read_position += channel_id_length - - return channel_id - - def read_inner_frame(self): - """Read an inner frame. - - Raises: - PhysicalConnectionError: when the inner frame is invalid. - """ - if len(self._data) == self._read_position: - raise PhysicalConnectionError( - _DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED) - - bits = ord(self._data[self._read_position]) - self._read_position += 1 - fin = (bits & 0x80) == 0x80 - rsv1 = (bits & 0x40) == 0x40 - rsv2 = (bits & 0x20) == 0x20 - rsv3 = (bits & 0x10) == 0x10 - opcode = bits & 0xf - payload = self.remaining_data() - # Consume rest of the message which is payload data of the original - # frame. - self._read_position = len(self._data) - return fin, rsv1, rsv2, rsv3, opcode, payload - - def _read_number(self): - if self._read_position + 1 > len(self._data): - raise ValueError( - 'Cannot read the first byte of number field') - - number = ord(self._data[self._read_position]) - if number & 0x80 == 0x80: - raise ValueError( - 'The most significant bit of the first byte of number should ' - 'be unset') - self._read_position += 1 - pos = self._read_position - if number == 127: - if pos + 8 > len(self._data): - raise ValueError('Invalid number field') - self._read_position += 8 - number = struct.unpack('!Q', self._data[pos:pos+8])[0] - if number > 0x7FFFFFFFFFFFFFFF: - raise ValueError('Encoded number(%d) >= 2^63' % number) - if number <= 0xFFFF: - raise ValueError( - '%d should not be encoded by 9 bytes encoding' % number) - return number - if number == 126: - if pos + 2 > len(self._data): - raise ValueError('Invalid number field') - self._read_position += 2 - number = struct.unpack('!H', self._data[pos:pos+2])[0] - if number <= 125: - raise ValueError( - '%d should not be encoded by 3 bytes encoding' % number) - return number - - def _read_size_and_contents(self): - """Read data that consists of the following: - - the size of the contents encoded the same way as payload length - of the WebSocket Protocol with 1 bit padding at the head. - - the contents. - """ - try: - size = self._read_number() - except ValueError as e: - raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - str(e)) - pos = self._read_position - if pos + size > len(self._data): - raise PhysicalConnectionError( - _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - 'Cannot read %d bytes data' % size) - - self._read_position += size - return self._data[pos:pos+size] - - def _read_add_channel_request(self, first_byte, control_block): - reserved = (first_byte >> 2) & 0x7 - if reserved != 0: - raise PhysicalConnectionError( - _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - 'Reserved bits must be unset') - - # Invalid encoding will be handled by MuxHandler. - encoding = first_byte & 0x3 - try: - control_block.channel_id = self.read_channel_id() - except ValueError as e: - raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK) - control_block.encoding = encoding - encoded_handshake = self._read_size_and_contents() - control_block.encoded_handshake = encoded_handshake - return control_block - - def _read_add_channel_response(self, first_byte, control_block): - reserved = (first_byte >> 2) & 0x3 - if reserved != 0: - raise PhysicalConnectionError( - _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - 'Reserved bits must be unset') - - control_block.accepted = (first_byte >> 4) & 1 - control_block.encoding = first_byte & 0x3 - try: - control_block.channel_id = self.read_channel_id() - except ValueError as e: - raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK) - control_block.encoded_handshake = self._read_size_and_contents() - return control_block - - def _read_flow_control(self, first_byte, control_block): - reserved = first_byte & 0x1f - if reserved != 0: - raise PhysicalConnectionError( - _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - 'Reserved bits must be unset') - - try: - control_block.channel_id = self.read_channel_id() - control_block.send_quota = self._read_number() - except ValueError as e: - raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - str(e)) - - return control_block - - def _read_drop_channel(self, first_byte, control_block): - reserved = first_byte & 0x1f - if reserved != 0: - raise PhysicalConnectionError( - _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - 'Reserved bits must be unset') - - try: - control_block.channel_id = self.read_channel_id() - except ValueError as e: - raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK) - reason = self._read_size_and_contents() - if len(reason) == 0: - control_block.drop_code = None - control_block.drop_message = '' - elif len(reason) >= 2: - control_block.drop_code = struct.unpack('!H', reason[:2])[0] - control_block.drop_message = reason[2:] - else: - raise PhysicalConnectionError( - _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - 'Received DropChannel that conains only 1-byte reason') - return control_block - - def _read_new_channel_slot(self, first_byte, control_block): - reserved = first_byte & 0x1e - if reserved != 0: - raise PhysicalConnectionError( - _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - 'Reserved bits must be unset') - control_block.fallback = first_byte & 1 - try: - control_block.slots = self._read_number() - control_block.send_quota = self._read_number() - except ValueError as e: - raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - str(e)) - return control_block - - def read_control_blocks(self): - """Read control block(s). - - Raises: - PhysicalConnectionError: when the payload contains invalid control - block(s). - StopIteration: when no control blocks left. - """ - while self._read_position < len(self._data): - first_byte = ord(self._data[self._read_position]) - self._read_position += 1 - opcode = (first_byte >> 5) & 0x7 - control_block = _ControlBlock(opcode=opcode) - if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST: - yield self._read_add_channel_request(first_byte, control_block) - elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE: - yield self._read_add_channel_response( - first_byte, control_block) - elif opcode == _MUX_OPCODE_FLOW_CONTROL: - yield self._read_flow_control(first_byte, control_block) - elif opcode == _MUX_OPCODE_DROP_CHANNEL: - yield self._read_drop_channel(first_byte, control_block) - elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT: - yield self._read_new_channel_slot(first_byte, control_block) - else: - raise PhysicalConnectionError( - _DROP_CODE_UNKNOWN_MUX_OPCODE, - 'Invalid opcode %d' % opcode) - - assert self._read_position == len(self._data) - raise StopIteration - - def remaining_data(self): - """Return remaining data.""" - return self._data[self._read_position:] - - -class _LogicalRequest(object): - - """Mimics mod_python request.""" - - def __init__(self, channel_id, command, path, protocol, headers, - connection): - """Construct an instance. - - Args: - channel_id: the channel id of the logical channel. - command: HTTP request command. - path: HTTP request path. - headers: HTTP headers. - connection: _LogicalConnection instance. - """ - self.channel_id = channel_id - self.method = command - self.uri = path - self.protocol = protocol - self.headers_in = headers - self.connection = connection - self.server_terminated = False - self.client_terminated = False - - def is_https(self): - """Mimic request.is_https(). - - Returns False because this method is used only by old protocols - (hixie and hybi00). - """ - return False - - -class _LogicalConnection(object): - - """Mimics mod_python mp_conn.""" - - # For details, see the comment of set_read_state(). - STATE_ACTIVE = 1 - STATE_GRACEFULLY_CLOSED = 2 - STATE_TERMINATED = 3 - - def __init__(self, mux_handler, channel_id): - """Construct an instance. - - Args: - mux_handler: _MuxHandler instance. - channel_id: channel id of this connection. - """ - self._mux_handler = mux_handler - self._channel_id = channel_id - self._incoming_data = '' - - # - Protects _waiting_write_completion - # - Signals the thread waiting for completion of write by mux handler - self._write_condition = threading.Condition() - self._waiting_write_completion = False - - self._read_condition = threading.Condition() - self._read_state = self.STATE_ACTIVE - - def get_local_addr(self): - """Getter to mimic mp_conn.local_addr.""" - return self._mux_handler.physical_connection.get_local_addr() - local_addr = property(get_local_addr) - - def get_remote_addr(self): - """Getter to mimic mp_conn.remote_addr.""" - return self._mux_handler.physical_connection.get_remote_addr() - remote_addr = property(get_remote_addr) - - def get_memorized_lines(self): - """Get memorized lines. Not supported.""" - raise MuxUnexpectedException('_LogicalConnection does not support ' - 'get_memorized_lines') - - def write(self, data): - """Write data. mux_handler sends data asynchronously. - - The caller will be suspended until write done. - - Args: - data: data to be written. - - Raises: - MuxUnexpectedException: when called before finishing the previous - write. - """ - try: - self._write_condition.acquire() - if self._waiting_write_completion: - raise MuxUnexpectedException( - 'Logical connection %d is already waiting the completion ' - 'of write' % self._channel_id) - - self._waiting_write_completion = True - self._mux_handler.send_data(self._channel_id, data) - self._write_condition.wait() - # TODO(tyoshino): Raise an exception if woke up by on_writer_done. - finally: - self._write_condition.release() - - def write_control_data(self, data): - """Write data via the control channel. - - Don't wait finishing write because this method can be called by - mux dispatcher. - - Args: - data: data to be written. - """ - self._mux_handler.send_control_data(data) - - def on_write_data_done(self): - """Called when sending data is completed.""" - try: - self._write_condition.acquire() - if not self._waiting_write_completion: - raise MuxUnexpectedException( - 'Invalid call of on_write_data_done for logical ' - 'connection %d' % self._channel_id) - self._waiting_write_completion = False - self._write_condition.notify() - finally: - self._write_condition.release() - - def on_writer_done(self): - """Called by the mux handler when the writer thread has finished.""" - try: - self._write_condition.acquire() - self._waiting_write_completion = False - self._write_condition.notify() - finally: - self._write_condition.release() - - def append_frame_data(self, frame_data): - """Append incoming frame data. - - Called when mux_handler dispatches frame data to the corresponding - application. - - Args: - frame_data: incoming frame data. - """ - self._read_condition.acquire() - self._incoming_data += frame_data - self._read_condition.notify() - self._read_condition.release() - - def read(self, length): - """Read data. - - Blocks until enough data has arrived via physical connection. - - Args: - length: length of data to be read. - Raises: - LogicalConnectionClosedException: when closing handshake for this - logical channel has been received. - ConnectionTerminatedException: when the physical connection has - closed, or an error is caused on the reader thread. - """ - self._read_condition.acquire() - while (self._read_state == self.STATE_ACTIVE and - len(self._incoming_data) < length): - self._read_condition.wait() - - try: - if self._read_state == self.STATE_GRACEFULLY_CLOSED: - raise LogicalConnectionClosedException( - 'Logical channel %d has closed.' % self._channel_id) - elif self._read_state == self.STATE_TERMINATED: - raise ConnectionTerminatedException( - 'Receiving %d byte failed. Logical channel (%d) closed' % - (length, self._channel_id)) - - value = self._incoming_data[:length] - self._incoming_data = self._incoming_data[length:] - finally: - self._read_condition.release() - - return value - - def set_read_state(self, new_state): - """Set the state of this connection. - - Called when an event for this connection has occurred. - Args: - new_state: state to be set. new_state must be one of followings: - - STATE_GRACEFULLY_CLOSED: when closing handshake for this - connection has been received. - - STATE_TERMINATED: when the physical connection has closed or - DropChannel of this connection has received. - """ - self._read_condition.acquire() - self._read_state = new_state - self._read_condition.notify() - self._read_condition.release() - - -class _InnerMessage(object): - - """Hold the result of _InnerMessageBuilder.build().""" - - def __init__(self, opcode, payload): - self.opcode = opcode - self.payload = payload - - -class _InnerMessageBuilder(object): - - """Class to build an _InnerMessage. - - A class that holds the context of inner message fragmentation and - builds a message from fragmented inner frame(s). - """ - - def __init__(self): - self._control_opcode = None - self._pending_control_fragments = [] - self._message_opcode = None - self._pending_message_fragments = [] - self._frame_handler = self._handle_first - - def _handle_first(self, frame): - if frame.opcode == common.OPCODE_CONTINUATION: - raise InvalidFrameException('Sending invalid continuation opcode') - - if common.is_control_opcode(frame.opcode): - return self._process_first_fragmented_control(frame) - else: - return self._process_first_fragmented_message(frame) - - def _process_first_fragmented_control(self, frame): - self._control_opcode = frame.opcode - self._pending_control_fragments.append(frame.payload) - if not frame.fin: - self._frame_handler = self._handle_fragmented_control - return None - return self._reassemble_fragmented_control() - - def _process_first_fragmented_message(self, frame): - self._message_opcode = frame.opcode - self._pending_message_fragments.append(frame.payload) - if not frame.fin: - self._frame_handler = self._handle_fragmented_message - return None - return self._reassemble_fragmented_message() - - def _handle_fragmented_control(self, frame): - if frame.opcode != common.OPCODE_CONTINUATION: - raise InvalidFrameException( - 'Sending invalid opcode %d while sending fragmented control ' - 'message' % frame.opcode) - self._pending_control_fragments.append(frame.payload) - if not frame.fin: - return None - return self._reassemble_fragmented_control() - - def _reassemble_fragmented_control(self): - opcode = self._control_opcode - payload = ''.join(self._pending_control_fragments) - self._control_opcode = None - self._pending_control_fragments = [] - if self._message_opcode is not None: - self._frame_handler = self._handle_fragmented_message - else: - self._frame_handler = self._handle_first - return _InnerMessage(opcode, payload) - - def _handle_fragmented_message(self, frame): - # Sender can interleave a control message while sending fragmented - # messages. - if common.is_control_opcode(frame.opcode): - if self._control_opcode is not None: - raise MuxUnexpectedException( - 'Should not reach here(Bug in builder)') - return self._process_first_fragmented_control(frame) - - if frame.opcode != common.OPCODE_CONTINUATION: - raise InvalidFrameException( - 'Sending invalid opcode %d while sending fragmented message' % - frame.opcode) - self._pending_message_fragments.append(frame.payload) - if not frame.fin: - return None - return self._reassemble_fragmented_message() - - def _reassemble_fragmented_message(self): - opcode = self._message_opcode - payload = ''.join(self._pending_message_fragments) - self._message_opcode = None - self._pending_message_fragments = [] - self._frame_handler = self._handle_first - return _InnerMessage(opcode, payload) - - def build(self, frame): - """Build an inner message. - - Returns an _InnerMessage instance when the given frame is the last - fragmented frame. Returns None otherwise. - - Args: - frame: an inner frame. - Raises: - InvalidFrameException: when received invalid opcode. (e.g. - receiving non continuation data opcode but the fin flag of - the previous inner frame was not set.) - """ - return self._frame_handler(frame) - - -class _LogicalStream(Stream): - - """Mimics the Stream class. - - This class interprets multiplexed WebSocket frames. - """ - - def __init__(self, request, stream_options, send_quota, receive_quota): - """Construct an instance. - - Args: - request: _LogicalRequest instance. - stream_options: StreamOptions instance. - send_quota: Initial send quota. - receive_quota: Initial receive quota. - """ - # Physical stream is responsible for masking. - stream_options.unmask_receive = False - Stream.__init__(self, request, stream_options) - - self._send_closed = False - self._send_quota = send_quota - # - Protects _send_closed and _send_quota - # - Signals the thread waiting for send quota replenished - self._send_condition = threading.Condition() - - # The opcode of the first frame in messages. - self._message_opcode = common.OPCODE_TEXT - # True when the last message was fragmented. - self._last_message_was_fragmented = False - - self._receive_quota = receive_quota - self._write_inner_frame_semaphore = threading.Semaphore() - - self._inner_message_builder = _InnerMessageBuilder() - - def _create_inner_frame(self, opcode, payload, end=True): - frame = Frame(fin=end, opcode=opcode, payload=payload) - for frame_filter in self._options.outgoing_frame_filters: - frame_filter.filter(frame) - - if len(payload) != len(frame.payload): - raise MuxUnexpectedException( - 'Mux extension must not be used after extensions which change ' - ' frame boundary') - - first_byte = ((frame.fin << 7) | (frame.rsv1 << 6) | - (frame.rsv2 << 5) | (frame.rsv3 << 4) | frame.opcode) - return chr(first_byte) + frame.payload - - def _write_inner_frame(self, opcode, payload, end=True): - payload_length = len(payload) - write_position = 0 - - try: - # An inner frame will be fragmented if there is no enough send - # quota. This semaphore ensures that fragmented inner frames are - # sent in order on the logical channel. - # Note that frames that come from other logical channels or - # multiplexing control blocks can be inserted between fragmented - # inner frames on the physical channel. - self._write_inner_frame_semaphore.acquire() - - # Consume an octet quota when this is the first fragmented frame. - if opcode != common.OPCODE_CONTINUATION: - try: - self._send_condition.acquire() - while (not self._send_closed) and self._send_quota == 0: - self._send_condition.wait() - - if self._send_closed: - raise BadOperationException( - 'Logical connection %d is closed' % - self._request.channel_id) - - self._send_quota -= 1 - finally: - self._send_condition.release() - - while write_position < payload_length: - try: - self._send_condition.acquire() - while (not self._send_closed) and self._send_quota == 0: - self._logger.debug( - 'No quota. Waiting FlowControl message for %d.' % - self._request.channel_id) - self._send_condition.wait() - - if self._send_closed: - raise BadOperationException( - 'Logical connection %d is closed' % - self.request._channel_id) - - remaining = payload_length - write_position - write_length = min(self._send_quota, remaining) - inner_frame_end = ( - end and - (write_position + write_length == payload_length)) - - inner_frame = self._create_inner_frame( - opcode, - payload[write_position:write_position+write_length], - inner_frame_end) - self._send_quota -= write_length - self._logger.debug('Consumed quota=%d, remaining=%d' % - (write_length, self._send_quota)) - finally: - self._send_condition.release() - - # Writing data will block the worker so we need to release - # _send_condition before writing. - self._logger.debug('Sending inner frame: %r' % inner_frame) - self._request.connection.write(inner_frame) - write_position += write_length - - opcode = common.OPCODE_CONTINUATION - - except ValueError as e: - raise BadOperationException(e) - finally: - self._write_inner_frame_semaphore.release() - - def replenish_send_quota(self, send_quota): - """Replenish send quota.""" - try: - self._send_condition.acquire() - if self._send_quota + send_quota > 0x7FFFFFFFFFFFFFFF: - self._send_quota = 0 - raise LogicalChannelError( - self._request.channel_id, _DROP_CODE_SEND_QUOTA_OVERFLOW) - self._send_quota += send_quota - self._logger.debug('Replenished send quota for channel id %d: %d' % - (self._request.channel_id, self._send_quota)) - finally: - self._send_condition.notify() - self._send_condition.release() - - def consume_receive_quota(self, amount): - """Consume receive quota. Returns False on failure.""" - if self._receive_quota < amount: - self._logger.debug('Violate quota on channel id %d: %d < %d' % - (self._request.channel_id, - self._receive_quota, amount)) - return False - self._receive_quota -= amount - return True - - def send_message(self, message, end=True, binary=False): - """Override Stream.send_message.""" - if self._request.server_terminated: - raise BadOperationException( - 'Requested send_message after sending out a closing handshake') - - if binary and isinstance(message, unicode): - raise BadOperationException( - 'Message for binary frame must be instance of str') - - if binary: - opcode = common.OPCODE_BINARY - else: - opcode = common.OPCODE_TEXT - message = message.encode('utf-8') - - for message_filter in self._options.outgoing_message_filters: - message = message_filter.filter(message, end, binary) - - if self._last_message_was_fragmented: - if opcode != self._message_opcode: - raise BadOperationException('Message types are different in ' - 'frames for the same message') - opcode = common.OPCODE_CONTINUATION - else: - self._message_opcode = opcode - - self._write_inner_frame(opcode, message, end) - self._last_message_was_fragmented = not end - - def _receive_frame(self): - """Override Stream._receive_frame. - - In addition to call Stream._receive_frame, this method adds the amount - of payload to receiving quota and sends FlowControl to the client. - We need to do it here because Stream.receive_message() handles - control frames internally. - """ - opcode, payload, fin, rsv1, rsv2, rsv3 = Stream._receive_frame(self) - amount = len(payload) - # Replenish extra one octet when receiving the first fragmented frame. - if opcode != common.OPCODE_CONTINUATION: - amount += 1 - self._receive_quota += amount - frame_data = _create_flow_control(self._request.channel_id, - amount) - self._logger.debug('Sending flow control for %d, replenished=%d' % - (self._request.channel_id, amount)) - self._request.connection.write_control_data(frame_data) - return opcode, payload, fin, rsv1, rsv2, rsv3 - - def _get_message_from_frame(self, frame): - """Override Stream._get_message_from_frame.""" - try: - inner_message = self._inner_message_builder.build(frame) - except InvalidFrameException: - raise LogicalChannelError( - self._request.channel_id, _DROP_CODE_BAD_FRAGMENTATION) - - if inner_message is None: - return None - self._original_opcode = inner_message.opcode - return inner_message.payload - - def receive_message(self): - """Override Stream.receive_message.""" - # Just call Stream.receive_message(), but catch - # LogicalConnectionClosedException, which is raised when the logical - # connection has closed gracefully. - try: - return Stream.receive_message(self) - except LogicalConnectionClosedException as e: - self._logger.debug('%s', e) - return None - - def _send_closing_handshake(self, code, reason): - """Override Stream._send_closing_handshake.""" - body = create_closing_handshake_body(code, reason) - self._logger.debug('Sending closing handshake for %d: (%r, %r)' % - (self._request.channel_id, code, reason)) - self._write_inner_frame(common.OPCODE_CLOSE, body, end=True) - - self._request.server_terminated = True - - def send_ping(self, body=''): - """Override Stream.send_ping.""" - self._logger.debug('Sending ping on logical channel %d: %r' % - (self._request.channel_id, body)) - self._write_inner_frame(common.OPCODE_PING, body, end=True) - - self._ping_queue.append(body) - - def _send_pong(self, body): - """Override Stream._send_pong.""" - self._logger.debug('Sending pong on logical channel %d: %r' % - (self._request.channel_id, body)) - self._write_inner_frame(common.OPCODE_PONG, body, end=True) - - def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''): - """Override Stream.close_connection.""" - # TODO(bashi): Implement - self._logger.debug('Closing logical connection %d' % - self._request.channel_id) - self._request.server_terminated = True - - def stop_sending(self): - """Stop accepting new send operation (_write_inner_frame).""" - self._send_condition.acquire() - self._send_closed = True - self._send_condition.notify() - self._send_condition.release() - - -class _OutgoingData(object): - - """Simple data/channel container. - - A structure that holds data to be sent via physical connection and - origin of the data. - """ - - def __init__(self, channel_id, data): - self.channel_id = channel_id - self.data = data - - -class _PhysicalConnectionWriter(threading.Thread): - - """A thread that is responsible for writing data to physical connection. - - TODO(bashi): Make sure there is no thread-safety problem when the reader - thread reads data from the same socket at a time. - """ - - def __init__(self, mux_handler): - """Construct an instance. - - Args: - mux_handler: _MuxHandler instance. - """ - threading.Thread.__init__(self) - self._logger = util.get_class_logger(self) - self._mux_handler = mux_handler - self.setDaemon(True) - - # When set, make this thread stop accepting new data, flush pending - # data and exit. - self._stop_requested = False - # The close code of the physical connection. - self._close_code = common.STATUS_NORMAL_CLOSURE - # Deque for passing write data. It's protected by _deque_condition - # until _stop_requested is set. - self._deque = collections.deque() - # - Protects _deque, _stop_requested and _close_code - # - Signals threads waiting for them to be available - self._deque_condition = threading.Condition() - - def put_outgoing_data(self, data): - """Put outgoing data. - - Args: - data: _OutgoingData instance. - Raises: - BadOperationException: when the thread has been requested to - terminate. - """ - try: - self._deque_condition.acquire() - if self._stop_requested: - raise BadOperationException('Cannot write data anymore') - - self._deque.append(data) - self._deque_condition.notify() - finally: - self._deque_condition.release() - - def _write_data(self, outgoing_data): - message = (_encode_channel_id(outgoing_data.channel_id) + - outgoing_data.data) - try: - self._mux_handler.physical_stream.send_message( - message=message, end=True, binary=True) - except Exception as e: - util.prepend_message_to_exception( - 'Failed to send message to %r: ' % - (self._mux_handler.physical_connection.remote_addr,), e) - raise - - # TODO(bashi): It would be better to block the thread that sends - # control data as well. - if outgoing_data.channel_id != _CONTROL_CHANNEL_ID: - self._mux_handler.notify_write_data_done(outgoing_data.channel_id) - - def run(self): - try: - self._deque_condition.acquire() - while not self._stop_requested: - if len(self._deque) == 0: - self._deque_condition.wait() - continue - - outgoing_data = self._deque.popleft() - - self._deque_condition.release() - self._write_data(outgoing_data) - self._deque_condition.acquire() - - # Flush deque. - # - # At this point, self._deque_condition is always acquired. - try: - while len(self._deque) > 0: - outgoing_data = self._deque.popleft() - self._write_data(outgoing_data) - finally: - self._deque_condition.release() - - # Close physical connection. - try: - # Don't wait the response here. The response will be read - # by the reader thread. - self._mux_handler.physical_stream.close_connection( - self._close_code, wait_response=False) - except Exception as e: - util.prepend_message_to_exception( - 'Failed to close the physical connection: %r' % e) - raise - finally: - self._mux_handler.notify_writer_done() - - def stop(self, close_code=common.STATUS_NORMAL_CLOSURE): - """Stop the writer thread.""" - self._deque_condition.acquire() - self._stop_requested = True - self._close_code = close_code - self._deque_condition.notify() - self._deque_condition.release() - - -class _PhysicalConnectionReader(threading.Thread): - - """A thread that is responsible for reading data from physical connection. - """ - - def __init__(self, mux_handler): - """Construct an instance. - - Args: - mux_handler: _MuxHandler instance. - """ - threading.Thread.__init__(self) - self._logger = util.get_class_logger(self) - self._mux_handler = mux_handler - self.setDaemon(True) - - def run(self): - while True: - try: - physical_stream = self._mux_handler.physical_stream - message = physical_stream.receive_message() - if message is None: - break - # Below happens only when a data message is received. - opcode = physical_stream.get_last_received_opcode() - if opcode != common.OPCODE_BINARY: - self._mux_handler.fail_physical_connection( - _DROP_CODE_INVALID_ENCAPSULATING_MESSAGE, - 'Received a text message on physical connection') - break - - except ConnectionTerminatedException as e: - self._logger.debug('%s', e) - break - - try: - self._mux_handler.dispatch_message(message) - except PhysicalConnectionError as e: - self._mux_handler.fail_physical_connection( - e.drop_code, e.message) - break - except LogicalChannelError as e: - self._mux_handler.fail_logical_channel( - e.channel_id, e.drop_code, e.message) - except Exception as e: - self._logger.debug(traceback.format_exc()) - break - - self._mux_handler.notify_reader_done() - - -class _Worker(threading.Thread): - - """A thread that is responsible for running the corresponding application - handler. - """ - - def __init__(self, mux_handler, request): - """Construct an instance. - - Args: - mux_handler: _MuxHandler instance. - request: _LogicalRequest instance. - """ - threading.Thread.__init__(self) - self._logger = util.get_class_logger(self) - self._mux_handler = mux_handler - self._request = request - self.setDaemon(True) - - def run(self): - self._logger.debug('Logical channel worker started. (id=%d)' % - self._request.channel_id) - try: - # Non-critical exceptions will be handled by dispatcher. - self._mux_handler.dispatcher.transfer_data(self._request) - except LogicalChannelError as e: - self._mux_handler.fail_logical_channel( - e.channel_id, e.drop_code, e.message) - finally: - self._mux_handler.notify_worker_done(self._request.channel_id) - - -class _MuxHandshaker(hybi.Handshaker): - - """Opening handshake processor for multiplexing.""" - - _DUMMY_WEBSOCKET_KEY = 'dGhlIHNhbXBsZSBub25jZQ==' - - def __init__(self, request, dispatcher, send_quota, receive_quota): - """Construct an instance. - - Args: - request: _LogicalRequest instance. - dispatcher: Dispatcher instance (dispatch.Dispatcher). - send_quota: Initial send quota. - receive_quota: Initial receive quota. - """ - hybi.Handshaker.__init__(self, request, dispatcher) - self._send_quota = send_quota - self._receive_quota = receive_quota - - # Append headers which should not be included in handshake field of - # AddChannelRequest. - # TODO(bashi): Make sure whether we should raise exception when - # these headers are included already. - request.headers_in[common.UPGRADE_HEADER] = ( - common.WEBSOCKET_UPGRADE_TYPE) - request.headers_in[common.SEC_WEBSOCKET_VERSION_HEADER] = ( - str(common.VERSION_HYBI_LATEST)) - request.headers_in[common.SEC_WEBSOCKET_KEY_HEADER] = ( - self._DUMMY_WEBSOCKET_KEY) - - def _create_stream(self, stream_options): - """Override hybi.Handshaker._create_stream.""" - self._logger.debug('Creating logical stream for %d' % - self._request.channel_id) - return _LogicalStream( - self._request, stream_options, self._send_quota, - self._receive_quota) - - def _create_handshake_response(self, accept): - """Override hybi._create_handshake_response.""" - response = [] - - response.append('HTTP/1.1 101 Switching Protocols\r\n') - - # Upgrade and Sec-WebSocket-Accept should be excluded. - response.append('%s: %s\r\n' % ( - common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE)) - if self._request.ws_protocol is not None: - response.append('%s: %s\r\n' % ( - common.SEC_WEBSOCKET_PROTOCOL_HEADER, - self._request.ws_protocol)) - if (self._request.ws_extensions is not None and - len(self._request.ws_extensions) != 0): - response.append('%s: %s\r\n' % ( - common.SEC_WEBSOCKET_EXTENSIONS_HEADER, - common.format_extensions(self._request.ws_extensions))) - response.append('\r\n') - - return ''.join(response) - - def _send_handshake(self, accept): - """Override hybi.Handshaker._send_handshake.""" - # Don't send handshake response for the default channel - if self._request.channel_id == _DEFAULT_CHANNEL_ID: - return - - handshake_response = self._create_handshake_response(accept) - frame_data = _create_add_channel_response( - self._request.channel_id, - handshake_response) - self._logger.debug('Sending handshake response for %d: %r' % - (self._request.channel_id, frame_data)) - self._request.connection.write_control_data(frame_data) - - -class _LogicalChannelData(object): - - """A structure that holds information about logical channel.""" - - def __init__(self, request, worker): - self.request = request - self.worker = worker - self.drop_code = _DROP_CODE_NORMAL_CLOSURE - self.drop_message = '' - - -class _HandshakeDeltaBase(object): - """A class that holds information for delta-encoded handshake.""" - - def __init__(self, headers): - self._headers = headers - - def create_headers(self, delta=None): - """Creates request headers for an AddChannelRequest that has - delta-encoded handshake. - - Args: - delta: headers should be overridden. - """ - - headers = copy.copy(self._headers) - if delta: - for key, value in delta.items(): - # The spec requires that a header with an empty value is - # removed from the delta base. - if len(value) == 0 and headers.has_key(key): - del headers[key] - else: - headers[key] = value - return headers - - -class _MuxHandler(object): - """Multiplexing handler. When a handler starts, it launches three - threads; the reader thread, the writer thread, and a worker thread. - - The reader thread reads data from the physical stream, i.e., the - ws_stream object of the underlying websocket connection. The reader - thread interprets multiplexed frames and dispatches them to logical - channels. Methods of this class are mostly called by the reader thread. - - The writer thread sends multiplexed frames which are created by - logical channels via the physical connection. - - The worker thread launched at the starting point handles the - "Implicitly Opened Connection". If multiplexing handler receives - an AddChannelRequest and accepts it, the handler will launch a new worker - thread and dispatch the request to it. - """ - - def __init__(self, request, dispatcher): - """Constructs an instance. - - Args: - request: mod_python request of the physical connection. - dispatcher: Dispatcher instance (dispatch.Dispatcher). - """ - - self.original_request = request - self.dispatcher = dispatcher - self.physical_connection = request.connection - self.physical_stream = request.ws_stream - self._logger = util.get_class_logger(self) - self._logical_channels = {} - self._logical_channels_condition = threading.Condition() - # Holds client's initial quota - self._channel_slots = collections.deque() - self._handshake_base = None - self._worker_done_notify_received = False - self._reader = None - self._writer = None - - def start(self): - """Starts the handler. - - Raises: - MuxUnexpectedException: when the handler already started, or when - opening handshake of the default channel fails. - """ - - if self._reader or self._writer: - raise MuxUnexpectedException('MuxHandler already started') - - self._reader = _PhysicalConnectionReader(self) - self._writer = _PhysicalConnectionWriter(self) - self._reader.start() - self._writer.start() - - # Create "Implicitly Opened Connection". - logical_connection = _LogicalConnection(self, _DEFAULT_CHANNEL_ID) - headers = copy.copy(self.original_request.headers_in) - # Add extensions for logical channel. - headers[common.SEC_WEBSOCKET_EXTENSIONS_HEADER] = ( - common.format_extensions( - self.original_request.mux_processor.extensions())) - self._handshake_base = _HandshakeDeltaBase(headers) - logical_request = _LogicalRequest( - _DEFAULT_CHANNEL_ID, - self.original_request.method, - self.original_request.uri, - self.original_request.protocol, - self._handshake_base.create_headers(), - logical_connection) - # Client's send quota for the implicitly opened connection is zero, - # but we will send FlowControl later so set the initial quota to - # _INITIAL_QUOTA_FOR_CLIENT. - self._channel_slots.append(_INITIAL_QUOTA_FOR_CLIENT) - send_quota = self.original_request.mux_processor.quota() - if not self._do_handshake_for_logical_request( - logical_request, send_quota=send_quota): - raise MuxUnexpectedException( - 'Failed handshake on the default channel id') - self._add_logical_channel(logical_request) - - # Send FlowControl for the implicitly opened connection. - frame_data = _create_flow_control(_DEFAULT_CHANNEL_ID, - _INITIAL_QUOTA_FOR_CLIENT) - logical_request.connection.write_control_data(frame_data) - - def add_channel_slots(self, slots, send_quota): - """Adds channel slots. - - Args: - slots: number of slots to be added. - send_quota: initial send quota for slots. - """ - - self._channel_slots.extend([send_quota] * slots) - # Send NewChannelSlot to client. - frame_data = _create_new_channel_slot(slots, send_quota) - self.send_control_data(frame_data) - - def wait_until_done(self, timeout=None): - """Waits until all workers are done. Returns False when timeout has - occurred. Returns True on success. - - Args: - timeout: timeout in sec. - """ - - self._logical_channels_condition.acquire() - try: - while len(self._logical_channels) > 0: - self._logger.debug('Waiting workers(%d)...' % - len(self._logical_channels)) - self._worker_done_notify_received = False - self._logical_channels_condition.wait(timeout) - if not self._worker_done_notify_received: - self._logger.debug('Waiting worker(s) timed out') - return False - finally: - self._logical_channels_condition.release() - - # Flush pending outgoing data - self._writer.stop() - self._writer.join() - - return True - - def notify_write_data_done(self, channel_id): - """Called by the writer thread when a write operation has done. - - Args: - channel_id: objective channel id. - """ - - try: - self._logical_channels_condition.acquire() - if channel_id in self._logical_channels: - channel_data = self._logical_channels[channel_id] - channel_data.request.connection.on_write_data_done() - else: - self._logger.debug('Seems that logical channel for %d has gone' - % channel_id) - finally: - self._logical_channels_condition.release() - - def send_control_data(self, data): - """Sends data via the control channel. - - Args: - data: data to be sent. - """ - - self._writer.put_outgoing_data(_OutgoingData( - channel_id=_CONTROL_CHANNEL_ID, data=data)) - - def send_data(self, channel_id, data): - """Sends data via given logical channel. This method is called by - worker threads. - - Args: - channel_id: objective channel id. - data: data to be sent. - """ - - self._writer.put_outgoing_data(_OutgoingData( - channel_id=channel_id, data=data)) - - def _send_drop_channel(self, channel_id, code=None, message=''): - frame_data = _create_drop_channel(channel_id, code, message) - self._logger.debug( - 'Sending drop channel for channel id %d' % channel_id) - self.send_control_data(frame_data) - - def _send_error_add_channel_response(self, channel_id, status=None): - if status is None: - status = common.HTTP_STATUS_BAD_REQUEST - - if status in _HTTP_BAD_RESPONSE_MESSAGES: - message = _HTTP_BAD_RESPONSE_MESSAGES[status] - else: - self._logger.debug('Response message for %d is not found' % status) - message = '???' - - response = 'HTTP/1.1 %d %s\r\n\r\n' % (status, message) - frame_data = _create_add_channel_response(channel_id, - encoded_handshake=response, - encoding=0, rejected=True) - self.send_control_data(frame_data) - - def _create_logical_request(self, block): - if block.channel_id == _CONTROL_CHANNEL_ID: - # TODO(bashi): Raise PhysicalConnectionError with code 2006 - # instead of MuxUnexpectedException. - raise MuxUnexpectedException( - 'Received the control channel id (0) as objective channel ' - 'id for AddChannel') - - if block.encoding > _HANDSHAKE_ENCODING_DELTA: - raise PhysicalConnectionError( - _DROP_CODE_UNKNOWN_REQUEST_ENCODING) - - method, path, version, headers = _parse_request_text( - block.encoded_handshake) - if block.encoding == _HANDSHAKE_ENCODING_DELTA: - headers = self._handshake_base.create_headers(headers) - - connection = _LogicalConnection(self, block.channel_id) - request = _LogicalRequest(block.channel_id, method, path, version, - headers, connection) - return request - - def _do_handshake_for_logical_request(self, request, send_quota=0): - try: - receive_quota = self._channel_slots.popleft() - except IndexError: - raise LogicalChannelError( - request.channel_id, _DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION) - - handshaker = _MuxHandshaker(request, self.dispatcher, - send_quota, receive_quota) - try: - handshaker.do_handshake() - except handshake.VersionException as e: - self._logger.info('%s', e) - self._send_error_add_channel_response( - request.channel_id, status=common.HTTP_STATUS_BAD_REQUEST) - return False - except handshake.HandshakeException as e: - # TODO(bashi): Should we _Fail the Logical Channel_ with 3001 - # instead? - self._logger.info('%s', e) - self._send_error_add_channel_response(request.channel_id, - status=e.status) - return False - except handshake.AbortedByUserException as e: - self._logger.info('%s', e) - self._send_error_add_channel_response(request.channel_id) - return False - - return True - - def _add_logical_channel(self, logical_request): - try: - self._logical_channels_condition.acquire() - if logical_request.channel_id in self._logical_channels: - self._logger.debug('Channel id %d already exists' % - logical_request.channel_id) - raise PhysicalConnectionError( - _DROP_CODE_CHANNEL_ALREADY_EXISTS, - 'Channel id %d already exists' % - logical_request.channel_id) - worker = _Worker(self, logical_request) - channel_data = _LogicalChannelData(logical_request, worker) - self._logical_channels[logical_request.channel_id] = channel_data - worker.start() - finally: - self._logical_channels_condition.release() - - def _process_add_channel_request(self, block): - try: - logical_request = self._create_logical_request(block) - except ValueError as e: - self._logger.debug('Failed to create logical request: %r' % e) - self._send_error_add_channel_response( - block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST) - return - if self._do_handshake_for_logical_request(logical_request): - if block.encoding == _HANDSHAKE_ENCODING_IDENTITY: - # Update handshake base. - # TODO(bashi): Make sure this is the right place to update - # handshake base. - self._handshake_base = _HandshakeDeltaBase( - logical_request.headers_in) - self._add_logical_channel(logical_request) - else: - self._send_error_add_channel_response( - block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST) - - def _process_flow_control(self, block): - try: - self._logical_channels_condition.acquire() - if not block.channel_id in self._logical_channels: - return - channel_data = self._logical_channels[block.channel_id] - channel_data.request.ws_stream.replenish_send_quota( - block.send_quota) - finally: - self._logical_channels_condition.release() - - def _process_drop_channel(self, block): - self._logger.debug( - 'DropChannel received for %d: code=%r, reason=%r' % - (block.channel_id, block.drop_code, block.drop_message)) - try: - self._logical_channels_condition.acquire() - if not block.channel_id in self._logical_channels: - return - channel_data = self._logical_channels[block.channel_id] - channel_data.drop_code = _DROP_CODE_ACKNOWLEDGED - - # Close the logical channel - channel_data.request.connection.set_read_state( - _LogicalConnection.STATE_TERMINATED) - channel_data.request.ws_stream.stop_sending() - finally: - self._logical_channels_condition.release() - - def _process_control_blocks(self, parser): - for control_block in parser.read_control_blocks(): - opcode = control_block.opcode - self._logger.debug('control block received, opcode: %d' % opcode) - if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST: - self._process_add_channel_request(control_block) - elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE: - raise PhysicalConnectionError( - _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - 'Received AddChannelResponse') - elif opcode == _MUX_OPCODE_FLOW_CONTROL: - self._process_flow_control(control_block) - elif opcode == _MUX_OPCODE_DROP_CHANNEL: - self._process_drop_channel(control_block) - elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT: - raise PhysicalConnectionError( - _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - 'Received NewChannelSlot') - else: - raise MuxUnexpectedException( - 'Unexpected opcode %r' % opcode) - - def _process_logical_frame(self, channel_id, parser): - self._logger.debug('Received a frame. channel id=%d' % channel_id) - try: - self._logical_channels_condition.acquire() - if not channel_id in self._logical_channels: - # We must ignore the message for an inactive channel. - return - channel_data = self._logical_channels[channel_id] - fin, rsv1, rsv2, rsv3, opcode, payload = parser.read_inner_frame() - consuming_byte = len(payload) - if opcode != common.OPCODE_CONTINUATION: - consuming_byte += 1 - if not channel_data.request.ws_stream.consume_receive_quota( - consuming_byte): - # The client violates quota. Close logical channel. - raise LogicalChannelError( - channel_id, _DROP_CODE_SEND_QUOTA_VIOLATION) - header = create_header(opcode, len(payload), fin, rsv1, rsv2, rsv3, - mask=False) - frame_data = header + payload - channel_data.request.connection.append_frame_data(frame_data) - finally: - self._logical_channels_condition.release() - - def dispatch_message(self, message): - """Dispatches message. The reader thread calls this method. - - Args: - message: a message that contains encapsulated frame. - Raises: - PhysicalConnectionError: if the message contains physical - connection level errors. - LogicalChannelError: if the message contains logical channel - level errors. - """ - - parser = _MuxFramePayloadParser(message) - try: - channel_id = parser.read_channel_id() - except ValueError as e: - raise PhysicalConnectionError(_DROP_CODE_CHANNEL_ID_TRUNCATED) - if channel_id == _CONTROL_CHANNEL_ID: - self._process_control_blocks(parser) - else: - self._process_logical_frame(channel_id, parser) - - def notify_worker_done(self, channel_id): - """Called when a worker has finished. - - Args: - channel_id: channel id corresponded with the worker. - """ - - self._logger.debug('Worker for channel id %d terminated' % channel_id) - try: - self._logical_channels_condition.acquire() - if not channel_id in self._logical_channels: - raise MuxUnexpectedException( - 'Channel id %d not found' % channel_id) - channel_data = self._logical_channels.pop(channel_id) - finally: - self._worker_done_notify_received = True - self._logical_channels_condition.notify() - self._logical_channels_condition.release() - - if not channel_data.request.server_terminated: - self._send_drop_channel( - channel_id, code=channel_data.drop_code, - message=channel_data.drop_message) - - def notify_reader_done(self): - """This method is called by the reader thread when the reader has - finished. - """ - - self._logger.debug( - 'Termiating all logical connections waiting for incoming data ' - '...') - self._logical_channels_condition.acquire() - for channel_data in self._logical_channels.values(): - try: - channel_data.request.connection.set_read_state( - _LogicalConnection.STATE_TERMINATED) - except Exception: - self._logger.debug(traceback.format_exc()) - self._logical_channels_condition.release() - - def notify_writer_done(self): - """This method is called by the writer thread when the writer has - finished. - """ - - self._logger.debug( - 'Termiating all logical connections waiting for write ' - 'completion ...') - self._logical_channels_condition.acquire() - for channel_data in self._logical_channels.values(): - try: - channel_data.request.connection.on_writer_done() - except Exception: - self._logger.debug(traceback.format_exc()) - self._logical_channels_condition.release() - - def fail_physical_connection(self, code, message): - """Fail the physical connection. - - Args: - code: drop reason code. - message: drop message. - """ - - self._logger.debug('Failing the physical connection...') - self._send_drop_channel(_CONTROL_CHANNEL_ID, code, message) - self._writer.stop(common.STATUS_INTERNAL_ENDPOINT_ERROR) - - def fail_logical_channel(self, channel_id, code, message): - """Fail a logical channel. - - Args: - channel_id: channel id. - code: drop reason code. - message: drop message. - """ - - self._logger.debug('Failing logical channel %d...' % channel_id) - try: - self._logical_channels_condition.acquire() - if channel_id in self._logical_channels: - channel_data = self._logical_channels[channel_id] - # Close the logical channel. notify_worker_done() will be - # called later and it will send DropChannel. - channel_data.drop_code = code - channel_data.drop_message = message - - channel_data.request.connection.set_read_state( - _LogicalConnection.STATE_TERMINATED) - channel_data.request.ws_stream.stop_sending() - else: - self._send_drop_channel(channel_id, code, message) - finally: - self._logical_channels_condition.release() - - -def use_mux(request): - return hasattr(request, 'mux_processor') and ( - request.mux_processor.is_active()) - - -def start(request, dispatcher): - mux_handler = _MuxHandler(request, dispatcher) - mux_handler.start() - - mux_handler.add_channel_slots(_INITIAL_NUMBER_OF_CHANNEL_SLOTS, - _INITIAL_QUOTA_FOR_CLIENT) - - mux_handler.wait_until_done() - - -# vi:sts=4 sw=4 et diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/standalone.py b/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/standalone.py deleted file mode 100755 index 2c878606488..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/standalone.py +++ /dev/null @@ -1,1208 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Standalone WebSocket server. - -Use this file to launch pywebsocket without Apache HTTP Server. - - -BASIC USAGE -=========== - -Go to the src directory and run - - $ python mod_pywebsocket/standalone.py [-p ] - [-w ] - [-d ] - - is the port number to use for ws:// connection. - - is the path to the root directory of HTML files. - - is the path to the root directory of WebSocket handlers. -If not specified, will be used. See __init__.py (or -run $ pydoc mod_pywebsocket) for how to write WebSocket handlers. - -For more detail and other options, run - - $ python mod_pywebsocket/standalone.py --help - -or see _build_option_parser method below. - -For trouble shooting, adding "--log_level debug" might help you. - - -TRY DEMO -======== - -Go to the src directory and run standalone.py with -d option to set the -document root to the directory containing example HTMLs and handlers like this: - - $ cd src - $ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example - -to launch pywebsocket with the sample handler and html on port 80. Open -http://localhost/console.html, click the connect button, type something into -the text box next to the send button and click the send button. If everything -is working, you'll see the message you typed echoed by the server. - - -USING TLS -========= - -To run the standalone server with TLS support, run it with -t, -k, and -c -options. When TLS is enabled, the standalone server accepts only TLS connection. - -Note that when ssl module is used and the key/cert location is incorrect, -TLS connection silently fails while pyOpenSSL fails on startup. - -Example: - - $ PYTHONPATH=. python mod_pywebsocket/standalone.py \ - -d example \ - -p 10443 \ - -t \ - -c ../test/cert/cert.pem \ - -k ../test/cert/key.pem \ - -Note that when passing a relative path to -c and -k option, it will be resolved -using the document root directory as the base. - - -USING CLIENT AUTHENTICATION -=========================== - -To run the standalone server with TLS client authentication support, run it with ---tls-client-auth and --tls-client-ca options in addition to ones required for -TLS support. - -Example: - - $ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example -p 10443 -t \ - -c ../test/cert/cert.pem -k ../test/cert/key.pem \ - --tls-client-auth \ - --tls-client-ca=../test/cert/cacert.pem - -Note that when passing a relative path to --tls-client-ca option, it will be -resolved using the document root directory as the base. - - -CONFIGURATION FILE -================== - -You can also write a configuration file and use it by specifying the path to -the configuration file by --config option. Please write a configuration file -following the documentation of the Python ConfigParser library. Name of each -entry must be the long version argument name. E.g. to set log level to debug, -add the following line: - -log_level=debug - -For options which doesn't take value, please add some fake value. E.g. for ---tls option, add the following line: - -tls=True - -Note that tls will be enabled even if you write tls=False as the value part is -fake. - -When both a command line argument and a configuration file entry are set for -the same configuration item, the command line value will override one in the -configuration file. - - -THREADING -========= - -This server is derived from socketserver.ThreadingMixIn. Hence a thread is -used for each request. - - -SECURITY WARNING -================ - -This uses CGIHTTPServer and CGIHTTPServer is not secure. -It may execute arbitrary Python code or external programs. It should not be -used outside a firewall. -""" - -from six.moves import BaseHTTPServer -from six.moves import CGIHTTPServer -from six.moves import SimpleHTTPServer -from six.moves import socketserver -from six.moves import configparser -import base64 -from six.moves import http_client -import logging -import logging.handlers -import optparse -import os -import re -import select -import socket -import sys -import threading -import time -from six.moves import urllib - -from mod_pywebsocket import common -from mod_pywebsocket import dispatch -from mod_pywebsocket import handshake -from mod_pywebsocket import http_header_util -from mod_pywebsocket import memorizingfile -from mod_pywebsocket import util -from mod_pywebsocket.xhr_benchmark_handler import XHRBenchmarkHandler - - -_DEFAULT_LOG_MAX_BYTES = 1024 * 256 -_DEFAULT_LOG_BACKUP_COUNT = 5 - -_DEFAULT_REQUEST_QUEUE_SIZE = 128 - -# 1024 is practically large enough to contain WebSocket handshake lines. -_MAX_MEMORIZED_LINES = 1024 - -# Constants for the --tls_module flag. -_TLS_BY_STANDARD_MODULE = 'ssl' -_TLS_BY_PYOPENSSL = 'pyopenssl' - - -class _StandaloneConnection(object): - """Mimic mod_python mp_conn.""" - - def __init__(self, request_handler): - """Construct an instance. - - Args: - request_handler: A WebSocketRequestHandler instance. - """ - - self._request_handler = request_handler - - def get_local_addr(self): - """Getter to mimic mp_conn.local_addr.""" - - return (self._request_handler.server.server_name, - self._request_handler.server.server_port) - local_addr = property(get_local_addr) - - def get_remote_addr(self): - """Getter to mimic mp_conn.remote_addr. - - Setting the property in __init__ won't work because the request - handler is not initialized yet there.""" - - return self._request_handler.client_address - remote_addr = property(get_remote_addr) - - def write(self, data): - """Mimic mp_conn.write().""" - - return self._request_handler.wfile.write(data) - - def read(self, length): - """Mimic mp_conn.read().""" - - return self._request_handler.rfile.read(length) - - def get_memorized_lines(self): - """Get memorized lines.""" - - return self._request_handler.rfile.get_memorized_lines() - - -class _StandaloneRequest(object): - """Mimic mod_python request.""" - - def __init__(self, request_handler, use_tls): - """Construct an instance. - - Args: - request_handler: A WebSocketRequestHandler instance. - """ - - self._logger = util.get_class_logger(self) - - self._request_handler = request_handler - self.connection = _StandaloneConnection(request_handler) - self._use_tls = use_tls - self.headers_in = request_handler.headers - - def get_uri(self): - """Getter to mimic request.uri. - - This method returns the raw data at the Request-URI part of the - Request-Line, while the uri method on the request object of mod_python - returns the path portion after parsing the raw data. This behavior is - kept for compatibility. - """ - - return self._request_handler.path - uri = property(get_uri) - - def get_unparsed_uri(self): - """Getter to mimic request.unparsed_uri.""" - - return self._request_handler.path - unparsed_uri = property(get_unparsed_uri) - - def get_method(self): - """Getter to mimic request.method.""" - - return self._request_handler.command - method = property(get_method) - - def get_protocol(self): - """Getter to mimic request.protocol.""" - - return self._request_handler.request_version - protocol = property(get_protocol) - - def is_https(self): - """Mimic request.is_https().""" - - return self._use_tls - - -def _import_ssl(): - global ssl - try: - import ssl - return True - except ImportError: - return False - - -def _import_pyopenssl(): - global OpenSSL - try: - import OpenSSL.SSL - return True - except ImportError: - return False - - -class _StandaloneSSLConnection(object): - """A wrapper class for OpenSSL.SSL.Connection to - - provide makefile method which is not supported by the class - - tweak shutdown method since OpenSSL.SSL.Connection.shutdown doesn't - accept the "how" argument. - - convert SysCallError exceptions that its recv method may raise into a - return value of '', meaning EOF. We cannot overwrite the recv method on - self._connection since it's immutable. - """ - - _OVERRIDDEN_ATTRIBUTES = ['_connection', 'makefile', 'shutdown', 'recv'] - - def __init__(self, connection): - self._connection = connection - - def __getattribute__(self, name): - if name in _StandaloneSSLConnection._OVERRIDDEN_ATTRIBUTES: - return object.__getattribute__(self, name) - return self._connection.__getattribute__(name) - - def __setattr__(self, name, value): - if name in _StandaloneSSLConnection._OVERRIDDEN_ATTRIBUTES: - return object.__setattr__(self, name, value) - return self._connection.__setattr__(name, value) - - def makefile(self, mode='r', bufsize=-1): - return socket._fileobject(self, mode, bufsize) - - def shutdown(self, unused_how): - self._connection.shutdown() - - def recv(self, bufsize, flags=0): - if flags != 0: - raise ValueError('Non-zero flags not allowed') - - try: - return self._connection.recv(bufsize) - except OpenSSL.SSL.SysCallError as e: - if e.args[0] == -1: - # Suppress "unexpected EOF" exception. See the OpenSSL document - # for SSL_get_error. - return '' - raise - - -def _alias_handlers(dispatcher, websock_handlers_map_file): - """Set aliases specified in websock_handler_map_file in dispatcher. - - Args: - dispatcher: dispatch.Dispatcher instance - websock_handler_map_file: alias map file - """ - - fp = open(websock_handlers_map_file) - try: - for line in fp: - if line[0] == '#' or line.isspace(): - continue - m = re.match(r'(\S+)\s+(\S+)', line) - if not m: - logging.warning('Wrong format in map file:' + line) - continue - try: - dispatcher.add_resource_path_alias( - m.group(1), m.group(2)) - except dispatch.DispatchException as e: - logging.error(str(e)) - finally: - fp.close() - - -class WebSocketServer(socketserver.ThreadingMixIn, BaseHTTPServer.HTTPServer): - """HTTPServer specialized for WebSocket.""" - - # Overrides socketserver.ThreadingMixIn.daemon_threads - daemon_threads = True - # Overrides BaseHTTPServer.HTTPServer.allow_reuse_address - allow_reuse_address = True - - def __init__(self, options): - """Override socketserver.TCPServer.__init__ to set SSL enabled - socket object to self.socket before server_bind and server_activate, - if necessary. - """ - - # Share a Dispatcher among request handlers to save time for - # instantiation. Dispatcher can be shared because it is thread-safe. - options.dispatcher = dispatch.Dispatcher( - options.websock_handlers, - options.scan_dir, - options.allow_handlers_outside_root_dir) - if options.websock_handlers_map_file: - _alias_handlers(options.dispatcher, - options.websock_handlers_map_file) - warnings = options.dispatcher.source_warnings() - if warnings: - for warning in warnings: - logging.warning('Warning in source loading: %s' % warning) - - self._logger = util.get_class_logger(self) - - self.request_queue_size = options.request_queue_size - self.__ws_is_shut_down = threading.Event() - self.__ws_serving = False - - socketserver.BaseServer.__init__( - self, (options.server_host, options.port), WebSocketRequestHandler) - - # Expose the options object to allow handler objects access it. We name - # it with websocket_ prefix to avoid conflict. - self.websocket_server_options = options - - self._create_sockets() - self.server_bind() - self.server_activate() - - def _create_sockets(self): - self.server_name, self.server_port = self.server_address - self._sockets = [] - if not self.server_name: - # On platforms that doesn't support IPv6, the first bind fails. - # On platforms that supports IPv6 - # - If it binds both IPv4 and IPv6 on call with AF_INET6, the - # first bind succeeds and the second fails (we'll see 'Address - # already in use' error). - # - If it binds only IPv6 on call with AF_INET6, both call are - # expected to succeed to listen both protocol. - addrinfo_array = [ - (socket.AF_INET6, socket.SOCK_STREAM, '', '', ''), - (socket.AF_INET, socket.SOCK_STREAM, '', '', '')] - else: - addrinfo_array = socket.getaddrinfo(self.server_name, - self.server_port, - socket.AF_UNSPEC, - socket.SOCK_STREAM, - socket.IPPROTO_TCP) - for addrinfo in addrinfo_array: - self._logger.info('Create socket on: %r', addrinfo) - family, socktype, proto, canonname, sockaddr = addrinfo - try: - socket_ = socket.socket(family, socktype) - except Exception as e: - self._logger.info('Skip by failure: %r', e) - continue - server_options = self.websocket_server_options - if server_options.use_tls: - # For the case of _HAS_OPEN_SSL, we do wrapper setup after - # accept. - if server_options.tls_module == _TLS_BY_STANDARD_MODULE: - if server_options.tls_client_auth: - if server_options.tls_client_cert_optional: - client_cert_ = ssl.CERT_OPTIONAL - else: - client_cert_ = ssl.CERT_REQUIRED - else: - client_cert_ = ssl.CERT_NONE - socket_ = ssl.wrap_socket(socket_, - keyfile=server_options.private_key, - certfile=server_options.certificate, - ssl_version=ssl.PROTOCOL_SSLv23, - ca_certs=server_options.tls_client_ca, - cert_reqs=client_cert_, - do_handshake_on_connect=False) - self._sockets.append((socket_, addrinfo)) - - def server_bind(self): - """Override socketserver.TCPServer.server_bind to enable multiple - sockets bind. - """ - - failed_sockets = [] - - for socketinfo in self._sockets: - socket_, addrinfo = socketinfo - self._logger.info('Bind on: %r', addrinfo) - if self.allow_reuse_address: - socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - try: - socket_.bind(self.server_address) - except Exception as e: - self._logger.info('Skip by failure: %r', e) - socket_.close() - failed_sockets.append(socketinfo) - if self.server_address[1] == 0: - # The operating system assigns the actual port number for port - # number 0. This case, the second and later sockets should use - # the same port number. Also self.server_port is rewritten - # because it is exported, and will be used by external code. - self.server_address = ( - self.server_name, socket_.getsockname()[1]) - self.server_port = self.server_address[1] - self._logger.info('Port %r is assigned', self.server_port) - - for socketinfo in failed_sockets: - self._sockets.remove(socketinfo) - - def server_activate(self): - """Override socketserver.TCPServer.server_activate to enable multiple - sockets listen. - """ - - failed_sockets = [] - - for socketinfo in self._sockets: - socket_, addrinfo = socketinfo - self._logger.info('Listen on: %r', addrinfo) - try: - socket_.listen(self.request_queue_size) - except Exception as e: - self._logger.info('Skip by failure: %r', e) - socket_.close() - failed_sockets.append(socketinfo) - - for socketinfo in failed_sockets: - self._sockets.remove(socketinfo) - - if len(self._sockets) == 0: - self._logger.critical( - 'No sockets activated. Use info log level to see the reason.') - - def server_close(self): - """Override socketserver.TCPServer.server_close to enable multiple - sockets close. - """ - - for socketinfo in self._sockets: - socket_, addrinfo = socketinfo - self._logger.info('Close on: %r', addrinfo) - socket_.close() - - def fileno(self): - """Override socketserver.TCPServer.fileno.""" - - self._logger.critical('Not supported: fileno') - return self._sockets[0][0].fileno() - - def handle_error(self, request, client_address): - """Override socketserver.handle_error.""" - - self._logger.error( - 'Exception in processing request from: %r\n%s', - client_address, - util.get_stack_trace()) - # Note: client_address is a tuple. - - def get_request(self): - """Override TCPServer.get_request to wrap OpenSSL.SSL.Connection - object with _StandaloneSSLConnection to provide makefile method. We - cannot substitute OpenSSL.SSL.Connection.makefile since it's readonly - attribute. - """ - - accepted_socket, client_address = self.socket.accept() - - server_options = self.websocket_server_options - if server_options.use_tls: - if server_options.tls_module == _TLS_BY_STANDARD_MODULE: - try: - accepted_socket.do_handshake() - except ssl.SSLError as e: - self._logger.debug('%r', e) - raise - - # Print cipher in use. Handshake is done on accept. - self._logger.debug('Cipher: %s', accepted_socket.cipher()) - self._logger.debug('Client cert: %r', - accepted_socket.getpeercert()) - elif server_options.tls_module == _TLS_BY_PYOPENSSL: - # We cannot print the cipher in use. pyOpenSSL doesn't provide - # any method to fetch that. - - ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) - ctx.use_privatekey_file(server_options.private_key) - ctx.use_certificate_file(server_options.certificate) - - def default_callback(conn, cert, errnum, errdepth, ok): - return ok == 1 - - # See the OpenSSL document for SSL_CTX_set_verify. - if server_options.tls_client_auth: - verify_mode = OpenSSL.SSL.VERIFY_PEER - if not server_options.tls_client_cert_optional: - verify_mode |= OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT - ctx.set_verify(verify_mode, default_callback) - ctx.load_verify_locations(server_options.tls_client_ca, - None) - else: - ctx.set_verify(OpenSSL.SSL.VERIFY_NONE, default_callback) - - accepted_socket = OpenSSL.SSL.Connection(ctx, accepted_socket) - accepted_socket.set_accept_state() - - # Convert SSL related error into socket.error so that - # SocketServer ignores them and keeps running. - # - # TODO(tyoshino): Convert all kinds of errors. - try: - accepted_socket.do_handshake() - except OpenSSL.SSL.Error as e: - # Set errno part to 1 (SSL_ERROR_SSL) like the ssl module - # does. - self._logger.debug('%r', e) - raise socket.error(1, '%r' % e) - cert = accepted_socket.get_peer_certificate() - if cert is not None: - self._logger.debug('Client cert subject: %r', - cert.get_subject().get_components()) - accepted_socket = _StandaloneSSLConnection(accepted_socket) - else: - raise ValueError('No TLS support module is available') - - return accepted_socket, client_address - - def serve_forever(self, poll_interval=0.5): - """Override socketserver.BaseServer.serve_forever.""" - - self.__ws_serving = True - self.__ws_is_shut_down.clear() - handle_request = self.handle_request - if hasattr(self, '_handle_request_noblock'): - handle_request = self._handle_request_noblock - else: - self._logger.warning('Fallback to blocking request handler') - try: - while self.__ws_serving: - r, w, e = select.select( - [socket_[0] for socket_ in self._sockets], - [], [], poll_interval) - for socket_ in r: - self.socket = socket_ - handle_request() - self.socket = None - finally: - self.__ws_is_shut_down.set() - - def shutdown(self): - """Override socketserver.BaseServer.shutdown.""" - - self.__ws_serving = False - self.__ws_is_shut_down.wait() - - -class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler): - """CGIHTTPRequestHandler specialized for WebSocket.""" - - # Use http_client.HTTPMessage instead of mimetools.Message. - MessageClass = http_client.HTTPMessage - - protocol_version = "HTTP/1.1" - - def setup(self): - """Override socketserver.StreamRequestHandler.setup to wrap rfile - with MemorizingFile. - - This method will be called by BaseRequestHandler's constructor - before calling BaseHTTPRequestHandler.handle. - BaseHTTPRequestHandler.handle will call - BaseHTTPRequestHandler.handle_one_request and it will call - WebSocketRequestHandler.parse_request. - """ - - # Call superclass's setup to prepare rfile, wfile, etc. See setup - # definition on the root class socketserver.StreamRequestHandler to - # understand what this does. - CGIHTTPServer.CGIHTTPRequestHandler.setup(self) - - self.rfile = memorizingfile.MemorizingFile( - self.rfile, - max_memorized_lines=_MAX_MEMORIZED_LINES) - - def __init__(self, request, client_address, server): - self._logger = util.get_class_logger(self) - - self._options = server.websocket_server_options - - # Overrides CGIHTTPServerRequestHandler.cgi_directories. - self.cgi_directories = self._options.cgi_directories - # Replace CGIHTTPRequestHandler.is_executable method. - if self._options.is_executable_method is not None: - self.is_executable = self._options.is_executable_method - - # This actually calls BaseRequestHandler.__init__. - CGIHTTPServer.CGIHTTPRequestHandler.__init__( - self, request, client_address, server) - - def parse_request(self): - """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request. - - Return True to continue processing for HTTP(S), False otherwise. - - See BaseHTTPRequestHandler.handle_one_request method which calls - this method to understand how the return value will be handled. - """ - - # We hook parse_request method, but also call the original - # CGIHTTPRequestHandler.parse_request since when we return False, - # CGIHTTPRequestHandler.handle_one_request continues processing and - # it needs variables set by CGIHTTPRequestHandler.parse_request. - # - # Variables set by this method will be also used by WebSocket request - # handling (self.path, self.command, self.requestline, etc. See also - # how _StandaloneRequest's members are implemented using these - # attributes). - if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self): - return False - - if self.command == "CONNECT": - self.send_response(200, "Connected") - self.send_header("Connection", "keep-alive") - self.end_headers() - return False - - if self._options.use_basic_auth: - auth = self.headers.getheader('Authorization') - if auth != self._options.basic_auth_credential: - self.send_response(401) - self.send_header('WWW-Authenticate', - 'Basic realm="Pywebsocket"') - self.end_headers() - self._logger.info('Request basic authentication') - return False - - # Special paths for XMLHttpRequest benchmark - xhr_benchmark_helper_prefix = '/073be001e10950692ccbf3a2ad21c245' - parsed_path = urllib.parse.urlsplit(self.path) - if parsed_path.path == (xhr_benchmark_helper_prefix + '_send'): - xhr_benchmark_handler = XHRBenchmarkHandler( - self.headers, self.rfile, self.wfile) - xhr_benchmark_handler.do_send() - return False - if parsed_path.path == (xhr_benchmark_helper_prefix + '_receive'): - xhr_benchmark_handler = XHRBenchmarkHandler( - self.headers, self.rfile, self.wfile) - xhr_benchmark_handler.do_receive_and_parse() - return False - if parsed_path.path == (xhr_benchmark_helper_prefix + - '_receive_getnocache'): - xhr_benchmark_handler = XHRBenchmarkHandler( - self.headers, self.rfile, self.wfile) - xhr_benchmark_handler.do_receive( - int(parsed_path.query), False, False) - return False - if parsed_path.path == (xhr_benchmark_helper_prefix + - '_receive_getcache'): - xhr_benchmark_handler = XHRBenchmarkHandler( - self.headers, self.rfile, self.wfile) - xhr_benchmark_handler.do_receive( - int(parsed_path.query), False, True) - return False - - host, port, resource = http_header_util.parse_uri(self.path) - if resource is None: - self._logger.info('Invalid URI: %r', self.path) - self._logger.info('Fallback to CGIHTTPRequestHandler') - return True - server_options = self.server.websocket_server_options - if host is not None: - validation_host = server_options.validation_host - if validation_host is not None and host != validation_host: - self._logger.info('Invalid host: %r (expected: %r)', - host, - validation_host) - self._logger.info('Fallback to CGIHTTPRequestHandler') - return True - if port is not None: - validation_port = server_options.validation_port - if validation_port is not None and port != validation_port: - self._logger.info('Invalid port: %r (expected: %r)', - port, - validation_port) - self._logger.info('Fallback to CGIHTTPRequestHandler') - return True - self.path = resource - - request = _StandaloneRequest(self, self._options.use_tls) - - try: - # Fallback to default http handler for request paths for which - # we don't have request handlers. - if not self._options.dispatcher.get_handler_suite(self.path): - self._logger.info('No handler for resource: %r', - self.path) - self._logger.info('Fallback to CGIHTTPRequestHandler') - return True - except dispatch.DispatchException as e: - self._logger.info('Dispatch failed for error: %s', e) - self.send_error(e.status) - return False - - # If any Exceptions without except clause setup (including - # DispatchException) is raised below this point, it will be caught - # and logged by WebSocketServer. - - try: - try: - handshake.do_handshake( - request, - self._options.dispatcher, - allowDraft75=self._options.allow_draft75, - strict=self._options.strict) - except handshake.VersionException as e: - self._logger.info('Handshake failed for version error: %s', e) - self.send_response(common.HTTP_STATUS_BAD_REQUEST) - self.send_header(common.SEC_WEBSOCKET_VERSION_HEADER, - e.supported_versions) - self.end_headers() - return False - except handshake.HandshakeException as e: - # Handshake for ws(s) failed. - self._logger.info('Handshake failed for error: %s', e) - self.send_error(e.status) - return False - - request._dispatcher = self._options.dispatcher - self._options.dispatcher.transfer_data(request) - except handshake.AbortedByUserException as e: - self._logger.info('Aborted: %s', e) - return False - - def log_request(self, code='-', size='-'): - """Override BaseHTTPServer.log_request.""" - - self._logger.info('"%s" %s %s', - self.requestline, str(code), str(size)) - - def log_error(self, *args): - """Override BaseHTTPServer.log_error.""" - - # Despite the name, this method is for warnings than for errors. - # For example, HTTP status code is logged by this method. - self._logger.warning('%s - %s', - self.address_string(), - args[0] % args[1:]) - - def is_cgi(self): - """Test whether self.path corresponds to a CGI script. - - Add extra check that self.path doesn't contains .. - Also check if the file is a executable file or not. - If the file is not executable, it is handled as static file or dir - rather than a CGI script. - """ - - if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self): - if '..' in self.path: - return False - # strip query parameter from request path - resource_name = self.path.split('?', 2)[0] - # convert resource_name into real path name in filesystem. - scriptfile = self.translate_path(resource_name) - if not os.path.isfile(scriptfile): - return False - if not self.is_executable(scriptfile): - return False - return True - return False - - -def _get_logger_from_class(c): - return logging.getLogger('%s.%s' % (c.__module__, c.__name__)) - - -def _configure_logging(options): - logging.addLevelName(common.LOGLEVEL_FINE, 'FINE') - - logger = logging.getLogger() - logger.setLevel(logging.getLevelName(options.log_level.upper())) - if options.log_file: - handler = logging.handlers.RotatingFileHandler( - options.log_file, 'a', options.log_max, options.log_count) - else: - handler = logging.StreamHandler() - formatter = logging.Formatter( - '[%(asctime)s] [%(levelname)s] %(name)s: %(message)s') - handler.setFormatter(formatter) - logger.addHandler(handler) - - deflate_log_level_name = logging.getLevelName( - options.deflate_log_level.upper()) - _get_logger_from_class(util._Deflater).setLevel( - deflate_log_level_name) - _get_logger_from_class(util._Inflater).setLevel( - deflate_log_level_name) - - -def _build_option_parser(): - parser = optparse.OptionParser() - - parser.add_option('--config', dest='config_file', type='string', - default=None, - help=('Path to configuration file. See the file comment ' - 'at the top of this file for the configuration ' - 'file format')) - parser.add_option('-H', '--server-host', '--server_host', - dest='server_host', - default='', - help='server hostname to listen to') - parser.add_option('-V', '--validation-host', '--validation_host', - dest='validation_host', - default=None, - help='server hostname to validate in absolute path.') - parser.add_option('-p', '--port', dest='port', type='int', - default=common.DEFAULT_WEB_SOCKET_PORT, - help='port to listen to') - parser.add_option('-P', '--validation-port', '--validation_port', - dest='validation_port', type='int', - default=None, - help='server port to validate in absolute path.') - parser.add_option('-w', '--websock-handlers', '--websock_handlers', - dest='websock_handlers', - default='.', - help=('The root directory of WebSocket handler files. ' - 'If the path is relative, --document-root is used ' - 'as the base.')) - parser.add_option('-m', '--websock-handlers-map-file', - '--websock_handlers_map_file', - dest='websock_handlers_map_file', - default=None, - help=('WebSocket handlers map file. ' - 'Each line consists of alias_resource_path and ' - 'existing_resource_path, separated by spaces.')) - parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir', - default=None, - help=('Must be a directory under --websock-handlers. ' - 'Only handlers under this directory are scanned ' - 'and registered to the server. ' - 'Useful for saving scan time when the handler ' - 'root directory contains lots of files that are ' - 'not handler file or are handler files but you ' - 'don\'t want them to be registered. ')) - parser.add_option('--allow-handlers-outside-root-dir', - '--allow_handlers_outside_root_dir', - dest='allow_handlers_outside_root_dir', - action='store_true', - default=False, - help=('Scans WebSocket handlers even if their canonical ' - 'path is not under --websock-handlers.')) - parser.add_option('-d', '--document-root', '--document_root', - dest='document_root', default='.', - help='Document root directory.') - parser.add_option('-x', '--cgi-paths', '--cgi_paths', dest='cgi_paths', - default=None, - help=('CGI paths relative to document_root.' - 'Comma-separated. (e.g -x /cgi,/htbin) ' - 'Files under document_root/cgi_path are handled ' - 'as CGI programs. Must be executable.')) - parser.add_option('-t', '--tls', dest='use_tls', action='store_true', - default=False, help='use TLS (wss://)') - parser.add_option('--tls-module', '--tls_module', dest='tls_module', - type='choice', - choices = [_TLS_BY_STANDARD_MODULE, _TLS_BY_PYOPENSSL], - help='Use ssl module if "%s" is specified. ' - 'Use pyOpenSSL module if "%s" is specified' % - (_TLS_BY_STANDARD_MODULE, _TLS_BY_PYOPENSSL)) - parser.add_option('-k', '--private-key', '--private_key', - dest='private_key', - default='', help='TLS private key file.') - parser.add_option('-c', '--certificate', dest='certificate', - default='', help='TLS certificate file.') - parser.add_option('--tls-client-auth', dest='tls_client_auth', - action='store_true', default=False, - help='Requests TLS client auth on every connection.') - parser.add_option('--tls-client-cert-optional', - dest='tls_client_cert_optional', - action='store_true', default=False, - help=('Makes client certificate optional even though ' - 'TLS client auth is enabled.')) - parser.add_option('--tls-client-ca', dest='tls_client_ca', default='', - help=('Specifies a pem file which contains a set of ' - 'concatenated CA certificates which are used to ' - 'validate certificates passed from clients')) - parser.add_option('--basic-auth', dest='use_basic_auth', - action='store_true', default=False, - help='Requires Basic authentication.') - parser.add_option('--basic-auth-credential', - dest='basic_auth_credential', default='test:test', - help='Specifies the credential of basic authentication ' - 'by username:password pair (e.g. test:test).') - parser.add_option('-l', '--log-file', '--log_file', dest='log_file', - default='', help='Log file.') - # Custom log level: - # - FINE: Prints status of each frame processing step - parser.add_option('--log-level', '--log_level', type='choice', - dest='log_level', default='warn', - choices=['fine', - 'debug', 'info', 'warning', 'warn', 'error', - 'critical'], - help='Log level.') - parser.add_option('--deflate-log-level', '--deflate_log_level', - type='choice', - dest='deflate_log_level', default='warn', - choices=['debug', 'info', 'warning', 'warn', 'error', - 'critical'], - help='Log level for _Deflater and _Inflater.') - parser.add_option('--thread-monitor-interval-in-sec', - '--thread_monitor_interval_in_sec', - dest='thread_monitor_interval_in_sec', - type='int', default=-1, - help=('If positive integer is specified, run a thread ' - 'monitor to show the status of server threads ' - 'periodically in the specified inteval in ' - 'second. If non-positive integer is specified, ' - 'disable the thread monitor.')) - parser.add_option('--log-max', '--log_max', dest='log_max', type='int', - default=_DEFAULT_LOG_MAX_BYTES, - help='Log maximum bytes') - parser.add_option('--log-count', '--log_count', dest='log_count', - type='int', default=_DEFAULT_LOG_BACKUP_COUNT, - help='Log backup count') - parser.add_option('--allow-draft75', dest='allow_draft75', - action='store_true', default=False, - help='Obsolete option. Ignored.') - parser.add_option('--strict', dest='strict', action='store_true', - default=False, help='Obsolete option. Ignored.') - parser.add_option('-q', '--queue', dest='request_queue_size', type='int', - default=_DEFAULT_REQUEST_QUEUE_SIZE, - help='request queue size') - - return parser - - -class ThreadMonitor(threading.Thread): - daemon = True - - def __init__(self, interval_in_sec): - threading.Thread.__init__(self, name='ThreadMonitor') - - self._logger = util.get_class_logger(self) - - self._interval_in_sec = interval_in_sec - - def run(self): - while True: - thread_name_list = [] - for thread in threading.enumerate(): - thread_name_list.append(thread.name) - self._logger.info( - "%d active threads: %s", - threading.active_count(), - ', '.join(thread_name_list)) - time.sleep(self._interval_in_sec) - - -def _parse_args_and_config(args): - parser = _build_option_parser() - - # First, parse options without configuration file. - temporary_options, temporary_args = parser.parse_args(args=args) - if temporary_args: - logging.critical( - 'Unrecognized positional arguments: %r', temporary_args) - sys.exit(1) - - if temporary_options.config_file: - try: - config_fp = open(temporary_options.config_file, 'r') - except IOError as e: - logging.critical( - 'Failed to open configuration file %r: %r', - temporary_options.config_file, - e) - sys.exit(1) - - config_parser = configparser.SafeConfigParser() - config_parser.readfp(config_fp) - config_fp.close() - - args_from_config = [] - for name, value in config_parser.items('pywebsocket'): - args_from_config.append('--' + name) - args_from_config.append(value) - if args is None: - args = args_from_config - else: - args = args_from_config + args - return parser.parse_args(args=args) - else: - return temporary_options, temporary_args - - -def _main(args=None): - """You can call this function from your own program, but please note that - this function has some side-effects that might affect your program. For - example, util.wrap_popen3_for_win use in this method replaces implementation - of os.popen3. - """ - - options, args = _parse_args_and_config(args=args) - - os.chdir(options.document_root) - - _configure_logging(options) - - if options.allow_draft75: - logging.warning('--allow_draft75 option is obsolete.') - - if options.strict: - logging.warning('--strict option is obsolete.') - - # TODO(tyoshino): Clean up initialization of CGI related values. Move some - # of code here to WebSocketRequestHandler class if it's better. - options.cgi_directories = [] - options.is_executable_method = None - if options.cgi_paths: - options.cgi_directories = options.cgi_paths.split(',') - if sys.platform in ('cygwin', 'win32'): - cygwin_path = None - # For Win32 Python, it is expected that CYGWIN_PATH - # is set to a directory of cygwin binaries. - # For example, websocket_server.py in Chromium sets CYGWIN_PATH to - # full path of third_party/cygwin/bin. - if 'CYGWIN_PATH' in os.environ: - cygwin_path = os.environ['CYGWIN_PATH'] - util.wrap_popen3_for_win(cygwin_path) - - def __check_script(scriptpath): - return util.get_script_interp(scriptpath, cygwin_path) - - options.is_executable_method = __check_script - - if options.use_tls: - if options.tls_module is None: - if _import_ssl(): - options.tls_module = _TLS_BY_STANDARD_MODULE - logging.debug('Using ssl module') - elif _import_pyopenssl(): - options.tls_module = _TLS_BY_PYOPENSSL - logging.debug('Using pyOpenSSL module') - else: - logging.critical( - 'TLS support requires ssl or pyOpenSSL module.') - sys.exit(1) - elif options.tls_module == _TLS_BY_STANDARD_MODULE: - if not _import_ssl(): - logging.critical('ssl module is not available') - sys.exit(1) - elif options.tls_module == _TLS_BY_PYOPENSSL: - if not _import_pyopenssl(): - logging.critical('pyOpenSSL module is not available') - sys.exit(1) - else: - logging.critical('Invalid --tls-module option: %r', - options.tls_module) - sys.exit(1) - - if not options.private_key or not options.certificate: - logging.critical( - 'To use TLS, specify private_key and certificate.') - sys.exit(1) - - if (options.tls_client_cert_optional and - not options.tls_client_auth): - logging.critical('Client authentication must be enabled to ' - 'specify tls_client_cert_optional') - sys.exit(1) - else: - if options.tls_module is not None: - logging.critical('Use --tls-module option only together with ' - '--use-tls option.') - sys.exit(1) - - if options.tls_client_auth: - logging.critical('TLS must be enabled for client authentication.') - sys.exit(1) - - if options.tls_client_cert_optional: - logging.critical('TLS must be enabled for client authentication.') - sys.exit(1) - - if not options.scan_dir: - options.scan_dir = options.websock_handlers - - if options.use_basic_auth: - options.basic_auth_credential = 'Basic ' + base64.b64encode( - options.basic_auth_credential) - - try: - if options.thread_monitor_interval_in_sec > 0: - # Run a thread monitor to show the status of server threads for - # debugging. - ThreadMonitor(options.thread_monitor_interval_in_sec).start() - - server = WebSocketServer(options) - server.serve_forever() - except Exception as e: - logging.critical('mod_pywebsocket: %s' % e) - logging.critical('mod_pywebsocket: %s' % util.get_stack_trace()) - sys.exit(1) - - -if __name__ == '__main__': - _main(sys.argv[1:]) - - -# vi:sts=4 sw=4 et diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/stream.py b/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/stream.py deleted file mode 100644 index a7d3136ac9b..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/stream.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""This file exports public symbols.""" - - -from mod_pywebsocket._stream_base import BadOperationException -from mod_pywebsocket._stream_base import ConnectionTerminatedException -from mod_pywebsocket._stream_base import InvalidFrameException -from mod_pywebsocket._stream_base import InvalidUTF8Exception -from mod_pywebsocket._stream_base import UnsupportedFrameException -from mod_pywebsocket._stream_hixie75 import StreamHixie75 -from mod_pywebsocket._stream_hybi import Frame -from mod_pywebsocket._stream_hybi import Stream -from mod_pywebsocket._stream_hybi import StreamOptions - -# These methods are intended to be used by WebSocket client developers to have -# their implementations receive broken data in tests. -from mod_pywebsocket._stream_hybi import create_close_frame -from mod_pywebsocket._stream_hybi import create_header -from mod_pywebsocket._stream_hybi import create_length_header -from mod_pywebsocket._stream_hybi import create_ping_frame -from mod_pywebsocket._stream_hybi import create_pong_frame -from mod_pywebsocket._stream_hybi import create_binary_frame -from mod_pywebsocket._stream_hybi import create_text_frame -from mod_pywebsocket._stream_hybi import create_closing_handshake_body - - -# vi:sts=4 sw=4 et diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/xhr_benchmark_handler.py b/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/xhr_benchmark_handler.py deleted file mode 100644 index 43a5b049220..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/xhr_benchmark_handler.py +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright 2014 Google Inc. All rights reserved. -# -# Use of this source code is governed by a BSD-style -# license that can be found in the COPYING file or at -# https://developers.google.com/open-source/licenses/bsd - - -from mod_pywebsocket import util - - -class XHRBenchmarkHandler(object): - def __init__(self, headers, rfile, wfile): - self._logger = util.get_class_logger(self) - - self.headers = headers - self.rfile = rfile - self.wfile = wfile - - def do_send(self): - content_length = int(self.headers.getheader('Content-Length')) - - self._logger.debug('Requested to receive %s bytes', content_length) - - RECEIVE_BLOCK_SIZE = 1024 * 1024 - - bytes_to_receive = content_length - while bytes_to_receive > 0: - bytes_to_receive_in_this_loop = bytes_to_receive - if bytes_to_receive_in_this_loop > RECEIVE_BLOCK_SIZE: - bytes_to_receive_in_this_loop = RECEIVE_BLOCK_SIZE - received_data = self.rfile.read(bytes_to_receive_in_this_loop) - if received_data != ('a' * bytes_to_receive_in_this_loop): - self._logger.debug('Request body verification failed') - return - bytes_to_receive -= len(received_data) - if bytes_to_receive < 0: - self._logger.debug('Received %d more bytes than expected' % - (-bytes_to_receive)) - return - - # Return the number of received bytes back to the client. - response_body = '%d' % content_length - self.wfile.write( - 'HTTP/1.1 200 OK\r\n' - 'Access-Control-Allow-Origin: *\r\n' - 'Content-Type: text/html\r\n' - 'Content-Length: %d\r\n' - '\r\n%s' % (len(response_body), response_body)) - self.wfile.flush() - - def do_receive_and_parse(self): - content_length = int(self.headers.getheader('Content-Length')) - request_body = self.rfile.read(content_length) - - request_array = request_body.split(' ') - if len(request_array) < 2: - self._logger.debug('Malformed request body: %r', request_body) - return - - # Parse the size parameter. - bytes_to_send = request_array[0] - try: - bytes_to_send = int(bytes_to_send) - except ValueError as e: - self._logger.debug('Malformed size parameter: %r', bytes_to_send) - return - - # Parse the transfer encoding parameter. - chunked_mode = False - mode_parameter = request_array[1] - if mode_parameter == 'chunked': - self._logger.debug('Requested chunked transfer encoding') - chunked_mode = True - elif mode_parameter != 'none': - self._logger.debug('Invalid mode parameter: %r', mode_parameter) - return - - self.do_receive(bytes_to_send, chunked_mode, False) - - def do_receive(self, bytes_to_send, chunked_mode, enable_cache): - self._logger.debug( - 'Requested to send %s bytes (chunked: %s, cache: %s)', - bytes_to_send, chunked_mode, enable_cache) - # Write a header - response_header = ( - 'HTTP/1.1 200 OK\r\n' - 'Access-Control-Allow-Origin: *\r\n' - 'Content-Type: application/octet-stream\r\n') - if enable_cache: - response_header += 'Cache-Control: private, max-age=10\r\n' - else: - response_header += \ - 'Cache-Control: no-cache, no-store, must-revalidate\r\n' - if chunked_mode: - response_header += 'Transfer-Encoding: chunked\r\n\r\n' - else: - response_header += ( - 'Content-Length: %d\r\n\r\n' % bytes_to_send) - self.wfile.write(response_header) - self.wfile.flush() - - # Write a body - SEND_BLOCK_SIZE = 1024 * 1024 - - while bytes_to_send > 0: - bytes_to_send_in_this_loop = bytes_to_send - if bytes_to_send_in_this_loop > SEND_BLOCK_SIZE: - bytes_to_send_in_this_loop = SEND_BLOCK_SIZE - - if chunked_mode: - self.wfile.write('%x\r\n' % bytes_to_send_in_this_loop) - self.wfile.write('a' * bytes_to_send_in_this_loop) - if chunked_mode: - self.wfile.write('\r\n') - self.wfile.flush() - - bytes_to_send -= bytes_to_send_in_this_loop - - if chunked_mode: - self.wfile.write('0\r\n\r\n') - self.wfile.flush() diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/test/client_for_testing.py b/tests/wpt/web-platform-tests/tools/pywebsocket/test/client_for_testing.py deleted file mode 100644 index 18d185712ee..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/test/client_for_testing.py +++ /dev/null @@ -1,1100 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""WebSocket client utility for testing. - -This module contains helper methods for performing handshake, frame -sending/receiving as a WebSocket client. - -This is code for testing mod_pywebsocket. Keep this code independent from -mod_pywebsocket. Don't import e.g. Stream class for generating frame for -testing. Using util.hexify, etc. that are not related to protocol processing -is allowed. - -Note: -This code is far from robust, e.g., we cut corners in handshake. -""" - - -import base64 -import errno -import logging -import os -import random -import re -import socket -import struct -import time - -from mod_pywebsocket import common -from mod_pywebsocket import util - - -DEFAULT_PORT = 80 -DEFAULT_SECURE_PORT = 443 - -# Opcodes introduced in IETF HyBi 01 for the new framing format -OPCODE_CONTINUATION = 0x0 -OPCODE_CLOSE = 0x8 -OPCODE_PING = 0x9 -OPCODE_PONG = 0xa -OPCODE_TEXT = 0x1 -OPCODE_BINARY = 0x2 - -# Strings used for handshake -_UPGRADE_HEADER = 'Upgrade: websocket\r\n' -_UPGRADE_HEADER_HIXIE75 = 'Upgrade: WebSocket\r\n' -_CONNECTION_HEADER = 'Connection: Upgrade\r\n' - -WEBSOCKET_ACCEPT_UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' - -# Status codes -STATUS_NORMAL_CLOSURE = 1000 -STATUS_GOING_AWAY = 1001 -STATUS_PROTOCOL_ERROR = 1002 -STATUS_UNSUPPORTED_DATA = 1003 -STATUS_NO_STATUS_RECEIVED = 1005 -STATUS_ABNORMAL_CLOSURE = 1006 -STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007 -STATUS_POLICY_VIOLATION = 1008 -STATUS_MESSAGE_TOO_BIG = 1009 -STATUS_MANDATORY_EXT = 1010 -STATUS_INTERNAL_ENDPOINT_ERROR = 1011 -STATUS_TLS_HANDSHAKE = 1015 - -# Extension tokens -_DEFLATE_FRAME_EXTENSION = 'deflate-frame' -# TODO(bashi): Update after mux implementation finished. -_MUX_EXTENSION = 'mux_DO_NOT_USE' -_PERMESSAGE_DEFLATE_EXTENSION = 'permessage-deflate' - -def _method_line(resource): - return 'GET %s HTTP/1.1\r\n' % resource - - -def _sec_origin_header(origin): - return 'Sec-WebSocket-Origin: %s\r\n' % origin.lower() - - -def _origin_header(origin): - # 4.1 13. concatenation of the string "Origin:", a U+0020 SPACE character, - # and the /origin/ value, converted to ASCII lowercase, to /fields/. - return 'Origin: %s\r\n' % origin.lower() - - -def _format_host_header(host, port, secure): - # 4.1 9. Let /hostport/ be an empty string. - # 4.1 10. Append the /host/ value, converted to ASCII lowercase, to - # /hostport/ - hostport = host.lower() - # 4.1 11. If /secure/ is false, and /port/ is not 80, or if /secure/ - # is true, and /port/ is not 443, then append a U+003A COLON character - # (:) followed by the value of /port/, expressed as a base-ten integer, - # to /hostport/ - if ((not secure and port != DEFAULT_PORT) or - (secure and port != DEFAULT_SECURE_PORT)): - hostport += ':' + str(port) - # 4.1 12. concatenation of the string "Host:", a U+0020 SPACE - # character, and /hostport/, to /fields/. - return 'Host: %s\r\n' % hostport - - -# TODO(tyoshino): Define a base class and move these shared methods to that. - - -def receive_bytes(socket, length): - received_bytes = [] - remaining = length - while remaining > 0: - new_received_bytes = socket.recv(remaining) - if not new_received_bytes: - raise Exception( - 'Connection closed before receiving requested length ' - '(requested %d bytes but received only %d bytes)' % - (length, length - remaining)) - received_bytes.append(new_received_bytes) - remaining -= len(new_received_bytes) - return ''.join(received_bytes) - - -# TODO(tyoshino): Now the WebSocketHandshake class diverts these methods. We -# should move to HTTP parser as specified in RFC 6455. For HyBi 00 and -# Hixie 75, pack these methods as some parser class. - - -def _read_fields(socket): - # 4.1 32. let /fields/ be a list of name-value pairs, initially empty. - fields = {} - while True: - # 4.1 33. let /name/ and /value/ be empty byte arrays - name = '' - value = '' - # 4.1 34. read /name/ - name = _read_name(socket) - if name is None: - break - # 4.1 35. read spaces - # TODO(tyoshino): Skip only one space as described in the spec. - ch = _skip_spaces(socket) - # 4.1 36. read /value/ - value = _read_value(socket, ch) - # 4.1 37. read a byte from the server - ch = receive_bytes(socket, 1) - if ch != '\n': # 0x0A - raise Exception( - 'Expected LF but found %r while reading value %r for header ' - '%r' % (ch, name, value)) - # 4.1 38. append an entry to the /fields/ list that has the name - # given by the string obtained by interpreting the /name/ byte - # array as a UTF-8 stream and the value given by the string - # obtained by interpreting the /value/ byte array as a UTF-8 byte - # stream. - fields.setdefault(name, []).append(value) - # 4.1 39. return to the "Field" step above - return fields - - -def _read_name(socket): - # 4.1 33. let /name/ be empty byte arrays - name = '' - while True: - # 4.1 34. read a byte from the server - ch = receive_bytes(socket, 1) - if ch == '\r': # 0x0D - return None - elif ch == '\n': # 0x0A - raise Exception( - 'Unexpected LF when reading header name %r' % name) - elif ch == ':': # 0x3A - return name - elif ch >= 'A' and ch <= 'Z': # range 0x31 to 0x5A - ch = chr(ord(ch) + 0x20) - name += ch - else: - name += ch - - -def _skip_spaces(socket): - # 4.1 35. read a byte from the server - while True: - ch = receive_bytes(socket, 1) - if ch == ' ': # 0x20 - continue - return ch - - -def _read_value(socket, ch): - # 4.1 33. let /value/ be empty byte arrays - value = '' - # 4.1 36. read a byte from server. - while True: - if ch == '\r': # 0x0D - return value - elif ch == '\n': # 0x0A - raise Exception( - 'Unexpected LF when reading header value %r' % value) - else: - value += ch - ch = receive_bytes(socket, 1) - - -def read_frame_header(socket): - received = receive_bytes(socket, 2) - - first_byte = ord(received[0]) - fin = (first_byte >> 7) & 1 - rsv1 = (first_byte >> 6) & 1 - rsv2 = (first_byte >> 5) & 1 - rsv3 = (first_byte >> 4) & 1 - opcode = first_byte & 0xf - - second_byte = ord(received[1]) - mask = (second_byte >> 7) & 1 - payload_length = second_byte & 0x7f - - if mask != 0: - raise Exception( - 'Mask bit must be 0 for frames coming from server') - - if payload_length == 127: - extended_payload_length = receive_bytes(socket, 8) - payload_length = struct.unpack( - '!Q', extended_payload_length)[0] - if payload_length > 0x7FFFFFFFFFFFFFFF: - raise Exception('Extended payload length >= 2^63') - elif payload_length == 126: - extended_payload_length = receive_bytes(socket, 2) - payload_length = struct.unpack( - '!H', extended_payload_length)[0] - - return fin, rsv1, rsv2, rsv3, opcode, payload_length - - -class _TLSSocket(object): - """Wrapper for a TLS connection.""" - - def __init__(self, raw_socket): - self._ssl = socket.ssl(raw_socket) - - def send(self, bytes): - return self._ssl.write(bytes) - - def recv(self, size=-1): - return self._ssl.read(size) - - def close(self): - # Nothing to do. - pass - - -class HttpStatusException(Exception): - """This exception will be raised when unexpected http status code was - received as a result of handshake. - """ - - def __init__(self, name, status): - super(HttpStatusException, self).__init__(name) - self.status = status - - -class WebSocketHandshake(object): - """Opening handshake processor for the WebSocket protocol (RFC 6455).""" - - def __init__(self, options): - self._logger = util.get_class_logger(self) - - self._options = options - - def handshake(self, socket): - """Handshake WebSocket. - - Raises: - Exception: handshake failed. - """ - - self._socket = socket - - request_line = _method_line(self._options.resource) - self._logger.debug('Opening handshake Request-Line: %r', request_line) - self._socket.sendall(request_line) - - fields = [] - fields.append(_UPGRADE_HEADER) - fields.append(_CONNECTION_HEADER) - - fields.append(_format_host_header( - self._options.server_host, - self._options.server_port, - self._options.use_tls)) - - if self._options.version == 8: - fields.append(_sec_origin_header(self._options.origin)) - else: - fields.append(_origin_header(self._options.origin)) - - original_key = os.urandom(16) - key = base64.b64encode(original_key) - self._logger.debug( - 'Sec-WebSocket-Key: %s (%s)', key, util.hexify(original_key)) - fields.append('Sec-WebSocket-Key: %s\r\n' % key) - - fields.append('Sec-WebSocket-Version: %d\r\n' % self._options.version) - - # Setting up extensions. - if len(self._options.extensions) > 0: - fields.append('Sec-WebSocket-Extensions: %s\r\n' % - ', '.join(self._options.extensions)) - - self._logger.debug('Opening handshake request headers: %r', fields) - - for field in fields: - self._socket.sendall(field) - self._socket.sendall('\r\n') - - self._logger.info('Sent opening handshake request') - - field = '' - while True: - ch = receive_bytes(self._socket, 1) - field += ch - if ch == '\n': - break - - self._logger.debug('Opening handshake Response-Line: %r', field) - - if len(field) < 7 or not field.endswith('\r\n'): - raise Exception('Wrong status line: %r' % field) - m = re.match('[^ ]* ([^ ]*) .*', field) - if m is None: - raise Exception( - 'No HTTP status code found in status line: %r' % field) - code = m.group(1) - if not re.match('[0-9][0-9][0-9]', code): - raise Exception( - 'HTTP status code %r is not three digit in status line: %r' % - (code, field)) - if code != '101': - raise HttpStatusException( - 'Expected HTTP status code 101 but found %r in status line: ' - '%r' % (code, field), int(code)) - fields = _read_fields(self._socket) - ch = receive_bytes(self._socket, 1) - if ch != '\n': # 0x0A - raise Exception('Expected LF but found: %r' % ch) - - self._logger.debug('Opening handshake response headers: %r', fields) - - # Check /fields/ - if len(fields['upgrade']) != 1: - raise Exception( - 'Multiple Upgrade headers found: %s' % fields['upgrade']) - if len(fields['connection']) != 1: - raise Exception( - 'Multiple Connection headers found: %s' % fields['connection']) - if fields['upgrade'][0] != 'websocket': - raise Exception( - 'Unexpected Upgrade header value: %s' % fields['upgrade'][0]) - if fields['connection'][0].lower() != 'upgrade': - raise Exception( - 'Unexpected Connection header value: %s' % - fields['connection'][0]) - - if len(fields['sec-websocket-accept']) != 1: - raise Exception( - 'Multiple Sec-WebSocket-Accept headers found: %s' % - fields['sec-websocket-accept']) - - accept = fields['sec-websocket-accept'][0] - - # Validate - try: - decoded_accept = base64.b64decode(accept) - except TypeError as e: - raise HandshakeException( - 'Illegal value for header Sec-WebSocket-Accept: ' + accept) - - if len(decoded_accept) != 20: - raise HandshakeException( - 'Decoded value of Sec-WebSocket-Accept is not 20-byte long') - - self._logger.debug('Actual Sec-WebSocket-Accept: %r (%s)', - accept, util.hexify(decoded_accept)) - - original_expected_accept = util.sha1_hash( - key + WEBSOCKET_ACCEPT_UUID).digest() - expected_accept = base64.b64encode(original_expected_accept) - - self._logger.debug('Expected Sec-WebSocket-Accept: %r (%s)', - expected_accept, - util.hexify(original_expected_accept)) - - if accept != expected_accept: - raise Exception( - 'Invalid Sec-WebSocket-Accept header: %r (expected) != %r ' - '(actual)' % (accept, expected_accept)) - - server_extensions_header = fields.get('sec-websocket-extensions') - accepted_extensions = [] - if server_extensions_header is not None: - accepted_extensions = common.parse_extensions( - ', '.join(server_extensions_header)) - - # Scan accepted extension list to check if there is any unrecognized - # extensions or extensions we didn't request in it. Then, for - # extensions we request, parse them and store parameters. They will be - # used later by each extension. - deflate_frame_accepted = False - mux_accepted = False - for extension in accepted_extensions: - if extension.name() == _DEFLATE_FRAME_EXTENSION: - if self._options.use_deflate_frame: - deflate_frame_accepted = True - continue - if extension.name() == _MUX_EXTENSION: - if self._options.use_mux: - mux_accepted = True - continue - if extension.name() == _PERMESSAGE_DEFLATE_EXTENSION: - checker = self._options.check_permessage_deflate - if checker: - checker(extension) - continue - - raise Exception( - 'Received unrecognized extension: %s' % extension.name()) - - # Let all extensions check the response for extension request. - - if (self._options.use_deflate_frame and - not deflate_frame_accepted): - raise Exception('%s extension not accepted' % - _DEFLATE_FRAME_EXTENSION) - - if self._options.use_mux and not mux_accepted: - raise Exception('%s extension not accepted' % _MUX_EXTENSION) - - -class WebSocketHybi00Handshake(object): - """Opening handshake processor for the WebSocket protocol version HyBi 00. - """ - - def __init__(self, options, draft_field): - self._logger = util.get_class_logger(self) - - self._options = options - self._draft_field = draft_field - - def handshake(self, socket): - """Handshake WebSocket. - - Raises: - Exception: handshake failed. - """ - - self._socket = socket - - # 4.1 5. send request line. - request_line = _method_line(self._options.resource) - self._logger.debug('Opening handshake Request-Line: %r', request_line) - self._socket.sendall(request_line) - # 4.1 6. Let /fields/ be an empty list of strings. - fields = [] - # 4.1 7. Add the string "Upgrade: WebSocket" to /fields/. - fields.append(_UPGRADE_HEADER_HIXIE75) - # 4.1 8. Add the string "Connection: Upgrade" to /fields/. - fields.append(_CONNECTION_HEADER) - # 4.1 9-12. Add Host: field to /fields/. - fields.append(_format_host_header( - self._options.server_host, - self._options.server_port, - self._options.use_tls)) - # 4.1 13. Add Origin: field to /fields/. - fields.append(_origin_header(self._options.origin)) - # TODO: 4.1 14 Add Sec-WebSocket-Protocol: field to /fields/. - # TODO: 4.1 15 Add cookie headers to /fields/. - - # 4.1 16-23. Add Sec-WebSocket-Key to /fields/. - self._number1, key1 = self._generate_sec_websocket_key() - self._logger.debug('Number1: %d', self._number1) - fields.append('Sec-WebSocket-Key1: %s\r\n' % key1) - self._number2, key2 = self._generate_sec_websocket_key() - self._logger.debug('Number2: %d', self._number1) - fields.append('Sec-WebSocket-Key2: %s\r\n' % key2) - - fields.append('Sec-WebSocket-Draft: %s\r\n' % self._draft_field) - - # 4.1 24. For each string in /fields/, in a random order: send the - # string, encoded as UTF-8, followed by a UTF-8 encoded U+000D CARRIAGE - # RETURN U+000A LINE FEED character pair (CRLF). - random.shuffle(fields) - - self._logger.debug('Opening handshake request headers: %r', fields) - for field in fields: - self._socket.sendall(field) - - # 4.1 25. send a UTF-8-encoded U+000D CARRIAGE RETURN U+000A LINE FEED - # character pair (CRLF). - self._socket.sendall('\r\n') - # 4.1 26. let /key3/ be a string consisting of eight random bytes (or - # equivalently, a random 64 bit integer encoded in a big-endian order). - self._key3 = self._generate_key3() - # 4.1 27. send /key3/ to the server. - self._socket.sendall(self._key3) - self._logger.debug( - 'Key3: %r (%s)', self._key3, util.hexify(self._key3)) - - self._logger.info('Sent opening handshake request') - - # 4.1 28. Read bytes from the server until either the connection - # closes, or a 0x0A byte is read. let /field/ be these bytes, including - # the 0x0A bytes. - field = '' - while True: - ch = receive_bytes(self._socket, 1) - field += ch - if ch == '\n': - break - - self._logger.debug('Opening handshake Response-Line: %r', field) - - # if /field/ is not at least seven bytes long, or if the last - # two bytes aren't 0x0D and 0x0A respectively, or if it does not - # contain at least two 0x20 bytes, then fail the WebSocket connection - # and abort these steps. - if len(field) < 7 or not field.endswith('\r\n'): - raise Exception('Wrong status line: %r' % field) - m = re.match('[^ ]* ([^ ]*) .*', field) - if m is None: - raise Exception('No code found in status line: %r' % field) - # 4.1 29. let /code/ be the substring of /field/ that starts from the - # byte after the first 0x20 byte, and ends with the byte before the - # second 0x20 byte. - code = m.group(1) - # 4.1 30. if /code/ is not three bytes long, or if any of the bytes in - # /code/ are not in the range 0x30 to 0x90, then fail the WebSocket - # connection and abort these steps. - if not re.match('[0-9][0-9][0-9]', code): - raise Exception( - 'HTTP status code %r is not three digit in status line: %r' % - (code, field)) - # 4.1 31. if /code/, interpreted as UTF-8, is "101", then move to the - # next step. - if code != '101': - raise HttpStatusException( - 'Expected HTTP status code 101 but found %r in status line: ' - '%r' % (code, field), int(code)) - # 4.1 32-39. read fields into /fields/ - fields = _read_fields(self._socket) - - self._logger.debug('Opening handshake response headers: %r', fields) - - # 4.1 40. _Fields processing_ - # read a byte from server - ch = receive_bytes(self._socket, 1) - if ch != '\n': # 0x0A - raise Exception('Expected LF but found %r' % ch) - # 4.1 41. check /fields/ - if len(fields['upgrade']) != 1: - raise Exception( - 'Multiple Upgrade headers found: %s' % fields['upgrade']) - if len(fields['connection']) != 1: - raise Exception( - 'Multiple Connection headers found: %s' % fields['connection']) - if len(fields['sec-websocket-origin']) != 1: - raise Exception( - 'Multiple Sec-WebSocket-Origin headers found: %s' % - fields['sec-sebsocket-origin']) - if len(fields['sec-websocket-location']) != 1: - raise Exception( - 'Multiple Sec-WebSocket-Location headers found: %s' % - fields['sec-sebsocket-location']) - # TODO(ukai): protocol - # if the entry's name is "upgrade" - # if the value is not exactly equal to the string "WebSocket", - # then fail the WebSocket connection and abort these steps. - if fields['upgrade'][0] != 'WebSocket': - raise Exception( - 'Unexpected Upgrade header value: %s' % fields['upgrade'][0]) - # if the entry's name is "connection" - # if the value, converted to ASCII lowercase, is not exactly equal - # to the string "upgrade", then fail the WebSocket connection and - # abort these steps. - if fields['connection'][0].lower() != 'upgrade': - raise Exception( - 'Unexpected Connection header value: %s' % - fields['connection'][0]) - # TODO(ukai): check origin, location, cookie, .. - - # 4.1 42. let /challenge/ be the concatenation of /number_1/, - # expressed as a big endian 32 bit integer, /number_2/, expressed - # as big endian 32 bit integer, and the eight bytes of /key_3/ in the - # order they were sent on the wire. - challenge = struct.pack('!I', self._number1) - challenge += struct.pack('!I', self._number2) - challenge += self._key3 - - self._logger.debug( - 'Challenge: %r (%s)', challenge, util.hexify(challenge)) - - # 4.1 43. let /expected/ be the MD5 fingerprint of /challenge/ as a - # big-endian 128 bit string. - expected = util.md5_hash(challenge).digest() - self._logger.debug( - 'Expected challenge response: %r (%s)', - expected, util.hexify(expected)) - - # 4.1 44. read sixteen bytes from the server. - # let /reply/ be those bytes. - reply = receive_bytes(self._socket, 16) - self._logger.debug( - 'Actual challenge response: %r (%s)', reply, util.hexify(reply)) - - # 4.1 45. if /reply/ does not exactly equal /expected/, then fail - # the WebSocket connection and abort these steps. - if expected != reply: - raise Exception( - 'Bad challenge response: %r (expected) != %r (actual)' % - (expected, reply)) - # 4.1 46. The *WebSocket connection is established*. - - def _generate_sec_websocket_key(self): - # 4.1 16. let /spaces_n/ be a random integer from 1 to 12 inclusive. - spaces = random.randint(1, 12) - # 4.1 17. let /max_n/ be the largest integer not greater than - # 4,294,967,295 divided by /spaces_n/. - maxnum = 4294967295 / spaces - # 4.1 18. let /number_n/ be a random integer from 0 to /max_n/ - # inclusive. - number = random.randint(0, maxnum) - # 4.1 19. let /product_n/ be the result of multiplying /number_n/ and - # /spaces_n/ together. - product = number * spaces - # 4.1 20. let /key_n/ be a string consisting of /product_n/, expressed - # in base ten using the numerals in the range U+0030 DIGIT ZERO (0) to - # U+0039 DIGIT NINE (9). - key = str(product) - # 4.1 21. insert between one and twelve random characters from the - # range U+0021 to U+002F and U+003A to U+007E into /key_n/ at random - # positions. - available_chars = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1) - n = random.randint(1, 12) - for _ in xrange(n): - ch = random.choice(available_chars) - pos = random.randint(0, len(key)) - key = key[0:pos] + chr(ch) + key[pos:] - # 4.1 22. insert /spaces_n/ U+0020 SPACE characters into /key_n/ at - # random positions other than start or end of the string. - for _ in xrange(spaces): - pos = random.randint(1, len(key) - 1) - key = key[0:pos] + ' ' + key[pos:] - return number, key - - def _generate_key3(self): - # 4.1 26. let /key3/ be a string consisting of eight random bytes (or - # equivalently, a random 64 bit integer encoded in a big-endian order). - return ''.join([chr(random.randint(0, 255)) for _ in xrange(8)]) - - -class WebSocketHixie75Handshake(object): - """WebSocket handshake processor for IETF Hixie 75.""" - - _EXPECTED_RESPONSE = ( - 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + - _UPGRADE_HEADER_HIXIE75 + - _CONNECTION_HEADER) - - def __init__(self, options): - self._logger = util.get_class_logger(self) - - self._options = options - - def _skip_headers(self): - terminator = '\r\n\r\n' - pos = 0 - while pos < len(terminator): - received = receive_bytes(self._socket, 1) - if received == terminator[pos]: - pos += 1 - elif received == terminator[0]: - pos = 1 - else: - pos = 0 - - def handshake(self, socket): - self._socket = socket - - request_line = _method_line(self._options.resource) - self._logger.debug('Opening handshake Request-Line: %r', request_line) - self._socket.sendall(request_line) - - headers = _UPGRADE_HEADER_HIXIE75 + _CONNECTION_HEADER - headers += _format_host_header( - self._options.server_host, - self._options.server_port, - self._options.use_tls) - headers += _origin_header(self._options.origin) - self._logger.debug('Opening handshake request headers: %r', headers) - self._socket.sendall(headers) - - self._socket.sendall('\r\n') - - self._logger.info('Sent opening handshake request') - - for expected_char in WebSocketHixie75Handshake._EXPECTED_RESPONSE: - received = receive_bytes(self._socket, 1) - if expected_char != received: - raise Exception('Handshake failure') - # We cut corners and skip other headers. - self._skip_headers() - - -class WebSocketStream(object): - """Frame processor for the WebSocket protocol (RFC 6455).""" - - def __init__(self, socket, handshake): - self._handshake = handshake - self._socket = socket - - # Filters applied to application data part of data frames. - self._outgoing_frame_filter = None - self._incoming_frame_filter = None - - if self._handshake._options.use_deflate_frame: - self._outgoing_frame_filter = ( - util._RFC1979Deflater(None, False)) - self._incoming_frame_filter = util._RFC1979Inflater() - - self._fragmented = False - - def _mask_hybi(self, s): - # TODO(tyoshino): os.urandom does open/read/close for every call. If - # performance matters, change this to some library call that generates - # cryptographically secure pseudo random number sequence. - masking_nonce = os.urandom(4) - result = [masking_nonce] - count = 0 - for c in s: - result.append(chr(ord(c) ^ ord(masking_nonce[count]))) - count = (count + 1) % len(masking_nonce) - return ''.join(result) - - def send_frame_of_arbitrary_bytes(self, header, body): - self._socket.sendall(header + self._mask_hybi(body)) - - def send_data(self, payload, frame_type, end=True, mask=True, - rsv1=0, rsv2=0, rsv3=0): - if self._outgoing_frame_filter is not None: - payload = self._outgoing_frame_filter.filter(payload) - - if self._fragmented: - opcode = OPCODE_CONTINUATION - else: - opcode = frame_type - - if end: - self._fragmented = False - fin = 1 - else: - self._fragmented = True - fin = 0 - - if self._handshake._options.use_deflate_frame: - rsv1 = 1 - - if mask: - mask_bit = 1 << 7 - else: - mask_bit = 0 - - header = chr(fin << 7 | rsv1 << 6 | rsv2 << 5 | rsv3 << 4 | opcode) - payload_length = len(payload) - if payload_length <= 125: - header += chr(mask_bit | payload_length) - elif payload_length < 1 << 16: - header += chr(mask_bit | 126) + struct.pack('!H', payload_length) - elif payload_length < 1 << 63: - header += chr(mask_bit | 127) + struct.pack('!Q', payload_length) - else: - raise Exception('Too long payload (%d byte)' % payload_length) - if mask: - payload = self._mask_hybi(payload) - self._socket.sendall(header + payload) - - def send_binary(self, payload, end=True, mask=True): - self.send_data(payload, OPCODE_BINARY, end, mask) - - def send_text(self, payload, end=True, mask=True): - self.send_data(payload.encode('utf-8'), OPCODE_TEXT, end, mask) - - def _assert_receive_data(self, payload, opcode, fin, rsv1, rsv2, rsv3): - (actual_fin, actual_rsv1, actual_rsv2, actual_rsv3, actual_opcode, - payload_length) = read_frame_header(self._socket) - - if actual_opcode != opcode: - raise Exception( - 'Unexpected opcode: %d (expected) vs %d (actual)' % - (opcode, actual_opcode)) - - if actual_fin != fin: - raise Exception( - 'Unexpected fin: %d (expected) vs %d (actual)' % - (fin, actual_fin)) - - if rsv1 is None: - rsv1 = 0 - if self._handshake._options.use_deflate_frame: - rsv1 = 1 - - if rsv2 is None: - rsv2 = 0 - - if rsv3 is None: - rsv3 = 0 - - if actual_rsv1 != rsv1: - raise Exception( - 'Unexpected rsv1: %r (expected) vs %r (actual)' % - (rsv1, actual_rsv1)) - - if actual_rsv2 != rsv2: - raise Exception( - 'Unexpected rsv2: %r (expected) vs %r (actual)' % - (rsv2, actual_rsv2)) - - if actual_rsv3 != rsv3: - raise Exception( - 'Unexpected rsv3: %r (expected) vs %r (actual)' % - (rsv3, actual_rsv3)) - - received = receive_bytes(self._socket, payload_length) - - if self._incoming_frame_filter is not None: - received = self._incoming_frame_filter.filter(received) - - if len(received) != len(payload): - raise Exception( - 'Unexpected payload length: %d (expected) vs %d (actual)' % - (len(payload), len(received))) - - if payload != received: - raise Exception( - 'Unexpected payload: %r (expected) vs %r (actual)' % - (payload, received)) - - def assert_receive_binary(self, payload, opcode=OPCODE_BINARY, fin=1, - rsv1=None, rsv2=None, rsv3=None): - self._assert_receive_data(payload, opcode, fin, rsv1, rsv2, rsv3) - - def assert_receive_text(self, payload, opcode=OPCODE_TEXT, fin=1, - rsv1=None, rsv2=None, rsv3=None): - self._assert_receive_data(payload.encode('utf-8'), opcode, fin, rsv1, - rsv2, rsv3) - - def _build_close_frame(self, code, reason, mask): - frame = chr(1 << 7 | OPCODE_CLOSE) - - if code is not None: - body = struct.pack('!H', code) + reason.encode('utf-8') - else: - body = '' - if mask: - frame += chr(1 << 7 | len(body)) + self._mask_hybi(body) - else: - frame += chr(len(body)) + body - return frame - - def send_close(self, code, reason): - self._socket.sendall( - self._build_close_frame(code, reason, True)) - - def assert_receive_close(self, code, reason): - expected_frame = self._build_close_frame(code, reason, False) - actual_frame = receive_bytes(self._socket, len(expected_frame)) - if actual_frame != expected_frame: - raise Exception( - 'Unexpected close frame: %r (expected) vs %r (actual)' % - (expected_frame, actual_frame)) - - -class WebSocketStreamHixie75(object): - """Frame processor for the WebSocket protocol version Hixie 75 and HyBi 00. - """ - - _CLOSE_FRAME = '\xff\x00' - - def __init__(self, socket, unused_handshake): - self._socket = socket - - def send_frame_of_arbitrary_bytes(self, header, body): - self._socket.sendall(header + body) - - def send_data(self, payload, unused_frame_typem, unused_end, unused_mask): - frame = ''.join(['\x00', payload, '\xff']) - self._socket.sendall(frame) - - def send_binary(self, unused_payload, unused_end, unused_mask): - pass - - def send_text(self, payload, unused_end, unused_mask): - encoded_payload = payload.encode('utf-8') - frame = ''.join(['\x00', encoded_payload, '\xff']) - self._socket.sendall(frame) - - def assert_receive_binary(self, payload, opcode=OPCODE_BINARY, fin=1, - rsv1=0, rsv2=0, rsv3=0): - raise Exception('Binary frame is not supported in hixie75') - - def assert_receive_text(self, payload): - received = receive_bytes(self._socket, 1) - - if received != '\x00': - raise Exception( - 'Unexpected frame type: %d (expected) vs %d (actual)' % - (0, ord(received))) - - received = receive_bytes(self._socket, len(payload) + 1) - if received[-1] != '\xff': - raise Exception( - 'Termination expected: 0xff (expected) vs %r (actual)' % - received) - - if received[0:-1] != payload: - raise Exception( - 'Unexpected payload: %r (expected) vs %r (actual)' % - (payload, received[0:-1])) - - def send_close(self, code, reason): - self._socket.sendall(self._CLOSE_FRAME) - - def assert_receive_close(self, unused_code, unused_reason): - closing = receive_bytes(self._socket, len(self._CLOSE_FRAME)) - if closing != self._CLOSE_FRAME: - raise Exception('Didn\'t receive closing handshake') - - -class ClientOptions(object): - """Holds option values to configure the Client object.""" - - def __init__(self): - self.version = 13 - self.server_host = '' - self.origin = '' - self.resource = '' - self.server_port = -1 - self.socket_timeout = 1000 - self.use_tls = False - self.extensions = [] - # Enable deflate-application-data. - self.use_deflate_frame = False - # Enable mux - self.use_mux = False - - def enable_deflate_frame(self): - self.use_deflate_frame = True - self.extensions.append(_DEFLATE_FRAME_EXTENSION) - - def enable_mux(self): - self.use_mux = True - self.extensions.append(_MUX_EXTENSION) - - -def connect_socket_with_retry(host, port, timeout, use_tls, - retry=10, sleep_sec=0.1): - retry_count = 0 - while retry_count < retry: - try: - s = socket.socket() - s.settimeout(timeout) - s.connect((host, port)) - if use_tls: - return _TLSSocket(s) - return s - except socket.error as e: - if e.errno != errno.ECONNREFUSED: - raise - else: - retry_count = retry_count + 1 - time.sleep(sleep_sec) - - return None - - -class Client(object): - """WebSocket client.""" - - def __init__(self, options, handshake, stream_class): - self._logger = util.get_class_logger(self) - - self._options = options - self._socket = None - - self._handshake = handshake - self._stream_class = stream_class - - def connect(self): - self._socket = connect_socket_with_retry( - self._options.server_host, - self._options.server_port, - self._options.socket_timeout, - self._options.use_tls) - - self._handshake.handshake(self._socket) - - self._stream = self._stream_class(self._socket, self._handshake) - - self._logger.info('Connection established') - - def send_frame_of_arbitrary_bytes(self, header, body): - self._stream.send_frame_of_arbitrary_bytes(header, body) - - def send_message(self, message, end=True, binary=False, raw=False, - mask=True): - if binary: - self._stream.send_binary(message, end, mask) - elif raw: - self._stream.send_data(message, OPCODE_TEXT, end, mask) - else: - self._stream.send_text(message, end, mask) - - def assert_receive(self, payload, binary=False): - if binary: - self._stream.assert_receive_binary(payload) - else: - self._stream.assert_receive_text(payload) - - def send_close(self, code=STATUS_NORMAL_CLOSURE, reason=''): - self._stream.send_close(code, reason) - - def assert_receive_close(self, code=STATUS_NORMAL_CLOSURE, reason=''): - self._stream.assert_receive_close(code, reason) - - def close_socket(self): - self._socket.close() - - def assert_connection_closed(self): - try: - read_data = receive_bytes(self._socket, 1) - except Exception as e: - if str(e).find( - 'Connection closed before receiving requested length ') == 0: - return - try: - error_number, message = e - for error_name in ['ECONNRESET', 'WSAECONNRESET']: - if (error_name in dir(errno) and - error_number == getattr(errno, error_name)): - return - except: - raise e - raise e - - raise Exception('Connection is not closed (Read: %r)' % read_data) - - -def create_client(options): - return Client( - options, WebSocketHandshake(options), WebSocketStream) - - -def create_client_hybi00(options): - return Client( - options, - WebSocketHybi00Handshake(options, '0'), - WebSocketStreamHixie75) - - -def create_client_hixie75(options): - return Client( - options, WebSocketHixie75Handshake(options), WebSocketStreamHixie75) - - -# vi:sts=4 sw=4 et diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/test/mux_client_for_testing.py b/tests/wpt/web-platform-tests/tools/pywebsocket/test/mux_client_for_testing.py deleted file mode 100644 index 304e7fc95d3..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/test/mux_client_for_testing.py +++ /dev/null @@ -1,690 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""WebSocket client utility for testing mux extension. - -This code should be independent from mod_pywebsocket. See the comment of -client_for_testing.py. - -NOTE: This code is far from robust like client_for_testing.py. -""" - - - -import Queue -import base64 -import collections -import email -import email.parser -import logging -import math -import os -import random -import socket -import struct -import threading - -from mod_pywebsocket import util - -from test import client_for_testing - - -_CONTROL_CHANNEL_ID = 0 -_DEFAULT_CHANNEL_ID = 1 - -_MUX_OPCODE_ADD_CHANNEL_REQUEST = 0 -_MUX_OPCODE_ADD_CHANNEL_RESPONSE = 1 -_MUX_OPCODE_FLOW_CONTROL = 2 -_MUX_OPCODE_DROP_CHANNEL = 3 -_MUX_OPCODE_NEW_CHANNEL_SLOT = 4 - - -class _ControlBlock: - def __init__(self, opcode): - self.opcode = opcode - - -def _parse_handshake_response(response): - status_line, header_lines = response.split('\r\n', 1) - - words = status_line.split(' ') - if len(words) < 3: - raise ValueError('Bad Status-Line syntax %r' % status_line) - [version, response_code] = words[:2] - if version != 'HTTP/1.1': - raise ValueError('Bad response version %r' % version) - - if response_code != '101': - raise ValueError('Bad response code %r ' % response_code) - headers = email.parser.Parser().parsestr(header_lines) - return headers - - -def _parse_channel_id(data, offset=0): - length = len(data) - remaining = length - offset - - if remaining <= 0: - raise Exception('No channel id found') - - channel_id = ord(data[offset]) - channel_id_length = 1 - if channel_id & 0xe0 == 0xe0: - if remaining < 4: - raise Exception('Invalid channel id format') - channel_id = struct.unpack('!L', - data[offset:offset+4])[0] & 0x1fffffff - channel_id_length = 4 - elif channel_id & 0xc0 == 0xc0: - if remaining < 3: - raise Exception('Invalid channel id format') - channel_id = (((channel_id & 0x1f) << 16) + - struct.unpack('!H', data[offset+1:offset+3])[0]) - channel_id_length = 3 - elif channel_id & 0x80 == 0x80: - if remaining < 2: - raise Exception('Invalid channel id format') - channel_id = struct.unpack('!H', data[offset:offset+2])[0] & 0x3fff - channel_id_length = 2 - - return channel_id, channel_id_length - - -def _parse_number(data, offset=0): - first_byte = ord(data[offset]) - if (first_byte & 0x80) != 0: - raise Exception('The MSB of number field must be unset') - first_byte = first_byte & 0x7f - if first_byte == 127: - if offset + 9 > len(data): - raise Exception('Invalid number') - return struct.unpack('!Q', data[offset+1:offset+9])[0], 9 - if first_byte == 126: - if offset + 3 > len(data): - raise Exception('Invalid number') - return struct.unpack('!H', data[offset+1:offset+3])[0], 3 - return first_byte, 1 - - -def _parse_size_and_contents(data, offset=0): - size, advance = _parse_number(data, offset) - start_position = offset + advance - end_position = start_position + size - if len(data) < end_position: - raise Exception('Invalid size of control block (%d < %d)' % ( - len(data), end_position)) - return data[start_position:end_position], size + advance - - -def _parse_control_blocks(data): - blocks = [] - length = len(data) - pos = 0 - - while pos < length: - first_byte = ord(data[pos]) - pos += 1 - opcode = (first_byte >> 5) & 0x7 - block = _ControlBlock(opcode) - - # TODO(bashi): Support more opcode - if opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE: - block.encode = first_byte & 3 - block.rejected = (first_byte >> 4) & 1 - - channel_id, advance = _parse_channel_id(data, pos) - block.channel_id = channel_id - pos += advance - - encoded_handshake, advance = _parse_size_and_contents(data, pos) - block.encoded_handshake = encoded_handshake - pos += advance - blocks.append(block) - elif opcode == _MUX_OPCODE_DROP_CHANNEL: - block.mux_error = (first_byte >> 4) & 1 - - channel_id, advance = _parse_channel_id(data, pos) - block.channel_id = channel_id - pos += advance - - reason, advance = _parse_size_and_contents(data, pos) - if len(reason) == 0: - block.drop_code = None - block.drop_message = '' - elif len(reason) >= 2: - block.drop_code = struct.unpack('!H', reason[:2])[0] - block.drop_message = reason[2:] - else: - raise Exception('Invalid DropChannel') - pos += advance - blocks.append(block) - elif opcode == _MUX_OPCODE_FLOW_CONTROL: - channel_id, advance = _parse_channel_id(data, pos) - block.channel_id = channel_id - pos += advance - send_quota, advance = _parse_number(data, pos) - block.send_quota = send_quota - pos += advance - blocks.append(block) - elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT: - fallback = first_byte & 1 - slots, advance = _parse_number(data, pos) - pos += advance - send_quota, advance = _parse_number(data, pos) - pos += advance - if fallback == 1 and (slots != 0 or send_quota != 0): - raise Exception('slots and send_quota must be zero if F bit ' - 'is set') - block.fallback = fallback - block.slots = slots - block.send_quota = send_quota - blocks.append(block) - else: - raise Exception( - 'Unsupported mux opcode %d received' % opcode) - - return blocks - - -def _encode_channel_id(channel_id): - if channel_id < 0: - raise ValueError('Channel id %d must not be negative' % channel_id) - - if channel_id < 2 ** 7: - return chr(channel_id) - if channel_id < 2 ** 14: - return struct.pack('!H', 0x8000 + channel_id) - if channel_id < 2 ** 21: - first = chr(0xc0 + (channel_id >> 16)) - return first + struct.pack('!H', channel_id & 0xffff) - if channel_id < 2 ** 29: - return struct.pack('!L', 0xe0000000 + channel_id) - - raise ValueError('Channel id %d is too large' % channel_id) - - -def _encode_number(number): - if number <= 125: - return chr(number) - elif number < (1 << 16): - return chr(0x7e) + struct.pack('!H', number) - elif number < (1 << 63): - return chr(0x7f) + struct.pack('!Q', number) - else: - raise Exception('Invalid number') - - -def _create_add_channel_request(channel_id, encoded_handshake, - encoding=0): - length = len(encoded_handshake) - handshake_length = _encode_number(length) - - first_byte = (_MUX_OPCODE_ADD_CHANNEL_REQUEST << 5) | encoding - return (chr(first_byte) + _encode_channel_id(channel_id) + - handshake_length + encoded_handshake) - - -def _create_flow_control(channel_id, replenished_quota): - first_byte = (_MUX_OPCODE_FLOW_CONTROL << 5) - return (chr(first_byte) + _encode_channel_id(channel_id) + - _encode_number(replenished_quota)) - - -class _MuxReaderThread(threading.Thread): - """Mux reader thread. - - Reads frames and passes them to the mux client. This thread accesses - private functions/variables of the mux client. - """ - - def __init__(self, mux): - threading.Thread.__init__(self) - self.setDaemon(True) - self._mux = mux - self._stop_requested = False - - def _receive_message(self): - first_opcode = None - pending_payload = [] - while not self._stop_requested: - fin, rsv1, rsv2, rsv3, opcode, payload_length = ( - client_for_testing.read_frame_header(self._mux._socket)) - - if not first_opcode: - if opcode == client_for_testing.OPCODE_TEXT: - raise Exception('Received a text message on physical ' - 'connection') - if opcode == client_for_testing.OPCODE_CONTINUATION: - raise Exception('Received an intermediate frame but ' - 'fragmentation was not started') - if (opcode == client_for_testing.OPCODE_BINARY or - opcode == client_for_testing.OPCODE_PONG or - opcode == client_for_testing.OPCODE_PONG or - opcode == client_for_testing.OPCODE_CLOSE): - first_opcode = opcode - else: - raise Exception('Received an undefined opcode frame: %d' % - opcode) - - elif opcode != client_for_testing.OPCODE_CONTINUATION: - raise Exception('Received a new opcode before ' - 'terminating fragmentation') - - payload = client_for_testing.receive_bytes( - self._mux._socket, payload_length) - - if self._mux._incoming_frame_filter is not None: - payload = self._mux._incoming_frame_filter.filter(payload) - - pending_payload.append(payload) - - if fin: - break - - if self._stop_requested: - return None, None - - message = ''.join(pending_payload) - return first_opcode, message - - def request_stop(self): - self._stop_requested = True - - def run(self): - try: - while not self._stop_requested: - # opcode is OPCODE_BINARY or control opcodes when a message - # is succesfully received. - opcode, message = self._receive_message() - if not opcode: - return - if opcode == client_for_testing.OPCODE_BINARY: - channel_id, advance = _parse_channel_id(message) - self._mux._dispatch_frame(channel_id, message[advance:]) - else: - self._mux._process_control_message(opcode, message) - finally: - self._mux._notify_reader_done() - - -class _InnerFrame(object): - def __init__(self, fin, rsv1, rsv2, rsv3, opcode, payload): - self.fin = fin - self.rsv1 = rsv1 - self.rsv2 = rsv2 - self.rsv3 = rsv3 - self.opcode = opcode - self.payload = payload - - -class _LogicalChannelData(object): - def __init__(self): - self.queue = Queue.Queue() - self.send_quota = 0 - self.receive_quota = 0 - - -class MuxClient(object): - """WebSocket mux client. - - Note that this class is NOT thread-safe. Do not access an instance of this - class from multiple threads at a same time. - """ - - def __init__(self, options): - self._logger = util.get_class_logger(self) - - self._options = options - self._options.enable_mux() - self._stream = None - self._socket = None - self._handshake = client_for_testing.WebSocketHandshake(self._options) - self._incoming_frame_filter = None - self._outgoing_frame_filter = None - - self._is_active = False - self._read_thread = None - self._control_blocks_condition = threading.Condition() - self._control_blocks = [] - self._channel_slots = collections.deque() - self._logical_channels_condition = threading.Condition(); - self._logical_channels = {} - self._timeout = 2 - self._physical_connection_close_event = None - self._physical_connection_close_message = None - - def _parse_inner_frame(self, data): - if len(data) == 0: - raise Exception('Invalid encapsulated frame received') - - first_byte = ord(data[0]) - fin = (first_byte << 7) & 1 - rsv1 = (first_byte << 6) & 1 - rsv2 = (first_byte << 5) & 1 - rsv3 = (first_byte << 4) & 1 - opcode = first_byte & 0xf - - if self._outgoing_frame_filter: - payload = self._outgoing_frame_filter.filter( - data[1:]) - else: - payload = data[1:] - - return _InnerFrame(fin, rsv1, rsv2, rsv3, opcode, payload) - - def _process_mux_control_blocks(self): - for block in self._control_blocks: - if block.opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE: - # AddChannelResponse will be handled in add_channel(). - continue - elif block.opcode == _MUX_OPCODE_FLOW_CONTROL: - try: - self._logical_channels_condition.acquire() - if not block.channel_id in self._logical_channels: - raise Exception('Invalid flow control received for ' - 'channel id %d' % block.channel_id) - self._logical_channels[block.channel_id].send_quota += ( - block.send_quota) - self._logical_channels_condition.notify() - finally: - self._logical_channels_condition.release() - elif block.opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT: - self._channel_slots.extend([block.send_quota] * block.slots) - - def _dispatch_frame(self, channel_id, payload): - if channel_id == _CONTROL_CHANNEL_ID: - try: - self._control_blocks_condition.acquire() - self._control_blocks += _parse_control_blocks(payload) - self._process_mux_control_blocks() - self._control_blocks_condition.notify() - finally: - self._control_blocks_condition.release() - else: - try: - self._logical_channels_condition.acquire() - if not channel_id in self._logical_channels: - raise Exception('Received logical frame on channel id ' - '%d, which is not established' % - channel_id) - - inner_frame = self._parse_inner_frame(payload) - self._logical_channels[channel_id].receive_quota -= ( - len(inner_frame.payload)) - if self._logical_channels[channel_id].receive_quota < 0: - raise Exception('The server violates quota on ' - 'channel id %d' % channel_id) - finally: - self._logical_channels_condition.release() - self._logical_channels[channel_id].queue.put(inner_frame) - - def _process_control_message(self, opcode, message): - # Ping/Pong are not supported. - if opcode == client_for_testing.OPCODE_CLOSE: - self._physical_connection_close_message = message - if self._is_active: - self._stream.send_close( - code=client_for_testing.STATUS_NORMAL_CLOSURE, reason='') - self._read_thread.request_stop() - - if self._physical_connection_close_event: - self._physical_connection_close_event.set() - - def _notify_reader_done(self): - self._logger.debug('Read thread terminated.') - self.close_socket() - - def _assert_channel_slot_available(self): - try: - self._control_blocks_condition.acquire() - if len(self._channel_slots) == 0: - # Wait once - self._control_blocks_condition.wait(timeout=self._timeout) - finally: - self._control_blocks_condition.release() - - if len(self._channel_slots) == 0: - raise Exception('Failed to receive NewChannelSlot') - - def _assert_send_quota_available(self, channel_id): - try: - self._logical_channels_condition.acquire() - if self._logical_channels[channel_id].send_quota == 0: - # Wait once - self._logical_channels_condition.wait(timeout=self._timeout) - finally: - self._logical_channels_condition.release() - - if self._logical_channels[channel_id].send_quota == 0: - raise Exception('Failed to receive FlowControl for channel id %d' % - channel_id) - - def connect(self): - self._socket = client_for_testing.connect_socket_with_retry( - self._options.server_host, - self._options.server_port, - self._options.socket_timeout, - self._options.use_tls) - - self._handshake.handshake(self._socket) - self._stream = client_for_testing.WebSocketStream( - self._socket, self._handshake) - - self._logical_channels[_DEFAULT_CHANNEL_ID] = _LogicalChannelData() - - self._read_thread = _MuxReaderThread(self) - self._read_thread.start() - - self._assert_channel_slot_available() - self._assert_send_quota_available(_DEFAULT_CHANNEL_ID) - - self._is_active = True - self._logger.info('Connection established') - - def add_channel(self, channel_id, options): - if not self._is_active: - raise Exception('Mux client is not active') - - if channel_id in self._logical_channels: - raise Exception('Channel id %d already exists' % channel_id) - - try: - send_quota = self._channel_slots.popleft() - except IndexError as e: - raise Exception('No channel slots: %r' % e) - - # Create AddChannel request - request_line = 'GET %s HTTP/1.1\r\n' % options.resource - fields = [] - if options.server_port == client_for_testing.DEFAULT_PORT: - fields.append('Host: %s\r\n' % options.server_host.lower()) - else: - fields.append('Host: %s:%d\r\n' % (options.server_host.lower(), - options.server_port)) - fields.append('Origin: %s\r\n' % options.origin.lower()) - fields.append('Connection: Upgrade\r\n') - - if len(options.extensions) > 0: - fields.append('Sec-WebSocket-Extensions: %s\r\n' % - ', '.join(options.extensions)) - - handshake = request_line + ''.join(fields) + '\r\n' - add_channel_request = _create_add_channel_request( - channel_id, handshake) - payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + add_channel_request - self._stream.send_binary(payload) - - # Wait AddChannelResponse - self._logger.debug('Waiting AddChannelResponse for the request...') - response = None - try: - self._control_blocks_condition.acquire() - while True: - for block in self._control_blocks: - if block.opcode != _MUX_OPCODE_ADD_CHANNEL_RESPONSE: - continue - if block.channel_id == channel_id: - response = block - self._control_blocks.remove(response) - break - if response: - break - self._control_blocks_condition.wait(self._timeout) - if not self._is_active: - raise Exception('AddChannelRequest timed out') - finally: - self._control_blocks_condition.release() - - # Validate AddChannelResponse - if response.rejected: - raise Exception('The server rejected AddChannelRequest') - - fields = _parse_handshake_response(response.encoded_handshake) - - # Should we reject when Upgrade, Connection, or Sec-WebSocket-Accept - # headers exist? - - self._logical_channels_condition.acquire() - self._logical_channels[channel_id] = _LogicalChannelData() - self._logical_channels[channel_id].send_quota = send_quota - self._logical_channels_condition.release() - - self._logger.debug('Logical channel %d established' % channel_id) - - def _check_logical_channel_is_opened(self, channel_id): - if not self._is_active: - raise Exception('Mux client is not active') - - if not channel_id in self._logical_channels: - raise Exception('Logical channel %d is not established.') - - def drop_channel(self, channel_id): - # TODO(bashi): Implement - pass - - def send_flow_control(self, channel_id, replenished_quota): - self._check_logical_channel_is_opened(channel_id) - flow_control = _create_flow_control(channel_id, replenished_quota) - payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + flow_control - # Replenish receive quota - try: - self._logical_channels_condition.acquire() - self._logical_channels[channel_id].receive_quota += ( - replenished_quota) - finally: - self._logical_channels_condition.release() - self._stream.send_binary(payload) - - def send_message(self, channel_id, message, end=True, binary=False): - self._check_logical_channel_is_opened(channel_id) - - if binary: - first_byte = (end << 7) | client_for_testing.OPCODE_BINARY - else: - first_byte = (end << 7) | client_for_testing.OPCODE_TEXT - message = message.encode('utf-8') - - try: - self._logical_channels_condition.acquire() - if self._logical_channels[channel_id].send_quota < len(message): - raise Exception('Send quota violation: %d < %d' % ( - self._logical_channels[channel_id].send_quota, - len(message))) - - self._logical_channels[channel_id].send_quota -= len(message) - finally: - self._logical_channels_condition.release() - payload = _encode_channel_id(channel_id) + chr(first_byte) + message - self._stream.send_binary(payload) - - def assert_receive(self, channel_id, payload, binary=False): - self._check_logical_channel_is_opened(channel_id) - - try: - inner_frame = self._logical_channels[channel_id].queue.get( - timeout=self._timeout) - except Queue.Empty as e: - raise Exception('Cannot receive message from channel id %d' % - channel_id) - - if binary: - opcode = client_for_testing.OPCODE_BINARY - else: - opcode = client_for_testing.OPCODE_TEXT - - if inner_frame.opcode != opcode: - raise Exception('Unexpected opcode received (%r != %r)' % - (expected_opcode, inner_frame.opcode)) - - if inner_frame.payload != payload: - raise Exception('Unexpected payload received') - - def send_close(self, channel_id, code=None, reason=''): - self._check_logical_channel_is_opened(channel_id) - - if code is not None: - body = struct.pack('!H', code) + reason.encode('utf-8') - else: - body = '' - - first_byte = (1 << 7) | client_for_testing.OPCODE_CLOSE - payload = _encode_channel_id(channel_id) + chr(first_byte) + body - self._stream.send_binary(payload) - - def assert_receive_close(self, channel_id): - self._check_logical_channel_is_opened(channel_id) - - try: - inner_frame = self._logical_channels[channel_id].queue.get( - timeout=self._timeout) - except Queue.Empty as e: - raise Exception('Cannot receive message from channel id %d' % - channel_id) - if inner_frame.opcode != client_for_testing.OPCODE_CLOSE: - raise Exception('Didn\'t receive close frame') - - def send_physical_connection_close(self, code=None, reason=''): - self._physical_connection_close_event = threading.Event() - self._stream.send_close(code, reason) - - # This method can be used only after calling - # send_physical_connection_close(). - def assert_physical_connection_receive_close( - self, code=client_for_testing.STATUS_NORMAL_CLOSURE, reason=''): - self._physical_connection_close_event.wait(timeout=self._timeout) - if (not self._physical_connection_close_event.isSet() or - not self._physical_connection_close_message): - raise Exception('Didn\'t receive closing handshake') - - def close_socket(self): - self._is_active = False - self._socket.close() diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/test/test_handshake_hybi00.py b/tests/wpt/web-platform-tests/tools/pywebsocket/test/test_handshake_hybi00.py deleted file mode 100755 index 73f9f27ca2d..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/test/test_handshake_hybi00.py +++ /dev/null @@ -1,516 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Tests for handshake.hybi00 module.""" - - -import unittest - -import set_sys_path # Update sys.path to locate mod_pywebsocket module. - -from mod_pywebsocket.handshake._base import HandshakeException -from mod_pywebsocket.handshake.hybi00 import Handshaker -from mod_pywebsocket.handshake.hybi00 import _validate_subprotocol -from test import mock - - -_TEST_KEY1 = '4 @1 46546xW%0l 1 5' -_TEST_KEY2 = '12998 5 Y3 1 .P00' -_TEST_KEY3 = '^n:ds[4U' -_TEST_CHALLENGE_RESPONSE = '8jKS\'y:G*Co,Wxa-' - - -_GOOD_REQUEST = ( - 80, - 'GET', - '/demo', - { - 'Host': 'example.com', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key2': _TEST_KEY2, - 'Sec-WebSocket-Protocol': 'sample', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': _TEST_KEY1, - 'Origin': 'http://example.com', - }, - _TEST_KEY3) - -_GOOD_REQUEST_CAPITALIZED_HEADER_VALUES = ( - 80, - 'GET', - '/demo', - { - 'Host': 'example.com', - 'Connection': 'UPGRADE', - 'Sec-WebSocket-Key2': _TEST_KEY2, - 'Sec-WebSocket-Protocol': 'sample', - 'Upgrade': 'WEBSOCKET', - 'Sec-WebSocket-Key1': _TEST_KEY1, - 'Origin': 'http://example.com', - }, - _TEST_KEY3) - -_GOOD_REQUEST_CASE_MIXED_HEADER_NAMES = ( - 80, - 'GET', - '/demo', - { - 'hOsT': 'example.com', - 'cOnNeCtIoN': 'Upgrade', - 'sEc-wEbsOcKeT-kEy2': _TEST_KEY2, - 'sEc-wEbsOcKeT-pRoToCoL': 'sample', - 'uPgRaDe': 'WebSocket', - 'sEc-wEbsOcKeT-kEy1': _TEST_KEY1, - 'oRiGiN': 'http://example.com', - }, - _TEST_KEY3) - -_GOOD_RESPONSE_DEFAULT_PORT = ( - 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' - 'Upgrade: WebSocket\r\n' - 'Connection: Upgrade\r\n' - 'Sec-WebSocket-Location: ws://example.com/demo\r\n' - 'Sec-WebSocket-Origin: http://example.com\r\n' - 'Sec-WebSocket-Protocol: sample\r\n' - '\r\n' + - _TEST_CHALLENGE_RESPONSE) - -_GOOD_RESPONSE_SECURE = ( - 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' - 'Upgrade: WebSocket\r\n' - 'Connection: Upgrade\r\n' - 'Sec-WebSocket-Location: wss://example.com/demo\r\n' - 'Sec-WebSocket-Origin: http://example.com\r\n' - 'Sec-WebSocket-Protocol: sample\r\n' - '\r\n' + - _TEST_CHALLENGE_RESPONSE) - -_GOOD_REQUEST_NONDEFAULT_PORT = ( - 8081, - 'GET', - '/demo', - { - 'Host': 'example.com:8081', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key2': _TEST_KEY2, - 'Sec-WebSocket-Protocol': 'sample', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': _TEST_KEY1, - 'Origin': 'http://example.com', - }, - _TEST_KEY3) - -_GOOD_RESPONSE_NONDEFAULT_PORT = ( - 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' - 'Upgrade: WebSocket\r\n' - 'Connection: Upgrade\r\n' - 'Sec-WebSocket-Location: ws://example.com:8081/demo\r\n' - 'Sec-WebSocket-Origin: http://example.com\r\n' - 'Sec-WebSocket-Protocol: sample\r\n' - '\r\n' + - _TEST_CHALLENGE_RESPONSE) - -_GOOD_RESPONSE_SECURE_NONDEF = ( - 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' - 'Upgrade: WebSocket\r\n' - 'Connection: Upgrade\r\n' - 'Sec-WebSocket-Location: wss://example.com:8081/demo\r\n' - 'Sec-WebSocket-Origin: http://example.com\r\n' - 'Sec-WebSocket-Protocol: sample\r\n' - '\r\n' + - _TEST_CHALLENGE_RESPONSE) - -_GOOD_REQUEST_NO_PROTOCOL = ( - 80, - 'GET', - '/demo', - { - 'Host': 'example.com', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key2': _TEST_KEY2, - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': _TEST_KEY1, - 'Origin': 'http://example.com', - }, - _TEST_KEY3) - -_GOOD_RESPONSE_NO_PROTOCOL = ( - 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' - 'Upgrade: WebSocket\r\n' - 'Connection: Upgrade\r\n' - 'Sec-WebSocket-Location: ws://example.com/demo\r\n' - 'Sec-WebSocket-Origin: http://example.com\r\n' - '\r\n' + - _TEST_CHALLENGE_RESPONSE) - -_GOOD_REQUEST_WITH_OPTIONAL_HEADERS = ( - 80, - 'GET', - '/demo', - { - 'Host': 'example.com', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key2': _TEST_KEY2, - 'EmptyValue': '', - 'Sec-WebSocket-Protocol': 'sample', - 'AKey': 'AValue', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': _TEST_KEY1, - 'Origin': 'http://example.com', - }, - _TEST_KEY3) - -# TODO(tyoshino): Include \r \n in key3, challenge response. - -_GOOD_REQUEST_WITH_NONPRINTABLE_KEY = ( - 80, - 'GET', - '/demo', - { - 'Host': 'example.com', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key2': 'y R2 48 Q1O4 e|BV3 i5 1 u- 65', - 'Sec-WebSocket-Protocol': 'sample', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': '36 7 74 i 92 2\'m 9 0G', - 'Origin': 'http://example.com', - }, - ''.join(map(chr, [0x01, 0xd1, 0xdd, 0x3b, 0xd1, 0x56, 0x63, 0xff]))) - -_GOOD_RESPONSE_WITH_NONPRINTABLE_KEY = ( - 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' - 'Upgrade: WebSocket\r\n' - 'Connection: Upgrade\r\n' - 'Sec-WebSocket-Location: ws://example.com/demo\r\n' - 'Sec-WebSocket-Origin: http://example.com\r\n' - 'Sec-WebSocket-Protocol: sample\r\n' - '\r\n' + - ''.join(map(chr, [0x0b, 0x99, 0xfa, 0x55, 0xbd, 0x01, 0x23, 0x7b, - 0x45, 0xa2, 0xf1, 0xd0, 0x87, 0x8a, 0xee, 0xeb]))) - -_GOOD_REQUEST_WITH_QUERY_PART = ( - 80, - 'GET', - '/demo?e=mc2', - { - 'Host': 'example.com', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key2': _TEST_KEY2, - 'Sec-WebSocket-Protocol': 'sample', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': _TEST_KEY1, - 'Origin': 'http://example.com', - }, - _TEST_KEY3) - -_GOOD_RESPONSE_WITH_QUERY_PART = ( - 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' - 'Upgrade: WebSocket\r\n' - 'Connection: Upgrade\r\n' - 'Sec-WebSocket-Location: ws://example.com/demo?e=mc2\r\n' - 'Sec-WebSocket-Origin: http://example.com\r\n' - 'Sec-WebSocket-Protocol: sample\r\n' - '\r\n' + - _TEST_CHALLENGE_RESPONSE) - -_BAD_REQUESTS = ( - ( # HTTP request - 80, - 'GET', - '/demo', - { - 'Host': 'www.google.com', - 'User-Agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5;' - ' en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3' - ' GTB6 GTBA', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,' - '*/*;q=0.8', - 'Accept-Language': 'en-us,en;q=0.5', - 'Accept-Encoding': 'gzip,deflate', - 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', - 'Keep-Alive': '300', - 'Connection': 'keep-alive', - }), - ( # Wrong method - 80, - 'POST', - '/demo', - { - 'Host': 'example.com', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key2': _TEST_KEY2, - 'Sec-WebSocket-Protocol': 'sample', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': _TEST_KEY1, - 'Origin': 'http://example.com', - }, - _TEST_KEY3), - ( # Missing Upgrade - 80, - 'GET', - '/demo', - { - 'Host': 'example.com', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key2': _TEST_KEY2, - 'Sec-WebSocket-Protocol': 'sample', - 'Sec-WebSocket-Key1': _TEST_KEY1, - 'Origin': 'http://example.com', - }, - _TEST_KEY3), - ( # Wrong Upgrade - 80, - 'GET', - '/demo', - { - 'Host': 'example.com', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key2': _TEST_KEY2, - 'Sec-WebSocket-Protocol': 'sample', - 'Upgrade': 'NonWebSocket', - 'Sec-WebSocket-Key1': _TEST_KEY1, - 'Origin': 'http://example.com', - }, - _TEST_KEY3), - ( # Empty WebSocket-Protocol - 80, - 'GET', - '/demo', - { - 'Host': 'example.com', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key2': _TEST_KEY2, - 'Sec-WebSocket-Protocol': '', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': _TEST_KEY1, - 'Origin': 'http://example.com', - }, - _TEST_KEY3), - ( # Wrong port number format - 80, - 'GET', - '/demo', - { - 'Host': 'example.com:0x50', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key2': _TEST_KEY2, - 'Sec-WebSocket-Protocol': 'sample', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': _TEST_KEY1, - 'Origin': 'http://example.com', - }, - _TEST_KEY3), - ( # Header/connection port mismatch - 8080, - 'GET', - '/demo', - { - 'Host': 'example.com', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key2': _TEST_KEY2, - 'Sec-WebSocket-Protocol': 'sample', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': _TEST_KEY1, - 'Origin': 'http://example.com', - }, - _TEST_KEY3), - ( # Illegal WebSocket-Protocol - 80, - 'GET', - '/demo', - { - 'Host': 'example.com', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key2': _TEST_KEY2, - 'Sec-WebSocket-Protocol': 'illegal\x09protocol', - 'Upgrade': 'WebSocket', - 'Sec-WebSocket-Key1': _TEST_KEY1, - 'Origin': 'http://example.com', - }, - _TEST_KEY3), -) - - -def _create_request(request_def): - data = '' - if len(request_def) > 4: - data = request_def[4] - conn = mock.MockConn(data) - conn.local_addr = ('0.0.0.0', request_def[0]) - return mock.MockRequest( - method=request_def[1], - uri=request_def[2], - headers_in=request_def[3], - connection=conn) - - -def _create_get_memorized_lines(lines): - """Creates a function that returns the given string.""" - - def get_memorized_lines(): - return lines - return get_memorized_lines - - -def _create_requests_with_lines(request_lines_set): - requests = [] - for lines in request_lines_set: - request = _create_request(_GOOD_REQUEST) - request.connection.get_memorized_lines = _create_get_memorized_lines( - lines) - requests.append(request) - return requests - - -class HyBi00HandshakerTest(unittest.TestCase): - - def test_good_request_default_port(self): - request = _create_request(_GOOD_REQUEST) - handshaker = Handshaker(request, mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_DEFAULT_PORT, - request.connection.written_data()) - self.assertEqual('/demo', request.ws_resource) - self.assertEqual('http://example.com', request.ws_origin) - self.assertEqual('ws://example.com/demo', request.ws_location) - self.assertEqual('sample', request.ws_protocol) - - def test_good_request_capitalized_header_values(self): - request = _create_request(_GOOD_REQUEST_CAPITALIZED_HEADER_VALUES) - handshaker = Handshaker(request, mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_DEFAULT_PORT, - request.connection.written_data()) - - def test_good_request_case_mixed_header_names(self): - request = _create_request(_GOOD_REQUEST_CASE_MIXED_HEADER_NAMES) - handshaker = Handshaker(request, mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_DEFAULT_PORT, - request.connection.written_data()) - - def test_good_request_secure_default_port(self): - request = _create_request(_GOOD_REQUEST) - request.connection.local_addr = ('0.0.0.0', 443) - request.is_https_ = True - handshaker = Handshaker(request, mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_SECURE, - request.connection.written_data()) - self.assertEqual('sample', request.ws_protocol) - - def test_good_request_nondefault_port(self): - request = _create_request(_GOOD_REQUEST_NONDEFAULT_PORT) - handshaker = Handshaker(request, - mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_NONDEFAULT_PORT, - request.connection.written_data()) - self.assertEqual('sample', request.ws_protocol) - - def test_good_request_secure_non_default_port(self): - request = _create_request(_GOOD_REQUEST_NONDEFAULT_PORT) - request.is_https_ = True - handshaker = Handshaker(request, mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_SECURE_NONDEF, - request.connection.written_data()) - self.assertEqual('sample', request.ws_protocol) - - def test_good_request_default_no_protocol(self): - request = _create_request(_GOOD_REQUEST_NO_PROTOCOL) - handshaker = Handshaker(request, mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_NO_PROTOCOL, - request.connection.written_data()) - self.assertEqual(None, request.ws_protocol) - - def test_good_request_optional_headers(self): - request = _create_request(_GOOD_REQUEST_WITH_OPTIONAL_HEADERS) - handshaker = Handshaker(request, mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual('AValue', - request.headers_in['AKey']) - self.assertEqual('', - request.headers_in['EmptyValue']) - - def test_good_request_with_nonprintable_key(self): - request = _create_request(_GOOD_REQUEST_WITH_NONPRINTABLE_KEY) - handshaker = Handshaker(request, mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_WITH_NONPRINTABLE_KEY, - request.connection.written_data()) - self.assertEqual('sample', request.ws_protocol) - - def test_good_request_with_query_part(self): - request = _create_request(_GOOD_REQUEST_WITH_QUERY_PART) - handshaker = Handshaker(request, mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_WITH_QUERY_PART, - request.connection.written_data()) - self.assertEqual('ws://example.com/demo?e=mc2', request.ws_location) - - def test_bad_requests(self): - for request in map(_create_request, _BAD_REQUESTS): - handshaker = Handshaker(request, mock.MockDispatcher()) - self.assertRaises(HandshakeException, handshaker.do_handshake) - - -class HyBi00ValidateSubprotocolTest(unittest.TestCase): - def test_validate_subprotocol(self): - # should succeed. - _validate_subprotocol('sample') - _validate_subprotocol('Sample') - _validate_subprotocol('sample\x7eprotocol') - _validate_subprotocol('sample\x20protocol') - - # should fail. - self.assertRaises(HandshakeException, - _validate_subprotocol, - '') - self.assertRaises(HandshakeException, - _validate_subprotocol, - 'sample\x19protocol') - self.assertRaises(HandshakeException, - _validate_subprotocol, - 'sample\x7fprotocol') - self.assertRaises(HandshakeException, - _validate_subprotocol, - # "Japan" in Japanese - u'\u65e5\u672c') - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/test/test_msgutil.py b/tests/wpt/web-platform-tests/tools/pywebsocket/test/test_msgutil.py deleted file mode 100755 index 234735c6304..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/test/test_msgutil.py +++ /dev/null @@ -1,1347 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Tests for msgutil module.""" -from __future__ import print_function - - -import array -import Queue -import random -import struct -import unittest -import zlib - -import set_sys_path # Update sys.path to locate mod_pywebsocket module. - -from mod_pywebsocket import common -from mod_pywebsocket.extensions import DeflateFrameExtensionProcessor -from mod_pywebsocket.extensions import PerMessageDeflateExtensionProcessor -from mod_pywebsocket import msgutil -from mod_pywebsocket.stream import InvalidUTF8Exception -from mod_pywebsocket.stream import Stream -from mod_pywebsocket.stream import StreamHixie75 -from mod_pywebsocket.stream import StreamOptions -from mod_pywebsocket import util -from test import mock - - -# We use one fixed nonce for testing instead of cryptographically secure PRNG. -_MASKING_NONCE = 'ABCD' - - -def _mask_hybi(frame): - frame_key = map(ord, _MASKING_NONCE) - frame_key_len = len(frame_key) - result = array.array('B') - result.fromstring(frame) - count = 0 - for i in xrange(len(result)): - result[i] ^= frame_key[count] - count = (count + 1) % frame_key_len - return _MASKING_NONCE + result.tostring() - - -def _install_extension_processor(processor, request, stream_options): - response = processor.get_extension_response() - if response is not None: - processor.setup_stream_options(stream_options) - request.ws_extension_processors.append(processor) - - -def _create_request_from_rawdata( - read_data, - deflate_frame_request=None, - permessage_deflate_request=None): - req = mock.MockRequest(connection=mock.MockConn(''.join(read_data))) - req.ws_version = common.VERSION_HYBI_LATEST - req.ws_extension_processors = [] - - processor = None - if deflate_frame_request is not None: - processor = DeflateFrameExtensionProcessor(deflate_frame_request) - elif permessage_deflate_request is not None: - processor = PerMessageDeflateExtensionProcessor( - permessage_deflate_request) - - stream_options = StreamOptions() - if processor is not None: - _install_extension_processor(processor, req, stream_options) - req.ws_stream = Stream(req, stream_options) - - return req - - -def _create_request(*frames): - """Creates MockRequest using data given as frames. - - frames will be returned on calling request.connection.read() where request - is MockRequest returned by this function. - """ - - read_data = [] - for (header, body) in frames: - read_data.append(header + _mask_hybi(body)) - - return _create_request_from_rawdata(read_data) - - -def _create_blocking_request(): - """Creates MockRequest. - - Data written to a MockRequest can be read out by calling - request.connection.written_data(). - """ - - req = mock.MockRequest(connection=mock.MockBlockingConn()) - req.ws_version = common.VERSION_HYBI_LATEST - stream_options = StreamOptions() - req.ws_stream = Stream(req, stream_options) - return req - - -def _create_request_hixie75(read_data=''): - req = mock.MockRequest(connection=mock.MockConn(read_data)) - req.ws_stream = StreamHixie75(req) - return req - - -def _create_blocking_request_hixie75(): - req = mock.MockRequest(connection=mock.MockBlockingConn()) - req.ws_stream = StreamHixie75(req) - return req - - -class BasicMessageTest(unittest.TestCase): - """Basic tests for Stream.""" - - def test_send_message(self): - request = _create_request() - msgutil.send_message(request, 'Hello') - self.assertEqual('\x81\x05Hello', request.connection.written_data()) - - payload = 'a' * 125 - request = _create_request() - msgutil.send_message(request, payload) - self.assertEqual('\x81\x7d' + payload, - request.connection.written_data()) - - def test_send_medium_message(self): - payload = 'a' * 126 - request = _create_request() - msgutil.send_message(request, payload) - self.assertEqual('\x81\x7e\x00\x7e' + payload, - request.connection.written_data()) - - payload = 'a' * ((1 << 16) - 1) - request = _create_request() - msgutil.send_message(request, payload) - self.assertEqual('\x81\x7e\xff\xff' + payload, - request.connection.written_data()) - - def test_send_large_message(self): - payload = 'a' * (1 << 16) - request = _create_request() - msgutil.send_message(request, payload) - self.assertEqual('\x81\x7f\x00\x00\x00\x00\x00\x01\x00\x00' + payload, - request.connection.written_data()) - - def test_send_message_unicode(self): - request = _create_request() - msgutil.send_message(request, u'\u65e5') - # U+65e5 is encoded as e6,97,a5 in UTF-8 - self.assertEqual('\x81\x03\xe6\x97\xa5', - request.connection.written_data()) - - def test_send_message_fragments(self): - request = _create_request() - msgutil.send_message(request, 'Hello', False) - msgutil.send_message(request, ' ', False) - msgutil.send_message(request, 'World', False) - msgutil.send_message(request, '!', True) - self.assertEqual('\x01\x05Hello\x00\x01 \x00\x05World\x80\x01!', - request.connection.written_data()) - - def test_send_fragments_immediate_zero_termination(self): - request = _create_request() - msgutil.send_message(request, 'Hello World!', False) - msgutil.send_message(request, '', True) - self.assertEqual('\x01\x0cHello World!\x80\x00', - request.connection.written_data()) - - def test_receive_message(self): - request = _create_request( - ('\x81\x85', 'Hello'), ('\x81\x86', 'World!')) - self.assertEqual('Hello', msgutil.receive_message(request)) - self.assertEqual('World!', msgutil.receive_message(request)) - - payload = 'a' * 125 - request = _create_request(('\x81\xfd', payload)) - self.assertEqual(payload, msgutil.receive_message(request)) - - def test_receive_medium_message(self): - payload = 'a' * 126 - request = _create_request(('\x81\xfe\x00\x7e', payload)) - self.assertEqual(payload, msgutil.receive_message(request)) - - payload = 'a' * ((1 << 16) - 1) - request = _create_request(('\x81\xfe\xff\xff', payload)) - self.assertEqual(payload, msgutil.receive_message(request)) - - def test_receive_large_message(self): - payload = 'a' * (1 << 16) - request = _create_request( - ('\x81\xff\x00\x00\x00\x00\x00\x01\x00\x00', payload)) - self.assertEqual(payload, msgutil.receive_message(request)) - - def test_receive_length_not_encoded_using_minimal_number_of_bytes(self): - # Log warning on receiving bad payload length field that doesn't use - # minimal number of bytes but continue processing. - - payload = 'a' - # 1 byte can be represented without extended payload length field. - request = _create_request( - ('\x81\xff\x00\x00\x00\x00\x00\x00\x00\x01', payload)) - self.assertEqual(payload, msgutil.receive_message(request)) - - def test_receive_message_unicode(self): - request = _create_request(('\x81\x83', '\xe6\x9c\xac')) - # U+672c is encoded as e6,9c,ac in UTF-8 - self.assertEqual(u'\u672c', msgutil.receive_message(request)) - - def test_receive_message_erroneous_unicode(self): - # \x80 and \x81 are invalid as UTF-8. - request = _create_request(('\x81\x82', '\x80\x81')) - # Invalid characters should raise InvalidUTF8Exception - self.assertRaises(InvalidUTF8Exception, - msgutil.receive_message, - request) - - def test_receive_fragments(self): - request = _create_request( - ('\x01\x85', 'Hello'), - ('\x00\x81', ' '), - ('\x00\x85', 'World'), - ('\x80\x81', '!')) - self.assertEqual('Hello World!', msgutil.receive_message(request)) - - def test_receive_fragments_unicode(self): - # UTF-8 encodes U+6f22 into e6bca2 and U+5b57 into e5ad97. - request = _create_request( - ('\x01\x82', '\xe6\xbc'), - ('\x00\x82', '\xa2\xe5'), - ('\x80\x82', '\xad\x97')) - self.assertEqual(u'\u6f22\u5b57', msgutil.receive_message(request)) - - def test_receive_fragments_immediate_zero_termination(self): - request = _create_request( - ('\x01\x8c', 'Hello World!'), ('\x80\x80', '')) - self.assertEqual('Hello World!', msgutil.receive_message(request)) - - def test_receive_fragments_duplicate_start(self): - request = _create_request( - ('\x01\x85', 'Hello'), ('\x01\x85', 'World')) - self.assertRaises(msgutil.InvalidFrameException, - msgutil.receive_message, - request) - - def test_receive_fragments_intermediate_but_not_started(self): - request = _create_request(('\x00\x85', 'Hello')) - self.assertRaises(msgutil.InvalidFrameException, - msgutil.receive_message, - request) - - def test_receive_fragments_end_but_not_started(self): - request = _create_request(('\x80\x85', 'Hello')) - self.assertRaises(msgutil.InvalidFrameException, - msgutil.receive_message, - request) - - def test_receive_message_discard(self): - request = _create_request( - ('\x8f\x86', 'IGNORE'), ('\x81\x85', 'Hello'), - ('\x8f\x89', 'DISREGARD'), ('\x81\x86', 'World!')) - self.assertRaises(msgutil.UnsupportedFrameException, - msgutil.receive_message, request) - self.assertEqual('Hello', msgutil.receive_message(request)) - self.assertRaises(msgutil.UnsupportedFrameException, - msgutil.receive_message, request) - self.assertEqual('World!', msgutil.receive_message(request)) - - def test_receive_close(self): - request = _create_request( - ('\x88\x8a', struct.pack('!H', 1000) + 'Good bye')) - self.assertEqual(None, msgutil.receive_message(request)) - self.assertEqual(1000, request.ws_close_code) - self.assertEqual('Good bye', request.ws_close_reason) - - def test_send_longest_close(self): - reason = 'a' * 123 - request = _create_request( - ('\x88\xfd', - struct.pack('!H', common.STATUS_NORMAL_CLOSURE) + reason)) - request.ws_stream.close_connection(common.STATUS_NORMAL_CLOSURE, - reason) - self.assertEqual(request.ws_close_code, common.STATUS_NORMAL_CLOSURE) - self.assertEqual(request.ws_close_reason, reason) - - def test_send_close_too_long(self): - request = _create_request() - self.assertRaises(msgutil.BadOperationException, - Stream.close_connection, - request.ws_stream, - common.STATUS_NORMAL_CLOSURE, - 'a' * 124) - - def test_send_close_inconsistent_code_and_reason(self): - request = _create_request() - # reason parameter must not be specified when code is None. - self.assertRaises(msgutil.BadOperationException, - Stream.close_connection, - request.ws_stream, - None, - 'a') - - def test_send_ping(self): - request = _create_request() - msgutil.send_ping(request, 'Hello World!') - self.assertEqual('\x89\x0cHello World!', - request.connection.written_data()) - - def test_send_longest_ping(self): - request = _create_request() - msgutil.send_ping(request, 'a' * 125) - self.assertEqual('\x89\x7d' + 'a' * 125, - request.connection.written_data()) - - def test_send_ping_too_long(self): - request = _create_request() - self.assertRaises(msgutil.BadOperationException, - msgutil.send_ping, - request, - 'a' * 126) - - def test_receive_ping(self): - """Tests receiving a ping control frame.""" - - def handler(request, message): - request.called = True - - # Stream automatically respond to ping with pong without any action - # by application layer. - request = _create_request( - ('\x89\x85', 'Hello'), ('\x81\x85', 'World')) - self.assertEqual('World', msgutil.receive_message(request)) - self.assertEqual('\x8a\x05Hello', - request.connection.written_data()) - - request = _create_request( - ('\x89\x85', 'Hello'), ('\x81\x85', 'World')) - request.on_ping_handler = handler - self.assertEqual('World', msgutil.receive_message(request)) - self.assertTrue(request.called) - - def test_receive_longest_ping(self): - request = _create_request( - ('\x89\xfd', 'a' * 125), ('\x81\x85', 'World')) - self.assertEqual('World', msgutil.receive_message(request)) - self.assertEqual('\x8a\x7d' + 'a' * 125, - request.connection.written_data()) - - def test_receive_ping_too_long(self): - request = _create_request(('\x89\xfe\x00\x7e', 'a' * 126)) - self.assertRaises(msgutil.InvalidFrameException, - msgutil.receive_message, - request) - - def test_receive_pong(self): - """Tests receiving a pong control frame.""" - - def handler(request, message): - request.called = True - - request = _create_request( - ('\x8a\x85', 'Hello'), ('\x81\x85', 'World')) - request.on_pong_handler = handler - msgutil.send_ping(request, 'Hello') - self.assertEqual('\x89\x05Hello', - request.connection.written_data()) - # Valid pong is received, but receive_message won't return for it. - self.assertEqual('World', msgutil.receive_message(request)) - # Check that nothing was written after receive_message call. - self.assertEqual('\x89\x05Hello', - request.connection.written_data()) - - self.assertTrue(request.called) - - def test_receive_unsolicited_pong(self): - # Unsolicited pong is allowed from HyBi 07. - request = _create_request( - ('\x8a\x85', 'Hello'), ('\x81\x85', 'World')) - msgutil.receive_message(request) - - request = _create_request( - ('\x8a\x85', 'Hello'), ('\x81\x85', 'World')) - msgutil.send_ping(request, 'Jumbo') - # Body mismatch. - msgutil.receive_message(request) - - def test_ping_cannot_be_fragmented(self): - request = _create_request(('\x09\x85', 'Hello')) - self.assertRaises(msgutil.InvalidFrameException, - msgutil.receive_message, - request) - - def test_ping_with_too_long_payload(self): - request = _create_request(('\x89\xfe\x01\x00', 'a' * 256)) - self.assertRaises(msgutil.InvalidFrameException, - msgutil.receive_message, - request) - - -class DeflateFrameTest(unittest.TestCase): - """Tests for checking deflate-frame extension.""" - - def test_send_message(self): - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - request = _create_request_from_rawdata( - '', deflate_frame_request=extension) - msgutil.send_message(request, 'Hello') - msgutil.send_message(request, 'World') - - expected = '' - - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello = compressed_hello[:-4] - expected += '\xc1%c' % len(compressed_hello) - expected += compressed_hello - - compressed_world = compress.compress('World') - compressed_world += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_world = compressed_world[:-4] - expected += '\xc1%c' % len(compressed_world) - expected += compressed_world - - self.assertEqual(expected, request.connection.written_data()) - - def test_send_message_bfinal(self): - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - request = _create_request_from_rawdata( - '', deflate_frame_request=extension) - self.assertEquals(1, len(request.ws_extension_processors)) - deflate_frame_processor = request.ws_extension_processors[0] - deflate_frame_processor.set_bfinal(True) - msgutil.send_message(request, 'Hello') - msgutil.send_message(request, 'World') - - expected = '' - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_FINISH) - compressed_hello = compressed_hello + chr(0) - expected += '\xc1%c' % len(compressed_hello) - expected += compressed_hello - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - compressed_world = compress.compress('World') - compressed_world += compress.flush(zlib.Z_FINISH) - compressed_world = compressed_world + chr(0) - expected += '\xc1%c' % len(compressed_world) - expected += compressed_world - - self.assertEqual(expected, request.connection.written_data()) - - def test_send_message_comp_bit(self): - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - request = _create_request_from_rawdata( - '', deflate_frame_request=extension) - self.assertEquals(1, len(request.ws_extension_processors)) - deflate_frame_processor = request.ws_extension_processors[0] - msgutil.send_message(request, 'Hello') - deflate_frame_processor.disable_outgoing_compression() - msgutil.send_message(request, 'Hello') - deflate_frame_processor.enable_outgoing_compression() - msgutil.send_message(request, 'Hello') - - expected = '' - - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello = compressed_hello[:-4] - expected += '\xc1%c' % len(compressed_hello) - expected += compressed_hello - - expected += '\x81\x05Hello' - - compressed_2nd_hello = compress.compress('Hello') - compressed_2nd_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_2nd_hello = compressed_2nd_hello[:-4] - expected += '\xc1%c' % len(compressed_2nd_hello) - expected += compressed_2nd_hello - - self.assertEqual(expected, request.connection.written_data()) - - def test_send_message_no_context_takeover_parameter(self): - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - extension.add_parameter('no_context_takeover', None) - request = _create_request_from_rawdata( - '', deflate_frame_request=extension) - for i in xrange(3): - msgutil.send_message(request, 'Hello') - - compressed_message = compress.compress('Hello') - compressed_message += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_message = compressed_message[:-4] - expected = '\xc1%c' % len(compressed_message) - expected += compressed_message - - self.assertEqual( - expected + expected + expected, request.connection.written_data()) - - def test_bad_request_parameters(self): - """Tests that if there's anything wrong with deflate-frame extension - request, deflate-frame is rejected. - """ - - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - # max_window_bits less than 8 is illegal. - extension.add_parameter('max_window_bits', '7') - processor = DeflateFrameExtensionProcessor(extension) - self.assertEqual(None, processor.get_extension_response()) - - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - # max_window_bits greater than 15 is illegal. - extension.add_parameter('max_window_bits', '16') - processor = DeflateFrameExtensionProcessor(extension) - self.assertEqual(None, processor.get_extension_response()) - - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - # Non integer max_window_bits is illegal. - extension.add_parameter('max_window_bits', 'foobar') - processor = DeflateFrameExtensionProcessor(extension) - self.assertEqual(None, processor.get_extension_response()) - - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - # no_context_takeover must not have any value. - extension.add_parameter('no_context_takeover', 'foobar') - processor = DeflateFrameExtensionProcessor(extension) - self.assertEqual(None, processor.get_extension_response()) - - def test_response_parameters(self): - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - processor = DeflateFrameExtensionProcessor(extension) - processor.set_response_window_bits(8) - response = processor.get_extension_response() - self.assertTrue(response.has_parameter('max_window_bits')) - self.assertEqual('8', response.get_parameter_value('max_window_bits')) - - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - processor = DeflateFrameExtensionProcessor(extension) - processor.set_response_no_context_takeover(True) - response = processor.get_extension_response() - self.assertTrue(response.has_parameter('no_context_takeover')) - self.assertTrue( - response.get_parameter_value('no_context_takeover') is None) - - def test_receive_message(self): - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - data = '' - - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello = compressed_hello[:-4] - data += '\xc1%c' % (len(compressed_hello) | 0x80) - data += _mask_hybi(compressed_hello) - - compressed_websocket = compress.compress('WebSocket') - compressed_websocket += compress.flush(zlib.Z_FINISH) - compressed_websocket += '\x00' - data += '\xc1%c' % (len(compressed_websocket) | 0x80) - data += _mask_hybi(compressed_websocket) - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - compressed_world = compress.compress('World') - compressed_world += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_world = compressed_world[:-4] - data += '\xc1%c' % (len(compressed_world) | 0x80) - data += _mask_hybi(compressed_world) - - # Close frame - data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye') - - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - request = _create_request_from_rawdata( - data, deflate_frame_request=extension) - self.assertEqual('Hello', msgutil.receive_message(request)) - self.assertEqual('WebSocket', msgutil.receive_message(request)) - self.assertEqual('World', msgutil.receive_message(request)) - - self.assertEqual(None, msgutil.receive_message(request)) - - def test_receive_message_client_using_smaller_window(self): - """Test that frames coming from a client which is using smaller window - size that the server are correctly received. - """ - - # Using the smallest window bits of 8 for generating input frames. - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -8) - - data = '' - - # Use a frame whose content is bigger than the clients' DEFLATE window - # size before compression. The content mainly consists of 'a' but - # repetition of 'b' is put at the head and tail so that if the window - # size is big, the head is back-referenced but if small, not. - payload = 'b' * 64 + 'a' * 1024 + 'b' * 64 - compressed_hello = compress.compress(payload) - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello = compressed_hello[:-4] - data += '\xc1%c' % (len(compressed_hello) | 0x80) - data += _mask_hybi(compressed_hello) - - # Close frame - data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye') - - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - request = _create_request_from_rawdata( - data, deflate_frame_request=extension) - self.assertEqual(payload, msgutil.receive_message(request)) - - self.assertEqual(None, msgutil.receive_message(request)) - - def test_receive_message_comp_bit(self): - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - data = '' - - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello = compressed_hello[:-4] - data += '\xc1%c' % (len(compressed_hello) | 0x80) - data += _mask_hybi(compressed_hello) - - data += '\x81\x85' + _mask_hybi('Hello') - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - compressed_2nd_hello = compress.compress('Hello') - compressed_2nd_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_2nd_hello = compressed_2nd_hello[:-4] - data += '\xc1%c' % (len(compressed_2nd_hello) | 0x80) - data += _mask_hybi(compressed_2nd_hello) - - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - request = _create_request_from_rawdata( - data, deflate_frame_request=extension) - for i in xrange(3): - self.assertEqual('Hello', msgutil.receive_message(request)) - - def test_receive_message_various_btype(self): - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - data = '' - - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello = compressed_hello[:-4] - data += '\xc1%c' % (len(compressed_hello) | 0x80) - data += _mask_hybi(compressed_hello) - - compressed_websocket = compress.compress('WebSocket') - compressed_websocket += compress.flush(zlib.Z_FINISH) - compressed_websocket += '\x00' - data += '\xc1%c' % (len(compressed_websocket) | 0x80) - data += _mask_hybi(compressed_websocket) - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - compressed_world = compress.compress('World') - compressed_world += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_world = compressed_world[:-4] - data += '\xc1%c' % (len(compressed_world) | 0x80) - data += _mask_hybi(compressed_world) - - # Close frame - data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye') - - extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) - request = _create_request_from_rawdata( - data, deflate_frame_request=extension) - self.assertEqual('Hello', msgutil.receive_message(request)) - self.assertEqual('WebSocket', msgutil.receive_message(request)) - self.assertEqual('World', msgutil.receive_message(request)) - - self.assertEqual(None, msgutil.receive_message(request)) - - -class PerMessageDeflateTest(unittest.TestCase): - """Tests for permessage-deflate extension.""" - - def test_response_parameters(self): - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - extension.add_parameter('server_no_context_takeover', None) - processor = PerMessageDeflateExtensionProcessor(extension) - response = processor.get_extension_response() - self.assertTrue( - response.has_parameter('server_no_context_takeover')) - self.assertEqual( - None, response.get_parameter_value('server_no_context_takeover')) - - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - extension.add_parameter('client_max_window_bits', None) - processor = PerMessageDeflateExtensionProcessor(extension) - - processor.set_client_max_window_bits(8) - processor.set_client_no_context_takeover(True) - response = processor.get_extension_response() - self.assertEqual( - '8', response.get_parameter_value('client_max_window_bits')) - self.assertTrue( - response.has_parameter('client_no_context_takeover')) - self.assertEqual( - None, - response.get_parameter_value('client_no_context_takeover')) - - def test_send_message(self): - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_request_from_rawdata( - '', permessage_deflate_request=extension) - msgutil.send_message(request, 'Hello') - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello = compressed_hello[:-4] - expected = '\xc1%c' % len(compressed_hello) - expected += compressed_hello - self.assertEqual(expected, request.connection.written_data()) - - def test_send_empty_message(self): - """Test that an empty message is compressed correctly.""" - - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_request_from_rawdata( - '', permessage_deflate_request=extension) - - msgutil.send_message(request, '') - - # Payload in binary: 0b00000000 - # From LSB, - # - 1 bit of BFINAL (0) - # - 2 bits of BTYPE (no compression) - # - 5 bits of padding - self.assertEqual('\xc1\x01\x00', - request.connection.written_data()) - - def test_send_message_with_null_character(self): - """Test that a simple payload (one null) is framed correctly.""" - - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_request_from_rawdata( - '', permessage_deflate_request=extension) - - msgutil.send_message(request, '\x00') - - # Payload in binary: 0b01100010 0b00000000 0b00000000 - # From LSB, - # - 1 bit of BFINAL (0) - # - 2 bits of BTYPE (01 that means fixed Huffman) - # - 8 bits of the first code (00110000 that is the code for the literal - # alphabet 0x00) - # - 7 bits of the second code (0000000 that is the code for the - # end-of-block) - # - 1 bit of BFINAL (0) - # - 2 bits of BTYPE (no compression) - # - 2 bits of padding - self.assertEqual('\xc1\x03\x62\x00\x00', - request.connection.written_data()) - - def test_send_two_messages(self): - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_request_from_rawdata( - '', permessage_deflate_request=extension) - msgutil.send_message(request, 'Hello') - msgutil.send_message(request, 'World') - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - expected = '' - - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello = compressed_hello[:-4] - expected += '\xc1%c' % len(compressed_hello) - expected += compressed_hello - - compressed_world = compress.compress('World') - compressed_world += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_world = compressed_world[:-4] - expected += '\xc1%c' % len(compressed_world) - expected += compressed_world - - self.assertEqual(expected, request.connection.written_data()) - - def test_send_message_fragmented(self): - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_request_from_rawdata( - '', permessage_deflate_request=extension) - msgutil.send_message(request, 'Hello', end=False) - msgutil.send_message(request, 'Goodbye', end=False) - msgutil.send_message(request, 'World') - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - expected = '\x41%c' % len(compressed_hello) - expected += compressed_hello - compressed_goodbye = compress.compress('Goodbye') - compressed_goodbye += compress.flush(zlib.Z_SYNC_FLUSH) - expected += '\x00%c' % len(compressed_goodbye) - expected += compressed_goodbye - compressed_world = compress.compress('World') - compressed_world += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_world = compressed_world[:-4] - expected += '\x80%c' % len(compressed_world) - expected += compressed_world - self.assertEqual(expected, request.connection.written_data()) - - def test_send_message_fragmented_empty_first_frame(self): - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_request_from_rawdata( - '', permessage_deflate_request=extension) - msgutil.send_message(request, '', end=False) - msgutil.send_message(request, 'Hello') - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - compressed_hello = compress.compress('') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - expected = '\x41%c' % len(compressed_hello) - expected += compressed_hello - compressed_empty = compress.compress('Hello') - compressed_empty += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_empty = compressed_empty[:-4] - expected += '\x80%c' % len(compressed_empty) - expected += compressed_empty - print('%r' % expected) - self.assertEqual(expected, request.connection.written_data()) - - def test_send_message_fragmented_empty_last_frame(self): - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_request_from_rawdata( - '', permessage_deflate_request=extension) - msgutil.send_message(request, 'Hello', end=False) - msgutil.send_message(request, '') - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - expected = '\x41%c' % len(compressed_hello) - expected += compressed_hello - compressed_empty = compress.compress('') - compressed_empty += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_empty = compressed_empty[:-4] - expected += '\x80%c' % len(compressed_empty) - expected += compressed_empty - self.assertEqual(expected, request.connection.written_data()) - - def test_send_message_using_small_window(self): - common_part = 'abcdefghijklmnopqrstuvwxyz' - test_message = common_part + '-' * 30000 + common_part - - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - extension.add_parameter('server_max_window_bits', '8') - request = _create_request_from_rawdata( - '', permessage_deflate_request=extension) - msgutil.send_message(request, test_message) - - expected_websocket_header_size = 2 - expected_websocket_payload_size = 91 - - actual_frame = request.connection.written_data() - self.assertEqual(expected_websocket_header_size + - expected_websocket_payload_size, - len(actual_frame)) - actual_header = actual_frame[0:expected_websocket_header_size] - actual_payload = actual_frame[expected_websocket_header_size:] - - self.assertEqual( - '\xc1%c' % expected_websocket_payload_size, actual_header) - decompress = zlib.decompressobj(-8) - decompressed_message = decompress.decompress( - actual_payload + '\x00\x00\xff\xff') - decompressed_message += decompress.flush() - self.assertEqual(test_message, decompressed_message) - self.assertEqual(0, len(decompress.unused_data)) - self.assertEqual(0, len(decompress.unconsumed_tail)) - - def test_send_message_no_context_takeover_parameter(self): - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - extension.add_parameter('server_no_context_takeover', None) - request = _create_request_from_rawdata( - '', permessage_deflate_request=extension) - for i in xrange(3): - msgutil.send_message(request, 'Hello', end=False) - msgutil.send_message(request, 'Hello', end=True) - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - first_hello = compress.compress('Hello') - first_hello += compress.flush(zlib.Z_SYNC_FLUSH) - expected = '\x41%c' % len(first_hello) - expected += first_hello - second_hello = compress.compress('Hello') - second_hello += compress.flush(zlib.Z_SYNC_FLUSH) - second_hello = second_hello[:-4] - expected += '\x80%c' % len(second_hello) - expected += second_hello - - self.assertEqual( - expected + expected + expected, - request.connection.written_data()) - - def test_send_message_fragmented_bfinal(self): - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_request_from_rawdata( - '', permessage_deflate_request=extension) - self.assertEquals(1, len(request.ws_extension_processors)) - request.ws_extension_processors[0].set_bfinal(True) - msgutil.send_message(request, 'Hello', end=False) - msgutil.send_message(request, 'World', end=True) - - expected = '' - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_FINISH) - compressed_hello = compressed_hello + chr(0) - expected += '\x41%c' % len(compressed_hello) - expected += compressed_hello - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - compressed_world = compress.compress('World') - compressed_world += compress.flush(zlib.Z_FINISH) - compressed_world = compressed_world + chr(0) - expected += '\x80%c' % len(compressed_world) - expected += compressed_world - - self.assertEqual(expected, request.connection.written_data()) - - def test_receive_message_deflate(self): - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello = compressed_hello[:-4] - data = '\xc1%c' % (len(compressed_hello) | 0x80) - data += _mask_hybi(compressed_hello) - - # Close frame - data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye') - - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_request_from_rawdata( - data, permessage_deflate_request=extension) - self.assertEqual('Hello', msgutil.receive_message(request)) - - self.assertEqual(None, msgutil.receive_message(request)) - - def test_receive_message_random_section(self): - """Test that a compressed message fragmented into lots of chunks is - correctly received. - """ - - random.seed(a=0) - payload = ''.join( - [chr(random.randint(0, 255)) for i in xrange(1000)]) - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - compressed_payload = compress.compress(payload) - compressed_payload += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_payload = compressed_payload[:-4] - - # Fragment the compressed payload into lots of frames. - bytes_chunked = 0 - data = '' - frame_count = 0 - - chunk_sizes = [] - - while bytes_chunked < len(compressed_payload): - # Make sure that - # - the length of chunks are equal or less than 125 so that we can - # use 1 octet length header format for all frames. - # - at least 10 chunks are created. - chunk_size = random.randint( - 1, min(125, - len(compressed_payload) / 10, - len(compressed_payload) - bytes_chunked)) - chunk_sizes.append(chunk_size) - chunk = compressed_payload[ - bytes_chunked:bytes_chunked + chunk_size] - bytes_chunked += chunk_size - - first_octet = 0x00 - if len(data) == 0: - first_octet = first_octet | 0x42 - if bytes_chunked == len(compressed_payload): - first_octet = first_octet | 0x80 - - data += '%c%c' % (first_octet, chunk_size | 0x80) - data += _mask_hybi(chunk) - - frame_count += 1 - - print("Chunk sizes: %r" % chunk_sizes) - self.assertTrue(len(chunk_sizes) > 10) - - # Close frame - data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye') - - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_request_from_rawdata( - data, permessage_deflate_request=extension) - self.assertEqual(payload, msgutil.receive_message(request)) - - self.assertEqual(None, msgutil.receive_message(request)) - - def test_receive_two_messages(self): - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - data = '' - - compressed_hello = compress.compress('HelloWebSocket') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello = compressed_hello[:-4] - split_position = len(compressed_hello) / 2 - data += '\x41%c' % (split_position | 0x80) - data += _mask_hybi(compressed_hello[:split_position]) - - data += '\x80%c' % ((len(compressed_hello) - split_position) | 0x80) - data += _mask_hybi(compressed_hello[split_position:]) - - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - - compressed_world = compress.compress('World') - compressed_world += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_world = compressed_world[:-4] - data += '\xc1%c' % (len(compressed_world) | 0x80) - data += _mask_hybi(compressed_world) - - # Close frame - data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye') - - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_request_from_rawdata( - data, permessage_deflate_request=extension) - self.assertEqual('HelloWebSocket', msgutil.receive_message(request)) - self.assertEqual('World', msgutil.receive_message(request)) - - self.assertEqual(None, msgutil.receive_message(request)) - - def test_receive_message_mixed_btype(self): - """Test that a message compressed using lots of DEFLATE blocks with - various flush mode is correctly received. - """ - - random.seed(a=0) - payload = ''.join( - [chr(random.randint(0, 255)) for i in xrange(1000)]) - - compress = None - - # Fragment the compressed payload into lots of frames. - bytes_chunked = 0 - compressed_payload = '' - - chunk_sizes = [] - methods = [] - sync_used = False - finish_used = False - - while bytes_chunked < len(payload): - # Make sure at least 10 chunks are created. - chunk_size = random.randint( - 1, min(100, len(payload) - bytes_chunked)) - chunk_sizes.append(chunk_size) - chunk = payload[bytes_chunked:bytes_chunked + chunk_size] - - bytes_chunked += chunk_size - - if compress is None: - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, - zlib.DEFLATED, - -zlib.MAX_WBITS) - - if bytes_chunked == len(payload): - compressed_payload += compress.compress(chunk) - compressed_payload += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_payload = compressed_payload[:-4] - else: - method = random.randint(0, 1) - methods.append(method) - if method == 0: - compressed_payload += compress.compress(chunk) - compressed_payload += compress.flush(zlib.Z_SYNC_FLUSH) - sync_used = True - else: - compressed_payload += compress.compress(chunk) - compressed_payload += compress.flush(zlib.Z_FINISH) - compress = None - finish_used = True - - print("Chunk sizes: %r" % chunk_sizes) - self.assertTrue(len(chunk_sizes) > 10) - print("Methods: %r" % methods) - self.assertTrue(sync_used) - self.assertTrue(finish_used) - - self.assertTrue(125 < len(compressed_payload)) - self.assertTrue(len(compressed_payload) < 65536) - data = '\xc2\xfe' + struct.pack('!H', len(compressed_payload)) - data += _mask_hybi(compressed_payload) - - # Close frame - data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye') - - extension = common.ExtensionParameter( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_request_from_rawdata( - data, permessage_deflate_request=extension) - self.assertEqual(payload, msgutil.receive_message(request)) - - self.assertEqual(None, msgutil.receive_message(request)) - - -class MessageTestHixie75(unittest.TestCase): - """Tests for draft-hixie-thewebsocketprotocol-76 stream class.""" - - def test_send_message(self): - request = _create_request_hixie75() - msgutil.send_message(request, 'Hello') - self.assertEqual('\x00Hello\xff', request.connection.written_data()) - - def test_send_message_unicode(self): - request = _create_request_hixie75() - msgutil.send_message(request, u'\u65e5') - # U+65e5 is encoded as e6,97,a5 in UTF-8 - self.assertEqual('\x00\xe6\x97\xa5\xff', - request.connection.written_data()) - - def test_receive_message(self): - request = _create_request_hixie75('\x00Hello\xff\x00World!\xff') - self.assertEqual('Hello', msgutil.receive_message(request)) - self.assertEqual('World!', msgutil.receive_message(request)) - - def test_receive_message_unicode(self): - request = _create_request_hixie75('\x00\xe6\x9c\xac\xff') - # U+672c is encoded as e6,9c,ac in UTF-8 - self.assertEqual(u'\u672c', msgutil.receive_message(request)) - - def test_receive_message_erroneous_unicode(self): - # \x80 and \x81 are invalid as UTF-8. - request = _create_request_hixie75('\x00\x80\x81\xff') - # Invalid characters should be replaced with - # U+fffd REPLACEMENT CHARACTER - self.assertEqual(u'\ufffd\ufffd', msgutil.receive_message(request)) - - def test_receive_message_discard(self): - request = _create_request_hixie75('\x80\x06IGNORE\x00Hello\xff' - '\x01DISREGARD\xff\x00World!\xff') - self.assertEqual('Hello', msgutil.receive_message(request)) - self.assertEqual('World!', msgutil.receive_message(request)) - - -class MessageReceiverTest(unittest.TestCase): - """Tests the Stream class using MessageReceiver.""" - - def test_queue(self): - request = _create_blocking_request() - receiver = msgutil.MessageReceiver(request) - - self.assertEqual(None, receiver.receive_nowait()) - - request.connection.put_bytes('\x81\x86' + _mask_hybi('Hello!')) - self.assertEqual('Hello!', receiver.receive()) - - def test_onmessage(self): - onmessage_queue = Queue.Queue() - - def onmessage_handler(message): - onmessage_queue.put(message) - - request = _create_blocking_request() - receiver = msgutil.MessageReceiver(request, onmessage_handler) - - request.connection.put_bytes('\x81\x86' + _mask_hybi('Hello!')) - self.assertEqual('Hello!', onmessage_queue.get()) - - -class MessageReceiverHixie75Test(unittest.TestCase): - """Tests the StreamHixie75 class using MessageReceiver.""" - - def test_queue(self): - request = _create_blocking_request_hixie75() - receiver = msgutil.MessageReceiver(request) - - self.assertEqual(None, receiver.receive_nowait()) - - request.connection.put_bytes('\x00Hello!\xff') - self.assertEqual('Hello!', receiver.receive()) - - def test_onmessage(self): - onmessage_queue = Queue.Queue() - - def onmessage_handler(message): - onmessage_queue.put(message) - - request = _create_blocking_request_hixie75() - receiver = msgutil.MessageReceiver(request, onmessage_handler) - - request.connection.put_bytes('\x00Hello!\xff') - self.assertEqual('Hello!', onmessage_queue.get()) - - -class MessageSenderTest(unittest.TestCase): - """Tests the Stream class using MessageSender.""" - - def test_send(self): - request = _create_blocking_request() - sender = msgutil.MessageSender(request) - - sender.send('World') - self.assertEqual('\x81\x05World', request.connection.written_data()) - - def test_send_nowait(self): - # Use a queue to check the bytes written by MessageSender. - # request.connection.written_data() cannot be used here because - # MessageSender runs in a separate thread. - send_queue = Queue.Queue() - - def write(bytes): - send_queue.put(bytes) - - request = _create_blocking_request() - request.connection.write = write - - sender = msgutil.MessageSender(request) - - sender.send_nowait('Hello') - sender.send_nowait('World') - self.assertEqual('\x81\x05Hello', send_queue.get()) - self.assertEqual('\x81\x05World', send_queue.get()) - - -class MessageSenderHixie75Test(unittest.TestCase): - """Tests the StreamHixie75 class using MessageSender.""" - - def test_send(self): - request = _create_blocking_request_hixie75() - sender = msgutil.MessageSender(request) - - sender.send('World') - self.assertEqual('\x00World\xff', request.connection.written_data()) - - def test_send_nowait(self): - # Use a queue to check the bytes written by MessageSender. - # request.connection.written_data() cannot be used here because - # MessageSender runs in a separate thread. - send_queue = Queue.Queue() - - def write(bytes): - send_queue.put(bytes) - - request = _create_blocking_request_hixie75() - request.connection.write = write - - sender = msgutil.MessageSender(request) - - sender.send_nowait('Hello') - sender.send_nowait('World') - self.assertEqual('\x00Hello\xff', send_queue.get()) - self.assertEqual('\x00World\xff', send_queue.get()) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/test/test_mux.py b/tests/wpt/web-platform-tests/tools/pywebsocket/test/test_mux.py deleted file mode 100644 index 0773decbfd6..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/test/test_mux.py +++ /dev/null @@ -1,2088 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Tests for mux module.""" - -import Queue -import copy -import logging -import optparse -import struct -import sys -import unittest -import time -import zlib - -import set_sys_path # Update sys.path to locate mod_pywebsocket module. - -from mod_pywebsocket import common -from mod_pywebsocket import mux -from mod_pywebsocket._stream_base import ConnectionTerminatedException -from mod_pywebsocket._stream_base import UnsupportedFrameException -from mod_pywebsocket._stream_hybi import Frame -from mod_pywebsocket._stream_hybi import Stream -from mod_pywebsocket._stream_hybi import StreamOptions -from mod_pywebsocket._stream_hybi import create_binary_frame -from mod_pywebsocket._stream_hybi import create_close_frame -from mod_pywebsocket._stream_hybi import create_closing_handshake_body -from mod_pywebsocket._stream_hybi import parse_frame -from mod_pywebsocket.extensions import MuxExtensionProcessor - - -import mock - - -_TEST_HEADERS = {'Host': 'server.example.com', - 'Upgrade': 'websocket', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': '13', - 'Origin': 'http://example.com'} - - -class _OutgoingChannelData(object): - def __init__(self): - self.messages = [] - self.control_messages = [] - - self.builder = mux._InnerMessageBuilder() - -class _MockMuxConnection(mock.MockBlockingConn): - """Mock class of mod_python connection for mux.""" - - def __init__(self): - mock.MockBlockingConn.__init__(self) - self._control_blocks = [] - self._channel_data = {} - - self._current_opcode = None - self._pending_fragments = [] - - self.server_close_code = None - - def write(self, data): - """Override MockBlockingConn.write.""" - - self._current_data = data - self._position = 0 - - def _receive_bytes(length): - if self._position + length > len(self._current_data): - raise ConnectionTerminatedException( - 'Failed to receive %d bytes from encapsulated ' - 'frame' % length) - data = self._current_data[self._position:self._position+length] - self._position += length - return data - - # Parse physical frames and assemble a message if the message is - # fragmented. - opcode, payload, fin, rsv1, rsv2, rsv3 = ( - parse_frame(_receive_bytes, unmask_receive=False)) - - self._pending_fragments.append(payload) - - if self._current_opcode is None: - if opcode == common.OPCODE_CONTINUATION: - raise Exception('Sending invalid continuation opcode') - self._current_opcode = opcode - else: - if opcode != common.OPCODE_CONTINUATION: - raise Exception('Sending invalid opcode %d' % opcode) - if not fin: - return - - inner_frame_data = ''.join(self._pending_fragments) - self._pending_fragments = [] - self._current_opcode = None - - # Handle a control message on the physical channel. - # TODO(bashi): Support other opcodes if needed. - if opcode == common.OPCODE_CLOSE: - if len(payload) >= 2: - self.server_close_code = struct.unpack('!H', payload[:2])[0] - close_body = create_closing_handshake_body( - common.STATUS_NORMAL_CLOSURE, '') - close_frame = create_close_frame(close_body, mask=True) - self.put_bytes(close_frame) - return - - # Parse the payload of the message on physical channel. - parser = mux._MuxFramePayloadParser(inner_frame_data) - channel_id = parser.read_channel_id() - if channel_id == mux._CONTROL_CHANNEL_ID: - self._control_blocks.extend(list(parser.read_control_blocks())) - return - - if not channel_id in self._channel_data: - self._channel_data[channel_id] = _OutgoingChannelData() - channel_data = self._channel_data[channel_id] - - # Parse logical frames and assemble an inner (logical) message. - (inner_fin, inner_rsv1, inner_rsv2, inner_rsv3, inner_opcode, - inner_payload) = parser.read_inner_frame() - inner_frame = Frame(inner_fin, inner_rsv1, inner_rsv2, inner_rsv3, - inner_opcode, inner_payload) - message = channel_data.builder.build(inner_frame) - if message is None: - return - - if (message.opcode == common.OPCODE_TEXT or - message.opcode == common.OPCODE_BINARY): - channel_data.messages.append(message.payload) - - self.on_data_message(message.payload) - else: - channel_data.control_messages.append( - {'opcode': message.opcode, - 'message': message.payload}) - - def on_data_message(self, message): - pass - - def get_written_control_blocks(self): - return self._control_blocks - - def get_written_messages(self, channel_id): - return self._channel_data[channel_id].messages - - def get_written_control_messages(self, channel_id): - return self._channel_data[channel_id].control_messages - - -class _FailOnWriteConnection(_MockMuxConnection): - """Specicialized version of _MockMuxConnection. Its write() method raises - an exception for testing when a data message is written. - """ - - def on_data_message(self, message): - """Override to raise an exception.""" - - raise Exception('Intentional failure') - - -class _ChannelEvent(object): - """A structure that records channel events.""" - - def __init__(self): - self.request = None - self.messages = [] - self.exception = None - self.client_initiated_closing = False - - -class _MuxMockDispatcher(object): - """Mock class of dispatch.Dispatcher for mux.""" - - def __init__(self): - self.channel_events = {} - - def do_extra_handshake(self, request): - if request.ws_requested_protocols is not None: - request.ws_protocol = request.ws_requested_protocols[0] - - def _do_echo(self, request, channel_events): - while True: - message = request.ws_stream.receive_message() - if message == None: - channel_events.client_initiated_closing = True - return - if message == 'Goodbye': - return - channel_events.messages.append(message) - # echo back - request.ws_stream.send_message(message) - - def _do_ping(self, request, channel_events): - request.ws_stream.send_ping('Ping!') - - def _do_ping_while_hello_world(self, request, channel_events): - request.ws_stream.send_message('Hello ', end=False) - request.ws_stream.send_ping('Ping!') - request.ws_stream.send_message('World!', end=True) - - def _do_two_ping_while_hello_world(self, request, channel_events): - request.ws_stream.send_message('Hello ', end=False) - request.ws_stream.send_ping('Ping!') - request.ws_stream.send_ping('Pong!') - request.ws_stream.send_message('World!', end=True) - - def transfer_data(self, request): - self.channel_events[request.channel_id] = _ChannelEvent() - self.channel_events[request.channel_id].request = request - - try: - # Note: more handler will be added. - if request.uri.endswith('echo'): - self._do_echo(request, - self.channel_events[request.channel_id]) - elif request.uri.endswith('ping'): - self._do_ping(request, - self.channel_events[request.channel_id]) - elif request.uri.endswith('two_ping_while_hello_world'): - self._do_two_ping_while_hello_world( - request, self.channel_events[request.channel_id]) - elif request.uri.endswith('ping_while_hello_world'): - self._do_ping_while_hello_world( - request, self.channel_events[request.channel_id]) - else: - raise ValueError('Cannot handle path %r' % request.path) - if not request.server_terminated: - request.ws_stream.close_connection() - except ConnectionTerminatedException as e: - self.channel_events[request.channel_id].exception = e - except Exception as e: - self.channel_events[request.channel_id].exception = e - raise - - -def _create_mock_request(connection=None, logical_channel_extensions=None): - if connection is None: - connection = _MockMuxConnection() - - request = mock.MockRequest(uri='/echo', - headers_in=_TEST_HEADERS, - connection=connection) - request.ws_stream = Stream(request, options=StreamOptions()) - request.mux_processor = MuxExtensionProcessor( - common.ExtensionParameter(common.MUX_EXTENSION)) - if logical_channel_extensions is not None: - request.mux_processor.set_extensions(logical_channel_extensions) - request.mux_processor.set_quota(8 * 1024) - return request - - -def _create_add_channel_request_frame(channel_id, encoding, encoded_handshake): - # Allow invalid encoding for testing. - first_byte = ((mux._MUX_OPCODE_ADD_CHANNEL_REQUEST << 5) | encoding) - payload = (chr(first_byte) + - mux._encode_channel_id(channel_id) + - mux._encode_number(len(encoded_handshake)) + - encoded_handshake) - return create_binary_frame( - (mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + payload), mask=True) - - -def _create_drop_channel_frame(channel_id, code=None, message=''): - payload = mux._create_drop_channel(channel_id, code, message) - return create_binary_frame( - (mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + payload), mask=True) - - -def _create_flow_control_frame(channel_id, replenished_quota): - payload = mux._create_flow_control(channel_id, replenished_quota) - return create_binary_frame( - (mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + payload), mask=True) - - -def _create_logical_frame(channel_id, message, opcode=common.OPCODE_BINARY, - fin=True, rsv1=False, rsv2=False, rsv3=False, - mask=True): - bits = chr((fin << 7) | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4) | opcode) - payload = mux._encode_channel_id(channel_id) + bits + message - return create_binary_frame(payload, mask=True) - - -def _create_request_header(path='/echo', extensions=None): - headers = ( - 'GET %s HTTP/1.1\r\n' - 'Host: server.example.com\r\n' - 'Connection: Upgrade\r\n' - 'Origin: http://example.com\r\n') % path - if extensions: - headers += '%s: %s' % ( - common.SEC_WEBSOCKET_EXTENSIONS_HEADER, extensions) - return headers - - -class MuxTest(unittest.TestCase): - """A unittest for mux module.""" - - def test_channel_id_decode(self): - data = '\x00\x01\xbf\xff\xdf\xff\xff\xff\xff\xff\xff' - parser = mux._MuxFramePayloadParser(data) - channel_id = parser.read_channel_id() - self.assertEqual(0, channel_id) - channel_id = parser.read_channel_id() - self.assertEqual(1, channel_id) - channel_id = parser.read_channel_id() - self.assertEqual(2 ** 14 - 1, channel_id) - channel_id = parser.read_channel_id() - self.assertEqual(2 ** 21 - 1, channel_id) - channel_id = parser.read_channel_id() - self.assertEqual(2 ** 29 - 1, channel_id) - self.assertEqual(len(data), parser._read_position) - - def test_channel_id_encode(self): - encoded = mux._encode_channel_id(0) - self.assertEqual('\x00', encoded) - encoded = mux._encode_channel_id(2 ** 14 - 1) - self.assertEqual('\xbf\xff', encoded) - encoded = mux._encode_channel_id(2 ** 14) - self.assertEqual('\xc0@\x00', encoded) - encoded = mux._encode_channel_id(2 ** 21 - 1) - self.assertEqual('\xdf\xff\xff', encoded) - encoded = mux._encode_channel_id(2 ** 21) - self.assertEqual('\xe0 \x00\x00', encoded) - encoded = mux._encode_channel_id(2 ** 29 - 1) - self.assertEqual('\xff\xff\xff\xff', encoded) - # channel_id is too large - self.assertRaises(ValueError, - mux._encode_channel_id, - 2 ** 29) - - def test_read_multiple_control_blocks(self): - # Use AddChannelRequest because it can contain arbitrary length of data - data = ('\x00\x01\x01a' - '\x00\x02\x7d%s' - '\x00\x03\x7e\xff\xff%s' - '\x00\x04\x7f\x00\x00\x00\x00\x00\x01\x00\x00%s') % ( - 'a' * 0x7d, 'b' * 0xffff, 'c' * 0x10000) - parser = mux._MuxFramePayloadParser(data) - blocks = list(parser.read_control_blocks()) - self.assertEqual(4, len(blocks)) - - self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[0].opcode) - self.assertEqual(1, blocks[0].channel_id) - self.assertEqual(1, len(blocks[0].encoded_handshake)) - - self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[1].opcode) - self.assertEqual(2, blocks[1].channel_id) - self.assertEqual(0x7d, len(blocks[1].encoded_handshake)) - - self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[2].opcode) - self.assertEqual(3, blocks[2].channel_id) - self.assertEqual(0xffff, len(blocks[2].encoded_handshake)) - - self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[3].opcode) - self.assertEqual(4, blocks[3].channel_id) - self.assertEqual(0x10000, len(blocks[3].encoded_handshake)) - - self.assertEqual(len(data), parser._read_position) - - def test_read_add_channel_request(self): - data = '\x00\x01\x01a' - parser = mux._MuxFramePayloadParser(data) - blocks = list(parser.read_control_blocks()) - self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[0].opcode) - self.assertEqual(1, blocks[0].channel_id) - self.assertEqual(1, len(blocks[0].encoded_handshake)) - - def test_read_drop_channel(self): - data = '\x60\x01\x00' - parser = mux._MuxFramePayloadParser(data) - blocks = list(parser.read_control_blocks()) - self.assertEqual(1, len(blocks)) - self.assertEqual(1, blocks[0].channel_id) - self.assertEqual(mux._MUX_OPCODE_DROP_CHANNEL, blocks[0].opcode) - self.assertEqual(None, blocks[0].drop_code) - self.assertEqual(0, len(blocks[0].drop_message)) - - data = '\x60\x02\x09\x03\xe8Success' - parser = mux._MuxFramePayloadParser(data) - blocks = list(parser.read_control_blocks()) - self.assertEqual(1, len(blocks)) - self.assertEqual(2, blocks[0].channel_id) - self.assertEqual(mux._MUX_OPCODE_DROP_CHANNEL, blocks[0].opcode) - self.assertEqual(1000, blocks[0].drop_code) - self.assertEqual('Success', blocks[0].drop_message) - - # Reason is too short. - data = '\x60\x01\x01\x00' - parser = mux._MuxFramePayloadParser(data) - self.assertRaises(mux.PhysicalConnectionError, - lambda: list(parser.read_control_blocks())) - - def test_read_flow_control(self): - data = '\x40\x01\x02' - parser = mux._MuxFramePayloadParser(data) - blocks = list(parser.read_control_blocks()) - self.assertEqual(1, len(blocks)) - self.assertEqual(1, blocks[0].channel_id) - self.assertEqual(mux._MUX_OPCODE_FLOW_CONTROL, blocks[0].opcode) - self.assertEqual(2, blocks[0].send_quota) - - def test_read_new_channel_slot(self): - data = '\x80\x01\x02\x02\x03' - parser = mux._MuxFramePayloadParser(data) - # TODO(bashi): Implement - self.assertRaises(mux.PhysicalConnectionError, - lambda: list(parser.read_control_blocks())) - - def test_read_invalid_number_field_in_control_block(self): - # No number field. - data = '' - parser = mux._MuxFramePayloadParser(data) - self.assertRaises(ValueError, parser._read_number) - - # The last two bytes are missing. - data = '\x7e' - parser = mux._MuxFramePayloadParser(data) - self.assertRaises(ValueError, parser._read_number) - - # Missing the last one byte. - data = '\x7f\x00\x00\x00\x00\x00\x01\x00' - parser = mux._MuxFramePayloadParser(data) - self.assertRaises(ValueError, parser._read_number) - - # The length of number field is too large. - data = '\x7f\xff\xff\xff\xff\xff\xff\xff\xff' - parser = mux._MuxFramePayloadParser(data) - self.assertRaises(ValueError, parser._read_number) - - # The msb of the first byte is set. - data = '\x80' - parser = mux._MuxFramePayloadParser(data) - self.assertRaises(ValueError, parser._read_number) - - # Using 3 bytes encoding for 125. - data = '\x7e\x00\x7d' - parser = mux._MuxFramePayloadParser(data) - self.assertRaises(ValueError, parser._read_number) - - # Using 9 bytes encoding for 0xffff - data = '\x7f\x00\x00\x00\x00\x00\x00\xff\xff' - parser = mux._MuxFramePayloadParser(data) - self.assertRaises(ValueError, parser._read_number) - - def test_read_invalid_size_and_contents(self): - # Only contain number field. - data = '\x01' - parser = mux._MuxFramePayloadParser(data) - self.assertRaises(mux.PhysicalConnectionError, - parser._read_size_and_contents) - - def test_create_add_channel_response(self): - data = mux._create_add_channel_response(channel_id=1, - encoded_handshake='FooBar', - encoding=0, - rejected=False) - self.assertEqual('\x20\x01\x06FooBar', data) - - data = mux._create_add_channel_response(channel_id=2, - encoded_handshake='Hello', - encoding=1, - rejected=True) - self.assertEqual('\x31\x02\x05Hello', data) - - def test_create_drop_channel(self): - data = mux._create_drop_channel(channel_id=1) - self.assertEqual('\x60\x01\x00', data) - - data = mux._create_drop_channel(channel_id=1, - code=2000, - message='error') - self.assertEqual('\x60\x01\x07\x07\xd0error', data) - - # reason must be empty if code is None - self.assertRaises(ValueError, - mux._create_drop_channel, - 1, None, 'FooBar') - - def test_parse_request_text(self): - request_text = _create_request_header() - command, path, version, headers = mux._parse_request_text(request_text) - self.assertEqual('GET', command) - self.assertEqual('/echo', path) - self.assertEqual('HTTP/1.1', version) - self.assertEqual(3, len(headers)) - self.assertEqual('server.example.com', headers['Host']) - self.assertEqual('http://example.com', headers['Origin']) - - -class MuxHandlerTest(unittest.TestCase): - - def test_add_channel(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=6) - request.connection.put_bytes(flow_control) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=3, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=3, - replenished_quota=6) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Hello')) - request.connection.put_bytes( - _create_logical_frame(channel_id=3, message='World')) - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=3, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - self.assertEqual([], dispatcher.channel_events[1].messages) - self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) - self.assertEqual(['World'], dispatcher.channel_events[3].messages) - # Channel 2 - messages = request.connection.get_written_messages(2) - self.assertEqual(1, len(messages)) - self.assertEqual('Hello', messages[0]) - # Channel 3 - messages = request.connection.get_written_messages(3) - self.assertEqual(1, len(messages)) - self.assertEqual('World', messages[0]) - control_blocks = request.connection.get_written_control_blocks() - # There should be 8 control blocks: - # - 1 NewChannelSlot - # - 2 AddChannelResponses for channel id 2 and 3 - # - 6 FlowControls for channel id 1 (initialize), 'Hello', 'World', - # and 3 'Goodbye's - self.assertEqual(9, len(control_blocks)) - - def test_physical_connection_write_failure(self): - # Use _FailOnWriteConnection. - request = _create_mock_request(connection=_FailOnWriteConnection()) - - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - - # Let the worker echo back 'Hello'. It causes _FailOnWriteConnection - # raising an exception. - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Hello')) - - # Let the worker exit. This will be unnecessary when - # _LogicalConnection.write() is changed to throw an exception if - # woke up by on_writer_done. - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - # All threads should be done. - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - def test_send_blocked(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - # On receiving this 'Hello', the server tries to echo back 'Hello', - # but it will be blocked since there's no send quota available for the - # channel 2. - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Hello')) - - # Wait until the worker is blocked due to send quota shortage. - time.sleep(1) - - # Close the channel 2. The worker should be notified of the end of - # writer thread and stop waiting for send quota to be replenished. - drop_channel = _create_drop_channel_frame(channel_id=2) - - request.connection.put_bytes(drop_channel) - - # Make sure the channel 1 is also closed. - drop_channel = _create_drop_channel_frame(channel_id=1) - request.connection.put_bytes(drop_channel) - - # All threads should be done. - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - def test_add_channel_delta_encoding(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - delta = 'GET /echo HTTP/1.1\r\n\r\n' - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=1, encoded_handshake=delta) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=6) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Hello')) - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) - messages = request.connection.get_written_messages(2) - self.assertEqual(1, len(messages)) - self.assertEqual('Hello', messages[0]) - - def test_add_channel_delta_encoding_override(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - # Override Sec-WebSocket-Protocol. - delta = ('GET /echo HTTP/1.1\r\n' - 'Sec-WebSocket-Protocol: x-foo\r\n' - '\r\n') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=1, encoded_handshake=delta) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=6) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Hello')) - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) - messages = request.connection.get_written_messages(2) - self.assertEqual(1, len(messages)) - self.assertEqual('Hello', messages[0]) - self.assertEqual('x-foo', - dispatcher.channel_events[2].request.ws_protocol) - - def test_add_channel_delta_after_identity(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - # Sec-WebSocket-Protocol is different from client's opening handshake - # of the physical connection. - # TODO(bashi): Remove Upgrade, Connection, Sec-WebSocket-Key and - # Sec-WebSocket-Version. - encoded_handshake = ( - 'GET /echo HTTP/1.1\r\n' - 'Host: server.example.com\r\n' - 'Sec-WebSocket-Protocol: x-foo\r\n' - 'Connection: Upgrade\r\n' - 'Origin: http://example.com\r\n' - '\r\n') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=6) - request.connection.put_bytes(flow_control) - - delta = 'GET /echo HTTP/1.1\r\n\r\n' - add_channel_request = _create_add_channel_request_frame( - channel_id=3, encoding=1, encoded_handshake=delta) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=3, - replenished_quota=6) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Hello')) - request.connection.put_bytes( - _create_logical_frame(channel_id=3, message='World')) - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=3, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - self.assertEqual([], dispatcher.channel_events[1].messages) - self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) - self.assertEqual(['World'], dispatcher.channel_events[3].messages) - # Channel 2 - messages = request.connection.get_written_messages(2) - self.assertEqual(1, len(messages)) - self.assertEqual('Hello', messages[0]) - # Channel 3 - messages = request.connection.get_written_messages(3) - self.assertEqual(1, len(messages)) - self.assertEqual('World', messages[0]) - # Handshake base should be updated. - self.assertEqual( - 'x-foo', - mux_handler._handshake_base._headers['Sec-WebSocket-Protocol']) - - def test_add_channel_delta_remove_header(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - # Override handshake delta base. - encoded_handshake = ( - 'GET /echo HTTP/1.1\r\n' - 'Host: server.example.com\r\n' - 'Sec-WebSocket-Protocol: x-foo\r\n' - 'Connection: Upgrade\r\n' - 'Origin: http://example.com\r\n' - '\r\n') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=6) - request.connection.put_bytes(flow_control) - - # Remove Sec-WebSocket-Protocol header. - delta = ('GET /echo HTTP/1.1\r\n' - 'Sec-WebSocket-Protocol:' - '\r\n') - add_channel_request = _create_add_channel_request_frame( - channel_id=3, encoding=1, encoded_handshake=delta) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=3, - replenished_quota=6) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Hello')) - request.connection.put_bytes( - _create_logical_frame(channel_id=3, message='World')) - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=3, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - self.assertEqual([], dispatcher.channel_events[1].messages) - self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) - self.assertEqual(['World'], dispatcher.channel_events[3].messages) - # Channel 2 - messages = request.connection.get_written_messages(2) - self.assertEqual(1, len(messages)) - self.assertEqual('Hello', messages[0]) - # Channel 3 - messages = request.connection.get_written_messages(3) - self.assertEqual(1, len(messages)) - self.assertEqual('World', messages[0]) - self.assertEqual( - 'x-foo', - dispatcher.channel_events[2].request.ws_protocol) - self.assertEqual( - None, - dispatcher.channel_events[3].request.ws_protocol) - - def test_add_channel_delta_encoding_permessage_deflate(self): - # Enable permessage deflate extension on the implicitly opened channel. - extensions = common.parse_extensions( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_mock_request( - logical_channel_extensions=extensions) - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - delta = 'GET /echo HTTP/1.1\r\n\r\n' - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=1, encoded_handshake=delta) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=20) - request.connection.put_bytes(flow_control) - - # Send compressed 'Hello' on logical channel 1 and 2. - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello = compressed_hello[:-4] - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message=compressed_hello, - rsv1=True)) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message=compressed_hello, - rsv1=True)) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - self.assertEqual(['Hello'], dispatcher.channel_events[1].messages) - self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) - # Written 'Hello's should be compressed. - messages = request.connection.get_written_messages(1) - self.assertEqual(1, len(messages)) - self.assertEqual(compressed_hello, messages[0]) - messages = request.connection.get_written_messages(2) - self.assertEqual(1, len(messages)) - self.assertEqual(compressed_hello, messages[0]) - - def test_add_channel_delta_encoding_remove_extensions(self): - # Enable permessage deflate extension on the implicitly opened channel. - extensions = common.parse_extensions( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_mock_request( - logical_channel_extensions=extensions) - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - # Remove permessage deflate extension. - delta = ('GET /echo HTTP/1.1\r\n' - 'Sec-WebSocket-Extensions:\r\n' - '\r\n') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=1, encoded_handshake=delta) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=20) - request.connection.put_bytes(flow_control) - - # Send compressed message on logical channel 2. The message should - # be rejected (since rsv1 is set). - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - compressed_hello = compress.compress('Hello') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello = compressed_hello[:-4] - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message=compressed_hello, - rsv1=True)) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - drop_channel = next( - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(mux._DROP_CODE_NORMAL_CLOSURE, drop_channel.drop_code) - self.assertEqual(2, drop_channel.channel_id) - # UnsupportedFrameException should be raised on logical channel 2. - self.assertTrue(isinstance(dispatcher.channel_events[2].exception, - UnsupportedFrameException)) - - def test_add_channel_invalid_encoding(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=3, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - drop_channel = next( - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(mux._DROP_CODE_UNKNOWN_REQUEST_ENCODING, - drop_channel.drop_code) - self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, - request.connection.server_close_code) - - def test_add_channel_incomplete_handshake(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - incomplete_encoded_handshake = 'GET /echo HTTP/1.1' - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=incomplete_encoded_handshake) - request.connection.put_bytes(add_channel_request) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - self.assertTrue(1 in dispatcher.channel_events) - self.assertTrue(not 2 in dispatcher.channel_events) - - def test_add_channel_duplicate_channel_id(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - drop_channel = next( - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(mux._DROP_CODE_CHANNEL_ALREADY_EXISTS, - drop_channel.drop_code) - self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, - request.connection.server_close_code) - - def test_receive_drop_channel(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - drop_channel = _create_drop_channel_frame(channel_id=2) - request.connection.put_bytes(drop_channel) - - # Terminate implicitly opened channel. - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - exception = dispatcher.channel_events[2].exception - self.assertTrue(exception.__class__ == ConnectionTerminatedException) - - def test_receive_ping_frame(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=13) - request.connection.put_bytes(flow_control) - - ping_frame = _create_logical_frame(channel_id=2, - message='Hello World!', - opcode=common.OPCODE_PING) - request.connection.put_bytes(ping_frame) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - messages = request.connection.get_written_control_messages(2) - self.assertEqual(common.OPCODE_PONG, messages[0]['opcode']) - self.assertEqual('Hello World!', messages[0]['message']) - - def test_receive_fragmented_ping(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=13) - request.connection.put_bytes(flow_control) - - # Send a ping with message 'Hello world!' in two fragmented frames. - ping_frame1 = _create_logical_frame(channel_id=2, - message='Hello ', - fin=False, - opcode=common.OPCODE_PING) - request.connection.put_bytes(ping_frame1) - ping_frame2 = _create_logical_frame(channel_id=2, - message='World!', - fin=True, - opcode=common.OPCODE_CONTINUATION) - request.connection.put_bytes(ping_frame2) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - messages = request.connection.get_written_control_messages(2) - self.assertEqual(common.OPCODE_PONG, messages[0]['opcode']) - self.assertEqual('Hello World!', messages[0]['message']) - - def test_receive_fragmented_ping_while_receiving_fragmented_message(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=19) - request.connection.put_bytes(flow_control) - - # Send a fragmented frame of message 'Hello '. - hello = _create_logical_frame(channel_id=2, - message='Hello ', - fin=False) - request.connection.put_bytes(hello) - - # Before sending the last fragmented frame of the message, send a - # fragmented ping. - ping1 = _create_logical_frame(channel_id=2, - message='Pi', - fin=False, - opcode=common.OPCODE_PING) - request.connection.put_bytes(ping1) - ping2 = _create_logical_frame(channel_id=2, - message='ng!', - fin=True, - opcode=common.OPCODE_CONTINUATION) - request.connection.put_bytes(ping2) - - # Send the last fragmented frame of the message. - world = _create_logical_frame(channel_id=2, - message='World!', - fin=True, - opcode=common.OPCODE_CONTINUATION) - request.connection.put_bytes(world) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - messages = request.connection.get_written_messages(2) - self.assertEqual(['Hello World!'], messages) - control_messages = request.connection.get_written_control_messages(2) - self.assertEqual(common.OPCODE_PONG, control_messages[0]['opcode']) - self.assertEqual('Ping!', control_messages[0]['message']) - - def test_receive_two_ping_while_receiving_fragmented_message(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=25) - request.connection.put_bytes(flow_control) - - # Send a fragmented frame of message 'Hello '. - hello = _create_logical_frame(channel_id=2, - message='Hello ', - fin=False) - request.connection.put_bytes(hello) - - # Before sending the last fragmented frame of the message, send a - # fragmented ping and a non-fragmented ping. - ping1 = _create_logical_frame(channel_id=2, - message='Pi', - fin=False, - opcode=common.OPCODE_PING) - request.connection.put_bytes(ping1) - ping2 = _create_logical_frame(channel_id=2, - message='ng!', - fin=True, - opcode=common.OPCODE_CONTINUATION) - request.connection.put_bytes(ping2) - ping3 = _create_logical_frame(channel_id=2, - message='Pong!', - fin=True, - opcode=common.OPCODE_PING) - request.connection.put_bytes(ping3) - - # Send the last fragmented frame of the message. - world = _create_logical_frame(channel_id=2, - message='World!', - fin=True, - opcode=common.OPCODE_CONTINUATION) - request.connection.put_bytes(world) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - messages = request.connection.get_written_messages(2) - self.assertEqual(['Hello World!'], messages) - control_messages = request.connection.get_written_control_messages(2) - self.assertEqual(common.OPCODE_PONG, control_messages[0]['opcode']) - self.assertEqual('Ping!', control_messages[0]['message']) - self.assertEqual(common.OPCODE_PONG, control_messages[1]['opcode']) - self.assertEqual('Pong!', control_messages[1]['message']) - - def test_receive_message_while_receiving_fragmented_ping(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=19) - request.connection.put_bytes(flow_control) - - # Send a fragmented ping. - ping1 = _create_logical_frame(channel_id=2, - message='Pi', - fin=False, - opcode=common.OPCODE_PING) - request.connection.put_bytes(ping1) - - # Before sending the last fragmented ping, send a message. - # The logical channel (2) should be dropped. - message = _create_logical_frame(channel_id=2, - message='Hello world!', - fin=True) - request.connection.put_bytes(message) - - # Send the last fragmented frame of the message. - ping2 = _create_logical_frame(channel_id=2, - message='ng!', - fin=True, - opcode=common.OPCODE_CONTINUATION) - request.connection.put_bytes(ping2) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - drop_channel = next( - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(2, drop_channel.channel_id) - # No message should be sent on channel 2. - self.assertRaises(KeyError, - request.connection.get_written_messages, - 2) - self.assertRaises(KeyError, - request.connection.get_written_control_messages, - 2) - - def test_send_ping(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/ping') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=6) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - messages = request.connection.get_written_control_messages(2) - self.assertEqual(common.OPCODE_PING, messages[0]['opcode']) - self.assertEqual('Ping!', messages[0]['message']) - - def test_send_fragmented_ping(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/ping') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - # Replenish 3 bytes. This isn't enough to send the whole ping frame - # because the frame will have 5 bytes message('Ping!'). The frame - # should be fragmented. - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=3) - request.connection.put_bytes(flow_control) - - # Wait until the worker is blocked due to send quota shortage. - time.sleep(1) - - # Replenish remaining 2 + 1 bytes (including extra cost). - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=3) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - messages = request.connection.get_written_control_messages(2) - self.assertEqual(common.OPCODE_PING, messages[0]['opcode']) - self.assertEqual('Ping!', messages[0]['message']) - - def test_send_fragmented_ping_while_sending_fragmented_message(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header( - path='/ping_while_hello_world') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - # Application will send: - # - text message 'Hello ' with fin=0 - # - ping with 'Ping!' message - # - text message 'World!' with fin=1 - # Replenish (6 + 1) + (2 + 1) bytes so that the ping will be - # fragmented on the logical channel. - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=10) - request.connection.put_bytes(flow_control) - - time.sleep(1) - - # Replenish remaining 3 + 6 bytes. - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=9) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - messages = request.connection.get_written_messages(2) - self.assertEqual(['Hello World!'], messages) - control_messages = request.connection.get_written_control_messages(2) - self.assertEqual(common.OPCODE_PING, control_messages[0]['opcode']) - self.assertEqual('Ping!', control_messages[0]['message']) - - def test_send_fragmented_two_ping_while_sending_fragmented_message(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header( - path='/two_ping_while_hello_world') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - # Application will send: - # - text message 'Hello ' with fin=0 - # - ping with 'Ping!' message - # - ping with 'Pong!' message - # - text message 'World!' with fin=1 - # Replenish (6 + 1) + (2 + 1) bytes so that the first ping will be - # fragmented on the logical channel. - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=10) - request.connection.put_bytes(flow_control) - - time.sleep(1) - - # Replenish remaining 3 + (5 + 1) + 6 bytes. The second ping won't - # be fragmented on the logical channel. - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=15) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - messages = request.connection.get_written_messages(2) - self.assertEqual(['Hello World!'], messages) - control_messages = request.connection.get_written_control_messages(2) - self.assertEqual(common.OPCODE_PING, control_messages[0]['opcode']) - self.assertEqual('Ping!', control_messages[0]['message']) - self.assertEqual(common.OPCODE_PING, control_messages[1]['opcode']) - self.assertEqual('Pong!', control_messages[1]['message']) - - def test_send_drop_channel(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - - # DropChannel for channel id 1 which doesn't have reason. - frame = create_binary_frame('\x00\x60\x01\x00', mask=True) - request.connection.put_bytes(frame) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - drop_channel = next( - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(mux._DROP_CODE_ACKNOWLEDGED, - drop_channel.drop_code) - self.assertEqual(1, drop_channel.channel_id) - - def test_two_flow_control(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - # Replenish 5 bytes. - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=5) - request.connection.put_bytes(flow_control) - - # Send 10 bytes. The server will try echo back 10 bytes. - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='HelloWorld')) - - # Replenish 5 + 1 (per-message extra cost) bytes. - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=6) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - messages = request.connection.get_written_messages(2) - self.assertEqual(['HelloWorld'], messages) - received_flow_controls = [ - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_FLOW_CONTROL and b.channel_id == 2] - # Replenishment for 'HelloWorld' + 1 - self.assertEqual(11, received_flow_controls[0].send_quota) - # Replenishment for 'Goodbye' + 1 - self.assertEqual(8, received_flow_controls[1].send_quota) - - def test_no_send_quota_on_server(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='HelloWorld')) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - # Just wait for 1 sec so that the server attempts to echo back - # 'HelloWorld'. - self.assertFalse(mux_handler.wait_until_done(timeout=1)) - - # No message should be sent on channel 2. - self.assertRaises(KeyError, - request.connection.get_written_messages, - 2) - - def test_no_send_quota_on_server_for_permessage_extra_cost(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=6) - request.connection.put_bytes(flow_control) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Hello')) - # Replenish only len('World') bytes. - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=5) - request.connection.put_bytes(flow_control) - # Server should not callback for this message. - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='World')) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - # Just wait for 1 sec so that the server attempts to echo back - # 'World'. - self.assertFalse(mux_handler.wait_until_done(timeout=1)) - - # Only one message should be sent on channel 2. - messages = request.connection.get_written_messages(2) - self.assertEqual(['Hello'], messages) - - def test_quota_violation_by_client(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, 0) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='HelloWorld')) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - control_blocks = request.connection.get_written_control_blocks() - self.assertEqual(5, len(control_blocks)) - drop_channel = next( - b for b in control_blocks - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(mux._DROP_CODE_SEND_QUOTA_VIOLATION, - drop_channel.drop_code) - - def test_consume_quota_empty_message(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - # Client has 1 byte quota. - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, 1) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=2) - request.connection.put_bytes(flow_control) - # Send an empty message. Pywebsocket always replenishes 1 byte quota - # for empty message - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='')) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - # This message violates quota on channel id 2. - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - self.assertEqual(1, len(dispatcher.channel_events[2].messages)) - self.assertEqual('', dispatcher.channel_events[2].messages[0]) - - received_flow_controls = [ - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_FLOW_CONTROL and b.channel_id == 2] - self.assertEqual(1, len(received_flow_controls)) - self.assertEqual(1, received_flow_controls[0].send_quota) - - drop_channel = next( - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(2, drop_channel.channel_id) - self.assertEqual(mux._DROP_CODE_SEND_QUOTA_VIOLATION, - drop_channel.drop_code) - - def test_consume_quota_fragmented_message(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - # Client has len('Hello') + len('Goodbye') + 2 bytes quota. - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, 14) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=6) - request.connection.put_bytes(flow_control) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='He', fin=False, - opcode=common.OPCODE_TEXT)) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='llo', fin=True, - opcode=common.OPCODE_CONTINUATION)) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - messages = request.connection.get_written_messages(2) - self.assertEqual(['Hello'], messages) - - def test_fragmented_control_message(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/ping') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - # Replenish total 6 bytes in 3 FlowControls. - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=1) - request.connection.put_bytes(flow_control) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=2) - request.connection.put_bytes(flow_control) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=3) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - messages = request.connection.get_written_control_messages(2) - self.assertEqual(common.OPCODE_PING, messages[0]['opcode']) - self.assertEqual('Ping!', messages[0]['message']) - - def test_channel_slot_violation_by_client(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(slots=1, - send_quota=mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=6) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Hello')) - - # This request should be rejected. - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=3, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - flow_control = _create_flow_control_frame(channel_id=3, - replenished_quota=6) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=3, message='Hello')) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - self.assertEqual([], dispatcher.channel_events[1].messages) - self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) - self.assertFalse(dispatcher.channel_events.has_key(3)) - drop_channel = next( - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(3, drop_channel.channel_id) - self.assertEqual(mux._DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION, - drop_channel.drop_code) - - def test_quota_overflow_by_client(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(slots=1, - send_quota=mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - # Replenish 0x7FFFFFFFFFFFFFFF bytes twice. - flow_control = _create_flow_control_frame( - channel_id=2, - replenished_quota=0x7FFFFFFFFFFFFFFF) - request.connection.put_bytes(flow_control) - request.connection.put_bytes(flow_control) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - drop_channel = next( - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(2, drop_channel.channel_id) - self.assertEqual(mux._DROP_CODE_SEND_QUOTA_OVERFLOW, - drop_channel.drop_code) - - def test_invalid_encapsulated_message(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - - first_byte = (mux._MUX_OPCODE_ADD_CHANNEL_REQUEST << 5) - block = (chr(first_byte) + - mux._encode_channel_id(1) + - mux._encode_number(0)) - payload = mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + block - text_frame = create_binary_frame(payload, opcode=common.OPCODE_TEXT, - mask=True) - request.connection.put_bytes(text_frame) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - drop_channel = next( - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(mux._DROP_CODE_INVALID_ENCAPSULATING_MESSAGE, - drop_channel.drop_code) - self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, - request.connection.server_close_code) - - def test_channel_id_truncated(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - - # The last byte of the channel id is missing. - frame = create_binary_frame('\x80', mask=True) - request.connection.put_bytes(frame) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - drop_channel = next( - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(mux._DROP_CODE_CHANNEL_ID_TRUNCATED, - drop_channel.drop_code) - self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, - request.connection.server_close_code) - - def test_inner_frame_truncated(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - - # Just contain channel id 1. - frame = create_binary_frame('\x01', mask=True) - request.connection.put_bytes(frame) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - drop_channel = next( - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(mux._DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED, - drop_channel.drop_code) - self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, - request.connection.server_close_code) - - def test_unknown_mux_opcode(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - - # Undefined opcode 5 - frame = create_binary_frame('\x00\xa0', mask=True) - request.connection.put_bytes(frame) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - drop_channel = next( - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(mux._DROP_CODE_UNKNOWN_MUX_OPCODE, - drop_channel.drop_code) - self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, - request.connection.server_close_code) - - def test_invalid_mux_control_block(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - - # DropChannel contains 1 byte reason - frame = create_binary_frame('\x00\x60\x00\x01\x00', mask=True) - request.connection.put_bytes(frame) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - drop_channel = next( - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) - self.assertEqual(mux._DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - drop_channel.drop_code) - self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, - request.connection.server_close_code) - - def test_permessage_deflate(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - # Enable permessage deflate extension on logical channel 2. - extensions = common.PERMESSAGE_DEFLATE_EXTENSION - encoded_handshake = _create_request_header(path='/echo', - extensions=extensions) - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - flow_control = _create_flow_control_frame(channel_id=2, - replenished_quota=20) - request.connection.put_bytes(flow_control) - - # Send compressed 'Hello' twice. - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - compressed_hello1 = compress.compress('Hello') - compressed_hello1 += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello1 = compressed_hello1[:-4] - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message=compressed_hello1, - rsv1=True)) - compressed_hello2 = compress.compress('Hello') - compressed_hello2 += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello2 = compressed_hello2[:-4] - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message=compressed_hello2, - rsv1=True)) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - request.connection.put_bytes( - _create_logical_frame(channel_id=2, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - self.assertEqual(['Hello', 'Hello'], - dispatcher.channel_events[2].messages) - # Written 'Hello's should be compressed. - messages = request.connection.get_written_messages(2) - self.assertEqual(2, len(messages)) - self.assertEqual(compressed_hello1, messages[0]) - self.assertEqual(compressed_hello2, messages[1]) - - - def test_permessage_deflate_fragmented_message(self): - extensions = common.parse_extensions( - common.PERMESSAGE_DEFLATE_EXTENSION) - request = _create_mock_request( - logical_channel_extensions=extensions) - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - # Send compressed 'HelloHelloHello' as fragmented message. - compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) - compressed_hello = compress.compress('HelloHelloHello') - compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) - compressed_hello = compressed_hello[:-4] - - m = len(compressed_hello) / 2 - request.connection.put_bytes( - _create_logical_frame(channel_id=1, - message=compressed_hello[:m], - fin=False, rsv1=True, - opcode=common.OPCODE_TEXT)) - request.connection.put_bytes( - _create_logical_frame(channel_id=1, - message=compressed_hello[m:], - fin=True, rsv1=False, - opcode=common.OPCODE_CONTINUATION)) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - self.assertEqual(['HelloHelloHello'], - dispatcher.channel_events[1].messages) - messages = request.connection.get_written_messages(1) - self.assertEqual(1, len(messages)) - self.assertEqual(compressed_hello, messages[0]) - - def test_receive_bad_fragmented_message(self): - request = _create_mock_request() - dispatcher = _MuxMockDispatcher() - mux_handler = mux._MuxHandler(request, dispatcher) - mux_handler.start() - mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, - mux._INITIAL_QUOTA_FOR_CLIENT) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=2, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - # Send a frame with fin=False, and then send a frame with - # opcode=TEXT (not CONTINUATION). Logical channel 2 should be dropped. - frame1 = _create_logical_frame(channel_id=2, - message='Hello ', - fin=False, - opcode=common.OPCODE_TEXT) - request.connection.put_bytes(frame1) - frame2 = _create_logical_frame(channel_id=2, - message='World!', - fin=True, - opcode=common.OPCODE_TEXT) - request.connection.put_bytes(frame2) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=3, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - # Send a frame with opcode=CONTINUATION without a preceding frame - # the fin of which is not set. Logical channel 3 should be dropped. - frame3 = _create_logical_frame(channel_id=3, - message='Hello', - fin=True, - opcode=common.OPCODE_CONTINUATION) - request.connection.put_bytes(frame3) - - encoded_handshake = _create_request_header(path='/echo') - add_channel_request = _create_add_channel_request_frame( - channel_id=4, encoding=0, - encoded_handshake=encoded_handshake) - request.connection.put_bytes(add_channel_request) - - # Send a frame with opcode=PING and fin=False, and then send a frame - # with opcode=TEXT (not CONTINUATION). Logical channel 4 should be - # dropped. - frame4 = _create_logical_frame(channel_id=4, - message='Ping', - fin=False, - opcode=common.OPCODE_PING) - request.connection.put_bytes(frame4) - frame5 = _create_logical_frame(channel_id=4, - message='Hello', - fin=True, - opcode=common.OPCODE_TEXT) - request.connection.put_bytes(frame5) - - request.connection.put_bytes( - _create_logical_frame(channel_id=1, message='Goodbye')) - - self.assertTrue(mux_handler.wait_until_done(timeout=2)) - - drop_channels = [ - b for b in request.connection.get_written_control_blocks() - if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL] - self.assertEqual(3, len(drop_channels)) - for d in drop_channels: - self.assertEqual(mux._DROP_CODE_BAD_FRAGMENTATION, - d.drop_code) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/test/test_stream_hixie75.py b/tests/wpt/web-platform-tests/tools/pywebsocket/test/test_stream_hixie75.py deleted file mode 100755 index ca9ac7130b7..00000000000 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/test/test_stream_hixie75.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Tests for stream module.""" - - -import unittest - -import set_sys_path # Update sys.path to locate mod_pywebsocket module. - -from mod_pywebsocket.stream import StreamHixie75 -from test.test_msgutil import _create_request_hixie75 - - -class StreamHixie75Test(unittest.TestCase): - """A unittest for StreamHixie75 class.""" - - def test_payload_length(self): - for length, bytes in ((0, '\x00'), (0x7f, '\x7f'), (0x80, '\x81\x00'), - (0x1234, '\x80\xa4\x34')): - test_stream = StreamHixie75(_create_request_hixie75(bytes)) - self.assertEqual( - length, test_stream._read_payload_length_hixie75()) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/tests/wpt/web-platform-tests/tools/serve/serve.py b/tests/wpt/web-platform-tests/tools/serve/serve.py index 055b60f1e76..f63bdcca5b0 100644 --- a/tests/wpt/web-platform-tests/tools/serve/serve.py +++ b/tests/wpt/web-platform-tests/tools/serve/serve.py @@ -592,21 +592,9 @@ class WebSocketDaemon(object): "-w", handlers_root] if ssl_config is not None: - # This is usually done through pywebsocket.main, however we're - # working around that to get the server instance and manually - # setup the wss server. - if pywebsocket._import_ssl(): - tls_module = pywebsocket._TLS_BY_STANDARD_MODULE - elif pywebsocket._import_pyopenssl(): - tls_module = pywebsocket._TLS_BY_PYOPENSSL - else: - print("No SSL module available") - sys.exit(1) - cmd_args += ["--tls", "--private-key", ssl_config["key_path"], - "--certificate", ssl_config["cert_path"], - "--tls-module", tls_module] + "--certificate", ssl_config["cert_path"]] if (bind_address): cmd_args = ["-H", host] + cmd_args diff --git a/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/.travis.yml b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/.travis.yml new file mode 100644 index 00000000000..2065a644dd6 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/.travis.yml @@ -0,0 +1,17 @@ +language: python +python: + - 2.7 + - 3.5 + - 3.6 + - 3.7 + - 3.8 + - nightly + +matrix: + allow_failures: + - python: 3.5, nightly +install: + - pip install six yapf +script: + - python test/run_all.py + - yapf --diff --recursive . diff --git a/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/CONTRIBUTING b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/CONTRIBUTING new file mode 100644 index 00000000000..f975be126fb --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/CONTRIBUTING @@ -0,0 +1,30 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. +For instructions for contributing code, please read: +https://github.com/google/pywebsocket/wiki/CodeReviewInstruction + +## Community Guidelines + +This project follows +[Google's Open Source Community Guidelines](https://opensource.google/conduct/). diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/LICENSE b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/LICENSE similarity index 98% rename from tests/wpt/web-platform-tests/tools/pywebsocket/LICENSE rename to tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/LICENSE index 989d02e4cd0..c91bea90254 100644 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/LICENSE +++ b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/LICENSE @@ -1,4 +1,4 @@ -Copyright 2012, Google Inc. +Copyright 2020, Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/MANIFEST.in b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/MANIFEST.in similarity index 100% rename from tests/wpt/web-platform-tests/tools/pywebsocket/MANIFEST.in rename to tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/MANIFEST.in diff --git a/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/README.md b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/README.md new file mode 100644 index 00000000000..277cbe550c0 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/README.md @@ -0,0 +1,36 @@ + +# pywebsocket3 # + +The pywebsocket project aims to provide a [WebSocket](https://tools.ietf.org/html/rfc6455) standalone server. + +pywebsocket is intended for **testing** or **experimental** purposes. + +Run this to read the general document: +``` +$ pydoc mod_pywebsocket +``` + +Please see [Wiki](../../wiki) for more details. + +# INSTALL # + +To install this package to the system, run this: +``` +$ python setup.py build +$ sudo python setup.py install +``` + +To install this package as a normal user, run this instead: + +``` +$ python setup.py build +$ python setup.py install --user +``` +# LAUNCH # + +To use pywebsocket as standalone server, run this to read the document: +``` +$ pydoc mod_pywebsocket.standalone +``` +# Disclaimer # +This is not an officially supported Google product diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/abort_handshake_wsh.py b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/abort_handshake_wsh.py similarity index 97% rename from tests/wpt/web-platform-tests/tools/pywebsocket/example/abort_handshake_wsh.py rename to tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/abort_handshake_wsh.py index 008023a1fe9..1b719ca8973 100644 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/abort_handshake_wsh.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/abort_handshake_wsh.py @@ -27,7 +27,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - +from __future__ import absolute_import from mod_pywebsocket import handshake diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/abort_wsh.py b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/abort_wsh.py similarity index 97% rename from tests/wpt/web-platform-tests/tools/pywebsocket/example/abort_wsh.py rename to tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/abort_wsh.py index 2bbf005f6a3..d4c240bf2c1 100644 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/abort_wsh.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/abort_wsh.py @@ -27,7 +27,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - +from __future__ import absolute_import from mod_pywebsocket import handshake diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/arraybuffer_benchmark.html b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/arraybuffer_benchmark.html similarity index 100% rename from tests/wpt/web-platform-tests/tools/pywebsocket/example/arraybuffer_benchmark.html rename to tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/arraybuffer_benchmark.html diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/example/bench_wsh.py b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/bench_wsh.py similarity index 96% rename from tests/wpt/web-platform-tests/tools/pywebsocket/example/bench_wsh.py rename to tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/bench_wsh.py index 5067ca7d895..2df50e77db2 100644 --- a/tests/wpt/web-platform-tests/tools/pywebsocket/example/bench_wsh.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pywebsocket3/example/bench_wsh.py @@ -26,8 +26,6 @@ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - """A simple load tester for WebSocket clients. A client program sends a message formatted as "