author | Jonathan Griffin <jgriffin@mozilla.com> |
Wed, 16 Jun 2010 22:38:55 -0700 | |
changeset 43718 | c69b38b80f632cb62d476cabf2dc6a1e83a904ca |
parent 43717 | 4ee79cff00249c26ed03af6dbd1e2adf81a37246 |
child 43719 | 96d248dcf4d24c9866b7720ea324b0a4bdba275f |
push id | unknown |
push user | unknown |
push date | unknown |
reviewers | ted |
bugs | 570789 |
milestone | 1.9.3a6pre |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/build/automation.py.in +++ b/build/automation.py.in @@ -53,16 +53,18 @@ import tempfile SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0]))) sys.path.insert(0, SCRIPT_DIR) import automationutils _DEFAULT_WEB_SERVER = "127.0.0.1" _DEFAULT_HTTP_PORT = 8888 _DEFAULT_SSL_PORT = 4443 +_DEFAULT_WEBSOCKET_PORT = 9999 +_DEFAULT_WEBSOCKET_PROXY_PORT = 7777 #expand _DIST_BIN = __XPC_BIN_PATH__ #expand _IS_WIN32 = len("__WIN32__") != 0 #expand _IS_MAC = __IS_MAC__ != 0 #expand _IS_LINUX = __IS_LINUX__ != 0 #ifdef IS_CYGWIN #expand _IS_CYGWIN = __IS_CYGWIN__ == 1 #else @@ -146,25 +148,34 @@ class Automation(object): IS_DEBUG_BUILD = _IS_DEBUG_BUILD CRASHREPORTER = _CRASHREPORTER # timeout, in seconds DEFAULT_TIMEOUT = 60.0 DEFAULT_WEB_SERVER = _DEFAULT_WEB_SERVER DEFAULT_HTTP_PORT = _DEFAULT_HTTP_PORT DEFAULT_SSL_PORT = _DEFAULT_SSL_PORT + DEFAULT_WEBSOCKET_PORT = _DEFAULT_WEBSOCKET_PORT + DEFAULT_WEBSOCKET_PROXY_PORT = _DEFAULT_WEBSOCKET_PROXY_PORT def __init__(self): self.log = _log self.lastTestSeen = "automation.py" - def setServerInfo(self, webServer = _DEFAULT_WEB_SERVER, httpPort = _DEFAULT_HTTP_PORT, sslPort = _DEFAULT_SSL_PORT): + def setServerInfo(self, + webServer = _DEFAULT_WEB_SERVER, + httpPort = _DEFAULT_HTTP_PORT, + sslPort = _DEFAULT_SSL_PORT, + webSocketPort = _DEFAULT_WEBSOCKET_PORT, + webSocketProxyPort = _DEFAULT_WEBSOCKET_PROXY_PORT): self.webServer = webServer self.httpPort = httpPort self.sslPort = sslPort + self.webSocketPort = webSocketPort + self.webSocketProxyPort = webSocketProxyPort @property def __all__(self): return [ "UNIXISH", "IS_WIN32", "IS_MAC", "log", @@ -370,34 +381,40 @@ function FindProxyForURL(url, host) '(?:[^/@]*@)?' + '(.*?)' + '(?::(\\\\\\\\d+))?/'); var matches = regex.exec(url); if (!matches) return 'DIRECT'; var isHttp = matches[1] == 'http'; var isHttps = matches[1] == 'https'; + var isWebSocket = matches[1] == 'ws'; if (!matches[3]) { - if (isHttp) matches[3] = '80'; + if (isHttp | isWebSocket) matches[3] = '80'; if (isHttps) matches[3] = '443'; } - + if (isWebSocket) + matches[1] = 'http'; + var origin = matches[1] + '://' + matches[2] + ':' + matches[3]; if (origins.indexOf(origin) < 0) return 'DIRECT'; if (isHttp) return 'PROXY %(remote)s:%(httpport)s'; if (isHttps) return 'PROXY %(remote)s:%(sslport)s'; + if (isWebSocket) + return 'PROXY %(remote)s:%(websocketproxyport)s'; return 'DIRECT'; }""" % { "origins": origins, "remote": self.webServer, "httpport":self.httpPort, - "sslport": self.sslPort } + "sslport": self.sslPort, + "websocketproxyport": self.webSocketProxyPort } pacURL = "".join(pacURL.splitlines()) part += """ user_pref("network.proxy.type", 2); user_pref("network.proxy.autoconfig_url", "%(pacURL)s"); user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless to others """ % {"pacURL": pacURL} @@ -434,16 +451,18 @@ user_pref("camino.use_system_proxy_setti # Create head of the ssltunnel configuration file sslTunnelConfigPath = os.path.join(profileDir, "ssltunnel.cfg") sslTunnelConfig = open(sslTunnelConfigPath, "w") sslTunnelConfig.write("httpproxy:1\n") sslTunnelConfig.write("certdbdir:%s\n" % certPath) sslTunnelConfig.write("forward:127.0.0.1:%s\n" % self.httpPort) + sslTunnelConfig.write("proxy:%s:%s:%s\n" % + (self.webSocketProxyPort, self.webServer, self.webSocketPort)) sslTunnelConfig.write("listen:*:%s:pgo server certificate\n" % self.sslPort) # Configure automatic certificate and bind custom certificates, client authentication locations = self.readLocations() locations.pop(0) for loc in locations: if loc.scheme == "https" and "nocert" not in loc.options: customCertRE = re.compile("^cert=(?P<nickname>[0-9a-zA-Z_ ]+)")
--- a/content/base/test/Makefile.in +++ b/content/base/test/Makefile.in @@ -389,16 +389,18 @@ include $(topsrcdir)/config/rules.mk test_bug562652.html \ test_bug562137.html \ file_bug562137.txt \ test_bug548193.html \ file_bug548193.sjs \ test_html_colors_quirks.html \ test_html_colors_standards.html \ test_bug571390.xul \ + test_websocket_hello.html \ + file_websocket_hello_wsh.py \ $(NULL) # This test fails on the Mac for some reason ifneq (,$(filter gtk2 windows,$(MOZ_WIDGET_TOOLKIT))) _TEST_FILES2 += test_copyimage.html \ $(NULL) endif
--- a/testing/mochitest/Makefile.in +++ b/testing/mochitest/Makefile.in @@ -77,19 +77,47 @@ include $(topsrcdir)/build/automation-bu redirect-a11y.html \ redirect.html \ redirect.js \ $(topsrcdir)/build/pgo/server-locations.txt \ $(topsrcdir)/netwerk/test/httpserver/httpd.js \ mozprefs.js \ $(NULL) +_PYWEBSOCKET_FILES = \ + pywebsocket/standalone.py \ + $(NULL) + +_MOD_PYWEBSOCKET_FILES = \ + pywebsocket/mod_pywebsocket/__init__.py \ + pywebsocket/mod_pywebsocket/dispatch.py \ + pywebsocket/mod_pywebsocket/util.py \ + pywebsocket/mod_pywebsocket/msgutil.py \ + pywebsocket/mod_pywebsocket/memorizingfile.py \ + pywebsocket/mod_pywebsocket/headerparserhandler.py \ + $(NULL) + +_HANDSHAKE_FILES = \ + pywebsocket/mod_pywebsocket/handshake/__init__.py \ + pywebsocket/mod_pywebsocket/handshake/_base.py \ + pywebsocket/mod_pywebsocket/handshake/draft75.py \ + pywebsocket/mod_pywebsocket/handshake/handshake.py \ + $(NULL) _DEST_DIR = $(DEPTH)/_tests/$(relativesrcdir) +libs:: $(_PYWEBSOCKET_FILES) + $(INSTALL) $(foreach f,$^,"$f") $(_DEST_DIR)/pywebsocket + +libs:: $(_MOD_PYWEBSOCKET_FILES) + $(INSTALL) $(foreach f,$^,"$f") $(_DEST_DIR)/pywebsocket/mod_pywebsocket + +libs:: $(_HANDSHAKE_FILES) + $(INSTALL) $(foreach f,$^,"$f") $(_DEST_DIR)/pywebsocket/mod_pywebsocket/handshake + runtests.py: runtests.py.in $(PYTHON) $(topsrcdir)/config/Preprocessor.py \ $(DEFINES) $(ACDEFINES) $^ > $@ GARBAGE += runtests.py libs:: $(_SERV_FILES) $(INSTALL) $^ $(_DEST_DIR)
new file mode 100644 --- /dev/null +++ b/testing/mochitest/pywebsocket/COPYING @@ -0,0 +1,28 @@ +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.
new file mode 100644 --- /dev/null +++ b/testing/mochitest/pywebsocket/README @@ -0,0 +1,1 @@ +This is mod_pywebsocket 0.5, from http://code.google.com/p/pywebsocket/
new file mode 100644 --- /dev/null +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/__init__.py @@ -0,0 +1,111 @@ +# 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. + + +"""Web Socket extension for Apache HTTP Server. + +mod_pywebsocket is a Web Socket 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 Web Socket handlers are placed. + + PythonOption mod_pywebsocket.handler_root <websock_handlers> + PythonHeaderParserHandler mod_pywebsocket.headerparserhandler + + To limit the search for Web Socket 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-Web Socket handler files. + + If you want to support old handshake based on + draft-hixie-thewebsocketprotocol-75: + + PythonOption mod_pywebsocket.allow_draft75 On + + + Example snippet of httpd.conf: + (mod_pywebsocket is in /websock_lib, Web Socket 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> + +Writing Web Socket handlers: + +When a Web Socket 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 Web Socket handler is composed of the following two functions: + + web_socket_do_extra_handshake(request) + web_socket_transfer_data(request) + +where: + request: mod_python request. + +web_socket_do_extra_handshake is called during the handshake after the +headers are successfully parsed and Web Socket properties (ws_location, +ws_origin, ws_protocol, and ws_resource) are added to request. A handler +can reject the request by raising an exception. + +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. + +A Web Socket handler must be thread-safe if the server (Apache or +standalone.py) is configured to use threads. +""" + + +# vi:sts=4 sw=4 et tw=72
new file mode 100644 --- /dev/null +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py @@ -0,0 +1,245 @@ +# 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. + + +"""Dispatch Web Socket request. +""" + + +import os +import re + +from mod_pywebsocket import msgutil +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' + + +class DispatchError(Exception): + """Exception in dispatching Web Socket request.""" + + pass + + +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 _path_to_resource_converter(base_dir): + 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 + path = _normalize_path(path) + if not path.startswith(base_dir): + return None + return path[base_len:-suffix_len] + return converter + + +def _source_file_paths(directory): + """Yield Web Socket 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 + + +def _source(source_str): + """Source a handler definition string.""" + + global_dic = {} + try: + exec source_str in global_dic + except Exception: + raise DispatchError('Error in sourcing handler:' + + util.get_stack_trace()) + return (_extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME), + _extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME)) + + +def _extract_handler(dic, name): + if name not in dic: + raise DispatchError('%s is not defined.' % name) + handler = dic[name] + if not callable(handler): + raise DispatchError('%s is not callable.' % name) + return handler + + +class Dispatcher(object): + """Dispatches Web Socket requests. + + This class maintains a map from resource name to handlers. + """ + + def __init__(self, root_dir, scan_dir=None): + """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. + """ + + self._handlers = {} + 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 DispatchError('scan_dir:%s must be a directory under ' + 'root_dir:%s.' % (scan_dir, root_dir)) + self._source_files_in_dir(root_dir, scan_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 = self._handlers[existing_resource_path] + self._handlers[alias_resource_path] = handler + except KeyError: + raise DispatchError('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 Web Socket handshake. + + Select a handler based on request.uri and call its + web_socket_do_extra_handshake function. + + Args: + request: mod_python request. + """ + + do_extra_handshake_, unused_transfer_data = self._handler(request) + try: + do_extra_handshake_(request) + except Exception, e: + util.prepend_message_to_exception( + '%s raised exception for %s: ' % ( + _DO_EXTRA_HANDSHAKE_HANDLER_NAME, + request.ws_resource), + e) + raise + + def transfer_data(self, request): + """Let a handler transfer_data with a Web Socket client. + + Select a handler based on request.ws_resource and call its + web_socket_transfer_data function. + + Args: + request: mod_python request. + """ + + unused_do_extra_handshake, transfer_data_ = self._handler(request) + try: + try: + request.client_terminated = False + request.server_terminated = False + transfer_data_(request) + except msgutil.ConnectionTerminatedException, e: + util.prepend_message_to_exception( + 'client initiated closing handshake for %s: ' % ( + request.ws_resource), + e) + raise + except Exception, e: + print 'exception: %s' % type(e) + util.prepend_message_to_exception( + '%s raised exception for %s: ' % ( + _TRANSFER_DATA_HANDLER_NAME, request.ws_resource), + e) + raise + finally: + msgutil.close_connection(request) + + + def _handler(self, request): + try: + ws_resource_path = request.ws_resource.split('?', 1)[0] + return self._handlers[ws_resource_path] + except KeyError: + raise DispatchError('No handler for: %r' % request.ws_resource) + + def _source_files_in_dir(self, root_dir, scan_dir): + """Source all the handler source files in the scan_dir directory. + + The resource path is determined relative to root_dir. + """ + + to_resource = _path_to_resource_converter(root_dir) + for path in _source_file_paths(scan_dir): + try: + handlers = _source(open(path).read()) + except DispatchError, e: + self._source_warnings.append('%s: %s' % (path, e)) + continue + self._handlers[to_resource(path)] = handlers + + +# vi:sts=4 sw=4 et
new file mode 100644 --- /dev/null +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/__init__.py @@ -0,0 +1,95 @@ +# Copyright 2010, 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. + + +"""Web Socket handshaking. + +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 + +from mod_pywebsocket.handshake import draft75 +from mod_pywebsocket.handshake import handshake +from mod_pywebsocket.handshake._base import DEFAULT_WEB_SOCKET_PORT +from mod_pywebsocket.handshake._base import DEFAULT_WEB_SOCKET_SECURE_PORT +from mod_pywebsocket.handshake._base import WEB_SOCKET_SCHEME +from mod_pywebsocket.handshake._base import WEB_SOCKET_SECURE_SCHEME +from mod_pywebsocket.handshake._base import HandshakeError +from mod_pywebsocket.handshake._base import validate_protocol + + +class Handshaker(object): + """This class performs Web Socket handshake.""" + + def __init__(self, request, dispatcher, allowDraft75=False, strict=False): + """Construct an instance. + + Args: + request: mod_python request. + dispatcher: Dispatcher (dispatch.Dispatcher). + allowDraft75: allow draft 75 handshake protocol. + strict: Strictly check handshake request in draft 75. + Default: False. If True, request.connection must provide + get_memorized_lines method. + + Handshaker will add attributes such as ws_resource in performing + handshake. + """ + + self._logger = logging.getLogger("mod_pywebsocket.handshake") + self._request = request + self._dispatcher = dispatcher + self._strict = strict + self._handshaker = handshake.Handshaker(request, dispatcher) + self._fallbackHandshaker = None + if allowDraft75: + self._fallbackHandshaker = draft75.Handshaker( + request, dispatcher, strict) + + def do_handshake(self): + """Perform Web Socket Handshake.""" + + try: + self._handshaker.do_handshake() + except HandshakeError, e: + self._logger.error('Handshake error: %s' % e) + if self._fallbackHandshaker: + self._logger.warning('fallback to old protocol') + self._fallbackHandshaker.do_handshake() + return + raise e + + +# vi:sts=4 sw=4 et
new file mode 100644 --- /dev/null +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/_base.py @@ -0,0 +1,101 @@ +# Copyright 2010, 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. + + +"""Web Socket handshaking. + +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. +""" + + +DEFAULT_WEB_SOCKET_PORT = 80 +DEFAULT_WEB_SOCKET_SECURE_PORT = 443 +WEB_SOCKET_SCHEME = 'ws' +WEB_SOCKET_SECURE_SCHEME = 'wss' + + +class HandshakeError(Exception): + """Exception in Web Socket Handshake.""" + + pass + + +def default_port(is_secure): + if is_secure: + return DEFAULT_WEB_SOCKET_SECURE_PORT + else: + return DEFAULT_WEB_SOCKET_PORT + + +def validate_protocol(protocol): + """Validate WebSocket-Protocol string.""" + + if not protocol: + raise HandshakeError('Invalid WebSocket-Protocol: empty') + for c in protocol: + if not 0x20 <= ord(c) <= 0x7e: + raise HandshakeError('Illegal character in protocol: %r' % c) + + +def parse_host_header(request): + fields = request.headers_in['Host'].split(':', 1) + if len(fields) == 1: + return fields[0], default_port(request.is_https()) + try: + return fields[0], int(fields[1]) + except ValueError, e: + raise HandshakeError('Invalid port number format: %r' % e) + + +def build_location(request): + """Build WebSocket location for request.""" + location_parts = [] + if request.is_https(): + location_parts.append(WEB_SOCKET_SECURE_SCHEME) + else: + location_parts.append(WEB_SOCKET_SCHEME) + location_parts.append('://') + host, port = parse_host_header(request) + connection_port = request.connection.local_addr[1] + if port != connection_port: + raise HandshakeError('Header/connection port mismatch: %d/%d' % + (port, connection_port)) + location_parts.append(host) + if (port != default_port(request.is_https())): + location_parts.append(':') + location_parts.append(str(port)) + location_parts.append(request.uri) + return ''.join(location_parts) + + + +# vi:sts=4 sw=4 et
new file mode 100644 --- /dev/null +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/draft75.py @@ -0,0 +1,168 @@ +# Copyright 2010, 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. + + +"""Web Socket handshaking defined in draft-hixie-thewebsocketprotocol-75. + +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 re + +from mod_pywebsocket.handshake._base import HandshakeError +from mod_pywebsocket.handshake._base import build_location +from mod_pywebsocket.handshake._base import validate_protocol + + +_MANDATORY_HEADERS = [ + # key, expected value or None + ['Upgrade', 'WebSocket'], + ['Connection', 'Upgrade'], + ['Host', None], + ['Origin', None], +] + +_FIRST_FIVE_LINES = map(re.compile, [ + r'^GET /[\S]* HTTP/1.1\r\n$', + r'^Upgrade: WebSocket\r\n$', + r'^Connection: Upgrade\r\n$', + r'^Host: [\S]+\r\n$', + r'^Origin: [\S]+\r\n$', +]) + +_SIXTH_AND_LATER = re.compile( + r'^' + r'(WebSocket-Protocol: [\x20-\x7e]+\r\n)?' + r'(Cookie: [^\r]*\r\n)*' + r'(Cookie2: [^\r]*\r\n)?' + r'(Cookie: [^\r]*\r\n)*' + r'\r\n') + + +class Handshaker(object): + """This class performs Web Socket handshake.""" + + def __init__(self, request, dispatcher, strict=False): + """Construct an instance. + + Args: + request: mod_python request. + dispatcher: Dispatcher (dispatch.Dispatcher). + strict: Strictly check handshake request. Default: False. + If True, request.connection must provide get_memorized_lines + method. + + Handshaker will add attributes such as ws_resource in performing + handshake. + """ + + self._request = request + self._dispatcher = dispatcher + self._strict = strict + + def do_handshake(self): + """Perform Web Socket Handshake.""" + + self._check_header_lines() + self._set_resource() + self._set_origin() + self._set_location() + self._set_protocol() + self._dispatcher.do_extra_handshake(self._request) + self._send_handshake() + + def _set_resource(self): + self._request.ws_resource = self._request.uri + + def _set_origin(self): + self._request.ws_origin = self._request.headers_in['Origin'] + + def _set_location(self): + self._request.ws_location = build_location(self._request) + + def _set_protocol(self): + protocol = self._request.headers_in.get('WebSocket-Protocol') + if protocol is not None: + validate_protocol(protocol) + self._request.ws_protocol = protocol + + def _send_handshake(self): + self._request.connection.write( + 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n') + self._request.connection.write('Upgrade: WebSocket\r\n') + self._request.connection.write('Connection: Upgrade\r\n') + self._request.connection.write('WebSocket-Origin: ') + self._request.connection.write(self._request.ws_origin) + self._request.connection.write('\r\n') + self._request.connection.write('WebSocket-Location: ') + self._request.connection.write(self._request.ws_location) + self._request.connection.write('\r\n') + if self._request.ws_protocol: + self._request.connection.write('WebSocket-Protocol: ') + self._request.connection.write(self._request.ws_protocol) + self._request.connection.write('\r\n') + self._request.connection.write('\r\n') + + def _check_header_lines(self): + for key, expected_value in _MANDATORY_HEADERS: + actual_value = self._request.headers_in.get(key) + if not actual_value: + raise HandshakeError('Header %s is not defined' % key) + if expected_value: + if actual_value != expected_value: + raise HandshakeError('Illegal value for header %s: %s' % + (key, actual_value)) + if self._strict: + try: + lines = self._request.connection.get_memorized_lines() + except AttributeError, e: + raise AttributeError( + 'Strict handshake is specified but the connection ' + 'doesn\'t provide get_memorized_lines()') + self._check_first_lines(lines) + + def _check_first_lines(self, lines): + if len(lines) < len(_FIRST_FIVE_LINES): + raise HandshakeError('Too few header lines: %d' % len(lines)) + for line, regexp in zip(lines, _FIRST_FIVE_LINES): + if not regexp.search(line): + raise HandshakeError('Unexpected header: %r doesn\'t match %r' + % (line, regexp.pattern)) + sixth_and_later = ''.join(lines[5:]) + if not _SIXTH_AND_LATER.search(sixth_and_later): + raise HandshakeError('Unexpected header: %r doesn\'t match %r' + % (sixth_and_later, + _SIXTH_AND_LATER.pattern)) + + +# vi:sts=4 sw=4 et
new file mode 100644 --- /dev/null +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/handshake.py @@ -0,0 +1,208 @@ +# 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. + + +"""Web Socket handshaking. + +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 +from md5 import md5 +import re +import struct + +from mod_pywebsocket.handshake._base import HandshakeError +from mod_pywebsocket.handshake._base import build_location +from mod_pywebsocket.handshake._base import validate_protocol + + +_MANDATORY_HEADERS = [ + # key, expected value or None + ['Upgrade', 'WebSocket'], + ['Connection', 'Upgrade'], +] + +def _hexify(s): + return re.sub('.', lambda x: '%02x ' % ord(x.group(0)), s) + +class Handshaker(object): + """This class performs Web Socket handshake.""" + + 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 = logging.getLogger("mod_pywebsocket.handshake") + self._request = request + self._dispatcher = dispatcher + + def do_handshake(self): + """Perform Web Socket Handshake.""" + + # 5.1 Reading the client's opening handshake. + # dispatcher sets it in self._request. + self._check_header_lines() + self._set_resource() + self._set_protocol() + self._set_location() + self._set_origin() + self._set_challenge_response() + self._dispatcher.do_extra_handshake(self._request) + self._send_handshake() + + def _check_header_lines(self): + # 5.1 1. The three character UTF-8 string "GET". + # 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte). + if self._request.method != 'GET': + raise HandshakeError('Method is not GET') + # The expected field names, and the meaning of their corresponding + # values, are as follows. + # |Upgrade| and |Connection| + for key, expected_value in _MANDATORY_HEADERS: + actual_value = self._request.headers_in.get(key) + if not actual_value: + raise HandshakeError('Header %s is not defined' % key) + if expected_value: + if actual_value != expected_value: + raise HandshakeError('Illegal value for header %s: %s' % + (key, actual_value)) + + def _set_resource(self): + self._request.ws_resource = self._request.uri + + def _set_protocol(self): + # |Sec-WebSocket-Protocol| + protocol = self._request.headers_in.get('Sec-WebSocket-Protocol') + if protocol is not None: + validate_protocol(protocol) + self._request.ws_protocol = protocol + + def _set_location(self): + # |Host| + host = self._request.headers_in.get('Host') + 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['Origin'] + if origin is not None: + self._request.ws_origin = origin + + 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 = md5( + self._request.ws_challenge).digest() + self._logger.debug("challenge: %s" % _hexify( + self._request.ws_challenge)) + self._logger.debug("response: %s" % _hexify( + self._request.ws_challenge_md5)) + + def _get_key_value(self, key_field): + key_value = self._request.headers_in.get(key_field) + if key_value is None: + self._logger.debug("no %s" % key_value) + return None + try: + # 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/ + key_number = int(re.sub("\\D", "", key_value)) + # 5.2 5. let /spaces_n/ be the number of U+0020 SPACE characters + # in /key_n/. + spaces = re.subn(" ", "", key_value)[1] + # 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 handshakeError('key_number %d is not an integral ' + 'multiple of spaces %d' % (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: %s => %d / %d => %d" % ( + key_field, key_value, key_number, spaces, part)) + return part + except: + return None + + def _get_challenge(self): + # 5.2 4-7. + key1 = self._get_key_value('Sec-Websocket-Key1') + if not key1: + raise HandshakeError('Sec-WebSocket-Key1 not found') + key2 = self._get_key_value('Sec-Websocket-Key2') + if not key2: + raise HandshakeError('Sec-WebSocket-Key2 not found') + # 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): + # 5.2 10. send the following line. + self._request.connection.write( + 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n') + # 5.2 11. send the following fields to the client. + self._request.connection.write('Upgrade: WebSocket\r\n') + self._request.connection.write('Connection: Upgrade\r\n') + self._request.connection.write('Sec-WebSocket-Location: ') + self._request.connection.write(self._request.ws_location) + self._request.connection.write('\r\n') + self._request.connection.write('Sec-WebSocket-Origin: ') + self._request.connection.write(self._request.ws_origin) + self._request.connection.write('\r\n') + if self._request.ws_protocol: + self._request.connection.write('Sec-WebSocket-Protocol: ') + self._request.connection.write(self._request.ws_protocol) + self._request.connection.write('\r\n') + # 5.2 12. send two bytes 0x0D 0x0A. + self._request.connection.write('\r\n') + # 5.2 13. send /response/ + self._request.connection.write(self._request.ws_challenge_md5) + + +# vi:sts=4 sw=4 et
new file mode 100644 --- /dev/null +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/headerparserhandler.py @@ -0,0 +1,138 @@ +# 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. + + +"""PythonHeaderParserHandler for mod_pywebsocket. + +Apache HTTP Server and mod_python must be configured such that this +function is called to handle Web Socket request. +""" + +import logging + +from mod_python import apache + +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 specify to allow draft75 handshake. +# The default is None (Off) +_PYOPT_ALLOW_DRAFT75 = 'mod_pywebsocket.allow_draft75' + + +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 + + def emit(self, record): + apache_level = apache.APLOG_DEBUG + if record.levelno in ApacheLogHandler._LEVELS: + apache_level = ApacheLogHandler._LEVELS[record.levelno] + self.log_error(record.getMessage(), apache_level) + + +logging.getLogger("mod_pywebsocket").addHandler(ApacheLogHandler()) + + +def _create_dispatcher(): + _HANDLER_ROOT = apache.main_server.get_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 = apache.main_server.get_options().get( + _PYOPT_HANDLER_SCAN, _HANDLER_ROOT) + dispatcher = dispatch.Dispatcher(_HANDLER_ROOT, _HANDLER_SCAN) + for warning in dispatcher.source_warnings(): + apache.log_error('mod_pywebsocket: %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. + """ + + try: + allowDraft75 = apache.main_server.get_options().get( + _PYOPT_ALLOW_DRAFT75, None) + handshaker = handshake.Handshaker(request, _dispatcher, + allowDraft75=allowDraft75) + handshaker.do_handshake() + request.log_error('mod_pywebsocket: resource: %r' % request.ws_resource, + apache.APLOG_DEBUG) + try: + _dispatcher.transfer_data(request) + except Exception, e: + # Catch exception in transfer_data. + # In this case, handshake has been successful, so just log the + # exception and return apache.DONE + request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_WARNING) + except handshake.HandshakeError, e: + # Handshake for ws/wss failed. + # But the request can be valid http/https request. + request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) + return apache.DECLINED + except dispatch.DispatchError, e: + request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_WARNING) + return apache.DECLINED + return apache.DONE # Return DONE such that no other handlers are invoked. + + +# vi:sts=4 sw=4 et
new file mode 100644 --- /dev/null +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/memorizingfile.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# +# 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. + + +"""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 + + def __getattribute__(self, name): + if name in ('_file', '_memorized_lines', '_max_memorized_lines', + 'readline', 'get_memorized_lines'): + return object.__getattribute__(self, name) + return self._file.__getattribute__(name) + + def readline(self): + """Override file.readline and memorize the line read.""" + + line = self._file.readline() + if line and len(self._memorized_lines) < self._max_memorized_lines: + self._memorized_lines.append(line) + return line + + def get_memorized_lines(self): + """Get lines memorized so far.""" + return self._memorized_lines + + +# vi:sts=4 sw=4 et
new file mode 100644 --- /dev/null +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/msgutil.py @@ -0,0 +1,290 @@ +# 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. + + +"""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 + +from mod_pywebsocket import util + + +class MsgUtilException(Exception): + pass + + +class ConnectionTerminatedException(MsgUtilException): + pass + + +def _read(request, length): + bytes = request.connection.read(length) + if not bytes: + raise MsgUtilException( + 'Failed to receive message from %r' % + (request.connection.remote_addr,)) + return bytes + + +def _write(request, bytes): + try: + request.connection.write(bytes) + except Exception, e: + util.prepend_message_to_exception( + 'Failed to send message to %r: ' % + (request.connection.remote_addr,), + e) + raise + + +def close_connection(request): + """Close connection. + + Args: + request: mod_python request. + """ + if request.server_terminated: + return + # 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. + _write(request, '\xff\x00') + request.server_terminated = True + # TODO(ukai): 2. wait until the /client terminated/ flag has been set, or + # until a server-defined timeout expires. + # TODO: 3. close the WebSocket connection. + # note: mod_python Connection (mp_conn) doesn't have close method. + + +def send_message(request, message): + """Send message. + + Args: + request: mod_python request. + message: unicode string to send. + Raises: + ConnectionTerminatedException: when server already terminated. + """ + if request.server_terminated: + raise ConnectionTerminatedException + _write(request, '\x00' + message.encode('utf-8') + '\xff') + + +def receive_message(request): + """Receive a Web Socket frame and return its payload as unicode string. + + Args: + request: mod_python request. + Raises: + ConnectionTerminatedException: when client already terminated. + """ + + if request.client_terminated: + raise ConnectionTerminatedException + 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 = _read(request, 1) + frame_type = ord(frame_type_str[0]) + if (frame_type & 0x80) == 0x80: + # The payload length is specified in the frame. + # Read and discard. + length = _payload_length(request) + _receive_bytes(request, length) + # 5.3 3. 12. if /type/ is 0xFF and /length/ is 0, then set the + # /client terminated/ flag and abort these steps. + if frame_type == 0xFF and length == 0: + request.client_terminated = True + raise ConnectionTerminatedException + else: + # The payload is delimited with \xff. + bytes = _read_until(request, '\xff') + # The Web Socket 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 _payload_length(request): + length = 0 + while True: + b_str = _read(request, 1) + b = ord(b_str[0]) + length = length * 128 + (b & 0x7f) + if (b & 0x80) == 0: + break + return length + + +def _receive_bytes(request, length): + bytes = [] + while length > 0: + new_bytes = _read(request, length) + bytes.append(new_bytes) + length -= len(new_bytes) + return ''.join(bytes) + + +def _read_until(request, delim_char): + bytes = [] + while True: + ch = _read(request, 1) + if ch == delim_char: + break + bytes.append(ch) + return ''.join(bytes) + + +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
new file mode 100644 --- /dev/null +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/util.py @@ -0,0 +1,121 @@ +# 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. + + +"""Web Sockets utilities. +""" + + +import StringIO +import os +import re +import traceback + + +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 + + +# vi:sts=4 sw=4 et
new file mode 100644 --- /dev/null +++ b/testing/mochitest/pywebsocket/standalone.py @@ -0,0 +1,472 @@ +#!/usr/bin/env python +# +# 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. + + +"""Standalone Web Socket server. + +Use this server to run mod_pywebsocket without Apache HTTP Server. + +Usage: + python standalone.py [-p <ws_port>] [-w <websock_handlers>] + [-s <scan_dir>] + [-d <document_root>] + [-m <websock_handlers_map_file>] + ... for other options, see _main below ... + +<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 Web Socket handlers. +See __init__.py for details of <websock_handlers> and how to write Web Socket +handlers. If this path is relative, <document_root> is used as the base. + +<scan_dir> is a path under the root directory. If specified, only the handlers +under scan_dir are scanned. This is useful in saving scan time. + +Note: +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 logging +import logging.handlers +import optparse +import os +import re +import socket +import sys + +_HAS_OPEN_SSL = False +try: + import OpenSSL.SSL + _HAS_OPEN_SSL = True +except ImportError: + pass + +from mod_pywebsocket import dispatch +from mod_pywebsocket import handshake +from mod_pywebsocket import memorizingfile +from mod_pywebsocket import util + + +_LOG_LEVELS = { + 'debug': logging.DEBUG, + 'info': logging.INFO, + 'warn': logging.WARN, + 'error': logging.ERROR, + 'critical': logging.CRITICAL}; + +_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 + +def _print_warnings_if_any(dispatcher): + warnings = dispatcher.source_warnings() + if warnings: + for warning in warnings: + logging.warning('mod_pywebsocket: %s' % warning) + + +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._request_handler = request_handler + self.connection = _StandaloneConnection(request_handler) + self._use_tls = use_tls + + def get_uri(self): + """Getter to mimic request.uri.""" + return self._request_handler.path + uri = property(get_uri) + + def get_method(self): + """Getter to mimic request.method.""" + return self._request_handler.command + method = property(get_method) + + def get_headers_in(self): + """Getter to mimic request.headers_in.""" + return self._request_handler.headers + headers_in = property(get_headers_in) + + def is_https(self): + """Mimic request.is_https().""" + return self._use_tls + + +class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): + """HTTPServer specialized for Web Socket.""" + + SocketServer.ThreadingMixIn.daemon_threads = True + + def __init__(self, server_address, RequestHandlerClass): + """Override SocketServer.BaseServer.__init__.""" + + SocketServer.BaseServer.__init__( + self, server_address, RequestHandlerClass) + self.socket = self._create_socket() + self.server_bind() + self.server_activate() + + def _create_socket(self): + socket_ = socket.socket(self.address_family, self.socket_type) + if WebSocketServer.options.use_tls: + ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) + ctx.use_privatekey_file(WebSocketServer.options.private_key) + ctx.use_certificate_file(WebSocketServer.options.certificate) + socket_ = OpenSSL.SSL.Connection(ctx, socket_) + return socket_ + + def handle_error(self, rquest, client_address): + """Override SocketServer.handle_error.""" + + logging.error( + ('Exception in processing request from: %r' % (client_address,)) + + '\n' + util.get_stack_trace()) + # Note: client_address is a tuple. To match it against %r, we need the + # trailing comma. + + +class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler): + """CGIHTTPRequestHandler specialized for Web Socket.""" + + def setup(self): + """Override SocketServer.StreamRequestHandler.setup.""" + + self.connection = self.request + self.rfile = memorizingfile.MemorizingFile( + socket._fileobject(self.request, 'rb', self.rbufsize), + max_memorized_lines=_MAX_MEMORIZED_LINES) + self.wfile = socket._fileobject(self.request, 'wb', self.wbufsize) + + def __init__(self, *args, **keywords): + self._request = _StandaloneRequest( + self, WebSocketRequestHandler.options.use_tls) + self._dispatcher = WebSocketRequestHandler.options.dispatcher + self._print_warnings_if_any() + self._handshaker = handshake.Handshaker( + self._request, self._dispatcher, + allowDraft75=WebSocketRequestHandler.options.allow_draft75, + strict=WebSocketRequestHandler.options.strict) + CGIHTTPServer.CGIHTTPRequestHandler.__init__( + self, *args, **keywords) + + def _print_warnings_if_any(self): + warnings = self._dispatcher.source_warnings() + if warnings: + for warning in warnings: + logging.warning('mod_pywebsocket: %s' % warning) + + def parse_request(self): + """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request. + + Return True to continue processing for HTTP(S), False otherwise. + """ + result = CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self) + if result: + try: + self._handshaker.do_handshake() + try: + self._dispatcher.transfer_data(self._request) + except Exception, e: + # Catch exception in transfer_data. + # In this case, handshake has been successful, so just log + # the exception and return False. + logging.info('mod_pywebsocket: %s' % e) + return False + except handshake.HandshakeError, e: + # Handshake for ws(s) failed. Assume http(s). + logging.info('mod_pywebsocket: %s' % e) + return True + except dispatch.DispatchError, e: + logging.warning('mod_pywebsocket: %s' % e) + return False + except Exception, e: + logging.warning('mod_pywebsocket: %s' % e) + logging.info('mod_pywebsocket: %s' % util.get_stack_trace()) + return False + return result + + def log_request(self, code='-', size='-'): + """Override BaseHTTPServer.log_request.""" + + logging.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. + logging.warn('%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 _configure_logging(options): + logger = logging.getLogger() + logger.setLevel(_LOG_LEVELS[options.log_level]) + 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) + +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.DispatchError, e: + logging.error(str(e)) + finally: + fp.close() + + + +def _main(): + parser = optparse.OptionParser() + parser.add_option('-H', '--server-host', '--server_host', + dest='server_host', + default='', + help='server hostname to listen to') + parser.add_option('-p', '--port', dest='port', type='int', + default=handshake.DEFAULT_WEB_SOCKET_PORT, + help='port to listen to') + parser.add_option('-w', '--websock-handlers', '--websock_handlers', + dest='websock_handlers', + default='.', + help='Web Socket handlers root directory.') + parser.add_option('-m', '--websock-handlers-map-file', + '--websock_handlers_map_file', + dest='websock_handlers_map_file', + default=None, + help=('Web Socket 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=('Web Socket handlers scan directory. ' + 'Must be a directory 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('-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('-l', '--log-file', '--log_file', dest='log_file', + default='', help='Log file.') + parser.add_option('--log-level', '--log_level', type='choice', + dest='log_level', default='warn', + choices=['debug', 'info', 'warn', 'error', 'critical'], + help='Log level.') + 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='Allow draft 75 handshake') + parser.add_option('--strict', dest='strict', action='store_true', + default=False, help='Strictly check handshake request') + parser.add_option('-q', '--queue', dest='request_queue_size', type='int', + default=_DEFAULT_REQUEST_QUEUE_SIZE, + help='request queue size') + options = parser.parse_args()[0] + + os.chdir(options.document_root) + + _configure_logging(options) + + SocketServer.TCPServer.request_queue_size = options.request_queue_size + CGIHTTPServer.CGIHTTPRequestHandler.cgi_directories = [] + + if options.cgi_paths: + CGIHTTPServer.CGIHTTPRequestHandler.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) + CGIHTTPServer.executable = __check_script + + if options.use_tls: + if not _HAS_OPEN_SSL: + logging.critical('To use TLS, install pyOpenSSL.') + 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 not options.scan_dir: + options.scan_dir = options.websock_handlers + + try: + # 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) + if options.websock_handlers_map_file: + _alias_handlers(options.dispatcher, + options.websock_handlers_map_file) + _print_warnings_if_any(options.dispatcher) + + WebSocketRequestHandler.options = options + WebSocketServer.options = options + + server = WebSocketServer((options.server_host, options.port), + WebSocketRequestHandler) + server.serve_forever() + except Exception, e: + logging.critical(str(e)) + sys.exit(1) + + +if __name__ == '__main__': + _main() + + +# vi:sts=4 sw=4 et
--- a/testing/mochitest/runtests.py.in +++ b/testing/mochitest/runtests.py.in @@ -260,16 +260,18 @@ See <http://mochikit.com/doc/html/MochiK options.utilityPath = mochitest.getFullPath(options.utilityPath) options.certPath = mochitest.getFullPath(options.certPath) if options.symbolsPath and not isURL(options.symbolsPath): options.symbolsPath = mochitest.getFullPath(options.symbolsPath) options.webServer = self._automation.DEFAULT_WEB_SERVER options.httpPort = self._automation.DEFAULT_HTTP_PORT options.sslPort = self._automation.DEFAULT_SSL_PORT + options.webSocketPort = self._automation.DEFAULT_WEBSOCKET_PORT + options.webSocketProxyPort = self._automation.DEFAULT_WEBSOCKET_PROXY_PORT if options.vmwareRecording: if not self._automation.IS_WIN32: self.error("use-vmware-recording is only supported on Windows.") mochitest.vmwareHelperPath = os.path.join( options.utilityPath, VMWARE_RECORDING_HELPER_BASENAME + ".dll") if not os.path.exists(mochitest.vmwareHelperPath): self.error("%s not found, cannot automate VMware recording." % @@ -338,16 +340,37 @@ class MochitestServer: try: c = urllib2.urlopen(self.shutdownURL) c.read() c.close() self._process.wait() except: self._process.kill() +class WebSocketServer(object): + "Class which encapsulates the mod_pywebsocket server" + + def __init__(self, automation, options, scriptdir): + self.port = options.webSocketPort + self._automation = automation + self._scriptdir = scriptdir + + def start(self): + script = os.path.join(self._scriptdir, 'pywebsocket/standalone.py') + cmd = [sys.executable, script, '-p', str(self.port), '-w', self._scriptdir, '-l', os.path.join(self._scriptdir, "websock.log"), '--log-level=debug'] + + self._process = self._automation.Process(cmd) + pid = self._process.pid + if pid < 0: + print "Error starting websocket server." + sys.exit(2) + self._automation.log.info("INFO | runtests.py | Websocket server pid: %d", pid) + + def stop(self): + self._process.kill() class Mochitest(object): # Path to the test script on the server TEST_PATH = "/tests/" CHROME_PATH = "/redirect.html"; A11Y_PATH = "/redirect-a11y.html" urlOpts = [] runSSLTunnel = True @@ -384,16 +407,30 @@ class Mochitest(object): elif options.a11y: testURL = testHost + self.A11Y_PATH if options.testPath: self.urlOpts.append("testPath=" + encodeURIComponent(options.testPath)) elif options.browserChrome: testURL = "about:blank" return testURL + def startWebSocketServer(self, options): + """ Launch the websocket server """ + if options.webServer != '127.0.0.1': + return + + self.wsserver = WebSocketServer(self.automation, options, self.SCRIPT_DIRECTORY) + self.wsserver.start() + + def stopWebSocketServer(self, options): + if options.webServer != '127.0.0.1': + return + + self.wsserver.stop() + def startWebServer(self, options): if options.webServer != '127.0.0.1': return """ Create the webserver and start it up """ self.server = MochitestServer(self.automation, options) self.server.start() @@ -542,17 +579,17 @@ class Mochitest(object): self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log") browserEnv = self.buildBrowserEnv(options) if (browserEnv == None): return 1 manifest = self.buildProfile(options) self.startWebServer(options) - + self.startWebSocketServer(options) testURL = self.buildTestPath(options) self.buildURLOptions(options) if (len(self.urlOpts) > 0): testURL += "?" + "&".join(self.urlOpts) self.runExtensionRegistration(options, browserEnv) @@ -582,16 +619,17 @@ class Mochitest(object): debuggerInfo=debuggerInfo, symbolsPath=options.symbolsPath, timeout = timeout) if options.vmwareRecording: self.stopVMwareRecording(); self.stopWebServer(options) + self.stopWebSocketServer(options) processLeakLog(self.leak_report_file, options.leakThreshold) self.automation.log.info("\nINFO | runtests.py | Running tests: end.") self.cleanup(manifest, options) return status def makeTestConfig(self, options): "Creates a test configuration file for customizing test execution." @@ -685,13 +723,17 @@ def main(): if options == None: sys.exit(1) options.utilityPath = mochitest.getFullPath(options.utilityPath) options.certPath = mochitest.getFullPath(options.certPath) if options.symbolsPath and not isURL(options.symbolsPath): options.symbolsPath = mochitest.getFullPath(options.symbolsPath) - automation.setServerInfo(options.webServer, options.httpPort, options.sslPort) + automation.setServerInfo(options.webServer, + options.httpPort, + options.sslPort, + options.webSocketPort, + options.webSocketProxyPort) sys.exit(mochitest.runTests(options)) if __name__ == "__main__": main()
--- a/testing/mochitest/ssltunnel/ssltunnel.cpp +++ b/testing/mochitest/ssltunnel/ssltunnel.cpp @@ -116,27 +116,36 @@ enum client_auth_option { }; // Structs for passing data into jobs on the thread pool typedef struct { PRInt32 listen_port; string cert_nickname; PLHashTable* host_cert_table; PLHashTable* host_clientauth_table; + // If not empty, and this server is using HTTP CONNECT, connections + // will be proxied to this address. + PRNetAddr remote_addr; + // True if no SSL should be used for this server's connections. + bool http_proxy_only; + // The original host in the Host: header for the initial connection is + // stored here, for proxied connections. + string original_host; } server_info_t; typedef struct { PRFileDesc* client_sock; PRNetAddr client_addr; server_info_t* server_info; } connection_info_t; const PRInt32 BUF_SIZE = 16384; const PRInt32 BUF_MARGIN = 1024; const PRInt32 BUF_TOTAL = BUF_SIZE + BUF_MARGIN; +const char HEADER_HOST[] = "Host:"; struct relayBuffer { char *buffer, *bufferhead, *buffertail, *bufferend; relayBuffer() { // Leave 1024 bytes more for request line manipulations @@ -249,17 +258,17 @@ bool ReadConnectRequest(server_info_t* s if (strncmp(buffer.buffertail-4, "\r\n\r\n", 4)) { printf(" !! request is not tailed with CRLFCRLF but with %x %x %x %x", *(buffer.buffertail-4), *(buffer.buffertail-3), *(buffer.buffertail-2), *(buffer.buffertail-1)); return false; } - + printf(" parsing initial connect request, dump:\n%.*s\n", (int)buffer.present(), buffer.bufferhead); *result = 400; char* token; char* _caret; token = strtok2(buffer.bufferhead, " ", &_caret); if (!token) { @@ -337,16 +346,110 @@ bool ConfigureSSLServerSocket(PRFileDesc } SSL_ResetHandshake(ssl_socket, PR_TRUE); return true; } /** + * This function examines the buffer for a S5ec-WebSocket-Location: field, + * and if it's present, it replaces the hostname in that field with the + * value in the server's original_host field. This function works + * in the reverse direction as AdjustHost(), replacing the real hostname + * of a response with the potentially fake hostname that is expected + * by the browser (e.g., mochi.test). + * + * @return true if the header was adjusted successfully, or not found, false + * if the header is present but the url is not, which should indicate + * that more data needs to be read from the socket + */ +bool AdjustWebSocketLocation(relayBuffer& buffer, server_info_t *si) +{ + assert(buffer.margin()); + buffer.buffertail[1] = '\0'; + + char* wsloc = strstr(buffer.bufferhead, "Sec-WebSocket-Location:"); + if (!wsloc) + return true; + // advance pointer to the start of the hostname + wsloc = strstr(wsloc, "ws://"); + if (!wsloc) + return false; + wsloc += 5; + // find the end of the hostname + char* wslocend = strchr(wsloc + 1, '/'); + if (!wslocend) + return false; + char *crlf = strstr(wsloc, "\r\n"); + if (!crlf) + return false; + if (si->original_host.empty()) + return true; + + int diff = si->original_host.length() - (wslocend-wsloc); + if (diff > 0) + assert(size_t(diff) <= buffer.margin()); + memmove(wslocend + diff, wslocend, buffer.buffertail - wsloc - diff); + buffer.buffertail += diff; + + memcpy(wsloc, si->original_host.c_str(), si->original_host.length()); + return true; +} + +/** + * This function examines the buffer for a Host: field, and if it's present, + * it replaces the hostname in that field with the hostname in the server's + * remote_addr field. This is needed because proxy requests may be coming + * from mochitest with fake hosts, like mochi.test, and these need to be + * replaced with the host that the destination server is actually running + * on. + */ +bool AdjustHost(relayBuffer& buffer, server_info_t *si) +{ + if (!si->remote_addr.inet.port) + return false; + + assert(buffer.margin()); + + // Cannot use strnchr so add a null char at the end. There is always some + // space left because we preserve a margin. + buffer.buffertail[1] = '\0'; + + char* host = strstr(buffer.bufferhead, HEADER_HOST); + if (!host) + return false; + // advance pointer to beginning of hostname + host += strlen(HEADER_HOST); + host += strspn(host, " \t"); + + char* endhost = strstr(host, "\r\n"); + if (!endhost) + return false; + + // Save the original host, so we can use it later on responses from the + // server. + si->original_host.assign(host, endhost-host); + + char newhost[40]; + PR_NetAddrToString(&si->remote_addr, newhost, sizeof(newhost)); + assert(strlen(newhost) < sizeof(newhost) - 7); + sprintf(newhost, "%s:%d", newhost, PR_ntohs(si->remote_addr.inet.port)); + + int diff = strlen(newhost) - (endhost-host); + if (diff > 0) + assert(size_t(diff) <= buffer.margin()); + memmove(endhost + diff, endhost, buffer.buffertail - host - diff); + buffer.buffertail += diff; + + memcpy(host, newhost, strlen(newhost)); + return true; +} + +/** * This function prefixes Request-URI path with a full scheme-host-port * string. */ bool AdjustRequestURI(relayBuffer& buffer, string *host) { assert(buffer.margin()); // Cannot use strnchr so add a null char at the end. There is always some space left @@ -424,17 +527,18 @@ void HandleConnection(void* data) if (other_sock) { PRInt32 numberOfSockets = 1; relayBuffer buffers[2]; if (!do_http_proxy) { - if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, caNone)) + if (!ci->server_info->http_proxy_only && + !ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, caNone)) client_error = true; else if (!ConnectSocket(other_sock, &remote_addr, connect_timeout)) client_error = true; else numberOfSockets = 2; } PRPollDesc sockets[2] = @@ -561,17 +665,20 @@ void HandleConnection(void* data) sprintf(buffers[s2].buffer, "HTTP/1.1 %d ERROR\r\nConnection: close\r\n\r\n", response); buffers[s2].buffertail = buffers[s2].buffer + strlen(buffers[s2].buffer); break; } strcpy(buffers[s2].buffer, "HTTP/1.1 200 Connected\r\nConnection: keep-alive\r\n\r\n"); buffers[s2].buffertail = buffers[s2].buffer + strlen(buffers[s2].buffer); - if (!ConnectSocket(other_sock, &remote_addr, connect_timeout)) + PRNetAddr* addr = &remote_addr; + if (ci->server_info->remote_addr.inet.port > 0) + addr = &ci->server_info->remote_addr; + if (!ConnectSocket(other_sock, addr, connect_timeout)) { printf(" could not open connection to the real server\n"); client_error = true; break; } printf(" accepted CONNECT request, connected to the server, sending OK to the client\n"); // Send the response to the client socket @@ -582,18 +689,28 @@ void HandleConnection(void* data) { // Do not poll for read when the buffer is full printf(" no place in our read buffer, stop reading"); in_flags &= ~PR_POLL_READ; } if (ssl_updated) { - if (s == 0 && expect_request_start) - expect_request_start = !AdjustRequestURI(buffers[s], &fullHost); + if (s == 0 && expect_request_start) + { + if (ci->server_info->http_proxy_only) + expect_request_start = !AdjustHost(buffers[s], ci->server_info); + else + expect_request_start = !AdjustRequestURI(buffers[s], &fullHost); + } + else + { + if (!AdjustWebSocketLocation(buffers[s], ci->server_info)) + continue; + } in_flags2 |= PR_POLL_WRITE; printf(" telling the other socket to write"); } else printf(" we have something for the other socket to write, but ssl has not been administered on it"); } } // PR_POLL_READ handling @@ -614,34 +731,35 @@ void HandleConnection(void* data) // the main loop, we will never more be able to send it. buffers[s2].bufferhead = buffers[s2].buffertail = buffers[s2].buffer; } else printf(" would block"); } else { - printf(", writen %d bytes", bytesWrite); + printf(", written %d bytes", bytesWrite); buffers[s2].buffertail[1] = '\0'; printf(" dump:\n%.*s\n", bytesWrite, buffers[s2].bufferhead); buffers[s2].bufferhead += bytesWrite; if (buffers[s2].present()) { printf(" still have to write %d bytes", (int)buffers[s2].present()); in_flags |= PR_POLL_WRITE; } else { if (!ssl_updated) { printf(" proxy response sent to the client"); // Proxy response has just been writen, update to ssl ssl_updated = true; - if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, clientAuth)) + if (!ci->server_info->http_proxy_only && + !ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, clientAuth)) { printf(" but failed to config server socket\n"); client_error = true; break; } printf(" client socket updated to SSL"); numberOfSockets = 2; @@ -788,16 +906,45 @@ int processConfigLine(char* configLine) fprintf(stderr, "Invalid remote port: %s\n", serverportstring); return 1; } remote_addr.inet.port = PR_htons(port); return 0; } + if (!strcmp(keyword, "proxy")) + { + server_info_t server; + server.host_cert_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, PL_CompareStrings, NULL, NULL); + server.host_clientauth_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, ClientAuthValueComparator, NULL, NULL); + server.http_proxy_only = true; + + char* listenport = strtok2(_caret, ":", &_caret); + server.listen_port = atoi(listenport); + if (server.listen_port <= 0) { + fprintf(stderr, "Invalid listen port in proxy config: %s\n", listenport); + return 1; + } + char* ipstring = strtok2(_caret, ":", &_caret); + if (PR_StringToNetAddr(ipstring, &server.remote_addr) != PR_SUCCESS) { + fprintf(stderr, "Invalid IP address in proxy config: %s\n", ipstring); + return 1; + } + char* remoteport = strtok2(_caret, ":", &_caret); + int port = atoi(remoteport); + if (port <= 0) { + fprintf(stderr, "Invalid remote port in proxy config: %s\n", remoteport); + return 1; + } + server.remote_addr.inet.port = PR_htons(port); + servers.push_back(server); + return 0; + } + // Configure all listen sockets and port+certificate bindings if (!strcmp(keyword, "listen")) { char* hostname = strtok2(_caret, ":", &_caret); char* hostportstring = NULL; if (strcmp(hostname, "*")) { any_host_spec_config = true; @@ -827,18 +974,20 @@ int processConfigLine(char* configLine) if (!entry) { fprintf(stderr, "Out of memory"); return 1; } } else { server_info_t server; + memset(&server.remote_addr, 0, sizeof(PRNetAddr)); server.cert_nickname = certnick; server.listen_port = port; + server.http_proxy_only = false; server.host_cert_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, PL_CompareStrings, NULL, NULL); if (!server.host_cert_table) { fprintf(stderr, "Internal, could not create hash table\n"); return 1; } server.host_clientauth_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, ClientAuthValueComparator, NULL, NULL); if (!server.host_clientauth_table) @@ -1008,17 +1157,21 @@ int main(int argc, char** argv) " # This only works in httpproxy mode and has higher priority\n" " # than the previous option.\n" " listen:my.host.name:443:4443:a different cert\n\n" " # To make a specific host require or just request a client certificate\n" " # to authenticate use the following options. This can only be used\n" " # in httpproxy mode and only after the 'listen' option has been\n" " # specified. You also have to specify the tunnel listen port.\n" " clientauth:requesting-client-cert.host.com:443:4443:request\n" - " clientauth:requiring-client-cert.host.com:443:4443:require\n", + " clientauth:requiring-client-cert.host.com:443:4443:require\n" + " # Act as a simple proxy for incoming connections on port 7777,\n" + " # tunneling them to the server at 127.0.0.1:9999. Not affected\n" + " # by the 'forward' option.\n" + " proxy:7777:127.0.0.1:9999\n", configFilePath); return 1; } // create a thread pool to handle connections threads = PR_CreateThreadPool(PR_MAX(INITIAL_THREADS, servers.size()*2), PR_MAX(MAX_THREADS, servers.size()*2), DEFAULT_STACKSIZE);