Bug 710345: Upgrade pywebsocket to v606 (RFC 6455). r=mcmanus
authorJason Duell <jduell.mcbugs@gmail.com>
Tue, 20 Dec 2011 00:20:00 -0800
changeset 83089 225f854f8ff13f493f745c23444ce49665b7b5c6
parent 83088 9eda39f28e5a0d027b8ed7c7ce2ccf200224e5b1
child 83090 ccb71819f0c27b16f219020ea90756b8c4d53692
push id4244
push userbmo@edmorley.co.uk
push dateTue, 20 Dec 2011 11:55:04 +0000
treeherdermozilla-inbound@e945a4b34a8a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus
bugs710345
milestone11.0a1
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
Bug 710345: Upgrade pywebsocket to v606 (RFC 6455). r=mcmanus
testing/mochitest/pywebsocket/mod_pywebsocket/__init__.py
testing/mochitest/pywebsocket/mod_pywebsocket/_stream_base.py
testing/mochitest/pywebsocket/mod_pywebsocket/_stream_hybi.py
testing/mochitest/pywebsocket/mod_pywebsocket/common.py
testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py
testing/mochitest/pywebsocket/mod_pywebsocket/extensions.py
testing/mochitest/pywebsocket/mod_pywebsocket/handshake/__init__.py
testing/mochitest/pywebsocket/mod_pywebsocket/handshake/_base.py
testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi.py
testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi00.py
testing/mochitest/pywebsocket/mod_pywebsocket/headerparserhandler.py
testing/mochitest/pywebsocket/mod_pywebsocket/http_header_util.py
testing/mochitest/pywebsocket/mod_pywebsocket/util.py
testing/mochitest/pywebsocket/standalone.py
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/__init__.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/__init__.py
@@ -60,16 +60,22 @@ 1. Specify the following Apache HTTP Ser
    <scan_dir> is useful in saving scan time when <websock_handlers>
    contains many non-WebSocket handler files.
 
    If you want to support old handshake based on
    draft-hixie-thewebsocketprotocol-75:
 
        PythonOption mod_pywebsocket.allow_draft75 On
 
+   If you want to allow handlers whose canonical path is not under the root
+   directory (i.e. symbolic link is in root directory but its target is not),
+   configure as follows:
+
+       PythonOption mod_pywebsocket.allow_handlers_outside_root_dir On
+
    Example snippet of httpd.conf:
    (mod_pywebsocket is in /websock_lib, WebSocket handlers are in
    /websock_handlers, port is 80 for ws, 443 for wss.)
 
        <IfModule python_module>
          PythonPath "sys.path+['/websock_lib']"
          PythonOption mod_pywebsocket.handler_root /websock_handlers
          PythonHeaderParserHandler mod_pywebsocket.headerparserhandler
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/_stream_base.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/_stream_base.py
@@ -106,24 +106,19 @@ class StreamBase(object):
         prepends remote address to the exception message and raise again.
 
         Raises:
             ConnectionTerminatedException: when read returns empty string.
         """
 
         bytes = self._request.connection.read(length)
         if not bytes:
-            # MOZILLA: Patrick McManus found we needed this for Python 2.5 to
-            # work.  Not sure which tests he meant: I found that
-            # content/base/test/test_websocket* all worked fine with 2.5 with
-            # the original Google code. JDuell
-            #raise ConnectionTerminatedException(
-            #    'Receiving %d byte failed. Peer (%r) closed connection' %
-            #    (length, (self._request.connection.remote_addr,)))
-            raise ConnectionTerminatedException('connection terminated: read failed')
+            raise ConnectionTerminatedException(
+                'Receiving %d byte failed. Peer (%r) closed connection' %
+                (length, (self._request.connection.remote_addr,)))
         return bytes
 
     def _write(self, bytes):
         """Writes given bytes to connection. In case we catch any exception,
         prepends remote address to the exception message and raise again.
         """
 
         try:
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/_stream_hybi.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/_stream_hybi.py
@@ -293,30 +293,45 @@ class Stream(StreamBase):
         mask = (second_byte >> 7) & 1
         payload_length = second_byte & 0x7f
 
         if (mask == 1) != self._options.unmask_receive:
             raise InvalidFrameException(
                 'Mask bit on the received frame did\'nt match masking '
                 'configuration for received frames')
 
-        # The spec doesn't disallow putting a value in 0x0-0xFFFF into the
-        # 8-octet extended payload length field (or 0x0-0xFD in 2-octet field).
-        # So, we don't check the range of extended_payload_length.
+        # The Hybi-13 and later specs disallow putting a value in 0x0-0xFFFF
+        # into the 8-octet extended payload length field (or 0x0-0xFD in
+        # 2-octet field).
+        valid_length_encoding = True
+        length_encoding_bytes = 1
         if payload_length == 127:
             extended_payload_length = self.receive_bytes(8)
             payload_length = struct.unpack(
                 '!Q', extended_payload_length)[0]
             if payload_length > 0x7FFFFFFFFFFFFFFF:
                 raise InvalidFrameException(
                     'Extended payload length >= 2^63')
+            if self._request.ws_version >= 13 and payload_length < 0x10000:
+                valid_length_encoding = False
+                length_encoding_bytes = 8
         elif payload_length == 126:
             extended_payload_length = self.receive_bytes(2)
             payload_length = struct.unpack(
                 '!H', extended_payload_length)[0]
+            if self._request.ws_version >= 13 and payload_length < 126:
+                valid_length_encoding = False
+                length_encoding_bytes = 2
+
+        if not valid_length_encoding:
+            self._logger.warning(
+                'Payload length is not encoded using the minimal number of '
+                'bytes (%d is encoded using %d bytes)',
+                payload_length,
+                length_encoding_bytes)
 
         if mask == 1:
             masking_nonce = self.receive_bytes(4)
             masker = util.RepeatedXorMasker(masking_nonce)
         else:
             masker = _NOOP_MASKER
 
         bytes = masker.mask(self.receive_bytes(payload_length))
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/common.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/common.py
@@ -36,19 +36,26 @@ VERSION_HYBI02 = 2
 VERSION_HYBI03 = 2
 VERSION_HYBI04 = 4
 VERSION_HYBI05 = 5
 VERSION_HYBI06 = 6
 VERSION_HYBI07 = 7
 VERSION_HYBI08 = 8
 VERSION_HYBI09 = 8
 VERSION_HYBI10 = 8
+VERSION_HYBI11 = 8
+VERSION_HYBI12 = 8
+VERSION_HYBI13 = 13
+VERSION_HYBI14 = 13
+VERSION_HYBI15 = 13
+VERSION_HYBI16 = 13
+VERSION_HYBI17 = 13
 
 # Constants indicating WebSocket protocol latest version.
-VERSION_HYBI_LATEST = VERSION_HYBI10
+VERSION_HYBI_LATEST = VERSION_HYBI13
 
 # Port numbers
 DEFAULT_WEB_SOCKET_PORT = 80
 DEFAULT_WEB_SOCKET_SECURE_PORT = 443
 
 # Schemes
 WEB_SOCKET_SCHEME = 'ws'
 WEB_SOCKET_SECURE_SCHEME = 'wss'
@@ -90,20 +97,27 @@ DEFLATE_FRAME_EXTENSION = 'deflate-frame
 # Status codes
 # Code STATUS_CODE_NOT_AVAILABLE should not be used in actual frames. This code
 # is exposed to JavaScript API as pseudo status code which represent actual
 # frame does not have status code.
 STATUS_NORMAL = 1000
 STATUS_GOING_AWAY = 1001
 STATUS_PROTOCOL_ERROR = 1002
 STATUS_UNSUPPORTED = 1003
-STATUS_TOO_LARGE = 1004
 STATUS_CODE_NOT_AVAILABLE = 1005
 STATUS_ABNORMAL_CLOSE = 1006
-STATUS_INVALID_UTF8 = 1007
+STATUS_INVALID_FRAME_PAYLOAD = 1007
+STATUS_POLICY_VIOLATION = 1008
+STATUS_MESSAGE_TOO_BIG = 1009
+STATUS_MANDATORY_EXT = 1010
+
+# HTTP status codes
+HTTP_STATUS_BAD_REQUEST = 400
+HTTP_STATUS_FORBIDDEN = 403
+HTTP_STATUS_NOT_FOUND = 404
 
 
 def is_control_opcode(opcode):
     return (opcode >> 3) == 1
 
 
 class ExtensionParameter(object):
     """Holds information about an extension which is exchanged on extension
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py
@@ -49,56 +49,65 @@ from mod_pywebsocket import util
 _TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data'
 _PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME = (
     'web_socket_passive_closing_handshake')
 
 
 class DispatchException(Exception):
     """Exception in dispatching WebSocket request."""
 
