diff options
Diffstat (limited to 'testing/web-platform/tests/tools/pywebsocket')
88 files changed, 0 insertions, 22152 deletions
diff --git a/testing/web-platform/tests/tools/pywebsocket/src/COPYING b/testing/web-platform/tests/tools/pywebsocket/src/COPYING deleted file mode 100644 index 989d02e4c..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/COPYING +++ /dev/null @@ -1,28 +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. diff --git a/testing/web-platform/tests/tools/pywebsocket/src/MANIFEST.in b/testing/web-platform/tests/tools/pywebsocket/src/MANIFEST.in deleted file mode 100644 index 19256882c..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include COPYING -include MANIFEST.in -include README -recursive-include example *.py -recursive-include mod_pywebsocket *.py -recursive-include test *.py diff --git a/testing/web-platform/tests/tools/pywebsocket/src/README b/testing/web-platform/tests/tools/pywebsocket/src/README deleted file mode 100644 index c8c758f5e..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/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/testing/web-platform/tests/tools/pywebsocket/src/example/abort_handshake_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/example/abort_handshake_wsh.py deleted file mode 100644 index 008023a1f..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/abort_handshake_wsh.py +++ /dev/null @@ -1,43 +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. - - -from mod_pywebsocket import handshake - - -def web_socket_do_extra_handshake(request): - raise handshake.AbortedByUserException( - "Aborted in web_socket_do_extra_handshake") - - -def web_socket_transfer_data(request): - pass - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/abort_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/example/abort_wsh.py deleted file mode 100644 index 2bbf005f6..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/abort_wsh.py +++ /dev/null @@ -1,43 +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. - - -from mod_pywebsocket import handshake - - -def web_socket_do_extra_handshake(request): - pass - - -def web_socket_transfer_data(request): - raise handshake.AbortedByUserException( - "Aborted in web_socket_transfer_data") - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/arraybuffer_benchmark.html b/testing/web-platform/tests/tools/pywebsocket/src/example/arraybuffer_benchmark.html deleted file mode 100644 index 869cd7e1e..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/arraybuffer_benchmark.html +++ /dev/null @@ -1,134 +0,0 @@ -<!-- -Copyright 2013, 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. ---> - -<html> -<head> -<title>ArrayBuffer benchmark</title> -<script src="util.js"></script> -<script> -var PRINT_SIZE = true; - -// Initial size of arrays. -var START_SIZE = 10 * 1024; -// Stops benchmark when the size of an array exceeds this threshold. -var STOP_THRESHOLD = 100000 * 1024; -// If the size of each array is small, write/read the array multiple times -// until the sum of sizes reaches this threshold. -var MIN_TOTAL = 100000 * 1024; -var MULTIPLIERS = [5, 2]; - -// Repeat benchmark for several times to measure performance of optimized -// (such as JIT) run. -var REPEAT_FOR_WARMUP = 3; - -function writeBenchmark(size, minTotal) { - var totalSize = 0; - while (totalSize < minTotal) { - var arrayBuffer = new ArrayBuffer(size); - - // Write 'a's. - fillArrayBuffer(arrayBuffer, 0x61); - - totalSize += size; - } - return totalSize; -} - -function readBenchmark(size, minTotal) { - var totalSize = 0; - while (totalSize < minTotal) { - var arrayBuffer = new ArrayBuffer(size); - - if (!verifyArrayBuffer(arrayBuffer, 0x00)) { - queueLog('Verification failed'); - return -1; - } - - totalSize += size; - } - return totalSize; -} - -function runBenchmark(benchmarkFunction, - size, - stopThreshold, - minTotal, - multipliers, - multiplierIndex) { - while (size <= stopThreshold) { - var maxSpeed = 0; - - for (var i = 0; i < REPEAT_FOR_WARMUP; ++i) { - var startTimeInMs = getTimeStamp(); - - var totalSize = benchmarkFunction(size, minTotal); - - maxSpeed = Math.max(maxSpeed, - calculateSpeedInKB(totalSize, startTimeInMs)); - } - queueLog(formatResultInKiB(size, maxSpeed, PRINT_SIZE)); - - size *= multipliers[multiplierIndex]; - multiplierIndex = (multiplierIndex + 1) % multipliers.length; - } -} - -function runBenchmarks() { - queueLog('Message size in KiB, Speed in kB/s'); - - queueLog('Write benchmark'); - runBenchmark( - writeBenchmark, START_SIZE, STOP_THRESHOLD, MIN_TOTAL, MULTIPLIERS, 0); - queueLog('Finished'); - - queueLog('Read benchmark'); - runBenchmark( - readBenchmark, START_SIZE, STOP_THRESHOLD, MIN_TOTAL, MULTIPLIERS, 0); - addToLog('Finished'); -} - -function init() { - logBox = document.getElementById('log'); - - queueLog(window.navigator.userAgent.toLowerCase()); - - addToLog('Started...'); - - setTimeout(runBenchmarks, 0); -} - -</script> -</head> -<body onload="init()"> -<textarea - id="log" rows="50" style="width: 100%" readonly></textarea> -</body> -</html> diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/bench_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/example/bench_wsh.py deleted file mode 100644 index 5067ca7d8..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/bench_wsh.py +++ /dev/null @@ -1,60 +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. - - -"""A simple load tester for WebSocket clients. - -A client program sends a message formatted as "<time> <count> <message>" to -this handler. This handler starts sending total <count> WebSocket messages -containing <message> every <time> seconds. <time> can be a floating point -value. <count> must be an integer value. -""" - - -import time - - -def web_socket_do_extra_handshake(request): - pass # Always accept. - - -def web_socket_transfer_data(request): - line = request.ws_stream.receive_message() - parts = line.split(' ') - if len(parts) != 3: - raise ValueError('Bad parameter format') - wait = float(parts[0]) - count = int(parts[1]) - message = parts[2] - for i in xrange(count): - request.ws_stream.send_message(message) - time.sleep(wait) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/benchmark.html b/testing/web-platform/tests/tools/pywebsocket/src/example/benchmark.html deleted file mode 100644 index 3a218173a..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/benchmark.html +++ /dev/null @@ -1,203 +0,0 @@ -<!-- -Copyright 2013, 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. ---> - -<html> -<head> -<title>WebSocket benchmark</title> -<script src="util_main.js"></script> -<script src="util.js"></script> -<script src="benchmark.js"></script> -<script> -var addressBox = null; - -function getConfig() { - return { - prefixUrl: addressBox.value, - printSize: getBoolFromCheckBox('printsize'), - numSockets: getIntFromInput('numsockets'), - // Initial size of messages. - numIterations: getIntFromInput('numiterations'), - numWarmUpIterations: getIntFromInput('numwarmupiterations'), - startSize: getIntFromInput('startsize'), - // Stops benchmark when the size of message exceeds this threshold. - stopThreshold: getIntFromInput('stopthreshold'), - // If the size of each message is small, send/receive multiple messages - // until the sum of sizes reaches this threshold. - minTotal: getIntFromInput('mintotal'), - multipliers: getIntArrayFromInput('multipliers'), - verifyData: getBoolFromCheckBox('verifydata') - }; -} - -var worker = new Worker('benchmark.js'); -worker.onmessage = onMessage; - -function onSendBenchmark() { - var config = getConfig(); - - if (getBoolFromCheckBox('worker')) { - worker.postMessage({type: 'sendBenchmark', config: config}); - } else { - config.addToLog = addToLog; - config.addToSummary = addToSummary; - config.measureValue = measureValue; - sendBenchmark(config); - } -} - -function onReceiveBenchmark() { - var config = getConfig(); - - if (getBoolFromCheckBox('worker')) { - worker.postMessage({type: 'receiveBenchmark', config: config}); - } else { - config.addToLog = addToLog; - config.addToSummary = addToSummary; - config.measureValue = measureValue; - receiveBenchmark(config); - } -} - -function onBatchBenchmark() { - var config = getConfig(); - - if (getBoolFromCheckBox('worker')) { - worker.postMessage({type: 'batchBenchmark', config: config}); - } else { - config.addToLog = addToLog; - config.addToSummary = addToSummary; - config.measureValue = measureValue; - batchBenchmark(config); - } -} - -function onStop() { - var config = getConfig(); - - if (getBoolFromCheckBox('worker')) { - worker.postMessage({type: 'stop', config: config}); - } else { - config.addToLog = addToLog; - config.addToSummary = addToSummary; - config.measureValue = measureValue; - stop(config); - } -} -function init() { - addressBox = document.getElementById('address'); - logBox = document.getElementById('log'); - - summaryBox = document.getElementById('summary'); - - var scheme = window.location.protocol == 'https:' ? 'wss://' : 'ws://'; - var defaultAddress = scheme + window.location.host + '/benchmark_helper'; - - addressBox.value = defaultAddress; - - addToLog(window.navigator.userAgent.toLowerCase()); - addToSummary(window.navigator.userAgent.toLowerCase()); - - if (!('WebSocket' in window)) { - addToLog('WebSocket is not available'); - } -} -</script> -</head> -<body onload="init()"> - -<div id="benchmark_div"> - url <input type="text" id="address" size="40"> - <input type="button" value="send" onclick="onSendBenchmark()"> - <input type="button" value="receive" onclick="onReceiveBenchmark()"> - <input type="button" value="batch" onclick="onBatchBenchmark()"> - <input type="button" value="stop" onclick="onStop()"> - - <br/> - - <input type="checkbox" id="printsize" checked> - <label for="printsize">Print size and time per message</label> - <input type="checkbox" id="verifydata" checked> - <label for="verifydata">Verify data</label> - <input type="checkbox" id="worker"> - <label for="worker">Run on worker</label> - - <br/> - - Parameters: - - <br/> - - <table> - <tr> - <td>Num sockets</td> - <td><input type="text" id="numsockets" value="1"></td> - </tr> - <tr> - <td>Number of iterations</td> - <td><input type="text" id="numiterations" value="1"></td> - </tr> - <tr> - <td>Number of warm-up iterations</td> - <td><input type="text" id="numwarmupiterations" value="0"></td> - </tr> - <tr> - <td>Start size</td> - <td><input type="text" id="startsize" value="10240"></td> - </tr> - <tr> - <td>Stop threshold</td> - <td><input type="text" id="stopthreshold" value="102400000"></td> - </tr> - <tr> - <td>Minimum total</td> - <td><input type="text" id="mintotal" value="102400000"></td> - </tr> - <tr> - <td>Multipliers</td> - <td><input type="text" id="multipliers" value="5, 2"></td> - </tr> - </table> -</div> - -<div id="log_div"> - <textarea - id="log" rows="20" style="width: 100%" readonly></textarea> -</div> -<div id="summary_div"> - Summary - <textarea - id="summary" rows="20" style="width: 100%" readonly></textarea> -</div> - -Note: Effect of RTT is not eliminated. - -</body> -</html> diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/benchmark.js b/testing/web-platform/tests/tools/pywebsocket/src/example/benchmark.js deleted file mode 100644 index d347ae9e1..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/benchmark.js +++ /dev/null @@ -1,309 +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 - -if (typeof importScripts !== "undefined") { - // Running on a worker - importScripts('util.js', 'util_worker.js'); -} - -// Namespace for holding globals. -var benchmark = {startTimeInMs: 0}; - -var sockets = []; -var numEstablishedSockets = 0; - -var timerID = null; - -function destroySocket(socket) { - socket.onopen = null; - socket.onmessage = null; - socket.onerror = null; - socket.onclose = null; - socket.close(); -} - -function destroyAllSockets() { - for (var i = 0; i < sockets.length; ++i) { - destroySocket(sockets[i]); - } - sockets = []; -} - -function sendBenchmarkStep(size, config) { - timerID = null; - - var totalSize = 0; - var totalReplied = 0; - - var onMessageHandler = function(event) { - if (!verifyAcknowledgement(config, event.data, size)) { - destroyAllSockets(); - return; - } - - totalReplied += size; - - if (totalReplied < totalSize) { - return; - } - - calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize); - - runNextTask(config); - }; - - for (var i = 0; i < sockets.length; ++i) { - var socket = sockets[i]; - socket.onmessage = onMessageHandler; - } - - var dataArray = []; - - while (totalSize < config.minTotal) { - var buffer = new ArrayBuffer(size); - - fillArrayBuffer(buffer, 0x61); - - dataArray.push(buffer); - totalSize += size; - } - - benchmark.startTimeInMs = getTimeStamp(); - - totalSize = 0; - - var socketIndex = 0; - var dataIndex = 0; - while (totalSize < config.minTotal) { - var command = ['send']; - command.push(config.verifyData ? '1' : '0'); - sockets[socketIndex].send(command.join(' ')); - sockets[socketIndex].send(dataArray[dataIndex]); - socketIndex = (socketIndex + 1) % sockets.length; - - totalSize += size; - ++dataIndex; - } -} - -function receiveBenchmarkStep(size, config) { - timerID = null; - - var totalSize = 0; - var totalReplied = 0; - - var onMessageHandler = function(event) { - var bytesReceived = event.data.byteLength; - if (bytesReceived != size) { - config.addToLog('Expected ' + size + 'B but received ' + - bytesReceived + 'B'); - destroyAllSockets(); - return; - } - - if (config.verifyData && !verifyArrayBuffer(event.data, 0x61)) { - config.addToLog('Response verification failed'); - destroyAllSockets(); - return; - } - - totalReplied += bytesReceived; - - if (totalReplied < totalSize) { - return; - } - - calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize); - - runNextTask(config); - }; - - for (var i = 0; i < sockets.length; ++i) { - var socket = sockets[i]; - socket.binaryType = 'arraybuffer'; - socket.onmessage = onMessageHandler; - } - - benchmark.startTimeInMs = getTimeStamp(); - - var socketIndex = 0; - while (totalSize < config.minTotal) { - sockets[socketIndex].send('receive ' + size); - socketIndex = (socketIndex + 1) % sockets.length; - - totalSize += size; - } -} - -function createSocket(config) { - // TODO(tyoshino): Add TCP warm up. - var url = config.prefixUrl; - - config.addToLog('Connect ' + url); - - var socket = new WebSocket(url); - socket.onmessage = function(event) { - config.addToLog('Unexpected message received. Aborting.'); - }; - socket.onerror = function() { - config.addToLog('Error'); - }; - socket.onclose = function(event) { - config.addToLog('Closed'); - }; - return socket; -} - -var tasks = []; - -function startBenchmark(config) { - clearTimeout(timerID); - destroyAllSockets(); - - numEstablishedSockets = 0; - - for (var i = 0; i < config.numSockets; ++i) { - var socket = createSocket(config); - socket.onopen = function() { - config.addToLog('Opened'); - - ++numEstablishedSockets; - - if (numEstablishedSockets == sockets.length) { - runNextTask(config); - } - }; - sockets.push(socket); - } -} - -function runNextTask(config) { - var task = tasks.shift(); - if (task == undefined) { - config.addToLog('Finished'); - destroyAllSockets(); - return; - } - timerID = setTimeout(task, 0); -} - -function buildLegendString(config) { - var legend = '' - if (config.printSize) - legend = 'Message size in KiB, Time/message in ms, '; - legend += 'Speed in kB/s'; - return legend; -} - -function getConfigString(config) { - return '(WebSocket' + - ', ' + (typeof importScripts !== "undefined" ? 'Worker' : 'Main') + - ', numSockets=' + config.numSockets + - ', numIterations=' + config.numIterations + - ', verifyData=' + config.verifyData + - ', minTotal=' + config.minTotal + - ', numWarmUpIterations=' + config.numWarmUpIterations + - ')'; -} - -function addTasks(config, stepFunc) { - for (var i = 0; - i < config.numWarmUpIterations + config.numIterations; ++i) { - // Ignore the first |config.numWarmUpIterations| iterations. - if (i == config.numWarmUpIterations) - addResultClearingTask(config); - - var multiplierIndex = 0; - for (var size = config.startSize; - size <= config.stopThreshold; - ++multiplierIndex) { - var task = stepFunc.bind( - null, - size, - config); - tasks.push(task); - size *= config.multipliers[ - multiplierIndex % config.multipliers.length]; - } - } -} - -function addResultReportingTask(config, title) { - tasks.push(function(){ - timerID = null; - config.addToSummary(title); - reportAverageData(config); - clearAverageData(); - runNextTask(config); - }); -} - -function addResultClearingTask(config) { - tasks.push(function(){ - timerID = null; - clearAverageData(); - runNextTask(config); - }); -} - -function sendBenchmark(config) { - config.addToLog('Send benchmark'); - config.addToLog(buildLegendString(config)); - - tasks = []; - clearAverageData(); - addTasks(config, sendBenchmarkStep); - addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config)); - startBenchmark(config); -} - -function receiveBenchmark(config) { - config.addToLog('Receive benchmark'); - config.addToLog(buildLegendString(config)); - - tasks = []; - clearAverageData(); - addTasks(config, receiveBenchmarkStep); - addResultReportingTask(config, - 'Receive Benchmark ' + getConfigString(config)); - startBenchmark(config); -} - -function batchBenchmark(config) { - config.addToLog('Batch benchmark'); - config.addToLog(buildLegendString(config)); - - tasks = []; - clearAverageData(); - addTasks(config, sendBenchmarkStep); - addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config)); - addTasks(config, receiveBenchmarkStep); - addResultReportingTask(config, 'Receive Benchmark ' + - getConfigString(config)); - startBenchmark(config); -} - -function stop(config) { - clearTimeout(timerID); - timerID = null; - config.addToLog('Stopped'); - destroyAllSockets(); -} - -onmessage = function (message) { - var config = message.data.config; - config.addToLog = workerAddToLog; - config.addToSummary = workerAddToSummary; - config.measureValue = workerMeasureValue; - if (message.data.type === 'sendBenchmark') - sendBenchmark(config); - else if (message.data.type === 'receiveBenchmark') - receiveBenchmark(config); - else if (message.data.type === 'batchBenchmark') - batchBenchmark(config); - else if (message.data.type === 'stop') - stop(config); -}; diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/benchmark_helper_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/example/benchmark_helper_wsh.py deleted file mode 100644 index 44ad0bfee..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/benchmark_helper_wsh.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright 2013, 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. - - -"""Handler for benchmark.html.""" - - -def web_socket_do_extra_handshake(request): - # Turn off compression. - request.ws_extension_processors = [] - - -def web_socket_transfer_data(request): - data = '' - - while True: - command = request.ws_stream.receive_message() - if command is None: - return - - if not isinstance(command, unicode): - raise ValueError('Invalid command data:' + command) - commands = command.split(' ') - if len(commands) == 0: - raise ValueError('Invalid command data: ' + command) - - if commands[0] == 'receive': - if len(commands) != 2: - raise ValueError( - 'Illegal number of arguments for send command' + - command) - size = int(commands[1]) - - # Reuse data if possible. - if len(data) != size: - data = 'a' * size - request.ws_stream.send_message(data, binary=True) - elif commands[0] == 'send': - if len(commands) != 2: - raise ValueError( - 'Illegal number of arguments for receive command' + - command) - verify_data = commands[1] == '1' - - data = request.ws_stream.receive_message() - if data is None: - raise ValueError('Payload not received') - size = len(data) - - if verify_data: - if data != 'a' * size: - raise ValueError('Payload verification failed') - - request.ws_stream.send_message(str(size)) - else: - raise ValueError('Invalid command: ' + commands[0]) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/close_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/example/close_wsh.py deleted file mode 100644 index 26b083840..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/close_wsh.py +++ /dev/null @@ -1,69 +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. - -import struct - -from mod_pywebsocket import common -from mod_pywebsocket import stream - - -def web_socket_do_extra_handshake(request): - pass - - -def web_socket_transfer_data(request): - while True: - line = request.ws_stream.receive_message() - if line is None: - return - code, reason = line.split(' ', 1) - if code is None or reason is None: - return - request.ws_stream.close_connection(int(code), reason) - # close_connection() initiates closing handshake. It validates code - # and reason. If you want to send a broken close frame for a test, - # following code will be useful. - # > data = struct.pack('!H', int(code)) + reason.encode('UTF-8') - # > request.connection.write(stream.create_close_frame(data)) - # > # Suppress to re-respond client responding close frame. - # > raise Exception("customized server initiated closing handshake") - - -def web_socket_passive_closing_handshake(request): - # Simply echo a close status code - code, reason = request.ws_close_code, request.ws_close_reason - - # pywebsocket sets pseudo code for receiving an empty body close frame. - if code == common.STATUS_NO_STATUS_RECEIVED: - code = None - reason = '' - return code, reason - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/console.html b/testing/web-platform/tests/tools/pywebsocket/src/example/console.html deleted file mode 100644 index ccd6d8f80..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/console.html +++ /dev/null @@ -1,317 +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. ---> - -<!-- -A simple console for testing WebSocket server. - -Type an address into the top text input and click connect to establish -WebSocket. Then, type some message into the bottom text input and click send -to send the message. Received/sent messages and connection state will be shown -on the middle textarea. ---> - -<html> -<head> -<title>WebSocket console</title> -<script> -var socket = null; - -var showTimeStamp = false; - -var addressBox = null; -var protocolsBox = null; -var logBox = null; -var messageBox = null; -var fileBox = null; -var codeBox = null; -var reasonBox = null; - -function getTimeStamp() { - return new Date().getTime(); -} - -function addToLog(log) { - if (showTimeStamp) { - logBox.value += '[' + getTimeStamp() + '] '; - } - logBox.value += log + '\n' - // Large enough to keep showing the latest message. - logBox.scrollTop = 1000000; -} - -function setbinarytype(binaryType) { - if (!socket) { - addToLog('Not connected'); - return; - } - - socket.binaryType = binaryType; - addToLog('Set binaryType to ' + binaryType); -} - -function send() { - if (!socket) { - addToLog('Not connected'); - return; - } - - socket.send(messageBox.value); - addToLog('> ' + messageBox.value); - messageBox.value = ''; -} - -function sendfile() { - if (!socket) { - addToLog('Not connected'); - return; - } - - var files = fileBox.files; - - if (files.length == 0) { - addToLog('File not selected'); - return; - } - - socket.send(files[0]); - addToLog('> Send ' + files[0].name); -} - -function parseProtocols(protocolsText) { - var protocols = protocolsText.split(','); - for (var i = 0; i < protocols.length; ++i) { - protocols[i] = protocols[i].trim(); - } - - if (protocols.length == 0) { - // Don't pass. - protocols = null; - } else if (protocols.length == 1) { - if (protocols[0].length == 0) { - // Don't pass. - protocols = null; - } else { - // Pass as a string. - protocols = protocols[0]; - } - } - - return protocols; -} - -function connect() { - var url = addressBox.value; - var protocols = parseProtocols(protocolsBox.value); - - if ('WebSocket' in window) { - if (protocols) { - socket = new WebSocket(url, protocols); - } else { - socket = new WebSocket(url); - } - } else { - return; - } - - socket.onopen = function () { - var extraInfo = []; - if (('protocol' in socket) && socket.protocol) { - extraInfo.push('protocol = ' + socket.protocol); - } - if (('extensions' in socket) && socket.extensions) { - extraInfo.push('extensions = ' + socket.extensions); - } - - var logMessage = 'Opened'; - if (extraInfo.length > 0) { - logMessage += ' (' + extraInfo.join(', ') + ')'; - } - addToLog(logMessage); - }; - socket.onmessage = function (event) { - if (('ArrayBuffer' in window) && (event.data instanceof ArrayBuffer)) { - addToLog('< Received an ArrayBuffer of ' + event.data.byteLength + - ' bytes') - } else if (('Blob' in window) && (event.data instanceof Blob)) { - addToLog('< Received a Blob of ' + event.data.size + ' bytes') - } else { - addToLog('< ' + event.data); - } - }; - socket.onerror = function () { - addToLog('Error'); - }; - socket.onclose = function (event) { - var logMessage = 'Closed ('; - if ((arguments.length == 1) && ('CloseEvent' in window) && - (event instanceof CloseEvent)) { - logMessage += 'wasClean = ' + event.wasClean; - // code and reason are present only for - // draft-ietf-hybi-thewebsocketprotocol-06 and later - if ('code' in event) { - logMessage += ', code = ' + event.code; - } - if ('reason' in event) { - logMessage += ', reason = ' + event.reason; - } - } else { - logMessage += 'CloseEvent is not available'; - } - addToLog(logMessage + ')'); - }; - - if (protocols) { - addToLog('Connect ' + url + ' (protocols = ' + protocols + ')'); - } else { - addToLog('Connect ' + url); - } -} - -function closeSocket() { - if (!socket) { - addToLog('Not connected'); - return; - } - - if (codeBox.value || reasonBox.value) { - socket.close(codeBox.value, reasonBox.value); - } else { - socket.close(); - } -} - -function printState() { - if (!socket) { - addToLog('Not connected'); - return; - } - - addToLog( - 'url = ' + socket.url + - ', readyState = ' + socket.readyState + - ', bufferedAmount = ' + socket.bufferedAmount); -} - -function init() { - var scheme = window.location.protocol == 'https:' ? 'wss://' : 'ws://'; - var defaultAddress = scheme + window.location.host + '/echo'; - - addressBox = document.getElementById('address'); - protocolsBox = document.getElementById('protocols'); - logBox = document.getElementById('log'); - messageBox = document.getElementById('message'); - fileBox = document.getElementById('file'); - codeBox = document.getElementById('code'); - reasonBox = document.getElementById('reason'); - - addressBox.value = defaultAddress; - - if (!('WebSocket' in window)) { - addToLog('WebSocket is not available'); - } -} -</script> -<style type="text/css"> -form { - margin: 0px; -} - -#connect_div, #log_div, #send_div, #sendfile_div, #close_div, #printstate_div { - padding: 5px; - margin: 5px; - border-width: 0px 0px 0px 10px; - border-style: solid; - border-color: silver; -} -</style> -</head> -<body onload="init()"> - -<div> - -<div id="connect_div"> - <form action="#" onsubmit="connect(); return false;"> - url <input type="text" id="address" size="40"> - <input type="submit" value="connect"> - <br/> - protocols <input type="text" id="protocols" size="20"> - </form> -</div> - -<div id="log_div"> - <textarea id="log" rows="10" cols="40" readonly></textarea> - <br/> - <input type="checkbox" - name="showtimestamp" - value="showtimestamp" - onclick="showTimeStamp = this.checked">Show time stamp -</div> - -<div id="send_div"> - <form action="#" onsubmit="send(); return false;"> - data <input type="text" id="message" size="40"> - <input type="submit" value="send"> - </form> -</div> - -<div id="sendfile_div"> - <form action="#" onsubmit="sendfile(); return false;"> - <input type="file" id="file" size="40"> - <input type="submit" value="send file"> - </form> - - Set binaryType - <input type="radio" - name="binarytype" - value="blob" - onclick="setbinarytype('blob')" checked>blob - <input type="radio" - name="binarytype" - value="arraybuffer" - onclick="setbinarytype('arraybuffer')">arraybuffer -</div> - -<div id="close_div"> - <form action="#" onsubmit="closeSocket(); return false;"> - code <input type="text" id="code" size="10"> - reason <input type="text" id="reason" size="20"> - <input type="submit" value="close"> - </form> -</div> - -<div id="printstate_div"> - <input type="button" value="print state" onclick="printState();"> -</div> - -</div> - -</body> -</html> diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/cookie_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/example/cookie_wsh.py deleted file mode 100644 index 8b327152e..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/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/testing/web-platform/tests/tools/pywebsocket/src/example/echo_client.py b/testing/web-platform/tests/tools/pywebsocket/src/example/echo_client.py deleted file mode 100755 index 943ce64e8..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/echo_client.py +++ /dev/null @@ -1,1128 +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 -""" - - -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, e: - 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<n> 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, 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/testing/web-platform/tests/tools/pywebsocket/src/example/echo_noext_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/example/echo_noext_wsh.py deleted file mode 100644 index 1df515122..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/echo_noext_wsh.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2013, 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. - - -_GOODBYE_MESSAGE = u'Goodbye' - - -def web_socket_do_extra_handshake(request): - """Received Sec-WebSocket-Extensions header value is parsed into - request.ws_requested_extensions. pywebsocket creates extension - processors using it before do_extra_handshake call and never looks at it - after the call. - - To reject requested extensions, clear the processor list. - """ - - request.ws_extension_processors = [] - - -def web_socket_transfer_data(request): - """Echo. Same as echo_wsh.py.""" - - while True: - line = request.ws_stream.receive_message() - if line is None: - return - if isinstance(line, unicode): - request.ws_stream.send_message(line, binary=False) - if line == _GOODBYE_MESSAGE: - return - else: - request.ws_stream.send_message(line, binary=True) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/echo_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/example/echo_wsh.py deleted file mode 100644 index 38646c32c..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/echo_wsh.py +++ /dev/null @@ -1,54 +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. - - -_GOODBYE_MESSAGE = u'Goodbye' - - -def web_socket_do_extra_handshake(request): - # This example handler accepts any request. See origin_check_wsh.py for how - # to reject access from untrusted scripts based on origin value. - - pass # Always accept. - - -def web_socket_transfer_data(request): - while True: - line = request.ws_stream.receive_message() - if line is None: - return - if isinstance(line, unicode): - request.ws_stream.send_message(line, binary=False) - if line == _GOODBYE_MESSAGE: - return - else: - request.ws_stream.send_message(line, binary=True) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/eventsource.cgi b/testing/web-platform/tests/tools/pywebsocket/src/example/eventsource.cgi deleted file mode 100755 index adddf237c..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/eventsource.cgi +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/python - -# Copyright 2013, 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 CGI script generates text/event-stream type data stream for testing -the Server-Sent Events. - -It will only work correctly with HTTP servers that do not buffer the output of -CGI scripts. -""" - - -import sys -import time - -sys.stdout.write('Content-type: text/event-stream\r\n\r\n') - -id = 0 - -while True: - sys.stdout.write('data: Hello\r\nid: %d\r\n\r\n' % id) - sys.stdout.flush() - - id = id + 1 - - time.sleep(1) diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/eventsource.html b/testing/web-platform/tests/tools/pywebsocket/src/example/eventsource.html deleted file mode 100644 index 1598a8807..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/eventsource.html +++ /dev/null @@ -1,74 +0,0 @@ -<!-- -Copyright 2013, 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 example of the Server-Sent Events -http://dev.w3.org/html5/eventsource/ - -For comparison with the WebSocket Protocol & API. - -Run the pywebsocket with the --cgi_path parameter. ---> - -<html> -<head> -<title>Server-Sent Events Example</title> -<script> -var eventSource = null; - -function addToLog(data) { - logBox.value += data + '\n'; - logBox.scrollTop = 1000000; -} - -function init() { - logBox = document.getElementById('log'); - - eventSource = new EventSource('/eventsource.cgi'); - eventSource.onopen = function() { - addToLog('onopen (readyState = ' + eventSource.readyState + ')'); - } - eventSource.onmessage = function(event) { - addToLog(event.data); - } - eventSource.onerror = function(event) { - addToLog('onerror (readyState = ' + eventSource.readyState + ')'); - } -} -</script> -</head> -<body onload="init()"> -<textarea id="log" rows="10" cols="40" readonly></textarea> -<p style="font-size: small"> - Make sure that pywebsocket is run with --cgi_path parameter. -</p> -</body> -</html> diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/handler_map.txt b/testing/web-platform/tests/tools/pywebsocket/src/example/handler_map.txt deleted file mode 100644 index 21c4c09aa..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/handler_map.txt +++ /dev/null @@ -1,11 +0,0 @@ -# websocket handler map file, used by standalone.py -m option. -# A line starting with '#' is a comment line. -# Each line consists of 'alias_resource_path' and 'existing_resource_path' -# separated by spaces. -# Aliasing is processed from the top to the bottom of the line, and -# 'existing_resource_path' must exist before it is aliased. -# For example, -# / /echo -# means that a request to '/' will be handled by handlers for '/echo'. -/ /echo - diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/hsts_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/example/hsts_wsh.py deleted file mode 100644 index e86194692..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/hsts_wsh.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2013, 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. - - -def web_socket_do_extra_handshake(request): - request.extra_headers.append( - ('Strict-Transport-Security', 'max-age=86400')) - - -def web_socket_transfer_data(request): - request.ws_stream.send_message('Hello', binary=False) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/internal_error_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/example/internal_error_wsh.py deleted file mode 100644 index fe581b54a..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/internal_error_wsh.py +++ /dev/null @@ -1,42 +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. - - -from mod_pywebsocket import msgutil - - -def web_socket_do_extra_handshake(request): - pass - - -def web_socket_transfer_data(request): - raise msgutil.BadOperationException('Intentional') - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/origin_check_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/example/origin_check_wsh.py deleted file mode 100644 index e05767ab9..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/origin_check_wsh.py +++ /dev/null @@ -1,44 +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 example is derived from test/testdata/handlers/origin_check_wsh.py. - - -def web_socket_do_extra_handshake(request): - if request.ws_origin == 'http://example.com': - return - raise ValueError('Unacceptable origin: %r' % request.ws_origin) - - -def web_socket_transfer_data(request): - request.connection.write('origin_check_wsh.py is called for %s, %s' % - (request.ws_resource, request.ws_protocol)) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/pywebsocket.conf b/testing/web-platform/tests/tools/pywebsocket/src/example/pywebsocket.conf deleted file mode 100644 index 335d130a5..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/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 -<IfModule python_module> - 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 - <Location /ws> - PythonHeaderParserHandler mod_pywebsocket.headerparserhandler - </Location> -</IfModule> diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/special_headers.cgi b/testing/web-platform/tests/tools/pywebsocket/src/example/special_headers.cgi deleted file mode 100755 index ea5080f1f..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/special_headers.cgi +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/python - -# 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 - -"""CGI script sample for testing effect of HTTP headers on the origin page. - -Note that CGI scripts don't work on the standalone pywebsocket running in TLS -mode. -""" - - -print """Content-type: text/html -Content-Security-Policy: connect-src self - -<html> -<head> -<title></title> -</head> -<body> -<script> -var socket = new WebSocket("ws://example.com"); -</script> -</body> -</html>""" diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/util.js b/testing/web-platform/tests/tools/pywebsocket/src/example/util.js deleted file mode 100644 index a1cad4975..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/util.js +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2013, 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. - - -// Utilities for example applications (for both main and worker thread). - -var results = {}; - -function getTimeStamp() { - return Date.now(); -} - -function formatResultInKiB(size, timePerMessageInMs, stddevTimePerMessageInMs, - speed, printSize) { - if (printSize) { - return (size / 1024) + - '\t' + timePerMessageInMs.toFixed(3) + - (stddevTimePerMessageInMs == -1 ? - '' : - '\t' + stddevTimePerMessageInMs.toFixed(3)) + - '\t' + speed.toFixed(3); - } else { - return speed.toString(); - } -} - -function clearAverageData() { - results = {}; -} - -function reportAverageData(config) { - config.addToSummary( - 'Size[KiB]\tAverage time[ms]\tStddev time[ms]\tSpeed[KB/s]'); - for (var size in results) { - var averageTimePerMessageInMs = results[size].sum_t / results[size].n; - var speed = calculateSpeedInKB(size, averageTimePerMessageInMs); - // Calculate sample standard deviation - var stddevTimePerMessageInMs = Math.sqrt( - (results[size].sum_t2 / results[size].n - - averageTimePerMessageInMs * averageTimePerMessageInMs) * - results[size].n / - (results[size].n - 1)); - config.addToSummary(formatResultInKiB( - size, averageTimePerMessageInMs, stddevTimePerMessageInMs, speed, - true)); - } -} - -function calculateSpeedInKB(size, timeSpentInMs) { - return Math.round(size / timeSpentInMs * 1000) / 1000; -} - -function calculateAndLogResult(config, size, startTimeInMs, totalSize) { - var timeSpentInMs = getTimeStamp() - startTimeInMs; - var speed = calculateSpeedInKB(totalSize, timeSpentInMs); - var timePerMessageInMs = timeSpentInMs / (totalSize / size); - if (!results[size]) { - results[size] = {n: 0, sum_t: 0, sum_t2: 0}; - } - config.measureValue(timePerMessageInMs); - results[size].n ++; - results[size].sum_t += timePerMessageInMs; - results[size].sum_t2 += timePerMessageInMs * timePerMessageInMs; - config.addToLog(formatResultInKiB(size, timePerMessageInMs, -1, speed, - config.printSize)); -} - -function fillArrayBuffer(buffer, c) { - var i; - - var u32Content = c * 0x01010101; - - var u32Blocks = Math.floor(buffer.byteLength / 4); - var u32View = new Uint32Array(buffer, 0, u32Blocks); - // length attribute is slow on Chrome. Don't use it for loop condition. - for (i = 0; i < u32Blocks; ++i) { - u32View[i] = u32Content; - } - - // Fraction - var u8Blocks = buffer.byteLength - u32Blocks * 4; - var u8View = new Uint8Array(buffer, u32Blocks * 4, u8Blocks); - for (i = 0; i < u8Blocks; ++i) { - u8View[i] = c; - } -} - -function verifyArrayBuffer(buffer, expectedChar) { - var i; - - var expectedU32Value = expectedChar * 0x01010101; - - var u32Blocks = Math.floor(buffer.byteLength / 4); - var u32View = new Uint32Array(buffer, 0, u32Blocks); - for (i = 0; i < u32Blocks; ++i) { - if (u32View[i] != expectedU32Value) { - return false; - } - } - - var u8Blocks = buffer.byteLength - u32Blocks * 4; - var u8View = new Uint8Array(buffer, u32Blocks * 4, u8Blocks); - for (i = 0; i < u8Blocks; ++i) { - if (u8View[i] != expectedChar) { - return false; - } - } - - return true; -} - -function verifyBlob(config, blob, expectedChar, doneCallback) { - var reader = new FileReader(blob); - reader.onerror = function() { - config.addToLog('FileReader Error: ' + reader.error.message); - doneCallback(blob.size, false); - } - reader.onloadend = function() { - var result = verifyArrayBuffer(reader.result, expectedChar); - doneCallback(blob.size, result); - } - reader.readAsArrayBuffer(blob); -} - -function verifyAcknowledgement(config, message, size) { - if (typeof message != 'string') { - config.addToLog('Invalid ack type: ' + typeof message); - return false; - } - var parsedAck = parseInt(message); - if (isNaN(parsedAck)) { - config.addToLog('Invalid ack value: ' + message); - return false; - } - if (parsedAck != size) { - config.addToLog( - 'Expected ack for ' + size + 'B but received one for ' + parsedAck + - 'B'); - return false; - } - - return true; -} - -function cloneConfig(obj) { - var newObj = {}; - for (key in obj) { - newObj[key] = obj[key]; - } - return newObj; -} diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/util_main.js b/testing/web-platform/tests/tools/pywebsocket/src/example/util_main.js deleted file mode 100644 index b03d1c2bd..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/util_main.js +++ /dev/null @@ -1,63 +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) { -} - -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 onMessage(message) { - if (message.data.type === 'addToLog') - addToLog(message.data.data); - else if (message.data.type === 'addToSummary') - addToSummary(message.data.data); - else if (message.data.type === 'measureValue') - measureValue(message.data.data); -} diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/util_worker.js b/testing/web-platform/tests/tools/pywebsocket/src/example/util_worker.js deleted file mode 100644 index b64f7829d..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/util_worker.js +++ /dev/null @@ -1,19 +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). - -function workerAddToLog(text) { - postMessage({type: 'addToLog', data: text}); -} - -function workerAddToSummary(text) { - postMessage({type: 'addToSummary', data: text}); -} - -function workerMeasureValue(value) { - postMessage({type: 'measureValue', data: value}); -} diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/xhr_benchmark.html b/testing/web-platform/tests/tools/pywebsocket/src/example/xhr_benchmark.html deleted file mode 100644 index 186229775..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/xhr_benchmark.html +++ /dev/null @@ -1,222 +0,0 @@ -<!-- -Copyright 2013, 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. ---> - -<html> -<head> -<title>XMLHttpRequest benchmark</title> -<script src="util_main.js"></script> -<script src="util.js"></script> -<script src="xhr_benchmark.js"></script> -<script> -var addressBox = null; - -function getConfig() { - return { - prefixUrl: addressBox.value, - printSize: getBoolFromCheckBox('printsize'), - numXHRs: getIntFromInput('numXHRs'), - async: getBoolFromCheckBox('async'), - // Initial size of messages. - numIterations: getIntFromInput('numiterations'), - numWarmUpIterations: getIntFromInput('numwarmupiterations'), - startSize: getIntFromInput('startsize'), - // Stops benchmark when the size of message exceeds this threshold. - stopThreshold: getIntFromInput('stopthreshold'), - // If the size of each message is small, send/receive multiple messages - // until the sum of sizes reaches this threshold. - // minTotal: getIntFromInput('mintotal'), - // minTotal is not yet implemented on XHR benchmark - multipliers: getIntArrayFromInput('multipliers'), - verifyData: getBoolFromCheckBox('verifydata') - }; -} - -var worker = new Worker('xhr_benchmark.js'); -worker.onmessage = onMessage; - -function onSendBenchmark() { - var config = getConfig(); - config.dataType = getStringFromRadioBox('datatyperadio'); - - if (getBoolFromCheckBox('worker')) { - worker.postMessage({type: 'sendBenchmark', config: config}); - } else { - config.addToLog = addToLog; - config.addToSummary = addToSummary; - config.measureValue = measureValue; - sendBenchmark(config); - } -} - -function onReceiveBenchmark() { - var config = getConfig(); - config.dataType = getStringFromRadioBox('datatyperadio'); - - if (getBoolFromCheckBox('worker')) { - worker.postMessage({type: 'receiveBenchmark', config: config}); - } else { - config.addToLog = addToLog; - config.addToSummary = addToSummary; - config.measureValue = measureValue; - receiveBenchmark(config); - } -} - -function onBatchBenchmark() { - var config = getConfig(); - - if (getBoolFromCheckBox('worker')) { - worker.postMessage({type: 'batchBenchmark', config: config}); - } else { - config.addToLog = addToLog; - config.addToSummary = addToSummary; - config.measureValue = measureValue; - batchBenchmark(config); - } -} - -function onStop() { - var config = getConfig(); - - if (getBoolFromCheckBox('worker')) { - worker.postMessage({type: 'stop', config: config}); - } else { - config.addToLog = addToLog; - config.addToSummary = addToSummary; - config.measureValue = measureValue; - stop(config); - } -} - -function init() { - addressBox = document.getElementById('address'); - logBox = document.getElementById('log'); - - summaryBox = document.getElementById('summary'); - - // Special address of pywebsocket for XHR benchmark. - addressBox.value = '/073be001e10950692ccbf3a2ad21c245'; - - addToLog(window.navigator.userAgent.toLowerCase()); - addToSummary(window.navigator.userAgent.toLowerCase()); -} -</script> -</head> -<body onload="init()"> - -<form id="benchmark_form"> - url prefix <input type="text" id="address" size="40"> - <input type="button" value="send" onclick="onSendBenchmark()"> - <input type="button" value="receive" onclick="onReceiveBenchmark()"> - <input type="button" value="batch" onclick="onBatchBenchmark()"> - <input type="button" value="stop" onclick="onStop()"> - - <br/> - - <input type="checkbox" id="printsize" checked> - <label for="printsize">Print size and time per message</label> - <input type="checkbox" id="verifydata" checked> - <label for="verifydata">Verify data</label> - <input type="checkbox" id="worker"> - <label for="worker">Run on worker</label> - <input type="checkbox" id="async" checked> - <label for="async">Async</label><br> - (Receive && Non-Worker && Sync is not supported by spec) - - <br/> - - Parameters: - - <br/> - - <table> - <tr> - <td>Num XHRs</td> - <td><input type="text" id="numXHRs" value="1"></td> - </tr> - <tr> - <td>Number of iterations</td> - <td><input type="text" id="numiterations" value="1"></td> - </tr> - <tr> - <td>Number of warm-up iterations</td> - <td><input type="text" id="numwarmupiterations" value="0"></td> - </tr> - <tr> - <td>Start size</td> - <td><input type="text" id="startsize" value="10240"></td> - </tr> - <tr> - <td>Stop threshold</td> - <td><input type="text" id="stopthreshold" value="102400000"></td> - </tr> - <tr> - <td>Minimum total</td> - <td><input type="text" id="mintotal" value="102400000"></td> - </tr> - <tr> - <td>Multipliers</td> - <td><input type="text" id="multipliers" value="5, 2"></td> - </tr> - </table> - - Set data type - <input type="radio" - name="datatyperadio" - id="datatyperadiotext" - value="text" - checked><label for="datatyperadiotext">text</label> - <input type="radio" - name="datatyperadio" - id="datatyperadioblob" - value="blob" - ><label for="datatyperadioblob">blob</label> - <input type="radio" - name="datatyperadio" - id="datatyperadioarraybuffer" - value="arraybuffer" - ><label for="datatyperadioarraybuffer">arraybuffer</label> -</form> - -<div id="log_div"> - <textarea - id="log" rows="20" style="width: 100%" readonly></textarea> -</div> -<div id="summary_div"> - Summary - <textarea - id="summary" rows="20" style="width: 100%" readonly></textarea> -</div> - -Note: Effect of RTT and time spent for ArrayBuffer creation in receive benchmarks are not eliminated. - -</body> -</html> diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/xhr_benchmark.js b/testing/web-platform/tests/tools/pywebsocket/src/example/xhr_benchmark.js deleted file mode 100644 index 233c7cb38..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/xhr_benchmark.js +++ /dev/null @@ -1,389 +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 repeatString(str, count) { - var data = ''; - var expChunk = str; - var remain = count; - while (true) { - if (remain % 2) { - data += expChunk; - remain = (remain - 1) / 2; - } else { - remain /= 2; - } - - if (remain == 0) - break; - - expChunk = expChunk + expChunk; - } - return data; -} - -function sendBenchmarkStep(size, config) { - 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(); - return; - } - - if (config.verifyData && - !verifyAcknowledgement(config, this.response, size)) { - destroyAllXHRs(); - return; - } - - totalReplied += size; - - if (totalReplied < totalSize) { - return; - } - - if (benchmark.startTimeInMs == null) { - config.addToLog('startTimeInMs not set'); - destroyAllXHRs(); - return; - } - - calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize); - - 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) { - timerID = null; - - benchmark.startTimeInMs = null; - var totalSize = 0; - var totalReplied = 0; - - var checkResultAndContinue = function (bytesReceived, verificationResult) { - if (!verificationResult) { - config.addToLog('Response verification failed'); - destroyAllXHRs(); - return; - } - - totalReplied += bytesReceived; - - if (totalReplied < totalSize) { - return; - } - - if (benchmark.startTimeInMs == null) { - config.addToLog('startTimeInMs not set'); - destroyAllXHRs(); - return; - } - - calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize); - - destroyAllXHRs(); - - runNextTask(config); - } - - var onReadyStateChangeHandler = function () { - if (this.readyState != this.DONE) { - return; - } - - if (this.status != 200) { - config.addToLog('Failed (status=' + this.status + ')'); - destroyAllXHRs(); - return; - } - - 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(); - 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]; - 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); -} - -// TODO(hiroshige): the following code is the same as benchmark.html -// and some of them should be merged into e.g. util.js - -var tasks = []; - -function runNextTask(config) { - var task = tasks.shift(); - if (task == undefined) { - config.addToLog('Finished'); - destroyAllXHRs(); - return; - } - timerID = setTimeout(task, 0); -} - -function buildLegendString(config) { - var legend = '' - if (config.printSize) - legend = 'Message size in KiB, Time/message in ms, '; - legend += 'Speed in kB/s'; - return legend; -} - -function addTasks(config, stepFunc) { - for (var i = 0; - i < config.numWarmUpIterations + config.numIterations; ++i) { - // Ignore the first |config.numWarmUpIterations| iterations. - if (i == config.numWarmUpIterations) - addResultClearingTask(config); - - var multiplierIndex = 0; - for (var size = config.startSize; - size <= config.stopThreshold; - ++multiplierIndex) { - var task = stepFunc.bind( - null, - size, - config); - tasks.push(task); - size *= config.multipliers[ - multiplierIndex % config.multipliers.length]; - } - } -} - -function addResultReportingTask(config, title) { - tasks.push(function(){ - timerID = null; - config.addToSummary(title); - reportAverageData(config); - clearAverageData(); - runNextTask(config); - }); -} - -function addResultClearingTask(config) { - tasks.push(function(){ - timerID = null; - clearAverageData(); - runNextTask(config); - }); -} - -// -------------------------------- - -function sendBenchmark(config) { - config.addToLog('Send benchmark'); - config.addToLog(buildLegendString(config)); - - tasks = []; - clearAverageData(); - addTasks(config, sendBenchmarkStep); - addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config)); - startBenchmark(config); -} - -function receiveBenchmark(config) { - config.addToLog('Receive benchmark'); - config.addToLog(buildLegendString(config)); - - tasks = []; - clearAverageData(); - addTasks(config, receiveBenchmarkStep); - addResultReportingTask(config, - 'Receive Benchmark ' + getConfigString(config)); - startBenchmark(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 stop(config) { - destroyAllXHRs(); - clearTimeout(timerID); - timerID = null; - config.addToLog('Stopped'); -} - -onmessage = function (message) { - var config = message.data.config; - config.addToLog = workerAddToLog; - config.addToSummary = workerAddToSummary; - config.measureValue = workerMeasureValue; - if (message.data.type === 'sendBenchmark') - sendBenchmark(config); - else if (message.data.type === 'receiveBenchmark') - receiveBenchmark(config); - else if (message.data.type === 'batchBenchmark') - batchBenchmark(config); - else if (message.data.type === 'stop') - stop(config); -}; diff --git a/testing/web-platform/tests/tools/pywebsocket/src/example/xhr_event_logger.html b/testing/web-platform/tests/tools/pywebsocket/src/example/xhr_event_logger.html deleted file mode 100644 index 6983553b8..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/example/xhr_event_logger.html +++ /dev/null @@ -1,110 +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 ---> - -<html> -<head> -<title>XHR event logger</title> -<script src="util_main.js"></script> -<script> -var events = []; - -function run() { - events = []; - - function pushToLog(type) { - if (events.length != 0 && type === events[events.length - 1].type) { - events[events.length - 1].count += 1; - } else { - events.push({type: type, count: 1}); - } - } - - var xhr = new XMLHttpRequest(); - - function getProgressEventDump(e) { - return '(' + e.lengthComputable + ', ' + e.loaded + ', ' + e.total + ')'; - } - - var dumpProgressEvent = getBoolFromCheckBox('dumpprogressevent'); - - function log(e) { - var type = e.type; - if (type === 'readystatechange') { - type += e.target.readyState; - } - if (dumpProgressEvent && (e instanceof ProgressEvent)) { - type += getProgressEventDump(e); - } - pushToLog(type); - }; - - function logUpload(e) { - var type = e.type; - if (dumpProgressEvent && (e instanceof ProgressEvent)) { - type += getProgressEventDump(e); - } - pushToLog('upload' + type); - } - - if (getBoolFromCheckBox('upload')) { - var upload = xhr.upload; - upload.onloadstart = logUpload; - upload.onprogress = logUpload; - upload.onabort = logUpload; - upload.onerror = logUpload; - upload.onload = logUpload; - upload.ontimeout = logUpload; - upload.onloadend = logUpload; - } - - xhr.onreadystatechange = log; - xhr.onloadstart = log; - xhr.onprogress = log; - xhr.onabort = log; - xhr.onerror = log; - xhr.onload = log; - xhr.ontimeout = log; - xhr.onloadend = log; - - xhr.open('POST', '/073be001e10950692ccbf3a2ad21c245_receive', - getBoolFromCheckBox('async')); - var size = getIntFromInput('size'); - var chunkedMode = 'none'; - if (getBoolFromCheckBox('chunkedresponse')) { - chunkedMode = 'chunked'; - } - xhr.send(size + ' ' + chunkedMode); -} - -function print() { - var result = ''; - for (var i = 0; i < events.length; ++i) { - var event = events[i]; - result += event.type + ' * ' + event.count + '\n'; - } - document.getElementById('log').value = result; -} -</script> - -<body> - <textarea id="log" rows="10" cols="40" readonly></textarea> - <br/> - Size: <input type="text" id="size" value="65536"><br/> - <input type="checkbox" id="chunkedresponse"> - <label for="chunkedresponse">Use Chunked T-E for response</label><br/> - <input type="checkbox" id="upload"> - <label for="upload">Upload progress</label><br/> - <input type="checkbox" id="dumpprogressevent"> - <label for="dumpprogressevent"> - Dump lengthComputable/loaded/total</label><br/> - <input type="checkbox" id="async" checked> - <label for="async">Async</label><br/> - <input type="button" onclick="run()" value="Run XHR"> - <input type="button" onclick="print()" value="Print log"> -</body> -</html> diff --git a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/__init__.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/__init__.py deleted file mode 100644 index 70933a220..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/__init__.py +++ /dev/null @@ -1,224 +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. - - -"""WebSocket extension for Apache HTTP Server. - -mod_pywebsocket is a WebSocket extension for Apache HTTP Server -intended for testing or experimental purposes. mod_python is required. - - -Installation -============ - -0. Prepare an Apache HTTP Server for which mod_python is enabled. - -1. Specify the following Apache HTTP Server directives to suit your - configuration. - - If mod_pywebsocket is not in the Python path, specify the following. - <websock_lib> is the directory where mod_pywebsocket is installed. - - PythonPath "sys.path+['<websock_lib>']" - - Always specify the following. <websock_handlers> is the directory where - user-written WebSocket handlers are placed. - - PythonOption mod_pywebsocket.handler_root <websock_handlers> - PythonHeaderParserHandler mod_pywebsocket.headerparserhandler - - To limit the search for WebSocket handlers to a directory <scan_dir> - under <websock_handlers>, configure as follows: - - PythonOption mod_pywebsocket.handler_scan <scan_dir> - - <scan_dir> is useful in saving scan time when <websock_handlers> - contains many non-WebSocket handler files. - - If you want to allow handlers whose canonical path is not under the root - directory (i.e. symbolic link is in root directory but its target is not), - configure as follows: - - PythonOption mod_pywebsocket.allow_handlers_outside_root_dir On - - Example snippet of httpd.conf: - (mod_pywebsocket is in /websock_lib, WebSocket handlers are in - /websock_handlers, port is 80 for ws, 443 for wss.) - - <IfModule python_module> - PythonPath "sys.path+['/websock_lib']" - PythonOption mod_pywebsocket.handler_root /websock_handlers - PythonHeaderParserHandler mod_pywebsocket.headerparserhandler - </IfModule> - -2. Tune Apache parameters for serving WebSocket. We'd like to note that at - least TimeOut directive from core features and RequestReadTimeout - directive from mod_reqtimeout should be modified not to kill connections - in only a few seconds of idle time. - -3. Verify installation. You can use example/console.html to poke the server. - - -Writing WebSocket handlers -========================== - -When a WebSocket request comes in, the resource name -specified in the handshake is considered as if it is a file path under -<websock_handlers> and the handler defined in -<websock_handlers>/<resource_name>_wsh.py is invoked. - -For example, if the resource name is /example/chat, the handler defined in -<websock_handlers>/example/chat_wsh.py is invoked. - -A WebSocket handler is composed of the following three functions: - - web_socket_do_extra_handshake(request) - web_socket_transfer_data(request) - web_socket_passive_closing_handshake(request) - -where: - request: mod_python request. - -web_socket_do_extra_handshake is called during the handshake after the -headers are successfully parsed and WebSocket properties (ws_location, -ws_origin, and ws_resource) are added to request. A handler -can reject the request by raising an exception. - -A request object has the following properties that you can use during the -extra handshake (web_socket_do_extra_handshake): -- ws_resource -- ws_origin -- ws_version -- ws_location (HyBi 00 only) -- ws_extensions (HyBi 06 and later) -- ws_deflate (HyBi 06 and later) -- ws_protocol -- ws_requested_protocols (HyBi 06 and later) - -The last two are a bit tricky. See the next subsection. - - -Subprotocol Negotiation ------------------------ - -For HyBi 06 and later, ws_protocol is always set to None when -web_socket_do_extra_handshake is called. If ws_requested_protocols is not -None, you must choose one subprotocol from this list and set it to -ws_protocol. - -For HyBi 00, when web_socket_do_extra_handshake is called, -ws_protocol is set to the value given by the client in -Sec-WebSocket-Protocol header or None if -such header was not found in the opening handshake request. Finish extra -handshake with ws_protocol untouched to accept the request subprotocol. -Then, Sec-WebSocket-Protocol header will be sent to -the client in response with the same value as requested. Raise an exception -in web_socket_do_extra_handshake to reject the requested subprotocol. - - -Data Transfer -------------- - -web_socket_transfer_data is called after the handshake completed -successfully. A handler can receive/send messages from/to the client -using request. mod_pywebsocket.msgutil module provides utilities -for data transfer. - -You can receive a message by the following statement. - - message = request.ws_stream.receive_message() - -This call blocks until any complete text frame arrives, and the payload data -of the incoming frame will be stored into message. When you're using IETF -HyBi 00 or later protocol, receive_message() will return None on receiving -client-initiated closing handshake. When any error occurs, receive_message() -will raise some exception. - -You can send a message by the following statement. - - request.ws_stream.send_message(message) - - -Closing Connection ------------------- - -Executing the following statement or just return-ing from -web_socket_transfer_data cause connection close. - - request.ws_stream.close_connection() - -close_connection will wait -for closing handshake acknowledgement coming from the client. When it -couldn't receive a valid acknowledgement, raises an exception. - -web_socket_passive_closing_handshake is called after the server receives -incoming closing frame from the client peer immediately. You can specify -code and reason by return values. They are sent as a outgoing closing frame -from the server. A request object has the following properties that you can -use in web_socket_passive_closing_handshake. -- ws_close_code -- ws_close_reason - - -Threading ---------- - -A WebSocket handler must be thread-safe if the server (Apache or -standalone.py) is configured to use threads. - - -Configuring WebSocket Extension Processors ------------------------------------------- - -See extensions.py for supported WebSocket extensions. Note that they are -unstable and their APIs are subject to change substantially. - -A request object has these extension processing related attributes. - -- ws_requested_extensions: - - A list of common.ExtensionParameter instances representing extension - parameters received from the client in the client's opening handshake. - You shouldn't modify it manually. - -- ws_extensions: - - A list of common.ExtensionParameter instances representing extension - parameters to send back to the client in the server's opening handshake. - You shouldn't touch it directly. Instead, call methods on extension - processors. - -- ws_extension_processors: - - A list of loaded extension processors. Find the processor for the - extension you want to configure from it, and call its methods. -""" - - -# vi:sts=4 sw=4 et tw=72 diff --git a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/_stream_base.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/_stream_base.py deleted file mode 100644 index 8235666bb..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/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, 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, 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, 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/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/_stream_hixie75.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/_stream_hixie75.py deleted file mode 100644 index 94cf5b31b..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/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/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/_stream_hybi.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/_stream_hybi.py deleted file mode 100644 index a8a49e3c3..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/_stream_hybi.py +++ /dev/null @@ -1,887 +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 parsing/building frames -of the WebSocket protocol (RFC 6455). - -Specification: -http://tools.ietf.org/html/rfc6455 -""" - - -from collections import deque -import logging -import os -import struct -import time - -from mod_pywebsocket import common -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_base import InvalidUTF8Exception -from mod_pywebsocket._stream_base import StreamBase -from mod_pywebsocket._stream_base import UnsupportedFrameException - - -_NOOP_MASKER = util.NoopMasker() - - -class Frame(object): - - def __init__(self, fin=1, rsv1=0, rsv2=0, rsv3=0, - opcode=None, payload=''): - self.fin = fin - self.rsv1 = rsv1 - self.rsv2 = rsv2 - self.rsv3 = rsv3 - self.opcode = opcode - self.payload = payload - - -# Helper functions made public to be used for writing unittests for WebSocket -# clients. - - -def create_length_header(length, mask): - """Creates a length header. - - Args: - length: Frame length. Must be less than 2^63. - mask: Mask bit. Must be boolean. - - Raises: - ValueError: when bad data is given. - """ - - if mask: - mask_bit = 1 << 7 - else: - mask_bit = 0 - - if length < 0: - raise ValueError('length must be non negative integer') - elif length <= 125: - return chr(mask_bit | length) - elif length < (1 << 16): - return chr(mask_bit | 126) + struct.pack('!H', length) - elif length < (1 << 63): - return chr(mask_bit | 127) + struct.pack('!Q', length) - else: - raise ValueError('Payload is too big for one frame') - - -def create_header(opcode, payload_length, fin, rsv1, rsv2, rsv3, mask): - """Creates a frame header. - - Raises: - Exception: when bad data is given. - """ - - if opcode < 0 or 0xf < opcode: - raise ValueError('Opcode out of range') - - if payload_length < 0 or (1 << 63) <= payload_length: - raise ValueError('payload_length out of range') - - if (fin | rsv1 | rsv2 | rsv3) & ~1: - raise ValueError('FIN bit and Reserved bit parameter must be 0 or 1') - - header = '' - - first_byte = ((fin << 7) - | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4) - | opcode) - header += chr(first_byte) - header += create_length_header(payload_length, mask) - - return header - - -def _build_frame(header, body, mask): - if not mask: - return header + body - - masking_nonce = os.urandom(4) - masker = util.RepeatedXorMasker(masking_nonce) - - return header + masking_nonce + masker.mask(body) - - -def _filter_and_format_frame_object(frame, mask, frame_filters): - for frame_filter in frame_filters: - frame_filter.filter(frame) - - header = create_header( - frame.opcode, len(frame.payload), frame.fin, - frame.rsv1, frame.rsv2, frame.rsv3, mask) - return _build_frame(header, frame.payload, mask) - - -def create_binary_frame( - message, opcode=common.OPCODE_BINARY, fin=1, mask=False, frame_filters=[]): - """Creates a simple binary frame with no extension, reserved bit.""" - - frame = Frame(fin=fin, opcode=opcode, payload=message) - return _filter_and_format_frame_object(frame, mask, frame_filters) - - -def create_text_frame( - message, opcode=common.OPCODE_TEXT, fin=1, mask=False, frame_filters=[]): - """Creates a simple text frame with no extension, reserved bit.""" - - encoded_message = message.encode('utf-8') - return create_binary_frame(encoded_message, opcode, fin, mask, - frame_filters) - - -def parse_frame(receive_bytes, logger=None, - ws_version=common.VERSION_HYBI_LATEST, - unmask_receive=True): - """Parses a frame. Returns a tuple containing each header field and - payload. - - Args: - receive_bytes: a function that reads frame data from a stream or - something similar. The function takes length of the bytes to be - read. The function must raise ConnectionTerminatedException if - there is not enough data to be read. - logger: a logging object. - ws_version: the version of WebSocket protocol. - unmask_receive: unmask received frames. When received unmasked - frame, raises InvalidFrameException. - - Raises: - ConnectionTerminatedException: when receive_bytes raises it. - InvalidFrameException: when the frame contains invalid data. - """ - - if not logger: - logger = logging.getLogger() - - logger.log(common.LOGLEVEL_FINE, 'Receive the first 2 octets of a frame') - - received = receive_bytes(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 - - logger.log(common.LOGLEVEL_FINE, - 'FIN=%s, RSV1=%s, RSV2=%s, RSV3=%s, opcode=%s, ' - 'Mask=%s, Payload_length=%s', - fin, rsv1, rsv2, rsv3, opcode, mask, payload_length) - - if (mask == 1) != unmask_receive: - raise InvalidFrameException( - 'Mask bit on the received frame did\'nt match masking ' - 'configuration for received frames') - - # The HyBi and later specs disallow putting a value in 0x0-0xFFFF - # into the 8-octet extended payload length field (or 0x0-0xFD in - # 2-octet field). - valid_length_encoding = True - length_encoding_bytes = 1 - if payload_length == 127: - logger.log(common.LOGLEVEL_FINE, - 'Receive 8-octet extended payload length') - - extended_payload_length = receive_bytes(8) - payload_length = struct.unpack( - '!Q', extended_payload_length)[0] - if payload_length > 0x7FFFFFFFFFFFFFFF: - raise InvalidFrameException( - 'Extended payload length >= 2^63') - if ws_version >= 13 and payload_length < 0x10000: - valid_length_encoding = False - length_encoding_bytes = 8 - - logger.log(common.LOGLEVEL_FINE, - 'Decoded_payload_length=%s', payload_length) - elif payload_length == 126: - logger.log(common.LOGLEVEL_FINE, - 'Receive 2-octet extended payload length') - - extended_payload_length = receive_bytes(2) - payload_length = struct.unpack( - '!H', extended_payload_length)[0] - if ws_version >= 13 and payload_length < 126: - valid_length_encoding = False - length_encoding_bytes = 2 - - logger.log(common.LOGLEVEL_FINE, - 'Decoded_payload_length=%s', payload_length) - - if not valid_length_encoding: - logger.warning( - 'Payload length is not encoded using the minimal number of ' - 'bytes (%d is encoded using %d bytes)', - payload_length, - length_encoding_bytes) - - if mask == 1: - logger.log(common.LOGLEVEL_FINE, 'Receive mask') - - masking_nonce = receive_bytes(4) - masker = util.RepeatedXorMasker(masking_nonce) - - logger.log(common.LOGLEVEL_FINE, 'Mask=%r', masking_nonce) - else: - masker = _NOOP_MASKER - - logger.log(common.LOGLEVEL_FINE, 'Receive payload data') - if logger.isEnabledFor(common.LOGLEVEL_FINE): - receive_start = time.time() - - raw_payload_bytes = receive_bytes(payload_length) - - if logger.isEnabledFor(common.LOGLEVEL_FINE): - logger.log( - common.LOGLEVEL_FINE, - 'Done receiving payload data at %s MB/s', - payload_length / (time.time() - receive_start) / 1000 / 1000) - logger.log(common.LOGLEVEL_FINE, 'Unmask payload data') - - if logger.isEnabledFor(common.LOGLEVEL_FINE): - unmask_start = time.time() - - unmasked_bytes = masker.mask(raw_payload_bytes) - - if logger.isEnabledFor(common.LOGLEVEL_FINE): - logger.log( - common.LOGLEVEL_FINE, - 'Done unmasking payload data at %s MB/s', - payload_length / (time.time() - unmask_start) / 1000 / 1000) - - return opcode, unmasked_bytes, fin, rsv1, rsv2, rsv3 - - -class FragmentedFrameBuilder(object): - """A stateful class to send a message as fragments.""" - - def __init__(self, mask, frame_filters=[], encode_utf8=True): - """Constructs an instance.""" - - self._mask = mask - self._frame_filters = frame_filters - # This is for skipping UTF-8 encoding when building text type frames - # from compressed data. - self._encode_utf8 = encode_utf8 - - self._started = False - - # Hold opcode of the first frame in messages to verify types of other - # frames in the message are all the same. - self._opcode = common.OPCODE_TEXT - - def build(self, payload_data, end, binary): - if binary: - frame_type = common.OPCODE_BINARY - else: - frame_type = common.OPCODE_TEXT - if self._started: - if self._opcode != frame_type: - raise ValueError('Message types are different in frames for ' - 'the same message') - opcode = common.OPCODE_CONTINUATION - else: - opcode = frame_type - self._opcode = frame_type - - if end: - self._started = False - fin = 1 - else: - self._started = True - fin = 0 - - if binary or not self._encode_utf8: - return create_binary_frame( - payload_data, opcode, fin, self._mask, self._frame_filters) - else: - return create_text_frame( - payload_data, opcode, fin, self._mask, self._frame_filters) - - -def _create_control_frame(opcode, body, mask, frame_filters): - frame = Frame(opcode=opcode, payload=body) - - for frame_filter in frame_filters: - frame_filter.filter(frame) - - if len(frame.payload) > 125: - raise BadOperationException( - 'Payload data size of control frames must be 125 bytes or less') - - header = create_header( - frame.opcode, len(frame.payload), frame.fin, - frame.rsv1, frame.rsv2, frame.rsv3, mask) - return _build_frame(header, frame.payload, mask) - - -def create_ping_frame(body, mask=False, frame_filters=[]): - return _create_control_frame(common.OPCODE_PING, body, mask, frame_filters) - - -def create_pong_frame(body, mask=False, frame_filters=[]): - return _create_control_frame(common.OPCODE_PONG, body, mask, frame_filters) - - -def create_close_frame(body, mask=False, frame_filters=[]): - return _create_control_frame( - common.OPCODE_CLOSE, body, mask, frame_filters) - - -def create_closing_handshake_body(code, reason): - body = '' - if code is not None: - if (code > common.STATUS_USER_PRIVATE_MAX or - code < common.STATUS_NORMAL_CLOSURE): - raise BadOperationException('Status code is out of range') - if (code == common.STATUS_NO_STATUS_RECEIVED or - code == common.STATUS_ABNORMAL_CLOSURE or - code == common.STATUS_TLS_HANDSHAKE): - raise BadOperationException('Status code is reserved pseudo ' - 'code') - encoded_reason = reason.encode('utf-8') - body = struct.pack('!H', code) + encoded_reason - return body - - -class StreamOptions(object): - """Holds option values to configure Stream objects.""" - - def __init__(self): - """Constructs StreamOptions.""" - - # Filters applied to frames. - self.outgoing_frame_filters = [] - self.incoming_frame_filters = [] - - # Filters applied to messages. Control frames are not affected by them. - self.outgoing_message_filters = [] - self.incoming_message_filters = [] - - self.encode_text_message_to_utf8 = True - self.mask_send = False - self.unmask_receive = True - - -class Stream(StreamBase): - """A class for parsing/building frames of the WebSocket protocol - (RFC 6455). - """ - - def __init__(self, request, options): - """Constructs an instance. - - Args: - request: mod_python request. - """ - - StreamBase.__init__(self, request) - - self._logger = util.get_class_logger(self) - - self._options = options - - self._request.client_terminated = False - self._request.server_terminated = False - - # Holds body of received fragments. - self._received_fragments = [] - # Holds the opcode of the first fragment. - self._original_opcode = None - - self._writer = FragmentedFrameBuilder( - self._options.mask_send, self._options.outgoing_frame_filters, - self._options.encode_text_message_to_utf8) - - self._ping_queue = deque() - - def _receive_frame(self): - """Receives a frame and return data in the frame as a tuple containing - each header field and payload separately. - - Raises: - ConnectionTerminatedException: when read returns empty - string. - InvalidFrameException: when the frame contains invalid data. - """ - - def _receive_bytes(length): - return self.receive_bytes(length) - - return parse_frame(receive_bytes=_receive_bytes, - logger=self._logger, - ws_version=self._request.ws_version, - unmask_receive=self._options.unmask_receive) - - def _receive_frame_as_frame_object(self): - opcode, unmasked_bytes, fin, rsv1, rsv2, rsv3 = self._receive_frame() - - return Frame(fin=fin, rsv1=rsv1, rsv2=rsv2, rsv3=rsv3, - opcode=opcode, payload=unmasked_bytes) - - def receive_filtered_frame(self): - """Receives a frame and applies frame filters and message filters. - The frame to be received must satisfy following conditions: - - The frame is not fragmented. - - The opcode of the frame is TEXT or BINARY. - - DO NOT USE this method except for testing purpose. - """ - - frame = self._receive_frame_as_frame_object() - if not frame.fin: - raise InvalidFrameException( - 'Segmented frames must not be received via ' - 'receive_filtered_frame()') - if (frame.opcode != common.OPCODE_TEXT and - frame.opcode != common.OPCODE_BINARY): - raise InvalidFrameException( - 'Control frames must not be received via ' - 'receive_filtered_frame()') - - for frame_filter in self._options.incoming_frame_filters: - frame_filter.filter(frame) - for message_filter in self._options.incoming_message_filters: - frame.payload = message_filter.filter(frame.payload) - return frame - - def send_message(self, message, end=True, binary=False): - """Send message. - - Args: - message: text in unicode or binary in str to send. - binary: send message as binary frame. - - Raises: - BadOperationException: when called on a server-terminated - connection or called with inconsistent message type or - binary parameter. - """ - - 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') - - for message_filter in self._options.outgoing_message_filters: - message = message_filter.filter(message, end, binary) - - try: - # Set this to any positive integer to limit maximum size of data in - # payload data of each frame. - MAX_PAYLOAD_DATA_SIZE = -1 - - if MAX_PAYLOAD_DATA_SIZE <= 0: - self._write(self._writer.build(message, end, binary)) - return - - bytes_written = 0 - while True: - end_for_this_frame = end - bytes_to_write = len(message) - bytes_written - if (MAX_PAYLOAD_DATA_SIZE > 0 and - bytes_to_write > MAX_PAYLOAD_DATA_SIZE): - end_for_this_frame = False - bytes_to_write = MAX_PAYLOAD_DATA_SIZE - - frame = self._writer.build( - message[bytes_written:bytes_written + bytes_to_write], - end_for_this_frame, - binary) - self._write(frame) - - bytes_written += bytes_to_write - - # This if must be placed here (the end of while block) so that - # at least one frame is sent. - if len(message) <= bytes_written: - break - except ValueError, e: - raise BadOperationException(e) - - def _get_message_from_frame(self, frame): - """Gets a message from frame. If the message is composed of fragmented - frames and the frame is not the last fragmented frame, this method - returns None. The whole message will be returned when the last - fragmented frame is passed to this method. - - Raises: - InvalidFrameException: when the frame doesn't match defragmentation - context, or the frame contains invalid data. - """ - - if frame.opcode == common.OPCODE_CONTINUATION: - if not self._received_fragments: - if frame.fin: - raise InvalidFrameException( - 'Received a termination frame but fragmentation ' - 'not started') - else: - raise InvalidFrameException( - 'Received an intermediate frame but ' - 'fragmentation not started') - - if frame.fin: - # End of fragmentation frame - self._received_fragments.append(frame.payload) - message = ''.join(self._received_fragments) - self._received_fragments = [] - return message - else: - # Intermediate frame - self._received_fragments.append(frame.payload) - return None - else: - if self._received_fragments: - if frame.fin: - raise InvalidFrameException( - 'Received an unfragmented frame without ' - 'terminating existing fragmentation') - else: - raise InvalidFrameException( - 'New fragmentation started without terminating ' - 'existing fragmentation') - - if frame.fin: - # Unfragmented frame - - self._original_opcode = frame.opcode - return frame.payload - else: - # Start of fragmentation frame - - if common.is_control_opcode(frame.opcode): - raise InvalidFrameException( - 'Control frames must not be fragmented') - - self._original_opcode = frame.opcode - self._received_fragments.append(frame.payload) - return None - - def _process_close_message(self, message): - """Processes close message. - - Args: - message: close message. - - Raises: - InvalidFrameException: when the message is invalid. - """ - - self._request.client_terminated = True - - # Status code is optional. We can have status reason only if we - # have status code. Status reason can be empty string. So, - # allowed cases are - # - no application data: no code no reason - # - 2 octet of application data: has code but no reason - # - 3 or more octet of application data: both code and reason - if len(message) == 0: - self._logger.debug('Received close frame (empty body)') - self._request.ws_close_code = ( - common.STATUS_NO_STATUS_RECEIVED) - elif len(message) == 1: - raise InvalidFrameException( - 'If a close frame has status code, the length of ' - 'status code must be 2 octet') - elif len(message) >= 2: - self._request.ws_close_code = struct.unpack( - '!H', message[0:2])[0] - self._request.ws_close_reason = message[2:].decode( - 'utf-8', 'replace') - self._logger.debug( - 'Received close frame (code=%d, reason=%r)', - self._request.ws_close_code, - self._request.ws_close_reason) - - # As we've received a close frame, no more data is coming over the - # socket. We can now safely close the socket without worrying about - # RST sending. - - if self._request.server_terminated: - self._logger.debug( - 'Received ack for server-initiated closing handshake') - return - - self._logger.debug( - 'Received client-initiated closing handshake') - - code = common.STATUS_NORMAL_CLOSURE - reason = '' - if hasattr(self._request, '_dispatcher'): - dispatcher = self._request._dispatcher - code, reason = dispatcher.passive_closing_handshake( - self._request) - if code is None and reason is not None and len(reason) > 0: - self._logger.warning( - 'Handler specified reason despite code being None') - reason = '' - if reason is None: - reason = '' - self._send_closing_handshake(code, reason) - self._logger.debug( - 'Acknowledged closing handshake initiated by the peer ' - '(code=%r, reason=%r)', code, reason) - - def _process_ping_message(self, message): - """Processes ping message. - - Args: - message: ping message. - """ - - try: - handler = self._request.on_ping_handler - if handler: - handler(self._request, message) - return - except AttributeError, e: - pass - self._send_pong(message) - - def _process_pong_message(self, message): - """Processes pong message. - - Args: - message: pong message. - """ - - # TODO(tyoshino): Add ping timeout handling. - - inflight_pings = deque() - - while True: - try: - expected_body = self._ping_queue.popleft() - if expected_body == message: - # inflight_pings contains pings ignored by the - # other peer. Just forget them. - self._logger.debug( - 'Ping %r is acked (%d pings were ignored)', - expected_body, len(inflight_pings)) - break - else: - inflight_pings.append(expected_body) - except IndexError, e: - # The received pong was unsolicited pong. Keep the - # ping queue as is. - self._ping_queue = inflight_pings - self._logger.debug('Received a unsolicited pong') - break - - try: - handler = self._request.on_pong_handler - if handler: - handler(self._request, message) - except AttributeError, e: - pass - - def receive_message(self): - """Receive a WebSocket frame and return its payload as a text in - unicode or a binary in str. - - Returns: - payload data of the frame - - as unicode instance if received text frame - - as str instance if received binary frame - or None iff received closing handshake. - Raises: - BadOperationException: when called on a client-terminated - connection. - ConnectionTerminatedException: when read returns empty - string. - InvalidFrameException: when the frame contains invalid - data. - UnsupportedFrameException: when the received frame has - flags, opcode we cannot handle. You can ignore this - exception and continue receiving the next frame. - """ - - if self._request.client_terminated: - raise BadOperationException( - 'Requested receive_message after receiving a closing ' - 'handshake') - - while True: - # mp_conn.read will block if no bytes are available. - # Timeout is controlled by TimeOut directive of Apache. - - frame = self._receive_frame_as_frame_object() - - # Check the constraint on the payload size for control frames - # before extension processes the frame. - # See also http://tools.ietf.org/html/rfc6455#section-5.5 - if (common.is_control_opcode(frame.opcode) and - len(frame.payload) > 125): - raise InvalidFrameException( - 'Payload data size of control frames must be 125 bytes or ' - 'less') - - for frame_filter in self._options.incoming_frame_filters: - frame_filter.filter(frame) - - if frame.rsv1 or frame.rsv2 or frame.rsv3: - raise UnsupportedFrameException( - 'Unsupported flag is set (rsv = %d%d%d)' % - (frame.rsv1, frame.rsv2, frame.rsv3)) - - message = self._get_message_from_frame(frame) - if message is None: - continue - - for message_filter in self._options.incoming_message_filters: - message = message_filter.filter(message) - - if self._original_opcode == common.OPCODE_TEXT: - # The WebSocket protocol section 4.4 specifies that invalid - # characters must be replaced with U+fffd REPLACEMENT - # CHARACTER. - try: - return message.decode('utf-8') - except UnicodeDecodeError, e: - raise InvalidUTF8Exception(e) - elif self._original_opcode == common.OPCODE_BINARY: - return message - elif self._original_opcode == common.OPCODE_CLOSE: - self._process_close_message(message) - return None - elif self._original_opcode == common.OPCODE_PING: - self._process_ping_message(message) - elif self._original_opcode == common.OPCODE_PONG: - self._process_pong_message(message) - else: - raise UnsupportedFrameException( - 'Opcode %d is not supported' % self._original_opcode) - - def _send_closing_handshake(self, code, reason): - body = create_closing_handshake_body(code, reason) - frame = create_close_frame( - body, mask=self._options.mask_send, - frame_filters=self._options.outgoing_frame_filters) - - self._request.server_terminated = True - - self._write(frame) - - def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason='', - wait_response=True): - """Closes a WebSocket connection. - - Args: - code: Status code for close frame. If code is None, a close - frame with empty body will be sent. - reason: string representing close reason. - wait_response: True when caller want to wait the response. - Raises: - BadOperationException: when reason is specified with code None - or reason is not an instance of both str and unicode. - """ - - if self._request.server_terminated: - self._logger.debug( - 'Requested close_connection but server is already terminated') - return - - if code is None: - if reason is not None and len(reason) > 0: - raise BadOperationException( - 'close reason must not be specified if code is None') - reason = '' - else: - if not isinstance(reason, str) and not isinstance(reason, unicode): - raise BadOperationException( - 'close reason must be an instance of str or unicode') - - self._send_closing_handshake(code, reason) - self._logger.debug( - 'Initiated closing handshake (code=%r, reason=%r)', - code, reason) - - if (code == common.STATUS_GOING_AWAY or - code == common.STATUS_PROTOCOL_ERROR) or not wait_response: - # It doesn't make sense to wait for a close frame if the reason is - # protocol error or that the server is going away. For some of - # other reasons, it might not make sense to wait for a close frame, - # but it's not clear, yet. - return - - # 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. - 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=''): - frame = create_ping_frame( - body, - self._options.mask_send, - self._options.outgoing_frame_filters) - self._write(frame) - - self._ping_queue.append(body) - - def _send_pong(self, body): - frame = create_pong_frame( - body, - self._options.mask_send, - self._options.outgoing_frame_filters) - self._write(frame) - - def get_last_received_opcode(self): - """Returns the opcode of the WebSocket message which the last received - frame belongs to. The return value is valid iff immediately after - receive_message call. - """ - - return self._original_opcode - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/common.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/common.py deleted file mode 100644 index 2fc2ead64..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/common.py +++ /dev/null @@ -1,303 +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 must not depend on any module specific to the WebSocket protocol. -""" - - -from mod_pywebsocket import http_header_util - - -# Additional log level definitions. -LOGLEVEL_FINE = 9 - -# Constants indicating WebSocket protocol version. -VERSION_HIXIE75 = -1 -VERSION_HYBI00 = 0 -VERSION_HYBI01 = 1 -VERSION_HYBI02 = 2 -VERSION_HYBI03 = 2 -VERSION_HYBI04 = 4 -VERSION_HYBI05 = 5 -VERSION_HYBI06 = 6 -VERSION_HYBI07 = 7 -VERSION_HYBI08 = 8 -VERSION_HYBI09 = 8 -VERSION_HYBI10 = 8 -VERSION_HYBI11 = 8 -VERSION_HYBI12 = 8 -VERSION_HYBI13 = 13 -VERSION_HYBI14 = 13 -VERSION_HYBI15 = 13 -VERSION_HYBI16 = 13 -VERSION_HYBI17 = 13 - -# Constants indicating WebSocket protocol latest version. -VERSION_HYBI_LATEST = VERSION_HYBI13 - -# Port numbers -DEFAULT_WEB_SOCKET_PORT = 80 -DEFAULT_WEB_SOCKET_SECURE_PORT = 443 - -# Schemes -WEB_SOCKET_SCHEME = 'ws' -WEB_SOCKET_SECURE_SCHEME = 'wss' - -# Frame opcodes defined in the spec. -OPCODE_CONTINUATION = 0x0 -OPCODE_TEXT = 0x1 -OPCODE_BINARY = 0x2 -OPCODE_CLOSE = 0x8 -OPCODE_PING = 0x9 -OPCODE_PONG = 0xa - -# UUIDs used by HyBi 04 and later opening handshake and frame masking. -WEBSOCKET_ACCEPT_UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' - -# Opening handshake header names and expected values. -UPGRADE_HEADER = 'Upgrade' -WEBSOCKET_UPGRADE_TYPE = 'websocket' -WEBSOCKET_UPGRADE_TYPE_HIXIE75 = 'WebSocket' -CONNECTION_HEADER = 'Connection' -UPGRADE_CONNECTION_TYPE = 'Upgrade' -HOST_HEADER = 'Host' -ORIGIN_HEADER = 'Origin' -SEC_WEBSOCKET_ORIGIN_HEADER = 'Sec-WebSocket-Origin' -SEC_WEBSOCKET_KEY_HEADER = 'Sec-WebSocket-Key' -SEC_WEBSOCKET_ACCEPT_HEADER = 'Sec-WebSocket-Accept' -SEC_WEBSOCKET_VERSION_HEADER = 'Sec-WebSocket-Version' -SEC_WEBSOCKET_PROTOCOL_HEADER = 'Sec-WebSocket-Protocol' -SEC_WEBSOCKET_EXTENSIONS_HEADER = 'Sec-WebSocket-Extensions' -SEC_WEBSOCKET_DRAFT_HEADER = 'Sec-WebSocket-Draft' -SEC_WEBSOCKET_KEY1_HEADER = 'Sec-WebSocket-Key1' -SEC_WEBSOCKET_KEY2_HEADER = 'Sec-WebSocket-Key2' -SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location' - -# Extensions -DEFLATE_FRAME_EXTENSION = 'deflate-frame' -PERMESSAGE_COMPRESSION_EXTENSION = 'permessage-compress' -PERMESSAGE_DEFLATE_EXTENSION = 'permessage-deflate' -X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame' -X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION = 'x-webkit-permessage-compress' -MUX_EXTENSION = 'mux_DO_NOT_USE' - -# Status codes -# Code STATUS_NO_STATUS_RECEIVED, STATUS_ABNORMAL_CLOSURE, and -# STATUS_TLS_HANDSHAKE are pseudo codes to indicate specific error cases. -# Could not be used for codes in actual closing frames. -# Application level errors must use codes in the range -# STATUS_USER_REGISTERED_BASE to STATUS_USER_PRIVATE_MAX. The codes in the -# range STATUS_USER_REGISTERED_BASE to STATUS_USER_REGISTERED_MAX are managed -# by IANA. Usually application must define user protocol level errors in the -# range STATUS_USER_PRIVATE_BASE to STATUS_USER_PRIVATE_MAX. -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_EXTENSION = 1010 -STATUS_INTERNAL_ENDPOINT_ERROR = 1011 -STATUS_TLS_HANDSHAKE = 1015 -STATUS_USER_REGISTERED_BASE = 3000 -STATUS_USER_REGISTERED_MAX = 3999 -STATUS_USER_PRIVATE_BASE = 4000 -STATUS_USER_PRIVATE_MAX = 4999 -# Following definitions are aliases to keep compatibility. Applications must -# not use these obsoleted definitions anymore. -STATUS_NORMAL = STATUS_NORMAL_CLOSURE -STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA -STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED -STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE -STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA -STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION - -# HTTP status codes -HTTP_STATUS_BAD_REQUEST = 400 -HTTP_STATUS_FORBIDDEN = 403 -HTTP_STATUS_NOT_FOUND = 404 - - -def is_control_opcode(opcode): - return (opcode >> 3) == 1 - - -class ExtensionParameter(object): - """Holds information about an extension which is exchanged on extension - negotiation in opening handshake. - """ - - def __init__(self, name): - self._name = name - # TODO(tyoshino): Change the data structure to more efficient one such - # as dict when the spec changes to say like - # - Parameter names must be unique - # - The order of parameters is not significant - self._parameters = [] - - def name(self): - return self._name - - def add_parameter(self, name, value): - self._parameters.append((name, value)) - - def get_parameters(self): - return self._parameters - - def get_parameter_names(self): - return [name for name, unused_value in self._parameters] - - def has_parameter(self, name): - for param_name, param_value in self._parameters: - if param_name == name: - return True - return False - - def get_parameter_value(self, name): - for param_name, param_value in self._parameters: - if param_name == name: - return param_value - - -class ExtensionParsingException(Exception): - def __init__(self, name): - super(ExtensionParsingException, self).__init__(name) - - -def _parse_extension_param(state, definition): - param_name = http_header_util.consume_token(state) - - if param_name is None: - raise ExtensionParsingException('No valid parameter name found') - - http_header_util.consume_lwses(state) - - if not http_header_util.consume_string(state, '='): - definition.add_parameter(param_name, None) - return - - http_header_util.consume_lwses(state) - - # TODO(tyoshino): Add code to validate that parsed param_value is token - param_value = http_header_util.consume_token_or_quoted_string(state) - if param_value is None: - raise ExtensionParsingException( - 'No valid parameter value found on the right-hand side of ' - 'parameter %r' % param_name) - - definition.add_parameter(param_name, param_value) - - -def _parse_extension(state): - extension_token = http_header_util.consume_token(state) - if extension_token is None: - return None - - extension = ExtensionParameter(extension_token) - - while True: - http_header_util.consume_lwses(state) - - if not http_header_util.consume_string(state, ';'): - break - - http_header_util.consume_lwses(state) - - try: - _parse_extension_param(state, extension) - except ExtensionParsingException, e: - raise ExtensionParsingException( - 'Failed to parse parameter for %r (%r)' % - (extension_token, e)) - - return extension - - -def parse_extensions(data): - """Parses Sec-WebSocket-Extensions header value returns a list of - ExtensionParameter objects. - - Leading LWSes must be trimmed. - """ - - state = http_header_util.ParsingState(data) - - extension_list = [] - while True: - extension = _parse_extension(state) - if extension is not None: - extension_list.append(extension) - - http_header_util.consume_lwses(state) - - if http_header_util.peek(state) is None: - break - - if not http_header_util.consume_string(state, ','): - raise ExtensionParsingException( - 'Failed to parse Sec-WebSocket-Extensions header: ' - 'Expected a comma but found %r' % - http_header_util.peek(state)) - - http_header_util.consume_lwses(state) - - if len(extension_list) == 0: - raise ExtensionParsingException( - 'No valid extension entry found') - - return extension_list - - -def format_extension(extension): - """Formats an ExtensionParameter object.""" - - formatted_params = [extension.name()] - for param_name, param_value in extension.get_parameters(): - if param_value is None: - formatted_params.append(param_name) - else: - quoted_value = http_header_util.quote_if_necessary(param_value) - formatted_params.append('%s=%s' % (param_name, quoted_value)) - return '; '.join(formatted_params) - - -def format_extensions(extension_list): - """Formats a list of ExtensionParameter objects.""" - - formatted_extension_list = [] - for extension in extension_list: - formatted_extension_list.append(format_extension(extension)) - return ', '.join(formatted_extension_list) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/dispatch.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/dispatch.py deleted file mode 100644 index 96c91e0c9..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/dispatch.py +++ /dev/null @@ -1,393 +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. - - -"""Dispatch WebSocket request. -""" - - -import logging -import os -import re - -from mod_pywebsocket import common -from mod_pywebsocket import handshake -from mod_pywebsocket import msgutil -from mod_pywebsocket import mux -from mod_pywebsocket import stream -from mod_pywebsocket import util - - -_SOURCE_PATH_PATTERN = re.compile(r'(?i)_wsh\.py$') -_SOURCE_SUFFIX = '_wsh.py' -_DO_EXTRA_HANDSHAKE_HANDLER_NAME = 'web_socket_do_extra_handshake' -_TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data' -_PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME = ( - 'web_socket_passive_closing_handshake') - - -class DispatchException(Exception): - """Exception in dispatching WebSocket request.""" - - def __init__(self, name, status=common.HTTP_STATUS_NOT_FOUND): - super(DispatchException, self).__init__(name) - self.status = status - - -def _default_passive_closing_handshake_handler(request): - """Default web_socket_passive_closing_handshake handler.""" - - return common.STATUS_NORMAL_CLOSURE, '' - - -def _normalize_path(path): - """Normalize path. - - Args: - path: the path to normalize. - - Path is converted to the absolute path. - The input path can use either '\\' or '/' as the separator. - The normalized path always uses '/' regardless of the platform. - """ - - path = path.replace('\\', os.path.sep) - path = os.path.realpath(path) - path = path.replace('\\', '/') - return path - - -def _create_path_to_resource_converter(base_dir): - """Returns a function that converts the path of a WebSocket handler source - file to a resource string by removing the path to the base directory from - its head, removing _SOURCE_SUFFIX from its tail, and replacing path - separators in it with '/'. - - Args: - base_dir: the path to the base directory. - """ - - base_dir = _normalize_path(base_dir) - - base_len = len(base_dir) - suffix_len = len(_SOURCE_SUFFIX) - - def converter(path): - if not path.endswith(_SOURCE_SUFFIX): - return None - # _normalize_path must not be used because resolving symlink breaks - # following path check. - path = path.replace('\\', '/') - if not path.startswith(base_dir): - return None - return path[base_len:-suffix_len] - - return converter - - -def _enumerate_handler_file_paths(directory): - """Returns a generator that enumerates WebSocket Handler source file names - in the given directory. - """ - - for root, unused_dirs, files in os.walk(directory): - for base in files: - path = os.path.join(root, base) - if _SOURCE_PATH_PATTERN.search(path): - yield path - - -class _HandlerSuite(object): - """A handler suite holder class.""" - - def __init__(self, do_extra_handshake, transfer_data, - passive_closing_handshake): - self.do_extra_handshake = do_extra_handshake - self.transfer_data = transfer_data - self.passive_closing_handshake = passive_closing_handshake - - -def _source_handler_file(handler_definition): - """Source a handler definition string. - - Args: - handler_definition: a string containing Python statements that define - handler functions. - """ - - global_dic = {} - try: - exec handler_definition in global_dic - except Exception: - raise DispatchException('Error in sourcing handler:' + - util.get_stack_trace()) - passive_closing_handshake_handler = None - try: - passive_closing_handshake_handler = _extract_handler( - global_dic, _PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME) - except Exception: - passive_closing_handshake_handler = ( - _default_passive_closing_handshake_handler) - return _HandlerSuite( - _extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME), - _extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME), - passive_closing_handshake_handler) - - -def _extract_handler(dic, name): - """Extracts a callable with the specified name from the given dictionary - dic. - """ - - if name not in dic: - raise DispatchException('%s is not defined.' % name) - handler = dic[name] - if not callable(handler): - raise DispatchException('%s is not callable.' % name) - return handler - - -class Dispatcher(object): - """Dispatches WebSocket requests. - - This class maintains a map from resource name to handlers. - """ - - def __init__( - self, root_dir, scan_dir=None, - allow_handlers_outside_root_dir=True): - """Construct an instance. - - Args: - root_dir: The directory where handler definition files are - placed. - scan_dir: The directory where handler definition files are - searched. scan_dir must be a directory under root_dir, - including root_dir itself. If scan_dir is None, - root_dir is used as scan_dir. scan_dir can be useful - in saving scan time when root_dir contains many - subdirectories. - allow_handlers_outside_root_dir: Scans handler files even if their - canonical path is not under root_dir. - """ - - self._logger = util.get_class_logger(self) - - self._handler_suite_map = {} - self._source_warnings = [] - if scan_dir is None: - scan_dir = root_dir - if not os.path.realpath(scan_dir).startswith( - os.path.realpath(root_dir)): - raise DispatchException('scan_dir:%s must be a directory under ' - 'root_dir:%s.' % (scan_dir, root_dir)) - self._source_handler_files_in_dir( - root_dir, scan_dir, allow_handlers_outside_root_dir) - - def add_resource_path_alias(self, - alias_resource_path, existing_resource_path): - """Add resource path alias. - - Once added, request to alias_resource_path would be handled by - handler registered for existing_resource_path. - - Args: - alias_resource_path: alias resource path - existing_resource_path: existing resource path - """ - try: - handler_suite = self._handler_suite_map[existing_resource_path] - self._handler_suite_map[alias_resource_path] = handler_suite - except KeyError: - raise DispatchException('No handler for: %r' % - existing_resource_path) - - def source_warnings(self): - """Return warnings in sourcing handlers.""" - - return self._source_warnings - - def do_extra_handshake(self, request): - """Do extra checking in WebSocket handshake. - - Select a handler based on request.uri and call its - web_socket_do_extra_handshake function. - - Args: - request: mod_python request. - - Raises: - DispatchException: when handler was not found - AbortedByUserException: when user handler abort connection - HandshakeException: when opening handshake failed - """ - - handler_suite = self.get_handler_suite(request.ws_resource) - if handler_suite is None: - raise DispatchException('No handler for: %r' % request.ws_resource) - do_extra_handshake_ = handler_suite.do_extra_handshake - try: - do_extra_handshake_(request) - except handshake.AbortedByUserException, e: - # Re-raise to tell the caller of this function to finish this - # connection without sending any error. - self._logger.debug('%s', util.get_stack_trace()) - raise - except Exception, e: - util.prepend_message_to_exception( - '%s raised exception for %s: ' % ( - _DO_EXTRA_HANDSHAKE_HANDLER_NAME, - request.ws_resource), - e) - raise handshake.HandshakeException(e, common.HTTP_STATUS_FORBIDDEN) - - def transfer_data(self, request): - """Let a handler transfer_data with a WebSocket client. - - Select a handler based on request.ws_resource and call its - web_socket_transfer_data function. - - Args: - request: mod_python request. - - Raises: - DispatchException: when handler was not found - AbortedByUserException: when user handler abort connection - """ - - # TODO(tyoshino): Terminate underlying TCP connection if possible. - try: - if mux.use_mux(request): - mux.start(request, self) - else: - handler_suite = self.get_handler_suite(request.ws_resource) - if handler_suite is None: - raise DispatchException('No handler for: %r' % - request.ws_resource) - transfer_data_ = handler_suite.transfer_data - transfer_data_(request) - - if not request.server_terminated: - request.ws_stream.close_connection() - # Catch non-critical exceptions the handler didn't handle. - except handshake.AbortedByUserException, e: - self._logger.debug('%s', util.get_stack_trace()) - raise - except msgutil.BadOperationException, e: - self._logger.debug('%s', e) - request.ws_stream.close_connection( - common.STATUS_INTERNAL_ENDPOINT_ERROR) - except msgutil.InvalidFrameException, e: - # InvalidFrameException must be caught before - # ConnectionTerminatedException that catches InvalidFrameException. - self._logger.debug('%s', e) - request.ws_stream.close_connection(common.STATUS_PROTOCOL_ERROR) - except msgutil.UnsupportedFrameException, e: - self._logger.debug('%s', e) - request.ws_stream.close_connection(common.STATUS_UNSUPPORTED_DATA) - except stream.InvalidUTF8Exception, e: - self._logger.debug('%s', e) - request.ws_stream.close_connection( - common.STATUS_INVALID_FRAME_PAYLOAD_DATA) - except msgutil.ConnectionTerminatedException, e: - self._logger.debug('%s', e) - except Exception, e: - # Any other exceptions are forwarded to the caller of this - # function. - util.prepend_message_to_exception( - '%s raised exception for %s: ' % ( - _TRANSFER_DATA_HANDLER_NAME, request.ws_resource), - e) - raise - - def passive_closing_handshake(self, request): - """Prepare code and reason for responding client initiated closing - handshake. - """ - - handler_suite = self.get_handler_suite(request.ws_resource) - if handler_suite is None: - return _default_passive_closing_handshake_handler(request) - return handler_suite.passive_closing_handshake(request) - - def get_handler_suite(self, resource): - """Retrieves two handlers (one for extra handshake processing, and one - for data transfer) for the given request as a HandlerSuite object. - """ - - fragment = None - if '#' in resource: - resource, fragment = resource.split('#', 1) - if '?' in resource: - resource = resource.split('?', 1)[0] - handler_suite = self._handler_suite_map.get(resource) - if handler_suite and fragment: - raise DispatchException('Fragment identifiers MUST NOT be used on ' - 'WebSocket URIs', - common.HTTP_STATUS_BAD_REQUEST) - return handler_suite - - def _source_handler_files_in_dir( - self, root_dir, scan_dir, allow_handlers_outside_root_dir): - """Source all the handler source files in the scan_dir directory. - - The resource path is determined relative to root_dir. - """ - - # We build a map from resource to handler code assuming that there's - # only one path from root_dir to scan_dir and it can be obtained by - # comparing realpath of them. - - # Here we cannot use abspath. See - # https://bugs.webkit.org/show_bug.cgi?id=31603 - - convert = _create_path_to_resource_converter(root_dir) - scan_realpath = os.path.realpath(scan_dir) - root_realpath = os.path.realpath(root_dir) - for path in _enumerate_handler_file_paths(scan_realpath): - if (not allow_handlers_outside_root_dir and - (not os.path.realpath(path).startswith(root_realpath))): - self._logger.debug( - 'Canonical path of %s is not under root directory' % - path) - continue - try: - handler_suite = _source_handler_file(open(path).read()) - except DispatchException, e: - self._source_warnings.append('%s: %s' % (path, e)) - continue - resource = convert(path) - if resource is None: - self._logger.debug( - 'Path to resource conversion on %s failed' % path) - else: - self._handler_suite_map[convert(path)] = handler_suite - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/extensions.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/extensions.py deleted file mode 100644 index 49a9fdcf9..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/extensions.py +++ /dev/null @@ -1,885 +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. - - -from mod_pywebsocket import common -from mod_pywebsocket import util -from mod_pywebsocket.http_header_util import quote_if_necessary - - -# The list of available server side extension processor classes. -_available_processors = {} -_compression_extension_names = [] - - -class ExtensionProcessorInterface(object): - - def __init__(self, request): - self._logger = util.get_class_logger(self) - - self._request = request - self._active = True - - def request(self): - return self._request - - def name(self): - return None - - def check_consistency_with_other_processors(self, processors): - pass - - def set_active(self, active): - self._active = active - - def is_active(self): - return self._active - - def _get_extension_response_internal(self): - return None - - def get_extension_response(self): - if not self._active: - self._logger.debug('Extension %s is deactivated', self.name()) - return None - - response = self._get_extension_response_internal() - if response is None: - self._active = False - return response - - def _setup_stream_options_internal(self, stream_options): - pass - - def setup_stream_options(self, stream_options): - if self._active: - self._setup_stream_options_internal(stream_options) - - -def _log_outgoing_compression_ratio( - logger, original_bytes, filtered_bytes, average_ratio): - # Print inf when ratio is not available. - ratio = float('inf') - if original_bytes != 0: - ratio = float(filtered_bytes) / original_bytes - - logger.debug('Outgoing compression ratio: %f (average: %f)' % - (ratio, average_ratio)) - - -def _log_incoming_compression_ratio( - logger, received_bytes, filtered_bytes, average_ratio): - # Print inf when ratio is not available. - ratio = float('inf') - if filtered_bytes != 0: - ratio = float(received_bytes) / filtered_bytes - - logger.debug('Incoming compression ratio: %f (average: %f)' % - (ratio, average_ratio)) - - -def _parse_window_bits(bits): - """Return parsed integer value iff the given string conforms to the - grammar of the window bits extension parameters. - """ - - if bits is None: - raise ValueError('Value is required') - - # For non integer values such as "10.0", ValueError will be raised. - int_bits = int(bits) - - # First condition is to drop leading zero case e.g. "08". - if bits != str(int_bits) or int_bits < 8 or int_bits > 15: - raise ValueError('Invalid value: %r' % bits) - - return int_bits - - -class _AverageRatioCalculator(object): - """Stores total bytes of original and result data, and calculates average - result / original ratio. - """ - - def __init__(self): - self._total_original_bytes = 0 - self._total_result_bytes = 0 - - def add_original_bytes(self, value): - self._total_original_bytes += value - - def add_result_bytes(self, value): - self._total_result_bytes += value - - def get_average_ratio(self): - if self._total_original_bytes != 0: - return (float(self._total_result_bytes) / - self._total_original_bytes) - else: - return float('inf') - - -class DeflateFrameExtensionProcessor(ExtensionProcessorInterface): - """deflate-frame extension processor. - - Specification: - http://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate - """ - - _WINDOW_BITS_PARAM = 'max_window_bits' - _NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover' - - def __init__(self, request): - ExtensionProcessorInterface.__init__(self, request) - self._logger = util.get_class_logger(self) - - self._response_window_bits = None - self._response_no_context_takeover = False - self._bfinal = False - - # Calculates - # (Total outgoing bytes supplied to this filter) / - # (Total bytes sent to the network after applying this filter) - self._outgoing_average_ratio_calculator = _AverageRatioCalculator() - - # Calculates - # (Total bytes received from the network) / - # (Total incoming bytes obtained after applying this filter) - self._incoming_average_ratio_calculator = _AverageRatioCalculator() - - def name(self): - return common.DEFLATE_FRAME_EXTENSION - - def _get_extension_response_internal(self): - # Any unknown parameter will be just ignored. - - window_bits = None - if self._request.has_parameter(self._WINDOW_BITS_PARAM): - window_bits = self._request.get_parameter_value( - self._WINDOW_BITS_PARAM) - try: - window_bits = _parse_window_bits(window_bits) - except ValueError, e: - return None - - no_context_takeover = self._request.has_parameter( - self._NO_CONTEXT_TAKEOVER_PARAM) - if (no_context_takeover and - self._request.get_parameter_value( - self._NO_CONTEXT_TAKEOVER_PARAM) is not None): - return None - - self._rfc1979_deflater = util._RFC1979Deflater( - window_bits, no_context_takeover) - - self._rfc1979_inflater = util._RFC1979Inflater() - - self._compress_outgoing = True - - response = common.ExtensionParameter(self._request.name()) - - if self._response_window_bits is not None: - response.add_parameter( - self._WINDOW_BITS_PARAM, str(self._response_window_bits)) - if self._response_no_context_takeover: - response.add_parameter( - self._NO_CONTEXT_TAKEOVER_PARAM, None) - - self._logger.debug( - 'Enable %s extension (' - 'request: window_bits=%s; no_context_takeover=%r, ' - 'response: window_wbits=%s; no_context_takeover=%r)' % - (self._request.name(), - window_bits, - no_context_takeover, - self._response_window_bits, - self._response_no_context_takeover)) - - return response - - def _setup_stream_options_internal(self, stream_options): - - class _OutgoingFilter(object): - - def __init__(self, parent): - self._parent = parent - - def filter(self, frame): - self._parent._outgoing_filter(frame) - - class _IncomingFilter(object): - - def __init__(self, parent): - self._parent = parent - - def filter(self, frame): - self._parent._incoming_filter(frame) - - stream_options.outgoing_frame_filters.append( - _OutgoingFilter(self)) - stream_options.incoming_frame_filters.insert( - 0, _IncomingFilter(self)) - - def set_response_window_bits(self, value): - self._response_window_bits = value - - def set_response_no_context_takeover(self, value): - self._response_no_context_takeover = value - - def set_bfinal(self, value): - self._bfinal = value - - def enable_outgoing_compression(self): - self._compress_outgoing = True - - def disable_outgoing_compression(self): - self._compress_outgoing = False - - def _outgoing_filter(self, frame): - """Transform outgoing frames. This method is called only by - an _OutgoingFilter instance. - """ - - original_payload_size = len(frame.payload) - self._outgoing_average_ratio_calculator.add_original_bytes( - original_payload_size) - - if (not self._compress_outgoing or - common.is_control_opcode(frame.opcode)): - self._outgoing_average_ratio_calculator.add_result_bytes( - original_payload_size) - return - - frame.payload = self._rfc1979_deflater.filter( - frame.payload, bfinal=self._bfinal) - frame.rsv1 = 1 - - filtered_payload_size = len(frame.payload) - self._outgoing_average_ratio_calculator.add_result_bytes( - filtered_payload_size) - - _log_outgoing_compression_ratio( - self._logger, - original_payload_size, - filtered_payload_size, - self._outgoing_average_ratio_calculator.get_average_ratio()) - - def _incoming_filter(self, frame): - """Transform incoming frames. This method is called only by - an _IncomingFilter instance. - """ - - received_payload_size = len(frame.payload) - self._incoming_average_ratio_calculator.add_result_bytes( - received_payload_size) - - if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode): - self._incoming_average_ratio_calculator.add_original_bytes( - received_payload_size) - return - - frame.payload = self._rfc1979_inflater.filter(frame.payload) - frame.rsv1 = 0 - - filtered_payload_size = len(frame.payload) - self._incoming_average_ratio_calculator.add_original_bytes( - filtered_payload_size) - - _log_incoming_compression_ratio( - self._logger, - received_payload_size, - filtered_payload_size, - self._incoming_average_ratio_calculator.get_average_ratio()) - - -_available_processors[common.DEFLATE_FRAME_EXTENSION] = ( - DeflateFrameExtensionProcessor) -_compression_extension_names.append(common.DEFLATE_FRAME_EXTENSION) - -_available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = ( - DeflateFrameExtensionProcessor) -_compression_extension_names.append(common.X_WEBKIT_DEFLATE_FRAME_EXTENSION) - - -def _parse_compression_method(data): - """Parses the value of "method" extension parameter.""" - - return common.parse_extensions(data) - - -def _create_accepted_method_desc(method_name, method_params): - """Creates accepted-method-desc from given method name and parameters""" - - extension = common.ExtensionParameter(method_name) - for name, value in method_params: - extension.add_parameter(name, value) - return common.format_extension(extension) - - -class CompressionExtensionProcessorBase(ExtensionProcessorInterface): - """Base class for perframe-compress and permessage-compress extension.""" - - _METHOD_PARAM = 'method' - - def __init__(self, request): - ExtensionProcessorInterface.__init__(self, request) - self._logger = util.get_class_logger(self) - self._compression_method_name = None - self._compression_processor = None - self._compression_processor_hook = None - - def name(self): - return '' - - def _lookup_compression_processor(self, method_desc): - return None - - def _get_compression_processor_response(self): - """Looks up the compression processor based on the self._request and - returns the compression processor's response. - """ - - method_list = self._request.get_parameter_value(self._METHOD_PARAM) - if method_list is None: - return None - methods = _parse_compression_method(method_list) - if methods is None: - return None - comression_processor = None - # The current implementation tries only the first method that matches - # supported algorithm. Following methods aren't tried even if the - # first one is rejected. - # TODO(bashi): Need to clarify this behavior. - for method_desc in methods: - compression_processor = self._lookup_compression_processor( - method_desc) - if compression_processor is not None: - self._compression_method_name = method_desc.name() - break - if compression_processor is None: - return None - - if self._compression_processor_hook: - self._compression_processor_hook(compression_processor) - - processor_response = compression_processor.get_extension_response() - if processor_response is None: - return None - self._compression_processor = compression_processor - return processor_response - - def _get_extension_response_internal(self): - processor_response = self._get_compression_processor_response() - if processor_response is None: - return None - - response = common.ExtensionParameter(self._request.name()) - accepted_method_desc = _create_accepted_method_desc( - self._compression_method_name, - processor_response.get_parameters()) - response.add_parameter(self._METHOD_PARAM, accepted_method_desc) - self._logger.debug( - 'Enable %s extension (method: %s)' % - (self._request.name(), self._compression_method_name)) - return response - - def _setup_stream_options_internal(self, stream_options): - if self._compression_processor is None: - return - self._compression_processor.setup_stream_options(stream_options) - - def set_compression_processor_hook(self, hook): - self._compression_processor_hook = hook - - def get_compression_processor(self): - return self._compression_processor - - -class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface): - """permessage-deflate extension processor. It's also used for - permessage-compress extension when the deflate method is chosen. - - Specification: - http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-08 - """ - - _SERVER_MAX_WINDOW_BITS_PARAM = 'server_max_window_bits' - _SERVER_NO_CONTEXT_TAKEOVER_PARAM = 'server_no_context_takeover' - _CLIENT_MAX_WINDOW_BITS_PARAM = 'client_max_window_bits' - _CLIENT_NO_CONTEXT_TAKEOVER_PARAM = 'client_no_context_takeover' - - def __init__(self, request, draft08=True): - """Construct PerMessageDeflateExtensionProcessor - - Args: - draft08: Follow the constraints on the parameters that were not - specified for permessage-compress but are specified for - permessage-deflate as on - draft-ietf-hybi-permessage-compression-08. - """ - - ExtensionProcessorInterface.__init__(self, request) - self._logger = util.get_class_logger(self) - - self._preferred_client_max_window_bits = None - self._client_no_context_takeover = False - - self._draft08 = draft08 - - def name(self): - return 'deflate' - - def _get_extension_response_internal(self): - if self._draft08: - for name in self._request.get_parameter_names(): - if name not in [self._SERVER_MAX_WINDOW_BITS_PARAM, - self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, - self._CLIENT_MAX_WINDOW_BITS_PARAM]: - self._logger.debug('Unknown parameter: %r', name) - return None - else: - # Any unknown parameter will be just ignored. - pass - - server_max_window_bits = None - if self._request.has_parameter(self._SERVER_MAX_WINDOW_BITS_PARAM): - server_max_window_bits = self._request.get_parameter_value( - self._SERVER_MAX_WINDOW_BITS_PARAM) - try: - server_max_window_bits = _parse_window_bits( - server_max_window_bits) - except ValueError, e: - self._logger.debug('Bad %s parameter: %r', - self._SERVER_MAX_WINDOW_BITS_PARAM, - e) - return None - - server_no_context_takeover = self._request.has_parameter( - self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) - if (server_no_context_takeover and - self._request.get_parameter_value( - self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) is not None): - self._logger.debug('%s parameter must not have a value: %r', - self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, - server_no_context_takeover) - return None - - # client_max_window_bits from a client indicates whether the client can - # accept client_max_window_bits from a server or not. - client_client_max_window_bits = self._request.has_parameter( - self._CLIENT_MAX_WINDOW_BITS_PARAM) - if (self._draft08 and - client_client_max_window_bits and - self._request.get_parameter_value( - self._CLIENT_MAX_WINDOW_BITS_PARAM) is not None): - self._logger.debug('%s parameter must not have a value in a ' - 'client\'s opening handshake: %r', - self._CLIENT_MAX_WINDOW_BITS_PARAM, - client_client_max_window_bits) - return None - - self._rfc1979_deflater = util._RFC1979Deflater( - server_max_window_bits, server_no_context_takeover) - - # Note that we prepare for incoming messages compressed with window - # bits upto 15 regardless of the client_max_window_bits value to be - # sent to the client. - self._rfc1979_inflater = util._RFC1979Inflater() - - self._framer = _PerMessageDeflateFramer( - server_max_window_bits, server_no_context_takeover) - self._framer.set_bfinal(False) - self._framer.set_compress_outgoing_enabled(True) - - response = common.ExtensionParameter(self._request.name()) - - if server_max_window_bits is not None: - response.add_parameter( - self._SERVER_MAX_WINDOW_BITS_PARAM, - str(server_max_window_bits)) - - if server_no_context_takeover: - response.add_parameter( - self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, None) - - if self._preferred_client_max_window_bits is not None: - if self._draft08 and not client_client_max_window_bits: - self._logger.debug('Processor is configured to use %s but ' - 'the client cannot accept it', - self._CLIENT_MAX_WINDOW_BITS_PARAM) - return None - response.add_parameter( - self._CLIENT_MAX_WINDOW_BITS_PARAM, - str(self._preferred_client_max_window_bits)) - - if self._client_no_context_takeover: - response.add_parameter( - self._CLIENT_NO_CONTEXT_TAKEOVER_PARAM, None) - - self._logger.debug( - 'Enable %s extension (' - 'request: server_max_window_bits=%s; ' - 'server_no_context_takeover=%r, ' - 'response: client_max_window_bits=%s; ' - 'client_no_context_takeover=%r)' % - (self._request.name(), - server_max_window_bits, - server_no_context_takeover, - self._preferred_client_max_window_bits, - self._client_no_context_takeover)) - - return response - - def _setup_stream_options_internal(self, stream_options): - self._framer.setup_stream_options(stream_options) - - def set_client_max_window_bits(self, value): - """If this option is specified, this class adds the - client_max_window_bits extension parameter to the handshake response, - but doesn't reduce the LZ77 sliding window size of its inflater. - I.e., you can use this for testing client implementation but cannot - reduce memory usage of this class. - - If this method has been called with True and an offer without the - client_max_window_bits extension parameter is received, - - (When processing the permessage-deflate extension) this processor - declines the request. - - (When processing the permessage-compress extension) this processor - accepts the request. - """ - - self._preferred_client_max_window_bits = value - - def set_client_no_context_takeover(self, value): - """If this option is specified, this class adds the - client_no_context_takeover extension parameter to the handshake - response, but doesn't reset inflater for each message. I.e., you can - use this for testing client implementation but cannot reduce memory - usage of this class. - """ - - self._client_no_context_takeover = value - - def set_bfinal(self, value): - self._framer.set_bfinal(value) - - def enable_outgoing_compression(self): - self._framer.set_compress_outgoing_enabled(True) - - def disable_outgoing_compression(self): - self._framer.set_compress_outgoing_enabled(False) - - -class _PerMessageDeflateFramer(object): - """A framer for extensions with per-message DEFLATE feature.""" - - def __init__(self, deflate_max_window_bits, deflate_no_context_takeover): - self._logger = util.get_class_logger(self) - - self._rfc1979_deflater = util._RFC1979Deflater( - deflate_max_window_bits, deflate_no_context_takeover) - - self._rfc1979_inflater = util._RFC1979Inflater() - - self._bfinal = False - - self._compress_outgoing_enabled = False - - # True if a message is fragmented and compression is ongoing. - self._compress_ongoing = False - - # Calculates - # (Total outgoing bytes supplied to this filter) / - # (Total bytes sent to the network after applying this filter) - self._outgoing_average_ratio_calculator = _AverageRatioCalculator() - - # Calculates - # (Total bytes received from the network) / - # (Total incoming bytes obtained after applying this filter) - self._incoming_average_ratio_calculator = _AverageRatioCalculator() - - def set_bfinal(self, value): - self._bfinal = value - - def set_compress_outgoing_enabled(self, value): - self._compress_outgoing_enabled = value - - def _process_incoming_message(self, message, decompress): - if not decompress: - return message - - received_payload_size = len(message) - self._incoming_average_ratio_calculator.add_result_bytes( - received_payload_size) - - message = self._rfc1979_inflater.filter(message) - - filtered_payload_size = len(message) - self._incoming_average_ratio_calculator.add_original_bytes( - filtered_payload_size) - - _log_incoming_compression_ratio( - self._logger, - received_payload_size, - filtered_payload_size, - self._incoming_average_ratio_calculator.get_average_ratio()) - - return message - - def _process_outgoing_message(self, message, end, binary): - if not binary: - message = message.encode('utf-8') - - if not self._compress_outgoing_enabled: - return message - - original_payload_size = len(message) - self._outgoing_average_ratio_calculator.add_original_bytes( - original_payload_size) - - message = self._rfc1979_deflater.filter( - message, end=end, bfinal=self._bfinal) - - filtered_payload_size = len(message) - self._outgoing_average_ratio_calculator.add_result_bytes( - filtered_payload_size) - - _log_outgoing_compression_ratio( - self._logger, - original_payload_size, - filtered_payload_size, - self._outgoing_average_ratio_calculator.get_average_ratio()) - - if not self._compress_ongoing: - self._outgoing_frame_filter.set_compression_bit() - self._compress_ongoing = not end - return message - - def _process_incoming_frame(self, frame): - if frame.rsv1 == 1 and not common.is_control_opcode(frame.opcode): - self._incoming_message_filter.decompress_next_message() - frame.rsv1 = 0 - - def _process_outgoing_frame(self, frame, compression_bit): - if (not compression_bit or - common.is_control_opcode(frame.opcode)): - return - - frame.rsv1 = 1 - - def setup_stream_options(self, stream_options): - """Creates filters and sets them to the StreamOptions.""" - - class _OutgoingMessageFilter(object): - - def __init__(self, parent): - self._parent = parent - - def filter(self, message, end=True, binary=False): - return self._parent._process_outgoing_message( - message, end, binary) - - class _IncomingMessageFilter(object): - - def __init__(self, parent): - self._parent = parent - self._decompress_next_message = False - - def decompress_next_message(self): - self._decompress_next_message = True - - def filter(self, message): - message = self._parent._process_incoming_message( - message, self._decompress_next_message) - self._decompress_next_message = False - return message - - self._outgoing_message_filter = _OutgoingMessageFilter(self) - self._incoming_message_filter = _IncomingMessageFilter(self) - stream_options.outgoing_message_filters.append( - self._outgoing_message_filter) - stream_options.incoming_message_filters.append( - self._incoming_message_filter) - - class _OutgoingFrameFilter(object): - - def __init__(self, parent): - self._parent = parent - self._set_compression_bit = False - - def set_compression_bit(self): - self._set_compression_bit = True - - def filter(self, frame): - self._parent._process_outgoing_frame( - frame, self._set_compression_bit) - self._set_compression_bit = False - - class _IncomingFrameFilter(object): - - def __init__(self, parent): - self._parent = parent - - def filter(self, frame): - self._parent._process_incoming_frame(frame) - - self._outgoing_frame_filter = _OutgoingFrameFilter(self) - self._incoming_frame_filter = _IncomingFrameFilter(self) - stream_options.outgoing_frame_filters.append( - self._outgoing_frame_filter) - stream_options.incoming_frame_filters.append( - self._incoming_frame_filter) - - stream_options.encode_text_message_to_utf8 = False - - -_available_processors[common.PERMESSAGE_DEFLATE_EXTENSION] = ( - PerMessageDeflateExtensionProcessor) -# TODO(tyoshino): Reorganize class names. -_compression_extension_names.append('deflate') - - -class PerMessageCompressExtensionProcessor( - CompressionExtensionProcessorBase): - """permessage-compress extension processor. - - Specification: - http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression - """ - - _DEFLATE_METHOD = 'deflate' - - def __init__(self, request): - CompressionExtensionProcessorBase.__init__(self, request) - - def name(self): - return common.PERMESSAGE_COMPRESSION_EXTENSION - - def _lookup_compression_processor(self, method_desc): - if method_desc.name() == self._DEFLATE_METHOD: - return PerMessageDeflateExtensionProcessor(method_desc, False) - return None - - -_available_processors[common.PERMESSAGE_COMPRESSION_EXTENSION] = ( - PerMessageCompressExtensionProcessor) -_compression_extension_names.append(common.PERMESSAGE_COMPRESSION_EXTENSION) - - -class MuxExtensionProcessor(ExtensionProcessorInterface): - """WebSocket multiplexing extension processor.""" - - _QUOTA_PARAM = 'quota' - - def __init__(self, request): - ExtensionProcessorInterface.__init__(self, request) - self._quota = 0 - self._extensions = [] - - def name(self): - return common.MUX_EXTENSION - - def check_consistency_with_other_processors(self, processors): - before_mux = True - for processor in processors: - name = processor.name() - if name == self.name(): - before_mux = False - continue - if not processor.is_active(): - continue - if before_mux: - # Mux extension cannot be used after extensions - # that depend on frame boundary, extension data field, or any - # reserved bits which are attributed to each frame. - if (name == common.DEFLATE_FRAME_EXTENSION or - name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION): - self.set_active(False) - return - else: - # Mux extension should not be applied before any history-based - # compression extension. - if (name == common.DEFLATE_FRAME_EXTENSION or - name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION or - name == common.PERMESSAGE_COMPRESSION_EXTENSION or - name == common.X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION): - self.set_active(False) - return - - def _get_extension_response_internal(self): - self._active = False - quota = self._request.get_parameter_value(self._QUOTA_PARAM) - if quota is not None: - try: - quota = int(quota) - except ValueError, e: - return None - if quota < 0 or quota >= 2 ** 32: - return None - self._quota = quota - - self._active = True - return common.ExtensionParameter(common.MUX_EXTENSION) - - def _setup_stream_options_internal(self, stream_options): - pass - - def set_quota(self, quota): - self._quota = quota - - def quota(self): - return self._quota - - def set_extensions(self, extensions): - self._extensions = extensions - - def extensions(self): - return self._extensions - - -_available_processors[common.MUX_EXTENSION] = MuxExtensionProcessor - - -def get_extension_processor(extension_request): - """Given an ExtensionParameter representing an extension offer received - from a client, configures and returns an instance of the corresponding - extension processor class. - """ - - processor_class = _available_processors.get(extension_request.name()) - if processor_class is None: - return None - return processor_class(extension_request) - - -def is_compression_extension(extension_name): - return extension_name in _compression_extension_names - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/fast_masking.i b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/fast_masking.i deleted file mode 100644 index ddaad27f5..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/fast_masking.i +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2013, 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. - - -%module fast_masking - -%include "cstring.i" - -%{ -#include <cstring> - -#ifdef __SSE2__ -#include <emmintrin.h> -#endif -%} - -%apply (char *STRING, int LENGTH) { - (const char* payload, int payload_length), - (const char* masking_key, int masking_key_length) }; -%cstring_output_allocate_size( - char** result, int* result_length, delete [] *$1); - -%inline %{ - -void mask( - const char* payload, int payload_length, - const char* masking_key, int masking_key_length, - int masking_key_index, - char** result, int* result_length) { - *result = new char[payload_length]; - *result_length = payload_length; - memcpy(*result, payload, payload_length); - - char* cursor = *result; - char* cursor_end = *result + *result_length; - -#ifdef __SSE2__ - while ((cursor < cursor_end) && - (reinterpret_cast<size_t>(cursor) & 0xf)) { - *cursor ^= masking_key[masking_key_index]; - ++cursor; - masking_key_index = (masking_key_index + 1) % masking_key_length; - } - if (cursor == cursor_end) { - return; - } - - const int kBlockSize = 16; - __m128i masking_key_block; - for (int i = 0; i < kBlockSize; ++i) { - *(reinterpret_cast<char*>(&masking_key_block) + i) = - masking_key[masking_key_index]; - masking_key_index = (masking_key_index + 1) % masking_key_length; - } - - while (cursor + kBlockSize <= cursor_end) { - __m128i payload_block = - _mm_load_si128(reinterpret_cast<__m128i*>(cursor)); - _mm_stream_si128(reinterpret_cast<__m128i*>(cursor), - _mm_xor_si128(payload_block, masking_key_block)); - cursor += kBlockSize; - } -#endif - - while (cursor < cursor_end) { - *cursor ^= masking_key[masking_key_index]; - ++cursor; - masking_key_index = (masking_key_index + 1) % masking_key_length; - } -} - -%} diff --git a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/handshake/__init__.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/handshake/__init__.py deleted file mode 100644 index 194f6b395..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/handshake/__init__.py +++ /dev/null @@ -1,110 +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. - - -"""WebSocket opening handshake processor. This class try to apply available -opening handshake processors for each protocol version until a connection is -successfully established. -""" - - -import logging - -from mod_pywebsocket import common -from mod_pywebsocket.handshake import hybi00 -from mod_pywebsocket.handshake import hybi -# Export AbortedByUserException, HandshakeException, and VersionException -# symbol from this module. -from mod_pywebsocket.handshake._base import AbortedByUserException -from mod_pywebsocket.handshake._base import HandshakeException -from mod_pywebsocket.handshake._base import VersionException - - -_LOGGER = logging.getLogger(__name__) - - -def do_handshake(request, dispatcher, allowDraft75=False, strict=False): - """Performs WebSocket handshake. - - Args: - request: mod_python request. - dispatcher: Dispatcher (dispatch.Dispatcher). - allowDraft75: obsolete argument. ignored. - strict: obsolete argument. ignored. - - Handshaker will add attributes such as ws_resource in performing - handshake. - """ - - _LOGGER.debug('Client\'s opening handshake resource: %r', request.uri) - # To print mimetools.Message as escaped one-line string, we converts - # headers_in to dict object. Without conversion, if we use %r, it just - # prints the type and address, and if we use %s, it prints the original - # header string as multiple lines. - # - # Both mimetools.Message and MpTable_Type of mod_python can be - # converted to dict. - # - # mimetools.Message.__str__ returns the original header string. - # dict(mimetools.Message object) returns the map from header names to - # header values. While MpTable_Type doesn't have such __str__ but just - # __repr__ which formats itself as well as dictionary object. - _LOGGER.debug( - 'Client\'s opening handshake headers: %r', dict(request.headers_in)) - - handshakers = [] - handshakers.append( - ('RFC 6455', hybi.Handshaker(request, dispatcher))) - handshakers.append( - ('HyBi 00', hybi00.Handshaker(request, dispatcher))) - - for name, handshaker in handshakers: - _LOGGER.debug('Trying protocol version %s', name) - try: - handshaker.do_handshake() - _LOGGER.info('Established (%s protocol)', name) - return - except HandshakeException, e: - _LOGGER.debug( - 'Failed to complete opening handshake as %s protocol: %r', - name, e) - if e.status: - raise e - except AbortedByUserException, e: - raise - except VersionException, e: - raise - - # TODO(toyoshim): Add a test to cover the case all handshakers fail. - raise HandshakeException( - 'Failed to complete opening handshake for all available protocols', - status=common.HTTP_STATUS_BAD_REQUEST) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/handshake/_base.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/handshake/_base.py deleted file mode 100644 index c993a584b..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/handshake/_base.py +++ /dev/null @@ -1,182 +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. - - -"""Common functions and exceptions used by WebSocket opening handshake -processors. -""" - - -from mod_pywebsocket import common -from mod_pywebsocket import http_header_util - - -class AbortedByUserException(Exception): - """Exception for aborting a connection intentionally. - - If this exception is raised in do_extra_handshake handler, the connection - will be abandoned. No other WebSocket or HTTP(S) handler will be invoked. - - If this exception is raised in transfer_data_handler, the connection will - be closed without closing handshake. No other WebSocket or HTTP(S) handler - will be invoked. - """ - - pass - - -class HandshakeException(Exception): - """This exception will be raised when an error occurred while processing - WebSocket initial handshake. - """ - - def __init__(self, name, status=None): - super(HandshakeException, self).__init__(name) - self.status = status - - -class VersionException(Exception): - """This exception will be raised when a version of client request does not - match with version the server supports. - """ - - def __init__(self, name, supported_versions=''): - """Construct an instance. - - Args: - supported_version: a str object to show supported hybi versions. - (e.g. '8, 13') - """ - super(VersionException, self).__init__(name) - self.supported_versions = supported_versions - - -def get_default_port(is_secure): - if is_secure: - return common.DEFAULT_WEB_SOCKET_SECURE_PORT - else: - return common.DEFAULT_WEB_SOCKET_PORT - - -def validate_subprotocol(subprotocol): - """Validate a value in the Sec-WebSocket-Protocol field. - - See the Section 4.1., 4.2.2., and 4.3. of RFC 6455. - """ - - if not subprotocol: - raise HandshakeException('Invalid subprotocol name: empty') - - # Parameter should be encoded HTTP token. - state = http_header_util.ParsingState(subprotocol) - token = http_header_util.consume_token(state) - rest = http_header_util.peek(state) - # If |rest| is not None, |subprotocol| is not one token or invalid. If - # |rest| is None, |token| must not be None because |subprotocol| is - # concatenation of |token| and |rest| and is not None. - if rest is not None: - raise HandshakeException('Invalid non-token string in subprotocol ' - 'name: %r' % rest) - - -def parse_host_header(request): - fields = request.headers_in[common.HOST_HEADER].split(':', 1) - if len(fields) == 1: - return fields[0], get_default_port(request.is_https()) - try: - return fields[0], int(fields[1]) - except ValueError, e: - raise HandshakeException('Invalid port number format: %r' % e) - - -def format_header(name, value): - return '%s: %s\r\n' % (name, value) - - -def get_mandatory_header(request, key): - value = request.headers_in.get(key) - if value is None: - raise HandshakeException('Header %s is not defined' % key) - return value - - -def validate_mandatory_header(request, key, expected_value, fail_status=None): - value = get_mandatory_header(request, key) - - if value.lower() != expected_value.lower(): - raise HandshakeException( - 'Expected %r for header %s but found %r (case-insensitive)' % - (expected_value, key, value), status=fail_status) - - -def check_request_line(request): - # 5.1 1. The three character UTF-8 string "GET". - # 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte). - if request.method != 'GET': - raise HandshakeException('Method is not GET: %r' % request.method) - - if request.protocol != 'HTTP/1.1': - raise HandshakeException('Version is not HTTP/1.1: %r' % - request.protocol) - - -def parse_token_list(data): - """Parses a header value which follows 1#token and returns parsed elements - as a list of strings. - - Leading LWSes must be trimmed. - """ - - state = http_header_util.ParsingState(data) - - token_list = [] - - while True: - token = http_header_util.consume_token(state) - if token is not None: - token_list.append(token) - - http_header_util.consume_lwses(state) - - if http_header_util.peek(state) is None: - break - - if not http_header_util.consume_string(state, ','): - raise HandshakeException( - 'Expected a comma but found %r' % http_header_util.peek(state)) - - http_header_util.consume_lwses(state) - - if len(token_list) == 0: - raise HandshakeException('No valid token found') - - return token_list - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/handshake/hybi.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/handshake/hybi.py deleted file mode 100644 index 1ad10ea37..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/handshake/hybi.py +++ /dev/null @@ -1,420 +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 the opening handshake processor for the WebSocket -protocol (RFC 6455). - -Specification: -http://tools.ietf.org/html/rfc6455 -""" - - -# Note: request.connection.write is used in this module, even though mod_python -# document says that it should be used only in connection handlers. -# Unfortunately, we have no other options. For example, request.write is not -# suitable because it doesn't allow direct raw bytes writing. - - -import base64 -import logging -import os -import re - -from mod_pywebsocket import common -from mod_pywebsocket.extensions import get_extension_processor -from mod_pywebsocket.extensions import is_compression_extension -from mod_pywebsocket.handshake._base import check_request_line -from mod_pywebsocket.handshake._base import format_header -from mod_pywebsocket.handshake._base import get_mandatory_header -from mod_pywebsocket.handshake._base import HandshakeException -from mod_pywebsocket.handshake._base import parse_token_list -from mod_pywebsocket.handshake._base import validate_mandatory_header -from mod_pywebsocket.handshake._base import validate_subprotocol -from mod_pywebsocket.handshake._base import VersionException -from mod_pywebsocket.stream import Stream -from mod_pywebsocket.stream import StreamOptions -from mod_pywebsocket import util - - -# Used to validate the value in the Sec-WebSocket-Key header strictly. RFC 4648 -# disallows non-zero padding, so the character right before == must be any of -# A, Q, g and w. -_SEC_WEBSOCKET_KEY_REGEX = re.compile('^[+/0-9A-Za-z]{21}[AQgw]==$') - -# Defining aliases for values used frequently. -_VERSION_LATEST = common.VERSION_HYBI_LATEST -_VERSION_LATEST_STRING = str(_VERSION_LATEST) -_SUPPORTED_VERSIONS = [ - _VERSION_LATEST, -] - - -def compute_accept(key): - """Computes value for the Sec-WebSocket-Accept header from value of the - Sec-WebSocket-Key header. - """ - - accept_binary = util.sha1_hash( - key + common.WEBSOCKET_ACCEPT_UUID).digest() - accept = base64.b64encode(accept_binary) - - return (accept, accept_binary) - - -class Handshaker(object): - """Opening handshake processor for the WebSocket protocol (RFC 6455).""" - - 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 during handshake. - """ - - self._logger = util.get_class_logger(self) - - self._request = request - self._dispatcher = dispatcher - - def _validate_connection_header(self): - connection = get_mandatory_header( - self._request, common.CONNECTION_HEADER) - - try: - connection_tokens = parse_token_list(connection) - except HandshakeException, e: - raise HandshakeException( - 'Failed to parse %s: %s' % (common.CONNECTION_HEADER, e)) - - connection_is_valid = False - for token in connection_tokens: - if token.lower() == common.UPGRADE_CONNECTION_TYPE.lower(): - connection_is_valid = True - break - if not connection_is_valid: - raise HandshakeException( - '%s header doesn\'t contain "%s"' % - (common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE)) - - def do_handshake(self): - self._request.ws_close_code = None - self._request.ws_close_reason = None - - # Parsing. - - check_request_line(self._request) - - validate_mandatory_header( - self._request, - common.UPGRADE_HEADER, - common.WEBSOCKET_UPGRADE_TYPE) - - self._validate_connection_header() - - self._request.ws_resource = self._request.uri - - unused_host = get_mandatory_header(self._request, common.HOST_HEADER) - - self._request.ws_version = self._check_version() - - try: - self._get_origin() - self._set_protocol() - self._parse_extensions() - - # Key validation, response generation. - - key = self._get_key() - (accept, accept_binary) = compute_accept(key) - self._logger.debug( - '%s: %r (%s)', - common.SEC_WEBSOCKET_ACCEPT_HEADER, - accept, - util.hexify(accept_binary)) - - self._logger.debug('Protocol version is RFC 6455') - - # Setup extension processors. - - processors = [] - if self._request.ws_requested_extensions is not None: - for extension_request in self._request.ws_requested_extensions: - processor = get_extension_processor(extension_request) - # Unknown extension requests are just ignored. - if processor is not None: - processors.append(processor) - self._request.ws_extension_processors = processors - - # List of extra headers. The extra handshake handler may add header - # data as name/value pairs to this list and pywebsocket appends - # them to the WebSocket handshake. - self._request.extra_headers = [] - - # Extra handshake handler may modify/remove processors. - self._dispatcher.do_extra_handshake(self._request) - processors = filter(lambda processor: processor is not None, - self._request.ws_extension_processors) - - # Ask each processor if there are extensions on the request which - # cannot co-exist. When processor decided other processors cannot - # co-exist with it, the processor marks them (or itself) as - # "inactive". The first extension processor has the right to - # make the final call. - for processor in reversed(processors): - if processor.is_active(): - processor.check_consistency_with_other_processors( - processors) - processors = filter(lambda processor: processor.is_active(), - processors) - - accepted_extensions = [] - - # We need to take into account of mux extension here. - # If mux extension exists: - # - Remove processors of extensions for logical channel, - # which are processors located before the mux processor - # - Pass extension requests for logical channel to mux processor - # - Attach the mux processor to the request. It will be referred - # by dispatcher to see whether the dispatcher should use mux - # handler or not. - mux_index = -1 - for i, processor in enumerate(processors): - if processor.name() == common.MUX_EXTENSION: - mux_index = i - break - if mux_index >= 0: - logical_channel_extensions = [] - for processor in processors[:mux_index]: - logical_channel_extensions.append(processor.request()) - processor.set_active(False) - self._request.mux_processor = processors[mux_index] - self._request.mux_processor.set_extensions( - logical_channel_extensions) - processors = filter(lambda processor: processor.is_active(), - processors) - - stream_options = StreamOptions() - - for index, processor in enumerate(processors): - if not processor.is_active(): - continue - - extension_response = processor.get_extension_response() - if extension_response is None: - # Rejected. - continue - - accepted_extensions.append(extension_response) - - processor.setup_stream_options(stream_options) - - if not is_compression_extension(processor.name()): - continue - - # Inactivate all of the following compression extensions. - for j in xrange(index + 1, len(processors)): - if is_compression_extension(processors[j].name()): - processors[j].set_active(False) - - if len(accepted_extensions) > 0: - self._request.ws_extensions = accepted_extensions - self._logger.debug( - 'Extensions accepted: %r', - map(common.ExtensionParameter.name, accepted_extensions)) - else: - self._request.ws_extensions = None - - self._request.ws_stream = self._create_stream(stream_options) - - if self._request.ws_requested_protocols is not None: - if self._request.ws_protocol is None: - raise HandshakeException( - 'do_extra_handshake must choose one subprotocol from ' - 'ws_requested_protocols and set it to ws_protocol') - validate_subprotocol(self._request.ws_protocol) - - self._logger.debug( - 'Subprotocol accepted: %r', - self._request.ws_protocol) - else: - if self._request.ws_protocol is not None: - raise HandshakeException( - 'ws_protocol must be None when the client didn\'t ' - 'request any subprotocol') - - self._send_handshake(accept) - except HandshakeException, e: - if not e.status: - # Fallback to 400 bad request by default. - e.status = common.HTTP_STATUS_BAD_REQUEST - raise e - - def _get_origin(self): - origin_header = common.ORIGIN_HEADER - origin = self._request.headers_in.get(origin_header) - if origin is None: - self._logger.debug('Client request does not have origin header') - self._request.ws_origin = origin - - def _check_version(self): - version = get_mandatory_header(self._request, - common.SEC_WEBSOCKET_VERSION_HEADER) - if version == _VERSION_LATEST_STRING: - return _VERSION_LATEST - - if version.find(',') >= 0: - raise HandshakeException( - 'Multiple versions (%r) are not allowed for header %s' % - (version, common.SEC_WEBSOCKET_VERSION_HEADER), - status=common.HTTP_STATUS_BAD_REQUEST) - raise VersionException( - 'Unsupported version %r for header %s' % - (version, common.SEC_WEBSOCKET_VERSION_HEADER), - supported_versions=', '.join(map(str, _SUPPORTED_VERSIONS))) - - def _set_protocol(self): - self._request.ws_protocol = None - - protocol_header = self._request.headers_in.get( - common.SEC_WEBSOCKET_PROTOCOL_HEADER) - - if protocol_header is None: - self._request.ws_requested_protocols = None - return - - self._request.ws_requested_protocols = parse_token_list( - protocol_header) - self._logger.debug('Subprotocols requested: %r', - self._request.ws_requested_protocols) - - def _parse_extensions(self): - extensions_header = self._request.headers_in.get( - common.SEC_WEBSOCKET_EXTENSIONS_HEADER) - if not extensions_header: - self._request.ws_requested_extensions = None - return - - try: - self._request.ws_requested_extensions = common.parse_extensions( - extensions_header) - except common.ExtensionParsingException, e: - raise HandshakeException( - 'Failed to parse Sec-WebSocket-Extensions header: %r' % e) - - self._logger.debug( - 'Extensions requested: %r', - map(common.ExtensionParameter.name, - self._request.ws_requested_extensions)) - - def _validate_key(self, key): - if key.find(',') >= 0: - raise HandshakeException('Request has multiple %s header lines or ' - 'contains illegal character \',\': %r' % - (common.SEC_WEBSOCKET_KEY_HEADER, key)) - - # Validate - key_is_valid = False - try: - # Validate key by quick regex match before parsing by base64 - # module. Because base64 module skips invalid characters, we have - # to do this in advance to make this server strictly reject illegal - # keys. - if _SEC_WEBSOCKET_KEY_REGEX.match(key): - decoded_key = base64.b64decode(key) - if len(decoded_key) == 16: - key_is_valid = True - except TypeError, e: - pass - - if not key_is_valid: - raise HandshakeException( - 'Illegal value for header %s: %r' % - (common.SEC_WEBSOCKET_KEY_HEADER, key)) - - return decoded_key - - def _get_key(self): - key = get_mandatory_header( - self._request, common.SEC_WEBSOCKET_KEY_HEADER) - - decoded_key = self._validate_key(key) - - self._logger.debug( - '%s: %r (%s)', - common.SEC_WEBSOCKET_KEY_HEADER, - key, - util.hexify(decoded_key)) - - return key - - def _create_stream(self, stream_options): - return Stream(self._request, stream_options) - - def _create_handshake_response(self, accept): - response = [] - - response.append('HTTP/1.1 101 Switching Protocols\r\n') - - # WebSocket headers - response.append(format_header( - common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE)) - response.append(format_header( - common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE)) - response.append(format_header( - common.SEC_WEBSOCKET_ACCEPT_HEADER, accept)) - if self._request.ws_protocol is not None: - response.append(format_header( - 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(format_header( - common.SEC_WEBSOCKET_EXTENSIONS_HEADER, - common.format_extensions(self._request.ws_extensions))) - - # Headers not specific for WebSocket - for name, value in self._request.extra_headers: - response.append(format_header(name, value)) - - response.append('\r\n') - - return ''.join(response) - - def _send_handshake(self, accept): - raw_response = self._create_handshake_response(accept) - 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/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/handshake/hybi00.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/handshake/hybi00.py deleted file mode 100644 index 8757717a6..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/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/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/headerparserhandler.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/headerparserhandler.py deleted file mode 100644 index c244421cf..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/headerparserhandler.py +++ /dev/null @@ -1,254 +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): - 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(): - _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, 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, 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, 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, e: - request.log_error('mod_pywebsocket: Aborted: %s' % e, apache.APLOG_INFO) - except Exception, 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/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/http_header_util.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/http_header_util.py deleted file mode 100644 index b77465393..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/http_header_util.py +++ /dev/null @@ -1,263 +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. - - -"""Utilities for parsing and formatting headers that follow the grammar defined -in HTTP RFC http://www.ietf.org/rfc/rfc2616.txt. -""" - - -import urlparse - - -_SEPARATORS = '()<>@,;:\\"/[]?={} \t' - - -def _is_char(c): - """Returns true iff c is in CHAR as specified in HTTP RFC.""" - - return ord(c) <= 127 - - -def _is_ctl(c): - """Returns true iff c is in CTL as specified in HTTP RFC.""" - - return ord(c) <= 31 or ord(c) == 127 - - -class ParsingState(object): - - def __init__(self, data): - self.data = data - self.head = 0 - - -def peek(state, pos=0): - """Peeks the character at pos from the head of data.""" - - if state.head + pos >= len(state.data): - return None - - return state.data[state.head + pos] - - -def consume(state, amount=1): - """Consumes specified amount of bytes from the head and returns the - consumed bytes. If there's not enough bytes to consume, returns None. - """ - - if state.head + amount > len(state.data): - return None - - result = state.data[state.head:state.head + amount] - state.head = state.head + amount - return result - - -def consume_string(state, expected): - """Given a parsing state and a expected string, consumes the string from - the head. Returns True if consumed successfully. Otherwise, returns - False. - """ - - pos = 0 - - for c in expected: - if c != peek(state, pos): - return False - pos += 1 - - consume(state, pos) - return True - - -def consume_lws(state): - """Consumes a LWS from the head. Returns True if any LWS is consumed. - Otherwise, returns False. - - LWS = [CRLF] 1*( SP | HT ) - """ - - original_head = state.head - - consume_string(state, '\r\n') - - pos = 0 - - while True: - c = peek(state, pos) - if c == ' ' or c == '\t': - pos += 1 - else: - if pos == 0: - state.head = original_head - return False - else: - consume(state, pos) - return True - - -def consume_lwses(state): - """Consumes *LWS from the head.""" - - while consume_lws(state): - pass - - -def consume_token(state): - """Consumes a token from the head. Returns the token or None if no token - was found. - """ - - pos = 0 - - while True: - c = peek(state, pos) - if c is None or c in _SEPARATORS or _is_ctl(c) or not _is_char(c): - if pos == 0: - return None - - return consume(state, pos) - else: - pos += 1 - - -def consume_token_or_quoted_string(state): - """Consumes a token or a quoted-string, and returns the token or unquoted - string. If no token or quoted-string was found, returns None. - """ - - original_head = state.head - - if not consume_string(state, '"'): - return consume_token(state) - - result = [] - - expect_quoted_pair = False - - while True: - if not expect_quoted_pair and consume_lws(state): - result.append(' ') - continue - - c = consume(state) - if c is None: - # quoted-string is not enclosed with double quotation - state.head = original_head - return None - elif expect_quoted_pair: - expect_quoted_pair = False - if _is_char(c): - result.append(c) - else: - # Non CHAR character found in quoted-pair - state.head = original_head - return None - elif c == '\\': - expect_quoted_pair = True - elif c == '"': - return ''.join(result) - elif _is_ctl(c): - # Invalid character %r found in qdtext - state.head = original_head - return None - else: - result.append(c) - - -def quote_if_necessary(s): - """Quotes arbitrary string into quoted-string.""" - - quote = False - if s == '': - return '""' - - result = [] - for c in s: - if c == '"' or c in _SEPARATORS or _is_ctl(c) or not _is_char(c): - quote = True - - if c == '"' or _is_ctl(c): - result.append('\\' + c) - else: - result.append(c) - - if quote: - return '"' + ''.join(result) + '"' - else: - return ''.join(result) - - -def parse_uri(uri): - """Parse absolute URI then return host, port and resource.""" - - parsed = urlparse.urlsplit(uri) - if parsed.scheme != 'wss' and parsed.scheme != 'ws': - # |uri| must be a relative URI. - # TODO(toyoshim): Should validate |uri|. - return None, None, uri - - if parsed.hostname is None: - return None, None, None - - port = None - try: - port = parsed.port - except ValueError, e: - # port property cause ValueError on invalid null port description like - # 'ws://host:/path'. - return None, None, None - - if port is None: - if parsed.scheme == 'ws': - port = 80 - else: - port = 443 - - path = parsed.path - if not path: - path += '/' - if parsed.query: - path += '?' + parsed.query - if parsed.fragment: - path += '#' + parsed.fragment - - return parsed.hostname, port, path - - -try: - urlparse.uses_netloc.index('ws') -except ValueError, e: - # urlparse in Python2.5.1 doesn't have 'ws' and 'wss' entries. - urlparse.uses_netloc.append('ws') - urlparse.uses_netloc.append('wss') - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/memorizingfile.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/memorizingfile.py deleted file mode 100644 index 4d4cd9585..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/memorizingfile.py +++ /dev/null @@ -1,99 +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. - - -"""Memorizing file. - -A memorizing file wraps a file and memorizes lines read by readline. -""" - - -import sys - - -class MemorizingFile(object): - """MemorizingFile wraps a file and memorizes lines read by readline. - - Note that data read by other methods are not memorized. This behavior - is good enough for memorizing lines SimpleHTTPServer reads before - the control reaches WebSocketRequestHandler. - """ - - def __init__(self, file_, max_memorized_lines=sys.maxint): - """Construct an instance. - - Args: - file_: the file object to wrap. - max_memorized_lines: the maximum number of lines to memorize. - Only the first max_memorized_lines are memorized. - Default: sys.maxint. - """ - - self._file = file_ - self._memorized_lines = [] - self._max_memorized_lines = max_memorized_lines - self._buffered = False - self._buffered_line = None - - def __getattribute__(self, name): - if name in ('_file', '_memorized_lines', '_max_memorized_lines', - '_buffered', '_buffered_line', 'readline', - 'get_memorized_lines'): - return object.__getattribute__(self, name) - return self._file.__getattribute__(name) - - def readline(self, size=-1): - """Override file.readline and memorize the line read. - - Note that even if size is specified and smaller than actual size, - the whole line will be read out from underlying file object by - subsequent readline calls. - """ - - if self._buffered: - line = self._buffered_line - self._buffered = False - else: - line = self._file.readline() - if line and len(self._memorized_lines) < self._max_memorized_lines: - self._memorized_lines.append(line) - if size >= 0 and size < len(line): - self._buffered = True - self._buffered_line = line[size:] - return line[:size] - return line - - def get_memorized_lines(self): - """Get lines memorized so far.""" - return self._memorized_lines - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/msgutil.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/msgutil.py deleted file mode 100644 index 4c1a0114b..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/msgutil.py +++ /dev/null @@ -1,219 +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. - - -"""Message related utilities. - -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 Queue -import threading - - -# Export Exception symbols from msgutil for backward compatibility -from mod_pywebsocket._stream_base import ConnectionTerminatedException -from mod_pywebsocket._stream_base import InvalidFrameException -from mod_pywebsocket._stream_base import BadOperationException -from mod_pywebsocket._stream_base import UnsupportedFrameException - - -# An API for handler to send/receive WebSocket messages. -def close_connection(request): - """Close connection. - - Args: - request: mod_python request. - """ - request.ws_stream.close_connection() - - -def send_message(request, payload_data, end=True, binary=False): - """Send a message (or part of a message). - - Args: - request: mod_python request. - payload_data: unicode text or str binary to send. - end: True to terminate a message. - False to send payload_data as part of a message that is to be - terminated by next or later send_message call with end=True. - binary: send payload_data as binary frame(s). - Raises: - BadOperationException: when server already terminated. - """ - request.ws_stream.send_message(payload_data, end, binary) - - -def receive_message(request): - """Receive a WebSocket frame and return its payload as a text in - unicode or a binary in str. - - Args: - request: mod_python request. - Raises: - InvalidFrameException: when client send invalid frame. - UnsupportedFrameException: when client send unsupported frame e.g. some - of reserved bit is set but no extension can - recognize it. - InvalidUTF8Exception: when client send a text frame containing any - invalid UTF-8 string. - ConnectionTerminatedException: when the connection is closed - unexpectedly. - BadOperationException: when client already terminated. - """ - return request.ws_stream.receive_message() - - -def send_ping(request, body=''): - request.ws_stream.send_ping(body) - - -class MessageReceiver(threading.Thread): - """This class receives messages from the client. - - This class provides three ways to receive messages: blocking, - non-blocking, and via callback. Callback has the highest precedence. - - Note: This class should not be used with the standalone server for wss - because pyOpenSSL used by the server raises a fatal error if the socket - is accessed from multiple threads. - """ - - def __init__(self, request, onmessage=None): - """Construct an instance. - - Args: - request: mod_python request. - onmessage: a function to be called when a message is received. - May be None. If not None, the function is called on - another thread. In that case, MessageReceiver.receive - and MessageReceiver.receive_nowait are useless - because they will never return any messages. - """ - - threading.Thread.__init__(self) - self._request = request - self._queue = Queue.Queue() - self._onmessage = onmessage - self._stop_requested = False - self.setDaemon(True) - self.start() - - def run(self): - try: - while not self._stop_requested: - message = receive_message(self._request) - if self._onmessage: - self._onmessage(message) - else: - self._queue.put(message) - finally: - close_connection(self._request) - - def receive(self): - """ Receive a message from the channel, blocking. - - Returns: - message as a unicode string. - """ - return self._queue.get() - - def receive_nowait(self): - """ Receive a message from the channel, non-blocking. - - Returns: - message as a unicode string if available. None otherwise. - """ - try: - message = self._queue.get_nowait() - except Queue.Empty: - message = None - return message - - def stop(self): - """Request to stop this instance. - - The instance will be stopped after receiving the next message. - This method may not be very useful, but there is no clean way - in Python to forcefully stop a running thread. - """ - self._stop_requested = True - - -class MessageSender(threading.Thread): - """This class sends messages to the client. - - This class provides both synchronous and asynchronous ways to send - messages. - - Note: This class should not be used with the standalone server for wss - because pyOpenSSL used by the server raises a fatal error if the socket - is accessed from multiple threads. - """ - - def __init__(self, request): - """Construct an instance. - - Args: - request: mod_python request. - """ - threading.Thread.__init__(self) - self._request = request - self._queue = Queue.Queue() - self.setDaemon(True) - self.start() - - def run(self): - while True: - message, condition = self._queue.get() - condition.acquire() - send_message(self._request, message) - condition.notify() - condition.release() - - def send(self, message): - """Send a message, blocking.""" - - condition = threading.Condition() - condition.acquire() - self._queue.put((message, condition)) - condition.wait() - - def send_nowait(self, message): - """Send a message, non-blocking.""" - - self._queue.put((message, threading.Condition())) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/mux.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/mux.py deleted file mode 100644 index 76334685b..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/mux.py +++ /dev/null @@ -1,1889 +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=''): - 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): - """Reads 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): - """Reads 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): - """Reads data that consists of followings: - - 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, 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, 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, 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, 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, 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, e: - raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK, - str(e)) - return control_block - - def read_control_blocks(self): - """Reads 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): - """Returns 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): - """Constructs 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): - """Mimics 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): - """Constructs 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): - """Gets memorized lines. Not supported.""" - - raise MuxUnexpectedException('_LogicalConnection does not support ' - 'get_memorized_lines') - - def write(self, data): - """Writes 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): - """Writes 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): - """Appends 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): - """Reads 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): - """Sets 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): - """Holds the result of _InnerMessageBuilder.build(). - """ - - def __init__(self, opcode, payload): - self.opcode = opcode - self.payload = payload - - -class _InnerMessageBuilder(object): - """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): - """Constructs 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, 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): - """Consumes 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): - """Overrides 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): - """Overrides 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): - """Overrides 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, e: - self._logger.debug('%s', e) - return None - - def _send_closing_handshake(self, code, reason): - """Overrides 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=''): - """Overrides 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): - """Overrides 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=''): - """Overrides 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): - """Stops 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): - """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): - """Constructs 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): - """Puts 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, 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, 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): - """Stops 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): - """Constructs 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, e: - self._logger.debug('%s', e) - break - - try: - self._mux_handler.dispatch_message(message) - except PhysicalConnectionError, e: - self._mux_handler.fail_physical_connection( - e.drop_code, e.message) - break - except LogicalChannelError, e: - self._mux_handler.fail_logical_channel( - e.channel_id, e.drop_code, e.message) - except Exception, 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): - """Constructs 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, 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): - """Constructs 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, 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, 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, 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, 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, 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/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/standalone.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/standalone.py deleted file mode 100755 index 24c299eaf..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/standalone.py +++ /dev/null @@ -1,1193 +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 <ws_port>] - [-w <websock_handlers>] - [-d <document_root>] - -<ws_port> is the port number to use for ws:// connection. - -<document_root> is the path to the root directory of HTML files. - -<websock_handlers> is the path to the root directory of WebSocket handlers. -If not specified, <document_root> 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. -""" - -import BaseHTTPServer -import CGIHTTPServer -import SimpleHTTPServer -import SocketServer -import ConfigParser -import base64 -import httplib -import logging -import logging.handlers -import optparse -import os -import re -import select -import socket -import sys -import threading -import time - -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, (err, message): - if err == -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('(\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, 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, 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, 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, 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, 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, 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 httplib.HTTPMessage instead of mimetools.Message. - MessageClass = httplib.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 - - host, port, resource = http_header_util.parse_uri(self.path) - - # Special paths for XMLHttpRequest benchmark - xhr_benchmark_helper_prefix = '/073be001e10950692ccbf3a2ad21c245' - if resource == (xhr_benchmark_helper_prefix + '_send'): - xhr_benchmark_handler = XHRBenchmarkHandler( - self.headers, self.rfile, self.wfile) - xhr_benchmark_handler.do_send() - return False - if resource == (xhr_benchmark_helper_prefix + '_receive'): - xhr_benchmark_handler = XHRBenchmarkHandler( - self.headers, self.rfile, self.wfile) - xhr_benchmark_handler.do_receive() - return False - - 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, 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, 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, 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, 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, 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, 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/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/stream.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/stream.py deleted file mode 100644 index edc533279..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/stream.py +++ /dev/null @@ -1,57 +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/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/util.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/util.py deleted file mode 100644 index d224ae394..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/util.py +++ /dev/null @@ -1,416 +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. - - -"""WebSocket utilities. -""" - - -import array -import errno - -# Import hash classes from a module available and recommended for each Python -# version and re-export those symbol. Use sha and md5 module in Python 2.4, and -# hashlib module in Python 2.6. -try: - import hashlib - md5_hash = hashlib.md5 - sha1_hash = hashlib.sha1 -except ImportError: - import md5 - import sha - md5_hash = md5.md5 - sha1_hash = sha.sha - -import StringIO -import logging -import os -import re -import socket -import traceback -import zlib - -try: - from mod_pywebsocket import fast_masking -except ImportError: - pass - - -def get_stack_trace(): - """Get the current stack trace as string. - - This is needed to support Python 2.3. - TODO: Remove this when we only support Python 2.4 and above. - Use traceback.format_exc instead. - """ - - out = StringIO.StringIO() - traceback.print_exc(file=out) - return out.getvalue() - - -def prepend_message_to_exception(message, exc): - """Prepend message to the exception.""" - - exc.args = (message + str(exc),) - return - - -def __translate_interp(interp, cygwin_path): - """Translate interp program path for Win32 python to run cygwin program - (e.g. perl). Note that it doesn't support path that contains space, - which is typically true for Unix, where #!-script is written. - For Win32 python, cygwin_path is a directory of cygwin binaries. - - Args: - interp: interp command line - cygwin_path: directory name of cygwin binary, or None - Returns: - translated interp command line. - """ - if not cygwin_path: - return interp - m = re.match('^[^ ]*/([^ ]+)( .*)?', interp) - if m: - cmd = os.path.join(cygwin_path, m.group(1)) - return cmd + m.group(2) - return interp - - -def get_script_interp(script_path, cygwin_path=None): - """Gets #!-interpreter command line from the script. - - It also fixes command path. When Cygwin Python is used, e.g. in WebKit, - it could run "/usr/bin/perl -wT hello.pl". - When Win32 Python is used, e.g. in Chromium, it couldn't. So, fix - "/usr/bin/perl" to "<cygwin_path>\perl.exe". - - Args: - script_path: pathname of the script - cygwin_path: directory name of cygwin binary, or None - Returns: - #!-interpreter command line, or None if it is not #!-script. - """ - fp = open(script_path) - line = fp.readline() - fp.close() - m = re.match('^#!(.*)', line) - if m: - return __translate_interp(m.group(1), cygwin_path) - return None - - -def wrap_popen3_for_win(cygwin_path): - """Wrap popen3 to support #!-script on Windows. - - Args: - cygwin_path: path for cygwin binary if command path is needed to be - translated. None if no translation required. - """ - - __orig_popen3 = os.popen3 - - def __wrap_popen3(cmd, mode='t', bufsize=-1): - cmdline = cmd.split(' ') - interp = get_script_interp(cmdline[0], cygwin_path) - if interp: - cmd = interp + ' ' + cmd - return __orig_popen3(cmd, mode, bufsize) - - os.popen3 = __wrap_popen3 - - -def hexify(s): - return ' '.join(map(lambda x: '%02x' % ord(x), s)) - - -def get_class_logger(o): - return logging.getLogger( - '%s.%s' % (o.__class__.__module__, o.__class__.__name__)) - - -class NoopMasker(object): - """A masking object that has the same interface as RepeatedXorMasker but - just returns the string passed in without making any change. - """ - - def __init__(self): - pass - - def mask(self, s): - return s - - -class RepeatedXorMasker(object): - """A masking object that applies XOR on the string given to mask method - with the masking bytes given to the constructor repeatedly. This object - remembers the position in the masking bytes the last mask method call - ended and resumes from that point on the next mask method call. - """ - - def __init__(self, masking_key): - self._masking_key = masking_key - self._masking_key_index = 0 - - def _mask_using_swig(self, s): - masked_data = fast_masking.mask( - s, self._masking_key, self._masking_key_index) - self._masking_key_index = ( - (self._masking_key_index + len(s)) % len(self._masking_key)) - return masked_data - - def _mask_using_array(self, s): - result = array.array('B') - result.fromstring(s) - - # Use temporary local variables to eliminate the cost to access - # attributes - masking_key = map(ord, self._masking_key) - masking_key_size = len(masking_key) - masking_key_index = self._masking_key_index - - for i in xrange(len(result)): - result[i] ^= masking_key[masking_key_index] - masking_key_index = (masking_key_index + 1) % masking_key_size - - self._masking_key_index = masking_key_index - - return result.tostring() - - if 'fast_masking' in globals(): - mask = _mask_using_swig - else: - mask = _mask_using_array - - -# By making wbits option negative, we can suppress CMF/FLG (2 octet) and -# ADLER32 (4 octet) fields of zlib so that we can use zlib module just as -# deflate library. DICTID won't be added as far as we don't set dictionary. -# LZ77 window of 32K will be used for both compression and decompression. -# For decompression, we can just use 32K to cover any windows size. For -# compression, we use 32K so receivers must use 32K. -# -# Compression level is Z_DEFAULT_COMPRESSION. We don't have to match level -# to decode. -# -# See zconf.h, deflate.cc, inflate.cc of zlib library, and zlibmodule.c of -# Python. See also RFC1950 (ZLIB 3.3). - - -class _Deflater(object): - - def __init__(self, window_bits): - self._logger = get_class_logger(self) - - self._compress = zlib.compressobj( - zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -window_bits) - - def compress(self, bytes): - compressed_bytes = self._compress.compress(bytes) - self._logger.debug('Compress input %r', bytes) - self._logger.debug('Compress result %r', compressed_bytes) - return compressed_bytes - - def compress_and_flush(self, bytes): - compressed_bytes = self._compress.compress(bytes) - compressed_bytes += self._compress.flush(zlib.Z_SYNC_FLUSH) - self._logger.debug('Compress input %r', bytes) - self._logger.debug('Compress result %r', compressed_bytes) - return compressed_bytes - - def compress_and_finish(self, bytes): - compressed_bytes = self._compress.compress(bytes) - compressed_bytes += self._compress.flush(zlib.Z_FINISH) - self._logger.debug('Compress input %r', bytes) - self._logger.debug('Compress result %r', compressed_bytes) - return compressed_bytes - - -class _Inflater(object): - - def __init__(self, window_bits): - self._logger = get_class_logger(self) - self._window_bits = window_bits - - self._unconsumed = '' - - self.reset() - - def decompress(self, size): - if not (size == -1 or size > 0): - raise Exception('size must be -1 or positive') - - data = '' - - while True: - if size == -1: - data += self._decompress.decompress(self._unconsumed) - # See Python bug http://bugs.python.org/issue12050 to - # understand why the same code cannot be used for updating - # self._unconsumed for here and else block. - self._unconsumed = '' - else: - data += self._decompress.decompress( - self._unconsumed, size - len(data)) - self._unconsumed = self._decompress.unconsumed_tail - if self._decompress.unused_data: - # Encountered a last block (i.e. a block with BFINAL = 1) and - # found a new stream (unused_data). We cannot use the same - # zlib.Decompress object for the new stream. Create a new - # Decompress object to decompress the new one. - # - # It's fine to ignore unconsumed_tail if unused_data is not - # empty. - self._unconsumed = self._decompress.unused_data - self.reset() - if size >= 0 and len(data) == size: - # data is filled. Don't call decompress again. - break - else: - # Re-invoke Decompress.decompress to try to decompress all - # available bytes before invoking read which blocks until - # any new byte is available. - continue - else: - # Here, since unused_data is empty, even if unconsumed_tail is - # not empty, bytes of requested length are already in data. We - # don't have to "continue" here. - break - - if data: - self._logger.debug('Decompressed %r', data) - return data - - def append(self, data): - self._logger.debug('Appended %r', data) - self._unconsumed += data - - def reset(self): - self._logger.debug('Reset') - self._decompress = zlib.decompressobj(-self._window_bits) - - -# Compresses/decompresses given octets using the method introduced in RFC1979. - - -class _RFC1979Deflater(object): - """A compressor class that applies DEFLATE to given byte sequence and - flushes using the algorithm described in the RFC1979 section 2.1. - """ - - def __init__(self, window_bits, no_context_takeover): - self._deflater = None - if window_bits is None: - window_bits = zlib.MAX_WBITS - self._window_bits = window_bits - self._no_context_takeover = no_context_takeover - - def filter(self, bytes, end=True, bfinal=False): - if self._deflater is None: - self._deflater = _Deflater(self._window_bits) - - if bfinal: - result = self._deflater.compress_and_finish(bytes) - # Add a padding block with BFINAL = 0 and BTYPE = 0. - result = result + chr(0) - self._deflater = None - return result - - result = self._deflater.compress_and_flush(bytes) - if end: - # Strip last 4 octets which is LEN and NLEN field of a - # non-compressed block added for Z_SYNC_FLUSH. - result = result[:-4] - - if self._no_context_takeover and end: - self._deflater = None - - return result - - -class _RFC1979Inflater(object): - """A decompressor class for byte sequence compressed and flushed following - the algorithm described in the RFC1979 section 2.1. - """ - - def __init__(self, window_bits=zlib.MAX_WBITS): - self._inflater = _Inflater(window_bits) - - def filter(self, bytes): - # Restore stripped LEN and NLEN field of a non-compressed block added - # for Z_SYNC_FLUSH. - self._inflater.append(bytes + '\x00\x00\xff\xff') - return self._inflater.decompress(-1) - - -class DeflateSocket(object): - """A wrapper class for socket object to intercept send and recv to perform - deflate compression and decompression transparently. - """ - - # Size of the buffer passed to recv to receive compressed data. - _RECV_SIZE = 4096 - - def __init__(self, socket): - self._socket = socket - - self._logger = get_class_logger(self) - - self._deflater = _Deflater(zlib.MAX_WBITS) - self._inflater = _Inflater(zlib.MAX_WBITS) - - def recv(self, size): - """Receives data from the socket specified on the construction up - to the specified size. Once any data is available, returns it even - if it's smaller than the specified size. - """ - - # TODO(tyoshino): Allow call with size=0. It should block until any - # decompressed data is available. - if size <= 0: - raise Exception('Non-positive size passed') - while True: - data = self._inflater.decompress(size) - if len(data) != 0: - return data - - read_data = self._socket.recv(DeflateSocket._RECV_SIZE) - if not read_data: - return '' - self._inflater.append(read_data) - - def sendall(self, bytes): - self.send(bytes) - - def send(self, bytes): - self._socket.sendall(self._deflater.compress_and_flush(bytes)) - return len(bytes) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/xhr_benchmark_handler.py b/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/xhr_benchmark_handler.py deleted file mode 100644 index 6735c7e2a..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/mod_pywebsocket/xhr_benchmark_handler.py +++ /dev/null @@ -1,109 +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' - '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(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, e: - self._logger.debug('Malformed size parameter: %r', bytes_to_send) - return - self._logger.debug('Requested to send %s bytes', bytes_to_send) - - # 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 - - # Write a header - response_header = ( - 'HTTP/1.1 200 OK\r\n' - 'Content-Type: application/octet-stream\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/testing/web-platform/tests/tools/pywebsocket/src/setup.py b/testing/web-platform/tests/tools/pywebsocket/src/setup.py deleted file mode 100755 index ada8db3e1..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/setup.py +++ /dev/null @@ -1,74 +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. - - -"""Set up script for mod_pywebsocket. -""" - - -from distutils.core import setup, Extension -import sys - - -_PACKAGE_NAME = 'mod_pywebsocket' - -# Build and use a C++ extension for faster masking. SWIG is required. -_USE_FAST_MASKING = False - -if sys.version < '2.3': - print >> sys.stderr, '%s requires Python 2.3 or later.' % _PACKAGE_NAME - sys.exit(1) - -if _USE_FAST_MASKING: - setup(ext_modules=[ - Extension( - 'mod_pywebsocket/_fast_masking', - ['mod_pywebsocket/fast_masking.i'], - swig_opts=['-c++'])]) - -setup(author='Yuzo Fujishima', - author_email='yuzo@chromium.org', - description='WebSocket extension for Apache HTTP Server.', - long_description=( - 'mod_pywebsocket is an Apache HTTP Server extension for ' - 'the WebSocket Protocol (RFC 6455). ' - 'See mod_pywebsocket/__init__.py for more detail.'), - license='See COPYING', - name=_PACKAGE_NAME, - packages=[_PACKAGE_NAME, _PACKAGE_NAME + '.handshake'], - url='http://code.google.com/p/pywebsocket/', - # See the source of distutils.version, distutils.versionpredicate and - # distutils.dist to understand how to name version numbers. - version='0.7.9', - ) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/__init__.py b/testing/web-platform/tests/tools/pywebsocket/src/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/__init__.py +++ /dev/null diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/cert/cacert.pem b/testing/web-platform/tests/tools/pywebsocket/src/test/cert/cacert.pem deleted file mode 100644 index 4dadae121..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/cert/cacert.pem +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICvDCCAiWgAwIBAgIJAKqVghkGF1rSMA0GCSqGSIb3DQEBBQUAMEkxCzAJBgNV -BAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEUMBIGA1UEChMLcHl3ZWJzb2NrZXQxFDAS -BgNVBAMTC3B5d2Vic29ja2V0MB4XDTEyMDYwNjA3MjQzM1oXDTM5MTAyMzA3MjQz -M1owSTELMAkGA1UEBhMCSlAxDjAMBgNVBAgTBVRva3lvMRQwEgYDVQQKEwtweXdl -YnNvY2tldDEUMBIGA1UEAxMLcHl3ZWJzb2NrZXQwgZ8wDQYJKoZIhvcNAQEBBQAD -gY0AMIGJAoGBAKoSEW2biQxVrMMKdn/8PJzDYiSXDPR9WQbLRRQ1Gm5jkCYiahXW -u2CbTThfPPfi2NHA3I+HlT7gO9yR7RVUvN6ISUzGwXDEq4f4UNqtQOhQaqqK+CZ9 -LO/BhO/YYfNrbSPlYzHUKaT9ese7xO9VzVKLW+qUf2Mjh4/+SzxBDNP7AgMBAAGj -gaswgagwHQYDVR0OBBYEFOsWdxCSuyhwaZeab6BoTho3++bzMHkGA1UdIwRyMHCA -FOsWdxCSuyhwaZeab6BoTho3++bzoU2kSzBJMQswCQYDVQQGEwJKUDEOMAwGA1UE -CBMFVG9reW8xFDASBgNVBAoTC3B5d2Vic29ja2V0MRQwEgYDVQQDEwtweXdlYnNv -Y2tldIIJAKqVghkGF1rSMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEA -gsMI1WEYqNw/jhUIdrTBcCxJ0X6hJvA9ziKANVm1Rs+4P3YDArkQ8bCr6xY+Kw7s -Zp0yE7dM8GMdi+DU6hL3t3E5eMkTS1yZr9WCK4f2RLo+et98selZydpHemF3DJJ3 -gAj8Sx4LBaG8Cb/WnEMPv3MxG3fBE5favF6V4jU07hQ= ------END CERTIFICATE----- diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/cert/cert.pem b/testing/web-platform/tests/tools/pywebsocket/src/test/cert/cert.pem deleted file mode 100644 index 25379a72b..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/cert/cert.pem +++ /dev/null @@ -1,61 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 1 (0x1) - Signature Algorithm: sha1WithRSAEncryption - Issuer: C=JP, ST=Tokyo, O=pywebsocket, CN=pywebsocket - Validity - Not Before: Jun 6 07:25:08 2012 GMT - Not After : Oct 23 07:25:08 2039 GMT - Subject: C=JP, ST=Tokyo, O=pywebsocket, CN=pywebsocket - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public Key: (1024 bit) - Modulus (1024 bit): - 00:de:10:ce:3a:5a:04:a4:1c:29:93:5c:23:82:1a: - f2:06:01:e6:2b:a4:0f:dd:77:49:76:89:03:a2:21: - de:04:75:c6:e2:dd:fb:35:27:3a:a2:92:8e:12:62: - 2b:3e:1f:f4:78:df:b6:94:cb:27:d6:cb:d6:37:d7: - 5c:08:f0:09:3e:c9:ce:24:2d:00:c9:df:4a:e0:99: - e5:fb:23:a9:e2:d6:c9:3d:96:fa:01:88:de:5a:89: - b0:cf:03:67:6f:04:86:1d:ef:62:1c:55:a9:07:9a: - 2e:66:2a:73:5b:4c:62:03:f9:82:83:db:68:bf:b8: - 4b:0b:8b:93:11:b8:54:73:7b - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: - CA:FALSE - Netscape Cert Type: - SSL Server - Netscape Comment: - OpenSSL Generated Certificate - X509v3 Subject Key Identifier: - 82:A1:73:8B:16:0C:7C:E4:D3:46:95:13:95:1A:32:C1:84:E9:06:00 - X509v3 Authority Key Identifier: - keyid:EB:16:77:10:92:BB:28:70:69:97:9A:6F:A0:68:4E:1A:37:FB:E6:F3 - - Signature Algorithm: sha1WithRSAEncryption - 6b:b3:46:29:02:df:b0:c8:8e:c4:d7:7f:a0:1e:0d:1a:eb:2f: - df:d1:48:57:36:5f:95:8c:1b:f0:51:d6:52:e7:8d:84:3b:9f: - d8:ed:22:9c:aa:bd:ee:9b:90:1d:84:a3:4c:0b:cb:eb:64:73: - ba:f7:15:ce:da:5f:db:8b:15:07:a6:28:7f:b9:8c:11:9b:64: - d3:f1:be:52:4f:c3:d8:58:fe:de:56:63:63:3b:51:ed:a7:81: - f9:05:51:70:63:32:09:0e:94:7e:05:fe:a1:56:18:34:98:d5: - 99:1e:4e:27:38:89:90:6a:e5:ce:60:35:01:f5:de:34:60:b1: - cb:ae ------BEGIN CERTIFICATE----- -MIICmDCCAgGgAwIBAgIBATANBgkqhkiG9w0BAQUFADBJMQswCQYDVQQGEwJKUDEO -MAwGA1UECBMFVG9reW8xFDASBgNVBAoTC3B5d2Vic29ja2V0MRQwEgYDVQQDEwtw -eXdlYnNvY2tldDAeFw0xMjA2MDYwNzI1MDhaFw0zOTEwMjMwNzI1MDhaMEkxCzAJ -BgNVBAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEUMBIGA1UEChMLcHl3ZWJzb2NrZXQx -FDASBgNVBAMTC3B5d2Vic29ja2V0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB -gQDeEM46WgSkHCmTXCOCGvIGAeYrpA/dd0l2iQOiId4Edcbi3fs1Jzqiko4SYis+ -H/R437aUyyfWy9Y311wI8Ak+yc4kLQDJ30rgmeX7I6ni1sk9lvoBiN5aibDPA2dv -BIYd72IcVakHmi5mKnNbTGID+YKD22i/uEsLi5MRuFRzewIDAQABo4GPMIGMMAkG -A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMCwGCWCGSAGG+EIBDQQfFh1PcGVu -U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUgqFzixYMfOTTRpUT -lRoywYTpBgAwHwYDVR0jBBgwFoAU6xZ3EJK7KHBpl5pvoGhOGjf75vMwDQYJKoZI -hvcNAQEFBQADgYEAa7NGKQLfsMiOxNd/oB4NGusv39FIVzZflYwb8FHWUueNhDuf -2O0inKq97puQHYSjTAvL62RzuvcVztpf24sVB6Yof7mMEZtk0/G+Uk/D2Fj+3lZj -YztR7aeB+QVRcGMyCQ6UfgX+oVYYNJjVmR5OJziJkGrlzmA1AfXeNGCxy64= ------END CERTIFICATE----- diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/cert/client_cert.p12 b/testing/web-platform/tests/tools/pywebsocket/src/test/cert/client_cert.p12 Binary files differdeleted file mode 100644 index 14e139927..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/cert/client_cert.p12 +++ /dev/null diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/cert/key.pem b/testing/web-platform/tests/tools/pywebsocket/src/test/cert/key.pem deleted file mode 100644 index fae858318..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/cert/key.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXgIBAAKBgQDeEM46WgSkHCmTXCOCGvIGAeYrpA/dd0l2iQOiId4Edcbi3fs1 -Jzqiko4SYis+H/R437aUyyfWy9Y311wI8Ak+yc4kLQDJ30rgmeX7I6ni1sk9lvoB -iN5aibDPA2dvBIYd72IcVakHmi5mKnNbTGID+YKD22i/uEsLi5MRuFRzewIDAQAB -AoGBAIuCuV1Vcnb7rm8CwtgZP5XgmY8vSjxTldafa6XvawEYUTP0S77v/1llg1Yv -UIV+I+PQgG9oVoYOl22LoimHS/Z3e1fsot5tDYszGe8/Gkst4oaReSoxvBUa6WXp -QSo7YFCajuHtE+W/gzF+UHbdzzXIDjQZ314LNF5t+4UnsEPBAkEA+girImqWoM2t -3UR8f8oekERwsmEMf9DH5YpH4cvUnvI+kwesC/r2U8Sho++fyEMUNm7aIXGqNLga -ogAM+4NX4QJBAONdSxSay22egTGNoIhLndljWkuOt/9FWj2klf/4QxD4blMJQ5Oq -QdOGAh7nVQjpPLQ5D7CBVAKpGM2CD+QJBtsCQEP2kz35pxPylG3urcC2mfQxBkkW -ZCViBNP58GwJ0bOauTOSBEwFXWuLqTw8aDwxL49UNmqc0N0fpe2fAehj3UECQQCm -FH/DjU8Lw7ybddjNtm6XXPuYNagxz3cbkB4B3FchDleIUDwMoVF0MW9bI5/54mV1 -QDk1tUKortxvQZJaAD4BAkEAhGOHQqPd6bBBoFBvpaLzPJMxwLKrB+Wtkq/QlC72 -ClRiMn2g8SALiIL3BDgGXKcKE/Wy7jo/af/JCzQ/cPqt/A== ------END RSA PRIVATE KEY----- diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/client_for_testing.py b/testing/web-platform/tests/tools/pywebsocket/src/test/client_for_testing.py deleted file mode 100644 index c7f805ee9..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/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 is 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, 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<n> 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, 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, 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/testing/web-platform/tests/tools/pywebsocket/src/test/endtoend_with_external_server.py b/testing/web-platform/tests/tools/pywebsocket/src/test/endtoend_with_external_server.py deleted file mode 100755 index 47f86fdb4..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/endtoend_with_external_server.py +++ /dev/null @@ -1,67 +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. - - -"""Test for end-to-end with external server. - -This test is not run by run_all.py because it requires some preparations. -If you would like to run this test correctly, launch Apache with mod_python -and mod_pywebsocket manually. In addition, you should pass allow_draft75 option -and example path as handler_scan option and Apache's DocumentRoot. -""" - - -import optparse -import sys -import test.test_endtoend -import unittest - - -_DEFAULT_WEB_SOCKET_PORT = 80 - - -class EndToEndTestWithExternalServer(test.test_endtoend.EndToEndTest): - pass - -if __name__ == '__main__': - parser = optparse.OptionParser() - parser.add_option('-p', '--port', dest='port', type='int', - default=_DEFAULT_WEB_SOCKET_PORT, - help='external test server port.') - (options, args) = parser.parse_args() - - test.test_endtoend._use_external_server = True - test.test_endtoend._external_server_port = options.port - - unittest.main(argv=[sys.argv[0]]) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/mock.py b/testing/web-platform/tests/tools/pywebsocket/src/test/mock.py deleted file mode 100644 index 6bffcac48..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/mock.py +++ /dev/null @@ -1,221 +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. - - -"""Mocks for testing. -""" - - -import Queue -import threading - -from mod_pywebsocket import common -from mod_pywebsocket.stream import StreamHixie75 - - -class _MockConnBase(object): - """Base class of mocks for mod_python.apache.mp_conn. - - This enables tests to check what is written to a (mock) mp_conn. - """ - - def __init__(self): - self._write_data = [] - self.remote_addr = 'fake_address' - - def write(self, data): - """Override mod_python.apache.mp_conn.write.""" - - self._write_data.append(data) - - def written_data(self): - """Get bytes written to this mock.""" - - return ''.join(self._write_data) - - -class MockConn(_MockConnBase): - """Mock for mod_python.apache.mp_conn. - - This enables tests to specify what should be read from a (mock) mp_conn as - well as to check what is written to it. - """ - - def __init__(self, read_data): - """Constructs an instance. - - Args: - read_data: bytes that should be returned when read* methods are - called. - """ - - _MockConnBase.__init__(self) - self._read_data = read_data - self._read_pos = 0 - - def readline(self): - """Override mod_python.apache.mp_conn.readline.""" - - if self._read_pos >= len(self._read_data): - return '' - end_index = self._read_data.find('\n', self._read_pos) + 1 - if not end_index: - end_index = len(self._read_data) - return self._read_up_to(end_index) - - def read(self, length): - """Override mod_python.apache.mp_conn.read.""" - - if self._read_pos >= len(self._read_data): - return '' - end_index = min(len(self._read_data), self._read_pos + length) - return self._read_up_to(end_index) - - def _read_up_to(self, end_index): - line = self._read_data[self._read_pos:end_index] - self._read_pos = end_index - return line - - -class MockBlockingConn(_MockConnBase): - """Blocking mock for mod_python.apache.mp_conn. - - This enables tests to specify what should be read from a (mock) mp_conn as - well as to check what is written to it. - Callers of read* methods will block if there is no bytes available. - """ - - def __init__(self): - _MockConnBase.__init__(self) - self._queue = Queue.Queue() - - def readline(self): - """Override mod_python.apache.mp_conn.readline.""" - line = '' - while True: - c = self._queue.get() - line += c - if c == '\n': - return line - - def read(self, length): - """Override mod_python.apache.mp_conn.read.""" - - data = '' - for unused in range(length): - data += self._queue.get() - return data - - def put_bytes(self, bytes): - """Put bytes to be read from this mock. - - Args: - bytes: bytes to be read. - """ - - for byte in bytes: - self._queue.put(byte) - - -class MockTable(dict): - """Mock table. - - This mimics mod_python mp_table. Note that only the methods used by - tests are overridden. - """ - - def __init__(self, copy_from={}): - if isinstance(copy_from, dict): - copy_from = copy_from.items() - for key, value in copy_from: - self.__setitem__(key, value) - - def __getitem__(self, key): - return super(MockTable, self).__getitem__(key.lower()) - - def __setitem__(self, key, value): - super(MockTable, self).__setitem__(key.lower(), value) - - def get(self, key, def_value=None): - return super(MockTable, self).get(key.lower(), def_value) - - -class MockRequest(object): - """Mock request. - - This mimics mod_python request. - """ - - def __init__(self, uri=None, headers_in={}, connection=None, method='GET', - protocol='HTTP/1.1', is_https=False): - """Construct an instance. - - Arguments: - uri: URI of the request. - headers_in: Request headers. - connection: Connection used for the request. - method: request method. - is_https: Whether this request is over SSL. - - See the document of mod_python Request for details. - """ - self.uri = uri - self.unparsed_uri = uri - self.connection = connection - self.method = method - self.protocol = protocol - self.headers_in = MockTable(headers_in) - # self.is_https_ needs to be accessible from tests. To avoid name - # conflict with self.is_https(), it is named as such. - self.is_https_ = is_https - self.ws_stream = StreamHixie75(self, True) - self.ws_close_code = None - self.ws_close_reason = None - self.ws_version = common.VERSION_HYBI00 - self.ws_deflate = False - - def is_https(self): - """Return whether this request is over SSL.""" - return self.is_https_ - - -class MockDispatcher(object): - """Mock for dispatch.Dispatcher.""" - - def __init__(self): - self.do_extra_handshake_called = False - - def do_extra_handshake(self, conn_context): - self.do_extra_handshake_called = True - - def transfer_data(self, conn_context): - pass - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/mux_client_for_testing.py b/testing/web-platform/tests/tools/pywebsocket/src/test/mux_client_for_testing.py deleted file mode 100644 index dd5435a8c..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/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, 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, 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, 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/testing/web-platform/tests/tools/pywebsocket/src/test/run_all.py b/testing/web-platform/tests/tools/pywebsocket/src/test/run_all.py deleted file mode 100755 index 80a5d87d8..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/run_all.py +++ /dev/null @@ -1,89 +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. - - -"""Run all tests in the same directory. - -This suite is expected to be run under pywebsocket's src directory, i.e. the -directory containing mod_pywebsocket, test, etc. - -To change loggin level, please specify --log-level option. - python test/run_test.py --log-level debug - -To pass any option to unittest module, please specify options after '--'. For -example, run this for making the test runner verbose. - python test/run_test.py --log-level debug -- -v -""" - - -import logging -import optparse -import os -import re -import sys -import unittest - - -_TEST_MODULE_PATTERN = re.compile(r'^(test_.+)\.py$') - - -def _list_test_modules(directory): - module_names = [] - for filename in os.listdir(directory): - match = _TEST_MODULE_PATTERN.search(filename) - if match: - module_names.append(match.group(1)) - return module_names - - -def _suite(): - loader = unittest.TestLoader() - return loader.loadTestsFromNames( - _list_test_modules(os.path.join(os.path.split(__file__)[0], '.'))) - - -if __name__ == '__main__': - parser = optparse.OptionParser() - parser.add_option('--log-level', '--log_level', type='choice', - dest='log_level', default='warning', - choices=['debug', 'info', 'warning', 'warn', 'error', - 'critical']) - options, args = parser.parse_args() - logging.basicConfig( - level=logging.getLevelName(options.log_level.upper()), - format='%(levelname)s %(asctime)s ' - '%(filename)s:%(lineno)d] ' - '%(message)s', - datefmt='%H:%M:%S') - unittest.main(defaultTest='_suite', argv=[sys.argv[0]] + args) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/set_sys_path.py b/testing/web-platform/tests/tools/pywebsocket/src/test/set_sys_path.py deleted file mode 100644 index e3c6db9ea..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/set_sys_path.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2009, 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. - - -"""Configuration for testing. - -Test files should import this module before mod_pywebsocket. -""" - - -import os -import sys - - -# Add the parent directory to sys.path to enable importing mod_pywebsocket. -sys.path.insert(0, os.path.join(os.path.split(__file__)[0], '..')) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/test_dispatch.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_dispatch.py deleted file mode 100755 index 9ca3d4f3a..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/test_dispatch.py +++ /dev/null @@ -1,288 +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 dispatch module.""" - - -import os -import unittest - -import set_sys_path # Update sys.path to locate mod_pywebsocket module. - -from mod_pywebsocket import dispatch -from mod_pywebsocket import handshake -from test import mock - - -_TEST_HANDLERS_DIR = os.path.join( - os.path.split(__file__)[0], 'testdata', 'handlers') - -_TEST_HANDLERS_SUB_DIR = os.path.join(_TEST_HANDLERS_DIR, 'sub') - - -class DispatcherTest(unittest.TestCase): - """A unittest for dispatch module.""" - - def test_normalize_path(self): - self.assertEqual(os.path.abspath('/a/b').replace('\\', '/'), - dispatch._normalize_path('/a/b')) - self.assertEqual(os.path.abspath('/a/b').replace('\\', '/'), - dispatch._normalize_path('\\a\\b')) - self.assertEqual(os.path.abspath('/a/b').replace('\\', '/'), - dispatch._normalize_path('/a/c/../b')) - self.assertEqual(os.path.abspath('abc').replace('\\', '/'), - dispatch._normalize_path('abc')) - - def test_converter(self): - converter = dispatch._create_path_to_resource_converter('/a/b') - # Python built by MSC inserts a drive name like 'C:\' via realpath(). - # Converter Generator expands provided path using realpath() and uses - # the path including a drive name to verify the prefix. - os_root = os.path.realpath('/') - self.assertEqual('/h', converter(os_root + 'a/b/h_wsh.py')) - self.assertEqual('/c/h', converter(os_root + 'a/b/c/h_wsh.py')) - self.assertEqual(None, converter(os_root + 'a/b/h.py')) - self.assertEqual(None, converter('a/b/h_wsh.py')) - - converter = dispatch._create_path_to_resource_converter('a/b') - self.assertEqual('/h', converter(dispatch._normalize_path( - 'a/b/h_wsh.py'))) - - converter = dispatch._create_path_to_resource_converter('/a/b///') - self.assertEqual('/h', converter(os_root + 'a/b/h_wsh.py')) - self.assertEqual('/h', converter(dispatch._normalize_path( - '/a/b/../b/h_wsh.py'))) - - converter = dispatch._create_path_to_resource_converter( - '/a/../a/b/../b/') - self.assertEqual('/h', converter(os_root + 'a/b/h_wsh.py')) - - converter = dispatch._create_path_to_resource_converter(r'\a\b') - self.assertEqual('/h', converter(os_root + r'a\b\h_wsh.py')) - self.assertEqual('/h', converter(os_root + r'a/b/h_wsh.py')) - - def test_enumerate_handler_file_paths(self): - paths = list( - dispatch._enumerate_handler_file_paths(_TEST_HANDLERS_DIR)) - paths.sort() - self.assertEqual(8, len(paths)) - expected_paths = [ - os.path.join(_TEST_HANDLERS_DIR, 'abort_by_user_wsh.py'), - os.path.join(_TEST_HANDLERS_DIR, 'blank_wsh.py'), - os.path.join(_TEST_HANDLERS_DIR, 'origin_check_wsh.py'), - os.path.join(_TEST_HANDLERS_DIR, 'sub', - 'exception_in_transfer_wsh.py'), - os.path.join(_TEST_HANDLERS_DIR, 'sub', 'non_callable_wsh.py'), - os.path.join(_TEST_HANDLERS_DIR, 'sub', 'plain_wsh.py'), - os.path.join(_TEST_HANDLERS_DIR, 'sub', - 'wrong_handshake_sig_wsh.py'), - os.path.join(_TEST_HANDLERS_DIR, 'sub', - 'wrong_transfer_sig_wsh.py'), - ] - for expected, actual in zip(expected_paths, paths): - self.assertEqual(expected, actual) - - def test_source_handler_file(self): - self.assertRaises( - dispatch.DispatchException, dispatch._source_handler_file, '') - self.assertRaises( - dispatch.DispatchException, dispatch._source_handler_file, 'def') - self.assertRaises( - dispatch.DispatchException, dispatch._source_handler_file, '1/0') - self.failUnless(dispatch._source_handler_file( - 'def web_socket_do_extra_handshake(request):pass\n' - 'def web_socket_transfer_data(request):pass\n')) - - def test_source_warnings(self): - dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - warnings = dispatcher.source_warnings() - warnings.sort() - expected_warnings = [ - (os.path.realpath(os.path.join( - _TEST_HANDLERS_DIR, 'blank_wsh.py')) + - ': web_socket_do_extra_handshake is not defined.'), - (os.path.realpath(os.path.join( - _TEST_HANDLERS_DIR, 'sub', 'non_callable_wsh.py')) + - ': web_socket_do_extra_handshake is not callable.'), - (os.path.realpath(os.path.join( - _TEST_HANDLERS_DIR, 'sub', 'wrong_handshake_sig_wsh.py')) + - ': web_socket_do_extra_handshake is not defined.'), - (os.path.realpath(os.path.join( - _TEST_HANDLERS_DIR, 'sub', 'wrong_transfer_sig_wsh.py')) + - ': web_socket_transfer_data is not defined.'), - ] - self.assertEquals(4, len(warnings)) - for expected, actual in zip(expected_warnings, warnings): - self.assertEquals(expected, actual) - - def test_do_extra_handshake(self): - dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - request = mock.MockRequest() - request.ws_resource = '/origin_check' - request.ws_origin = 'http://example.com' - dispatcher.do_extra_handshake(request) # Must not raise exception. - - request.ws_origin = 'http://bad.example.com' - try: - dispatcher.do_extra_handshake(request) - self.fail('Could not catch HandshakeException with 403 status') - except handshake.HandshakeException, e: - self.assertEquals(403, e.status) - except Exception, e: - self.fail('Unexpected exception: %r' % e) - - def test_abort_extra_handshake(self): - dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - request = mock.MockRequest() - request.ws_resource = '/abort_by_user' - self.assertRaises(handshake.AbortedByUserException, - dispatcher.do_extra_handshake, request) - - def test_transfer_data(self): - dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - - request = mock.MockRequest(connection=mock.MockConn('\xff\x00')) - request.ws_resource = '/origin_check' - request.ws_protocol = 'p1' - dispatcher.transfer_data(request) - self.assertEqual('origin_check_wsh.py is called for /origin_check, p1' - '\xff\x00', - request.connection.written_data()) - - request = mock.MockRequest(connection=mock.MockConn('\xff\x00')) - request.ws_resource = '/sub/plain' - request.ws_protocol = None - dispatcher.transfer_data(request) - self.assertEqual('sub/plain_wsh.py is called for /sub/plain, None' - '\xff\x00', - request.connection.written_data()) - - request = mock.MockRequest(connection=mock.MockConn('\xff\x00')) - request.ws_resource = '/sub/plain?' - request.ws_protocol = None - dispatcher.transfer_data(request) - self.assertEqual('sub/plain_wsh.py is called for /sub/plain?, None' - '\xff\x00', - request.connection.written_data()) - - request = mock.MockRequest(connection=mock.MockConn('\xff\x00')) - request.ws_resource = '/sub/plain?q=v' - request.ws_protocol = None - dispatcher.transfer_data(request) - self.assertEqual('sub/plain_wsh.py is called for /sub/plain?q=v, None' - '\xff\x00', - request.connection.written_data()) - - def test_transfer_data_no_handler(self): - dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - for resource in ['/blank', '/sub/non_callable', - '/sub/no_wsh_at_the_end', '/does/not/exist']: - request = mock.MockRequest(connection=mock.MockConn('')) - request.ws_resource = resource - request.ws_protocol = 'p2' - try: - dispatcher.transfer_data(request) - self.fail() - except dispatch.DispatchException, e: - self.failUnless(str(e).find('No handler') != -1) - except Exception: - self.fail() - - def test_transfer_data_handler_exception(self): - dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - request = mock.MockRequest(connection=mock.MockConn('')) - request.ws_resource = '/sub/exception_in_transfer' - request.ws_protocol = 'p3' - try: - dispatcher.transfer_data(request) - self.fail() - except Exception, e: - self.failUnless(str(e).find('Intentional') != -1, - 'Unexpected exception: %s' % e) - - def test_abort_transfer_data(self): - dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - request = mock.MockRequest() - request.ws_resource = '/abort_by_user' - self.assertRaises(handshake.AbortedByUserException, - dispatcher.transfer_data, request) - - def test_scan_dir(self): - disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - self.assertEqual(4, len(disp._handler_suite_map)) - self.failUnless('/origin_check' in disp._handler_suite_map) - self.failUnless( - '/sub/exception_in_transfer' in disp._handler_suite_map) - self.failUnless('/sub/plain' in disp._handler_suite_map) - - def test_scan_sub_dir(self): - disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, _TEST_HANDLERS_SUB_DIR) - self.assertEqual(2, len(disp._handler_suite_map)) - self.failIf('/origin_check' in disp._handler_suite_map) - self.failUnless( - '/sub/exception_in_transfer' in disp._handler_suite_map) - self.failUnless('/sub/plain' in disp._handler_suite_map) - - def test_scan_sub_dir_as_root(self): - disp = dispatch.Dispatcher(_TEST_HANDLERS_SUB_DIR, - _TEST_HANDLERS_SUB_DIR) - self.assertEqual(2, len(disp._handler_suite_map)) - self.failIf('/origin_check' in disp._handler_suite_map) - self.failIf('/sub/exception_in_transfer' in disp._handler_suite_map) - self.failIf('/sub/plain' in disp._handler_suite_map) - self.failUnless('/exception_in_transfer' in disp._handler_suite_map) - self.failUnless('/plain' in disp._handler_suite_map) - - def test_scan_dir_must_under_root(self): - dispatch.Dispatcher('a/b', 'a/b/c') # OK - dispatch.Dispatcher('a/b///', 'a/b') # OK - self.assertRaises(dispatch.DispatchException, - dispatch.Dispatcher, 'a/b/c', 'a/b') - - def test_resource_path_alias(self): - disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - disp.add_resource_path_alias('/', '/origin_check') - self.assertEqual(5, len(disp._handler_suite_map)) - self.failUnless('/origin_check' in disp._handler_suite_map) - self.failUnless( - '/sub/exception_in_transfer' in disp._handler_suite_map) - self.failUnless('/sub/plain' in disp._handler_suite_map) - self.failUnless('/' in disp._handler_suite_map) - self.assertRaises(dispatch.DispatchException, - disp.add_resource_path_alias, '/alias', '/not-exist') - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/test_endtoend.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_endtoend.py deleted file mode 100755 index 5e5cf6157..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/test_endtoend.py +++ /dev/null @@ -1,753 +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. - - -"""End-to-end tests for pywebsocket. Tests standalone.py by default. You -can also test mod_pywebsocket hosted on an Apache server by setting -_use_external_server to True and modifying _external_server_port to point to -the port on which the Apache server is running. -""" - - -import logging -import os -import signal -import socket -import subprocess -import sys -import time -import unittest - -import set_sys_path # Update sys.path to locate mod_pywebsocket module. - -from test import client_for_testing -from test import mux_client_for_testing - - -# Special message that tells the echo server to start closing handshake -_GOODBYE_MESSAGE = 'Goodbye' - -_SERVER_WARMUP_IN_SEC = 0.2 - -# If you want to use external server to run end to end tests, set following -# parameters correctly. -_use_external_server = False -_external_server_port = 0 - - -# Test body functions -def _echo_check_procedure(client): - client.connect() - - client.send_message('test') - client.assert_receive('test') - client.send_message('helloworld') - client.assert_receive('helloworld') - - client.send_close() - client.assert_receive_close() - - client.assert_connection_closed() - - -def _echo_check_procedure_with_binary(client): - client.connect() - - client.send_message('binary', binary=True) - client.assert_receive('binary', binary=True) - client.send_message('\x00\x80\xfe\xff\x00\x80', binary=True) - client.assert_receive('\x00\x80\xfe\xff\x00\x80', binary=True) - - client.send_close() - client.assert_receive_close() - - client.assert_connection_closed() - - -def _echo_check_procedure_with_goodbye(client): - client.connect() - - client.send_message('test') - client.assert_receive('test') - - client.send_message(_GOODBYE_MESSAGE) - client.assert_receive(_GOODBYE_MESSAGE) - - client.assert_receive_close() - client.send_close() - - client.assert_connection_closed() - - -def _echo_check_procedure_with_code_and_reason(client, code, reason): - client.connect() - - client.send_close(code, reason) - client.assert_receive_close(code, reason) - - client.assert_connection_closed() - - -def _unmasked_frame_check_procedure(client): - client.connect() - - client.send_message('test', mask=False) - client.assert_receive_close(client_for_testing.STATUS_PROTOCOL_ERROR, '') - - client.assert_connection_closed() - - -def _mux_echo_check_procedure(mux_client): - mux_client.connect() - mux_client.send_flow_control(1, 1024) - - logical_channel_options = client_for_testing.ClientOptions() - logical_channel_options.server_host = 'localhost' - logical_channel_options.server_port = 80 - logical_channel_options.origin = 'http://localhost' - logical_channel_options.resource = '/echo' - mux_client.add_channel(2, logical_channel_options) - mux_client.send_flow_control(2, 1024) - - mux_client.send_message(2, 'test') - mux_client.assert_receive(2, 'test') - - mux_client.add_channel(3, logical_channel_options) - mux_client.send_flow_control(3, 1024) - - mux_client.send_message(2, 'hello') - mux_client.send_message(3, 'world') - mux_client.assert_receive(2, 'hello') - mux_client.assert_receive(3, 'world') - - # Don't send close message on channel id 1 so that server-initiated - # closing handshake won't occur. - mux_client.send_close(2) - mux_client.send_close(3) - mux_client.assert_receive_close(2) - mux_client.assert_receive_close(3) - - mux_client.send_physical_connection_close() - mux_client.assert_physical_connection_receive_close() - - -class EndToEndTestBase(unittest.TestCase): - """Base class for end-to-end tests that launch pywebsocket standalone - server as a separate process, connect to it using the client_for_testing - module, and check if the server behaves correctly by exchanging opening - handshake and frames over a TCP connection. - """ - - def setUp(self): - self.server_stderr = None - self.top_dir = os.path.join(os.path.split(__file__)[0], '..') - os.putenv('PYTHONPATH', os.path.pathsep.join(sys.path)) - self.standalone_command = os.path.join( - self.top_dir, 'mod_pywebsocket', 'standalone.py') - self.document_root = os.path.join(self.top_dir, 'example') - s = socket.socket() - s.bind(('localhost', 0)) - (_, self.test_port) = s.getsockname() - s.close() - - self._options = client_for_testing.ClientOptions() - self._options.server_host = 'localhost' - self._options.origin = 'http://localhost' - self._options.resource = '/echo' - - # TODO(toyoshim): Eliminate launching a standalone server on using - # external server. - - if _use_external_server: - self._options.server_port = _external_server_port - else: - self._options.server_port = self.test_port - - # TODO(tyoshino): Use tearDown to kill the server. - - def _run_python_command(self, commandline, stdout=None, stderr=None): - return subprocess.Popen([sys.executable] + commandline, close_fds=True, - stdout=stdout, stderr=stderr) - - def _run_server(self): - args = [self.standalone_command, - '-H', 'localhost', - '-V', 'localhost', - '-p', str(self.test_port), - '-P', str(self.test_port), - '-d', self.document_root] - - # Inherit the level set to the root logger by test runner. - root_logger = logging.getLogger() - log_level = root_logger.getEffectiveLevel() - if log_level != logging.NOTSET: - args.append('--log-level') - args.append(logging.getLevelName(log_level).lower()) - - return self._run_python_command(args, - stderr=self.server_stderr) - - def _kill_process(self, pid): - if sys.platform in ('win32', 'cygwin'): - subprocess.call( - ('taskkill.exe', '/f', '/pid', str(pid)), close_fds=True) - else: - os.kill(pid, signal.SIGKILL) - - -class EndToEndHyBiTest(EndToEndTestBase): - def setUp(self): - EndToEndTestBase.setUp(self) - - def _run_test_with_client_options(self, test_function, options): - server = self._run_server() - try: - # TODO(tyoshino): add some logic to poll the server until it - # becomes ready - time.sleep(_SERVER_WARMUP_IN_SEC) - - client = client_for_testing.create_client(options) - try: - test_function(client) - finally: - client.close_socket() - finally: - self._kill_process(server.pid) - - def _run_test(self, test_function): - self._run_test_with_client_options(test_function, self._options) - - def _run_deflate_frame_test(self, test_function): - server = self._run_server() - try: - time.sleep(_SERVER_WARMUP_IN_SEC) - - self._options.enable_deflate_frame() - client = client_for_testing.create_client(self._options) - try: - test_function(client) - finally: - client.close_socket() - finally: - self._kill_process(server.pid) - - def _run_permessage_deflate_test( - self, offer, response_checker, test_function): - server = self._run_server() - try: - time.sleep(_SERVER_WARMUP_IN_SEC) - - self._options.extensions += offer - self._options.check_permessage_deflate = response_checker - client = client_for_testing.create_client(self._options) - - try: - client.connect() - - if test_function is not None: - test_function(client) - - client.assert_connection_closed() - finally: - client.close_socket() - finally: - self._kill_process(server.pid) - - def _run_close_with_code_and_reason_test(self, test_function, code, - reason): - server = self._run_server() - try: - time.sleep(_SERVER_WARMUP_IN_SEC) - - client = client_for_testing.create_client(self._options) - try: - test_function(client, code, reason) - finally: - client.close_socket() - finally: - self._kill_process(server.pid) - - def _run_http_fallback_test(self, options, status): - server = self._run_server() - try: - time.sleep(_SERVER_WARMUP_IN_SEC) - - client = client_for_testing.create_client(options) - try: - client.connect() - self.fail('Could not catch HttpStatusException') - except client_for_testing.HttpStatusException, e: - self.assertEqual(status, e.status) - except Exception, e: - self.fail('Catch unexpected exception') - finally: - client.close_socket() - finally: - self._kill_process(server.pid) - - def _run_mux_test(self, test_function): - server = self._run_server() - try: - time.sleep(_SERVER_WARMUP_IN_SEC) - - client = mux_client_for_testing.MuxClient(self._options) - try: - test_function(client) - finally: - client.close_socket() - finally: - self._kill_process(server.pid) - - def test_echo(self): - self._run_test(_echo_check_procedure) - - def test_echo_binary(self): - self._run_test(_echo_check_procedure_with_binary) - - def test_echo_server_close(self): - self._run_test(_echo_check_procedure_with_goodbye) - - def test_unmasked_frame(self): - self._run_test(_unmasked_frame_check_procedure) - - def test_echo_deflate_frame(self): - self._run_deflate_frame_test(_echo_check_procedure) - - def test_echo_deflate_frame_server_close(self): - self._run_deflate_frame_test( - _echo_check_procedure_with_goodbye) - - def test_echo_permessage_deflate(self): - def test_function(client): - # From the examples in the spec. - compressed_hello = '\xf2\x48\xcd\xc9\xc9\x07\x00' - client._stream.send_data( - compressed_hello, - client_for_testing.OPCODE_TEXT, - rsv1=1) - client._stream.assert_receive_binary( - compressed_hello, - opcode=client_for_testing.OPCODE_TEXT, - rsv1=1) - - client.send_close() - client.assert_receive_close() - - def response_checker(parameter): - self.assertEquals('permessage-deflate', parameter.name()) - self.assertEquals([], parameter.get_parameters()) - - self._run_permessage_deflate_test( - ['permessage-deflate'], - response_checker, - test_function) - - def test_echo_permessage_deflate_two_frames(self): - def test_function(client): - # From the examples in the spec. - client._stream.send_data( - '\xf2\x48\xcd', - client_for_testing.OPCODE_TEXT, - end=False, - rsv1=1) - client._stream.send_data( - '\xc9\xc9\x07\x00', - client_for_testing.OPCODE_TEXT) - client._stream.assert_receive_binary( - '\xf2\x48\xcd\xc9\xc9\x07\x00', - opcode=client_for_testing.OPCODE_TEXT, - rsv1=1) - - client.send_close() - client.assert_receive_close() - - def response_checker(parameter): - self.assertEquals('permessage-deflate', parameter.name()) - self.assertEquals([], parameter.get_parameters()) - - self._run_permessage_deflate_test( - ['permessage-deflate'], - response_checker, - test_function) - - def test_echo_permessage_deflate_two_messages(self): - def test_function(client): - # From the examples in the spec. - client._stream.send_data( - '\xf2\x48\xcd\xc9\xc9\x07\x00', - client_for_testing.OPCODE_TEXT, - rsv1=1) - client._stream.send_data( - '\xf2\x00\x11\x00\x00', - client_for_testing.OPCODE_TEXT, - rsv1=1) - client._stream.assert_receive_binary( - '\xf2\x48\xcd\xc9\xc9\x07\x00', - opcode=client_for_testing.OPCODE_TEXT, - rsv1=1) - client._stream.assert_receive_binary( - '\xf2\x00\x11\x00\x00', - opcode=client_for_testing.OPCODE_TEXT, - rsv1=1) - - client.send_close() - client.assert_receive_close() - - def response_checker(parameter): - self.assertEquals('permessage-deflate', parameter.name()) - self.assertEquals([], parameter.get_parameters()) - - self._run_permessage_deflate_test( - ['permessage-deflate'], - response_checker, - test_function) - - def test_echo_permessage_deflate_two_msgs_server_no_context_takeover(self): - def test_function(client): - # From the examples in the spec. - client._stream.send_data( - '\xf2\x48\xcd\xc9\xc9\x07\x00', - client_for_testing.OPCODE_TEXT, - rsv1=1) - client._stream.send_data( - '\xf2\x00\x11\x00\x00', - client_for_testing.OPCODE_TEXT, - rsv1=1) - client._stream.assert_receive_binary( - '\xf2\x48\xcd\xc9\xc9\x07\x00', - opcode=client_for_testing.OPCODE_TEXT, - rsv1=1) - client._stream.assert_receive_binary( - '\xf2\x48\xcd\xc9\xc9\x07\x00', - opcode=client_for_testing.OPCODE_TEXT, - rsv1=1) - - client.send_close() - client.assert_receive_close() - - def response_checker(parameter): - self.assertEquals('permessage-deflate', parameter.name()) - self.assertEquals([('server_no_context_takeover', None)], - parameter.get_parameters()) - - self._run_permessage_deflate_test( - ['permessage-deflate; server_no_context_takeover'], - response_checker, - test_function) - - def test_echo_permessage_deflate_preference(self): - def test_function(client): - # From the examples in the spec. - compressed_hello = '\xf2\x48\xcd\xc9\xc9\x07\x00' - client._stream.send_data( - compressed_hello, - client_for_testing.OPCODE_TEXT, - rsv1=1) - client._stream.assert_receive_binary( - compressed_hello, - opcode=client_for_testing.OPCODE_TEXT, - rsv1=1) - - client.send_close() - client.assert_receive_close() - - def response_checker(parameter): - self.assertEquals('permessage-deflate', parameter.name()) - self.assertEquals([], parameter.get_parameters()) - - self._run_permessage_deflate_test( - ['permessage-deflate', 'deflate-frame'], - response_checker, - test_function) - - def test_echo_permessage_deflate_with_parameters(self): - def test_function(client): - # From the examples in the spec. - compressed_hello = '\xf2\x48\xcd\xc9\xc9\x07\x00' - client._stream.send_data( - compressed_hello, - client_for_testing.OPCODE_TEXT, - rsv1=1) - client._stream.assert_receive_binary( - compressed_hello, - opcode=client_for_testing.OPCODE_TEXT, - rsv1=1) - - client.send_close() - client.assert_receive_close() - - def response_checker(parameter): - self.assertEquals('permessage-deflate', parameter.name()) - self.assertEquals([('server_max_window_bits', '10'), - ('server_no_context_takeover', None)], - parameter.get_parameters()) - - self._run_permessage_deflate_test( - ['permessage-deflate; server_max_window_bits=10; ' - 'server_no_context_takeover'], - response_checker, - test_function) - - def test_echo_permessage_deflate_with_bad_server_max_window_bits(self): - def test_function(client): - client.send_close() - client.assert_receive_close() - - def response_checker(parameter): - raise Exception('Unexpected acceptance of permessage-deflate') - - self._run_permessage_deflate_test( - ['permessage-deflate; server_max_window_bits=3000000'], - response_checker, - test_function) - - def test_echo_permessage_deflate_with_bad_server_max_window_bits(self): - def test_function(client): - client.send_close() - client.assert_receive_close() - - def response_checker(parameter): - raise Exception('Unexpected acceptance of permessage-deflate') - - self._run_permessage_deflate_test( - ['permessage-deflate; server_max_window_bits=3000000'], - response_checker, - test_function) - - def test_echo_permessage_deflate_with_undefined_parameter(self): - def test_function(client): - client.send_close() - client.assert_receive_close() - - def response_checker(parameter): - raise Exception('Unexpected acceptance of permessage-deflate') - - self._run_permessage_deflate_test( - ['permessage-deflate; foo=bar'], - response_checker, - test_function) - - def test_echo_close_with_code_and_reason(self): - self._options.resource = '/close' - self._run_close_with_code_and_reason_test( - _echo_check_procedure_with_code_and_reason, 3333, 'sunsunsunsun') - - def test_echo_close_with_empty_body(self): - self._options.resource = '/close' - self._run_close_with_code_and_reason_test( - _echo_check_procedure_with_code_and_reason, None, '') - - def test_mux_echo(self): - self._run_mux_test(_mux_echo_check_procedure) - - def test_close_on_protocol_error(self): - """Tests that the server sends a close frame with protocol error status - code when the client sends data with some protocol error. - """ - - def test_function(client): - client.connect() - - # Intermediate frame without any preceding start of fragmentation - # frame. - client.send_frame_of_arbitrary_bytes('\x80\x80', '') - client.assert_receive_close( - client_for_testing.STATUS_PROTOCOL_ERROR) - - self._run_test(test_function) - - def test_close_on_unsupported_frame(self): - """Tests that the server sends a close frame with unsupported operation - status code when the client sends data asking some operation that is - not supported by the server. - """ - - def test_function(client): - client.connect() - - # Text frame with RSV3 bit raised. - client.send_frame_of_arbitrary_bytes('\x91\x80', '') - client.assert_receive_close( - client_for_testing.STATUS_UNSUPPORTED_DATA) - - self._run_test(test_function) - - def test_close_on_invalid_frame(self): - """Tests that the server sends a close frame with invalid frame payload - data status code when the client sends an invalid frame like containing - invalid UTF-8 character. - """ - - def test_function(client): - client.connect() - - # Text frame with invalid UTF-8 string. - client.send_message('\x80', raw=True) - client.assert_receive_close( - client_for_testing.STATUS_INVALID_FRAME_PAYLOAD_DATA) - - self._run_test(test_function) - - def test_close_on_internal_endpoint_error(self): - """Tests that the server sends a close frame with internal endpoint - error status code when the handler does bad operation. - """ - - self._options.resource = '/internal_error' - - def test_function(client): - client.connect() - client.assert_receive_close( - client_for_testing.STATUS_INTERNAL_ENDPOINT_ERROR) - - self._run_test(test_function) - - # TODO(toyoshim): Add tests to verify invalid absolute uri handling like - # host unmatch, port unmatch and invalid port description (':' without port - # number). - - def test_absolute_uri(self): - """Tests absolute uri request.""" - - options = self._options - options.resource = 'ws://localhost:%d/echo' % options.server_port - self._run_test_with_client_options(_echo_check_procedure, options) - - def test_origin_check(self): - """Tests http fallback on origin check fail.""" - - options = self._options - options.resource = '/origin_check' - # Server shows warning message for http 403 fallback. This warning - # message is confusing. Following pipe disposes warning messages. - self.server_stderr = subprocess.PIPE - self._run_http_fallback_test(options, 403) - - def test_version_check(self): - """Tests http fallback on version check fail.""" - - options = self._options - options.version = 99 - self._run_http_fallback_test(options, 400) - - -class EndToEndHyBi00Test(EndToEndTestBase): - def setUp(self): - EndToEndTestBase.setUp(self) - - def _run_test(self, test_function): - server = self._run_server() - try: - time.sleep(_SERVER_WARMUP_IN_SEC) - - client = client_for_testing.create_client_hybi00(self._options) - try: - test_function(client) - finally: - client.close_socket() - finally: - self._kill_process(server.pid) - - def test_echo(self): - self._run_test(_echo_check_procedure) - - def test_echo_server_close(self): - self._run_test(_echo_check_procedure_with_goodbye) - - -class EndToEndTestWithEchoClient(EndToEndTestBase): - def setUp(self): - EndToEndTestBase.setUp(self) - - def _check_example_echo_client_result( - self, expected, stdoutdata, stderrdata): - actual = stdoutdata.decode("utf-8") - if actual != expected: - raise Exception('Unexpected result on example echo client: ' - '%r (expected) vs %r (actual)' % - (expected, actual)) - if stderrdata is not None: - raise Exception('Unexpected error message on example echo ' - 'client: %r' % stderrdata) - - def test_example_echo_client(self): - """Tests that the echo_client.py example can talk with the server.""" - - server = self._run_server() - try: - time.sleep(_SERVER_WARMUP_IN_SEC) - - client_command = os.path.join( - self.top_dir, 'example', 'echo_client.py') - - # Expected output for the default messages. - default_expectation = ('Send: Hello\n' 'Recv: Hello\n' - u'Send: \u65e5\u672c\n' u'Recv: \u65e5\u672c\n' - 'Send close\n' 'Recv ack\n') - - args = [client_command, - '-p', str(self._options.server_port)] - client = self._run_python_command(args, stdout=subprocess.PIPE) - stdoutdata, stderrdata = client.communicate() - self._check_example_echo_client_result( - default_expectation, stdoutdata, stderrdata) - - # Process a big message for which extended payload length is used. - # To handle extended payload length, ws_version attribute will be - # accessed. This test checks that ws_version is correctly set. - big_message = 'a' * 1024 - args = [client_command, - '-p', str(self._options.server_port), - '-m', big_message] - client = self._run_python_command(args, stdout=subprocess.PIPE) - stdoutdata, stderrdata = client.communicate() - expected = ('Send: %s\nRecv: %s\nSend close\nRecv ack\n' % - (big_message, big_message)) - self._check_example_echo_client_result( - expected, stdoutdata, stderrdata) - - # Test the permessage-deflate extension. - args = [client_command, - '-p', str(self._options.server_port), - '--use_permessage_deflate'] - client = self._run_python_command(args, stdout=subprocess.PIPE) - stdoutdata, stderrdata = client.communicate() - self._check_example_echo_client_result( - default_expectation, stdoutdata, stderrdata) - finally: - self._kill_process(server.pid) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/test_extensions.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_extensions.py deleted file mode 100755 index 6c8b1262d..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/test_extensions.py +++ /dev/null @@ -1,360 +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 extensions module.""" - - -import unittest -import zlib - -import set_sys_path # Update sys.path to locate mod_pywebsocket module. - -from mod_pywebsocket import common -from mod_pywebsocket import extensions - - -class ExtensionsTest(unittest.TestCase): - """A unittest for non-class methods in extensions.py""" - - def test_parse_window_bits(self): - self.assertRaises(ValueError, extensions._parse_window_bits, None) - self.assertRaises(ValueError, extensions._parse_window_bits, 'foobar') - self.assertRaises(ValueError, extensions._parse_window_bits, ' 8 ') - self.assertRaises(ValueError, extensions._parse_window_bits, 'a8a') - self.assertRaises(ValueError, extensions._parse_window_bits, '00000') - self.assertRaises(ValueError, extensions._parse_window_bits, '00008') - self.assertRaises(ValueError, extensions._parse_window_bits, '0x8') - - self.assertRaises(ValueError, extensions._parse_window_bits, '9.5') - self.assertRaises(ValueError, extensions._parse_window_bits, '8.0') - - self.assertTrue(extensions._parse_window_bits, '8') - self.assertTrue(extensions._parse_window_bits, '15') - - self.assertRaises(ValueError, extensions._parse_window_bits, '-8') - self.assertRaises(ValueError, extensions._parse_window_bits, '0') - self.assertRaises(ValueError, extensions._parse_window_bits, '7') - - self.assertRaises(ValueError, extensions._parse_window_bits, '16') - self.assertRaises( - ValueError, extensions._parse_window_bits, '10000000') - - -class CompressionMethodParameterParserTest(unittest.TestCase): - """A unittest for _parse_compression_method which parses the compression - method description used by perframe-compression and permessage-compression - extension in their "method" extension parameter. - """ - - def test_parse_method_simple(self): - method_list = extensions._parse_compression_method('foo') - self.assertEqual(1, len(method_list)) - method = method_list[0] - self.assertEqual('foo', method.name()) - self.assertEqual(0, len(method.get_parameters())) - - def test_parse_method_with_parameter(self): - method_list = extensions._parse_compression_method('foo; x; y=10') - self.assertEqual(1, len(method_list)) - method = method_list[0] - self.assertEqual('foo', method.name()) - self.assertEqual(2, len(method.get_parameters())) - self.assertTrue(method.has_parameter('x')) - self.assertEqual(None, method.get_parameter_value('x')) - self.assertTrue(method.has_parameter('y')) - self.assertEqual('10', method.get_parameter_value('y')) - - def test_parse_method_with_quoted_parameter(self): - method_list = extensions._parse_compression_method( - 'foo; x="Hello World"; y=10') - self.assertEqual(1, len(method_list)) - method = method_list[0] - self.assertEqual('foo', method.name()) - self.assertEqual(2, len(method.get_parameters())) - self.assertTrue(method.has_parameter('x')) - self.assertEqual('Hello World', method.get_parameter_value('x')) - self.assertTrue(method.has_parameter('y')) - self.assertEqual('10', method.get_parameter_value('y')) - - def test_parse_method_multiple(self): - method_list = extensions._parse_compression_method('foo, bar') - self.assertEqual(2, len(method_list)) - self.assertEqual('foo', method_list[0].name()) - self.assertEqual(0, len(method_list[0].get_parameters())) - self.assertEqual('bar', method_list[1].name()) - self.assertEqual(0, len(method_list[1].get_parameters())) - - def test_parse_method_multiple_methods_with_quoted_parameter(self): - method_list = extensions._parse_compression_method( - 'foo; x="Hello World", bar; y=10') - self.assertEqual(2, len(method_list)) - self.assertEqual('foo', method_list[0].name()) - self.assertEqual(1, len(method_list[0].get_parameters())) - self.assertTrue(method_list[0].has_parameter('x')) - self.assertEqual('Hello World', - method_list[0].get_parameter_value('x')) - self.assertEqual('bar', method_list[1].name()) - self.assertEqual(1, len(method_list[1].get_parameters())) - self.assertTrue(method_list[1].has_parameter('y')) - self.assertEqual('10', method_list[1].get_parameter_value('y')) - - def test_create_method_desc_simple(self): - params = common.ExtensionParameter('foo') - desc = extensions._create_accepted_method_desc('foo', - params.get_parameters()) - self.assertEqual('foo', desc) - - def test_create_method_desc_with_parameters(self): - params = common.ExtensionParameter('foo') - params.add_parameter('x', 'Hello, World') - params.add_parameter('y', '10') - desc = extensions._create_accepted_method_desc('foo', - params.get_parameters()) - self.assertEqual('foo; x="Hello, World"; y=10', desc) - - -class DeflateFrameExtensionProcessorParsingTest(unittest.TestCase): - """A unittest for checking that DeflateFrameExtensionProcessor parses given - extension parameter correctly. - """ - - def test_registry(self): - processor = extensions.get_extension_processor( - common.ExtensionParameter('deflate-frame')) - self.assertIsInstance(processor, - extensions.DeflateFrameExtensionProcessor) - - processor = extensions.get_extension_processor( - common.ExtensionParameter('x-webkit-deflate-frame')) - self.assertIsInstance(processor, - extensions.DeflateFrameExtensionProcessor) - - def test_minimal_offer(self): - processor = extensions.DeflateFrameExtensionProcessor( - common.ExtensionParameter('perframe-deflate')) - - response = processor.get_extension_response() - self.assertEqual('perframe-deflate', response.name()) - self.assertEqual(0, len(response.get_parameters())) - - self.assertEqual(zlib.MAX_WBITS, - processor._rfc1979_deflater._window_bits) - self.assertFalse(processor._rfc1979_deflater._no_context_takeover) - - def test_offer_with_max_window_bits(self): - parameter = common.ExtensionParameter('perframe-deflate') - parameter.add_parameter('max_window_bits', '10') - processor = extensions.DeflateFrameExtensionProcessor(parameter) - - response = processor.get_extension_response() - self.assertEqual('perframe-deflate', response.name()) - self.assertEqual(0, len(response.get_parameters())) - - self.assertEqual(10, processor._rfc1979_deflater._window_bits) - - def test_offer_with_out_of_range_max_window_bits(self): - parameter = common.ExtensionParameter('perframe-deflate') - parameter.add_parameter('max_window_bits', '0') - processor = extensions.DeflateFrameExtensionProcessor(parameter) - - self.assertIsNone(processor.get_extension_response()) - - def test_offer_with_max_window_bits_without_value(self): - parameter = common.ExtensionParameter('perframe-deflate') - parameter.add_parameter('max_window_bits', None) - processor = extensions.DeflateFrameExtensionProcessor(parameter) - - self.assertIsNone(processor.get_extension_response()) - - def test_offer_with_no_context_takeover(self): - parameter = common.ExtensionParameter('perframe-deflate') - parameter.add_parameter('no_context_takeover', None) - processor = extensions.DeflateFrameExtensionProcessor(parameter) - - response = processor.get_extension_response() - self.assertEqual('perframe-deflate', response.name()) - self.assertEqual(0, len(response.get_parameters())) - - self.assertTrue(processor._rfc1979_deflater._no_context_takeover) - - def test_offer_with_no_context_takeover_with_value(self): - parameter = common.ExtensionParameter('perframe-deflate') - parameter.add_parameter('no_context_takeover', 'foobar') - processor = extensions.DeflateFrameExtensionProcessor(parameter) - - self.assertIsNone(processor.get_extension_response()) - - def test_offer_with_unknown_parameter(self): - parameter = common.ExtensionParameter('perframe-deflate') - parameter.add_parameter('foo', 'bar') - processor = extensions.DeflateFrameExtensionProcessor(parameter) - - response = processor.get_extension_response() - self.assertEqual('perframe-deflate', response.name()) - self.assertEqual(0, len(response.get_parameters())) - - -class PerMessageDeflateExtensionProcessorParsingTest(unittest.TestCase): - """A unittest for checking that PerMessageDeflateExtensionProcessor parses - given extension parameter correctly. - """ - - def test_registry(self): - processor = extensions.get_extension_processor( - common.ExtensionParameter('permessage-deflate')) - self.assertIsInstance(processor, - extensions.PerMessageDeflateExtensionProcessor) - - def test_minimal_offer(self): - processor = extensions.PerMessageDeflateExtensionProcessor( - common.ExtensionParameter('permessage-deflate')) - - response = processor.get_extension_response() - self.assertEqual('permessage-deflate', response.name()) - self.assertEqual(0, len(response.get_parameters())) - - self.assertEqual(zlib.MAX_WBITS, - processor._rfc1979_deflater._window_bits) - self.assertFalse(processor._rfc1979_deflater._no_context_takeover) - - def test_offer_with_max_window_bits(self): - parameter = common.ExtensionParameter('permessage-deflate') - parameter.add_parameter('server_max_window_bits', '10') - processor = extensions.PerMessageDeflateExtensionProcessor(parameter) - - response = processor.get_extension_response() - self.assertEqual('permessage-deflate', response.name()) - self.assertEqual([('server_max_window_bits', '10')], - response.get_parameters()) - - self.assertEqual(10, processor._rfc1979_deflater._window_bits) - - def test_offer_with_out_of_range_max_window_bits(self): - parameter = common.ExtensionParameter('permessage-deflate') - parameter.add_parameter('server_max_window_bits', '0') - processor = extensions.PerMessageDeflateExtensionProcessor(parameter) - - self.assertIsNone(processor.get_extension_response()) - - def test_offer_with_max_window_bits_without_value(self): - parameter = common.ExtensionParameter('permessage-deflate') - parameter.add_parameter('server_max_window_bits', None) - processor = extensions.PerMessageDeflateExtensionProcessor(parameter) - - self.assertIsNone(processor.get_extension_response()) - - def test_offer_with_no_context_takeover(self): - parameter = common.ExtensionParameter('permessage-deflate') - parameter.add_parameter('server_no_context_takeover', None) - processor = extensions.PerMessageDeflateExtensionProcessor(parameter) - - response = processor.get_extension_response() - self.assertEqual('permessage-deflate', response.name()) - self.assertEqual([('server_no_context_takeover', None)], - response.get_parameters()) - - self.assertTrue(processor._rfc1979_deflater._no_context_takeover) - - def test_offer_with_no_context_takeover_with_value(self): - parameter = common.ExtensionParameter('permessage-deflate') - parameter.add_parameter('server_no_context_takeover', 'foobar') - processor = extensions.PerMessageDeflateExtensionProcessor(parameter) - - self.assertIsNone(processor.get_extension_response()) - - def test_offer_with_unknown_parameter(self): - parameter = common.ExtensionParameter('permessage-deflate') - parameter.add_parameter('foo', 'bar') - processor = extensions.PerMessageDeflateExtensionProcessor(parameter) - - self.assertIsNone(processor.get_extension_response()) - - -class PerMessageDeflateExtensionProcessorBuildingTest(unittest.TestCase): - """A unittest for checking that PerMessageDeflateExtensionProcessor builds - a response based on specified options correctly. - """ - - def test_response_with_max_window_bits(self): - parameter = common.ExtensionParameter('permessage-deflate') - parameter.add_parameter('client_max_window_bits', None) - processor = extensions.PerMessageDeflateExtensionProcessor(parameter) - processor.set_client_max_window_bits(10) - - response = processor.get_extension_response() - self.assertEqual('permessage-deflate', response.name()) - self.assertEqual([('client_max_window_bits', '10')], - response.get_parameters()) - - def test_response_with_max_window_bits_without_client_permission(self): - processor = extensions.PerMessageDeflateExtensionProcessor( - common.ExtensionParameter('permessage-deflate')) - processor.set_client_max_window_bits(10) - - response = processor.get_extension_response() - self.assertIsNone(response) - - def test_response_with_true_for_no_context_takeover(self): - processor = extensions.PerMessageDeflateExtensionProcessor( - common.ExtensionParameter('permessage-deflate')) - - processor.set_client_no_context_takeover(True) - - response = processor.get_extension_response() - self.assertEqual('permessage-deflate', response.name()) - self.assertEqual([('client_no_context_takeover', None)], - response.get_parameters()) - - def test_response_with_false_for_no_context_takeover(self): - processor = extensions.PerMessageDeflateExtensionProcessor( - common.ExtensionParameter('permessage-deflate')) - - processor.set_client_no_context_takeover(False) - - response = processor.get_extension_response() - self.assertEqual('permessage-deflate', response.name()) - self.assertEqual(0, len(response.get_parameters())) - - -class PerMessageCompressExtensionProcessorTest(unittest.TestCase): - def test_registry(self): - processor = extensions.get_extension_processor( - common.ExtensionParameter('permessage-compress')) - self.assertIsInstance(processor, - extensions.PerMessageCompressExtensionProcessor) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/test_handshake.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_handshake.py deleted file mode 100755 index aa78ac05e..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/test_handshake.py +++ /dev/null @@ -1,188 +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 handshake._base module.""" - - -import unittest - -import set_sys_path # Update sys.path to locate mod_pywebsocket module. - -from mod_pywebsocket.common import ExtensionParameter -from mod_pywebsocket.common import ExtensionParsingException -from mod_pywebsocket.common import format_extensions -from mod_pywebsocket.common import parse_extensions -from mod_pywebsocket.handshake._base import HandshakeException -from mod_pywebsocket.handshake._base import validate_subprotocol - - -class ValidateSubprotocolTest(unittest.TestCase): - """A unittest for validate_subprotocol method.""" - - def test_validate_subprotocol(self): - # Should succeed. - validate_subprotocol('sample') - validate_subprotocol('Sample') - validate_subprotocol('sample\x7eprotocol') - - # Should fail. - self.assertRaises(HandshakeException, - validate_subprotocol, - '') - self.assertRaises(HandshakeException, - validate_subprotocol, - 'sample\x09protocol') - self.assertRaises(HandshakeException, - validate_subprotocol, - 'sample\x19protocol') - self.assertRaises(HandshakeException, - validate_subprotocol, - 'sample\x20protocol') - self.assertRaises(HandshakeException, - validate_subprotocol, - 'sample\x7fprotocol') - self.assertRaises(HandshakeException, - validate_subprotocol, - # "Japan" in Japanese - u'\u65e5\u672c') - - -_TEST_TOKEN_EXTENSION_DATA = [ - ('foo', [('foo', [])]), - ('foo; bar', [('foo', [('bar', None)])]), - ('foo; bar=baz', [('foo', [('bar', 'baz')])]), - ('foo; bar=baz; car=cdr', [('foo', [('bar', 'baz'), ('car', 'cdr')])]), - ('foo; bar=baz, car; cdr', - [('foo', [('bar', 'baz')]), ('car', [('cdr', None)])]), - ('a, b, c, d', - [('a', []), ('b', []), ('c', []), ('d', [])]), - ] - - -_TEST_QUOTED_EXTENSION_DATA = [ - ('foo; bar=""', [('foo', [('bar', '')])]), - ('foo; bar=" baz "', [('foo', [('bar', ' baz ')])]), - ('foo; bar=",baz;"', [('foo', [('bar', ',baz;')])]), - ('foo; bar="\\\r\\\nbaz"', [('foo', [('bar', '\r\nbaz')])]), - ('foo; bar="\\"baz"', [('foo', [('bar', '"baz')])]), - ('foo; bar="\xbbbaz"', [('foo', [('bar', '\xbbbaz')])]), - ] - - -_TEST_REDUNDANT_TOKEN_EXTENSION_DATA = [ - ('foo \t ', [('foo', [])]), - ('foo; \r\n bar', [('foo', [('bar', None)])]), - ('foo; bar=\r\n \r\n baz', [('foo', [('bar', 'baz')])]), - ('foo ;bar = baz ', [('foo', [('bar', 'baz')])]), - ('foo,bar,,baz', [('foo', []), ('bar', []), ('baz', [])]), - ] - - -_TEST_REDUNDANT_QUOTED_EXTENSION_DATA = [ - ('foo; bar="\r\n \r\n baz"', [('foo', [('bar', ' baz')])]), - ] - - -class ExtensionsParserTest(unittest.TestCase): - - def _verify_extension_list(self, expected_list, actual_list): - """Verifies that ExtensionParameter objects in actual_list have the - same members as extension definitions in expected_list. Extension - definition used in this test is a pair of an extension name and a - parameter dictionary. - """ - - self.assertEqual(len(expected_list), len(actual_list)) - for expected, actual in zip(expected_list, actual_list): - (name, parameters) = expected - self.assertEqual(name, actual._name) - self.assertEqual(parameters, actual._parameters) - - def test_parse(self): - for formatted_string, definition in _TEST_TOKEN_EXTENSION_DATA: - self._verify_extension_list( - definition, parse_extensions(formatted_string)) - - def test_parse_quoted_data(self): - for formatted_string, definition in _TEST_QUOTED_EXTENSION_DATA: - self._verify_extension_list( - definition, parse_extensions(formatted_string)) - - def test_parse_redundant_data(self): - for (formatted_string, - definition) in _TEST_REDUNDANT_TOKEN_EXTENSION_DATA: - self._verify_extension_list( - definition, parse_extensions(formatted_string)) - - def test_parse_redundant_quoted_data(self): - for (formatted_string, - definition) in _TEST_REDUNDANT_QUOTED_EXTENSION_DATA: - self._verify_extension_list( - definition, parse_extensions(formatted_string)) - - def test_parse_bad_data(self): - _TEST_BAD_EXTENSION_DATA = [ - ('foo; ; '), - ('foo; a a'), - ('foo foo'), - (',,,'), - ('foo; bar='), - ('foo; bar="hoge'), - ('foo; bar="a\r"'), - ('foo; bar="\\\xff"'), - ('foo; bar=\ra'), - ] - - for formatted_string in _TEST_BAD_EXTENSION_DATA: - self.assertRaises( - ExtensionParsingException, parse_extensions, formatted_string) - - -class FormatExtensionsTest(unittest.TestCase): - - def test_format_extensions(self): - for formatted_string, definitions in _TEST_TOKEN_EXTENSION_DATA: - extensions = [] - for definition in definitions: - (name, parameters) = definition - extension = ExtensionParameter(name) - extension._parameters = parameters - extensions.append(extension) - self.assertEqual( - formatted_string, format_extensions(extensions)) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/test_handshake_hybi.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_handshake_hybi.py deleted file mode 100755 index 6c8713823..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/test_handshake_hybi.py +++ /dev/null @@ -1,534 +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 module.""" - - -import unittest - -import set_sys_path # Update sys.path to locate mod_pywebsocket module. -from mod_pywebsocket import common -from mod_pywebsocket.handshake._base import AbortedByUserException -from mod_pywebsocket.handshake._base import HandshakeException -from mod_pywebsocket.handshake._base import VersionException -from mod_pywebsocket.handshake.hybi import Handshaker - -import mock - - -class RequestDefinition(object): - """A class for holding data for constructing opening handshake strings for - testing the opening handshake processor. - """ - - def __init__(self, method, uri, headers): - self.method = method - self.uri = uri - self.headers = headers - - -def _create_good_request_def(): - return RequestDefinition( - 'GET', '/demo', - {'Host': 'server.example.com', - 'Upgrade': 'websocket', - 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', - 'Sec-WebSocket-Version': '13', - 'Origin': 'http://example.com'}) - - -def _create_request(request_def): - conn = mock.MockConn('') - return mock.MockRequest( - method=request_def.method, - uri=request_def.uri, - headers_in=request_def.headers, - connection=conn) - - -def _create_handshaker(request): - handshaker = Handshaker(request, mock.MockDispatcher()) - return handshaker - - -class SubprotocolChoosingDispatcher(object): - """A dispatcher for testing. This dispatcher sets the i-th subprotocol - of requested ones to ws_protocol where i is given on construction as index - argument. If index is negative, default_value will be set to ws_protocol. - """ - - def __init__(self, index, default_value=None): - self.index = index - self.default_value = default_value - - def do_extra_handshake(self, conn_context): - if self.index >= 0: - conn_context.ws_protocol = conn_context.ws_requested_protocols[ - self.index] - else: - conn_context.ws_protocol = self.default_value - - def transfer_data(self, conn_context): - pass - - -class HandshakeAbortedException(Exception): - pass - - -class AbortingDispatcher(object): - """A dispatcher for testing. This dispatcher raises an exception in - do_extra_handshake to reject the request. - """ - - def do_extra_handshake(self, conn_context): - raise HandshakeAbortedException('An exception to reject the request') - - def transfer_data(self, conn_context): - pass - - -class AbortedByUserDispatcher(object): - """A dispatcher for testing. This dispatcher raises an - AbortedByUserException in do_extra_handshake to reject the request. - """ - - def do_extra_handshake(self, conn_context): - raise AbortedByUserException('An AbortedByUserException to reject the ' - 'request') - - def transfer_data(self, conn_context): - pass - - -_EXPECTED_RESPONSE = ( - 'HTTP/1.1 101 Switching Protocols\r\n' - 'Upgrade: websocket\r\n' - 'Connection: Upgrade\r\n' - 'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n') - - -class HandshakerTest(unittest.TestCase): - """A unittest for draft-ietf-hybi-thewebsocketprotocol-06 and later - handshake processor. - """ - - def test_do_handshake(self): - request = _create_request(_create_good_request_def()) - dispatcher = mock.MockDispatcher() - handshaker = Handshaker(request, dispatcher) - handshaker.do_handshake() - - self.assertTrue(dispatcher.do_extra_handshake_called) - - self.assertEqual( - _EXPECTED_RESPONSE, request.connection.written_data()) - self.assertEqual('/demo', request.ws_resource) - self.assertEqual('http://example.com', request.ws_origin) - self.assertEqual(None, request.ws_protocol) - self.assertEqual(None, request.ws_extensions) - self.assertEqual(common.VERSION_HYBI_LATEST, request.ws_version) - - def test_do_handshake_with_extra_headers(self): - request_def = _create_good_request_def() - # Add headers not related to WebSocket opening handshake. - request_def.headers['FooKey'] = 'BarValue' - request_def.headers['EmptyKey'] = '' - - request = _create_request(request_def) - handshaker = _create_handshaker(request) - handshaker.do_handshake() - self.assertEqual( - _EXPECTED_RESPONSE, request.connection.written_data()) - - def test_do_handshake_with_capitalized_value(self): - request_def = _create_good_request_def() - request_def.headers['upgrade'] = 'WEBSOCKET' - - request = _create_request(request_def) - handshaker = _create_handshaker(request) - handshaker.do_handshake() - self.assertEqual( - _EXPECTED_RESPONSE, request.connection.written_data()) - - request_def = _create_good_request_def() - request_def.headers['Connection'] = 'UPGRADE' - - request = _create_request(request_def) - handshaker = _create_handshaker(request) - handshaker.do_handshake() - self.assertEqual( - _EXPECTED_RESPONSE, request.connection.written_data()) - - def test_do_handshake_with_multiple_connection_values(self): - request_def = _create_good_request_def() - request_def.headers['Connection'] = 'Upgrade, keep-alive, , ' - - request = _create_request(request_def) - handshaker = _create_handshaker(request) - handshaker.do_handshake() - self.assertEqual( - _EXPECTED_RESPONSE, request.connection.written_data()) - - def test_aborting_handshake(self): - handshaker = Handshaker( - _create_request(_create_good_request_def()), - AbortingDispatcher()) - # do_extra_handshake raises an exception. Check that it's not caught by - # do_handshake. - self.assertRaises(HandshakeAbortedException, handshaker.do_handshake) - - def test_do_handshake_with_protocol(self): - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Protocol'] = 'chat, superchat' - - request = _create_request(request_def) - handshaker = Handshaker(request, SubprotocolChoosingDispatcher(0)) - handshaker.do_handshake() - - EXPECTED_RESPONSE = ( - 'HTTP/1.1 101 Switching Protocols\r\n' - 'Upgrade: websocket\r\n' - 'Connection: Upgrade\r\n' - 'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n' - 'Sec-WebSocket-Protocol: chat\r\n\r\n') - - self.assertEqual(EXPECTED_RESPONSE, request.connection.written_data()) - self.assertEqual('chat', request.ws_protocol) - - def test_do_handshake_protocol_not_in_request_but_in_response(self): - request_def = _create_good_request_def() - request = _create_request(request_def) - handshaker = Handshaker( - request, SubprotocolChoosingDispatcher(-1, 'foobar')) - # No request has been made but ws_protocol is set. HandshakeException - # must be raised. - self.assertRaises(HandshakeException, handshaker.do_handshake) - - def test_do_handshake_with_protocol_no_protocol_selection(self): - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Protocol'] = 'chat, superchat' - - request = _create_request(request_def) - handshaker = _create_handshaker(request) - # ws_protocol is not set. HandshakeException must be raised. - self.assertRaises(HandshakeException, handshaker.do_handshake) - - def test_do_handshake_with_extensions(self): - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Extensions'] = ( - 'permessage-compress; method=deflate, unknown') - - EXPECTED_RESPONSE = ( - 'HTTP/1.1 101 Switching Protocols\r\n' - 'Upgrade: websocket\r\n' - 'Connection: Upgrade\r\n' - 'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n' - 'Sec-WebSocket-Extensions: permessage-compress; method=deflate\r\n' - '\r\n') - - request = _create_request(request_def) - handshaker = _create_handshaker(request) - handshaker.do_handshake() - self.assertEqual(EXPECTED_RESPONSE, request.connection.written_data()) - self.assertEqual(1, len(request.ws_extensions)) - extension = request.ws_extensions[0] - self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, - extension.name()) - self.assertEqual(['method'], extension.get_parameter_names()) - self.assertEqual('deflate', extension.get_parameter_value('method')) - self.assertEqual(1, len(request.ws_extension_processors)) - self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, - request.ws_extension_processors[0].name()) - - def test_do_handshake_with_permessage_compress(self): - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Extensions'] = ( - 'permessage-compress; method=deflate') - request = _create_request(request_def) - handshaker = _create_handshaker(request) - handshaker.do_handshake() - self.assertEqual(1, len(request.ws_extensions)) - self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, - request.ws_extensions[0].name()) - self.assertEqual(1, len(request.ws_extension_processors)) - self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, - request.ws_extension_processors[0].name()) - - def test_do_handshake_with_quoted_extensions(self): - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Extensions'] = ( - 'permessage-compress; method=deflate, , ' - 'unknown; e = "mc^2"; ma="\r\n \\\rf "; pv=nrt') - - request = _create_request(request_def) - handshaker = _create_handshaker(request) - handshaker.do_handshake() - self.assertEqual(2, len(request.ws_requested_extensions)) - first_extension = request.ws_requested_extensions[0] - self.assertEqual('permessage-compress', first_extension.name()) - self.assertEqual(['method'], first_extension.get_parameter_names()) - self.assertEqual('deflate', - first_extension.get_parameter_value('method')) - second_extension = request.ws_requested_extensions[1] - self.assertEqual('unknown', second_extension.name()) - self.assertEqual( - ['e', 'ma', 'pv'], second_extension.get_parameter_names()) - self.assertEqual('mc^2', second_extension.get_parameter_value('e')) - self.assertEqual(' \rf ', second_extension.get_parameter_value('ma')) - self.assertEqual('nrt', second_extension.get_parameter_value('pv')) - - def test_do_handshake_with_optional_headers(self): - request_def = _create_good_request_def() - request_def.headers['EmptyValue'] = '' - request_def.headers['AKey'] = 'AValue' - - request = _create_request(request_def) - handshaker = _create_handshaker(request) - handshaker.do_handshake() - self.assertEqual( - 'AValue', request.headers_in['AKey']) - self.assertEqual( - '', request.headers_in['EmptyValue']) - - def test_abort_extra_handshake(self): - handshaker = Handshaker( - _create_request(_create_good_request_def()), - AbortedByUserDispatcher()) - # do_extra_handshake raises an AbortedByUserException. Check that it's - # not caught by do_handshake. - self.assertRaises(AbortedByUserException, handshaker.do_handshake) - - def test_do_handshake_with_mux_and_deflate_frame(self): - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Extensions'] = ('%s, %s' % ( - common.MUX_EXTENSION, - common.DEFLATE_FRAME_EXTENSION)) - request = _create_request(request_def) - handshaker = _create_handshaker(request) - handshaker.do_handshake() - # mux should be rejected. - self.assertEqual(1, len(request.ws_extensions)) - self.assertEqual(common.DEFLATE_FRAME_EXTENSION, - request.ws_extensions[0].name()) - self.assertEqual(2, len(request.ws_extension_processors)) - self.assertEqual(common.MUX_EXTENSION, - request.ws_extension_processors[0].name()) - self.assertEqual(common.DEFLATE_FRAME_EXTENSION, - request.ws_extension_processors[1].name()) - self.assertFalse(hasattr(request, 'mux_processor')) - - def test_do_handshake_with_deflate_frame_and_mux(self): - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Extensions'] = ('%s, %s' % ( - common.DEFLATE_FRAME_EXTENSION, - common.MUX_EXTENSION)) - request = _create_request(request_def) - handshaker = _create_handshaker(request) - handshaker.do_handshake() - # mux should be rejected. - self.assertEqual(1, len(request.ws_extensions)) - first_extension = request.ws_extensions[0] - self.assertEqual(common.DEFLATE_FRAME_EXTENSION, - first_extension.name()) - self.assertEqual(2, len(request.ws_extension_processors)) - self.assertEqual(common.DEFLATE_FRAME_EXTENSION, - request.ws_extension_processors[0].name()) - self.assertEqual(common.MUX_EXTENSION, - request.ws_extension_processors[1].name()) - self.assertFalse(hasattr(request, 'mux')) - - def test_do_handshake_with_permessage_compress_and_mux(self): - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Extensions'] = ( - '%s; method=deflate, %s' % ( - common.PERMESSAGE_COMPRESSION_EXTENSION, - common.MUX_EXTENSION)) - request = _create_request(request_def) - handshaker = _create_handshaker(request) - handshaker.do_handshake() - - self.assertEqual(1, len(request.ws_extensions)) - self.assertEqual(common.MUX_EXTENSION, - request.ws_extensions[0].name()) - self.assertEqual(2, len(request.ws_extension_processors)) - self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, - request.ws_extension_processors[0].name()) - self.assertEqual(common.MUX_EXTENSION, - request.ws_extension_processors[1].name()) - self.assertTrue(hasattr(request, 'mux_processor')) - self.assertTrue(request.mux_processor.is_active()) - mux_extensions = request.mux_processor.extensions() - self.assertEqual(1, len(mux_extensions)) - self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, - mux_extensions[0].name()) - - def test_do_handshake_with_mux_and_permessage_compress(self): - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Extensions'] = ( - '%s, %s; method=deflate' % ( - common.MUX_EXTENSION, - common.PERMESSAGE_COMPRESSION_EXTENSION)) - request = _create_request(request_def) - handshaker = _create_handshaker(request) - handshaker.do_handshake() - # mux should be rejected. - self.assertEqual(1, len(request.ws_extensions)) - first_extension = request.ws_extensions[0] - self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, - first_extension.name()) - self.assertEqual(2, len(request.ws_extension_processors)) - self.assertEqual(common.MUX_EXTENSION, - request.ws_extension_processors[0].name()) - self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, - request.ws_extension_processors[1].name()) - self.assertFalse(hasattr(request, 'mux_processor')) - - def test_bad_requests(self): - bad_cases = [ - ('HTTP request', - RequestDefinition( - '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'}), None, True)] - - request_def = _create_good_request_def() - request_def.method = 'POST' - bad_cases.append(('Wrong method', request_def, None, True)) - - request_def = _create_good_request_def() - del request_def.headers['Host'] - bad_cases.append(('Missing Host', request_def, None, True)) - - request_def = _create_good_request_def() - del request_def.headers['Upgrade'] - bad_cases.append(('Missing Upgrade', request_def, None, True)) - - request_def = _create_good_request_def() - request_def.headers['Upgrade'] = 'nonwebsocket' - bad_cases.append(('Wrong Upgrade', request_def, None, True)) - - request_def = _create_good_request_def() - del request_def.headers['Connection'] - bad_cases.append(('Missing Connection', request_def, None, True)) - - request_def = _create_good_request_def() - request_def.headers['Connection'] = 'Downgrade' - bad_cases.append(('Wrong Connection', request_def, None, True)) - - request_def = _create_good_request_def() - del request_def.headers['Sec-WebSocket-Key'] - bad_cases.append(('Missing Sec-WebSocket-Key', request_def, 400, True)) - - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Key'] = ( - 'dGhlIHNhbXBsZSBub25jZQ==garbage') - bad_cases.append(('Wrong Sec-WebSocket-Key (with garbage on the tail)', - request_def, 400, True)) - - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Key'] = 'YQ==' # BASE64 of 'a' - bad_cases.append( - ('Wrong Sec-WebSocket-Key (decoded value is not 16 octets long)', - request_def, 400, True)) - - request_def = _create_good_request_def() - # The last character right before == must be any of A, Q, w and g. - request_def.headers['Sec-WebSocket-Key'] = ( - 'AQIDBAUGBwgJCgsMDQ4PEC==') - bad_cases.append( - ('Wrong Sec-WebSocket-Key (padding bits are not zero)', - request_def, 400, True)) - - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Key'] = ( - 'dGhlIHNhbXBsZSBub25jZQ==,dGhlIHNhbXBsZSBub25jZQ==') - bad_cases.append( - ('Wrong Sec-WebSocket-Key (multiple values)', - request_def, 400, True)) - - request_def = _create_good_request_def() - del request_def.headers['Sec-WebSocket-Version'] - bad_cases.append(('Missing Sec-WebSocket-Version', request_def, None, - True)) - - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Version'] = '3' - bad_cases.append(('Wrong Sec-WebSocket-Version', request_def, None, - False)) - - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Version'] = '13, 13' - bad_cases.append(('Wrong Sec-WebSocket-Version (multiple values)', - request_def, 400, True)) - - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Protocol'] = 'illegal\x09protocol' - bad_cases.append(('Illegal Sec-WebSocket-Protocol', - request_def, 400, True)) - - request_def = _create_good_request_def() - request_def.headers['Sec-WebSocket-Protocol'] = '' - bad_cases.append(('Empty Sec-WebSocket-Protocol', - request_def, 400, True)) - - for (case_name, request_def, expected_status, - expect_handshake_exception) in bad_cases: - request = _create_request(request_def) - handshaker = Handshaker(request, mock.MockDispatcher()) - try: - handshaker.do_handshake() - self.fail('No exception thrown for \'%s\' case' % case_name) - except HandshakeException, e: - self.assertTrue(expect_handshake_exception) - self.assertEqual(expected_status, e.status) - except VersionException, e: - self.assertFalse(expect_handshake_exception) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/test_handshake_hybi00.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_handshake_hybi00.py deleted file mode 100755 index 73f9f27ca..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/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/testing/web-platform/tests/tools/pywebsocket/src/test/test_http_header_util.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_http_header_util.py deleted file mode 100755 index 436dc57c3..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/test_http_header_util.py +++ /dev/null @@ -1,90 +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 http_header_util module.""" - - -import unittest - -from mod_pywebsocket import http_header_util - - -class UnitTest(unittest.TestCase): - """A unittest for http_header_util module.""" - - def test_parse_relative_uri(self): - host, port, resource = http_header_util.parse_uri('/ws/test') - self.assertEqual(None, host) - self.assertEqual(None, port) - self.assertEqual('/ws/test', resource) - - def test_parse_absolute_uri(self): - host, port, resource = http_header_util.parse_uri( - 'ws://localhost:10080/ws/test') - self.assertEqual('localhost', host) - self.assertEqual(10080, port) - self.assertEqual('/ws/test', resource) - - host, port, resource = http_header_util.parse_uri( - 'ws://example.com/ws/test') - self.assertEqual('example.com', host) - self.assertEqual(80, port) - self.assertEqual('/ws/test', resource) - - host, port, resource = http_header_util.parse_uri( - 'wss://example.com/') - self.assertEqual('example.com', host) - self.assertEqual(443, port) - self.assertEqual('/', resource) - - host, port, resource = http_header_util.parse_uri( - 'ws://example.com:8080') - self.assertEqual('example.com', host) - self.assertEqual(8080, port) - self.assertEqual('/', resource) - - def test_parse_invalid_uri(self): - host, port, resource = http_header_util.parse_uri('ws:///') - self.assertEqual(None, resource) - - host, port, resource = http_header_util.parse_uri('ws://localhost:') - self.assertEqual(None, resource) - - host, port, resource = http_header_util.parse_uri('ws://localhost:/ws') - self.assertEqual(None, resource) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/test_memorizingfile.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_memorizingfile.py deleted file mode 100755 index 8f1b8eef4..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/test_memorizingfile.py +++ /dev/null @@ -1,104 +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 memorizingfile module.""" - - -import StringIO -import unittest - -import set_sys_path # Update sys.path to locate mod_pywebsocket module. - -from mod_pywebsocket import memorizingfile - - -class UtilTest(unittest.TestCase): - """A unittest for memorizingfile module.""" - - def check(self, memorizing_file, num_read, expected_list): - for unused in range(num_read): - memorizing_file.readline() - actual_list = memorizing_file.get_memorized_lines() - self.assertEqual(len(expected_list), len(actual_list)) - for expected, actual in zip(expected_list, actual_list): - self.assertEqual(expected, actual) - - def check_with_size(self, memorizing_file, read_size, expected_list): - read_list = [] - read_line = '' - while True: - line = memorizing_file.readline(read_size) - line_length = len(line) - self.assertTrue(line_length <= read_size) - if line_length == 0: - if read_line != '': - read_list.append(read_line) - break - read_line += line - if line[line_length - 1] == '\n': - read_list.append(read_line) - read_line = '' - actual_list = memorizing_file.get_memorized_lines() - self.assertEqual(len(expected_list), len(actual_list)) - self.assertEqual(len(expected_list), len(read_list)) - for expected, actual, read in zip(expected_list, actual_list, - read_list): - self.assertEqual(expected, actual) - self.assertEqual(expected, read) - - def test_get_memorized_lines(self): - memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO( - 'Hello\nWorld\nWelcome')) - self.check(memorizing_file, 3, ['Hello\n', 'World\n', 'Welcome']) - - def test_get_memorized_lines_limit_memorized_lines(self): - memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO( - 'Hello\nWorld\nWelcome'), 2) - self.check(memorizing_file, 3, ['Hello\n', 'World\n']) - - def test_get_memorized_lines_empty_file(self): - memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO( - '')) - self.check(memorizing_file, 10, []) - - def test_get_memorized_lines_with_size(self): - for size in range(1, 10): - memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO( - 'Hello\nWorld\nWelcome')) - self.check_with_size(memorizing_file, size, - ['Hello\n', 'World\n', 'Welcome']) - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/test_mock.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_mock.py deleted file mode 100755 index 7dc23a73d..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/test_mock.py +++ /dev/null @@ -1,145 +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 mock module.""" - - -import Queue -import threading -import unittest - -import set_sys_path # Update sys.path to locate mod_pywebsocket module. - -from test import mock - - -class MockConnTest(unittest.TestCase): - """A unittest for MockConn class.""" - - def setUp(self): - self._conn = mock.MockConn('ABC\r\nDEFG\r\n\r\nHIJK') - - def test_readline(self): - self.assertEqual('ABC\r\n', self._conn.readline()) - self.assertEqual('DEFG\r\n', self._conn.readline()) - self.assertEqual('\r\n', self._conn.readline()) - self.assertEqual('HIJK', self._conn.readline()) - self.assertEqual('', self._conn.readline()) - - def test_read(self): - self.assertEqual('ABC\r\nD', self._conn.read(6)) - self.assertEqual('EFG\r\n\r\nHI', self._conn.read(9)) - self.assertEqual('JK', self._conn.read(10)) - self.assertEqual('', self._conn.read(10)) - - def test_read_and_readline(self): - self.assertEqual('ABC\r\nD', self._conn.read(6)) - self.assertEqual('EFG\r\n', self._conn.readline()) - self.assertEqual('\r\nHIJK', self._conn.read(9)) - self.assertEqual('', self._conn.readline()) - - def test_write(self): - self._conn.write('Hello\r\n') - self._conn.write('World\r\n') - self.assertEqual('Hello\r\nWorld\r\n', self._conn.written_data()) - - -class MockBlockingConnTest(unittest.TestCase): - """A unittest for MockBlockingConn class.""" - - def test_read(self): - """Tests that data put to MockBlockingConn by put_bytes method can be - read from it. - """ - - class LineReader(threading.Thread): - """A test class that launches a thread, calls readline on the - specified conn repeatedly and puts the read data to the specified - queue. - """ - - def __init__(self, conn, queue): - threading.Thread.__init__(self) - self._queue = queue - self._conn = conn - self.setDaemon(True) - self.start() - - def run(self): - while True: - data = self._conn.readline() - self._queue.put(data) - - conn = mock.MockBlockingConn() - queue = Queue.Queue() - reader = LineReader(conn, queue) - self.failUnless(queue.empty()) - conn.put_bytes('Foo bar\r\n') - read = queue.get() - self.assertEqual('Foo bar\r\n', read) - - -class MockTableTest(unittest.TestCase): - """A unittest for MockTable class.""" - - def test_create_from_dict(self): - table = mock.MockTable({'Key': 'Value'}) - self.assertEqual('Value', table.get('KEY')) - self.assertEqual('Value', table['key']) - - def test_create_from_list(self): - table = mock.MockTable([('Key', 'Value')]) - self.assertEqual('Value', table.get('KEY')) - self.assertEqual('Value', table['key']) - - def test_create_from_tuple(self): - table = mock.MockTable((('Key', 'Value'),)) - self.assertEqual('Value', table.get('KEY')) - self.assertEqual('Value', table['key']) - - def test_set_and_get(self): - table = mock.MockTable() - self.assertEqual(None, table.get('Key')) - table['Key'] = 'Value' - self.assertEqual('Value', table.get('Key')) - self.assertEqual('Value', table.get('key')) - self.assertEqual('Value', table.get('KEY')) - self.assertEqual('Value', table['Key']) - self.assertEqual('Value', table['key']) - self.assertEqual('Value', table['KEY']) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/test_msgutil.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_msgutil.py deleted file mode 100755 index 5fedcf92f..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/test_msgutil.py +++ /dev/null @@ -1,1356 +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.""" - - -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 PerMessageCompressExtensionProcessor -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_compression_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_compression_request is not None: - processor = PerMessageCompressExtensionProcessor( - permessage_compression_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_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: 0b00000010 0b00000000 - # From LSB, - # - 1 bit of BFINAL (0) - # - 2 bits of BTYPE (01 that means fixed Huffman) - # - 7 bits of the first code (0000000 that is the code for the - # end-of-block) - # - 1 bit of BFINAL (0) - # - 2 bits of BTYPE (no compression) - # - 3 bits of padding - self.assertEqual('\xc1\x02\x02\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 PerMessageCompressTest(unittest.TestCase): - """Tests for checking permessage-compression extension.""" - - def test_deflate_response_parameters(self): - extension = common.ExtensionParameter( - common.PERMESSAGE_COMPRESSION_EXTENSION) - extension.add_parameter('method', 'deflate') - processor = PerMessageCompressExtensionProcessor(extension) - response = processor.get_extension_response() - self.assertEqual('deflate', - response.get_parameter_value('method')) - - extension = common.ExtensionParameter( - common.PERMESSAGE_COMPRESSION_EXTENSION) - extension.add_parameter('method', 'deflate') - processor = PerMessageCompressExtensionProcessor(extension) - - def _compression_processor_hook(compression_processor): - compression_processor.set_client_max_window_bits(8) - compression_processor.set_client_no_context_takeover(True) - processor.set_compression_processor_hook( - _compression_processor_hook) - response = processor.get_extension_response() - self.assertEqual( - 'deflate; client_max_window_bits=8; client_no_context_takeover', - response.get_parameter_value('method')) - - -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/testing/web-platform/tests/tools/pywebsocket/src/test/test_mux.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_mux.py deleted file mode 100644 index d4598944e..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/test_mux.py +++ /dev/null @@ -1,2089 +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, e: - self.channel_events[request.channel_id].exception = e - except Exception, 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_compress(self): - # Enable permessage compress extension on the implicitly opened channel. - extensions = common.parse_extensions( - '%s; method=deflate' % common.PERMESSAGE_COMPRESSION_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 compress extension on the implicitly opened channel. - extensions = common.parse_extensions( - '%s; method=deflate' % common.PERMESSAGE_COMPRESSION_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 compress 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_compress(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 compress extension on logical channel 2. - extensions = '%s; method=deflate' % ( - common.PERMESSAGE_COMPRESSION_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_compress_fragmented_message(self): - extensions = common.parse_extensions( - '%s; method=deflate' % common.PERMESSAGE_COMPRESSION_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/testing/web-platform/tests/tools/pywebsocket/src/test/test_stream.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_stream.py deleted file mode 100755 index 81acfeb04..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/test_stream.py +++ /dev/null @@ -1,77 +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 import common -from mod_pywebsocket import stream - - -class StreamTest(unittest.TestCase): - """A unittest for stream module.""" - - def test_create_header(self): - # more, rsv1, ..., rsv4 are all true - header = stream.create_header(common.OPCODE_TEXT, 1, 1, 1, 1, 1, 1) - self.assertEqual('\xf1\x81', header) - - # Maximum payload size - header = stream.create_header( - common.OPCODE_TEXT, (1 << 63) - 1, 0, 0, 0, 0, 0) - self.assertEqual('\x01\x7f\x7f\xff\xff\xff\xff\xff\xff\xff', header) - - # Invalid opcode 0x10 - self.assertRaises(ValueError, - stream.create_header, - 0x10, 0, 0, 0, 0, 0, 0) - - # Invalid value 0xf passed to more parameter - self.assertRaises(ValueError, - stream.create_header, - common.OPCODE_TEXT, 0, 0xf, 0, 0, 0, 0) - - # Too long payload_length - self.assertRaises(ValueError, - stream.create_header, - common.OPCODE_TEXT, 1 << 63, 0, 0, 0, 0, 0) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/test_stream_hixie75.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_stream_hixie75.py deleted file mode 100755 index ca9ac7130..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/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/testing/web-platform/tests/tools/pywebsocket/src/test/test_util.py b/testing/web-platform/tests/tools/pywebsocket/src/test/test_util.py deleted file mode 100755 index 20f4ab059..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/test_util.py +++ /dev/null @@ -1,200 +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 util module.""" - - -import os -import random -import sys -import unittest - -import set_sys_path # Update sys.path to locate mod_pywebsocket module. - -from mod_pywebsocket import util - - -_TEST_DATA_DIR = os.path.join(os.path.split(__file__)[0], 'testdata') - - -class UtilTest(unittest.TestCase): - """A unittest for util module.""" - - def test_get_stack_trace(self): - self.assertEqual('None\n', util.get_stack_trace()) - try: - a = 1 / 0 # Intentionally raise exception. - except Exception: - trace = util.get_stack_trace() - self.failUnless(trace.startswith('Traceback')) - self.failUnless(trace.find('ZeroDivisionError') != -1) - - def test_prepend_message_to_exception(self): - exc = Exception('World') - self.assertEqual('World', str(exc)) - util.prepend_message_to_exception('Hello ', exc) - self.assertEqual('Hello World', str(exc)) - - def test_get_script_interp(self): - cygwin_path = 'c:\\cygwin\\bin' - cygwin_perl = os.path.join(cygwin_path, 'perl') - self.assertEqual(None, util.get_script_interp( - os.path.join(_TEST_DATA_DIR, 'README'))) - self.assertEqual(None, util.get_script_interp( - os.path.join(_TEST_DATA_DIR, 'README'), cygwin_path)) - self.assertEqual('/usr/bin/perl -wT', util.get_script_interp( - os.path.join(_TEST_DATA_DIR, 'hello.pl'))) - self.assertEqual(cygwin_perl + ' -wT', util.get_script_interp( - os.path.join(_TEST_DATA_DIR, 'hello.pl'), cygwin_path)) - - def test_hexify(self): - self.assertEqual('61 7a 41 5a 30 39 20 09 0d 0a 00 ff', - util.hexify('azAZ09 \t\r\n\x00\xff')) - - -class RepeatedXorMaskerTest(unittest.TestCase): - """A unittest for RepeatedXorMasker class.""" - - def test_mask(self): - # Sample input e6,97,a5 is U+65e5 in UTF-8 - masker = util.RepeatedXorMasker('\xff\xff\xff\xff') - result = masker.mask('\xe6\x97\xa5') - self.assertEqual('\x19\x68\x5a', result) - - masker = util.RepeatedXorMasker('\x00\x00\x00\x00') - result = masker.mask('\xe6\x97\xa5') - self.assertEqual('\xe6\x97\xa5', result) - - masker = util.RepeatedXorMasker('\xe6\x97\xa5\x20') - result = masker.mask('\xe6\x97\xa5') - self.assertEqual('\x00\x00\x00', result) - - def test_mask_twice(self): - masker = util.RepeatedXorMasker('\x00\x7f\xff\x20') - # mask[0], mask[1], ... will be used. - result = masker.mask('\x00\x00\x00\x00\x00') - self.assertEqual('\x00\x7f\xff\x20\x00', result) - # mask[2], mask[0], ... will be used for the next call. - result = masker.mask('\x00\x00\x00\x00\x00') - self.assertEqual('\x7f\xff\x20\x00\x7f', result) - - def test_mask_large_data(self): - masker = util.RepeatedXorMasker('mASk') - original = ''.join([chr(i % 256) for i in xrange(1000)]) - result = masker.mask(original) - expected = ''.join( - [chr((i % 256) ^ ord('mASk'[i % 4])) for i in xrange(1000)]) - self.assertEqual(expected, result) - - masker = util.RepeatedXorMasker('MaSk') - first_part = 'The WebSocket Protocol enables two-way communication.' - result = masker.mask(first_part) - self.assertEqual( - '\x19\t6K\x1a\x0418"\x028\x0e9A\x03\x19"\x15<\x08"\rs\x0e#' - '\x001\x07(\x12s\x1f:\x0e~\x1c,\x18s\x08"\x0c>\x1e#\x080\n9' - '\x08<\x05c', - result) - second_part = 'It has two parts: a handshake and the data transfer.' - result = masker.mask(second_part) - self.assertEqual( - "('K%\x00 K9\x16<K=\x00!\x1f>[s\nm\t2\x05)\x12;\n&\x04s\n#" - "\x05s\x1f%\x04s\x0f,\x152K9\x132\x05>\x076\x19c", - result) - - -def get_random_section(source, min_num_chunks): - chunks = [] - bytes_chunked = 0 - - while bytes_chunked < len(source): - chunk_size = random.randint( - 1, - min(len(source) / min_num_chunks, len(source) - bytes_chunked)) - chunk = source[bytes_chunked:bytes_chunked + chunk_size] - chunks.append(chunk) - bytes_chunked += chunk_size - - return chunks - - -class InflaterDeflaterTest(unittest.TestCase): - """A unittest for _Inflater and _Deflater class.""" - - def test_inflate_deflate_default(self): - input = b'hello' + '-' * 30000 + b'hello' - inflater15 = util._Inflater(15) - deflater15 = util._Deflater(15) - inflater8 = util._Inflater(8) - deflater8 = util._Deflater(8) - - compressed15 = deflater15.compress_and_finish(input) - compressed8 = deflater8.compress_and_finish(input) - - inflater15.append(compressed15) - inflater8.append(compressed8) - - self.assertNotEqual(compressed15, compressed8) - self.assertEqual(input, inflater15.decompress(-1)) - self.assertEqual(input, inflater8.decompress(-1)) - - def test_random_section(self): - random.seed(a=0) - source = ''.join( - [chr(random.randint(0, 255)) for i in xrange(100 * 1024)]) - - chunked_input = get_random_section(source, 10) - print "Input chunk sizes: %r" % [len(c) for c in chunked_input] - - deflater = util._Deflater(15) - compressed = [] - for chunk in chunked_input: - compressed.append(deflater.compress(chunk)) - compressed.append(deflater.compress_and_finish('')) - - chunked_expectation = get_random_section(source, 10) - print ("Expectation chunk sizes: %r" % - [len(c) for c in chunked_expectation]) - - inflater = util._Inflater(15) - inflater.append(''.join(compressed)) - for chunk in chunked_expectation: - decompressed = inflater.decompress(len(chunk)) - self.assertEqual(chunk, decompressed) - - self.assertEqual('', inflater.decompress(-1)) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/README b/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/README deleted file mode 100644 index c001aa559..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/README +++ /dev/null @@ -1 +0,0 @@ -Test data directory diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/abort_by_user_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/abort_by_user_wsh.py deleted file mode 100644 index 367f9930f..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/abort_by_user_wsh.py +++ /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. - - -from mod_pywebsocket import handshake - - -def web_socket_do_extra_handshake(request): - raise handshake.AbortedByUserException("abort for test") - - -def web_socket_transfer_data(request): - raise handshake.AbortedByUserException("abort for test") - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/blank_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/blank_wsh.py deleted file mode 100644 index 7f87c6af2..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/blank_wsh.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2009, 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. - - -# intentionally left blank diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/origin_check_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/origin_check_wsh.py deleted file mode 100644 index 2c139fa17..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/origin_check_wsh.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2009, 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. - - -def web_socket_do_extra_handshake(request): - if request.ws_origin == 'http://example.com': - return - raise ValueError('Unacceptable origin: %r' % request.ws_origin) - - -def web_socket_transfer_data(request): - request.connection.write('origin_check_wsh.py is called for %s, %s' % - (request.ws_resource, request.ws_protocol)) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/exception_in_transfer_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/exception_in_transfer_wsh.py deleted file mode 100644 index b982d0231..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/exception_in_transfer_wsh.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2009, 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. - - -"""Exception in web_socket_transfer_data(). -""" - - -def web_socket_do_extra_handshake(request): - pass - - -def web_socket_transfer_data(request): - raise Exception('Intentional Exception for %s, %s' % - (request.ws_resource, request.ws_protocol)) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/no_wsh_at_the_end.py b/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/no_wsh_at_the_end.py deleted file mode 100644 index 17e7be180..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/no_wsh_at_the_end.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2009, 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. - - -"""Correct signatures, wrong file name. -""" - - -def web_socket_do_extra_handshake(request): - pass - - -def web_socket_transfer_data(request): - request.connection.write( - 'sub/no_wsh_at_the_end.py is called for %s, %s' % - (request.ws_resource, request.ws_protocol)) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/non_callable_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/non_callable_wsh.py deleted file mode 100644 index 26352eb4c..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/non_callable_wsh.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2009, 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. - - -"""Non-callable handlers. -""" - - -web_socket_do_extra_handshake = True -web_socket_transfer_data = 1 - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/plain_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/plain_wsh.py deleted file mode 100644 index db3ff6930..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/plain_wsh.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2009, 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. - - -def web_socket_do_extra_handshake(request): - pass - - -def web_socket_transfer_data(request): - request.connection.write('sub/plain_wsh.py is called for %s, %s' % - (request.ws_resource, request.ws_protocol)) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py deleted file mode 100644 index 6bf659bc9..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2009, 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. - - -"""Wrong web_socket_do_extra_handshake signature. -""" - - -def no_web_socket_do_extra_handshake(request): - pass - - -def web_socket_transfer_data(request): - request.connection.write( - 'sub/wrong_handshake_sig_wsh.py is called for %s, %s' % - (request.ws_resource, request.ws_protocol)) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py b/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py deleted file mode 100644 index e0e2e5507..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2009, 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. - - -"""Wrong web_socket_transfer_data() signature. -""" - - -def web_socket_do_extra_handshake(request): - pass - - -def no_web_socket_transfer_data(request): - request.connection.write( - 'sub/wrong_transfer_sig_wsh.py is called for %s, %s' % - (request.ws_resource, request.ws_protocol)) - - -# vi:sts=4 sw=4 et diff --git a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/hello.pl b/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/hello.pl deleted file mode 100644 index 882ef5a10..000000000 --- a/testing/web-platform/tests/tools/pywebsocket/src/test/testdata/hello.pl +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/perl -wT -# -# 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. - -print "Hello\n"; |