diff --git a/components/net/websocket_loader.rs b/components/net/websocket_loader.rs index fb24e6566a4..c4a8483837a 100644 --- a/components/net/websocket_loader.rs +++ b/components/net/websocket_loader.rs @@ -268,21 +268,31 @@ pub fn init( let dom_action = message.to().expect("Ws dom_action message to deserialize"); match dom_action { WebSocketDomAction::SendMessage(MessageData::Text(data)) => { - ws_sender.send(Message::text(data)).unwrap(); + if let Err(e) = ws_sender.send(Message::text(data)) { + warn!("Error sending websocket message: {:?}", e); + } }, WebSocketDomAction::SendMessage(MessageData::Binary(data)) => { - ws_sender.send(Message::binary(data)).unwrap(); + if let Err(e) = ws_sender.send(Message::binary(data)) { + warn!("Error sending websocket message: {:?}", e); + } }, WebSocketDomAction::Close(code, reason) => { if !initiated_close.fetch_or(true, Ordering::SeqCst) { match code { - Some(code) => ws_sender - .close_with_reason( + Some(code) => { + if let Err(e) = ws_sender.close_with_reason( code.into(), reason.unwrap_or("".to_owned()), - ) - .unwrap(), - None => ws_sender.close(CloseCode::Status).unwrap(), + ) { + warn!("Error closing websocket: {:?}", e); + } + }, + None => { + if let Err(e) = ws_sender.close(CloseCode::Status) { + warn!("Error closing websocket: {:?}", e); + } + }, }; } }, diff --git a/tests/wpt/metadata-layout-2020/css/cssom-view/idlharness.html.ini b/tests/wpt/metadata-layout-2020/css/cssom-view/idlharness.html.ini index ace2c693107..ad53ff7bee7 100644 --- a/tests/wpt/metadata-layout-2020/css/cssom-view/idlharness.html.ini +++ b/tests/wpt/metadata-layout-2020/css/cssom-view/idlharness.html.ini @@ -287,3 +287,192 @@ [Element interface: document.createElement("div") must inherit property "scrollIntoView([object Object\],[object Object\])" with the proper type] expected: FAIL + [Element interface: calling scrollIntoView(optional (boolean or ScrollIntoViewOptions)) on document.createElement("img") with too few arguments must throw TypeError] + expected: FAIL + + [Element interface: document.createElement("img") must inherit property "convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Text interface: document.createTextNode("x") must inherit property "convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [CSSPseudoElement interface: operation convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions)] + expected: FAIL + + [Element interface: document.createElementNS("x", "y") must inherit property "scrollIntoView(optional (boolean or ScrollIntoViewOptions))" with the proper type] + expected: FAIL + + [Element interface: document.createElement("img") must inherit property "getBoxQuads(optional BoxQuadOptions)" with the proper type] + expected: FAIL + + [CSSPseudoElement interface: operation convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions)] + expected: FAIL + + [Document interface: operation getBoxQuads(optional BoxQuadOptions)] + expected: FAIL + + [Element interface: calling getBoxQuads(optional BoxQuadOptions) on document.createElement("img") with too few arguments must throw TypeError] + expected: FAIL + + [Text interface: operation convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions)] + expected: FAIL + + [Element interface: calling getBoxQuads(optional BoxQuadOptions) on document.createElementNS("x", "y") with too few arguments must throw TypeError] + expected: FAIL + + [Document interface: calling getBoxQuads(optional BoxQuadOptions) on document with too few arguments must throw TypeError] + expected: FAIL + + [Document interface: calling convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions) on document with too few arguments must throw TypeError] + expected: FAIL + + [Document interface: operation convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions)] + expected: FAIL + + [Document interface: document must inherit property "convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Element interface: operation convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions)] + expected: FAIL + + [Document interface: operation convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions)] + expected: FAIL + + [Element interface: calling convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions) on document.createElement("img") with too few arguments must throw TypeError] + expected: FAIL + + [CSSPseudoElement interface: operation convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions)] + expected: FAIL + + [Element interface: calling getBoxQuads(optional BoxQuadOptions) on document.createElement("div") with too few arguments must throw TypeError] + expected: FAIL + + [Element interface: document.createElement("img") must inherit property "convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Element interface: calling convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions) on document.createElement("div") with too few arguments must throw TypeError] + expected: FAIL + + [Element interface: calling convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions) on document.createElementNS("x", "y") with too few arguments must throw TypeError] + expected: FAIL + + [Element interface: calling convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions) on document.createElementNS("x", "y") with too few arguments must throw TypeError] + expected: FAIL + + [Text interface: document.createTextNode("x") must inherit property "convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Element interface: calling convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions) on document.createElement("div") with too few arguments must throw TypeError] + expected: FAIL + + [Document interface: calling convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions) on document with too few arguments must throw TypeError] + expected: FAIL + + [Element interface: calling convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions) on document.createElement("img") with too few arguments must throw TypeError] + expected: FAIL + + [Element interface: operation getBoxQuads(optional BoxQuadOptions)] + expected: FAIL + + [Element interface: document.createElement("div") must inherit property "convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Element interface: operation convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions)] + expected: FAIL + + [Element interface: document.createElementNS("x", "y") must inherit property "convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Element interface: document.createElementNS("x", "y") must inherit property "convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Document interface: operation convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions)] + expected: FAIL + + [Text interface: calling convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions) on document.createTextNode("x") with too few arguments must throw TypeError] + expected: FAIL + + [Element interface: document.createElement("div") must inherit property "convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Text interface: calling convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions) on document.createTextNode("x") with too few arguments must throw TypeError] + expected: FAIL + + [Element interface: document.createElementNS("x", "y") must inherit property "getBoxQuads(optional BoxQuadOptions)" with the proper type] + expected: FAIL + + [Text interface: document.createTextNode("x") must inherit property "convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Text interface: operation getBoxQuads(optional BoxQuadOptions)] + expected: FAIL + + [Document interface: document must inherit property "convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Element interface: calling convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions) on document.createElement("img") with too few arguments must throw TypeError] + expected: FAIL + + [Text interface: operation convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions)] + expected: FAIL + + [Text interface: calling convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions) on document.createTextNode("x") with too few arguments must throw TypeError] + expected: FAIL + + [Text interface: calling getBoxQuads(optional BoxQuadOptions) on document.createTextNode("x") with too few arguments must throw TypeError] + expected: FAIL + + [Element interface: calling scrollIntoView(optional (boolean or ScrollIntoViewOptions)) on document.createElement("div") with too few arguments must throw TypeError] + expected: FAIL + + [Element interface: document.createElement("img") must inherit property "scrollIntoView(optional (boolean or ScrollIntoViewOptions))" with the proper type] + expected: FAIL + + [Element interface: operation scrollIntoView(optional (boolean or ScrollIntoViewOptions))] + expected: FAIL + + [Document interface: document must inherit property "convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Document interface: calling convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions) on document with too few arguments must throw TypeError] + expected: FAIL + + [Text interface: operation convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions)] + expected: FAIL + + [CSSPseudoElement interface: operation getBoxQuads(optional BoxQuadOptions)] + expected: FAIL + + [Element interface: calling scrollIntoView(optional (boolean or ScrollIntoViewOptions)) on document.createElementNS("x", "y") with too few arguments must throw TypeError] + expected: FAIL + + [Element interface: document.createElement("div") must inherit property "scrollIntoView(optional (boolean or ScrollIntoViewOptions))" with the proper type] + expected: FAIL + + [Element interface: document.createElementNS("x", "y") must inherit property "convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Element interface: document.createElement("div") must inherit property "getBoxQuads(optional BoxQuadOptions)" with the proper type] + expected: FAIL + + [Element interface: calling convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions) on document.createElementNS("x", "y") with too few arguments must throw TypeError] + expected: FAIL + + [Document interface: document must inherit property "getBoxQuads(optional BoxQuadOptions)" with the proper type] + expected: FAIL + + [Element interface: calling convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions) on document.createElement("div") with too few arguments must throw TypeError] + expected: FAIL + + [Element interface: document.createElement("img") must inherit property "convertPointFromNode(DOMPointInit, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Element interface: operation convertQuadFromNode(DOMQuadInit, GeometryNode, optional ConvertCoordinateOptions)] + expected: FAIL + + [Element interface: document.createElement("div") must inherit property "convertRectFromNode(DOMRectReadOnly, GeometryNode, optional ConvertCoordinateOptions)" with the proper type] + expected: FAIL + + [Text interface: document.createTextNode("x") must inherit property "getBoxQuads(optional BoxQuadOptions)" with the proper type] + expected: FAIL + 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 dd4ffcf4345..4c61301124d 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 @@ -9,8 +9,8 @@ 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 diff --git a/tests/wpt/metadata/MANIFEST.json b/tests/wpt/metadata/MANIFEST.json index ce655a6864c..f04c3749914 100644 --- a/tests/wpt/metadata/MANIFEST.json +++ b/tests/wpt/metadata/MANIFEST.json @@ -61,6 +61,13 @@ ] }, "css-flexbox": { + "padding-overflow-crash.html": [ + "fd0c333fabadb073b9977a63de6a8c5c3ed79042", + [ + null, + {} + ] + ], "zero-content-size-with-scrollbar-crash.html": [ "c38659a9d8cff6d0003793896dc83cc78e7a22c4", [ @@ -1868,14 +1875,14 @@ }, "clipboard-apis": { "async-write-blobs-read-blobs-manual.https.html": [ - "b5f0f3d9dc13d2b67264b3a61b9f0f8be0cd6ecd", + "f53d7f9c36e2b26082d180de227dd1675ca769dc", [ null, {} ] ], "async-write-image-read-image-manual.https.html": [ - "6381b453abfeb11f4ae2d03d511da2ba8d934eb3", + "71d14984953974a66f769edf9a3566fe526cdb6f", [ null, {} @@ -1883,28 +1890,28 @@ ], "detached-iframe": { "read-on-detaching-iframe-manual.https.html": [ - "e4a4283c900d974c83b8f438cc64e0dce6d8d2ae", + "8361ceff125c69c4b7f79e8dec4e648deeac4f32", [ null, {} ] ], "write-on-detaching-iframe-manual.https.html": [ - "033cc0fd9f1aa05d55abfbcee0677bb533de3faf", + "4a3dbd7dc327fcee6b8305b9a837b80d933b0fa7", [ null, {} ] ], "write-read-on-detached-iframe-manual.https.html": [ - "31168092480abdfc51a78bce8b407d408955e636", + "af12ff8d880df8f7e8fb72f10bb12ad6acff0060", [ null, {} ] ], "writeText-readText-on-detached-iframe-manual.https.html": [ - "883e8dbd32e4d569a865b4e98d0984251a043709", + "c2d8be1ace2528242eb015b37c9da0e11895e857", [ null, {} @@ -1913,21 +1920,21 @@ }, "events": { "copy-event-manual.html": [ - "6f687af196fa198cda7d83f468945f9f69330568", + "9f9f1950e7cad11da8cc9e8984f130e182d64d64", [ null, {} ] ], "cut-event-manual.html": [ - "c5593171754cfa2bd684e1ff3a8a724283456cbd", + "72c11ec3b98a7839ba6ba22b0340664dcc9782c2", [ null, {} ] ], "paste-event-manual.html": [ - "19e6b95c5f32a0eb7dbccb0f5bd538e9dbb1360e", + "608a0d6f2323c2bda0a7c5b3a73388c13d706ac1", [ null, {} @@ -1936,28 +1943,28 @@ }, "text-write-read": { "async-write-read-manual.https.html": [ - "b374333ca944a5a675f71678f1f30f1216a100d1", + "14488b1e71666089981430538596040accb704d0", [ null, {} ] ], "async-write-readText-manual.https.html": [ - "2d78b2f186e3c761549b578555f3ac6c15145221", + "c76df06a8b0216b7ae6d1c3944d3b063fb694b4f", [ null, {} ] ], "async-writeText-read-manual.https.html": [ - "4f9fe25bca588d47d4d4d045a8fa3afe0cd44314", + "e74726ec5e7f3d37572fd247e85acd91db6e9d77", [ null, {} ] ], "async-writeText-readText-manual.https.html": [ - "48e5adb51d249b0846398a5a742c430809972e87", + "d2c43f0b6f20ad516e4058fe51fc8a0d6aa14d4c", [ null, {} @@ -10086,6 +10093,20 @@ null, {} ] + ], + "page-size-011.xht": [ + "6c9e3bcb11ea8ae2635d7e202c7a1b0ca789aa00", + [ + null, + {} + ] + ], + "page-size-012.xht": [ + "0def30ae455b719335b5b60c701ba2d6f92d2e94", + [ + null, + {} + ] ] }, "css-pseudo": { @@ -136322,7 +136343,7 @@ ] ], "percentage-heights-004.html": [ - "4f162487a711c78f48bf457d3fb0764bb5f2b746", + "3fb0806b63d6b634129d1ba3e804042a762e4d29", [ null, [ @@ -173613,6 +173634,19 @@ {} ] ], + "text-decoration-skip-ink-005.html": [ + "2d91f73e2ee97d3809eacdfccc43215cd2011655", + [ + null, + [ + [ + "/css/css-text-decor/reference/text-decoration-skip-ink-005-notref.html", + "!=" + ] + ], + {} + ] + ], "text-decoration-skip-ink-sidewayslr-001.html": [ "48d427e238253e26ff18f7b3cbef925e987e8b6a", [ @@ -222925,13 +222959,13 @@ {} ] ], - "forced-colors-mode-18.tentative.html": [ - "307420af9fc771978c983d728c3537c06aea249f", + "forced-colors-mode-18.html": [ + "435b9e4326f9a012e3f9a0eacc4cb9761477f3f3", [ null, [ [ - "/forced-colors-mode/forced-colors-mode-18.tentative-ref.html", + "/forced-colors-mode/forced-colors-mode-18-ref.html", "==" ] ], @@ -222977,13 +223011,13 @@ {} ] ], - "forced-colors-mode-26.tentative.html": [ - "ac21cae23805ffebd6f8396f56025ba8210a6add", + "forced-colors-mode-26.html": [ + "a9aa59013217ecdc32908330a4c520a761c0d09b", [ null, [ [ - "/forced-colors-mode/forced-colors-mode-26.tentative-ref.html", + "/forced-colors-mode/forced-colors-mode-26-ref.html", "==" ] ], @@ -229842,7 +229876,7 @@ }, "portals": { "portals-rendering.html": [ - "0eae0ddfd6ef1e9de2251f50914a855f4142b9d5", + "229dacf4e69962e0cde95658a328ad666c0b487e", [ null, [ @@ -236718,7 +236752,7 @@ [] ], "gentestutils.py": [ - "bd1e3c75768a2644ad772578e6f6ee2266db7fb9", + "64ad669b329584ee59a0c9cc424951de26f1df57", [] ], "name2dir.yaml": [ @@ -236742,7 +236776,7 @@ [] ], "tests2d.yaml": [ - "6b971a4b2fc5435a8e802a529b1c925baf605860", + "42a572995a6997b5f61a54ae65abd0dc6b59626d", [] ], "tests2dtext.yaml": [ @@ -282828,7 +282862,7 @@ [] ], "percentage-heights-004-ref.html": [ - "ffb44a82b5338a605c9c0395e478627d00014d36", + "7c1e5858130b6150d36c52df0eaa525836b9075a", [] ], "percentage-widths-001-ref.html": [ @@ -297906,6 +297940,10 @@ "2861918d87f3cebaa3fe0d4978668e970424d84e", [] ], + "text-decoration-skip-ink-005-notref.html": [ + "9cbee037c0ec7c25b5bfa9813e96652750481c52", + [] + ], "text-decoration-skip-ink-sidewayslr-001-notref.html": [ "49cbf1530d7c9447f125c7ceea34b81a96de2f4a", [] @@ -305718,7 +305756,7 @@ ] }, "check-for-references.sh": [ - "f5f68d5f57849e625442d4bce3138136615a0330", + "adcc7dd2e7dca8a2c820dcf8e1844667a7333b0b", [] ], "color4": { @@ -305953,7 +305991,7 @@ [] ], "reftest.list": [ - "3dde877e231363e1642e876a62141d45b24ce2d9", + "1c5b5447c7049a61174659944e7ab283f189b164", [] ] }, @@ -307559,7 +307597,7 @@ ] }, "reftest.list": [ - "867a23079d7ce12289e74914c677344fb8332568", + "35102af64b63f93534d41ec1c8dbc4ad25ce6156", [] ] }, @@ -308090,7 +308128,7 @@ [] ], "sync-tests.sh": [ - "431ead2482e2ce46ac4ecc7d2ea454b11f8083f6", + "7e3f087681e99002f3532950716b7ef414e6ddfb", [] ], "text-decor-3": { @@ -309276,7 +309314,7 @@ [] ], "testharness-api.md": [ - "d83ebc4e67669303945741d8933acc5ed4bd29d0", + "2ee52786add43f036fe0ac7c8b66acc892d876ff", [] ], "testharness-tutorial.md": [ @@ -309646,7 +309684,7 @@ [] ], "Element-matches.js": [ - "9494f6f0eacb1b9604d3781b6e541bf7a47e6071", + "a1455c671f7a372f239ca7843bd50b9652d59b73", [] ], "Node-isEqualNode-iframe1.xml": [ @@ -309670,7 +309708,7 @@ [] ], "ParentNode-querySelector-All.js": [ - "3d424f750b9700a23261bc84547c11e28f4b1fea", + "3c6c50317988712fa64d26da8d31d4f91c498f2c", [] ], "attributes.js": [ @@ -313546,8 +313584,8 @@ "5a5d7f980f4d1ea51b03c4ce26442f64956df517", [] ], - "forced-colors-mode-18.tentative-ref.html": [ - "34eba583caa924d5aa9a6d0271acbc29c1b25a4b", + "forced-colors-mode-18-ref.html": [ + "a1701e3fdac842ea26c8f291b23f1febdb305d11", [] ], "forced-colors-mode-19-ref.html": [ @@ -313562,8 +313600,8 @@ "9c626ae768676c155459e90a75e2bd4fdda02867", [] ], - "forced-colors-mode-26.tentative-ref.html": [ - "f551a434290652dbb1e9bb003dec8bcbb34562d3", + "forced-colors-mode-26-ref.html": [ + "39caee40b52aca059a9a65ffde754788228ff9dc", [] ], "resources": { @@ -313907,6 +313945,10 @@ "26b28a0d7dcb912dc1f1131b98993bb29d3a18c1", [] ], + "form.html": [ + "6523a82b3930912f175115d2a82d8fd4ed016152", + [] + ], "href.html": [ "eccadadf41a873d9b50f643dcdc5fd83336f1605", [] @@ -315136,6 +315178,14 @@ "1008f70ff123ae5e507a95ac4f16d32bbb74c983", [] ], + "navigate-require-corp-same-site.sub.html": [ + "9a0ebe544a0e5f09ba3f1b0790e052e96d81a6fc", + [] + ], + "navigate-require-corp-same-site.sub.html.headers": [ + "56d0ac342824434c621fb50f9351e2e74aa077cb", + [] + ], "navigate-require-corp.sub.html": [ "d4b38bc1a10003f8adc0e62224c5d3378638fb4f", [] @@ -319375,6 +319425,10 @@ ] }, "shared-array-buffers": { + "blob-data.https.html.headers": [ + "63b60e490f47f4db77d33d7a4ca2f5b9a4181de8", + [] + ], "broadcastchannel-success-and-failure.https.html.headers": [ "63b60e490f47f4db77d33d7a4ca2f5b9a4181de8", [] @@ -324549,7 +324603,7 @@ [] ], "css-font-loading.idl": [ - "2bbc35a25912256fb1d56ada3372d35e59ca69e5", + "936ded788bcbbf38b71be5d58e886059245b6b89", [] ], "css-fonts.idl": [ @@ -324593,7 +324647,7 @@ [] ], "cssom-view.idl": [ - "2745ba96dc04a66501c2e5f7ffb0846a700f097d", + "82517fefcc218ce1abb41169d45e6aefc580ca6d", [] ], "cssom.idl": [ @@ -324853,7 +324907,7 @@ [] ], "resize-observer.idl": [ - "d53e9da55ee33faef05aeb10e50ba31f27cf88c8", + "448c949df9e38d90b96171a15ec0a4705e486395", [] ], "resource-timing.idl": [ @@ -324869,7 +324923,7 @@ [] ], "scroll-animations.idl": [ - "865419c15b58c0c4ff113d0a8651f3c8eae82a2c", + "a247308e50e4275d047f65c01adbe04e6fd1fe3f", [] ], "secure-contexts.idl": [ @@ -324933,7 +324987,7 @@ [] ], "visual-viewport.idl": [ - "8e9316bf854fb80c2dc79cad72d90cdde6d95c46", + "5e4b54136940d4341d97e73383144e3b79477b7e", [] ], "wai-aria.idl": [ @@ -324957,7 +325011,7 @@ [] ], "web-bluetooth.idl": [ - "65c4e20ef341d8ba9ac28d85e1d34057935ce6cf", + "cbf09e1d7ed736f27f2c2a0ee185da681e1ffed6", [] ], "web-locks.idl": [ @@ -324973,7 +325027,7 @@ [] ], "webaudio.idl": [ - "9491090337c342fc06a35a97ee6dc6887829c1bc", + "f0a5c7a9020b1eaa0af6df3664c41ecd03f71082", [] ], "webauthn.idl": [ @@ -325250,7 +325304,7 @@ ] }, "lint.whitelist": [ - "a79d77f2fedfc5a63a8b4c24b31687d50f7c9dc7", + "6ea2bd4ec1e2dcc8ee77d962b723104b7ee02b0f", [] ], "loading": { @@ -337216,10 +337270,6 @@ "fd46101ca0099e76a8ed5723515f6e1bd220aebb", [] ], - "LICENSE": [ - "45896e6be2bd51f4b78e9703caefb9b672e10a55", - [] - ], "META.yml": [ "64a240ccbe8116a06ec40fd03140e6bbee7a260f", [] @@ -337398,7 +337448,7 @@ [] ], "webxr-test.js": [ - "be8be8067e94bfb0b43d258d450d6637b12f645d", + "dee05d08ff8a87e25e30be8ca53105fcc8667db1", [] ], "webxr-test.js.headers": [ @@ -337407,7 +337457,7 @@ ] }, "idlharness.js": [ - "18b11ec506da18d40546928f78aa4f911eb03dcf", + "ff40847a1fc8d4c8393e03ce34e0643522e182a3", [] ], "idlharness.js.headers": [ @@ -337490,7 +337540,7 @@ [] ], "api-tests-1.html": [ - "05b570a6d0ae101729292b7ceaf21933b6a75f80", + "7c77afdda8e37ee429ed272e24461af6cd68429a", [] ], "api-tests-2.html": [ @@ -337737,6 +337787,10 @@ "is_to_json_regular_operation.html": [ "abfa4ab800b3c8e6b5f6fd3b42a982cc2e4fe99e", [] + ], + "toString.html": [ + "779c7a15e6d7b9ec20c38ba24900f752096df3e2", + [] ] }, "assert_object_equals.html": [ @@ -337823,7 +337877,7 @@ [] ], "testharness.js": [ - "7a0e4872899aa5f4961c946cdc0cf2b187542cce", + "0ec232c1d27d792b1dc000a9cf7899e011217698", [] ], "testharness.js.headers": [ @@ -337903,7 +337957,7 @@ [] ], "scroll-to-text-fragment-target.html": [ - "b06f3c889c19c65a5a0d7b419d6d31429838bf5a", + "b2be85132cb8f2f2c14c82bf1854c3130f375c96", [] ], "stash.js": [ @@ -339336,6 +339390,10 @@ "dc3f1a1e98563ad1b5924c8315c0d64d17ffbbfd", [] ], + "fetch-event-handled-worker.js": [ + "4af58e20d05c902d74511579e6b74894731d1239", + [] + ], "fetch-event-network-error-controllee-iframe.html": [ "f6c1919bbcf63a0f23f7ea4f0f8baa0e5bae4035", [] @@ -340343,10 +340401,6 @@ ] }, "untriaged": { - "LICENSE": [ - "531fac43af201b503c333f845587f0cca88c9c5f", - [] - ], "README": [ "5b7572bda4e2c2b2477a151345de3441265a8ca8", [] @@ -342329,7 +342383,7 @@ } }, "localpaths.py": [ - "f6e24b486d58b788b9441175c5e1c89f93874feb", + "ce3b41e300c9f11380ea065f5b288d5aef812588", [] ], "manifest": { @@ -342434,408 +342488,6 @@ "6bdb4d563f6dd5086ed3e09715187e549edf255a", [] ], - "pywebsocket": { - "CONTRIBUTING": [ - "6415f5d011f3e22be554ff755c078052213afa90", - [] - ], - "LICENSE": [ - "989d02e4cd09237806f7df8beaecc6915273428e", - [] - ], - "MANIFEST.in": [ - "19256882c56feb72812c99c8a8379979e63e4a0c", - [] - ], - "README": [ - "c8c758f5eef4c731892641de333e5d75d10a3e56", - [] - ], - "README.md": [ - "ae8e910a84ef995669ea0723f1bc7f2993466589", - [] - ], - "example": { - "abort_handshake_wsh.py": [ - "008023a1fe91e2f2977b163f977dae6850009aa4", - [] - ], - "abort_wsh.py": [ - "2bbf005f6a37c609e16abde4df94024268d0daee", - [] - ], - "arraybuffer_benchmark.html": [ - "869cd7e1ee653d59f04b802ff34c8d081317ca89", - [] - ], - "bench_wsh.py": [ - "5067ca7d8955fc163d5001cd8812e6ae20c16293", - [] - ], - "benchmark.html": [ - "a923863a99569763ab7976a7de78205ca6192a2a", - [] - ], - "benchmark.js": [ - "856435d1d0dd78d9794aa84c029c8ab4f7339ec1", - [] - ], - "benchmark_helper_wsh.py": [ - "44ad0bfeefc6f4430571b7b53233fb7950614cf2", - [] - ], - "close_wsh.py": [ - "26b0838408124539797657d40d64278f1a6481c0", - [] - ], - "console.html": [ - "ccd6d8f80676b9991e22dfc7ec85083411ddd0b3", - [] - ], - "cookie_wsh.py": [ - "8b327152e701f7d2031d7c9ce04624cfa84ac16d", - [] - ], - "echo_client.py": [ - "8ac740e7058bd31a78bcf1b1b897d3bdf3fc7d4b", - [] - ], - "echo_noext_wsh.py": [ - "1df5151227111e114424c5586a142a9adb7d604d", - [] - ], - "echo_wsh.py": [ - "38646c32ceb515f15dd572622689c79ee61e543f", - [] - ], - "eventsource.cgi": [ - "adddf237cf7d476497347d095a122994177e1409", - [] - ], - "eventsource.html": [ - "1598a8807d1ffccecf585f1bb656fd2d902fcd39", - [] - ], - "fetch_benchmark.html": [ - "2105eb67a5532b76bc97bf83108a19e043e54f31", - [] - ], - "fetch_benchmark.js": [ - "13952190a8e0739e3a0f2a48cb83dbe9b3015396", - [] - ], - "fetch_performance_test_iframe.html": [ - "e12df2e7df4d3ea62dc2a0bd5e81be8bf5b5dbc9", - [] - ], - "handler_map.txt": [ - "21c4c09aa0091d8baf2908959905867842aeb865", - [] - ], - "hsts_wsh.py": [ - "e861946921af04594d62026b46f07758ddfe402a", - [] - ], - "internal_error_wsh.py": [ - "fe581b54a648c09a22f2545127d70df051349234", - [] - ], - "origin_check_wsh.py": [ - "e05767ab932fb582824aaf1a57d9249c304bd51e", - [] - ], - "performance_test_iframe.html": [ - "78aede97a143042854e555728937228feac7beb0", - [] - ], - "performance_test_iframe.js": [ - "065b25bd1ffd637593ef3f7f06f0a4fe660ad0e8", - [] - ], - "pywebsocket.conf": [ - "335d130a5c84f69de4f81d358fb2cdb5bde4af6d", - [] - ], - "special_headers.cgi": [ - "ea5080f1f1aea3a37a7e729c678d6a12337bedb7", - [] - ], - "util.js": [ - "9ec0007b1edd5a0c5717a28da0552187a67da9a7", - [] - ], - "util_main.js": [ - "471ed63fb5d749d0ed2181b5aabcce2e9365b87e", - [] - ], - "util_worker.js": [ - "98dcf4417299f9c9d61deefcb1a595aeab05d56c", - [] - ], - "xhr_benchmark.html": [ - "800d20c9ccb4abecf239c75b18577b0d1ff11c92", - [] - ], - "xhr_benchmark.js": [ - "14a97e8fb83e948c85f9f021021dc7479305e6c0", - [] - ], - "xhr_event_logger.html": [ - "50db0fbec49465091329bdaf4717d7219886ad6f", - [] - ], - "xhr_performance_test_iframe.html": [ - "ab02c9ea56f6c10d03bec1038d6722de6e8d31e5", - [] - ] - }, - "mod_pywebsocket": { - "__init__.py": [ - "70933a220451fadb6a01a5c1d45c2551468d7d85", - [] - ], - "_stream_base.py": [ - "21a1254ae6d0e36fb5e97981283c57b15df6f3eb", - [] - ], - "_stream_hixie75.py": [ - "94cf5b31ba0fc3c3a3477de82297a0c26da1c48c", - [] - ], - "_stream_hybi.py": [ - "718e8ffa3955d5af5c3b7c516bcfa980efe6dd88", - [] - ], - "common.py": [ - "9f73d66560d644155ee0f5f2a550988987130d9b", - [] - ], - "dispatch.py": [ - "2591fe8b9e9eaf2e8eb4d6d8bcfcba72e030bb41", - [] - ], - "extensions.py": [ - "d1be749fff59896517fc51571746c7f1c2a7eb09", - [] - ], - "fast_masking.i": [ - "ddaad27f53c46be2a71c704c708fbb3f42f1a3d1", - [] - ], - "handshake": { - "__init__.py": [ - "707263a69fe95156b5f3b39d5883db975259d34c", - [] - ], - "_base.py": [ - "0af05242bfcd6fc96cbd8839b070384424a7e29f", - [] - ], - "hybi.py": [ - "2dbf25a1dcca77cd589dba7054f4e6272c6dea22", - [] - ], - "hybi00.py": [ - "8757717a639bac3db05d4cb3027e055fdbf7f3cf", - [] - ] - }, - "headerparserhandler.py": [ - "06e7638597985a96a4c00ea0052a0d3717e4403c", - [] - ], - "http_header_util.py": [ - "8e0584c5dac8cc44c776f7ccb4e6a5040ce52224", - [] - ], - "memorizingfile.py": [ - "c7afcc157ed7a1d942cbf4358afa0454846f01d4", - [] - ], - "msgutil.py": [ - "fc6c5b1dbce97d72f26cb77cdb7f846741b1bb3c", - [] - ], - "mux.py": [ - "994f8a14645c54b674b81458367319b66030447d", - [] - ], - "standalone.py": [ - "2c8786064889e84f115a75679ab71c74466a0c62", - [] - ], - "stream.py": [ - "a7d3136ac9b42f5df5652d91f3c886f85ecbf1f4", - [] - ], - "util.py": [ - "6c8284cd0744cde384c542a96dcc30aef5ade874", - [] - ], - "xhr_benchmark_handler.py": [ - "43a5b049220ee790efea51bda6a3f95d8dcf0975", - [] - ] - }, - "setup.py": [ - "935d6b105899ca1348d7d6d68bbe2bd49e85d118", - [] - ], - "test": { - "__init__.py": [ - "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", - [] - ], - "cert": { - "cacert.pem": [ - "4dadae121bdd3adb744383cd3bf08c226f8554ba", - [] - ], - "cert.pem": [ - "25379a72b0dac5f41979c802c663df8b88dea829", - [] - ], - "client_cert.p12": [ - "14e139927982fa9823a57231af113499d6a5a8b0", - [] - ], - "key.pem": [ - "fae858318f29ba6b4623bd5fbc4a08ce73d90311", - [] - ] - }, - "client_for_testing.py": [ - "18d185712eea408a23d24fbcce1aaf493a5aa7e3", - [] - ], - "endtoend_with_external_server.py": [ - "47f86fdb4163d4e738952b3ccb77f19216bc5915", - [] - ], - "mock.py": [ - "6bffcac48dc43a52b9ccf1bcd012975424048d67", - [] - ], - "mux_client_for_testing.py": [ - "304e7fc95d3fc5b3ee2d9e54f20782addae2040f", - [] - ], - "run_all.py": [ - "80a5d87d83822b0783be13e33dda3ab61a5afe40", - [] - ], - "set_sys_path.py": [ - "e3c6db9ea4d383a37c27a060a0ef1c563bbb1e14", - [] - ], - "test_dispatch.py": [ - "e16d605609c8ab7b30d72063830ff434c1c56dc0", - [] - ], - "test_endtoend.py": [ - "3680ec2eba6724a74905be4f1581f36a7a7f7713", - [] - ], - "test_extensions.py": [ - "94a4384792bffd0a20d2e75433dc2909cf93de6d", - [] - ], - "test_handshake.py": [ - "aa78ac05e5871d159a67f7e416f302700fa0f926", - [] - ], - "test_handshake_hybi.py": [ - "d0aaf640a588e2b042f32a9c66a3697ad4dd10be", - [] - ], - "test_handshake_hybi00.py": [ - "73f9f27ca2d9421b23015f58c893acf1dc009def", - [] - ], - "test_http_header_util.py": [ - "436dc57c3731071fe2901919e33f24c1b559f605", - [] - ], - "test_memorizingfile.py": [ - "8f1b8eef43378a9b00625f220c97fe22f740a8f7", - [] - ], - "test_mock.py": [ - "7dc23a73d8fb1a07aeb3e5ae9402cc86b8fcc366", - [] - ], - "test_msgutil.py": [ - "234735c63047d2edb238cb5255ad474a2e4d0a83", - [] - ], - "test_mux.py": [ - "0773decbfd66d0dffef0b4287bad87453fc1d644", - [] - ], - "test_stream.py": [ - "81acfeb048d3b2032a7948cfa2adea7ce64f95ae", - [] - ], - "test_stream_hixie75.py": [ - "ca9ac7130b7304eb92dbbbc1533aa41545e0246d", - [] - ], - "test_util.py": [ - "64ba3f47737a331ef0390cc6b0c81c2d34a42833", - [] - ], - "testdata": { - "README": [ - "c001aa5595cdd13407f5728ecd532a5d12263efa", - [] - ], - "handlers": { - "abort_by_user_wsh.py": [ - "367f9930f7824c3a535914a5b3779f6b6fba243f", - [] - ], - "blank_wsh.py": [ - "7f87c6af23df08efe3575cae208de5022cbaa660", - [] - ], - "origin_check_wsh.py": [ - "2c139fa176cc3417be96de798e6a81ed149b1992", - [] - ], - "sub": { - "exception_in_transfer_wsh.py": [ - "b982d0231ef00f02e0d90df678eb804ba43862d9", - [] - ], - "no_wsh_at_the_end.py": [ - "17e7be1802e3c66c63849875ea35e2406a381e60", - [] - ], - "non_callable_wsh.py": [ - "26352eb4ca238952239e3542728ce3b38d83dd6b", - [] - ], - "plain_wsh.py": [ - "db3ff6930256bc1b13e2c4c2d1c843c4b7c80aa4", - [] - ], - "wrong_handshake_sig_wsh.py": [ - "6bf659bc9af01c107ef3508b2b5d0173dcf59009", - [] - ], - "wrong_transfer_sig_wsh.py": [ - "e0e2e550794b7b990e0b44b33cfc2d4d152e6c04", - [] - ] - } - }, - "hello.pl": [ - "882ef5a10065feb691af5f716b1e4996b397da65", - [] - ] - } - } - }, "requirements_flake8.txt": [ "0a45224016417cddbf59209196380cce0a089edf", [] @@ -342934,7 +342586,7 @@ [] ], "serve.py": [ - "055b60f1e768c9e397f6cd086d41f1b8927c948a", + "f63bdcca5b00520334d99334a74e748544fc45e1", [] ], "test_functional.py": [ @@ -347005,6 +346657,336 @@ [] ] }, + "pywebsocket3": { + ".travis.yml": [ + "2065a644dd6b60797128b09998f67b5135b1189e", + [] + ], + "CONTRIBUTING": [ + "f975be126fb4589c865994c59d613e3d41a7a966", + [] + ], + "LICENSE": [ + "c91bea9025428779e164459c2a120e1a07aa85c8", + [] + ], + "MANIFEST.in": [ + "19256882c56feb72812c99c8a8379979e63e4a0c", + [] + ], + "README.md": [ + "277cbe550c065782b0e315de04651c9e068cb3ad", + [] + ], + "example": { + "abort_handshake_wsh.py": [ + "1b719ca89737105719c71c2a42eaff0d464478c7", + [] + ], + "abort_wsh.py": [ + "d4c240bf2c1cb0bd538ec37bf551c11256396b8c", + [] + ], + "arraybuffer_benchmark.html": [ + "869cd7e1ee653d59f04b802ff34c8d081317ca89", + [] + ], + "bench_wsh.py": [ + "2df50e77db276cca1de472059a074071b81a93cc", + [] + ], + "benchmark.html": [ + "f1e5c97b3a71bcf9ebde7c4fcaf15375389b1c69", + [] + ], + "benchmark.js": [ + "2701472a4faac446b1c8bd2456f271e934db571c", + [] + ], + "benchmark_helper_wsh.py": [ + "fc175333354cc9c88562cf6fc81ef32609372986", + [] + ], + "close_wsh.py": [ + "8f0005ffea9905d6c197c0f4dd593c06ff437d3a", + [] + ], + "console.html": [ + "ccd6d8f80676b9991e22dfc7ec85083411ddd0b3", + [] + ], + "cookie_wsh.py": [ + "815209694e3ff2320f5683ee0596ae34428b15eb", + [] + ], + "echo_client.py": [ + "e4459a19bc68c7879a174f572122382914e6f7dd", + [] + ], + "echo_noext_wsh.py": [ + "eba5032218d269958bf751bf608bbb52556c6f76", + [] + ], + "echo_wsh.py": [ + "f7b3c6c5311bbb99f334f3357d801a2f42056008", + [] + ], + "handler_map.txt": [ + "21c4c09aa0091d8baf2908959905867842aeb865", + [] + ], + "hsts_wsh.py": [ + "e861946921af04594d62026b46f07758ddfe402a", + [] + ], + "internal_error_wsh.py": [ + "04aa684283a603f5b33dda526301999b44792ddf", + [] + ], + "origin_check_wsh.py": [ + "e05767ab932fb582824aaf1a57d9249c304bd51e", + [] + ], + "performance_test_iframe.html": [ + "c18b2c08f6a2254a1f9d76507902c1a6b5aee88a", + [] + ], + "performance_test_iframe.js": [ + "270409aa6eb5fd1a11ae2d5ba156bb8aac2fcaac", + [] + ], + "special_headers.cgi": [ + "703cb7401b787ac6e11c6746c6842e57d380956c", + [] + ], + "util.js": [ + "990160cb40c4c7800cd9e76300140b9bfe9f14d0", + [] + ], + "util_main.js": [ + "78add48731f88fe85eeea40e6e019cf17ccce3c8", + [] + ], + "util_worker.js": [ + "dd90449a90a7d782b97023e8d9d5a7aaee94a32f", + [] + ] + }, + "mod_pywebsocket": { + "__init__.py": [ + "28d5f5950f347531b9262c31b77d0c29d426e59d", + [] + ], + "_stream_exceptions.py": [ + "b47878bc4a379b5ad8c423a8e38df405a96ccbf5", + [] + ], + "common.py": [ + "9cb11f15cb2f582e6c29e698ee5fff2afde8a768", + [] + ], + "dispatch.py": [ + "4ee943a5b80afdc0bda953aa2a73198b99a459b0", + [] + ], + "extensions.py": [ + "344539f6fce14bee65d7fa7a583acbb65f023aff", + [] + ], + "fast_masking.i": [ + "ddaad27f53c46be2a71c704c708fbb3f42f1a3d1", + [] + ], + "handshake": { + "__init__.py": [ + "82be5ae9fdfd46d18970bf992cc1adea25acc5f7", + [] + ], + "_base.py": [ + "358dd8d497d9d89dd9c10432bcdba3e903e1b616", + [] + ], + "hybi.py": [ + "7fd63eb63edd1f4acf887df35d4bdc3b6e139915", + [] + ] + }, + "http_header_util.py": [ + "ded90b247b8ded9f254daba70909cbc8ff3c82d6", + [] + ], + "memorizingfile.py": [ + "d35396761847a6840464840b0e72ebc7ba150e41", + [] + ], + "msgutil.py": [ + "f58ca78e14ad25e7a44d1ab262e7bd35ceaea5f2", + [] + ], + "request_handler.py": [ + "e038d9f43acd37bf5a1b10a0ce5585bd51ed602d", + [] + ], + "server_util.py": [ + "8f9e273e9770844b7cb44616ebf9825886bf44e6", + [] + ], + "standalone.py": [ + "1042c496c58a23b6ce4c23eee0ba462095452671", + [] + ], + "stream.py": [ + "fc399f2a85b9fe6c52ec5690f580d06b222e4748", + [] + ], + "util.py": [ + "e164e6b8e4682467187fc3894b1ca79ec82a413a", + [] + ], + "websocket_server.py": [ + "df8cd393c7ee8069675713665d85be7d97657b70", + [] + ] + }, + "setup.py": [ + "57e9428d691d756cf3d687e756b73814ccd3be7d", + [] + ], + "test": { + "__init__.py": [ + "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + [] + ], + "cert": { + "cacert.pem": [ + "4dadae121bdd3adb744383cd3bf08c226f8554ba", + [] + ], + "cert.pem": [ + "25379a72b0dac5f41979c802c663df8b88dea829", + [] + ], + "client_cert.p12": [ + "14e139927982fa9823a57231af113499d6a5a8b0", + [] + ], + "key.pem": [ + "fae858318f29ba6b4623bd5fbc4a08ce73d90311", + [] + ] + }, + "client_for_testing.py": [ + "450a6acd5376223c603abe264b967796832fb573", + [] + ], + "mock.py": [ + "eeaef52ecf0197f7e57f346a71548252a3861e67", + [] + ], + "run_all.py": [ + "f622015539e47fdf518875158f6bd6f36994faba", + [] + ], + "set_sys_path.py": [ + "9fce7b34b6751d635dcda6e0d4a5711951a0a33b", + [] + ], + "test_dispatch.py": [ + "ae63980f2581f9f6136e85dc16bb64ccbe373dc9", + [] + ], + "test_endtoend.py": [ + "445d29c67ddf07b3f123245977f95cfe0148c83a", + [] + ], + "test_extensions.py": [ + "39a111888be9005bd7b684366aebfd1d914f4a32", + [] + ], + "test_handshake.py": [ + "e0e2278dfe65019d7659489b538fcc85ad57d455", + [] + ], + "test_handshake_hybi.py": [ + "04841f1f79fa89f03f0e0219e06b213c34f5f0fc", + [] + ], + "test_http_header_util.py": [ + "5ce01ef283913e3fc8775b3b27311f6195c57be7", + [] + ], + "test_memorizingfile.py": [ + "f7288c510b89bb28931dff9a779183a4991756e6", + [] + ], + "test_mock.py": [ + "073873dde97df03f20df5ac9722f6ad65dd02eac", + [] + ], + "test_msgutil.py": [ + "943f21b8d297668770cb91c501c2b2fc8258b9eb", + [] + ], + "test_stream.py": [ + "153899d20533eb4cf0a9816c8b3b2bc8c50548bf", + [] + ], + "test_util.py": [ + "20347941051455ebee58823b4b0be93762912b0a", + [] + ], + "testdata": { + "README": [ + "c001aa5595cdd13407f5728ecd532a5d12263efa", + [] + ], + "handlers": { + "abort_by_user_wsh.py": [ + "63cb541bb7ec4044745798ab97ae7ccba57e04f7", + [] + ], + "blank_wsh.py": [ + "b398e96778d8f75c8d7424c27b40c1fad9918efc", + [] + ], + "origin_check_wsh.py": [ + "bf6442e65b99dd7d455cb09a0afc9d4958df041b", + [] + ], + "sub": { + "exception_in_transfer_wsh.py": [ + "54390994d712dfe8f1e4dfdd94ff56abe6673d8e", + [] + ], + "no_wsh_at_the_end.py": [ + "ebfddb74495eef1d1f351e334ac4850473ed1648", + [] + ], + "non_callable_wsh.py": [ + "8afcfa97a954a2be696c148dd1f553628f121ea6", + [] + ], + "plain_wsh.py": [ + "8a7db1e5ac326df51c68e3ed2690be2a03c873cb", + [] + ], + "wrong_handshake_sig_wsh.py": [ + "cebb0da1ab6b0bdd077b833281376c1a811550f2", + [] + ], + "wrong_transfer_sig_wsh.py": [ + "ad27d6bcfeeaf02a942a8b30b7d3340fb20d2330", + [] + ] + } + }, + "hello.pl": [ + "882ef5a10065feb691af5f716b1e4996b397da65", + [] + ] + } + } + }, "six": { "CHANGES": [ "ffa702601b6509b25fbd6787d1e7682e5ac39356", @@ -347167,7 +347149,7 @@ [] ], "client.py": [ - "cf4c5fd35fd98bb54ba248d1c1c31590310727ed", + "9885e99c4f7c8c339caad4fd2172e59688d9b2fe", [] ], "error.py": [ @@ -347179,7 +347161,7 @@ [] ], "transport.py": [ - "c33582db1a906e3f62c0c912fc29e55027dff2e4", + "edaf3186039d287dd5ef3ce6e4e616fd8bc0e302", [] ] } @@ -347289,10 +347271,6 @@ "495616ef1d19c54cb963e99e1a226e8320cc3744", [] ], - "LICENSE": [ - "45896e6be2bd51f4b78e9703caefb9b672e10a55", - [] - ], "MANIFEST.in": [ "d36344f9668919f9dfc8f2e60a470a5d0484c292", [] @@ -347640,7 +347618,7 @@ [] ], "base.py": [ - "e6b44f02641f94cc9591ea08d6d748ac3ebbec1c", + "cb7323ece0ae4a9af7213a0134e976ef325e3e13", [] ], "executorchrome.py": [ @@ -347684,7 +347662,7 @@ [] ], "executorwebdriver.py": [ - "da763074e2e58aa2d3c4724b864f08072f18af33", + "eccba8c4a127f7895baa85948dc21006d5910aa5", [] ], "executorwebkit.py": [ @@ -347904,7 +347882,7 @@ [] ], "sync.py": [ - "0d75a1232fc0cba9fbd6e8a3363adea0475dd436", + "0a41f706ebc3760729d9ba553076fe0d7e0b40b8", [] ], "tree.py": [ @@ -348013,10 +347991,6 @@ "8e87d388488bbc21c664209fecd2f3e030411b0e", [] ], - "LICENSE": [ - "45896e6be2bd51f4b78e9703caefb9b672e10a55", - [] - ], "MANIFEST.in": [ "4bf44835220d8d466be23757a194bdc92aaa5584", [] @@ -348262,7 +348236,7 @@ [] ], "test_response.py": [ - "d8d64d54ccc78a3c6deba21b73428e71aeb1be60", + "6e947e478d9e14e33676cbd62bbbada2f118ec7f", [] ], "test_server.py": [ @@ -349949,7 +349923,7 @@ [] ], "helpers.js": [ - "bbbba464948c77d57bf3fbc19f31cd4579551229", + "bf28e69cccf9b9fe2ba6becbda30910bb50a140e", [] ], "resources": { @@ -351106,7 +351080,7 @@ [] ], "simple_handshake_wsh.py": [ - "bd9bb9c45f5351418dc83fa3862d7128d1340c26", + "a612a1a833d93758175d0b9d2e4afa18e7ba2e91", [] ], "sleep_10_v13_wsh.py": [ @@ -357788,6 +357762,27 @@ {} ] ], + "2d.pattern.image.nonexistent-but-loading.html": [ + "d3a219d7b2f7e46bb578eb1b048e5adeeeaef3bc", + [ + null, + {} + ] + ], + "2d.pattern.image.nonexistent.html": [ + "c6c33b5c5220451433bd6b3113c3e0e6ceb5bdef", + [ + null, + {} + ] + ], + "2d.pattern.image.nosrc.html": [ + "0c275f82ac7e66c3f85830872932a3e126954f2c", + [ + null, + {} + ] + ], "2d.pattern.image.null.html": [ "be7ee14ea14e4313545c4337ce8735257c5b2be6", [ @@ -357809,6 +357804,20 @@ {} ] ], + "2d.pattern.image.zeroheight.html": [ + "143eec9d2cfa26d971359b7dcfb99f561aac83da", + [ + null, + {} + ] + ], + "2d.pattern.image.zerowidth.html": [ + "66890cfe2e0aff6e0bb0d7a468f751123b284c11", + [ + null, + {} + ] + ], "2d.pattern.modify.canvas1.html": [ "bf1c51564860697f43e842bcde2f1ffcbc5d30bc", [ @@ -358012,6 +358021,27 @@ {} ] ], + "2d.pattern.svgimage.nonexistent.html": [ + "756c2094c96f9de1f2c5dcf9718485ddf9ef4d81", + [ + null, + {} + ] + ], + "2d.pattern.svgimage.zeroheight.html": [ + "2cb1374d2db7454d5c9c0539f4d74f417e1bc65c", + [ + null, + {} + ] + ], + "2d.pattern.svgimage.zerowidth.html": [ + "0b3f70ddf002f523ea80e0be0d22d0e3fe18e47c", + [ + null, + {} + ] + ], "2d.strokeStyle.default.html": [ "e30c3edf50f742cc5d09554a52d504144a62f395", [ @@ -376192,35 +376222,35 @@ }, "clipboard-apis": { "async-interfaces.https.html": [ - "1c423e95b669a30a6af86db7a84d76a5a9c176b4", + "06b777fbe328a191fc9625fe85e4f05eb751ed52", [ null, {} ] ], "async-navigator-clipboard-basics.https.html": [ - "22358f16d9699cc3b3897eaa266132b82760726b", + "65759186a6c98ab25f35e18d4f9d9350caedb1a0", [ null, {} ] ], "async-raw-write-read.tentative.https.html": [ - "6cfa2dbcca79f57db3fa57aeaff33c688423ff87", + "a6889d61efc43ae266c13d22b13286ade24e05a1", [ null, {} ] ], "clipboard-events-synthetic.html": [ - "fa40d8c20038c5a9c2cc09fe7dcfb1568de58be2", + "878682975266a3154756361a4e5d87c47482e393", [ null, {} ] ], "clipboard-item.https.html": [ - "d9dcee76419cdb3a17ff3d64f58b3e7143bb3a48", + "afec778eb99e8605a4c5a1001a2576e1e246b27c", [ null, {} @@ -376228,7 +376258,7 @@ ], "detached-iframe": { "clipboard-on-detached-iframe.https.html": [ - "9b43b3695afae88675f9ca543ec37def3b62bc11", + "84fce80323624af9d1f431e85bc90e9d620174b0", [ null, {} @@ -378699,7 +378729,7 @@ }, "nonce-hiding": { "nonces.html": [ - "e7bada96f94f8081d257529dbd1c7f63bf31f462", + "b023d060323d9d9f366ffdb53159a6f25ab24065", [ null, {} @@ -378713,21 +378743,21 @@ ] ], "script-nonces-hidden.html": [ - "b32635ff65817d739bc2b7ff003211bce166db14", + "d9718d904c24326b86bd2853dcdfebe11d644da5", [ null, {} ] ], "svgscript-nonces-hidden-meta.sub.html": [ - "0bdf2ab8f488b7c32acd95acaa2e0628add504b8", + "870fef316eae9ca2fc5ab1182f746cfa6487cb05", [ null, {} ] ], "svgscript-nonces-hidden.html": [ - "d6e9379d4710b13d7ea47ba3d0e3c5cb5721899c", + "a50c75b34ca59fe51b57b55aec95d1fcddb84350", [ null, {} @@ -398160,7 +398190,7 @@ ] ], "text-decoration-skip-ink.html": [ - "cd6f7a049c673e3dc92546f557e1813b20448128", + "2d6a5d3671f40e2389e916f204ad353402bca799", [ null, {} @@ -402306,7 +402336,7 @@ ] ], "minmax-number-computed.html": [ - "c72c276625466a193bf8829e3ddbb87d7a565dc0", + "9a6e6142ea85ede12d742ca49f5bddf03b2e56b0", [ null, {} @@ -403244,7 +403274,7 @@ ] ], "CSSStyleSheet-constructable.html": [ - "d196b3965a5db70ee32604bd3fee9205309d4b36", + "539075b47efc8599c3d9147f2e4b417783fb1df5", [ null, {} @@ -405575,7 +405605,7 @@ ] ], "syntax.html": [ - "0eda7374053fd5a5ce23c00998f5b4923a57c4bd", + "e62c13a03409636b770126c0768f0f90912aafc7", [ null, {} @@ -406207,7 +406237,7 @@ ], "custom-element-registry": { "define.html": [ - "61717b2e20035d5e1e4b722eadfc525118c39d0b", + "2aad1c18025af6f6322c0d7e6ce4c6089f1ac906", [ null, {} @@ -406789,7 +406819,7 @@ ] ], "with-exceptions.html": [ - "e7cdee789d4a50b25dfd8620aaa2a756686201d2", + "131348b1c41ab8698e4daaf1d27071994babb0a7", [ null, {} @@ -406813,14 +406843,14 @@ ] }, "throw-on-dynamic-markup-insertion-counter-construct.html": [ - "853595040143df0f70173279c755dd64f102f10e", + "3e9254e2a50f0ca572b9e9aa1641a4dab5f22e50", [ null, {} ] ], "throw-on-dynamic-markup-insertion-counter-reactions.html": [ - "49335bf9b425f3da178cc20ae4860333f5de4468", + "e798d332e38b2e85703e7752486fe221376212db", [ null, {} @@ -407405,7 +407435,7 @@ ], "scrolling": { "overscroll-deltas.html": [ - "287987c3ee20d07a6d5d2387efc1c3a6209f82e7", + "091cfd0631b454babaeb8c32dd289468b6d509d3", [ null, { @@ -408019,14 +408049,14 @@ ] ], "Document-createElement.html": [ - "c0126b4f8a88d887cd9fa05618e1b7d441064641", + "93435ac82d60c07dbe88b4d0bb744cd2cfd20cc9", [ null, {} ] ], "Document-createElementNS.html": [ - "43cf800b4cced3ced2b09216c0d909f26412dfeb", + "5ad81043de0717f4957b205a51d13f8dc41f41aa", [ null, {} @@ -408712,7 +408742,7 @@ ] ], "Node-removeChild.html": [ - "a4581aadc9b2526001cb5a70282d5e1939c0e819", + "6158423359c925c48ba27568c034ec02809e59fb", [ null, {} @@ -408835,7 +408865,7 @@ ] ], "aria-attribute-reflection.tentative.html": [ - "0a3a66bdf435ebef64a282eaec8f7a471096daaf", + "d5f4adff59f4f9e06ed0c22851c3af3b78a6a1c7", [ null, {} @@ -409133,7 +409163,7 @@ ] ], "Range-cloneContents.html": [ - "73bce34572583b66098e6cdb7cd487ece8cb5481", + "6064151f62d44307a081e735f1d688c4cafae718", [ null, { @@ -409228,7 +409258,7 @@ ] ], "Range-extractContents.html": [ - "b43769ebc01044c2dd46499df74b91fa955090e3", + "88f8fa55f8652fc189b4205f83f0b413e4fb7503", [ null, { @@ -409237,7 +409267,7 @@ ] ], "Range-insertNode.html": [ - "aca3a23ff5d58b891f4e6bcf295026470a9e2960", + "04727dbc4d2855dce34c3a9356f161fdeb1e0a86", [ null, { @@ -409395,7 +409425,7 @@ ] ], "Range-surroundContents.html": [ - "c4ff0509f8f4da0d56c190bc911852a77dbc973d", + "c1ed30d0053f2157776b8c58b19ce82cafb5e74f", [ null, { @@ -423701,7 +423731,7 @@ ] ], "serviceworker-intercepted.https.html": [ - "05eadefa4c8227410275b289388c7184b021a8d1", + "603f29eac981c7abcacd72c153685bc181ee80c4", [ null, {} @@ -425030,6 +425060,27 @@ } ] ], + "data-url-iframe.html": [ + "217baa3c46b631cbfe7d872e1a98c87d147e2d86", + [ + null, + {} + ] + ], + "data-url-shared-worker.html": [ + "d69748ab261b90cdc52b511c47a5f32a01bd21f2", + [ + null, + {} + ] + ], + "data-url-worker.html": [ + "13113e62621ac815ab10448c1185ffd13d830d59", + [ + null, + {} + ] + ], "sandboxed-iframe.html": [ "feb9f1f2e5bd3e2a1d1937103ea13c2fdb32aea6", [ @@ -428166,6 +428217,27 @@ {} ] ], + "anchor-fragment-form-submit-longfragment.html": [ + "8c4c77014fc92be94b7f71dd0fb2bfc7ee9a8a8c", + [ + null, + {} + ] + ], + "anchor-fragment-form-submit-withpath.html": [ + "b891623a2f560fe5cfec844b2a377b05139258f9", + [ + null, + {} + ] + ], + "anchor-fragment-form-submit.html": [ + "57e44f180856d6f8aa2d57b45a20c140e99d71c0", + [ + null, + {} + ] + ], "child_navigates_parent_location.html": [ "9111232e26e7de672b9e4df4b47b1c408338ae1c", [ @@ -428702,14 +428774,14 @@ }, "the-history-interface": { "001.html": [ - "da93e0dafc7a749a14408977cb38a3a334ea2feb", + "b4a5fd4bf4de7bff169409913e658af9bb81017b", [ null, {} ] ], "002.html": [ - "dba28b0471cbfa832051078583c15df92d3fe331", + "dcda248282f494c76ba175eb966137704fdf8010", [ null, {} @@ -429546,7 +429618,7 @@ ] ], "document_domain_setter.html": [ - "77f438c0166bcb570050800d3227bd7dcc691545", + "25395283411203ca9d15a00513cfd23425c32b58", [ null, {} @@ -430548,7 +430620,7 @@ ] ], "none.https.html": [ - "7f296e60441458a5f38dfd351d717845677ce30b", + "e603753084cf2c3c85b0187a7a8f66b3de8cf401", [ null, { @@ -430592,7 +430664,7 @@ ] ], "require-corp.https.html": [ - "69a81311c130d5538242db6be85089d242e06698", + "6f799b6e4086c1e73654b694174cacc496b2582d", [ null, { @@ -432116,6 +432188,13 @@ ] ], "shared-array-buffers": { + "blob-data.https.html": [ + "b35d8c2798a56c74277f82d8b5c50582592ca962", + [ + null, + {} + ] + ], "broadcastchannel-success-and-failure.https.html": [ "8902de49cfb10293ddb6246dc834268621e0dcad", [ @@ -433252,6 +433331,13 @@ {} ] ], + "img-empty-alt-replaced.html": [ + "3cc06d6c8538a4b1431c1de9d46ac4a1c706dbec", + [ + null, + {} + ] + ], "img-no-alt-replaced.html": [ "896c7363113ca8872208059bbac51f7616b05f3f", [ @@ -439122,6 +439208,15 @@ {} ] ], + "maxlength-number.html": [ + "1e1d9f694cf215ca162bd8cdeceb982db33ea512", + [ + null, + { + "testdriver": true + } + ] + ], "maxlength.html": [ "da5d18d00a95ce4ca4b7ea9861d3d4930e0d157d", [ @@ -445377,14 +445472,14 @@ ] ], "bailout-exception-vs-return-origin.sub.window.js": [ - "d60be3b8d6096aaee5770780b024c0e28114f2af", + "b20c3e3f31798efb2ee151ee752c0ce56dbb0b68", [ "html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-origin.sub.window.html", {} ] ], "bailout-exception-vs-return-xml.window.js": [ - "8b1a04fe83adfe911d04b10a45f24569421c57ec", + "45a67f925bc10f5d5dfd7bb744850f050dc45dcf", [ "html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-xml.window.html", {} @@ -445405,7 +445500,7 @@ ] ], "bailout-side-effects-same-origin-domain.sub.window.js": [ - "9adacb2a99f9b4d5167a863b2d2ab9a1480266af", + "f5edd7aed9c19cb884fb399441d714542a700e3b", [ "html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-same-origin-domain.sub.window.html", { @@ -445437,7 +445532,7 @@ ] ], "bailout-side-effects-xml.window.js": [ - "bcc4266319a428e77dc5ba6ea7d75b6445c76113", + "bbfc015c68e95d5aa8526936adf9693bcacef973", [ "html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-xml.window.html", { @@ -445570,7 +445665,7 @@ ] ], "origin-check-in-document-open-same-origin-domain.sub.html": [ - "83ce50d6c4d6d424b9231b2a0aa505a59f19d3fc", + "ba4ef3bae88b74951a039b421531f074297c0811", [ null, {} @@ -446696,7 +446791,7 @@ ] ], "context-creation-offscreen.html": [ - "05a920cd3e605ade63aa4d01171d9d3f5597ed72", + "66d4cce420a8ca550d71391692225887ddb5a4df", [ null, {} @@ -470542,6 +470637,31 @@ } ] ], + "po-disconnect-removes-observed-types.any.js": [ + "cac97bea0755c1182956fdf94c98ee78ca40b017", + [ + "performance-timeline/po-disconnect-removes-observed-types.any.html", + { + "script_metadata": [ + [ + "script", + "performanceobservers.js" + ] + ] + } + ], + [ + "performance-timeline/po-disconnect-removes-observed-types.any.worker.html", + { + "script_metadata": [ + [ + "script", + "performanceobservers.js" + ] + ] + } + ] + ], "po-disconnect.any.js": [ "5f5fb5aa43ba46268f8c355fdc93c6f1829968b4", [ @@ -470642,6 +470762,31 @@ } ] ], + "po-observe-repeated-type.any.js": [ + "2bba396a6b69ebad56ad4f1b135cb8ad49d321b7", + [ + "performance-timeline/po-observe-repeated-type.any.html", + { + "script_metadata": [ + [ + "script", + "performanceobservers.js" + ] + ] + } + ], + [ + "performance-timeline/po-observe-repeated-type.any.worker.html", + { + "script_metadata": [ + [ + "script", + "performanceobservers.js" + ] + ] + } + ] + ], "po-observe-type.any.js": [ "2c0719a8d146882ad317ff2418e89d4e77a2da18", [ @@ -471851,7 +471996,7 @@ }, "portals": { "about-blank-cannot-host.html": [ - "59858c690dcbfafaa673371384271d8b2a3c1a7c", + "6a721c32e6807cbd615b3adbffd7422467375525", [ null, {} @@ -471868,7 +472013,7 @@ ] ], "frame-src.sub.html": [ - "34f1474d4b37f6a867b858a0bfc88e03db48153a", + "1b37fd4ac2148e10828112876bb8ad0aaa818075", [ null, {} @@ -471884,7 +472029,7 @@ ] ], "history-manipulation-inside-portal.html": [ - "d19e153e51cabedecd4e86ce457dd714e7eac82e", + "efbf1de136cca726701fc1827dd0a00fda93c987", [ null, {} @@ -471892,14 +472037,14 @@ ] }, "htmlportalelement-event-handler-content-attributes.html": [ - "8fc26386cfc9d30e89062440b9347c62b60aa592", + "04b4615205ae9f036231370b9d30aa217608f2c4", [ null, {} ] ], "portal-activate-data.html": [ - "9a712c914950eaa69d469c4218af8629f8ebe87a", + "cefb0eae3dc9a8b10880687fc084d44991f8f26f", [ null, {} @@ -471913,70 +472058,70 @@ ] ], "portal-activate-event.html": [ - "780e8b58af98cc910e3739e3a9a2e5567d5613d0", + "990dc2d4b85ac3ca49e246ba17249a2986592e4d", [ null, {} ] ], "portal-non-http-navigation.html": [ - "3b79df3c230e1128574d4a6eb81d407cd9303e2c", + "e2b5d3d3937e4991d99caa3d87901ffe6202a100", [ null, {} ] ], "portal-onload-event.html": [ - "c9f07fcc889043cd523206728f7daaaefa653409", + "da770013e26506cca3a314e39f6deb962e42578f", [ null, {} ] ], "portals-activate-empty-browsing-context.html": [ - "4beff610ea0d3da3d4d9133ceaa570f879e0c03b", + "d904cd58543ff02c280d3f43267b8d54cad7882b", [ null, {} ] ], "portals-activate-inside-iframe.html": [ - "4fbe9ba333b4e81e62a1b918c914d45617daf70d", + "5234babfdf9528c5b9d7861ba90ffe6ceebaccce", [ null, {} ] ], "portals-activate-inside-portal.html": [ - "efeb23d4e38d21627be812fa85bbd5d74ba83da4", + "bd0d9e27bb6b25adbc3844adc8f8f947cf83dce1", [ null, {} ] ], "portals-activate-no-browsing-context.html": [ - "cf5cb35da2fd7b9e5d6561fb58a63495de82f1c5", + "bfc45f68c1859401b015d9cd22aaf50cdefb1770", [ null, {} ] ], "portals-activate-resolution.html": [ - "db77a990e532a3d1b88f57300094cbd60215d69d", + "ada8d4619a075df9c8c422c5d8ff54a0f9700deb", [ null, {} ] ], "portals-activate-twice.html": [ - "731a342c7f58101cabd82f9a8f71e739ac31d217", + "eaef8b7cc94e0e62bc34e32d4c48c7b6bcb9f83d", [ null, {} ] ], "portals-adopt-predecessor.html": [ - "96d0be9a401771759c0d0cc5a9785497f9bf3447", + "21893987e63839bbfb8a9d67e0f41e7502c88812", [ null, {} @@ -471990,14 +472135,14 @@ ] ], "portals-cross-origin-load.sub.html": [ - "e19a225de046ba01a1232fb0cb456b6a706f583a", + "be817b8a0aa44de1773f7c9448a572a15bec9866", [ null, {} ] ], "portals-focus.sub.html": [ - "97b7579eb63694310aa4d34b656c7414e68bd7c2", + "54fcf3a3d4734f0a1a1f49462afbeba21a5a0644", [ null, { @@ -472007,14 +472152,14 @@ ] ], "portals-host-exposure.sub.html": [ - "36fc2b48c8d87e10a1cb533e19372de1a9251825", + "3ff88413691db6c64b1cdb7a23fe2191640d47c2", [ null, {} ] ], "portals-host-hidden-after-activation.html": [ - "f51e54bd49c5c1a422dab6f249fc3ee1da87eb0d", + "571ec55810aa8e76c3dd977f774b3a1eeb835904", [ null, {} @@ -472028,7 +472173,7 @@ ] ], "portals-host-post-message.sub.html": [ - "5331da0d7c3f0887bc161b7c8bc53ffcf3f1ebda", + "8750dd43d011ccf48d40f9671bd02c8dd29047c1", [ null, { @@ -472037,21 +472182,21 @@ ] ], "portals-navigate-after-adoption.html": [ - "5ad30d7928a6c4eda0cbcb5afad9c29234b9a057", + "f403902031303f21b9019c159d63b39ba1ff00d1", [ null, {} ] ], "portals-nested.html": [ - "9d8d09be9b104b7fa0c58f58c5b19d641db6c0eb", + "41d053bd48d11a9511168bf570e2d9d08bd78fdb", [ null, {} ] ], "portals-post-message.sub.html": [ - "5eee95cce654b30ce8401026d314b320f6cb4e45", + "f3927a0c05ace4e9e811548d49f6e0114560abf6", [ null, { @@ -472061,42 +472206,42 @@ ] ], "portals-referrer-inherit-header.html": [ - "0207474f37dea658ed9edb4352bd4e0719465558", + "da908cb2c21ff0c7996d36b326f18f400ad8b6be", [ null, {} ] ], "portals-referrer-inherit-meta.html": [ - "cf0493fb6224233396f6169b59641c866096a82d", + "eecb247973e838c6fc5e09f32180fe58c81a2727", [ null, {} ] ], "portals-referrer.html": [ - "cb08f56def7dfecaf3f5ed83cd668ed7f3ab663a", + "b9abcdb80de70dd5ed8135617945d59bdbb35269", [ null, {} ] ], "portals-repeated-activate.html": [ - "a3843dddb47b55d920b249b01f47199b1202bda6", + "bf8d8ad42641c4d775db4255087383007df07698", [ null, {} ] ], "portals-set-src-after-activate.html": [ - "70a16436a67f254372be5f861d83d9e9b66f895f", + "f9728170858792aae99aebeffe507bc5c94b0e19", [ null, {} ] ], "predecessor-fires-unload.html": [ - "381221222b59c0346dae48cc83d4985d155610c9", + "dce2afb8e6fde03c292456a7e960b52b8558c8ca", [ null, {} @@ -472104,7 +472249,7 @@ ], "xfo": { "portals-xfo-deny.sub.html": [ - "13c3f7f4d1fcdde9d2a744955bce6308805f1618", + "dde09c6e72bf9d2f0632aecbf6f8ca1cd0006436", [ null, { @@ -523047,7 +523192,7 @@ ] ], "observe.html": [ - "3b336837b3bbea08f21d5d5c9534dd638f29a24b", + "1ab5f786877cb6aa6ae85dd566db0f4e38f1c330", [ null, {} @@ -523854,7 +523999,7 @@ ] ], "scroll-animation.html": [ - "7a6f87574403a03703e0f4fd148667a0d573218d", + "62e5bb854aa2cc7d564c258f58e05342ffb8b446", [ null, {} @@ -523904,7 +524049,7 @@ ] ], "scroll-to-text-fragment.html": [ - "f0b167a9051aa5a8bc03f5df55020a1c7a3d726d", + "73931d4b0e617dcf07fd50f90c15a83dc664d277", [ null, { @@ -525149,7 +525294,7 @@ ] ], "detached-context.https.html": [ - "c9015b809301ad706cbcd7eda9f17c88323df3e5", + "cbb818f7ac0ebcb87ea53d94e85b5045d9ead35e", [ null, {} @@ -525280,6 +525425,13 @@ {} ] ], + "fetch-event-handled.https.html": [ + "2d6f6c86d876daf40ea8f41be01bca2c549bcc41", + [ + null, + {} + ] + ], "fetch-event-network-error.https.html": [ "fea2ad1e3c26effcc9fd7c8d7a66917e420606b4", [ @@ -525696,7 +525848,7 @@ ] }, "multipart-image.https.html": [ - "98336d7d9e23f9be2d8ce3033903b3ff90c841c5", + "00c20d25f90ac38c33ba57ced62d82798481dd42", [ null, {} @@ -547868,7 +548020,7 @@ ] ], "NDEFRecord_constructor.https.html": [ - "4772407baafb029cd10c09f6434fd71f703905a8", + "6d6b8117ef03fc8a1357e3eec4b10e39a9cc7dac", [ null, {} @@ -549465,132 +549617,150 @@ }, "webauthn": { "createcredential-badargs-attestation.https.html": [ - "6bce3233a1c029fb705a245afd0913beab1f56e7", - [ - null, - {} - ] - ], - "createcredential-badargs-authnrselection.https.html": [ - "87bdb040055b403b3fca64b3ae6e0e80e2cffd65", + "1bdebbbf2b286f69eecd0774ac9bdfb96c20a50b", [ null, { + "testdriver": true + } + ] + ], + "createcredential-badargs-authnrselection.https.html": [ + "5da0745734ffbfbcd0d1658e4f4c1914d9940611", + [ + null, + { + "testdriver": true, "timeout": "long" } ] ], "createcredential-badargs-challenge.https.html": [ - "554fc0ec43b8d7265b8f0bf003641a9f5cba150c", + "4fa41df11dd43d720cc9467580913455ab2a60ec", [ null, { + "testdriver": true, "timeout": "long" } ] ], "createcredential-badargs-rp.https.html": [ - "890ba21a81bb2bcc585a2c3969f2155f54361b4b", + "cbd86b8f083e3d1d3b28842b9fa5759047cfac1e", [ null, { + "testdriver": true, "timeout": "long" } ] ], "createcredential-badargs-user.https.html": [ - "070a5c77dea294d340033424872a774786167986", + "e487242571769125238e87f0d22731a54c58bed5", [ null, { + "testdriver": true, "timeout": "long" } ] ], "createcredential-excludecredentials.https.html": [ - "f084d00864f140070c67fa523edc1efce20aab82", + "3a5af481fcac6cd1f9f0e1e95caab7ba7a7facb4", [ null, { + "testdriver": true, "timeout": "long" } ] ], "createcredential-extensions.https.html": [ - "f4a05c3042f8172dcdc4737a30c15af4bd6af8d4", + "036200dbbf949e7d0b274e44cc88e7aef1366cb8", [ null, { + "testdriver": true, "timeout": "long" } ] ], "createcredential-passing.https.html": [ - "92ef14912ba6fb7aaec85ce207d236d29d87b6bc", + "ab10c7f519a6868c28c233fa6f933da925fe3dc9", [ null, { + "testdriver": true, "timeout": "long" } ] ], "createcredential-pubkeycredparams.https.html": [ - "34622fdf74e50b65a425488a94f17cbc4364b59f", + "c845a90687576a2d33b7f459c293aa74a15c1fb9", [ null, { + "testdriver": true, "timeout": "long" } ] ], "createcredential-timeout.https.html": [ - "d4aa459240b732dfdcebdd85c2afccd862a9d947", - [ - null, - {} - ] - ], - "getcredential-badargs-rpid.https.html": [ - "956b26fb9d96d2a6f5c320da3de6fc1e5e5de2b1", + "09af4520ae87f3540772d3d257d385dfc3647058", [ null, { + "testdriver": true, + "timeout": "long" + } + ] + ], + "getcredential-badargs-rpid.https.html": [ + "6e0ef5f201842f878d4cc2cd38a05e1923c992a8", + [ + null, + { + "testdriver": true, "timeout": "long" } ] ], "getcredential-badargs-userverification.https.html": [ - "5bc579bc3335e56aa71adcc5d3a58bab8d51f898", + "7f8571586e4094e663399b79f473676cdfb0a660", [ null, { + "testdriver": true, "timeout": "long" } ] ], "getcredential-extensions.https.html": [ - "763f48b70f34428e83a3a5d6419e7726d45211bb", + "820f9b529f16e5644d3d288f406db6e71a72cf34", [ null, { + "testdriver": true, "timeout": "long" } ] ], "getcredential-passing.https.html": [ - "86dd668d49518f7d18ba6dbd848f8ed2e012cf5d", + "dae05e0993a0358ec77c3c2c7981c39a7483c82e", [ null, { + "testdriver": true, "timeout": "long" } ] ], "getcredential-timeout.https.html": [ - "e6cd884ae064a59976c9191ff09da106e6de9739", + "228ef50978971c2b17882c5f33ad4e68da61b44f", [ null, { + "testdriver": true, "timeout": "long" } ] @@ -549623,14 +549793,14 @@ ] ], "securecontext.http.html": [ - "52786950f383eb0454edcf9fad9b637dde956ca8", + "27d2dbfce3e9c15c453597fc53b745426d33aae4", [ null, {} ] ], "securecontext.https.html": [ - "6c9aabd11ea3a51688962890dde29d5c41ec6c6f", + "f927004702443be4d23f2c206ee1b4d07a0e1bea", [ null, {} @@ -550029,7 +550199,7 @@ ], "broadcastchannel": { "basics.html": [ - "3d8ba76fb1ba35658c44702059925fe53e3fb6f6", + "ed16e32f5437dc430b5cc11b967e6538ef6cf393", [ null, {} @@ -550193,14 +550363,14 @@ ] ], "postMessage_Document.htm": [ - "a5580ef4b2b9137fc9add8ab1eb9a8f224dc6a80", + "c00a09a8658557c14d4c2925affb2660b9bd76bf", [ null, {} ] ], "postMessage_Function.htm": [ - "fd31f83df4b75fe36a3a8581c81d230888b3a879", + "3976cebb747bbe92bf1dc671a297b502954c52e9", [ null, {} @@ -550249,14 +550419,14 @@ ] ], "postMessage_dup_transfer_objects.htm": [ - "511b51271f30a2655951e8556488621a7095b826", + "ade9515d3acd42e1a12bd97627a57f7a13e75787", [ null, {} ] ], "postMessage_invalid_targetOrigin.htm": [ - "b9cc6377ef82a511264857f5b1bf424ef08d007d", + "e2b39df22df1bbe07f1220b4def7998a5a3f641d", [ null, {} @@ -551093,7 +551263,7 @@ ] ], "RTCPeerConnection-onnegotiationneeded.html": [ - "3e31c327ac2480bb9d526e5c6665c3486449ff31", + "3b65faca927fad96d45911350be355a1886ba3fb", [ null, {} @@ -551228,7 +551398,7 @@ ] ], "RTCPeerConnection-setRemoteDescription-rollback.html": [ - "66067dae69672b357d88ebb6fe5845f817c09cc9", + "516cb57025b195552ade0e600c02f7c92c8f4aa8", [ null, {} @@ -551597,6 +551767,15 @@ } ] ], + "ice-ufragpwd.html": [ + "bd151284cbb70592d71c67ddfd89c230e737a21f", + [ + null, + { + "timeout": "long" + } + ] + ], "jsep-initial-offer.https.html": [ "50527f88dfe8f3f025cfffae91b347fdc2527a1d", [ @@ -557211,7 +557390,7 @@ }, "dom-overlay": { "ar_dom_overlay.https.html": [ - "d63197c46a56f622e959420fb07d64b0840e6083", + "15eac78d9288b507cbd58143c94ea545145d9441", [ null, {} @@ -557260,6 +557439,13 @@ {} ] ], + "events_session_squeeze.https.html": [ + "59e4e3f72c5d63cff47b6a7e1dfbac6194203534", + [ + null, + {} + ] + ], "exclusive_requestFrame_nolayer.https.html": [ "f76847a8769073bb4753702f32dd5c2ccc9d3532", [ @@ -557962,7 +558148,7 @@ ] ], "Worker-constructor-proto.any.js": [ - "90ad767b9807853f6fbc611dbeac435b4c14079b", + "f0544b6c15a9bf54333cff1ab7299d2445cf358b", [ "workers/Worker-constructor-proto.any.serviceworker.html", { @@ -562190,14 +562376,14 @@ ] ], "open-url-multi-window-2.htm": [ - "fdd91016db8c3ecc93bafee43dade4aea2a44ea2", + "64cc96b61b25dc6647d0eb64bb4d945a5072f1d5", [ null, {} ] ], "open-url-multi-window-3.htm": [ - "cfce8310dc42b916ed958e69f0b4761c694f2619", + "e156857fe05daa4e3a47a3ca413a9c9abaa062bc", [ null, {} @@ -562211,7 +562397,7 @@ ] ], "open-url-multi-window-5.htm": [ - "40a3429fbb472a7396bc7d0475cd0f6b0c23e6e7", + "32c467a4956c4b47614b832bbe7111efc7c4139e", [ null, {} diff --git a/tests/wpt/metadata/WebCryptoAPI/idlharness.https.any.js.ini b/tests/wpt/metadata/WebCryptoAPI/idlharness.https.any.js.ini index d489c813cca..bce2e9a48e8 100644 --- a/tests/wpt/metadata/WebCryptoAPI/idlharness.https.any.js.ini +++ b/tests/wpt/metadata/WebCryptoAPI/idlharness.https.any.js.ini @@ -1,232 +1,417 @@ [idlharness.https.any.html] [idlharness] expected: FAIL + [Crypto interface: attribute subtle] expected: FAIL + [Crypto interface: crypto must inherit property "subtle" with the proper type] expected: FAIL + [CryptoKey interface: existence and properties of interface object] expected: FAIL + [CryptoKey interface object length] expected: FAIL + [CryptoKey interface object name] expected: FAIL + [CryptoKey interface: existence and properties of interface prototype object] expected: FAIL + [CryptoKey interface: existence and properties of interface prototype object's "constructor" property] expected: FAIL + [CryptoKey interface: existence and properties of interface prototype object's @@unscopables property] expected: FAIL + [CryptoKey interface: attribute type] expected: FAIL + [CryptoKey interface: attribute extractable] expected: FAIL + [CryptoKey interface: attribute algorithm] expected: FAIL + [CryptoKey interface: attribute usages] expected: FAIL + [SubtleCrypto interface: existence and properties of interface object] expected: FAIL + [SubtleCrypto interface object length] expected: FAIL + [SubtleCrypto interface object name] expected: FAIL + [SubtleCrypto interface: existence and properties of interface prototype object] expected: FAIL + [SubtleCrypto interface: existence and properties of interface prototype object's "constructor" property] expected: FAIL + [SubtleCrypto interface: existence and properties of interface prototype object's @@unscopables property] expected: FAIL + [SubtleCrypto interface: operation encrypt(AlgorithmIdentifier, CryptoKey, BufferSource)] expected: FAIL + [SubtleCrypto interface: operation decrypt(AlgorithmIdentifier, CryptoKey, BufferSource)] expected: FAIL + [SubtleCrypto interface: operation sign(AlgorithmIdentifier, CryptoKey, BufferSource)] expected: FAIL + [SubtleCrypto interface: operation verify(AlgorithmIdentifier, CryptoKey, BufferSource, BufferSource)] expected: FAIL + [SubtleCrypto interface: operation digest(AlgorithmIdentifier, BufferSource)] expected: FAIL - [SubtleCrypto interface: operation generateKey(AlgorithmIdentifier, boolean, \[object Object\])] + + [SubtleCrypto interface: operation generateKey(AlgorithmIdentifier, boolean, [object Object\])] expected: FAIL - [SubtleCrypto interface: operation deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, \[object Object\])] + + [SubtleCrypto interface: operation deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, [object Object\])] expected: FAIL + [SubtleCrypto interface: operation deriveBits(AlgorithmIdentifier, CryptoKey, unsigned long)] expected: FAIL - [SubtleCrypto interface: operation importKey(KeyFormat, \[object Object\],\[object Object\], AlgorithmIdentifier, boolean, \[object Object\])] + + [SubtleCrypto interface: operation importKey(KeyFormat, [object Object\],[object Object\], AlgorithmIdentifier, boolean, [object Object\])] expected: FAIL + [SubtleCrypto interface: operation exportKey(KeyFormat, CryptoKey)] expected: FAIL + [SubtleCrypto interface: operation wrapKey(KeyFormat, CryptoKey, CryptoKey, AlgorithmIdentifier)] expected: FAIL - [SubtleCrypto interface: operation unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, \[object Object\])] + + [SubtleCrypto interface: operation unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, [object Object\])] expected: FAIL + [SubtleCrypto must be primary interface of crypto.subtle] expected: FAIL + [Stringification of crypto.subtle] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "encrypt(AlgorithmIdentifier, CryptoKey, BufferSource)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling encrypt(AlgorithmIdentifier, CryptoKey, BufferSource) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "decrypt(AlgorithmIdentifier, CryptoKey, BufferSource)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling decrypt(AlgorithmIdentifier, CryptoKey, BufferSource) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "sign(AlgorithmIdentifier, CryptoKey, BufferSource)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling sign(AlgorithmIdentifier, CryptoKey, BufferSource) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "verify(AlgorithmIdentifier, CryptoKey, BufferSource, BufferSource)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling verify(AlgorithmIdentifier, CryptoKey, BufferSource, BufferSource) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "digest(AlgorithmIdentifier, BufferSource)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling digest(AlgorithmIdentifier, BufferSource) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL - [SubtleCrypto interface: crypto.subtle must inherit property "generateKey(AlgorithmIdentifier, boolean, \[object Object\])" with the proper type] + + [SubtleCrypto interface: crypto.subtle must inherit property "generateKey(AlgorithmIdentifier, boolean, [object Object\])" with the proper type] expected: FAIL - [SubtleCrypto interface: calling generateKey(AlgorithmIdentifier, boolean, \[object Object\]) on crypto.subtle with too few arguments must throw TypeError] + + [SubtleCrypto interface: calling generateKey(AlgorithmIdentifier, boolean, [object Object\]) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL - [SubtleCrypto interface: crypto.subtle must inherit property "deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, \[object Object\])" with the proper type] + + [SubtleCrypto interface: crypto.subtle must inherit property "deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, [object Object\])" with the proper type] expected: FAIL - [SubtleCrypto interface: calling deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, \[object Object\]) on crypto.subtle with too few arguments must throw TypeError] + + [SubtleCrypto interface: calling deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, [object Object\]) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "deriveBits(AlgorithmIdentifier, CryptoKey, unsigned long)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling deriveBits(AlgorithmIdentifier, CryptoKey, unsigned long) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL - [SubtleCrypto interface: crypto.subtle must inherit property "importKey(KeyFormat, \[object Object\],\[object Object\], AlgorithmIdentifier, boolean, \[object Object\])" with the proper type] + + [SubtleCrypto interface: crypto.subtle must inherit property "importKey(KeyFormat, [object Object\],[object Object\], AlgorithmIdentifier, boolean, [object Object\])" with the proper type] expected: FAIL - [SubtleCrypto interface: calling importKey(KeyFormat, \[object Object\],\[object Object\], AlgorithmIdentifier, boolean, \[object Object\]) on crypto.subtle with too few arguments must throw TypeError] + + [SubtleCrypto interface: calling importKey(KeyFormat, [object Object\],[object Object\], AlgorithmIdentifier, boolean, [object Object\]) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "exportKey(KeyFormat, CryptoKey)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling exportKey(KeyFormat, CryptoKey) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "wrapKey(KeyFormat, CryptoKey, CryptoKey, AlgorithmIdentifier)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling wrapKey(KeyFormat, CryptoKey, CryptoKey, AlgorithmIdentifier) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL - [SubtleCrypto interface: crypto.subtle must inherit property "unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, \[object Object\])" with the proper type] + + [SubtleCrypto interface: crypto.subtle must inherit property "unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, [object Object\])" with the proper type] expected: FAIL - [SubtleCrypto interface: calling unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, \[object Object\]) on crypto.subtle with too few arguments must throw TypeError] + + [SubtleCrypto interface: calling unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, [object Object\]) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL - + + [SubtleCrypto interface: operation unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, sequence)] + expected: FAIL + + [SubtleCrypto interface: crypto.subtle must inherit property "unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, sequence)" with the proper type] + expected: FAIL + + [SubtleCrypto interface: crypto.subtle must inherit property "importKey(KeyFormat, (BufferSource or JsonWebKey), AlgorithmIdentifier, boolean, sequence)" with the proper type] + expected: FAIL + + [SubtleCrypto interface: crypto.subtle must inherit property "generateKey(AlgorithmIdentifier, boolean, sequence)" with the proper type] + expected: FAIL + + [SubtleCrypto interface: calling importKey(KeyFormat, (BufferSource or JsonWebKey), AlgorithmIdentifier, boolean, sequence) on crypto.subtle with too few arguments must throw TypeError] + expected: FAIL + + [SubtleCrypto interface: operation deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence)] + expected: FAIL + + [SubtleCrypto interface: calling unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, sequence) on crypto.subtle with too few arguments must throw TypeError] + expected: FAIL + + [SubtleCrypto interface: operation generateKey(AlgorithmIdentifier, boolean, sequence)] + expected: FAIL + + [SubtleCrypto interface: crypto.subtle must inherit property "deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence)" with the proper type] + expected: FAIL + + [SubtleCrypto interface: operation importKey(KeyFormat, (BufferSource or JsonWebKey), AlgorithmIdentifier, boolean, sequence)] + expected: FAIL + + [SubtleCrypto interface: calling deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence) on crypto.subtle with too few arguments must throw TypeError] + expected: FAIL + + [SubtleCrypto interface: calling generateKey(AlgorithmIdentifier, boolean, sequence) on crypto.subtle with too few arguments must throw TypeError] + expected: FAIL + [idlharness.https.any.worker.html] [idlharness] expected: FAIL + [Crypto interface: attribute subtle] expected: FAIL + [Crypto interface: crypto must inherit property "subtle" with the proper type] expected: FAIL + [CryptoKey interface: existence and properties of interface object] expected: FAIL + [CryptoKey interface object length] expected: FAIL + [CryptoKey interface object name] expected: FAIL + [CryptoKey interface: existence and properties of interface prototype object] expected: FAIL + [CryptoKey interface: existence and properties of interface prototype object's "constructor" property] expected: FAIL + [CryptoKey interface: existence and properties of interface prototype object's @@unscopables property] expected: FAIL + [CryptoKey interface: attribute type] expected: FAIL + [CryptoKey interface: attribute extractable] expected: FAIL + [CryptoKey interface: attribute algorithm] expected: FAIL + [CryptoKey interface: attribute usages] expected: FAIL + [SubtleCrypto interface: existence and properties of interface object] expected: FAIL + [SubtleCrypto interface object length] expected: FAIL + [SubtleCrypto interface object name] expected: FAIL + [SubtleCrypto interface: existence and properties of interface prototype object] expected: FAIL + [SubtleCrypto interface: existence and properties of interface prototype object's "constructor" property] expected: FAIL + [SubtleCrypto interface: existence and properties of interface prototype object's @@unscopables property] expected: FAIL + [SubtleCrypto interface: operation encrypt(AlgorithmIdentifier, CryptoKey, BufferSource)] expected: FAIL + [SubtleCrypto interface: operation decrypt(AlgorithmIdentifier, CryptoKey, BufferSource)] expected: FAIL + [SubtleCrypto interface: operation sign(AlgorithmIdentifier, CryptoKey, BufferSource)] expected: FAIL + [SubtleCrypto interface: operation verify(AlgorithmIdentifier, CryptoKey, BufferSource, BufferSource)] expected: FAIL + [SubtleCrypto interface: operation digest(AlgorithmIdentifier, BufferSource)] expected: FAIL - [SubtleCrypto interface: operation generateKey(AlgorithmIdentifier, boolean, \[object Object\])] + + [SubtleCrypto interface: operation generateKey(AlgorithmIdentifier, boolean, [object Object\])] expected: FAIL - [SubtleCrypto interface: operation deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, \[object Object\])] + + [SubtleCrypto interface: operation deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, [object Object\])] expected: FAIL + [SubtleCrypto interface: operation deriveBits(AlgorithmIdentifier, CryptoKey, unsigned long)] expected: FAIL - [SubtleCrypto interface: operation importKey(KeyFormat, \[object Object\],\[object Object\], AlgorithmIdentifier, boolean, \[object Object\])] + + [SubtleCrypto interface: operation importKey(KeyFormat, [object Object\],[object Object\], AlgorithmIdentifier, boolean, [object Object\])] expected: FAIL + [SubtleCrypto interface: operation exportKey(KeyFormat, CryptoKey)] expected: FAIL + [SubtleCrypto interface: operation wrapKey(KeyFormat, CryptoKey, CryptoKey, AlgorithmIdentifier)] expected: FAIL - [SubtleCrypto interface: operation unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, \[object Object\])] + + [SubtleCrypto interface: operation unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, [object Object\])] expected: FAIL + [SubtleCrypto must be primary interface of crypto.subtle] expected: FAIL + [Stringification of crypto.subtle] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "encrypt(AlgorithmIdentifier, CryptoKey, BufferSource)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling encrypt(AlgorithmIdentifier, CryptoKey, BufferSource) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "decrypt(AlgorithmIdentifier, CryptoKey, BufferSource)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling decrypt(AlgorithmIdentifier, CryptoKey, BufferSource) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "sign(AlgorithmIdentifier, CryptoKey, BufferSource)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling sign(AlgorithmIdentifier, CryptoKey, BufferSource) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "verify(AlgorithmIdentifier, CryptoKey, BufferSource, BufferSource)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling verify(AlgorithmIdentifier, CryptoKey, BufferSource, BufferSource) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "digest(AlgorithmIdentifier, BufferSource)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling digest(AlgorithmIdentifier, BufferSource) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL - [SubtleCrypto interface: crypto.subtle must inherit property "generateKey(AlgorithmIdentifier, boolean, \[object Object\])" with the proper type] + + [SubtleCrypto interface: crypto.subtle must inherit property "generateKey(AlgorithmIdentifier, boolean, [object Object\])" with the proper type] expected: FAIL - [SubtleCrypto interface: calling generateKey(AlgorithmIdentifier, boolean, \[object Object\]) on crypto.subtle with too few arguments must throw TypeError] + + [SubtleCrypto interface: calling generateKey(AlgorithmIdentifier, boolean, [object Object\]) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL - [SubtleCrypto interface: crypto.subtle must inherit property "deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, \[object Object\])" with the proper type] + + [SubtleCrypto interface: crypto.subtle must inherit property "deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, [object Object\])" with the proper type] expected: FAIL - [SubtleCrypto interface: calling deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, \[object Object\]) on crypto.subtle with too few arguments must throw TypeError] + + [SubtleCrypto interface: calling deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, [object Object\]) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "deriveBits(AlgorithmIdentifier, CryptoKey, unsigned long)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling deriveBits(AlgorithmIdentifier, CryptoKey, unsigned long) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL - [SubtleCrypto interface: crypto.subtle must inherit property "importKey(KeyFormat, \[object Object\],\[object Object\], AlgorithmIdentifier, boolean, \[object Object\])" with the proper type] + + [SubtleCrypto interface: crypto.subtle must inherit property "importKey(KeyFormat, [object Object\],[object Object\], AlgorithmIdentifier, boolean, [object Object\])" with the proper type] expected: FAIL - [SubtleCrypto interface: calling importKey(KeyFormat, \[object Object\],\[object Object\], AlgorithmIdentifier, boolean, \[object Object\]) on crypto.subtle with too few arguments must throw TypeError] + + [SubtleCrypto interface: calling importKey(KeyFormat, [object Object\],[object Object\], AlgorithmIdentifier, boolean, [object Object\]) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "exportKey(KeyFormat, CryptoKey)" with the proper type] expected: FAIL + [SubtleCrypto interface: calling exportKey(KeyFormat, CryptoKey) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + [SubtleCrypto interface: crypto.subtle must inherit property "wrapKey(KeyFormat, CryptoKey, CryptoKey, AlgorithmIdentifier)" with the proper type] expected: FAIL - [SubtleCrypto interface: calling wrapKey(KeyFormat, CryptoKey, CryptoKey, AlgorithmIdentifier) on crypto.subtle with too few arguments must throw TypeError] + + [SubtleCrypto interface: calling wrapKey(KeyFormat, CryptoKey, CryptoKey, AlgorithmIdentifier) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL - [SubtleCrypto interface: crypto.subtle must inherit property "unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, \[object Object\])" with the proper type] + + [SubtleCrypto interface: crypto.subtle must inherit property "unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, [object Object\])" with the proper type] expected: FAIL - [SubtleCrypto interface: calling unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, \[object Object\]) on crypto.subtle with too few arguments must throw TypeError] + + [SubtleCrypto interface: calling unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, [object Object\]) on crypto.subtle with too few arguments must throw TypeError] expected: FAIL + + [SubtleCrypto interface: operation unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, sequence)] + expected: FAIL + + [SubtleCrypto interface: crypto.subtle must inherit property "unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, sequence)" with the proper type] + expected: FAIL + + [SubtleCrypto interface: crypto.subtle must inherit property "importKey(KeyFormat, (BufferSource or JsonWebKey), AlgorithmIdentifier, boolean, sequence)" with the proper type] + expected: FAIL + + [SubtleCrypto interface: crypto.subtle must inherit property "generateKey(AlgorithmIdentifier, boolean, sequence)" with the proper type] + expected: FAIL + + [SubtleCrypto interface: calling importKey(KeyFormat, (BufferSource or JsonWebKey), AlgorithmIdentifier, boolean, sequence) on crypto.subtle with too few arguments must throw TypeError] + expected: FAIL + + [SubtleCrypto interface: operation deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence)] + expected: FAIL + + [SubtleCrypto interface: calling unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, sequence) on crypto.subtle with too few arguments must throw TypeError] + expected: FAIL + + [SubtleCrypto interface: operation generateKey(AlgorithmIdentifier, boolean, sequence)] + expected: FAIL + + [SubtleCrypto interface: crypto.subtle must inherit property "deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence)" with the proper type] + expected: FAIL + + [SubtleCrypto interface: operation importKey(KeyFormat, (BufferSource or JsonWebKey), AlgorithmIdentifier, boolean, sequence)] + expected: FAIL + + [SubtleCrypto interface: calling deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence) on crypto.subtle with too few arguments must throw TypeError] + expected: FAIL + + [SubtleCrypto interface: calling generateKey(AlgorithmIdentifier, boolean, sequence) on crypto.subtle with too few arguments must throw TypeError] + expected: FAIL + diff --git a/tests/wpt/metadata/bluetooth/idl/idlharness.tentative.https.window.js.ini b/tests/wpt/metadata/bluetooth/idl/idlharness.tentative.https.window.js.ini index e85766f5e1d..58f6917c666 100644 --- a/tests/wpt/metadata/bluetooth/idl/idlharness.tentative.https.window.js.ini +++ b/tests/wpt/metadata/bluetooth/idl/idlharness.tentative.https.window.js.ini @@ -216,4 +216,26 @@ expected: FAIL [BluetoothRemoteGATTCharacteristic interface: operation writeValueWithoutResponse(BufferSource)] - expected: FAIL \ No newline at end of file + expected: FAIL + + [Bluetooth interface: navigator.bluetooth must inherit property "getDevices()" with the proper type] + expected: FAIL + + [Bluetooth interface: operation requestDevice(optional RequestDeviceOptions)] + expected: FAIL + + [BluetoothRemoteGATTCharacteristic interface: operation getDescriptors(optional BluetoothDescriptorUUID)] + expected: FAIL + + [Bluetooth interface: operation getDevices()] + expected: FAIL + + [BluetoothRemoteGATTService interface: operation getIncludedServices(optional BluetoothServiceUUID)] + expected: FAIL + + [BluetoothRemoteGATTServer interface: operation getPrimaryServices(optional BluetoothServiceUUID)] + expected: FAIL + + [BluetoothRemoteGATTService interface: operation getCharacteristics(optional BluetoothCharacteristicUUID)] + expected: FAIL + diff --git a/tests/wpt/metadata/css/CSS2/floats/hit-test-floats-002.html.ini b/tests/wpt/metadata/css/CSS2/floats/hit-test-floats-002.html.ini deleted file mode 100644 index f64b45fea6b..00000000000 --- a/tests/wpt/metadata/css/CSS2/floats/hit-test-floats-002.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[hit-test-floats-002.html] - [Hit test float] - expected: FAIL - diff --git a/tests/wpt/metadata/css/CSS2/floats/hit-test-floats-003.html.ini b/tests/wpt/metadata/css/CSS2/floats/hit-test-floats-004.html.ini similarity index 67% rename from tests/wpt/metadata/css/CSS2/floats/hit-test-floats-003.html.ini rename to tests/wpt/metadata/css/CSS2/floats/hit-test-floats-004.html.ini index f29da48a2a0..4bfb0c2053a 100644 --- a/tests/wpt/metadata/css/CSS2/floats/hit-test-floats-003.html.ini +++ b/tests/wpt/metadata/css/CSS2/floats/hit-test-floats-004.html.ini @@ -1,4 +1,4 @@ -[hit-test-floats-003.html] +[hit-test-floats-004.html] [Miss float below something else] expected: FAIL diff --git a/tests/wpt/metadata/css/css-fonts/idlharness.html.ini b/tests/wpt/metadata/css/css-fonts/idlharness.html.ini index e81cba11107..0f59480628a 100644 --- a/tests/wpt/metadata/css/css-fonts/idlharness.html.ini +++ b/tests/wpt/metadata/css/css-fonts/idlharness.html.ini @@ -116,3 +116,6 @@ [CSSRule interface: cssRule must inherit property "FONT_FEATURE_VALUES_RULE" with the proper type] expected: FAIL + [CSSFontFeatureValuesMap interface: operation set(CSSOMString, (unsigned long or sequence))] + expected: FAIL + diff --git a/tests/wpt/metadata/css/css-text-decor/text-decoration-skip-ink-005.html.ini b/tests/wpt/metadata/css/css-text-decor/text-decoration-skip-ink-005.html.ini new file mode 100644 index 00000000000..9cedd2f8ed3 --- /dev/null +++ b/tests/wpt/metadata/css/css-text-decor/text-decoration-skip-ink-005.html.ini @@ -0,0 +1,2 @@ +[text-decoration-skip-ink-005.html] + expected: FAIL diff --git a/tests/wpt/metadata/css/css-text-decor/text-decoration-skip-ink.html.ini b/tests/wpt/metadata/css/css-text-decor/text-decoration-skip-ink.html.ini index 33a3b8b189b..512ec173754 100644 --- a/tests/wpt/metadata/css/css-text-decor/text-decoration-skip-ink.html.ini +++ b/tests/wpt/metadata/css/css-text-decor/text-decoration-skip-ink.html.ini @@ -5,3 +5,6 @@ [Property text-decoration-skip-ink must support values auto and none.] expected: FAIL + [Property text-decoration-skip-ink must support values auto, none and all.] + expected: FAIL + diff --git a/tests/wpt/metadata/css/css-transforms/transform-scale-hittest.html.ini b/tests/wpt/metadata/css/css-transforms/transform-scale-hittest.html.ini index f8e7e539aae..4a1e8110f6f 100644 --- a/tests/wpt/metadata/css/css-transforms/transform-scale-hittest.html.ini +++ b/tests/wpt/metadata/css/css-transforms/transform-scale-hittest.html.ini @@ -2,6 +2,3 @@ [Hit test intersecting scaled box] expected: FAIL - [Hit test within unscaled box] - expected: FAIL - diff --git a/tests/wpt/metadata/css/css-transitions/no-transition-from-ua-to-blocking-stylesheet.html.ini b/tests/wpt/metadata/css/css-transitions/no-transition-from-ua-to-blocking-stylesheet.html.ini index e35a452a186..70a00a101f6 100644 --- a/tests/wpt/metadata/css/css-transitions/no-transition-from-ua-to-blocking-stylesheet.html.ini +++ b/tests/wpt/metadata/css/css-transitions/no-transition-from-ua-to-blocking-stylesheet.html.ini @@ -1,2 +1,2 @@ [no-transition-from-ua-to-blocking-stylesheet.html] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/metadata/css/css-values/minmax-number-computed.html.ini b/tests/wpt/metadata/css/css-values/minmax-number-computed.html.ini index 7293d0a802a..632a2867a95 100644 --- a/tests/wpt/metadata/css/css-values/minmax-number-computed.html.ini +++ b/tests/wpt/metadata/css/css-values/minmax-number-computed.html.ini @@ -83,3 +83,9 @@ [Property opacity value 'calc(max(0.1, 0.2) * 2)'] expected: FAIL + [Property opacity value 'max(0, 0.5)'] + expected: FAIL + + [Property opacity value 'min(0, 0.5)'] + expected: FAIL + diff --git a/tests/wpt/metadata/css/cssom-view/CaretPosition-001.html.ini b/tests/wpt/metadata/css/cssom-view/CaretPosition-001.html.ini new file mode 100644 index 00000000000..4c79907309b --- /dev/null +++ b/tests/wpt/metadata/css/cssom-view/CaretPosition-001.html.ini @@ -0,0 +1,4 @@ +[CaretPosition-001.html] + [Element at (400, 100)] + expected: FAIL + diff --git a/tests/wpt/metadata/css/cssom-view/MediaQueryList-addListener-removeListener.html.ini b/tests/wpt/metadata/css/cssom-view/MediaQueryList-addListener-removeListener.html.ini index 628b1fab770..c884dc82eab 100644 --- a/tests/wpt/metadata/css/cssom-view/MediaQueryList-addListener-removeListener.html.ini +++ b/tests/wpt/metadata/css/cssom-view/MediaQueryList-addListener-removeListener.html.ini @@ -2,3 +2,6 @@ [listeners are called when +
+
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 "