-    def __init__(self, name, status=404):
+    def __init__(self, name, status=common.HTTP_STATUS_NOT_FOUND):
         super(DispatchException, self).__init__(name)
         self.status = status
 
 
 def _default_passive_closing_handshake_handler(request):
     """Default web_socket_passive_closing_handshake handler."""
+
     return common.STATUS_NORMAL, ''
 
 
 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)
-
-    # MOZILLA: do not normalize away symlinks in mochitest
-    #path = os.path.realpath(path)
-
+    path = os.path.realpath(path)
     path = path.replace('\\', '/')
     return path
 
 
 def _create_path_to_resource_converter(base_dir):
+    """Returns a function that converts the path of a WebSocket handler source
+    file to a resource string by removing the path to the base directory from
+    its head, removing _SOURCE_SUFFIX from its tail, and replacing path
+    separators in it with '/'.
+
+    Args:
+        base_dir: the path to the base directory.
+    """
+
     base_dir = _normalize_path(base_dir)
 
     base_len = len(base_dir)
     suffix_len = len(_SOURCE_SUFFIX)
 
     def converter(path):
         if not path.endswith(_SOURCE_SUFFIX):
             return None
-        path = _normalize_path(path)
+        # _normalize_path must not be used because resolving symlink breaks
+        # following path check.
+        path = path.replace('\\', '/')
         if not path.startswith(base_dir):
             return None
         return path[base_len:-suffix_len]
 
     return converter
 
 
 def _enumerate_handler_file_paths(directory):
@@ -164,41 +173,46 @@ def _extract_handler(dic, name):
 
 
 class Dispatcher(object):
     """Dispatches WebSocket requests.
 
     This class maintains a map from resource name to handlers.
     """
 
-    def __init__(self, root_dir, scan_dir=None):
+    def __init__(
+        self, root_dir, scan_dir=None,
+        allow_handlers_outside_root_dir=True):
         """Construct an instance.
 
         Args:
             root_dir: The directory where handler definition files are
                       placed.
             scan_dir: The directory where handler definition files are
                       searched. scan_dir must be a directory under root_dir,
                       including root_dir itself.  If scan_dir is None,
                       root_dir is used as scan_dir. scan_dir can be useful
                       in saving scan time when root_dir contains many
                       subdirectories.
+            allow_handlers_outside_root_dir: Scans handler files even if their
+                      canonical path is not under root_dir.
         """
 
         self._logger = util.get_class_logger(self)
 
         self._handler_suite_map = {}
         self._source_warnings = []
         if scan_dir is None:
             scan_dir = root_dir
         if not os.path.realpath(scan_dir).startswith(
                 os.path.realpath(root_dir)):
             raise DispatchException('scan_dir:%s must be a directory under '
                                     'root_dir:%s.' % (scan_dir, root_dir))
-        self._source_handler_files_in_dir(root_dir, scan_dir)
+        self._source_handler_files_in_dir(
+            root_dir, scan_dir, allow_handlers_outside_root_dir)
 
     def add_resource_path_alias(self,
                                 alias_resource_path, existing_resource_path):
         """Add resource path alias.
 
         Once added, request to alias_resource_path would be handled by
         handler registered for existing_resource_path.
 
@@ -242,17 +256,17 @@ class Dispatcher(object):
         except handshake.AbortedByUserException, e:
             raise
         except Exception, e:
             util.prepend_message_to_exception(
                     '%s raised exception for %s: ' % (
                             _DO_EXTRA_HANDSHAKE_HANDLER_NAME,
                             request.ws_resource),
                     e)
-            raise handshake.HandshakeException(e, 403)
+            raise handshake.HandshakeException(e, common.HTTP_STATUS_FORBIDDEN)
 
     def transfer_data(self, request):
         """Let a handler transfer_data with a WebSocket client.
 
         Select a handler based on request.ws_resource and call its
         web_socket_transfer_data function.
 
         Args:
@@ -283,18 +297,19 @@ class Dispatcher(object):
             # InvalidFrameException must be caught before
             # ConnectionTerminatedException that catches InvalidFrameException.
             self._logger.debug('%s', e)
             request.ws_stream.close_connection(common.STATUS_PROTOCOL_ERROR)
         except msgutil.UnsupportedFrameException, e:
             self._logger.debug('%s', e)
             request.ws_stream.close_connection(common.STATUS_UNSUPPORTED)
         except stream.InvalidUTF8Exception, e:
-            self._logger_debug('%s', e)
-            request.ws_stream.close_connection(common.STATUS_INVALID_UTF8)
+            self._logger.debug('%s', e)
+            request.ws_stream.close_connection(
+                common.STATUS_INVALID_FRAME_PAYLOAD)
         except msgutil.ConnectionTerminatedException, e:
             self._logger.debug('%s', e)
         except Exception, e:
             util.prepend_message_to_exception(
                 '%s raised exception for %s: ' % (
                     _TRANSFER_DATA_HANDLER_NAME, request.ws_resource),
                 e)
             raise
@@ -317,28 +332,50 @@ class Dispatcher(object):
         fragment = None
         if '#' in resource:
             resource, fragment = resource.split('#', 1)
         if '?' in resource:
             resource = resource.split('?', 1)[0]
         handler_suite = self._handler_suite_map.get(resource)
         if handler_suite and fragment:
             raise DispatchException('Fragment identifiers MUST NOT be used on '
-                                    'WebSocket URIs', 400);
+                                    'WebSocket URIs',
+                                    common.HTTP_STATUS_BAD_REQUEST)
         return handler_suite
 
-    def _source_handler_files_in_dir(self, root_dir, scan_dir):
+    def _source_handler_files_in_dir(
+        self, root_dir, scan_dir, allow_handlers_outside_root_dir):
         """Source all the handler source files in the scan_dir directory.
 
         The resource path is determined relative to root_dir.
         """
 
+        # We build a map from resource to handler code assuming that there's
+        # only one path from root_dir to scan_dir and it can be obtained by
+        # comparing realpath of them.
+
+        # Here we cannot use abspath. See
+        # https://bugs.webkit.org/show_bug.cgi?id=31603
+
         convert = _create_path_to_resource_converter(root_dir)
-        for path in _enumerate_handler_file_paths(scan_dir):
+        scan_realpath = os.path.realpath(scan_dir)
+        root_realpath = os.path.realpath(root_dir)
+        for path in _enumerate_handler_file_paths(scan_realpath):
+            if (not allow_handlers_outside_root_dir and
+                (not os.path.realpath(path).startswith(root_realpath))):
+                self._logger.debug(
+                    'Canonical path of %s is not under root directory' %
+                    path)
+                continue
             try:
                 handler_suite = _source_handler_file(open(path).read())
             except DispatchException, e:
                 self._source_warnings.append('%s: %s' % (path, e))
                 continue
-            self._handler_suite_map[convert(path)] = handler_suite
+            resource = convert(path)
+            if resource is None:
+                self._logger.debug(
+                    'Path to resource conversion on %s failed' % path)
+            else:
+                self._handler_suite_map[convert(path)] = handler_suite
 
 
 # vi:sts=4 sw=4 et
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/extensions.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/extensions.py
@@ -31,16 +31,17 @@
 from mod_pywebsocket import common
 from mod_pywebsocket import util
 
 
 _available_processors = {}
 
 
 class ExtensionProcessorInterface(object):
+
     def get_extension_response(self):
         return None
 
     def setup_stream_options(self, stream_options):
         pass
 
 
 class DeflateStreamExtensionProcessor(ExtensionProcessorInterface):
@@ -126,24 +127,27 @@ class DeflateFrameExtensionProcessor(Ext
              window_bits,
              no_context_takeover,
              self._response_window_bits,
              self._response_no_context_takeover))
 
         return response
 
     def setup_stream_options(self, stream_options):
+
         class _OutgoingFilter(object):
+
             def __init__(self, parent):
                 self._parent = parent
 
             def filter(self, frame):
                 self._parent._outgoing_filter(frame)
 
         class _IncomingFilter(object):
+
             def __init__(self, parent):
                 self._parent = parent
 
             def filter(self, frame):
                 self._parent._incoming_filter(frame)
 
         stream_options.outgoing_frame_filters.append(
             _OutgoingFilter(self))
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/__init__.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/__init__.py
@@ -31,22 +31,25 @@
 """WebSocket opening handshake processor. This class try to apply available
 opening handshake processors for each protocol version until a connection is
 successfully established.
 """
 
 
 import logging
 
+from mod_pywebsocket import common
 from mod_pywebsocket.handshake import draft75
 from mod_pywebsocket.handshake import hybi00
 from mod_pywebsocket.handshake import hybi
-# Export AbortedByUserException and HandshakeException symbol from this module.
+# Export AbortedByUserException, HandshakeException, and VersionException
+# symbol from this module.
 from mod_pywebsocket.handshake._base import AbortedByUserException
 from mod_pywebsocket.handshake._base import HandshakeException
+from mod_pywebsocket.handshake._base import VersionException
 
 
 _LOGGER = logging.getLogger(__name__)
 
 
 def do_handshake(request, dispatcher, allowDraft75=False, strict=False):
     """Performs WebSocket handshake.
 
@@ -57,52 +60,57 @@ def do_handshake(request, dispatcher, al
         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.
     """
 
-    _LOGGER.debug('Opening handshake resource: %r', request.uri)
+    _LOGGER.debug('Client\'s opening handshake resource: %r', request.uri)
     # To print mimetools.Message as escaped one-line string, we converts
     # headers_in to dict object. Without conversion, if we use %r, it just
     # prints the type and address, and if we use %s, it prints the original
     # header string as multiple lines.
     #
     # Both mimetools.Message and MpTable_Type of mod_python can be
     # converted to dict.
     #
     # mimetools.Message.__str__ returns the original header string.
     # dict(mimetools.Message object) returns the map from header names to
     # header values. While MpTable_Type doesn't have such __str__ but just
     # __repr__ which formats itself as well as dictionary object.
     _LOGGER.debug(
-        'Opening handshake request headers: %r', dict(request.headers_in))
+        'Client\'s opening handshake headers: %r', dict(request.headers_in))
 
     handshakers = []
     handshakers.append(
         ('IETF HyBi latest', hybi.Handshaker(request, dispatcher)))
     handshakers.append(
         ('IETF HyBi 00', hybi00.Handshaker(request, dispatcher)))
     if allowDraft75:
         handshakers.append(
             ('IETF Hixie 75', draft75.Handshaker(request, dispatcher, strict)))
 
     for name, handshaker in handshakers:
-        _LOGGER.info('Trying %s protocol', name)
+        _LOGGER.debug('Trying %s protocol', name)
         try:
             handshaker.do_handshake()
+            _LOGGER.info('Established (%s protocol)', name)
             return
         except HandshakeException, e:
-            _LOGGER.info(
+            _LOGGER.debug(
                 'Failed to complete opening handshake as %s protocol: %r',
                 name, e)
             if e.status:
                 raise e
         except AbortedByUserException, e:
             raise
+        except VersionException, e:
+            raise
 
+    # TODO(toyoshim): Add a test to cover the case all handshakers fail.
     raise HandshakeException(
-        'Failed to complete opening handshake for all available protocols')
+        'Failed to complete opening handshake for all available protocols',
+        status=common.HTTP_STATUS_BAD_REQUEST)
 
 
 # vi:sts=4 sw=4 et
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/_base.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/_base.py
@@ -56,16 +56,32 @@ class HandshakeException(Exception):
     WebSocket initial handshake.
     """
 
     def __init__(self, name, status=None):
         super(HandshakeException, self).__init__(name)
         self.status = status
 
 
+class VersionException(Exception):
+    """This exception will be raised when a version of client request does not
+    match with version the server supports.
+    """
+
+    def __init__(self, name, supported_versions=''):
+        """Construct an instance.
+
+        Args:
+            supported_version: a str object to show supported hybi versions.
+                               (e.g. '8, 13')
+        """
+        super(VersionException, self).__init__(name)
+        self.supported_versions = supported_versions
+
+
 def get_default_port(is_secure):
     if is_secure:
         return common.DEFAULT_WEB_SOCKET_SECURE_PORT
     else:
         return common.DEFAULT_WEB_SOCKET_PORT
 
 
 def validate_subprotocol(subprotocol, hixie):
@@ -195,77 +211,81 @@ def parse_token_list(data):
         http_header_util.consume_lwses(state)
 
     if len(token_list) == 0:
         raise HandshakeException('No valid token found')
 
     return token_list
 
 
-def _parse_extension_param(state, definition):
+def _parse_extension_param(state, definition, allow_quoted_string):
     param_name = http_header_util.consume_token(state)
 
     if param_name is None:
         raise HandshakeException('No valid parameter name found')
 
     http_header_util.consume_lwses(state)
 
     if not http_header_util.consume_string(state, '='):
         definition.add_parameter(param_name, None)
         return
 
     http_header_util.consume_lwses(state)
 
-    param_value = http_header_util.consume_token_or_quoted_string(state)
+    if allow_quoted_string:
+        # TODO(toyoshim): Add code to validate that parsed param_value is token
+        param_value = http_header_util.consume_token_or_quoted_string(state)
+    else:
+        param_value = http_header_util.consume_token(state)
     if param_value is None:
         raise HandshakeException(
             'No valid parameter value found on the right-hand side of '
             'parameter %r' % param_name)
 
     definition.add_parameter(param_name, param_value)
 
 
-def _parse_extension(state):
+def _parse_extension(state, allow_quoted_string):
     extension_token = http_header_util.consume_token(state)
     if extension_token is None:
         return None
 
     extension = common.ExtensionParameter(extension_token)
 
     while True:
         http_header_util.consume_lwses(state)
 
         if not http_header_util.consume_string(state, ';'):
             break
 
         http_header_util.consume_lwses(state)
 
         try:
-            _parse_extension_param(state, extension)
+            _parse_extension_param(state, extension, allow_quoted_string)
         except HandshakeException, e:
             raise HandshakeException(
                 'Failed to parse Sec-WebSocket-Extensions header: '
                 'Failed to parse parameter for %r (%r)' %
                 (extension_token, e))
 
     return extension
 
 
-def parse_extensions(data):
+def parse_extensions(data, allow_quoted_string=False):
     """Parses Sec-WebSocket-Extensions header value returns a list of
     common.ExtensionParameter objects.
 
     Leading LWSes must be trimmed.
     """
 
     state = http_header_util.ParsingState(data)
 
     extension_list = []
     while True:
-        extension = _parse_extension(state)
+        extension = _parse_extension(state, allow_quoted_string)
         if extension is not None:
             extension_list.append(extension)
 
         http_header_util.consume_lwses(state)
 
         if http_header_util.peek(state) is None:
             break
 
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi.py
@@ -48,23 +48,34 @@ from mod_pywebsocket.handshake._base imp
 from mod_pywebsocket.handshake._base import format_extensions
 from mod_pywebsocket.handshake._base import format_header
 from mod_pywebsocket.handshake._base import get_mandatory_header
 from mod_pywebsocket.handshake._base import HandshakeException
 from mod_pywebsocket.handshake._base import parse_extensions
 from mod_pywebsocket.handshake._base import parse_token_list
 from mod_pywebsocket.handshake._base import validate_mandatory_header
 from mod_pywebsocket.handshake._base import validate_subprotocol
+from mod_pywebsocket.handshake._base import VersionException
 from mod_pywebsocket.stream import Stream
 from mod_pywebsocket.stream import StreamOptions
 from mod_pywebsocket import util
 
 
 _BASE64_REGEX = re.compile('^[+/0-9A-Za-z]*=*$')
 
+# Defining aliases for values used frequently.
+_VERSION_HYBI08 = common.VERSION_HYBI08
+_VERSION_HYBI08_STRING = str(_VERSION_HYBI08)
+_VERSION_LATEST = common.VERSION_HYBI_LATEST
+_VERSION_LATEST_STRING = str(_VERSION_LATEST)
+_SUPPORTED_VERSIONS = [
+    _VERSION_LATEST,
+    _VERSION_HYBI08,
+]
+
 
 def compute_accept(key):
     """Computes value for the Sec-WebSocket-Accept header from value of the
     Sec-WebSocket-Key header.
     """
 
     accept_binary = util.sha1_hash(
         key + common.WEBSOCKET_ACCEPT_UUID).digest()
@@ -125,17 +136,17 @@ class Handshaker(object):
             common.WEBSOCKET_UPGRADE_TYPE)
 
         self._validate_connection_header()
 
         self._request.ws_resource = self._request.uri
 
         unused_host = get_mandatory_header(self._request, common.HOST_HEADER)
 
-        self._check_version()
+        self._request.ws_version = self._check_version()
 
         # This handshake must be based on latest hybi. We are responsible to
         # fallback to HTTP on handshake failure as latest hybi handshake
         # specifies.
         try:
             self._get_origin()
             self._set_protocol()
             self._parse_extensions()
@@ -146,17 +157,16 @@ class Handshaker(object):
             (accept, accept_binary) = compute_accept(key)
             self._logger.debug(
                 '%s: %r (%s)',
                 common.SEC_WEBSOCKET_ACCEPT_HEADER,
                 accept,
                 util.hexify(accept_binary))
 
             self._logger.debug('IETF HyBi protocol')
-            self._request.ws_version = common.VERSION_HYBI_LATEST
 
             # Setup extension processors.
 
             processors = []
             if self._request.ws_requested_extensions is not None:
                 for extension_request in self._request.ws_requested_extensions:
                     processor = get_extension_processor(extension_request)
                     # Unknown extension requests are just ignored.
@@ -207,39 +217,52 @@ class Handshaker(object):
                     self._request.ws_protocol)
             else:
                 if self._request.ws_protocol is not None:
                     raise HandshakeException(
                         'ws_protocol must be None when the client didn\'t '
                         'request any subprotocol')
 
             self._send_handshake(accept)
-
-            self._logger.debug('Sent opening handshake response')
         except HandshakeException, e:
             if not e.status:
                 # Fallback to 400 bad request by default.
-                e.status = 400
+                e.status = common.HTTP_STATUS_BAD_REQUEST
             raise e
 
     def _get_origin(self):
-        origin = self._request.headers_in.get(
-            common.SEC_WEBSOCKET_ORIGIN_HEADER)
+        if self._request.ws_version is _VERSION_HYBI08:
+            origin_header = common.SEC_WEBSOCKET_ORIGIN_HEADER
+        else:
+            origin_header = common.ORIGIN_HEADER
+        origin = self._request.headers_in.get(origin_header)
+        if origin is None:
+            self._logger.debug('Client request does not have origin header')
         self._request.ws_origin = origin
 
     def _check_version(self):
-        unused_value = validate_mandatory_header(
-            self._request, common.SEC_WEBSOCKET_VERSION_HEADER,
-            str(common.VERSION_HYBI_LATEST), fail_status=426)
+        version = get_mandatory_header(self._request,
+                                       common.SEC_WEBSOCKET_VERSION_HEADER)
+        if version == _VERSION_HYBI08_STRING:
+            return _VERSION_HYBI08
+        if version == _VERSION_LATEST_STRING:
+            return _VERSION_LATEST
+
+        if version.find(',') >= 0:
+            raise HandshakeException(
+                'Multiple versions (%r) are not allowed for header %s' %
+                (version, common.SEC_WEBSOCKET_VERSION_HEADER),
+                status=common.HTTP_STATUS_BAD_REQUEST)
+        raise VersionException(
+            'Unsupported version %r for header %s' %
+            (version, common.SEC_WEBSOCKET_VERSION_HEADER),
+            supported_versions=', '.join(map(str, _SUPPORTED_VERSIONS)))
 
     def _set_protocol(self):
         self._request.ws_protocol = None
-        # MOZILLA
-        self._request.sts = None
-        # /MOZILLA
 
         protocol_header = self._request.headers_in.get(
             common.SEC_WEBSOCKET_PROTOCOL_HEADER)
 
         if not protocol_header:
             self._request.ws_requested_protocols = None
             return
 
@@ -250,25 +273,34 @@ class Handshaker(object):
 
     def _parse_extensions(self):
         extensions_header = self._request.headers_in.get(
             common.SEC_WEBSOCKET_EXTENSIONS_HEADER)
         if not extensions_header:
             self._request.ws_requested_extensions = None
             return
 
+        if self._request.ws_version is common.VERSION_HYBI08:
+            allow_quoted_string=False
+        else:
+            allow_quoted_string=True
         self._request.ws_requested_extensions = parse_extensions(
-            extensions_header)
+            extensions_header, allow_quoted_string=allow_quoted_string)
 
         self._logger.debug(
             'Extensions requested: %r',
             map(common.ExtensionParameter.name,
                 self._request.ws_requested_extensions))
 
     def _validate_key(self, key):
+        if key.find(',') >= 0:
+            raise HandshakeException('Request has multiple %s header lines or '
+                                     'contains illegal character \',\': %r' %
+                                     (common.SEC_WEBSOCKET_KEY_HEADER, key))
+
         # Validate
         key_is_valid = False
         try:
             # Validate key by quick regex match before parsing by base64
             # module. Because base64 module skips invalid characters, we have
             # to do this in advance to make this server strictly reject illegal
             # keys.
             if _BASE64_REGEX.match(key):
@@ -314,21 +346,17 @@ class Handshaker(object):
             response.append(format_header(
                 common.SEC_WEBSOCKET_PROTOCOL_HEADER,
                 self._request.ws_protocol))
         if (self._request.ws_extensions is not None and
             len(self._request.ws_extensions) != 0):
             response.append(format_header(
                 common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
                 format_extensions(self._request.ws_extensions)))
-        # MOZILLA: Add HSTS header if requested to
-        if self._request.sts is not None:
-            response.append(format_header("Strict-Transport-Security",
-                                          self._request.sts))
-        # /MOZILLA
         response.append('\r\n')
 
         raw_response = ''.join(response)
-        self._logger.debug('Opening handshake response: %r', raw_response)
         self._request.connection.write(raw_response)
+        self._logger.debug('Sent server\'s opening handshake: %r',
+                           raw_response)
 
 
 # vi:sts=4 sw=4 et
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi00.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi00.py
@@ -102,18 +102,16 @@ class Handshaker(object):
         self._set_origin()
         self._set_challenge_response()
         self._set_protocol_version()
 
         self._dispatcher.do_extra_handshake(self._request)
 
         self._send_handshake()
 
-        self._logger.debug('Sent opening handshake response')
-
     def _set_resource(self):
         self._request.ws_resource = self._request.uri
 
     def _set_subprotocol(self):
         # |Sec-WebSocket-Protocol|
         subprotocol = self._request.headers_in.get(
             common.SEC_WEBSOCKET_PROTOCOL_HEADER)
         if subprotocol is not None:
@@ -133,17 +131,18 @@ class Handshaker(object):
         if origin is not None:
             self._request.ws_origin = origin
 
     def _set_protocol_version(self):
         # |Sec-WebSocket-Draft|
         draft = self._request.headers_in.get(common.SEC_WEBSOCKET_DRAFT_HEADER)
         if draft is not None and draft != '0':
             raise HandshakeException('Illegal value for %s: %s' %
-                                     (common.SEC_WEBSOCKET_DRAFT_HEADER, draft))
+                                     (common.SEC_WEBSOCKET_DRAFT_HEADER,
+                                      draft))
 
         self._logger.debug('IETF HyBi 00 protocol')
         self._request.ws_version = common.VERSION_HYBI00
         self._request.ws_stream = StreamHixie75(self._request, True)
 
     def _set_challenge_response(self):
         # 5.2 4-8.
         self._request.ws_challenge = self._get_challenge()
@@ -224,13 +223,14 @@ class Handshaker(object):
                 common.SEC_WEBSOCKET_PROTOCOL_HEADER,
                 self._request.ws_protocol))
         # 5.2 12. send two bytes 0x0D 0x0A.
         response.append('\r\n')
         # 5.2 13. send /response/
         response.append(self._request.ws_challenge_md5)
 
         raw_response = ''.join(response)
-        self._logger.debug('Opening handshake response: %r', raw_response)
         self._request.connection.write(raw_response)
+        self._logger.debug('Sent server\'s opening handshake: %r',
+                           raw_response)
 
 
 # vi:sts=4 sw=4 et
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/headerparserhandler.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/headerparserhandler.py
@@ -34,56 +34,74 @@ Apache HTTP Server and mod_python must b
 function is called to handle WebSocket request.
 """
 
 
 import logging
 
 from mod_python import apache
 
+from mod_pywebsocket import common
 from mod_pywebsocket import dispatch
 from mod_pywebsocket import handshake
 from mod_pywebsocket import util
 
 
 # PythonOption to specify the handler root directory.
 _PYOPT_HANDLER_ROOT = 'mod_pywebsocket.handler_root'
 
 # PythonOption to specify the handler scan directory.
 # This must be a directory under the root directory.
 # The default is the root directory.
 _PYOPT_HANDLER_SCAN = 'mod_pywebsocket.handler_scan'
 
+# PythonOption to allow handlers whose canonical path is
+# not under the root directory. It's disallowed by default.
+# Set this option with value of 'yes' to allow.
+_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT = (
+    'mod_pywebsocket.allow_handlers_outside_root_dir')
+# Map from values to their meanings. 'Yes' and 'No' are allowed just for
+# compatibility.
+_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION = {
+    'off': False, 'no': False, 'on': True, 'yes': True}
+
 # PythonOption to specify to allow draft75 handshake.
 # The default is None (Off)
 _PYOPT_ALLOW_DRAFT75 = 'mod_pywebsocket.allow_draft75'
+# Map from values to their meanings.
+_PYOPT_ALLOW_DRAFT75_DEFINITION = {'off': False, 'on': True}
 
 
 class ApacheLogHandler(logging.Handler):
     """Wrapper logging.Handler to emit log message to apache's error.log."""
 
     _LEVELS = {
         logging.DEBUG: apache.APLOG_DEBUG,
         logging.INFO: apache.APLOG_INFO,
         logging.WARNING: apache.APLOG_WARNING,
         logging.ERROR: apache.APLOG_ERR,
         logging.CRITICAL: apache.APLOG_CRIT,
         }
 
     def __init__(self, request=None):
         logging.Handler.__init__(self)
-        self.log_error = apache.log_error
+        self._log_error = apache.log_error
         if request is not None:
-            self.log_error = request.log_error
+            self._log_error = request.log_error
+
+        # Time and level will be printed by Apache.
+        self._formatter = logging.Formatter('%(name)s: %(message)s')
 
     def emit(self, record):
         apache_level = apache.APLOG_DEBUG
         if record.levelno in ApacheLogHandler._LEVELS:
             apache_level = ApacheLogHandler._LEVELS[record.levelno]
 
+        msg = self._formatter.format(record)
+
         # "server" parameter must be passed to have "level" parameter work.
         # If only "level" parameter is passed, nothing shows up on Apache's
         # log. However, at this point, we cannot get the server object of the
         # virtual host which will process WebSocket requests. The only server
         # object we can get here is apache.main_server. But Wherever (server
         # configuration context or virtual host context) we put
         # PythonHeaderParserHandler directive, apache.main_server just points
         # the main server instance (not any of virtual server instance). Then,
@@ -94,38 +112,67 @@ class ApacheLogHandler(logging.Handler):
         # DEBUG level logs never show up unless "LogLevel debug" is specified
         # in the server configuration context.
         #
         # TODO(tyoshino): Provide logging methods on request object. When
         # request is mp_request object (when used together with Apache), the
         # methods call request.log_error indirectly. When request is
         # _StandaloneRequest, the methods call Python's logging facility which
         # we create in standalone.py.
-        self.log_error(record.getMessage(), apache_level, apache.main_server)
+        self._log_error(msg, apache_level, apache.main_server)
+
+
+def _configure_logging():
+    logger = logging.getLogger()
+    # Logs are filtered by Apache based on LogLevel directive in Apache
+    # configuration file. We must just pass logs for all levels to
+    # ApacheLogHandler.
+    logger.setLevel(logging.DEBUG)
+    logger.addHandler(ApacheLogHandler())
 
 
-_LOGGER = logging.getLogger('mod_pywebsocket')
-# Logs are filtered by Apache based on LogLevel directive in Apache
-# configuration file. We must just pass logs for all levels to
-# ApacheLogHandler.
-_LOGGER.setLevel(logging.DEBUG)
-_LOGGER.addHandler(ApacheLogHandler())
+_configure_logging()
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def _parse_option(name, value, definition):
+    if value is None:
+        return False
+
+    meaning = definition.get(value.lower())
+    if meaning is None:
+        raise Exception('Invalid value for PythonOption %s: %r' %
+                        (name, value))
+    return meaning
 
 
 def _create_dispatcher():
-    _HANDLER_ROOT = apache.main_server.get_options().get(
-            _PYOPT_HANDLER_ROOT, None)
-    if not _HANDLER_ROOT:
+    _LOGGER.info('Initializing Dispatcher')
+
+    options = apache.main_server.get_options()
+
+    handler_root = options.get(_PYOPT_HANDLER_ROOT, None)
+    if not handler_root:
         raise Exception('PythonOption %s is not defined' % _PYOPT_HANDLER_ROOT,
                         apache.APLOG_ERR)
-    _HANDLER_SCAN = apache.main_server.get_options().get(
-            _PYOPT_HANDLER_SCAN, _HANDLER_ROOT)
-    dispatcher = dispatch.Dispatcher(_HANDLER_ROOT, _HANDLER_SCAN)
+
+    handler_scan = options.get(_PYOPT_HANDLER_SCAN, handler_root)
+
+    allow_handlers_outside_root = _parse_option(
+        _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT,
+        options.get(_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT),
+        _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION)
+
+    dispatcher = dispatch.Dispatcher(
+        handler_root, handler_scan, allow_handlers_outside_root)
+
     for warning in dispatcher.source_warnings():
         apache.log_error('mod_pywebsocket: %s' % warning, apache.APLOG_WARNING)
+
     return dispatcher
 
 
 # Initialize
 _dispatcher = _create_dispatcher()
 
 
 def headerparserhandler(request):
@@ -135,41 +182,62 @@ def headerparserhandler(request):
         request: mod_python request.
 
     This function is named headerparserhandler because it is the default
     name for a PythonHeaderParserHandler.
     """
 
     handshake_is_done = False
     try:
-        allowDraft75 = apache.main_server.get_options().get(
-            _PYOPT_ALLOW_DRAFT75, None)
-        handshake.do_handshake(
-            request, _dispatcher, allowDraft75=allowDraft75)
+        # Fallback to default http handler for request paths for which
+        # we don't have request handlers.
+        if not _dispatcher.get_handler_suite(request.uri):
+            request.log_error('No handler for resource: %r' % request.uri,
+                              apache.APLOG_INFO)
+            request.log_error('Fallback to Apache', apache.APLOG_INFO)
+            return apache.DECLINED
+    except dispatch.DispatchException, e:
+        request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO)
+        if not handshake_is_done:
+            return e.status
+
+    try:
+        allow_draft75 = _parse_option(
+            _PYOPT_ALLOW_DRAFT75,
+            apache.main_server.get_options().get(_PYOPT_ALLOW_DRAFT75),
+            _PYOPT_ALLOW_DRAFT75_DEFINITION)
+
+        try:
+            handshake.do_handshake(
+                request, _dispatcher, allowDraft75=allow_draft75)
+        except handshake.VersionException, e:
+            request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO)
+            request.err_headers_out.add(common.SEC_WEBSOCKET_VERSION_HEADER,
+                                        e.supported_versions)
+            return apache.HTTP_BAD_REQUEST
+        except handshake.HandshakeException, e:
+            # Handshake for ws/wss failed.
+            # Send http response with error status.
+            request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO)
+            return e.status
+
         handshake_is_done = True
-        request.log_error(
-            'mod_pywebsocket: resource: %r' % request.ws_resource,
-            apache.APLOG_DEBUG)
         request._dispatcher = _dispatcher
         _dispatcher.transfer_data(request)
-    except dispatch.DispatchException, e:
-        request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_WARNING)
-        if not handshake_is_done:
-            return e.status
     except handshake.AbortedByUserException, e:
         request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO)
-    except handshake.HandshakeException, e:
-        # Handshake for ws/wss failed.
-        # The request handling fallback into http/https.
-        request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO)
-        return e.status
     except Exception, e:
-        request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_WARNING)
+        # DispatchException can also be thrown if something is wrong in
+        # pywebsocket code. It's caught here, then.
+
+        request.log_error('mod_pywebsocket: %s\n%s' %
+                          (e, util.get_stack_trace()),
+                          apache.APLOG_ERR)
         # Unknown exceptions before handshake mean Apache must handle its
         # request with another handler.
         if not handshake_is_done:
-            return apache.DECLINE
+            return apache.DECLINED
     # Set assbackwards to suppress response header generation by Apache.
     request.assbackwards = 1
     return apache.DONE  # Return DONE such that no other handlers are invoked.
 
 
 # vi:sts=4 sw=4 et
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/http_header_util.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/http_header_util.py
@@ -47,16 +47,17 @@ def _is_char(c):
 
 def _is_ctl(c):
     """Returns true iff c is in CTL as specified in HTTP RFC."""
 
     return ord(c) <= 31 or ord(c) == 127
 
 
 class ParsingState(object):
+
     def __init__(self, data):
         self.data = data
         self.head = 0
 
 
 def peek(state, pos=0):
     """Peeks the character at pos from the head of data."""
 
@@ -204,17 +205,17 @@ def quote_if_necessary(s):
             quote = True
 
         if c == '"' or _is_ctl(c):
             result.append('\\' + c)
         else:
             result.append(c)
 
     if quote:
-        return '"' + ''.join(result) + '"';
+        return '"' + ''.join(result) + '"'
     else:
         return ''.join(result)
 
 
 def parse_uri(uri):
     """Parse absolute URI then return host, port and resource."""
 
     parsed = urlparse.urlsplit(uri)
@@ -246,9 +247,17 @@ def parse_uri(uri):
     if parsed.query:
         path += '?' + parsed.query
     if parsed.fragment:
         path += '#' + parsed.fragment
 
     return parsed.hostname, port, path
 
 
+try:
+    urlparse.uses_netloc.index('ws')
+except ValueError, e:
+    # urlparse in Python2.5.1 doesn't have 'ws' and 'wss' entries.
+    urlparse.uses_netloc.append('ws')
+    urlparse.uses_netloc.append('wss')
+
+
 # vi:sts=4 sw=4 et
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/util.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/util.py
@@ -213,31 +213,33 @@ class DeflateRequest(object):
 # Compression level is Z_DEFAULT_COMPRESSION. We don't have to match level
 # to decode.
 #
 # See zconf.h, deflate.cc, inflate.cc of zlib library, and zlibmodule.c of
 # Python. See also RFC1950 (ZLIB 3.3).
 
 
 class _Deflater(object):
+
     def __init__(self, window_bits):
         self._logger = get_class_logger(self)
 
         self._compress = zlib.compressobj(
             zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -window_bits)
 
     def compress_and_flush(self, bytes):
         compressed_bytes = self._compress.compress(bytes)
         compressed_bytes += self._compress.flush(zlib.Z_SYNC_FLUSH)
         self._logger.debug('Compress input %r', bytes)
         self._logger.debug('Compress result %r', compressed_bytes)
         return compressed_bytes
 
 
 class _Inflater(object):
+
     def __init__(self):
         self._logger = get_class_logger(self)
 
         self._unconsumed = ''
 
         self.reset()
 
     def decompress(self, size):
@@ -385,16 +387,20 @@ class DeflateConnection(object):
     def __init__(self, connection):
         self._connection = connection
 
         self._logger = get_class_logger(self)
 
         self._deflater = _Deflater(zlib.MAX_WBITS)
         self._inflater = _Inflater()
 
+    def get_remote_addr(self):
+        return self._connection.remote_addr
+    remote_addr = property(get_remote_addr)
+
     def put_bytes(self, bytes):
         self.write(bytes)
 
     def read(self, size=-1):
         """Reads at most size bytes. Blocks until there's at least one byte
         available.
         """
 
--- a/testing/mochitest/pywebsocket/standalone.py
+++ b/testing/mochitest/pywebsocket/standalone.py
@@ -60,25 +60,28 @@ SECURITY WARNING: This uses CGIHTTPServe
 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 httplib
 import logging
 import logging.handlers
 import optparse
 import os
 import re
 import select
 import socket
 import sys
 import threading
+import time
+
 
 _HAS_OPEN_SSL = False
 try:
     import OpenSSL.SSL
     _HAS_OPEN_SSL = True
 except ImportError:
     pass
 
@@ -94,23 +97,16 @@ from mod_pywebsocket import util
 _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.
@@ -160,35 +156,30 @@ class _StandaloneRequest(object):
             request_handler: A WebSocketRequestHandler instance.
         """
 
         self._logger = util.get_class_logger(self)
 
         self._request_handler = request_handler
         self.connection = _StandaloneConnection(request_handler)
         self._use_tls = use_tls
+        self.headers_in = request_handler.headers
 
     def get_uri(self):
         """Getter to mimic request.uri."""
 
         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
 
     def _drain_received_data(self):
         """Don't use this method from WebSocket handler. Drains unread data
         in the receive buffer.
@@ -211,16 +202,18 @@ class WebSocketServer(SocketServer.Threa
     allow_reuse_address = True
 
     def __init__(self, options):
         """Override SocketServer.TCPServer.__init__ to set SSL enabled
         socket object to self.socket before server_bind and server_activate,
         if necessary.
         """
 
+        self._logger = util.get_class_logger(self)
+
         self.request_queue_size = options.request_queue_size
         self.__ws_is_shut_down = threading.Event()
         self.__ws_serving = False
 
         SocketServer.BaseServer.__init__(
             self, (options.server_host, options.port), WebSocketRequestHandler)
 
         # Expose the options object to allow handler objects access it. We name
@@ -230,108 +223,126 @@ class WebSocketServer(SocketServer.Threa
         self._create_sockets()
         self.server_bind()
         self.server_activate()
 
     def _create_sockets(self):
         self.server_name, self.server_port = self.server_address
         self._sockets = []
         if not self.server_name:
+            # On platforms that doesn't support IPv6, the first bind fails.
+            # On platforms that supports IPv6
+            # - If it binds both IPv4 and IPv6 on call with AF_INET6, the
+            #   first bind succeeds and the second fails (we'll see 'Address
+            #   already in use' error).
+            # - If it binds only IPv6 on call with AF_INET6, both call are
+            #   expected to succeed to listen both protocol.
             addrinfo_array = [
-                (self.address_family, self.socket_type, '', '', '')]
+                (socket.AF_INET6, socket.SOCK_STREAM, '', '', ''),
+                (socket.AF_INET, socket.SOCK_STREAM, '', '', '')]
         else:
             addrinfo_array = socket.getaddrinfo(self.server_name,
                                                 self.server_port,
                                                 socket.AF_UNSPEC,
                                                 socket.SOCK_STREAM,
                                                 socket.IPPROTO_TCP)
         for addrinfo in addrinfo_array:
-            logging.info('Create socket on: %r', addrinfo)
+            self._logger.info('Create socket on: %r', addrinfo)
             family, socktype, proto, canonname, sockaddr = addrinfo
             try:
                 socket_ = socket.socket(family, socktype)
             except Exception, e:
-                logging.info('Skip by failure: %r', e)
+                self._logger.info('Skip by failure: %r', e)
                 continue
             if self.websocket_server_options.use_tls:
                 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
                 ctx.use_privatekey_file(
                     self.websocket_server_options.private_key)
                 ctx.use_certificate_file(
                     self.websocket_server_options.certificate)
                 socket_ = OpenSSL.SSL.Connection(ctx, socket_)
             self._sockets.append((socket_, addrinfo))
 
     def server_bind(self):
         """Override SocketServer.TCPServer.server_bind to enable multiple
         sockets bind.
         """
 
-        for socket_, addrinfo in self._sockets:
-            logging.info('Bind on: %r', addrinfo)
+        failed_sockets = []
+
+        for socketinfo in self._sockets:
+            socket_, addrinfo = socketinfo
+            self._logger.info('Bind on: %r', addrinfo)
             if self.allow_reuse_address:
                 socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-            socket_.bind(self.server_address)
+            try:
+                socket_.bind(self.server_address)
+            except Exception, e:
+                self._logger.info('Skip by failure: %r', e)
+                socket_.close()
+                failed_sockets.append(socketinfo)
+
+        for socketinfo in failed_sockets:
+            self._sockets.remove(socketinfo)
 
     def server_activate(self):
         """Override SocketServer.TCPServer.server_activate to enable multiple
         sockets listen.
         """
 
         failed_sockets = []
 
         for socketinfo in self._sockets:
             socket_, addrinfo = socketinfo
-            logging.info('Listen on: %r', addrinfo)
+            self._logger.info('Listen on: %r', addrinfo)
             try:
                 socket_.listen(self.request_queue_size)
             except Exception, e:
-                logging.info('Skip by failure: %r', e)
+                self._logger.info('Skip by failure: %r', e)
                 socket_.close()
                 failed_sockets.append(socketinfo)
 
         for socketinfo in failed_sockets:
             self._sockets.remove(socketinfo)
 
     def server_close(self):
         """Override SocketServer.TCPServer.server_close to enable multiple
         sockets close.
         """
 
         for socketinfo in self._sockets:
             socket_, addrinfo = socketinfo
-            logging.info('Close on: %r', addrinfo)
+            self._logger.info('Close on: %r', addrinfo)
             socket_.close()
 
     def fileno(self):
         """Override SocketServer.TCPServer.fileno."""
 
-        logging.critical('Not supported: fileno')
+        self._logger.critical('Not supported: fileno')
         return self._sockets[0][0].fileno()
 
     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.
+        self._logger.error(
+            'Exception in processing request from: %r\n%s',
+            client_address,
+            util.get_stack_trace())
+        # Note: client_address is a tuple.
 
     def serve_forever(self, poll_interval=0.5):
         """Override SocketServer.BaseServer.serve_forever."""
 
         self.__ws_serving = True
         self.__ws_is_shut_down.clear()
         handle_request = self.handle_request
         if hasattr(self, '_handle_request_noblock'):
             handle_request = self._handle_request_noblock
         else:
-            logging.warning('mod_pywebsocket: fallback to blocking request '
-                            'handler')
+            self._logger.warning('Fallback to blocking request handler')
         try:
             while self.__ws_serving:
                 r, w, e = select.select(
                     [socket_[0] for socket_ in self._sockets],
                     [], [], poll_interval)
                 for socket_ in r:
                     self.socket = socket_
                     handle_request()
@@ -344,16 +355,19 @@ class WebSocketServer(SocketServer.Threa
 
         self.__ws_serving = False
         self.__ws_is_shut_down.wait()
 
 
 class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
     """CGIHTTPRequestHandler specialized for WebSocket."""
 
+    # Use httplib.HTTPMessage instead of mimetools.Message.
+    MessageClass = httplib.HTTPMessage
+
     def setup(self):
         """Override SocketServer.StreamRequestHandler.setup to wrap rfile
         with MemorizingFile.
 
         This method will be called by BaseRequestHandler's constructor
         before calling BaseHTTPRequestHandler.handle.
         BaseHTTPRequestHandler.handle will call
         BaseHTTPRequestHandler.handle_one_request and it will call
@@ -365,28 +379,26 @@ class WebSocketRequestHandler(CGIHTTPSer
         # understand what this does.
         CGIHTTPServer.CGIHTTPRequestHandler.setup(self)
 
         self.rfile = memorizingfile.MemorizingFile(
             self.rfile,
             max_memorized_lines=_MAX_MEMORIZED_LINES)
 
     def __init__(self, request, client_address, server):
+        self._logger = util.get_class_logger(self)
+
         self._options = server.websocket_server_options
 
         # Overrides CGIHTTPServerRequestHandler.cgi_directories.
         self.cgi_directories = self._options.cgi_directories
         # Replace CGIHTTPRequestHandler.is_executable method.
         if self._options.is_executable_method is not None:
             self.is_executable = self._options.is_executable_method
 
-        self._request = _StandaloneRequest(self, self._options.use_tls)
-
-        _print_warnings_if_any(self._options.dispatcher)
-
         # This actually calls BaseRequestHandler.__init__.
         CGIHTTPServer.CGIHTTPRequestHandler.__init__(
             self, request, client_address, server)
 
     def parse_request(self):
         """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
 
         Return True to continue processing for HTTP(S), False otherwise.
@@ -401,89 +413,97 @@ class WebSocketRequestHandler(CGIHTTPSer
         # it needs variables set by CGIHTTPRequestHandler.parse_request.
         #
         # Variables set by this method will be also used by WebSocket request
         # handling. See _StandaloneRequest.get_request, etc.
         if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self):
             return False
         host, port, resource = http_header_util.parse_uri(self.path)
         if resource is None:
-            logging.info('mod_pywebsocket: invalid uri %r' % self.path)
+            self._logger.info('Invalid URI: %r', self.path)
+            self._logger.info('Fallback to CGIHTTPRequestHandler')
             return True
         server_options = self.server.websocket_server_options
         if host is not None:
             validation_host = server_options.validation_host
             if validation_host is not None and host != validation_host:
-                logging.info('mod_pywebsocket: invalid host %r '
-                             '(expected: %r)' % (host, validation_host))
+                self._logger.info('Invalid host: %r (expected: %r)',
+                                  host,
+                                  validation_host)
+                self._logger.info('Fallback to CGIHTTPRequestHandler')
                 return True
         if port is not None:
             validation_port = server_options.validation_port
             if validation_port is not None and port != validation_port:
-                logging.info('mod_pywebsocket: invalid port %r '
-                             '(expected: %r)' % (port, validation_port))
+                self._logger.info('Invalid port: %r (expected: %r)',
+                                  port,
+                                  validation_port)
+                self._logger.info('Fallback to CGIHTTPRequestHandler')
                 return True
         self.path = resource
 
+        request = _StandaloneRequest(self, self._options.use_tls)
+
         try:
             # Fallback to default http handler for request paths for which
             # we don't have request handlers.
             if not self._options.dispatcher.get_handler_suite(self.path):
-                logging.info('No handlers for request: %s' % self.path)
+                self._logger.info('No handler for resource: %r',
+                                  self.path)
+                self._logger.info('Fallback to CGIHTTPRequestHandler')
                 return True
+        except dispatch.DispatchException, e:
+            self._logger.info('%s', e)
+            self.send_error(e.status)
+            return False
 
+        # If any Exceptions without except clause setup (including
+        # DispatchException) is raised below this point, it will be caught
+        # and logged by WebSocketServer.
+
+        try:
             try:
                 handshake.do_handshake(
-                    self._request,
+                    request,
                     self._options.dispatcher,
                     allowDraft75=self._options.allow_draft75,
                     strict=self._options.strict)
-            except handshake.AbortedByUserException, e:
-                logging.info('mod_pywebsocket: %s' % e)
-                return False
-            try:
-                self._request._dispatcher = self._options.dispatcher
-                self._options.dispatcher.transfer_data(self._request)
-            except dispatch.DispatchException, e:
-                logging.warning('mod_pywebsocket: %s' % e)
+            except handshake.VersionException, e:
+                self._logger.info('%s', e)
+                self.send_response(common.HTTP_STATUS_BAD_REQUEST)
+                self.send_header(common.SEC_WEBSOCKET_VERSION_HEADER,
+                                 e.supported_versions)
+                self.end_headers()
                 return False
-            except handshake.AbortedByUserException, e:
-                logging.info('mod_pywebsocket: %s' % e)
-            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)
-                logging.info(
-                    'mod_pywebsocket: %s' % util.get_stack_trace())
-        except dispatch.DispatchException, e:
-            logging.warning('mod_pywebsocket: %s' % e)
-            self.send_error(e.status)
-        except handshake.HandshakeException, e:
-            # Handshake for ws(s) failed. Assume http(s).
-            logging.info('mod_pywebsocket: %s' % e)
-            self.send_error(e.status)
-        except Exception, e:
-            logging.warning('mod_pywebsocket: %s' % e)
-            logging.warning('mod_pywebsocket: %s' % util.get_stack_trace())
+            except handshake.HandshakeException, e:
+                # Handshake for ws(s) failed.
+                self._logger.info('%s', e)
+                self.send_error(e.status)
+                return False
+
+            request._dispatcher = self._options.dispatcher
+            self._options.dispatcher.transfer_data(request)
+        except handshake.AbortedByUserException, e:
+            self._logger.info('%s', e)
         return False
 
     def log_request(self, code='-', size='-'):
         """Override BaseHTTPServer.log_request."""
 
-        logging.info('"%s" %s %s',
-                     self.requestline, str(code), str(size))
+        self._logger.info('"%s" %s %s',
+                          self.requestline, str(code), str(size))
 
     def log_error(self, *args):
         """Override BaseHTTPServer.log_error."""
 
         # Despite the name, this method is for warnings than for errors.
         # For example, HTTP status code is logged by this method.
-        logging.warning('%s - %s' %
-                        (self.address_string(), (args[0] % args[1:])))
+        self._logger.warning('%s - %s',
+                             self.address_string(),
+                             args[0] % args[1:])
 
     def is_cgi(self):
         """Test whether self.path corresponds to a CGI script.
 
         Add extra check that self.path doesn't contains ..
         Also check if the file is a executable file or not.
         If the file is not executable, it is handled as static file or dir
         rather than a CGI script.
@@ -539,18 +559,19 @@ def _alias_handlers(dispatcher, websock_
                 dispatcher.add_resource_path_alias(
                     m.group(1), m.group(2))
             except dispatch.DispatchException, e:
                 logging.error(str(e))
     finally:
         fp.close()
 
 
-def _main():
+def _build_option_parser():
     parser = optparse.OptionParser()
+
     parser.add_option('-H', '--server-host', '--server_host',
                       dest='server_host',
                       default='',
                       help='server hostname to listen to')
     parser.add_option('-V', '--validation-host', '--validation_host',
                       dest='validation_host',
                       default=None,
                       help='server hostname to validate in absolute path.')
@@ -571,16 +592,23 @@ def _main():
                       default=None,
                       help=('WebSocket handlers map file. '
                             'Each line consists of alias_resource_path and '
                             'existing_resource_path, separated by spaces.'))
     parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir',
                       default=None,
                       help=('WebSocket handlers scan directory. '
                             'Must be a directory under websock_handlers.'))
+    parser.add_option('--allow-handlers-outside-root-dir',
+                      '--allow_handlers_outside_root_dir',
+                      dest='allow_handlers_outside_root_dir',
+                      action='store_true',
+                      default=False,
+                      help=('Scans WebSocket handlers even if their canonical '
+                            'path is not under websock_handlers.'))
     parser.add_option('-d', '--document-root', '--document_root',
                       dest='document_root', default='.',
                       help='Document root directory.')
     parser.add_option('-x', '--cgi-paths', '--cgi_paths', dest='cgi_paths',
                       default=None,
                       help=('CGI paths relative to document_root.'
                             'Comma-separated. (e.g -x /cgi,/htbin) '
                             'Files under document_root/cgi_path are handled '
@@ -594,31 +622,72 @@ def _main():
                       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', 'warning', 'warn', 'error',
                                'critical'],
                       help='Log level.')
+    parser.add_option('--thread-monitor-interval-in-sec',
+                      '--thread_monitor_interval_in_sec',
+                      dest='thread_monitor_interval_in_sec',
+                      type='int', default=-1,
+                      help=('If positive integer is specified, run a thread '
+                            'monitor to show the status of server threads '
+                            'periodically in the specified inteval in '
+                            'second. If non-positive integer is specified, '
+                            'disable the thread monitor.'))
     parser.add_option('--log-max', '--log_max', dest='log_max', type='int',
                       default=_DEFAULT_LOG_MAX_BYTES,
                       help='Log maximum bytes')
     parser.add_option('--log-count', '--log_count', dest='log_count',
                       type='int', default=_DEFAULT_LOG_BACKUP_COUNT,
                       help='Log backup count')
     parser.add_option('--allow-draft75', dest='allow_draft75',
                       action='store_true', default=False,
                       help='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]
+
+    return parser
+
+
+class ThreadMonitor(threading.Thread):
+    daemon = True
+
+    def __init__(self, interval_in_sec):
+        threading.Thread.__init__(self, name='ThreadMonitor')
+
+        self._logger = util.get_class_logger(self)
+
+        self._interval_in_sec = interval_in_sec
+
+    def run(self):
+        while True:
+            thread_name_list = []
+            for thread in threading.enumerate():
+                thread_name_list.append(thread.name)
+            self._logger.info(
+                "%d active threads: %s",
+                threading.active_count(),
+                ', '.join(thread_name_list))
+            time.sleep(self._interval_in_sec)
+
+
+def _main(args=None):
+    parser = _build_option_parser()
+
+    options, args = parser.parse_args(args=args)
+    if args:
+        logging.critical('Unrecognized positional arguments: %r', args)
+        sys.exit(1)
 
     os.chdir(options.document_root)
 
     _configure_logging(options)
 
     # TODO(tyoshino): Clean up initialization of CGI related values. Move some
     # of code here to WebSocketRequestHandler class if it's better.
     options.cgi_directories = []
@@ -648,24 +717,34 @@ def _main():
             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:
+        if options.thread_monitor_interval_in_sec > 0:
+            # Run a thread monitor to show the status of server threads for
+            # debugging.
+            ThreadMonitor(options.thread_monitor_interval_in_sec).start()
+
         # Share a Dispatcher among request handlers to save time for
         # instantiation.  Dispatcher can be shared because it is thread-safe.
-        options.dispatcher = dispatch.Dispatcher(options.websock_handlers,
-                                                 options.scan_dir)
+        options.dispatcher = dispatch.Dispatcher(
+            options.websock_handlers,
+            options.scan_dir,
+            options.allow_handlers_outside_root_dir)
         if options.websock_handlers_map_file:
             _alias_handlers(options.dispatcher,
                             options.websock_handlers_map_file)
-        _print_warnings_if_any(options.dispatcher)
+        warnings = options.dispatcher.source_warnings()
+        if warnings:
+            for warning in warnings:
+                logging.warning('mod_pywebsocket: %s' % warning)
 
         server = WebSocketServer(options)
         server.serve_forever()
     except Exception, e:
         logging.critical('mod_pywebsocket: %s' % e)
         logging.critical('mod_pywebsocket: %s' % util.get_stack_trace())
         sys.exit(1)