don't use autoinstall to import pywebsocket but check it in WebKit directly.
authortoyoshim@chromium.org <toyoshim@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 10 May 2012 18:38:34 +0000 (18:38 +0000)
committertoyoshim@chromium.org <toyoshim@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 10 May 2012 18:38:34 +0000 (18:38 +0000)
https://bugs.webkit.org/show_bug.cgi?id=86107

Reviewed by Adam Barth.

This change removes pywebsocket from webkitpy's autoinstall list and
imports pywebsocket 0.7.4 directly into webkitpy/thirparty.

* Scripts/webkitpy/layout_tests/servers/websocket_server.py:
(PyWebSocket._prepare_config):
* Scripts/webkitpy/thirdparty/__init__.py:
(AutoinstallImportHook.find_module):
(AutoinstallImportHook._install_irc):
* Scripts/webkitpy/thirdparty/__init___unittest.py:
(ThirdpartyTest.test_import_hook.MockImportHook.__init__):
(ThirdpartyTest.test_import_hook.MockImportHook._install_eliza):
(ThirdpartyTest):
(ThirdpartyTest.test_import_hook):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/COPYING: Added.
* Scripts/webkitpy/thirdparty/mod_pywebsocket/__init__.py: Added.
* Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_base.py: Added.
(ConnectionTerminatedException):
(InvalidFrameException):
(BadOperationException):
(UnsupportedFrameException):
(InvalidUTF8Exception):
(StreamBase):
(StreamBase.__init__):
(StreamBase._read):
(StreamBase._write):
(StreamBase.receive_bytes):
(StreamBase._read_until):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hixie75.py: Added.
(for):
(StreamHixie75):
(StreamHixie75.for):
(StreamHixie75.__init__):
(StreamHixie75.send_message):
(StreamHixie75._read_payload_length_hixie75):
(StreamHixie75.receive_message):
(StreamHixie75._send_closing_handshake):
(StreamHixie75.close_connection):
(StreamHixie75.send_ping):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py: Added.
(Frame):
(Frame.__init__):
(create_length_header):
(create_header):
(_build_frame):
(_filter_and_format_frame_object):
(create_binary_frame):
(create_text_frame):
(FragmentedFrameBuilder):
(FragmentedFrameBuilder.to):
(FragmentedFrameBuilder.__init__):
(FragmentedFrameBuilder.build):
(_create_control_frame):
(create_ping_frame):
(create_pong_frame):
(create_close_frame):
(StreamOptions):
(StreamOptions.__init__):
(Stream):
(Stream.for):
(Stream.__init__):
(Stream._receive_frame):
(Stream._receive_frame_as_frame_object):
(Stream.send_message):
(Stream.receive_message):
(Stream._send_closing_handshake):
(Stream.close_connection):
(Stream.send_ping):
(Stream._send_pong):
(Stream._drain_received_data):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py: Added.
(is_control_opcode):
(ExtensionParameter):
(ExtensionParameter.__init__):
(ExtensionParameter.name):
(ExtensionParameter.add_parameter):
(ExtensionParameter.get_parameters):
(ExtensionParameter.get_parameter_names):
(ExtensionParameter.has_parameter):
(ExtensionParameter.get_parameter_value):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/dispatch.py: Added.
(DispatchException):
(DispatchException.__init__):
(_default_passive_closing_handshake_handler):
(_normalize_path):
(_create_path_to_resource_converter):
(_create_path_to_resource_converter.converter):
(_enumerate_handler_file_paths):
(_HandlerSuite):
(_HandlerSuite.__init__):
(_source_handler_file):
(_extract_handler):
(Dispatcher):
(Dispatcher.maintains):
(Dispatcher.__init__):
(Dispatcher.add_resource_path_alias):
(Dispatcher.source_warnings):
(Dispatcher.do_extra_handshake):
(Dispatcher.transfer_data):
(Dispatcher.passive_closing_handshake):
(Dispatcher.get_handler_suite):
(Dispatcher._source_handler_files_in_dir):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py: Added.
(ExtensionProcessorInterface):
(ExtensionProcessorInterface.get_extension_response):
(ExtensionProcessorInterface.setup_stream_options):
(DeflateStreamExtensionProcessor):
(DeflateStreamExtensionProcessor.__init__):
(DeflateStreamExtensionProcessor.get_extension_response):
(DeflateStreamExtensionProcessor.setup_stream_options):
(DeflateFrameExtensionProcessor):
(DeflateFrameExtensionProcessor.__init__):
(DeflateFrameExtensionProcessor.get_extension_response):
(DeflateFrameExtensionProcessor.setup_stream_options):
(DeflateFrameExtensionProcessor.setup_stream_options._OutgoingFilter):
(DeflateFrameExtensionProcessor.setup_stream_options._OutgoingFilter.__init__):
(DeflateFrameExtensionProcessor.setup_stream_options._OutgoingFilter.filter):
(DeflateFrameExtensionProcessor.setup_stream_options._IncomingFilter):
(DeflateFrameExtensionProcessor.setup_stream_options._IncomingFilter.__init__):
(DeflateFrameExtensionProcessor.setup_stream_options._IncomingFilter.filter):
(DeflateFrameExtensionProcessor.set_response_window_bits):
(DeflateFrameExtensionProcessor.set_response_no_context_takeover):
(DeflateFrameExtensionProcessor.enable_outgoing_compression):
(DeflateFrameExtensionProcessor.disable_outgoing_compression):
(DeflateFrameExtensionProcessor._outgoing_filter):
(DeflateFrameExtensionProcessor._incoming_filter):
(get_extension_processor):
(get_extension_processor.is):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/__init__.py: Added.
(try):
(do_handshake):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py: Added.
(AbortedByUserException):
(HandshakeException):
(HandshakeException.__init__):
(VersionException):
(VersionException.__init__):
(get_default_port):
(validate_subprotocol):
(parse_host_header):
(format_header):
(build_location):
(get_mandatory_header):
(validate_mandatory_header):
(check_request_line):
(check_header_lines):
(parse_token_list):
(_parse_extension_param):
(_parse_extension):
(parse_extensions):
(format_extensions):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/draft75.py: Added.
(Handshaker):
(Handshaker.performs):
(Handshaker.__init__):
(Handshaker.do_handshake):
(Handshaker._set_resource):
(Handshaker._set_origin):
(Handshaker._set_location):
(Handshaker._set_subprotocol):
(Handshaker._set_protocol_version):
(Handshaker._sendall):
(Handshaker._send_handshake):
(Handshaker._check_header_lines):
(Handshaker._check_first_lines):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi.py: Added.
(compute_accept):
(Handshaker):
(Handshaker.__init__):
(Handshaker._validate_connection_header):
(Handshaker.do_handshake):
(Handshaker._get_origin):
(Handshaker._check_version):
(Handshaker._set_protocol):
(Handshaker._parse_extensions):
(Handshaker._validate_key):
(Handshaker._get_key):
(Handshaker._send_handshake):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi00.py: Added.
(Handshaker):
(Handshaker.__init__):
(Handshaker.do_handshake):
(Handshaker._set_resource):
(Handshaker._set_subprotocol):
(Handshaker._set_location):
(Handshaker._set_origin):
(Handshaker._set_protocol_version):
(Handshaker._set_challenge_response):
(Handshaker._get_key_value):
(Handshaker._get_challenge):
(Handshaker._send_handshake):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/headerparserhandler.py: Added.
(ApacheLogHandler):
(ApacheLogHandler.__init__):
(ApacheLogHandler.emit):
(_configure_logging):
(_parse_option):
(_create_dispatcher):
(headerparserhandler):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/http_header_util.py: Added.
(_is_char):
(_is_ctl):
(ParsingState):
(ParsingState.__init__):
(peek):
(consume):
(consume_string):
(consume_lws):
(consume_lwses):
(consume_token):
(consume_token_or_quoted_string):
(quote_if_necessary):
(parse_uri):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/memorizingfile.py: Added.
(MemorizingFile):
(MemorizingFile.__init__):
(MemorizingFile.__getattribute__):
(MemorizingFile.readline):
(MemorizingFile.get_memorized_lines):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/msgutil.py: Added.
(close_connection):
(send_message):
(receive_message):
(send_ping):
(MessageReceiver):
(MessageReceiver.receives):
(MessageReceiver.provides):
(MessageReceiver.should):
(MessageReceiver.__init__):
(MessageReceiver.run):
(MessageReceiver.receive):
(MessageReceiver.receive_nowait):
(MessageReceiver.stop):
(MessageSender):
(MessageSender.sends):
(MessageSender.provides):
(MessageSender.should):
(MessageSender.__init__):
(MessageSender.run):
(MessageSender.send):
(MessageSender.send_nowait):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py: Added.
(_StandaloneConnection):
(_StandaloneConnection.__init__):
(_StandaloneConnection.get_local_addr):
(_StandaloneConnection.get_remote_addr):
(_StandaloneConnection.write):
(_StandaloneConnection.read):
(_StandaloneConnection.get_memorized_lines):
(_StandaloneRequest):
(_StandaloneRequest.__init__):
(_StandaloneRequest.get_uri):
(_StandaloneRequest.get_method):
(_StandaloneRequest.is_https):
(_StandaloneRequest._drain_received_data):
(_StandaloneSSLConnection):
(_StandaloneSSLConnection.for):
(_StandaloneSSLConnection.__init__):
(_StandaloneSSLConnection.__getattribute__):
(_StandaloneSSLConnection.__setattr__):
(_StandaloneSSLConnection.makefile):
(WebSocketServer):
(WebSocketServer.__init__):
(WebSocketServer._create_sockets):
(WebSocketServer.server_bind):
(WebSocketServer.server_activate):
(WebSocketServer.server_close):
(WebSocketServer.fileno):
(WebSocketServer.handle_error):
(WebSocketServer.get_request):
(WebSocketServer.serve_forever):
(WebSocketServer.shutdown):
(WebSocketRequestHandler):
(WebSocketRequestHandler.setup):
(WebSocketRequestHandler.setup.SocketServer):
(WebSocketRequestHandler.__init__):
(WebSocketRequestHandler.parse_request):
(WebSocketRequestHandler.log_request):
(WebSocketRequestHandler.log_error):
(WebSocketRequestHandler.is_cgi):
(_configure_logging):
(_alias_handlers):
(_build_option_parser):
(ThreadMonitor):
(ThreadMonitor.__init__):
(ThreadMonitor.run):
(_parse_args_and_config):
(_main):
(_main.if):
(_main.if.__check_script):
* Scripts/webkitpy/thirdparty/mod_pywebsocket/stream.py: Copied from Tools/Scripts/webkitpy/thirdparty/__init___unittest.py.
* Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py: Added.
(get_stack_trace):
(prepend_message_to_exception):
(__translate_interp):
(get_script_interp):
(wrap_popen3_for_win):
(wrap_popen3_for_win.__wrap_popen3):
(hexify):
(get_class_logger):
(NoopMasker):
(NoopMasker.__init__):
(NoopMasker.mask):
(RepeatedXorMasker):
(RepeatedXorMasker.__init__):
(RepeatedXorMasker.mask):
(DeflateRequest):
(DeflateRequest.for):
(DeflateRequest.__init__):
(DeflateRequest.__getattribute__):
(DeflateRequest.__setattr__):
(_Deflater):
(_Deflater.__init__):
(_Deflater.compress_and_flush):
(_Inflater):
(_Inflater.__init__):
(_Inflater.decompress):
(_Inflater.append):
(_Inflater.reset):
(_RFC1979Deflater):
(_RFC1979Deflater.that):
(_RFC1979Deflater.__init__):
(_RFC1979Deflater.filter):
(_RFC1979Inflater):
(_RFC1979Inflater.for):
(_RFC1979Inflater.__init__):
(_RFC1979Inflater.filter):
(DeflateSocket):
(DeflateSocket.for):
(DeflateSocket.__init__):
(DeflateSocket.recv):
(DeflateSocket.sendall):
(DeflateSocket.send):
(DeflateConnection):
(DeflateConnection.for):
(DeflateConnection.__init__):
(DeflateConnection.get_remote_addr):
(DeflateConnection.put_bytes):
(DeflateConnection.read):
(DeflateConnection.write):
(_is_ewouldblock_errno):
(drain_received_data):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@116668 268f45cc-cd09-0410-ab3c-d52691b4dbfc

24 files changed:
Tools/ChangeLog
Tools/Scripts/webkitpy/layout_tests/servers/websocket_server.py
Tools/Scripts/webkitpy/thirdparty/__init__.py
Tools/Scripts/webkitpy/thirdparty/__init___unittest.py
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/COPYING [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/__init__.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_base.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hixie75.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/dispatch.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/__init__.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/draft75.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi00.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/headerparserhandler.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/http_header_util.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/memorizingfile.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/msgutil.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py [new file with mode: 0755]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/stream.py [new file with mode: 0644]
Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py [new file with mode: 0644]

index 7e0e431..a679254 100644 (file)
@@ -1,3 +1,352 @@
+2012-05-10  Takashi Toyoshima  <toyoshim@chromium.org>
+
+        don't use autoinstall to import pywebsocket but check it in WebKit directly.
+        https://bugs.webkit.org/show_bug.cgi?id=86107
+
+        Reviewed by Adam Barth.
+
+        This change removes pywebsocket from webkitpy's autoinstall list and
+        imports pywebsocket 0.7.4 directly into webkitpy/thirparty.
+
+        * Scripts/webkitpy/layout_tests/servers/websocket_server.py:
+        (PyWebSocket._prepare_config):
+        * Scripts/webkitpy/thirdparty/__init__.py:
+        (AutoinstallImportHook.find_module):
+        (AutoinstallImportHook._install_irc):
+        * Scripts/webkitpy/thirdparty/__init___unittest.py:
+        (ThirdpartyTest.test_import_hook.MockImportHook.__init__):
+        (ThirdpartyTest.test_import_hook.MockImportHook._install_eliza):
+        (ThirdpartyTest):
+        (ThirdpartyTest.test_import_hook):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/COPYING: Added.
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/__init__.py: Added.
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_base.py: Added.
+        (ConnectionTerminatedException):
+        (InvalidFrameException):
+        (BadOperationException):
+        (UnsupportedFrameException):
+        (InvalidUTF8Exception):
+        (StreamBase):
+        (StreamBase.__init__):
+        (StreamBase._read):
+        (StreamBase._write):
+        (StreamBase.receive_bytes):
+        (StreamBase._read_until):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hixie75.py: Added.
+        (for):
+        (StreamHixie75):
+        (StreamHixie75.for):
+        (StreamHixie75.__init__):
+        (StreamHixie75.send_message):
+        (StreamHixie75._read_payload_length_hixie75):
+        (StreamHixie75.receive_message):
+        (StreamHixie75._send_closing_handshake):
+        (StreamHixie75.close_connection):
+        (StreamHixie75.send_ping):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py: Added.
+        (Frame):
+        (Frame.__init__):
+        (create_length_header):
+        (create_header):
+        (_build_frame):
+        (_filter_and_format_frame_object):
+        (create_binary_frame):
+        (create_text_frame):
+        (FragmentedFrameBuilder):
+        (FragmentedFrameBuilder.to):
+        (FragmentedFrameBuilder.__init__):
+        (FragmentedFrameBuilder.build):
+        (_create_control_frame):
+        (create_ping_frame):
+        (create_pong_frame):
+        (create_close_frame):
+        (StreamOptions):
+        (StreamOptions.__init__):
+        (Stream):
+        (Stream.for):
+        (Stream.__init__):
+        (Stream._receive_frame):
+        (Stream._receive_frame_as_frame_object):
+        (Stream.send_message):
+        (Stream.receive_message):
+        (Stream._send_closing_handshake):
+        (Stream.close_connection):
+        (Stream.send_ping):
+        (Stream._send_pong):
+        (Stream._drain_received_data):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py: Added.
+        (is_control_opcode):
+        (ExtensionParameter):
+        (ExtensionParameter.__init__):
+        (ExtensionParameter.name):
+        (ExtensionParameter.add_parameter):
+        (ExtensionParameter.get_parameters):
+        (ExtensionParameter.get_parameter_names):
+        (ExtensionParameter.has_parameter):
+        (ExtensionParameter.get_parameter_value):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/dispatch.py: Added.
+        (DispatchException):
+        (DispatchException.__init__):
+        (_default_passive_closing_handshake_handler):
+        (_normalize_path):
+        (_create_path_to_resource_converter):
+        (_create_path_to_resource_converter.converter):
+        (_enumerate_handler_file_paths):
+        (_HandlerSuite):
+        (_HandlerSuite.__init__):
+        (_source_handler_file):
+        (_extract_handler):
+        (Dispatcher):
+        (Dispatcher.maintains):
+        (Dispatcher.__init__):
+        (Dispatcher.add_resource_path_alias):
+        (Dispatcher.source_warnings):
+        (Dispatcher.do_extra_handshake):
+        (Dispatcher.transfer_data):
+        (Dispatcher.passive_closing_handshake):
+        (Dispatcher.get_handler_suite):
+        (Dispatcher._source_handler_files_in_dir):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py: Added.
+        (ExtensionProcessorInterface):
+        (ExtensionProcessorInterface.get_extension_response):
+        (ExtensionProcessorInterface.setup_stream_options):
+        (DeflateStreamExtensionProcessor):
+        (DeflateStreamExtensionProcessor.__init__):
+        (DeflateStreamExtensionProcessor.get_extension_response):
+        (DeflateStreamExtensionProcessor.setup_stream_options):
+        (DeflateFrameExtensionProcessor):
+        (DeflateFrameExtensionProcessor.__init__):
+        (DeflateFrameExtensionProcessor.get_extension_response):
+        (DeflateFrameExtensionProcessor.setup_stream_options):
+        (DeflateFrameExtensionProcessor.setup_stream_options._OutgoingFilter):
+        (DeflateFrameExtensionProcessor.setup_stream_options._OutgoingFilter.__init__):
+        (DeflateFrameExtensionProcessor.setup_stream_options._OutgoingFilter.filter):
+        (DeflateFrameExtensionProcessor.setup_stream_options._IncomingFilter):
+        (DeflateFrameExtensionProcessor.setup_stream_options._IncomingFilter.__init__):
+        (DeflateFrameExtensionProcessor.setup_stream_options._IncomingFilter.filter):
+        (DeflateFrameExtensionProcessor.set_response_window_bits):
+        (DeflateFrameExtensionProcessor.set_response_no_context_takeover):
+        (DeflateFrameExtensionProcessor.enable_outgoing_compression):
+        (DeflateFrameExtensionProcessor.disable_outgoing_compression):
+        (DeflateFrameExtensionProcessor._outgoing_filter):
+        (DeflateFrameExtensionProcessor._incoming_filter):
+        (get_extension_processor):
+        (get_extension_processor.is):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/__init__.py: Added.
+        (try):
+        (do_handshake):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py: Added.
+        (AbortedByUserException):
+        (HandshakeException):
+        (HandshakeException.__init__):
+        (VersionException):
+        (VersionException.__init__):
+        (get_default_port):
+        (validate_subprotocol):
+        (parse_host_header):
+        (format_header):
+        (build_location):
+        (get_mandatory_header):
+        (validate_mandatory_header):
+        (check_request_line):
+        (check_header_lines):
+        (parse_token_list):
+        (_parse_extension_param):
+        (_parse_extension):
+        (parse_extensions):
+        (format_extensions):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/draft75.py: Added.
+        (Handshaker):
+        (Handshaker.performs):
+        (Handshaker.__init__):
+        (Handshaker.do_handshake):
+        (Handshaker._set_resource):
+        (Handshaker._set_origin):
+        (Handshaker._set_location):
+        (Handshaker._set_subprotocol):
+        (Handshaker._set_protocol_version):
+        (Handshaker._sendall):
+        (Handshaker._send_handshake):
+        (Handshaker._check_header_lines):
+        (Handshaker._check_first_lines):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi.py: Added.
+        (compute_accept):
+        (Handshaker):
+        (Handshaker.__init__):
+        (Handshaker._validate_connection_header):
+        (Handshaker.do_handshake):
+        (Handshaker._get_origin):
+        (Handshaker._check_version):
+        (Handshaker._set_protocol):
+        (Handshaker._parse_extensions):
+        (Handshaker._validate_key):
+        (Handshaker._get_key):
+        (Handshaker._send_handshake):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi00.py: Added.
+        (Handshaker):
+        (Handshaker.__init__):
+        (Handshaker.do_handshake):
+        (Handshaker._set_resource):
+        (Handshaker._set_subprotocol):
+        (Handshaker._set_location):
+        (Handshaker._set_origin):
+        (Handshaker._set_protocol_version):
+        (Handshaker._set_challenge_response):
+        (Handshaker._get_key_value):
+        (Handshaker._get_challenge):
+        (Handshaker._send_handshake):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/headerparserhandler.py: Added.
+        (ApacheLogHandler):
+        (ApacheLogHandler.__init__):
+        (ApacheLogHandler.emit):
+        (_configure_logging):
+        (_parse_option):
+        (_create_dispatcher):
+        (headerparserhandler):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/http_header_util.py: Added.
+        (_is_char):
+        (_is_ctl):
+        (ParsingState):
+        (ParsingState.__init__):
+        (peek):
+        (consume):
+        (consume_string):
+        (consume_lws):
+        (consume_lwses):
+        (consume_token):
+        (consume_token_or_quoted_string):
+        (quote_if_necessary):
+        (parse_uri):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/memorizingfile.py: Added.
+        (MemorizingFile):
+        (MemorizingFile.__init__):
+        (MemorizingFile.__getattribute__):
+        (MemorizingFile.readline):
+        (MemorizingFile.get_memorized_lines):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/msgutil.py: Added.
+        (close_connection):
+        (send_message):
+        (receive_message):
+        (send_ping):
+        (MessageReceiver):
+        (MessageReceiver.receives):
+        (MessageReceiver.provides):
+        (MessageReceiver.should):
+        (MessageReceiver.__init__):
+        (MessageReceiver.run):
+        (MessageReceiver.receive):
+        (MessageReceiver.receive_nowait):
+        (MessageReceiver.stop):
+        (MessageSender):
+        (MessageSender.sends):
+        (MessageSender.provides):
+        (MessageSender.should):
+        (MessageSender.__init__):
+        (MessageSender.run):
+        (MessageSender.send):
+        (MessageSender.send_nowait):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py: Added.
+        (_StandaloneConnection):
+        (_StandaloneConnection.__init__):
+        (_StandaloneConnection.get_local_addr):
+        (_StandaloneConnection.get_remote_addr):
+        (_StandaloneConnection.write):
+        (_StandaloneConnection.read):
+        (_StandaloneConnection.get_memorized_lines):
+        (_StandaloneRequest):
+        (_StandaloneRequest.__init__):
+        (_StandaloneRequest.get_uri):
+        (_StandaloneRequest.get_method):
+        (_StandaloneRequest.is_https):
+        (_StandaloneRequest._drain_received_data):
+        (_StandaloneSSLConnection):
+        (_StandaloneSSLConnection.for):
+        (_StandaloneSSLConnection.__init__):
+        (_StandaloneSSLConnection.__getattribute__):
+        (_StandaloneSSLConnection.__setattr__):
+        (_StandaloneSSLConnection.makefile):
+        (WebSocketServer):
+        (WebSocketServer.__init__):
+        (WebSocketServer._create_sockets):
+        (WebSocketServer.server_bind):
+        (WebSocketServer.server_activate):
+        (WebSocketServer.server_close):
+        (WebSocketServer.fileno):
+        (WebSocketServer.handle_error):
+        (WebSocketServer.get_request):
+        (WebSocketServer.serve_forever):
+        (WebSocketServer.shutdown):
+        (WebSocketRequestHandler):
+        (WebSocketRequestHandler.setup):
+        (WebSocketRequestHandler.setup.SocketServer):
+        (WebSocketRequestHandler.__init__):
+        (WebSocketRequestHandler.parse_request):
+        (WebSocketRequestHandler.log_request):
+        (WebSocketRequestHandler.log_error):
+        (WebSocketRequestHandler.is_cgi):
+        (_configure_logging):
+        (_alias_handlers):
+        (_build_option_parser):
+        (ThreadMonitor):
+        (ThreadMonitor.__init__):
+        (ThreadMonitor.run):
+        (_parse_args_and_config):
+        (_main):
+        (_main.if):
+        (_main.if.__check_script):
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/stream.py: Copied from Tools/Scripts/webkitpy/thirdparty/__init___unittest.py.
+        * Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py: Added.
+        (get_stack_trace):
+        (prepend_message_to_exception):
+        (__translate_interp):
+        (get_script_interp):
+        (wrap_popen3_for_win):
+        (wrap_popen3_for_win.__wrap_popen3):
+        (hexify):
+        (get_class_logger):
+        (NoopMasker):
+        (NoopMasker.__init__):
+        (NoopMasker.mask):
+        (RepeatedXorMasker):
+        (RepeatedXorMasker.__init__):
+        (RepeatedXorMasker.mask):
+        (DeflateRequest):
+        (DeflateRequest.for):
+        (DeflateRequest.__init__):
+        (DeflateRequest.__getattribute__):
+        (DeflateRequest.__setattr__):
+        (_Deflater):
+        (_Deflater.__init__):
+        (_Deflater.compress_and_flush):
+        (_Inflater):
+        (_Inflater.__init__):
+        (_Inflater.decompress):
+        (_Inflater.append):
+        (_Inflater.reset):
+        (_RFC1979Deflater):
+        (_RFC1979Deflater.that):
+        (_RFC1979Deflater.__init__):
+        (_RFC1979Deflater.filter):
+        (_RFC1979Inflater):
+        (_RFC1979Inflater.for):
+        (_RFC1979Inflater.__init__):
+        (_RFC1979Inflater.filter):
+        (DeflateSocket):
+        (DeflateSocket.for):
+        (DeflateSocket.__init__):
+        (DeflateSocket.recv):
+        (DeflateSocket.sendall):
+        (DeflateSocket.send):
+        (DeflateConnection):
+        (DeflateConnection.for):
+        (DeflateConnection.__init__):
+        (DeflateConnection.get_remote_addr):
+        (DeflateConnection.put_bytes):
+        (DeflateConnection.read):
+        (DeflateConnection.write):
+        (_is_ewouldblock_errno):
+        (drain_received_data):
+
 2012-05-10  Raphael Kubo da Costa  <rakuco@webkit.org>
 
         [EFL][webkitpy] Define the 'wrapper' option in EflPort instead of creating a EflDriver.
index 7671f60..296ab98 100644 (file)
@@ -109,11 +109,11 @@ class PyWebSocket(http_server.Lighttpd):
         output_log = self._filesystem.join(self._output_dir, log_file_name + "-out.txt")
         self._wsout = self._filesystem.open_text_file_for_writing(output_log)
 
-        from webkitpy.thirdparty.autoinstalled.pywebsocket import mod_pywebsocket
+        from webkitpy.thirdparty import mod_pywebsocket
         python_interp = sys.executable
         # FIXME: Use self._filesystem.path_to_module(self.__module__) instead of __file__
         # I think this is trying to get the chrome directory?  Doesn't the port object know that?
-        pywebsocket_base = self._filesystem.join(self._filesystem.dirname(self._filesystem.dirname(self._filesystem.dirname(self._filesystem.abspath(__file__)))), 'thirdparty', 'autoinstalled', 'pywebsocket')
+        pywebsocket_base = self._filesystem.join(self._filesystem.dirname(self._filesystem.dirname(self._filesystem.dirname(self._filesystem.abspath(__file__)))), 'thirdparty')
         pywebsocket_script = self._filesystem.join(pywebsocket_base, 'mod_pywebsocket', 'standalone.py')
         start_cmd = [
             python_interp, '-u', pywebsocket_script,
index a0cf0f4..0df0cf7 100644 (file)
@@ -80,8 +80,6 @@ class AutoinstallImportHook(object):
             self._install_eliza()
         elif '.irc' in fullname:
             self._install_irc()
-        elif '.pywebsocket' in fullname:
-            self._install_pywebsocket()
         elif '.buildbot' in fullname:
             self._install_buildbot()
 
@@ -128,12 +126,6 @@ class AutoinstallImportHook(object):
         installer.install(url="http://downloads.sourceforge.net/project/python-irclib/python-irclib/0.4.8/python-irclib-0.4.8.zip",
                           url_subpath="ircbot.py")
 
-    def _install_pywebsocket(self):
-        pywebsocket_dir = self._fs.join(_AUTOINSTALLED_DIR, "pywebsocket")
-        installer = AutoInstaller(target_dir=pywebsocket_dir)
-        installer.install(url="http://pywebsocket.googlecode.com/files/mod_pywebsocket-0.7.4.tar.gz",
-                          url_subpath="pywebsocket-0.7.4/src/mod_pywebsocket")
-
     def _install(self, url, url_subpath):
         installer = AutoInstaller(target_dir=_AUTOINSTALLED_DIR)
         installer.install(url=url, url_subpath=url_subpath)
index b1964e9..3583ab4 100644 (file)
@@ -39,14 +39,10 @@ class ThirdpartyTest(unittest.TestCase):
             def __init__(self):
                 AutoinstallImportHook.__init__(self)
                 self._eliza_installed = False
-                self._pywebsocket_installed = False
 
             def _install_eliza(self):
                 self.eliza_installed = True
 
-            def _install_pywebsocket(self):
-                self.pywebsocket_installed = True
-
         mock_import_hook = MockImportHook()
         try:
             # The actual AutoinstallImportHook should be installed before us,
@@ -55,8 +51,6 @@ class ThirdpartyTest(unittest.TestCase):
             from webkitpy.thirdparty.autoinstalled import eliza
             self.assertTrue(mock_import_hook.eliza_installed)
 
-            from webkitpy.thirdparty.autoinstalled.pywebsocket import mod_pywebsocket
-            self.assertTrue(mock_import_hook.pywebsocket_installed)
         finally:
             sys.meta_path.remove(mock_import_hook)
 
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/COPYING b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/COPYING
new file mode 100644 (file)
index 0000000..ab9d52d
--- /dev/null
@@ -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.
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/__init__.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/__init__.py
new file mode 100644 (file)
index 0000000..c154da4
--- /dev/null
@@ -0,0 +1,184 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""WebSocket extension for Apache HTTP Server.
+
+mod_pywebsocket is a WebSocket extension for Apache HTTP Server
+intended for testing or experimental purposes. mod_python is required.
+
+
+Installation:
+
+0. Prepare an Apache HTTP Server for which mod_python is enabled.
+
+1. Specify the following Apache HTTP Server directives to suit your
+   configuration.
+
+   If mod_pywebsocket is not in the Python path, specify the following.
+   <websock_lib> is the directory where mod_pywebsocket is installed.
+
+       PythonPath "sys.path+['<websock_lib>']"
+
+   Always specify the following. <websock_handlers> is the directory where
+   user-written WebSocket handlers are placed.
+
+       PythonOption mod_pywebsocket.handler_root <websock_handlers>
+       PythonHeaderParserHandler mod_pywebsocket.headerparserhandler
+
+   To limit the search for WebSocket handlers to a directory <scan_dir>
+   under <websock_handlers>, configure as follows:
+
+       PythonOption mod_pywebsocket.handler_scan <scan_dir>
+
+   <scan_dir> is useful in saving scan time when <websock_handlers>
+   contains many non-WebSocket handler files.
+
+   If you want to 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
+       </IfModule>
+
+2. Tune Apache parameters for serving WebSocket. We'd like to note that at
+   least TimeOut directive from core features and RequestReadTimeout
+   directive from mod_reqtimeout should be modified not to kill connections
+   in only a few seconds of idle time.
+
+3. Verify installation. You can use example/console.html to poke the server.
+
+
+Writing WebSocket handlers:
+
+When a WebSocket request comes in, the resource name
+specified in the handshake is considered as if it is a file path under
+<websock_handlers> and the handler defined in
+<websock_handlers>/<resource_name>_wsh.py is invoked.
+
+For example, if the resource name is /example/chat, the handler defined in
+<websock_handlers>/example/chat_wsh.py is invoked.
+
+A WebSocket handler is composed of the following three functions:
+
+    web_socket_do_extra_handshake(request)
+    web_socket_transfer_data(request)
+    web_socket_passive_closing_handshake(request)
+
+where:
+    request: mod_python request.
+
+web_socket_do_extra_handshake is called during the handshake after the
+headers are successfully parsed and WebSocket properties (ws_location,
+ws_origin, and ws_resource) are added to request. A handler
+can reject the request by raising an exception.
+
+A request object has the following properties that you can use during the
+extra handshake (web_socket_do_extra_handshake):
+- ws_resource
+- ws_origin
+- ws_version
+- ws_location (Hixie 75 and HyBi 00 only)
+- ws_extensions (Hybi 06 and later)
+- ws_deflate (HyBi 06 and later)
+- ws_protocol
+- ws_requested_protocols (HyBi 06 and later)
+
+The last two are a bit tricky.
+
+For HyBi 06 and later, ws_protocol is always set to None when
+web_socket_do_extra_handshake is called. If ws_requested_protocols is not
+None, you must choose one subprotocol from this list and set it to
+ws_protocol.
+
+For Hixie 75 and HyBi 00, when web_socket_do_extra_handshake is called,
+ws_protocol is set to the value given by the client in
+Sec-WebSocket-Protocol (WebSocket-Protocol for Hixie 75) header or None if
+such header was not found in the opening handshake request. Finish extra
+handshake with ws_protocol untouched to accept the request subprotocol.
+Then, Sec-WebSocket-Protocol (or WebSocket-Protocol) header will be sent to
+the client in response with the same value as requested. Raise an exception
+in web_socket_do_extra_handshake to reject the requested subprotocol.
+
+web_socket_transfer_data is called after the handshake completed
+successfully. A handler can receive/send messages from/to the client
+using request. mod_pywebsocket.msgutil module provides utilities
+for data transfer.
+
+You can receive a message by the following statement.
+
+    message = request.ws_stream.receive_message()
+
+This call blocks until any complete text frame arrives, and the payload data
+of the incoming frame will be stored into message. When you're using IETF
+HyBi 00 or later protocol, receive_message() will return None on receiving
+client-initiated closing handshake. When any error occurs, receive_message()
+will raise some exception.
+
+You can send a message by the following statement.
+
+    request.ws_stream.send_message(message)
+
+Executing the following statement or just return-ing from
+web_socket_transfer_data cause connection close.
+
+    request.ws_stream.close_connection()
+
+When you're using IETF HyBi 00 or later protocol, close_connection will wait
+for closing handshake acknowledgement coming from the client. When it
+couldn't receive a valid acknowledgement, raises an exception.
+
+web_socket_passive_closing_handshake is called after the server receives
+incoming closing frame from the client peer immediately. You can specify
+code and reason by return values. They are sent as a outgoing closing frame
+from the server. A request object has the following properties that you can
+use in web_socket_passive_closing_handshake.
+- ws_close_code
+- ws_close_reason
+
+A WebSocket 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
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_base.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_base.py
new file mode 100644 (file)
index 0000000..60fb33d
--- /dev/null
@@ -0,0 +1,165 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Base stream class.
+"""
+
+
+# Note: request.connection.write/read are used in this module, even though
+# mod_python document says that they should be used only in connection
+# handlers. Unfortunately, we have no other options. For example,
+# request.write/read are not suitable because they don't allow direct raw bytes
+# writing/reading.
+
+
+from mod_pywebsocket import util
+
+
+# Exceptions
+
+
+class ConnectionTerminatedException(Exception):
+    """This exception will be raised when a connection is terminated
+    unexpectedly.
+    """
+
+    pass
+
+
+class InvalidFrameException(ConnectionTerminatedException):
+    """This exception will be raised when we received an invalid frame we
+    cannot parse.
+    """
+
+    pass
+
+
+class BadOperationException(Exception):
+    """This exception will be raised when send_message() is called on
+    server-terminated connection or receive_message() is called on
+    client-terminated connection.
+    """
+
+    pass
+
+
+class UnsupportedFrameException(Exception):
+    """This exception will be raised when we receive a frame with flag, opcode
+    we cannot handle. Handlers can just catch and ignore this exception and
+    call receive_message() again to continue processing the next frame.
+    """
+
+    pass
+
+
+class InvalidUTF8Exception(Exception):
+    """This exception will be raised when we receive a text frame which
+    contains invalid UTF-8 strings.
+    """
+
+    pass
+
+
+class StreamBase(object):
+    """Base stream class."""
+
+    def __init__(self, request):
+        """Construct an instance.
+
+        Args:
+            request: mod_python request.
+        """
+
+        self._logger = util.get_class_logger(self)
+
+        self._request = request
+
+    def _read(self, length):
+        """Reads length bytes from connection. In case we catch any exception,
+        prepends remote address to the exception message and raise again.
+
+        Raises:
+            ConnectionTerminatedException: when read returns empty string.
+        """
+
+        bytes = self._request.connection.read(length)
+        if not bytes:
+            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:
+            self._request.connection.write(bytes)
+        except Exception, e:
+            util.prepend_message_to_exception(
+                    'Failed to send message to %r: ' %
+                            (self._request.connection.remote_addr,),
+                    e)
+            raise
+
+    def receive_bytes(self, length):
+        """Receives multiple bytes. Retries read when we couldn't receive the
+        specified amount.
+
+        Raises:
+            ConnectionTerminatedException: when read returns empty string.
+        """
+
+        bytes = []
+        while length > 0:
+            new_bytes = self._read(length)
+            bytes.append(new_bytes)
+            length -= len(new_bytes)
+        return ''.join(bytes)
+
+    def _read_until(self, delim_char):
+        """Reads bytes until we encounter delim_char. The result will not
+        contain delim_char.
+
+        Raises:
+            ConnectionTerminatedException: when read returns empty string.
+        """
+
+        bytes = []
+        while True:
+            ch = self._read(1)
+            if ch == delim_char:
+                break
+            bytes.append(ch)
+        return ''.join(bytes)
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hixie75.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hixie75.py
new file mode 100644 (file)
index 0000000..c84ca6e
--- /dev/null
@@ -0,0 +1,228 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""This file provides a class for parsing/building frames of the WebSocket
+protocol version HyBi 00 and Hixie 75.
+
+Specification:
+http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
+"""
+
+
+from mod_pywebsocket import common
+from mod_pywebsocket._stream_base import BadOperationException
+from mod_pywebsocket._stream_base import ConnectionTerminatedException
+from mod_pywebsocket._stream_base import InvalidFrameException
+from mod_pywebsocket._stream_base import StreamBase
+from mod_pywebsocket._stream_base import UnsupportedFrameException
+from mod_pywebsocket import util
+
+
+class StreamHixie75(StreamBase):
+    """A class for parsing/building frames of the WebSocket protocol version
+    HyBi 00 and Hixie 75.
+    """
+
+    def __init__(self, request, enable_closing_handshake=False):
+        """Construct an instance.
+
+        Args:
+            request: mod_python request.
+            enable_closing_handshake: to let StreamHixie75 perform closing
+                                      handshake as specified in HyBi 00, set
+                                      this option to True.
+        """
+
+        StreamBase.__init__(self, request)
+
+        self._logger = util.get_class_logger(self)
+
+        self._enable_closing_handshake = enable_closing_handshake
+
+        self._request.client_terminated = False
+        self._request.server_terminated = False
+
+    def send_message(self, message, end=True, binary=False):
+        """Send message.
+
+        Args:
+            message: unicode string to send.
+            binary: not used in hixie75.
+
+        Raises:
+            BadOperationException: when called on a server-terminated
+                connection.
+        """
+
+        if not end:
+            raise BadOperationException(
+                'StreamHixie75 doesn\'t support send_message with end=False')
+
+        if binary:
+            raise BadOperationException(
+                'StreamHixie75 doesn\'t support send_message with binary=True')
+
+        if self._request.server_terminated:
+            raise BadOperationException(
+                'Requested send_message after sending out a closing handshake')
+
+        self._write(''.join(['\x00', message.encode('utf-8'), '\xff']))
+
+    def _read_payload_length_hixie75(self):
+        """Reads a length header in a Hixie75 version frame with length.
+
+        Raises:
+            ConnectionTerminatedException: when read returns empty string.
+        """
+
+        length = 0
+        while True:
+            b_str = self._read(1)
+            b = ord(b_str)
+            length = length * 128 + (b & 0x7f)
+            if (b & 0x80) == 0:
+                break
+        return length
+
+    def receive_message(self):
+        """Receive a WebSocket frame and return its payload an unicode string.
+
+        Returns:
+            payload unicode string in a WebSocket frame.
+
+        Raises:
+            ConnectionTerminatedException: when read returns empty
+                string.
+            BadOperationException: when called on a client-terminated
+                connection.
+        """
+
+        if self._request.client_terminated:
+            raise BadOperationException(
+                'Requested receive_message after receiving a closing '
+                'handshake')
+
+        while True:
+            # Read 1 byte.
+            # mp_conn.read will block if no bytes are available.
+            # Timeout is controlled by TimeOut directive of Apache.
+            frame_type_str = self.receive_bytes(1)
+            frame_type = ord(frame_type_str)
+            if (frame_type & 0x80) == 0x80:
+                # The payload length is specified in the frame.
+                # Read and discard.
+                length = self._read_payload_length_hixie75()
+                if length > 0:
+                    _ = self.receive_bytes(length)
+                # 5.3 3. 12. if /type/ is 0xFF and /length/ is 0, then set the
+                # /client terminated/ flag and abort these steps.
+                if not self._enable_closing_handshake:
+                    continue
+
+                if frame_type == 0xFF and length == 0:
+                    self._request.client_terminated = True
+
+                    if self._request.server_terminated:
+                        self._logger.debug(
+                            'Received ack for server-initiated closing '
+                            'handshake')
+                        return None
+
+                    self._logger.debug(
+                        'Received client-initiated closing handshake')
+
+                    self._send_closing_handshake()
+                    self._logger.debug(
+                        'Sent ack for client-initiated closing handshake')
+                    return None
+            else:
+                # The payload is delimited with \xff.
+                bytes = self._read_until('\xff')
+                # The WebSocket protocol section 4.4 specifies that invalid
+                # characters must be replaced with U+fffd REPLACEMENT
+                # CHARACTER.
+                message = bytes.decode('utf-8', 'replace')
+                if frame_type == 0x00:
+                    return message
+                # Discard data of other types.
+
+    def _send_closing_handshake(self):
+        if not self._enable_closing_handshake:
+            raise BadOperationException(
+                'Closing handshake is not supported in Hixie 75 protocol')
+
+        self._request.server_terminated = True
+
+        # 5.3 the server may decide to terminate the WebSocket connection by
+        # running through the following steps:
+        # 1. send a 0xFF byte and a 0x00 byte to the client to indicate the
+        # start of the closing handshake.
+        self._write('\xff\x00')
+
+    def close_connection(self, unused_code='', unused_reason=''):
+        """Closes a WebSocket connection.
+
+        Raises:
+            ConnectionTerminatedException: when closing handshake was
+                not successfull.
+        """
+
+        if self._request.server_terminated:
+            self._logger.debug(
+                'Requested close_connection but server is already terminated')
+            return
+
+        if not self._enable_closing_handshake:
+            self._request.server_terminated = True
+            self._logger.debug('Connection closed')
+            return
+
+        self._send_closing_handshake()
+        self._logger.debug('Sent server-initiated closing handshake')
+
+        # TODO(ukai): 2. wait until the /client terminated/ flag has been set,
+        # or until a server-defined timeout expires.
+        #
+        # For now, we expect receiving closing handshake right after sending
+        # out closing handshake, and if we couldn't receive non-handshake
+        # frame, we take it as ConnectionTerminatedException.
+        message = self.receive_message()
+        if message is not None:
+            raise ConnectionTerminatedException(
+                'Didn\'t receive valid ack for closing handshake')
+        # TODO: 3. close the WebSocket connection.
+        # note: mod_python Connection (mp_conn) doesn't have close method.
+
+    def send_ping(self, body):
+        raise BadOperationException(
+            'StreamHixie75 doesn\'t support send_ping')
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py
new file mode 100644 (file)
index 0000000..c00f569
--- /dev/null
@@ -0,0 +1,668 @@
+# Copyright 2012, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""This file provides classes and helper functions for parsing/building frames
+of the WebSocket protocol (RFC 6455).
+
+Specification:
+http://tools.ietf.org/html/rfc6455
+"""
+
+
+from collections import deque
+import os
+import struct
+
+from mod_pywebsocket import common
+from mod_pywebsocket import util
+from mod_pywebsocket._stream_base import BadOperationException
+from mod_pywebsocket._stream_base import ConnectionTerminatedException
+from mod_pywebsocket._stream_base import InvalidFrameException
+from mod_pywebsocket._stream_base import InvalidUTF8Exception
+from mod_pywebsocket._stream_base import StreamBase
+from mod_pywebsocket._stream_base import UnsupportedFrameException
+
+
+_NOOP_MASKER = util.NoopMasker()
+
+
+class Frame(object):
+
+    def __init__(self, fin=1, rsv1=0, rsv2=0, rsv3=0,
+                 opcode=None, payload=''):
+        self.fin = fin
+        self.rsv1 = rsv1
+        self.rsv2 = rsv2
+        self.rsv3 = rsv3
+        self.opcode = opcode
+        self.payload = payload
+
+
+# Helper functions made public to be used for writing unittests for WebSocket
+# clients.
+
+
+def create_length_header(length, mask):
+    """Creates a length header.
+
+    Args:
+        length: Frame length. Must be less than 2^63.
+        mask: Mask bit. Must be boolean.
+
+    Raises:
+        ValueError: when bad data is given.
+    """
+
+    if mask:
+        mask_bit = 1 << 7
+    else:
+        mask_bit = 0
+
+    if length < 0:
+        raise ValueError('length must be non negative integer')
+    elif length <= 125:
+        return chr(mask_bit | length)
+    elif length < (1 << 16):
+        return chr(mask_bit | 126) + struct.pack('!H', length)
+    elif length < (1 << 63):
+        return chr(mask_bit | 127) + struct.pack('!Q', length)
+    else:
+        raise ValueError('Payload is too big for one frame')
+
+
+def create_header(opcode, payload_length, fin, rsv1, rsv2, rsv3, mask):
+    """Creates a frame header.
+
+    Raises:
+        Exception: when bad data is given.
+    """
+
+    if opcode < 0 or 0xf < opcode:
+        raise ValueError('Opcode out of range')
+
+    if payload_length < 0 or (1 << 63) <= payload_length:
+        raise ValueError('payload_length out of range')
+
+    if (fin | rsv1 | rsv2 | rsv3) & ~1:
+        raise ValueError('FIN bit and Reserved bit parameter must be 0 or 1')
+
+    header = ''
+
+    first_byte = ((fin << 7)
+                  | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4)
+                  | opcode)
+    header += chr(first_byte)
+    header += create_length_header(payload_length, mask)
+
+    return header
+
+
+def _build_frame(header, body, mask):
+    if not mask:
+        return header + body
+
+    masking_nonce = os.urandom(4)
+    masker = util.RepeatedXorMasker(masking_nonce)
+
+    return header + masking_nonce + masker.mask(body)
+
+
+def _filter_and_format_frame_object(frame, mask, frame_filters):
+    for frame_filter in frame_filters:
+        frame_filter.filter(frame)
+
+    header = create_header(
+        frame.opcode, len(frame.payload), frame.fin,
+        frame.rsv1, frame.rsv2, frame.rsv3, mask)
+    return _build_frame(header, frame.payload, mask)
+
+
+def create_binary_frame(
+    message, opcode=common.OPCODE_BINARY, fin=1, mask=False, frame_filters=[]):
+    """Creates a simple binary frame with no extension, reserved bit."""
+
+    frame = Frame(fin=fin, opcode=opcode, payload=message)
+    return _filter_and_format_frame_object(frame, mask, frame_filters)
+
+
+def create_text_frame(
+    message, opcode=common.OPCODE_TEXT, fin=1, mask=False, frame_filters=[]):
+    """Creates a simple text frame with no extension, reserved bit."""
+
+    encoded_message = message.encode('utf-8')
+    return create_binary_frame(encoded_message, opcode, fin, mask,
+                               frame_filters)
+
+
+class FragmentedFrameBuilder(object):
+    """A stateful class to send a message as fragments."""
+
+    def __init__(self, mask, frame_filters=[]):
+        """Constructs an instance."""
+
+        self._mask = mask
+        self._frame_filters = frame_filters
+
+        self._started = False
+
+        # Hold opcode of the first frame in messages to verify types of other
+        # frames in the message are all the same.
+        self._opcode = common.OPCODE_TEXT
+
+    def build(self, message, end, binary):
+        if binary:
+            frame_type = common.OPCODE_BINARY
+        else:
+            frame_type = common.OPCODE_TEXT
+        if self._started:
+            if self._opcode != frame_type:
+                raise ValueError('Message types are different in frames for '
+                                 'the same message')
+            opcode = common.OPCODE_CONTINUATION
+        else:
+            opcode = frame_type
+            self._opcode = frame_type
+
+        if end:
+            self._started = False
+            fin = 1
+        else:
+            self._started = True
+            fin = 0
+
+        if binary:
+            return create_binary_frame(
+                message, opcode, fin, self._mask, self._frame_filters)
+        else:
+            return create_text_frame(
+                message, opcode, fin, self._mask, self._frame_filters)
+
+
+def _create_control_frame(opcode, body, mask, frame_filters):
+    frame = Frame(opcode=opcode, payload=body)
+
+    return _filter_and_format_frame_object(frame, mask, frame_filters)
+
+
+def create_ping_frame(body, mask=False, frame_filters=[]):
+    return _create_control_frame(common.OPCODE_PING, body, mask, frame_filters)
+
+
+def create_pong_frame(body, mask=False, frame_filters=[]):
+    return _create_control_frame(common.OPCODE_PONG, body, mask, frame_filters)
+
+
+def create_close_frame(body, mask=False, frame_filters=[]):
+    return _create_control_frame(
+        common.OPCODE_CLOSE, body, mask, frame_filters)
+
+
+class StreamOptions(object):
+    """Holds option values to configure Stream objects."""
+
+    def __init__(self):
+        """Constructs StreamOptions."""
+
+        # Enables deflate-stream extension.
+        self.deflate_stream = False
+
+        # Filters applied to frames.
+        self.outgoing_frame_filters = []
+        self.incoming_frame_filters = []
+
+        self.mask_send = False
+        self.unmask_receive = True
+
+
+class Stream(StreamBase):
+    """A class for parsing/building frames of the WebSocket protocol
+    (RFC 6455).
+    """
+
+    def __init__(self, request, options):
+        """Constructs an instance.
+
+        Args:
+            request: mod_python request.
+        """
+
+        StreamBase.__init__(self, request)
+
+        self._logger = util.get_class_logger(self)
+
+        self._options = options
+
+        if self._options.deflate_stream:
+            self._logger.debug('Setup filter for deflate-stream')
+            self._request = util.DeflateRequest(self._request)
+
+        self._request.client_terminated = False
+        self._request.server_terminated = False
+
+        # Holds body of received fragments.
+        self._received_fragments = []
+        # Holds the opcode of the first fragment.
+        self._original_opcode = None
+
+        self._writer = FragmentedFrameBuilder(
+            self._options.mask_send, self._options.outgoing_frame_filters)
+
+        self._ping_queue = deque()
+
+    def _receive_frame(self):
+        """Receives a frame and return data in the frame as a tuple containing
+        each header field and payload separately.
+
+        Raises:
+            ConnectionTerminatedException: when read returns empty
+                string.
+            InvalidFrameException: when the frame contains invalid data.
+        """
+
+        received = self.receive_bytes(2)
+
+        first_byte = ord(received[0])
+        fin = (first_byte >> 7) & 1
+        rsv1 = (first_byte >> 6) & 1
+        rsv2 = (first_byte >> 5) & 1
+        rsv3 = (first_byte >> 4) & 1
+        opcode = first_byte & 0xf
+
+        second_byte = ord(received[1])
+        mask = (second_byte >> 7) & 1
+        payload_length = second_byte & 0x7f
+
+        if (mask == 1) != self._options.unmask_receive:
+            raise InvalidFrameException(
+                'Mask bit on the received frame did\'nt match masking '
+                'configuration for received frames')
+
+        # 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))
+
+        return opcode, bytes, fin, rsv1, rsv2, rsv3
+
+    def _receive_frame_as_frame_object(self):
+        opcode, bytes, fin, rsv1, rsv2, rsv3 = self._receive_frame()
+
+        return Frame(fin=fin, rsv1=rsv1, rsv2=rsv2, rsv3=rsv3,
+                     opcode=opcode, payload=bytes)
+
+    def send_message(self, message, end=True, binary=False):
+        """Send message.
+
+        Args:
+            message: text in unicode or binary in str to send.
+            binary: send message as binary frame.
+
+        Raises:
+            BadOperationException: when called on a server-terminated
+                connection or called with inconsistent message type or binary
+                parameter.
+        """
+
+        if self._request.server_terminated:
+            raise BadOperationException(
+                'Requested send_message after sending out a closing handshake')
+
+        if binary and isinstance(message, unicode):
+            raise BadOperationException(
+                'Message for binary frame must be instance of str')
+
+        try:
+            self._write(self._writer.build(message, end, binary))
+        except ValueError, e:
+            raise BadOperationException(e)
+
+    def receive_message(self):
+        """Receive a WebSocket frame and return its payload as a text in
+        unicode or a binary in str.
+
+        Returns:
+            payload data of the frame
+            - as unicode instance if received text frame
+            - as str instance if received binary frame
+            or None iff received closing handshake.
+        Raises:
+            BadOperationException: when called on a client-terminated
+                connection.
+            ConnectionTerminatedException: when read returns empty
+                string.
+            InvalidFrameException: when the frame contains invalid
+                data.
+            UnsupportedFrameException: when the received frame has
+                flags, opcode we cannot handle. You can ignore this
+                exception and continue receiving the next frame.
+        """
+
+        if self._request.client_terminated:
+            raise BadOperationException(
+                'Requested receive_message after receiving a closing '
+                'handshake')
+
+        while True:
+            # mp_conn.read will block if no bytes are available.
+            # Timeout is controlled by TimeOut directive of Apache.
+
+            frame = self._receive_frame_as_frame_object()
+
+            for frame_filter in self._options.incoming_frame_filters:
+                frame_filter.filter(frame)
+
+            if frame.rsv1 or frame.rsv2 or frame.rsv3:
+                raise UnsupportedFrameException(
+                    'Unsupported flag is set (rsv = %d%d%d)' %
+                    (frame.rsv1, frame.rsv2, frame.rsv3))
+
+            if frame.opcode == common.OPCODE_CONTINUATION:
+                if not self._received_fragments:
+                    if frame.fin:
+                        raise InvalidFrameException(
+                            'Received a termination frame but fragmentation '
+                            'not started')
+                    else:
+                        raise InvalidFrameException(
+                            'Received an intermediate frame but '
+                            'fragmentation not started')
+
+                if frame.fin:
+                    # End of fragmentation frame
+                    self._received_fragments.append(frame.payload)
+                    message = ''.join(self._received_fragments)
+                    self._received_fragments = []
+                else:
+                    # Intermediate frame
+                    self._received_fragments.append(frame.payload)
+                    continue
+            else:
+                if self._received_fragments:
+                    if frame.fin:
+                        raise InvalidFrameException(
+                            'Received an unfragmented frame without '
+                            'terminating existing fragmentation')
+                    else:
+                        raise InvalidFrameException(
+                            'New fragmentation started without terminating '
+                            'existing fragmentation')
+
+                if frame.fin:
+                    # Unfragmented frame
+
+                    if (common.is_control_opcode(frame.opcode) and
+                        len(frame.payload) > 125):
+                        raise InvalidFrameException(
+                            'Application data size of control frames must be '
+                            '125 bytes or less')
+
+                    self._original_opcode = frame.opcode
+                    message = frame.payload
+                else:
+                    # Start of fragmentation frame
+
+                    if common.is_control_opcode(frame.opcode):
+                        raise InvalidFrameException(
+                            'Control frames must not be fragmented')
+
+                    self._original_opcode = frame.opcode
+                    self._received_fragments.append(frame.payload)
+                    continue
+
+            if self._original_opcode == common.OPCODE_TEXT:
+                # The WebSocket protocol section 4.4 specifies that invalid
+                # characters must be replaced with U+fffd REPLACEMENT
+                # CHARACTER.
+                try:
+                    return message.decode('utf-8')
+                except UnicodeDecodeError, e:
+                    raise InvalidUTF8Exception(e)
+            elif self._original_opcode == common.OPCODE_BINARY:
+                return message
+            elif self._original_opcode == common.OPCODE_CLOSE:
+                self._request.client_terminated = True
+
+                # Status code is optional. We can have status reason only if we
+                # have status code. Status reason can be empty string. So,
+                # allowed cases are
+                # - no application data: no code no reason
+                # - 2 octet of application data: has code but no reason
+                # - 3 or more octet of application data: both code and reason
+                if len(message) == 1:
+                    raise InvalidFrameException(
+                        'If a close frame has status code, the length of '
+                        'status code must be 2 octet')
+                elif len(message) >= 2:
+                    self._request.ws_close_code = struct.unpack(
+                        '!H', message[0:2])[0]
+                    self._request.ws_close_reason = message[2:].decode(
+                        'utf-8', 'replace')
+                    self._logger.debug(
+                        'Received close frame (code=%d, reason=%r)',
+                        self._request.ws_close_code,
+                        self._request.ws_close_reason)
+
+                # Drain junk data after the close frame if necessary.
+                self._drain_received_data()
+
+                if self._request.server_terminated:
+                    self._logger.debug(
+                        'Received ack for server-initiated closing '
+                        'handshake')
+                    return None
+
+                self._logger.debug(
+                    'Received client-initiated closing handshake')
+
+                code = common.STATUS_NORMAL_CLOSURE
+                reason = ''
+                if hasattr(self._request, '_dispatcher'):
+                    dispatcher = self._request._dispatcher
+                    code, reason = dispatcher.passive_closing_handshake(
+                        self._request)
+                self._send_closing_handshake(code, reason)
+                self._logger.debug(
+                    'Sent ack for client-initiated closing handshake')
+                return None
+            elif self._original_opcode == common.OPCODE_PING:
+                try:
+                    handler = self._request.on_ping_handler
+                    if handler:
+                        handler(self._request, message)
+                        continue
+                except AttributeError, e:
+                    pass
+                self._send_pong(message)
+            elif self._original_opcode == common.OPCODE_PONG:
+                # TODO(tyoshino): Add ping timeout handling.
+
+                inflight_pings = deque()
+
+                while True:
+                    try:
+                        expected_body = self._ping_queue.popleft()
+                        if expected_body == message:
+                            # inflight_pings contains pings ignored by the
+                            # other peer. Just forget them.
+                            self._logger.debug(
+                                'Ping %r is acked (%d pings were ignored)',
+                                expected_body, len(inflight_pings))
+                            break
+                        else:
+                            inflight_pings.append(expected_body)
+                    except IndexError, e:
+                        # The received pong was unsolicited pong. Keep the
+                        # ping queue as is.
+                        self._ping_queue = inflight_pings
+                        self._logger.debug('Received a unsolicited pong')
+                        break
+
+                try:
+                    handler = self._request.on_pong_handler
+                    if handler:
+                        handler(self._request, message)
+                        continue
+                except AttributeError, e:
+                    pass
+
+                continue
+            else:
+                raise UnsupportedFrameException(
+                    'Opcode %d is not supported' % self._original_opcode)
+
+    def _send_closing_handshake(self, code, reason):
+        if code >= (1 << 16) or code < 0:
+            raise BadOperationException('Status code is out of range')
+
+        encoded_reason = reason.encode('utf-8')
+        if len(encoded_reason) + 2 > 125:
+            raise BadOperationException(
+                'Application data size of close frames must be 125 bytes or '
+                'less')
+
+        frame = create_close_frame(
+            struct.pack('!H', code) + encoded_reason,
+            self._options.mask_send,
+            self._options.outgoing_frame_filters)
+
+        self._request.server_terminated = True
+
+        self._write(frame)
+
+    def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''):
+        """Closes a WebSocket connection."""
+
+        if self._request.server_terminated:
+            self._logger.debug(
+                'Requested close_connection but server is already terminated')
+            return
+
+        self._send_closing_handshake(code, reason)
+        self._logger.debug('Sent server-initiated closing handshake')
+
+        if (code == common.STATUS_GOING_AWAY or
+            code == common.STATUS_PROTOCOL_ERROR):
+            # It doesn't make sense to wait for a close frame if the reason is
+            # protocol error or that the server is going away. For some of
+            # other reasons, it might not make sense to wait for a close frame,
+            # but it's not clear, yet.
+            return
+
+        # TODO(ukai): 2. wait until the /client terminated/ flag has been set,
+        # or until a server-defined timeout expires.
+        #
+        # For now, we expect receiving closing handshake right after sending
+        # out closing handshake.
+        message = self.receive_message()
+        if message is not None:
+            raise ConnectionTerminatedException(
+                'Didn\'t receive valid ack for closing handshake')
+        # TODO: 3. close the WebSocket connection.
+        # note: mod_python Connection (mp_conn) doesn't have close method.
+
+    def send_ping(self, body=''):
+        if len(body) > 125:
+            raise ValueError(
+                'Application data size of control frames must be 125 bytes or '
+                'less')
+        frame = create_ping_frame(
+            body,
+            self._options.mask_send,
+            self._options.outgoing_frame_filters)
+        self._write(frame)
+
+        self._ping_queue.append(body)
+
+    def _send_pong(self, body):
+        if len(body) > 125:
+            raise ValueError(
+                'Application data size of control frames must be 125 bytes or '
+                'less')
+        frame = create_pong_frame(
+            body,
+            self._options.mask_send,
+            self._options.outgoing_frame_filters)
+        self._write(frame)
+
+    def _drain_received_data(self):
+        """Drains unread data in the receive buffer to avoid sending out TCP
+        RST packet. This is because when deflate-stream is enabled, some
+        DEFLATE block for flushing data may follow a close frame. If any data
+        remains in the receive buffer of a socket when the socket is closed,
+        it sends out TCP RST packet to the other peer.
+
+        Since mod_python's mp_conn object doesn't support non-blocking read,
+        we perform this only when pywebsocket is running in standalone mode.
+        """
+
+        # If self._options.deflate_stream is true, self._request is
+        # DeflateRequest, so we can get wrapped request object by
+        # self._request._request.
+        #
+        # Only _StandaloneRequest has _drain_received_data method.
+        if (self._options.deflate_stream and
+            ('_drain_received_data' in dir(self._request._request))):
+            self._request._request._drain_received_data()
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py
new file mode 100644 (file)
index 0000000..ba670bb
--- /dev/null
@@ -0,0 +1,179 @@
+# Copyright 2012, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+# Constants indicating WebSocket protocol version.
+VERSION_HIXIE75 = -1
+VERSION_HYBI00 = 0
+VERSION_HYBI01 = 1
+VERSION_HYBI02 = 2
+VERSION_HYBI03 = 2
+VERSION_HYBI04 = 4
+VERSION_HYBI05 = 5
+VERSION_HYBI06 = 6
+VERSION_HYBI07 = 7
+VERSION_HYBI08 = 8
+VERSION_HYBI09 = 8
+VERSION_HYBI10 = 8
+VERSION_HYBI11 = 8
+VERSION_HYBI12 = 8
+VERSION_HYBI13 = 13
+VERSION_HYBI14 = 13
+VERSION_HYBI15 = 13
+VERSION_HYBI16 = 13
+VERSION_HYBI17 = 13
+
+# Constants indicating WebSocket protocol latest version.
+VERSION_HYBI_LATEST = VERSION_HYBI13
+
+# Port numbers
+DEFAULT_WEB_SOCKET_PORT = 80
+DEFAULT_WEB_SOCKET_SECURE_PORT = 443
+
+# Schemes
+WEB_SOCKET_SCHEME = 'ws'
+WEB_SOCKET_SECURE_SCHEME = 'wss'
+
+# Frame opcodes defined in the spec.
+OPCODE_CONTINUATION = 0x0
+OPCODE_TEXT = 0x1
+OPCODE_BINARY = 0x2
+OPCODE_CLOSE = 0x8
+OPCODE_PING = 0x9
+OPCODE_PONG = 0xa
+
+# UUIDs used by HyBi 04 and later opening handshake and frame masking.
+WEBSOCKET_ACCEPT_UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
+
+# Opening handshake header names and expected values.
+UPGRADE_HEADER = 'Upgrade'
+WEBSOCKET_UPGRADE_TYPE = 'websocket'
+WEBSOCKET_UPGRADE_TYPE_HIXIE75 = 'WebSocket'
+CONNECTION_HEADER = 'Connection'
+UPGRADE_CONNECTION_TYPE = 'Upgrade'
+HOST_HEADER = 'Host'
+ORIGIN_HEADER = 'Origin'
+SEC_WEBSOCKET_ORIGIN_HEADER = 'Sec-WebSocket-Origin'
+SEC_WEBSOCKET_KEY_HEADER = 'Sec-WebSocket-Key'
+SEC_WEBSOCKET_ACCEPT_HEADER = 'Sec-WebSocket-Accept'
+SEC_WEBSOCKET_VERSION_HEADER = 'Sec-WebSocket-Version'
+SEC_WEBSOCKET_PROTOCOL_HEADER = 'Sec-WebSocket-Protocol'
+SEC_WEBSOCKET_EXTENSIONS_HEADER = 'Sec-WebSocket-Extensions'
+SEC_WEBSOCKET_DRAFT_HEADER = 'Sec-WebSocket-Draft'
+SEC_WEBSOCKET_KEY1_HEADER = 'Sec-WebSocket-Key1'
+SEC_WEBSOCKET_KEY2_HEADER = 'Sec-WebSocket-Key2'
+SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location'
+
+# Extensions
+DEFLATE_STREAM_EXTENSION = 'deflate-stream'
+DEFLATE_FRAME_EXTENSION = 'deflate-frame'
+X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
+
+# Status codes
+# Code STATUS_NO_STATUS_RECEIVED, STATUS_ABNORMAL_CLOSURE, and
+# STATUS_TLS_HANDSHAKE are pseudo codes to indicate specific error cases.
+# Could not be used for codes in actual closing frames.
+# Application level errors must use codes in the range
+# STATUS_USER_REGISTERED_BASE to STATUS_USER_PRIVATE_MAX. The codes in the
+# range STATUS_USER_REGISTERED_BASE to STATUS_USER_REGISTERED_MAX are managed
+# by IANA. Usually application must define user protocol level errors in the
+# range STATUS_USER_PRIVATE_BASE to STATUS_USER_PRIVATE_MAX.
+STATUS_NORMAL_CLOSURE = 1000
+STATUS_GOING_AWAY = 1001
+STATUS_PROTOCOL_ERROR = 1002
+STATUS_UNSUPPORTED_DATA = 1003
+STATUS_NO_STATUS_RECEIVED = 1005
+STATUS_ABNORMAL_CLOSURE = 1006
+STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007
+STATUS_POLICY_VIOLATION = 1008
+STATUS_MESSAGE_TOO_BIG = 1009
+STATUS_MANDATORY_EXTENSION = 1010
+STATUS_INTERNAL_SERVER_ERROR = 1011
+STATUS_TLS_HANDSHAKE = 1015
+STATUS_USER_REGISTERED_BASE = 3000
+STATUS_USER_REGISTERED_MAX = 3999
+STATUS_USER_PRIVATE_BASE = 4000
+STATUS_USER_PRIVATE_MAX = 4999
+# Following definitions are aliases to keep compatibility. Applications must
+# not use these obsoleted definitions anymore.
+STATUS_NORMAL = STATUS_NORMAL_CLOSURE
+STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA
+STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED
+STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE
+STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA
+STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION
+
+# HTTP status codes
+HTTP_STATUS_BAD_REQUEST = 400
+HTTP_STATUS_FORBIDDEN = 403
+HTTP_STATUS_NOT_FOUND = 404
+
+
+def is_control_opcode(opcode):
+    return (opcode >> 3) == 1
+
+
+class ExtensionParameter(object):
+    """Holds information about an extension which is exchanged on extension
+    negotiation in opening handshake.
+    """
+
+    def __init__(self, name):
+        self._name = name
+        # TODO(tyoshino): Change the data structure to more efficient one such
+        # as dict when the spec changes to say like
+        # - Parameter names must be unique
+        # - The order of parameters is not significant
+        self._parameters = []
+
+    def name(self):
+        return self._name
+
+    def add_parameter(self, name, value):
+        self._parameters.append((name, value))
+
+    def get_parameters(self):
+        return self._parameters
+
+    def get_parameter_names(self):
+        return [name for name, unused_value in self._parameters]
+
+    def has_parameter(self, name):
+        for param_name, param_value in self._parameters:
+            if param_name == name:
+                return True
+        return False
+
+    def get_parameter_value(self, name):
+        for param_name, param_value in self._parameters:
+            if param_name == name:
+                return param_value
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/dispatch.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/dispatch.py
new file mode 100644 (file)
index 0000000..ab1eb4f
--- /dev/null
@@ -0,0 +1,381 @@
+# Copyright 2012, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Dispatch WebSocket request.
+"""
+
+
+import logging
+import os
+import re
+
+from mod_pywebsocket import common
+from mod_pywebsocket import handshake
+from mod_pywebsocket import msgutil
+from mod_pywebsocket import stream
+from mod_pywebsocket import util
+
+
+_SOURCE_PATH_PATTERN = re.compile(r'(?i)_wsh\.py$')
+_SOURCE_SUFFIX = '_wsh.py'
+_DO_EXTRA_HANDSHAKE_HANDLER_NAME = 'web_socket_do_extra_handshake'
+_TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data'
+_PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME = (
+    'web_socket_passive_closing_handshake')
+
+
+class DispatchException(Exception):
+    """Exception in dispatching WebSocket request."""
+
+    def __init__(self, name, status=common.HTTP_STATUS_NOT_FOUND):
+        super(DispatchException, self).__init__(name)
+        self.status = status
+
+
+def _default_passive_closing_handshake_handler(request):
+    """Default web_socket_passive_closing_handshake handler."""
+
+    return common.STATUS_NORMAL_CLOSURE, ''
+
+
+def _normalize_path(path):
+    """Normalize path.
+
+    Args:
+        path: the path to normalize.
+
+    Path is converted to the absolute path.
+    The input path can use either '\\' or '/' as the separator.
+    The normalized path always uses '/' regardless of the platform.
+    """
+
+    path = path.replace('\\', os.path.sep)
+    path = os.path.realpath(path)
+    path = path.replace('\\', '/')
+    return path
+
+
+def _create_path_to_resource_converter(base_dir):
+    """Returns a function that converts the path of a WebSocket handler source
+    file to a resource string by removing the path to the base directory from
+    its head, removing _SOURCE_SUFFIX from its tail, and replacing path
+    separators in it with '/'.
+
+    Args:
+        base_dir: the path to the base directory.
+    """
+
+    base_dir = _normalize_path(base_dir)
+
+    base_len = len(base_dir)
+    suffix_len = len(_SOURCE_SUFFIX)
+
+    def converter(path):
+        if not path.endswith(_SOURCE_SUFFIX):
+            return None
+        # _normalize_path must not be used because resolving symlink breaks
+        # following path check.
+        path = path.replace('\\', '/')
+        if not path.startswith(base_dir):
+            return None
+        return path[base_len:-suffix_len]
+
+    return converter
+
+
+def _enumerate_handler_file_paths(directory):
+    """Returns a generator that enumerates WebSocket Handler source file names
+    in the given directory.
+    """
+
+    for root, unused_dirs, files in os.walk(directory):
+        for base in files:
+            path = os.path.join(root, base)
+            if _SOURCE_PATH_PATTERN.search(path):
+                yield path
+
+
+class _HandlerSuite(object):
+    """A handler suite holder class."""
+
+    def __init__(self, do_extra_handshake, transfer_data,
+                 passive_closing_handshake):
+        self.do_extra_handshake = do_extra_handshake
+        self.transfer_data = transfer_data
+        self.passive_closing_handshake = passive_closing_handshake
+
+
+def _source_handler_file(handler_definition):
+    """Source a handler definition string.
+
+    Args:
+        handler_definition: a string containing Python statements that define
+                            handler functions.
+    """
+
+    global_dic = {}
+    try:
+        exec handler_definition in global_dic
+    except Exception:
+        raise DispatchException('Error in sourcing handler:' +
+                                util.get_stack_trace())
+    passive_closing_handshake_handler = None
+    try:
+        passive_closing_handshake_handler = _extract_handler(
+            global_dic, _PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME)
+    except Exception:
+        passive_closing_handshake_handler = (
+            _default_passive_closing_handshake_handler)
+    return _HandlerSuite(
+        _extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME),
+        _extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME),
+        passive_closing_handshake_handler)
+
+
+def _extract_handler(dic, name):
+    """Extracts a callable with the specified name from the given dictionary
+    dic.
+    """
+
+    if name not in dic:
+        raise DispatchException('%s is not defined.' % name)
+    handler = dic[name]
+    if not callable(handler):
+        raise DispatchException('%s is not callable.' % name)
+    return handler
+
+
+class Dispatcher(object):
+    """Dispatches WebSocket requests.
+
+    This class maintains a map from resource name to handlers.
+    """
+
+    def __init__(
+        self, root_dir, scan_dir=None,
+        allow_handlers_outside_root_dir=True):
+        """Construct an instance.
+
+        Args:
+            root_dir: The directory where handler definition files are
+                      placed.
+            scan_dir: The directory where handler definition files are
+                      searched. scan_dir must be a directory under root_dir,
+                      including root_dir itself.  If scan_dir is None,
+                      root_dir is used as scan_dir. scan_dir can be useful
+                      in saving scan time when root_dir contains many
+                      subdirectories.
+            allow_handlers_outside_root_dir: Scans handler files even if their
+                      canonical path is not under root_dir.
+        """
+
+        self._logger = util.get_class_logger(self)
+
+        self._handler_suite_map = {}
+        self._source_warnings = []
+        if scan_dir is None:
+            scan_dir = root_dir
+        if not os.path.realpath(scan_dir).startswith(
+                os.path.realpath(root_dir)):
+            raise DispatchException('scan_dir:%s must be a directory under '
+                                    'root_dir:%s.' % (scan_dir, root_dir))
+        self._source_handler_files_in_dir(
+            root_dir, scan_dir, allow_handlers_outside_root_dir)
+
+    def add_resource_path_alias(self,
+                                alias_resource_path, existing_resource_path):
+        """Add resource path alias.
+
+        Once added, request to alias_resource_path would be handled by
+        handler registered for existing_resource_path.
+
+        Args:
+            alias_resource_path: alias resource path
+            existing_resource_path: existing resource path
+        """
+        try:
+            handler_suite = self._handler_suite_map[existing_resource_path]
+            self._handler_suite_map[alias_resource_path] = handler_suite
+        except KeyError:
+            raise DispatchException('No handler for: %r' %
+                                    existing_resource_path)
+
+    def source_warnings(self):
+        """Return warnings in sourcing handlers."""
+
+        return self._source_warnings
+
+    def do_extra_handshake(self, request):
+        """Do extra checking in WebSocket handshake.
+
+        Select a handler based on request.uri and call its
+        web_socket_do_extra_handshake function.
+
+        Args:
+            request: mod_python request.
+
+        Raises:
+            DispatchException: when handler was not found
+            AbortedByUserException: when user handler abort connection
+            HandshakeException: when opening handshake failed
+        """
+
+        handler_suite = self.get_handler_suite(request.ws_resource)
+        if handler_suite is None:
+            raise DispatchException('No handler for: %r' % request.ws_resource)
+        do_extra_handshake_ = handler_suite.do_extra_handshake
+        try:
+            do_extra_handshake_(request)
+        except handshake.AbortedByUserException, e:
+            raise
+        except Exception, e:
+            util.prepend_message_to_exception(
+                    '%s raised exception for %s: ' % (
+                            _DO_EXTRA_HANDSHAKE_HANDLER_NAME,
+                            request.ws_resource),
+                    e)
+            raise handshake.HandshakeException(e, common.HTTP_STATUS_FORBIDDEN)
+
+    def transfer_data(self, request):
+        """Let a handler transfer_data with a WebSocket client.
+
+        Select a handler based on request.ws_resource and call its
+        web_socket_transfer_data function.
+
+        Args:
+            request: mod_python request.
+
+        Raises:
+            DispatchException: when handler was not found
+            AbortedByUserException: when user handler abort connection
+        """
+
+        handler_suite = self.get_handler_suite(request.ws_resource)
+        if handler_suite is None:
+            raise DispatchException('No handler for: %r' % request.ws_resource)
+        transfer_data_ = handler_suite.transfer_data
+        # TODO(tyoshino): Terminate underlying TCP connection if possible.
+        try:
+            transfer_data_(request)
+            if not request.server_terminated:
+                request.ws_stream.close_connection()
+        # Catch non-critical exceptions the handler didn't handle.
+        except handshake.AbortedByUserException, e:
+            self._logger.debug('%s', e)
+            raise
+        except msgutil.BadOperationException, e:
+            self._logger.debug('%s', e)
+            request.ws_stream.close_connection(common.STATUS_ABNORMAL_CLOSURE)
+        except msgutil.InvalidFrameException, e:
+            # InvalidFrameException must be caught before
+            # ConnectionTerminatedException that catches InvalidFrameException.
+            self._logger.debug('%s', e)
+            request.ws_stream.close_connection(common.STATUS_PROTOCOL_ERROR)
+        except msgutil.UnsupportedFrameException, e:
+            self._logger.debug('%s', e)
+            request.ws_stream.close_connection(common.STATUS_UNSUPPORTED_DATA)
+        except stream.InvalidUTF8Exception, e:
+            self._logger.debug('%s', e)
+            request.ws_stream.close_connection(
+                common.STATUS_INVALID_FRAME_PAYLOAD_DATA)
+        except msgutil.ConnectionTerminatedException, e:
+            self._logger.debug('%s', e)
+        except Exception, e:
+            util.prepend_message_to_exception(
+                '%s raised exception for %s: ' % (
+                    _TRANSFER_DATA_HANDLER_NAME, request.ws_resource),
+                e)
+            raise
+
+    def passive_closing_handshake(self, request):
+        """Prepare code and reason for responding client initiated closing
+        handshake.
+        """
+
+        handler_suite = self.get_handler_suite(request.ws_resource)
+        if handler_suite is None:
+            return _default_passive_closing_handshake_handler(request)
+        return handler_suite.passive_closing_handshake(request)
+
+    def get_handler_suite(self, resource):
+        """Retrieves two handlers (one for extra handshake processing, and one
+        for data transfer) for the given request as a HandlerSuite object.
+        """
+
+        fragment = None
+        if '#' in resource:
+            resource, fragment = resource.split('#', 1)
+        if '?' in resource:
+            resource = resource.split('?', 1)[0]
+        handler_suite = self._handler_suite_map.get(resource)
+        if handler_suite and fragment:
+            raise DispatchException('Fragment identifiers MUST NOT be used on '
+                                    'WebSocket URIs',
+                                    common.HTTP_STATUS_BAD_REQUEST)
+        return handler_suite
+
+    def _source_handler_files_in_dir(
+        self, root_dir, scan_dir, allow_handlers_outside_root_dir):
+        """Source all the handler source files in the scan_dir directory.
+
+        The resource path is determined relative to root_dir.
+        """
+
+        # We build a map from resource to handler code assuming that there's
+        # only one path from root_dir to scan_dir and it can be obtained by
+        # comparing realpath of them.
+
+        # Here we cannot use abspath. See
+        # https://bugs.webkit.org/show_bug.cgi?id=31603
+
+        convert = _create_path_to_resource_converter(root_dir)
+        scan_realpath = os.path.realpath(scan_dir)
+        root_realpath = os.path.realpath(root_dir)
+        for path in _enumerate_handler_file_paths(scan_realpath):
+            if (not allow_handlers_outside_root_dir and
+                (not os.path.realpath(path).startswith(root_realpath))):
+                self._logger.debug(
+                    'Canonical path of %s is not under root directory' %
+                    path)
+                continue
+            try:
+                handler_suite = _source_handler_file(open(path).read())
+            except DispatchException, e:
+                self._source_warnings.append('%s: %s' % (path, e))
+                continue
+            resource = convert(path)
+            if resource is None:
+                self._logger.debug(
+                    'Path to resource conversion on %s failed' % path)
+            else:
+                self._handler_suite_map[convert(path)] = handler_suite
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py
new file mode 100644 (file)
index 0000000..ce37846
--- /dev/null
@@ -0,0 +1,265 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+from mod_pywebsocket import 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):
+    """WebSocket DEFLATE stream extension processor."""
+
+    def __init__(self, request):
+        self._logger = util.get_class_logger(self)
+
+        self._request = request
+
+    def get_extension_response(self):
+        if len(self._request.get_parameter_names()) != 0:
+            return None
+
+        self._logger.debug(
+            'Enable %s extension', common.DEFLATE_STREAM_EXTENSION)
+
+        return common.ExtensionParameter(common.DEFLATE_STREAM_EXTENSION)
+
+    def setup_stream_options(self, stream_options):
+        stream_options.deflate_stream = True
+
+
+_available_processors[common.DEFLATE_STREAM_EXTENSION] = (
+    DeflateStreamExtensionProcessor)
+
+
+class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
+    """WebSocket Per-frame DEFLATE extension processor."""
+
+    _WINDOW_BITS_PARAM = 'max_window_bits'
+    _NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover'
+
+    def __init__(self, request):
+        self._logger = util.get_class_logger(self)
+
+        self._request = request
+
+        self._response_window_bits = None
+        self._response_no_context_takeover = False
+
+        # Counters for statistics.
+
+        # Total number of outgoing bytes supplied to this filter.
+        self._total_outgoing_payload_bytes = 0
+        # Total number of bytes sent to the network after applying this filter.
+        self._total_filtered_outgoing_payload_bytes = 0
+
+        # Total number of bytes received from the network.
+        self._total_incoming_payload_bytes = 0
+        # Total number of incoming bytes obtained after applying this filter.
+        self._total_filtered_incoming_payload_bytes = 0
+
+    def get_extension_response(self):
+        # Any unknown parameter will be just ignored.
+
+        window_bits = self._request.get_parameter_value(
+            self._WINDOW_BITS_PARAM)
+        no_context_takeover = self._request.has_parameter(
+            self._NO_CONTEXT_TAKEOVER_PARAM)
+        if (no_context_takeover and
+            self._request.get_parameter_value(
+                self._NO_CONTEXT_TAKEOVER_PARAM) is not None):
+            return None
+
+        if window_bits is not None:
+            try:
+                window_bits = int(window_bits)
+            except ValueError, e:
+                return None
+            if window_bits < 8 or window_bits > 15:
+                return None
+
+        self._deflater = util._RFC1979Deflater(
+            window_bits, no_context_takeover)
+
+        self._inflater = util._RFC1979Inflater()
+
+        self._compress_outgoing = True
+
+        response = common.ExtensionParameter(self._request.name())
+
+        if self._response_window_bits is not None:
+            response.add_parameter(
+                self._WINDOW_BITS_PARAM, str(self._response_window_bits))
+        if self._response_no_context_takeover:
+            response.add_parameter(
+                self._NO_CONTEXT_TAKEOVER_PARAM, None)
+
+        self._logger.debug(
+            'Enable %s extension ('
+            'request: window_bits=%s; no_context_takeover=%r, '
+            'response: window_wbits=%s; no_context_takeover=%r)' %
+            (self._request.name(),
+             window_bits,
+             no_context_takeover,
+             self._response_window_bits,
+             self._response_no_context_takeover))
+
+        return response
+
+    def setup_stream_options(self, stream_options):
+
+        class _OutgoingFilter(object):
+
+            def __init__(self, parent):
+                self._parent = parent
+
+            def filter(self, frame):
+                self._parent._outgoing_filter(frame)
+
+        class _IncomingFilter(object):
+
+            def __init__(self, parent):
+                self._parent = parent
+
+            def filter(self, frame):
+                self._parent._incoming_filter(frame)
+
+        stream_options.outgoing_frame_filters.append(
+            _OutgoingFilter(self))
+        stream_options.incoming_frame_filters.insert(
+            0, _IncomingFilter(self))
+
+    def set_response_window_bits(self, value):
+        self._response_window_bits = value
+
+    def set_response_no_context_takeover(self, value):
+        self._response_no_context_takeover = value
+
+    def enable_outgoing_compression(self):
+        self._compress_outgoing = True
+
+    def disable_outgoing_compression(self):
+        self._compress_outgoing = False
+
+    def _outgoing_filter(self, frame):
+        """Transform outgoing frames. This method is called only by
+        an _OutgoingFilter instance.
+        """
+
+        original_payload_size = len(frame.payload)
+        self._total_outgoing_payload_bytes += original_payload_size
+
+        if (not self._compress_outgoing or
+            common.is_control_opcode(frame.opcode)):
+            self._total_filtered_outgoing_payload_bytes += (
+                original_payload_size)
+            return
+
+        frame.payload = self._deflater.filter(frame.payload)
+        frame.rsv1 = 1
+
+        filtered_payload_size = len(frame.payload)
+        self._total_filtered_outgoing_payload_bytes += filtered_payload_size
+
+        # Print inf when ratio is not available.
+        ratio = float('inf')
+        average_ratio = float('inf')
+        if original_payload_size != 0:
+            ratio = float(filtered_payload_size) / original_payload_size
+        if self._total_outgoing_payload_bytes != 0:
+            average_ratio = (
+                float(self._total_filtered_outgoing_payload_bytes) /
+                self._total_outgoing_payload_bytes)
+        self._logger.debug(
+            'Outgoing compress ratio: %f (average: %f)' %
+            (ratio, average_ratio))
+
+    def _incoming_filter(self, frame):
+        """Transform incoming frames. This method is called only by
+        an _IncomingFilter instance.
+        """
+
+        received_payload_size = len(frame.payload)
+        self._total_incoming_payload_bytes += received_payload_size
+
+        if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode):
+            self._total_filtered_incoming_payload_bytes += (
+                received_payload_size)
+            return
+
+        frame.payload = self._inflater.filter(frame.payload)
+        frame.rsv1 = 0
+
+        filtered_payload_size = len(frame.payload)
+        self._total_filtered_incoming_payload_bytes += filtered_payload_size
+
+        # Print inf when ratio is not available.
+        ratio = float('inf')
+        average_ratio = float('inf')
+        if received_payload_size != 0:
+            ratio = float(received_payload_size) / filtered_payload_size
+        if self._total_filtered_incoming_payload_bytes != 0:
+            average_ratio = (
+                float(self._total_incoming_payload_bytes) /
+                self._total_filtered_incoming_payload_bytes)
+        self._logger.debug(
+            'Incoming compress ratio: %f (average: %f)' %
+            (ratio, average_ratio))
+
+
+_available_processors[common.DEFLATE_FRAME_EXTENSION] = (
+    DeflateFrameExtensionProcessor)
+
+
+# Adding vendor-prefixed deflate-frame extension.
+# TODO(bashi): Remove this after WebKit stops using vender prefix.
+_available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = (
+    DeflateFrameExtensionProcessor)
+
+
+def get_extension_processor(extension_request):
+    global _available_processors
+    processor_class = _available_processors.get(extension_request.name())
+    if processor_class is None:
+        return None
+    return processor_class(extension_request)
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/__init__.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/__init__.py
new file mode 100644 (file)
index 0000000..10a1783
--- /dev/null
@@ -0,0 +1,116 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""WebSocket opening handshake processor. This class try to apply available
+opening handshake processors for each protocol version until a connection is
+successfully established.
+"""
+
+
+import logging
+
+from mod_pywebsocket import common
+from mod_pywebsocket.handshake import draft75
+from mod_pywebsocket.handshake import hybi00
+from mod_pywebsocket.handshake import hybi
+# Export AbortedByUserException, HandshakeException, and VersionException
+# symbol from this module.
+from mod_pywebsocket.handshake._base import AbortedByUserException
+from mod_pywebsocket.handshake._base import HandshakeException
+from mod_pywebsocket.handshake._base import VersionException
+
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def do_handshake(request, dispatcher, allowDraft75=False, strict=False):
+    """Performs WebSocket handshake.
+
+    Args:
+        request: mod_python request.
+        dispatcher: Dispatcher (dispatch.Dispatcher).
+        allowDraft75: 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.
+    """
+
+    _LOGGER.debug('Client\'s opening handshake resource: %r', request.uri)
+    # To print mimetools.Message as escaped one-line string, we converts
+    # headers_in to dict object. Without conversion, if we use %r, it just
+    # prints the type and address, and if we use %s, it prints the original
+    # header string as multiple lines.
+    #
+    # Both mimetools.Message and MpTable_Type of mod_python can be
+    # converted to dict.
+    #
+    # mimetools.Message.__str__ returns the original header string.
+    # dict(mimetools.Message object) returns the map from header names to
+    # header values. While MpTable_Type doesn't have such __str__ but just
+    # __repr__ which formats itself as well as dictionary object.
+    _LOGGER.debug(
+        'Client\'s opening handshake headers: %r', dict(request.headers_in))
+
+    handshakers = []
+    handshakers.append(
+        ('RFC 6455', hybi.Handshaker(request, dispatcher)))
+    handshakers.append(
+        ('HyBi 00', hybi00.Handshaker(request, dispatcher)))
+    if allowDraft75:
+        handshakers.append(
+            ('Hixie 75', draft75.Handshaker(request, dispatcher, strict)))
+
+    for name, handshaker in handshakers:
+        _LOGGER.debug('Trying protocol version %s', name)
+        try:
+            handshaker.do_handshake()
+            _LOGGER.info('Established (%s protocol)', name)
+            return
+        except HandshakeException, e:
+            _LOGGER.debug(
+                'Failed to complete opening handshake as %s protocol: %r',
+                name, e)
+            if e.status:
+                raise e
+        except AbortedByUserException, e:
+            raise
+        except VersionException, e:
+            raise
+
+    # TODO(toyoshim): Add a test to cover the case all handshakers fail.
+    raise HandshakeException(
+        'Failed to complete opening handshake for all available protocols',
+        status=common.HTTP_STATUS_BAD_REQUEST)
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py
new file mode 100644 (file)
index 0000000..4d7c32e
--- /dev/null
@@ -0,0 +1,323 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Common functions and exceptions used by WebSocket opening handshake
+processors.
+"""
+
+
+from mod_pywebsocket import common
+from mod_pywebsocket import http_header_util
+
+
+class AbortedByUserException(Exception):
+    """Exception for aborting a connection intentionally.
+
+    If this exception is raised in do_extra_handshake handler, the connection
+    will be abandoned. No other WebSocket or HTTP(S) handler will be invoked.
+
+    If this exception is raised in transfer_data_handler, the connection will
+    be closed without closing handshake. No other WebSocket or HTTP(S) handler
+    will be invoked.
+    """
+
+    pass
+
+
+class HandshakeException(Exception):
+    """This exception will be raised when an error occurred while processing
+    WebSocket initial handshake.
+    """
+
+    def __init__(self, name, status=None):
+        super(HandshakeException, self).__init__(name)
+        self.status = status
+
+
+class VersionException(Exception):
+    """This exception will be raised when a version of client request does not
+    match with version the server supports.
+    """
+
+    def __init__(self, name, supported_versions=''):
+        """Construct an instance.
+
+        Args:
+            supported_version: a str object to show supported hybi versions.
+                               (e.g. '8, 13')
+        """
+        super(VersionException, self).__init__(name)
+        self.supported_versions = supported_versions
+
+
+def get_default_port(is_secure):
+    if is_secure:
+        return common.DEFAULT_WEB_SOCKET_SECURE_PORT
+    else:
+        return common.DEFAULT_WEB_SOCKET_PORT
+
+
+def validate_subprotocol(subprotocol, hixie):
+    """Validate a value in subprotocol fields such as WebSocket-Protocol,
+    Sec-WebSocket-Protocol.
+
+    See
+    - RFC 6455: Section 4.1., 4.2.2., and 4.3.
+    - HyBi 00: Section 4.1. Opening handshake
+    - Hixie 75: Section 4.1. Handshake
+    """
+
+    if not subprotocol:
+        raise HandshakeException('Invalid subprotocol name: empty')
+    if hixie:
+        # Parameter should be in the range U+0020 to U+007E.
+        for c in subprotocol:
+            if not 0x20 <= ord(c) <= 0x7e:
+                raise HandshakeException(
+                    'Illegal character in subprotocol name: %r' % c)
+    else:
+        # Parameter should be encoded HTTP token.
+        state = http_header_util.ParsingState(subprotocol)
+        token = http_header_util.consume_token(state)
+        rest = http_header_util.peek(state)
+        # If |rest| is not None, |subprotocol| is not one token or invalid. If
+        # |rest| is None, |token| must not be None because |subprotocol| is
+        # concatenation of |token| and |rest| and is not None.
+        if rest is not None:
+            raise HandshakeException('Invalid non-token string in subprotocol '
+                                     'name: %r' % rest)
+
+
+def parse_host_header(request):
+    fields = request.headers_in['Host'].split(':', 1)
+    if len(fields) == 1:
+        return fields[0], get_default_port(request.is_https())
+    try:
+        return fields[0], int(fields[1])
+    except ValueError, e:
+        raise HandshakeException('Invalid port number format: %r' % e)
+
+
+def format_header(name, value):
+    return '%s: %s\r\n' % (name, value)
+
+
+def build_location(request):
+    """Build WebSocket location for request."""
+    location_parts = []
+    if request.is_https():
+        location_parts.append(common.WEB_SOCKET_SECURE_SCHEME)
+    else:
+        location_parts.append(common.WEB_SOCKET_SCHEME)
+    location_parts.append('://')
+    host, port = parse_host_header(request)
+    connection_port = request.connection.local_addr[1]
+    if port != connection_port:
+        raise HandshakeException('Header/connection port mismatch: %d/%d' %
+                                 (port, connection_port))
+    location_parts.append(host)
+    if (port != get_default_port(request.is_https())):
+        location_parts.append(':')
+        location_parts.append(str(port))
+    location_parts.append(request.uri)
+    return ''.join(location_parts)
+
+
+def get_mandatory_header(request, key):
+    value = request.headers_in.get(key)
+    if value is None:
+        raise HandshakeException('Header %s is not defined' % key)
+    return value
+
+
+def validate_mandatory_header(request, key, expected_value, fail_status=None):
+    value = get_mandatory_header(request, key)
+
+    if value.lower() != expected_value.lower():
+        raise HandshakeException(
+            'Expected %r for header %s but found %r (case-insensitive)' %
+            (expected_value, key, value), status=fail_status)
+
+
+def check_request_line(request):
+    # 5.1 1. The three character UTF-8 string "GET".
+    # 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte).
+    if request.method != 'GET':
+        raise HandshakeException('Method is not GET')
+
+
+def check_header_lines(request, mandatory_headers):
+    check_request_line(request)
+
+    # The expected field names, and the meaning of their corresponding
+    # values, are as follows.
+    #  |Upgrade| and |Connection|
+    for key, expected_value in mandatory_headers:
+        validate_mandatory_header(request, key, expected_value)
+
+
+def parse_token_list(data):
+    """Parses a header value which follows 1#token and returns parsed elements
+    as a list of strings.
+
+    Leading LWSes must be trimmed.
+    """
+
+    state = http_header_util.ParsingState(data)
+
+    token_list = []
+
+    while True:
+        token = http_header_util.consume_token(state)
+        if token is not None:
+            token_list.append(token)
+
+        http_header_util.consume_lwses(state)
+
+        if http_header_util.peek(state) is None:
+            break
+
+        if not http_header_util.consume_string(state, ','):
+            raise HandshakeException(
+                'Expected a comma but found %r' % http_header_util.peek(state))
+
+        http_header_util.consume_lwses(state)
+
+    if len(token_list) == 0:
+        raise HandshakeException('No valid token found')
+
+    return token_list
+
+
+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)
+
+    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, 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, 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, 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, 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
+
+        if not http_header_util.consume_string(state, ','):
+            raise HandshakeException(
+                'Failed to parse Sec-WebSocket-Extensions header: '
+                'Expected a comma but found %r' %
+                http_header_util.peek(state))
+
+        http_header_util.consume_lwses(state)
+
+    if len(extension_list) == 0:
+        raise HandshakeException(
+            'Sec-WebSocket-Extensions header contains no valid extension')
+
+    return extension_list
+
+
+def format_extensions(extension_list):
+    formatted_extension_list = []
+    for extension in extension_list:
+        formatted_params = [extension.name()]
+        for param_name, param_value in extension.get_parameters():
+            if param_value is None:
+                formatted_params.append(param_name)
+            else:
+                quoted_value = http_header_util.quote_if_necessary(param_value)
+                formatted_params.append('%s=%s' % (param_name, quoted_value))
+
+        formatted_extension_list.append('; '.join(formatted_params))
+
+    return ', '.join(formatted_extension_list)
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/draft75.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/draft75.py
new file mode 100644 (file)
index 0000000..802a31c
--- /dev/null
@@ -0,0 +1,190 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""WebSocket handshaking defined in draft-hixie-thewebsocketprotocol-75."""
+
+
+# Note: request.connection.write is used in this module, even though mod_python
+# document says that it should be used only in connection handlers.
+# Unfortunately, we have no other options. For example, request.write is not
+# suitable because it doesn't allow direct raw bytes writing.
+
+
+import logging
+import re
+
+from mod_pywebsocket import common
+from mod_pywebsocket.stream import StreamHixie75
+from mod_pywebsocket import util
+from mod_pywebsocket.handshake._base import HandshakeException
+from mod_pywebsocket.handshake._base import build_location
+from mod_pywebsocket.handshake._base import validate_subprotocol
+
+
+_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 WebSocket 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._logger = util.get_class_logger(self)
+
+        self._request = request
+        self._dispatcher = dispatcher
+        self._strict = strict
+
+    def do_handshake(self):
+        """Perform WebSocket Handshake.
+
+        On _request, we set
+            ws_resource, ws_origin, ws_location, ws_protocol
+            ws_challenge_md5: WebSocket handshake information.
+            ws_stream: Frame generation/parsing class.
+            ws_version: Protocol version.
+        """
+
+        self._check_header_lines()
+        self._set_resource()
+        self._set_origin()
+        self._set_location()
+        self._set_subprotocol()
+        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_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_subprotocol(self):
+        subprotocol = self._request.headers_in.get('WebSocket-Protocol')
+        if subprotocol is not None:
+            validate_subprotocol(subprotocol, hixie=True)
+        self._request.ws_protocol = subprotocol
+
+    def _set_protocol_version(self):
+        self._logger.debug('IETF Hixie 75 protocol')
+        self._request.ws_version = common.VERSION_HIXIE75
+        self._request.ws_stream = StreamHixie75(self._request)
+
+    def _sendall(self, data):
+        self._request.connection.write(data)
+
+    def _send_handshake(self):
+        self._sendall('HTTP/1.1 101 Web Socket Protocol Handshake\r\n')
+        self._sendall('Upgrade: WebSocket\r\n')
+        self._sendall('Connection: Upgrade\r\n')
+        self._sendall('WebSocket-Origin: %s\r\n' % self._request.ws_origin)
+        self._sendall('WebSocket-Location: %s\r\n' % self._request.ws_location)
+        if self._request.ws_protocol:
+            self._sendall(
+                'WebSocket-Protocol: %s\r\n' % self._request.ws_protocol)
+        self._sendall('\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 HandshakeException('Header %s is not defined' % key)
+            if expected_value:
+                if actual_value != expected_value:
+                    raise HandshakeException(
+                        'Expected %r for header %s but found %r' %
+                        (expected_value, 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 HandshakeException('Too few header lines: %d' % len(lines))
+        for line, regexp in zip(lines, _FIRST_FIVE_LINES):
+            if not regexp.search(line):
+                raise HandshakeException(
+                    '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 HandshakeException(
+                'Unexpected header: %r doesn\'t match %r'
+                % (sixth_and_later, _SIXTH_AND_LATER.pattern))
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi.py
new file mode 100644 (file)
index 0000000..3bc84bd
--- /dev/null
@@ -0,0 +1,370 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""This file provides the opening handshake processor for the WebSocket
+protocol (RFC 6455).
+
+Specification:
+http://tools.ietf.org/html/rfc6455
+"""
+
+
+# Note: request.connection.write is used in this module, even though mod_python
+# document says that it should be used only in connection handlers.
+# Unfortunately, we have no other options. For example, request.write is not
+# suitable because it doesn't allow direct raw bytes writing.
+
+
+import base64
+import logging
+import os
+import re
+
+from mod_pywebsocket import common
+from mod_pywebsocket.extensions import get_extension_processor
+from mod_pywebsocket.handshake._base import check_request_line
+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
+
+
+# Used to validate the value in the Sec-WebSocket-Key header strictly. RFC 4648
+# disallows non-zero padding, so the character right before == must be any of
+# A, Q, g and w.
+_SEC_WEBSOCKET_KEY_REGEX = re.compile('^[+/0-9A-Za-z]{21}[AQgw]==$')
+
+# Defining aliases for values used frequently.
+_VERSION_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()
+    accept = base64.b64encode(accept_binary)
+
+    return (accept, accept_binary)
+
+
+class Handshaker(object):
+    """Opening handshake processor for the WebSocket protocol (RFC 6455)."""
+
+    def __init__(self, request, dispatcher):
+        """Construct an instance.
+
+        Args:
+            request: mod_python request.
+            dispatcher: Dispatcher (dispatch.Dispatcher).
+
+        Handshaker will add attributes such as ws_resource during handshake.
+        """
+
+        self._logger = util.get_class_logger(self)
+
+        self._request = request
+        self._dispatcher = dispatcher
+
+    def _validate_connection_header(self):
+        connection = get_mandatory_header(
+            self._request, common.CONNECTION_HEADER)
+
+        try:
+            connection_tokens = parse_token_list(connection)
+        except HandshakeException, e:
+            raise HandshakeException(
+                'Failed to parse %s: %s' % (common.CONNECTION_HEADER, e))
+
+        connection_is_valid = False
+        for token in connection_tokens:
+            if token.lower() == common.UPGRADE_CONNECTION_TYPE.lower():
+                connection_is_valid = True
+                break
+        if not connection_is_valid:
+            raise HandshakeException(
+                '%s header doesn\'t contain "%s"' %
+                (common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE))
+
+    def do_handshake(self):
+        self._request.ws_close_code = None
+        self._request.ws_close_reason = None
+
+        # Parsing.
+
+        check_request_line(self._request)
+
+        validate_mandatory_header(
+            self._request,
+            common.UPGRADE_HEADER,
+            common.WEBSOCKET_UPGRADE_TYPE)
+
+        self._validate_connection_header()
+
+        self._request.ws_resource = self._request.uri
+
+        unused_host = get_mandatory_header(self._request, common.HOST_HEADER)
+
+        self._request.ws_version = self._check_version()
+
+        # 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()
+
+            # Key validation, response generation.
+
+            key = self._get_key()
+            (accept, accept_binary) = compute_accept(key)
+            self._logger.debug(
+                '%s: %r (%s)',
+                common.SEC_WEBSOCKET_ACCEPT_HEADER,
+                accept,
+                util.hexify(accept_binary))
+
+            self._logger.debug('Protocol version is RFC 6455')
+
+            # Setup extension processors.
+
+            processors = []
+            if self._request.ws_requested_extensions is not None:
+                for extension_request in self._request.ws_requested_extensions:
+                    processor = get_extension_processor(extension_request)
+                    # Unknown extension requests are just ignored.
+                    if processor is not None:
+                        processors.append(processor)
+            self._request.ws_extension_processors = processors
+
+            # Extra handshake handler may modify/remove processors.
+            self._dispatcher.do_extra_handshake(self._request)
+
+            stream_options = StreamOptions()
+
+            self._request.ws_extensions = None
+            for processor in self._request.ws_extension_processors:
+                if processor is None:
+                    # Some processors may be removed by extra handshake
+                    # handler.
+                    continue
+
+                extension_response = processor.get_extension_response()
+                if extension_response is None:
+                    # Rejected.
+                    continue
+
+                if self._request.ws_extensions is None:
+                    self._request.ws_extensions = []
+                self._request.ws_extensions.append(extension_response)
+
+                processor.setup_stream_options(stream_options)
+
+            if self._request.ws_extensions is not None:
+                self._logger.debug(
+                    'Extensions accepted: %r',
+                    map(common.ExtensionParameter.name,
+                        self._request.ws_extensions))
+
+            self._request.ws_stream = Stream(self._request, stream_options)
+
+            if self._request.ws_requested_protocols is not None:
+                if self._request.ws_protocol is None:
+                    raise HandshakeException(
+                        'do_extra_handshake must choose one subprotocol from '
+                        'ws_requested_protocols and set it to ws_protocol')
+                validate_subprotocol(self._request.ws_protocol, hixie=False)
+
+                self._logger.debug(
+                    'Subprotocol accepted: %r',
+                    self._request.ws_protocol)
+            else:
+                if self._request.ws_protocol is not None:
+                    raise HandshakeException(
+                        'ws_protocol must be None when the client didn\'t '
+                        'request any subprotocol')
+
+            self._send_handshake(accept)
+        except HandshakeException, e:
+            if not e.status:
+                # Fallback to 400 bad request by default.
+                e.status = common.HTTP_STATUS_BAD_REQUEST
+            raise e
+
+    def _get_origin(self):
+        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):
+        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
+
+        protocol_header = self._request.headers_in.get(
+            common.SEC_WEBSOCKET_PROTOCOL_HEADER)
+
+        if not protocol_header:
+            self._request.ws_requested_protocols = None
+            return
+
+        self._request.ws_requested_protocols = parse_token_list(
+            protocol_header)
+        self._logger.debug('Subprotocols requested: %r',
+                           self._request.ws_requested_protocols)
+
+    def _parse_extensions(self):
+        extensions_header = self._request.headers_in.get(
+            common.SEC_WEBSOCKET_EXTENSIONS_HEADER)
+        if not extensions_header:
+            self._request.ws_requested_extensions = None
+            return
+
+        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, 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 _SEC_WEBSOCKET_KEY_REGEX.match(key):
+                decoded_key = base64.b64decode(key)
+                if len(decoded_key) == 16:
+                    key_is_valid = True
+        except TypeError, e:
+            pass
+
+        if not key_is_valid:
+            raise HandshakeException(
+                'Illegal value for header %s: %r' %
+                (common.SEC_WEBSOCKET_KEY_HEADER, key))
+
+        return decoded_key
+
+    def _get_key(self):
+        key = get_mandatory_header(
+            self._request, common.SEC_WEBSOCKET_KEY_HEADER)
+
+        decoded_key = self._validate_key(key)
+
+        self._logger.debug(
+            '%s: %r (%s)',
+            common.SEC_WEBSOCKET_KEY_HEADER,
+            key,
+            util.hexify(decoded_key))
+
+        return key
+
+    def _send_handshake(self, accept):
+        response = []
+
+        response.append('HTTP/1.1 101 Switching Protocols\r\n')
+
+        response.append(format_header(
+            common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE))
+        response.append(format_header(
+            common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE))
+        response.append(format_header(
+            common.SEC_WEBSOCKET_ACCEPT_HEADER, accept))
+        if self._request.ws_protocol is not None:
+            response.append(format_header(
+                common.SEC_WEBSOCKET_PROTOCOL_HEADER,
+                self._request.ws_protocol))
+        if (self._request.ws_extensions is not None and
+            len(self._request.ws_extensions) != 0):
+            response.append(format_header(
+                common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
+                format_extensions(self._request.ws_extensions)))
+        response.append('\r\n')
+
+        raw_response = ''.join(response)
+        self._request.connection.write(raw_response)
+        self._logger.debug('Sent server\'s opening handshake: %r',
+                           raw_response)
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi00.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi00.py
new file mode 100644 (file)
index 0000000..cc6f8dc
--- /dev/null
@@ -0,0 +1,242 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""This file provides the opening handshake processor for the WebSocket
+protocol version HyBi 00.
+
+Specification:
+http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
+"""
+
+
+# Note: request.connection.write/read are used in this module, even though
+# mod_python document says that they should be used only in connection
+# handlers. Unfortunately, we have no other options. For example,
+# request.write/read are not suitable because they don't allow direct raw bytes
+# writing/reading.
+
+
+import logging
+import re
+import struct
+
+from mod_pywebsocket import common
+from mod_pywebsocket.stream import StreamHixie75
+from mod_pywebsocket import util
+from mod_pywebsocket.handshake._base import HandshakeException
+from mod_pywebsocket.handshake._base import build_location
+from mod_pywebsocket.handshake._base import check_header_lines
+from mod_pywebsocket.handshake._base import format_header
+from mod_pywebsocket.handshake._base import get_mandatory_header
+from mod_pywebsocket.handshake._base import validate_subprotocol
+
+
+_MANDATORY_HEADERS = [
+    # key, expected value or None
+    [common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75],
+    [common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE],
+]
+
+
+class Handshaker(object):
+    """Opening handshake processor for the WebSocket protocol version HyBi 00.
+    """
+
+    def __init__(self, request, dispatcher):
+        """Construct an instance.
+
+        Args:
+            request: mod_python request.
+            dispatcher: Dispatcher (dispatch.Dispatcher).
+
+        Handshaker will add attributes such as ws_resource in performing
+        handshake.
+        """
+
+        self._logger = util.get_class_logger(self)
+
+        self._request = request
+        self._dispatcher = dispatcher
+
+    def do_handshake(self):
+        """Perform WebSocket Handshake.
+
+        On _request, we set
+            ws_resource, ws_protocol, ws_location, ws_origin, ws_challenge,
+            ws_challenge_md5: WebSocket handshake information.
+            ws_stream: Frame generation/parsing class.
+            ws_version: Protocol version.
+
+        Raises:
+            HandshakeException: when any error happened in parsing the opening
+                                handshake request.
+        """
+
+        # 5.1 Reading the client's opening handshake.
+        # dispatcher sets it in self._request.
+        check_header_lines(self._request, _MANDATORY_HEADERS)
+        self._set_resource()
+        self._set_subprotocol()
+        self._set_location()
+        self._set_origin()
+        self._set_challenge_response()
+        self._set_protocol_version()
+
+        self._dispatcher.do_extra_handshake(self._request)
+
+        self._send_handshake()
+
+    def _set_resource(self):
+        self._request.ws_resource = self._request.uri
+
+    def _set_subprotocol(self):
+        # |Sec-WebSocket-Protocol|
+        subprotocol = self._request.headers_in.get(
+            common.SEC_WEBSOCKET_PROTOCOL_HEADER)
+        if subprotocol is not None:
+            validate_subprotocol(subprotocol, hixie=True)
+        self._request.ws_protocol = subprotocol
+
+    def _set_location(self):
+        # |Host|
+        host = self._request.headers_in.get(common.HOST_HEADER)
+        if host is not None:
+            self._request.ws_location = build_location(self._request)
+        # TODO(ukai): check host is this host.
+
+    def _set_origin(self):
+        # |Origin|
+        origin = self._request.headers_in.get(common.ORIGIN_HEADER)
+        if origin is not None:
+            self._request.ws_origin = origin
+
+    def _set_protocol_version(self):
+        # |Sec-WebSocket-Draft|
+        draft = self._request.headers_in.get(common.SEC_WEBSOCKET_DRAFT_HEADER)
+        if draft is not None and draft != '0':
+            raise HandshakeException('Illegal value for %s: %s' %
+                                     (common.SEC_WEBSOCKET_DRAFT_HEADER,
+                                      draft))
+
+        self._logger.debug('Protocol version is HyBi 00')
+        self._request.ws_version = common.VERSION_HYBI00
+        self._request.ws_stream = StreamHixie75(self._request, True)
+
+    def _set_challenge_response(self):
+        # 5.2 4-8.
+        self._request.ws_challenge = self._get_challenge()
+        # 5.2 9. let /response/ be the MD5 finterprint of /challenge/
+        self._request.ws_challenge_md5 = util.md5_hash(
+            self._request.ws_challenge).digest()
+        self._logger.debug(
+            'Challenge: %r (%s)',
+            self._request.ws_challenge,
+            util.hexify(self._request.ws_challenge))
+        self._logger.debug(
+            'Challenge response: %r (%s)',
+            self._request.ws_challenge_md5,
+            util.hexify(self._request.ws_challenge_md5))
+
+    def _get_key_value(self, key_field):
+        key_value = get_mandatory_header(self._request, key_field)
+
+        self._logger.debug('%s: %r', key_field, key_value)
+
+        # 5.2 4. let /key-number_n/ be the digits (characters in the range
+        # U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_n/,
+        # interpreted as a base ten integer, ignoring all other characters
+        # in /key_n/.
+        try:
+            key_number = int(re.sub("\\D", "", key_value))
+        except:
+            raise HandshakeException('%s field contains no digit' % key_field)
+        # 5.2 5. let /spaces_n/ be the number of U+0020 SPACE characters
+        # in /key_n/.
+        spaces = re.subn(" ", "", key_value)[1]
+        if spaces == 0:
+            raise HandshakeException('%s field contains no space' % key_field)
+
+        self._logger.debug(
+            '%s: Key-number is %d and number of spaces is %d',
+            key_field, key_number, spaces)
+
+        # 5.2 6. if /key-number_n/ is not an integral multiple of /spaces_n/
+        # then abort the WebSocket connection.
+        if key_number % spaces != 0:
+            raise HandshakeException(
+                '%s: Key-number (%d) is not an integral multiple of spaces '
+                '(%d)' % (key_field, key_number, spaces))
+        # 5.2 7. let /part_n/ be /key-number_n/ divided by /spaces_n/.
+        part = key_number / spaces
+        self._logger.debug('%s: Part is %d', key_field, part)
+        return part
+
+    def _get_challenge(self):
+        # 5.2 4-7.
+        key1 = self._get_key_value(common.SEC_WEBSOCKET_KEY1_HEADER)
+        key2 = self._get_key_value(common.SEC_WEBSOCKET_KEY2_HEADER)
+        # 5.2 8. let /challenge/ be the concatenation of /part_1/,
+        challenge = ''
+        challenge += struct.pack('!I', key1)  # network byteorder int
+        challenge += struct.pack('!I', key2)  # network byteorder int
+        challenge += self._request.connection.read(8)
+        return challenge
+
+    def _send_handshake(self):
+        response = []
+
+        # 5.2 10. send the following line.
+        response.append('HTTP/1.1 101 WebSocket Protocol Handshake\r\n')
+
+        # 5.2 11. send the following fields to the client.
+        response.append(format_header(
+            common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75))
+        response.append(format_header(
+            common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE))
+        response.append(format_header(
+            common.SEC_WEBSOCKET_LOCATION_HEADER, self._request.ws_location))
+        response.append(format_header(
+            common.SEC_WEBSOCKET_ORIGIN_HEADER, self._request.ws_origin))
+        if self._request.ws_protocol:
+            response.append(format_header(
+                common.SEC_WEBSOCKET_PROTOCOL_HEADER,
+                self._request.ws_protocol))
+        # 5.2 12. send two bytes 0x0D 0x0A.
+        response.append('\r\n')
+        # 5.2 13. send /response/
+        response.append(self._request.ws_challenge_md5)
+
+        raw_response = ''.join(response)
+        self._request.connection.write(raw_response)
+        self._logger.debug('Sent server\'s opening handshake: %r',
+                           raw_response)
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/headerparserhandler.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/headerparserhandler.py
new file mode 100644 (file)
index 0000000..b68c240
--- /dev/null
@@ -0,0 +1,243 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""PythonHeaderParserHandler for mod_pywebsocket.
+
+Apache HTTP Server and mod_python must be configured such that this
+function is called to handle WebSocket request.
+"""
+
+
+import logging
+
+from mod_python import apache
+
+from mod_pywebsocket import common
+from mod_pywebsocket import dispatch
+from mod_pywebsocket import handshake
+from mod_pywebsocket import util
+
+
+# PythonOption to specify the handler root directory.
+_PYOPT_HANDLER_ROOT = 'mod_pywebsocket.handler_root'
+
+# PythonOption to specify the handler scan directory.
+# This must be a directory under the root directory.
+# The default is the root directory.
+_PYOPT_HANDLER_SCAN = 'mod_pywebsocket.handler_scan'
+
+# PythonOption to allow handlers whose canonical path is
+# not under the root directory. It's disallowed by default.
+# Set this option with value of 'yes' to allow.
+_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT = (
+    'mod_pywebsocket.allow_handlers_outside_root_dir')
+# Map from values to their meanings. 'Yes' and 'No' are allowed just for
+# compatibility.
+_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION = {
+    'off': False, 'no': False, 'on': True, 'yes': True}
+
+# 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
+        if request is not None:
+            self._log_error = request.log_error
+
+        # Time and level will be printed by Apache.
+        self._formatter = logging.Formatter('%(name)s: %(message)s')
+
+    def emit(self, record):
+        apache_level = apache.APLOG_DEBUG
+        if record.levelno in ApacheLogHandler._LEVELS:
+            apache_level = ApacheLogHandler._LEVELS[record.levelno]
+
+        msg = self._formatter.format(record)
+
+        # "server" parameter must be passed to have "level" parameter work.
+        # If only "level" parameter is passed, nothing shows up on Apache's
+        # log. However, at this point, we cannot get the server object of the
+        # virtual host which will process WebSocket requests. The only server
+        # object we can get here is apache.main_server. But Wherever (server
+        # configuration context or virtual host context) we put
+        # PythonHeaderParserHandler directive, apache.main_server just points
+        # the main server instance (not any of virtual server instance). Then,
+        # Apache follows LogLevel directive in the server configuration context
+        # to filter logs. So, we need to specify LogLevel in the server
+        # configuration context. Even if we specify "LogLevel debug" in the
+        # virtual host context which actually handles WebSocket connections,
+        # DEBUG level logs never show up unless "LogLevel debug" is specified
+        # in the server configuration context.
+        #
+        # TODO(tyoshino): Provide logging methods on request object. When
+        # request is mp_request object (when used together with Apache), the
+        # methods call request.log_error indirectly. When request is
+        # _StandaloneRequest, the methods call Python's logging facility which
+        # we create in standalone.py.
+        self._log_error(msg, apache_level, apache.main_server)
+
+
+def _configure_logging():
+    logger = logging.getLogger()
+    # Logs are filtered by Apache based on LogLevel directive in Apache
+    # configuration file. We must just pass logs for all levels to
+    # ApacheLogHandler.
+    logger.setLevel(logging.DEBUG)
+    logger.addHandler(ApacheLogHandler())
+
+
+_configure_logging()
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def _parse_option(name, value, definition):
+    if value is None:
+        return False
+
+    meaning = definition.get(value.lower())
+    if meaning is None:
+        raise Exception('Invalid value for PythonOption %s: %r' %
+                        (name, value))
+    return meaning
+
+
+def _create_dispatcher():
+    _LOGGER.info('Initializing Dispatcher')
+
+    options = apache.main_server.get_options()
+
+    handler_root = options.get(_PYOPT_HANDLER_ROOT, None)
+    if not handler_root:
+        raise Exception('PythonOption %s is not defined' % _PYOPT_HANDLER_ROOT,
+                        apache.APLOG_ERR)
+
+    handler_scan = options.get(_PYOPT_HANDLER_SCAN, handler_root)
+
+    allow_handlers_outside_root = _parse_option(
+        _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT,
+        options.get(_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT),
+        _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION)
+
+    dispatcher = dispatch.Dispatcher(
+        handler_root, handler_scan, allow_handlers_outside_root)
+
+    for warning in dispatcher.source_warnings():
+        apache.log_error('mod_pywebsocket: %s' % warning, apache.APLOG_WARNING)
+
+    return dispatcher
+
+
+# Initialize
+_dispatcher = _create_dispatcher()
+
+
+def headerparserhandler(request):
+    """Handle request.
+
+    Args:
+        request: mod_python request.
+
+    This function is named headerparserhandler because it is the default
+    name for a PythonHeaderParserHandler.
+    """
+
+    handshake_is_done = False
+    try:
+        # Fallback to default http handler for request paths for which
+        # we don't have request handlers.
+        if not _dispatcher.get_handler_suite(request.uri):
+            request.log_error('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._dispatcher = _dispatcher
+        _dispatcher.transfer_data(request)
+    except handshake.AbortedByUserException, e:
+        request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO)
+    except Exception, e:
+        # DispatchException can also be thrown if something is wrong in
+        # pywebsocket code. It's caught here, then.
+
+        request.log_error('mod_pywebsocket: %s\n%s' %
+                          (e, util.get_stack_trace()),
+                          apache.APLOG_ERR)
+        # Unknown exceptions before handshake mean Apache must handle its
+        # request with another handler.
+        if not handshake_is_done:
+            return apache.DECLINED
+    # Set assbackwards to suppress response header generation by Apache.
+    request.assbackwards = 1
+    return apache.DONE  # Return DONE such that no other handlers are invoked.
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/http_header_util.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/http_header_util.py
new file mode 100644 (file)
index 0000000..b774653
--- /dev/null
@@ -0,0 +1,263 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Utilities for parsing and formatting headers that follow the grammar defined
+in HTTP RFC http://www.ietf.org/rfc/rfc2616.txt.
+"""
+
+
+import urlparse
+
+
+_SEPARATORS = '()<>@,;:\\"/[]?={} \t'
+
+
+def _is_char(c):
+    """Returns true iff c is in CHAR as specified in HTTP RFC."""
+
+    return ord(c) <= 127
+
+
+def _is_ctl(c):
+    """Returns true iff c is in CTL as specified in HTTP RFC."""
+
+    return ord(c) <= 31 or ord(c) == 127
+
+
+class ParsingState(object):
+
+    def __init__(self, data):
+        self.data = data
+        self.head = 0
+
+
+def peek(state, pos=0):
+    """Peeks the character at pos from the head of data."""
+
+    if state.head + pos >= len(state.data):
+        return None
+
+    return state.data[state.head + pos]
+
+
+def consume(state, amount=1):
+    """Consumes specified amount of bytes from the head and returns the
+    consumed bytes. If there's not enough bytes to consume, returns None.
+    """
+
+    if state.head + amount > len(state.data):
+        return None
+
+    result = state.data[state.head:state.head + amount]
+    state.head = state.head + amount
+    return result
+
+
+def consume_string(state, expected):
+    """Given a parsing state and a expected string, consumes the string from
+    the head. Returns True if consumed successfully. Otherwise, returns
+    False.
+    """
+
+    pos = 0
+
+    for c in expected:
+        if c != peek(state, pos):
+            return False
+        pos += 1
+
+    consume(state, pos)
+    return True
+
+
+def consume_lws(state):
+    """Consumes a LWS from the head. Returns True if any LWS is consumed.
+    Otherwise, returns False.
+
+    LWS = [CRLF] 1*( SP | HT )
+    """
+
+    original_head = state.head
+
+    consume_string(state, '\r\n')
+
+    pos = 0
+
+    while True:
+        c = peek(state, pos)
+        if c == ' ' or c == '\t':
+            pos += 1
+        else:
+            if pos == 0:
+                state.head = original_head
+                return False
+            else:
+                consume(state, pos)
+                return True
+
+
+def consume_lwses(state):
+    """Consumes *LWS from the head."""
+
+    while consume_lws(state):
+        pass
+
+
+def consume_token(state):
+    """Consumes a token from the head. Returns the token or None if no token
+    was found.
+    """
+
+    pos = 0
+
+    while True:
+        c = peek(state, pos)
+        if c is None or c in _SEPARATORS or _is_ctl(c) or not _is_char(c):
+            if pos == 0:
+                return None
+
+            return consume(state, pos)
+        else:
+            pos += 1
+
+
+def consume_token_or_quoted_string(state):
+    """Consumes a token or a quoted-string, and returns the token or unquoted
+    string. If no token or quoted-string was found, returns None.
+    """
+
+    original_head = state.head
+
+    if not consume_string(state, '"'):
+        return consume_token(state)
+
+    result = []
+
+    expect_quoted_pair = False
+
+    while True:
+        if not expect_quoted_pair and consume_lws(state):
+            result.append(' ')
+            continue
+
+        c = consume(state)
+        if c is None:
+            # quoted-string is not enclosed with double quotation
+            state.head = original_head
+            return None
+        elif expect_quoted_pair:
+            expect_quoted_pair = False
+            if _is_char(c):
+                result.append(c)
+            else:
+                # Non CHAR character found in quoted-pair
+                state.head = original_head
+                return None
+        elif c == '\\':
+            expect_quoted_pair = True
+        elif c == '"':
+            return ''.join(result)
+        elif _is_ctl(c):
+            # Invalid character %r found in qdtext
+            state.head = original_head
+            return None
+        else:
+            result.append(c)
+
+
+def quote_if_necessary(s):
+    """Quotes arbitrary string into quoted-string."""
+
+    quote = False
+    if s == '':
+        return '""'
+
+    result = []
+    for c in s:
+        if c == '"' or c in _SEPARATORS or _is_ctl(c) or not _is_char(c):
+            quote = True
+
+        if c == '"' or _is_ctl(c):
+            result.append('\\' + c)
+        else:
+            result.append(c)
+
+    if quote:
+        return '"' + ''.join(result) + '"'
+    else:
+        return ''.join(result)
+
+
+def parse_uri(uri):
+    """Parse absolute URI then return host, port and resource."""
+
+    parsed = urlparse.urlsplit(uri)
+    if parsed.scheme != 'wss' and parsed.scheme != 'ws':
+        # |uri| must be a relative URI.
+        # TODO(toyoshim): Should validate |uri|.
+        return None, None, uri
+
+    if parsed.hostname is None:
+        return None, None, None
+
+    port = None
+    try:
+        port = parsed.port
+    except ValueError, e:
+        # port property cause ValueError on invalid null port description like
+        # 'ws://host:/path'.
+        return None, None, None
+
+    if port is None:
+        if parsed.scheme == 'ws':
+            port = 80
+        else:
+            port = 443
+
+    path = parsed.path
+    if not path:
+        path += '/'
+    if parsed.query:
+        path += '?' + parsed.query
+    if parsed.fragment:
+        path += '#' + parsed.fragment
+
+    return parsed.hostname, port, path
+
+
+try:
+    urlparse.uses_netloc.index('ws')
+except ValueError, e:
+    # urlparse in Python2.5.1 doesn't have 'ws' and 'wss' entries.
+    urlparse.uses_netloc.append('ws')
+    urlparse.uses_netloc.append('wss')
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/memorizingfile.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/memorizingfile.py
new file mode 100644 (file)
index 0000000..4d4cd95
--- /dev/null
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+#
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Memorizing file.
+
+A memorizing file wraps a file and memorizes lines read by readline.
+"""
+
+
+import sys
+
+
+class MemorizingFile(object):
+    """MemorizingFile wraps a file and memorizes lines read by readline.
+
+    Note that data read by other methods are not memorized. This behavior
+    is good enough for memorizing lines SimpleHTTPServer reads before
+    the control reaches WebSocketRequestHandler.
+    """
+
+    def __init__(self, file_, max_memorized_lines=sys.maxint):
+        """Construct an instance.
+
+        Args:
+            file_: the file object to wrap.
+            max_memorized_lines: the maximum number of lines to memorize.
+                Only the first max_memorized_lines are memorized.
+                Default: sys.maxint.
+        """
+
+        self._file = file_
+        self._memorized_lines = []
+        self._max_memorized_lines = max_memorized_lines
+        self._buffered = False
+        self._buffered_line = None
+
+    def __getattribute__(self, name):
+        if name in ('_file', '_memorized_lines', '_max_memorized_lines',
+                    '_buffered', '_buffered_line', 'readline',
+                    'get_memorized_lines'):
+            return object.__getattribute__(self, name)
+        return self._file.__getattribute__(name)
+
+    def readline(self, size=-1):
+        """Override file.readline and memorize the line read.
+
+        Note that even if size is specified and smaller than actual size,
+        the whole line will be read out from underlying file object by
+        subsequent readline calls.
+        """
+
+        if self._buffered:
+            line = self._buffered_line
+            self._buffered = False
+        else:
+            line = self._file.readline()
+            if line and len(self._memorized_lines) < self._max_memorized_lines:
+                self._memorized_lines.append(line)
+        if size >= 0 and size < len(line):
+            self._buffered = True
+            self._buffered_line = line[size:]
+            return line[:size]
+        return line
+
+    def get_memorized_lines(self):
+        """Get lines memorized so far."""
+        return self._memorized_lines
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/msgutil.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/msgutil.py
new file mode 100644 (file)
index 0000000..21ffdac
--- /dev/null
@@ -0,0 +1,219 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Message related utilities.
+
+Note: request.connection.write/read are used in this module, even though
+mod_python document says that they should be used only in connection
+handlers. Unfortunately, we have no other options. For example,
+request.write/read are not suitable because they don't allow direct raw
+bytes writing/reading.
+"""
+
+
+import Queue
+import threading
+
+
+# Export Exception symbols from msgutil for backward compatibility
+from mod_pywebsocket._stream_base import ConnectionTerminatedException
+from mod_pywebsocket._stream_base import InvalidFrameException
+from mod_pywebsocket._stream_base import BadOperationException
+from mod_pywebsocket._stream_base import UnsupportedFrameException
+
+
+# An API for handler to send/receive WebSocket messages.
+def close_connection(request):
+    """Close connection.
+
+    Args:
+        request: mod_python request.
+    """
+    request.ws_stream.close_connection()
+
+
+def send_message(request, message, end=True, binary=False):
+    """Send message.
+
+    Args:
+        request: mod_python request.
+        message: unicode text or str binary to send.
+        end: False to send message as a fragment. All messages until the
+             first call with end=True (inclusive) will be delivered to the
+             client in separate frames but as one WebSocket message.
+        binary: send message as binary frame.
+    Raises:
+        BadOperationException: when server already terminated.
+    """
+    request.ws_stream.send_message(message, end, binary)
+
+
+def receive_message(request):
+    """Receive a WebSocket frame and return its payload as a text in
+    unicode or a binary in str.
+
+    Args:
+        request: mod_python request.
+    Raises:
+        InvalidFrameException:     when client send invalid frame.
+        UnsupportedFrameException: when client send unsupported frame e.g. some
+                                   of reserved bit is set but no extension can
+                                   recognize it.
+        InvalidUTF8Exception:      when client send a text frame containing any
+                                   invalid UTF-8 string.
+        ConnectionTerminatedException: when the connection is closed
+                                   unexpectedly.
+        BadOperationException:     when client already terminated.
+    """
+    return request.ws_stream.receive_message()
+
+
+def send_ping(request, body=''):
+    request.ws_stream.send_ping(body)
+
+
+class MessageReceiver(threading.Thread):
+    """This class receives messages from the client.
+
+    This class provides three ways to receive messages: blocking,
+    non-blocking, and via callback. Callback has the highest precedence.
+
+    Note: This class should not be used with the standalone server for wss
+    because pyOpenSSL used by the server raises a fatal error if the socket
+    is accessed from multiple threads.
+    """
+
+    def __init__(self, request, onmessage=None):
+        """Construct an instance.
+
+        Args:
+            request: mod_python request.
+            onmessage: a function to be called when a message is received.
+                       May be None. If not None, the function is called on
+                       another thread. In that case, MessageReceiver.receive
+                       and MessageReceiver.receive_nowait are useless
+                       because they will never return any messages.
+        """
+
+        threading.Thread.__init__(self)
+        self._request = request
+        self._queue = Queue.Queue()
+        self._onmessage = onmessage
+        self._stop_requested = False
+        self.setDaemon(True)
+        self.start()
+
+    def run(self):
+        try:
+            while not self._stop_requested:
+                message = receive_message(self._request)
+                if self._onmessage:
+                    self._onmessage(message)
+                else:
+                    self._queue.put(message)
+        finally:
+            close_connection(self._request)
+
+    def receive(self):
+        """ Receive a message from the channel, blocking.
+
+        Returns:
+            message as a unicode string.
+        """
+        return self._queue.get()
+
+    def receive_nowait(self):
+        """ Receive a message from the channel, non-blocking.
+
+        Returns:
+            message as a unicode string if available. None otherwise.
+        """
+        try:
+            message = self._queue.get_nowait()
+        except Queue.Empty:
+            message = None
+        return message
+
+    def stop(self):
+        """Request to stop this instance.
+
+        The instance will be stopped after receiving the next message.
+        This method may not be very useful, but there is no clean way
+        in Python to forcefully stop a running thread.
+        """
+        self._stop_requested = True
+
+
+class MessageSender(threading.Thread):
+    """This class sends messages to the client.
+
+    This class provides both synchronous and asynchronous ways to send
+    messages.
+
+    Note: This class should not be used with the standalone server for wss
+    because pyOpenSSL used by the server raises a fatal error if the socket
+    is accessed from multiple threads.
+    """
+
+    def __init__(self, request):
+        """Construct an instance.
+
+        Args:
+            request: mod_python request.
+        """
+        threading.Thread.__init__(self)
+        self._request = request
+        self._queue = Queue.Queue()
+        self.setDaemon(True)
+        self.start()
+
+    def run(self):
+        while True:
+            message, condition = self._queue.get()
+            condition.acquire()
+            send_message(self._request, message)
+            condition.notify()
+            condition.release()
+
+    def send(self, message):
+        """Send a message, blocking."""
+
+        condition = threading.Condition()
+        condition.acquire()
+        self._queue.put((message, condition))
+        condition.wait()
+
+    def send_nowait(self, message):
+        """Send a message, non-blocking."""
+
+        self._queue.put((message, threading.Condition()))
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py
new file mode 100755 (executable)
index 0000000..dc143ea
--- /dev/null
@@ -0,0 +1,870 @@
+#!/usr/bin/env python
+#
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Standalone WebSocket server.
+
+BASIC USAGE
+
+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 WebSocket handlers.
+See __init__.py for details of <websock_handlers> and how to write WebSocket
+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.
+
+
+CONFIGURATION FILE
+
+You can also write a configuration file and use it by specifying the path to
+the configuration file by --config option. Please write a configuration file
+following the documentation of the Python ConfigParser library. Name of each
+entry must be the long version argument name. E.g. to set log level to debug,
+add the following line:
+
+log_level=debug
+
+For options which doesn't take value, please add some fake value. E.g. for
+--tls option, add the following line:
+
+tls=True
+
+Note that tls will be enabled even if you write tls=False as the value part is
+fake.
+
+When both a command line argument and a configuration file entry are set for
+the same configuration item, the command line value will override one in the
+configuration file.
+
+
+THREADING
+
+This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
+used for each request.
+
+
+SECURITY WARNING
+
+This uses CGIHTTPServer and CGIHTTPServer is not secure.
+It may execute arbitrary Python code or external programs. It should not be
+used outside a firewall.
+"""
+
+import BaseHTTPServer
+import CGIHTTPServer
+import SimpleHTTPServer
+import SocketServer
+import ConfigParser
+import httplib
+import logging
+import logging.handlers
+import optparse
+import os
+import re
+import select
+import socket
+import sys
+import threading
+import time
+
+_HAS_SSL = False
+_HAS_OPEN_SSL = False
+try:
+    import ssl
+    _HAS_SSL = True
+except ImportError:
+    try:
+        import OpenSSL.SSL
+        _HAS_OPEN_SSL = True
+    except ImportError:
+        pass
+
+from mod_pywebsocket import common
+from mod_pywebsocket import dispatch
+from mod_pywebsocket import handshake
+from mod_pywebsocket import http_header_util
+from mod_pywebsocket import memorizingfile
+from mod_pywebsocket import util
+
+
+_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
+
+
+class _StandaloneConnection(object):
+    """Mimic mod_python mp_conn."""
+
+    def __init__(self, request_handler):
+        """Construct an instance.
+
+        Args:
+            request_handler: A WebSocketRequestHandler instance.
+        """
+
+        self._request_handler = request_handler
+
+    def get_local_addr(self):
+        """Getter to mimic mp_conn.local_addr."""
+
+        return (self._request_handler.server.server_name,
+                self._request_handler.server.server_port)
+    local_addr = property(get_local_addr)
+
+    def get_remote_addr(self):
+        """Getter to mimic mp_conn.remote_addr.
+
+        Setting the property in __init__ won't work because the request
+        handler is not initialized yet there."""
+
+        return self._request_handler.client_address
+    remote_addr = property(get_remote_addr)
+
+    def write(self, data):
+        """Mimic mp_conn.write()."""
+
+        return self._request_handler.wfile.write(data)
+
+    def read(self, length):
+        """Mimic mp_conn.read()."""
+
+        return self._request_handler.rfile.read(length)
+
+    def get_memorized_lines(self):
+        """Get memorized lines."""
+
+        return self._request_handler.rfile.get_memorized_lines()
+
+
+class _StandaloneRequest(object):
+    """Mimic mod_python request."""
+
+    def __init__(self, request_handler, use_tls):
+        """Construct an instance.
+
+        Args:
+            request_handler: A WebSocketRequestHandler instance.
+        """
+
+        self._logger = util.get_class_logger(self)
+
+        self._request_handler = request_handler
+        self.connection = _StandaloneConnection(request_handler)
+        self._use_tls = use_tls
+        self.headers_in = request_handler.headers
+
+    def get_uri(self):
+        """Getter to mimic request.uri."""
+
+        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 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.
+        """
+
+        raw_socket = self._request_handler.connection
+        drained_data = util.drain_received_data(raw_socket)
+
+        if drained_data:
+            self._logger.debug(
+                'Drained data following close frame: %r', drained_data)
+
+
+class _StandaloneSSLConnection(object):
+    """A wrapper class for OpenSSL.SSL.Connection to provide makefile method
+    which is not supported by the class.
+    """
+
+    def __init__(self, connection):
+        self._connection = connection
+
+    def __getattribute__(self, name):
+        if name in ('_connection', 'makefile'):
+            return object.__getattribute__(self, name)
+        return self._connection.__getattribute__(name)
+
+    def __setattr__(self, name, value):
+        if name in ('_connection', 'makefile'):
+            return object.__setattr__(self, name, value)
+        return self._connection.__setattr__(name, value)
+
+    def makefile(self, mode='r', bufsize=-1):
+        return socket._fileobject(self._connection, mode, bufsize)
+
+
+class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
+    """HTTPServer specialized for WebSocket."""
+
+    # Overrides SocketServer.ThreadingMixIn.daemon_threads
+    daemon_threads = True
+    # Overrides BaseHTTPServer.HTTPServer.allow_reuse_address
+    allow_reuse_address = True
+
+    def __init__(self, options):
+        """Override SocketServer.TCPServer.__init__ to set SSL enabled
+        socket object to self.socket before server_bind and server_activate,
+        if necessary.
+        """
+
+        self._logger = util.get_class_logger(self)
+
+        self.request_queue_size = options.request_queue_size
+        self.__ws_is_shut_down = threading.Event()
+        self.__ws_serving = False
+
+        SocketServer.BaseServer.__init__(
+            self, (options.server_host, options.port), WebSocketRequestHandler)
+
+        # Expose the options object to allow handler objects access it. We name
+        # it with websocket_ prefix to avoid conflict.
+        self.websocket_server_options = options
+
+        self._create_sockets()
+        self.server_bind()
+        self.server_activate()
+
+    def _create_sockets(self):
+        self.server_name, self.server_port = self.server_address
+        self._sockets = []
+        if not self.server_name:
+            # On platforms that doesn't support IPv6, the first bind fails.
+            # On platforms that supports IPv6
+            # - If it binds both IPv4 and IPv6 on call with AF_INET6, the
+            #   first bind succeeds and the second fails (we'll see 'Address
+            #   already in use' error).
+            # - If it binds only IPv6 on call with AF_INET6, both call are
+            #   expected to succeed to listen both protocol.
+            addrinfo_array = [
+                (socket.AF_INET6, socket.SOCK_STREAM, '', '', ''),
+                (socket.AF_INET, socket.SOCK_STREAM, '', '', '')]
+        else:
+            addrinfo_array = socket.getaddrinfo(self.server_name,
+                                                self.server_port,
+                                                socket.AF_UNSPEC,
+                                                socket.SOCK_STREAM,
+                                                socket.IPPROTO_TCP)
+        for addrinfo in addrinfo_array:
+            self._logger.info('Create socket on: %r', addrinfo)
+            family, socktype, proto, canonname, sockaddr = addrinfo
+            try:
+                socket_ = socket.socket(family, socktype)
+            except Exception, e:
+                self._logger.info('Skip by failure: %r', e)
+                continue
+            if self.websocket_server_options.use_tls:
+                if _HAS_SSL:
+                    socket_ = ssl.wrap_socket(socket_,
+                        keyfile=self.websocket_server_options.private_key,
+                        certfile=self.websocket_server_options.certificate,
+                        ssl_version=ssl.PROTOCOL_SSLv23)
+                if _HAS_OPEN_SSL:
+                    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.
+        """
+
+        failed_sockets = []
+
+        for socketinfo in self._sockets:
+            socket_, addrinfo = socketinfo
+            self._logger.info('Bind on: %r', addrinfo)
+            if self.allow_reuse_address:
+                socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+            try:
+                socket_.bind(self.server_address)
+            except Exception, e:
+                self._logger.info('Skip by failure: %r', e)
+                socket_.close()
+                failed_sockets.append(socketinfo)
+
+        for socketinfo in failed_sockets:
+            self._sockets.remove(socketinfo)
+
+    def server_activate(self):
+        """Override SocketServer.TCPServer.server_activate to enable multiple
+        sockets listen.
+        """
+
+        failed_sockets = []
+
+        for socketinfo in self._sockets:
+            socket_, addrinfo = socketinfo
+            self._logger.info('Listen on: %r', addrinfo)
+            try:
+                socket_.listen(self.request_queue_size)
+            except Exception, e:
+                self._logger.info('Skip by failure: %r', e)
+                socket_.close()
+                failed_sockets.append(socketinfo)
+
+        for socketinfo in failed_sockets:
+            self._sockets.remove(socketinfo)
+
+    def server_close(self):
+        """Override SocketServer.TCPServer.server_close to enable multiple
+        sockets close.
+        """
+
+        for socketinfo in self._sockets:
+            socket_, addrinfo = socketinfo
+            self._logger.info('Close on: %r', addrinfo)
+            socket_.close()
+
+    def fileno(self):
+        """Override SocketServer.TCPServer.fileno."""
+
+        self._logger.critical('Not supported: fileno')
+        return self._sockets[0][0].fileno()
+
+    def handle_error(self, rquest, client_address):
+        """Override SocketServer.handle_error."""
+
+        self._logger.error(
+            'Exception in processing request from: %r\n%s',
+            client_address,
+            util.get_stack_trace())
+        # Note: client_address is a tuple.
+
+    def get_request(self):
+        """Override TCPServer.get_request to wrap OpenSSL.SSL.Connection
+        object with _StandaloneSSLConnection to provide makefile method. We
+        cannot substitute OpenSSL.SSL.Connection.makefile since it's readonly
+        attribute.
+        """
+
+        accepted_socket, client_address = self.socket.accept()
+        if self.websocket_server_options.use_tls and _HAS_OPEN_SSL:
+            accepted_socket = _StandaloneSSLConnection(accepted_socket)
+        return accepted_socket, client_address
+
+    def serve_forever(self, poll_interval=0.5):
+        """Override SocketServer.BaseServer.serve_forever."""
+
+        self.__ws_serving = True
+        self.__ws_is_shut_down.clear()
+        handle_request = self.handle_request
+        if hasattr(self, '_handle_request_noblock'):
+            handle_request = self._handle_request_noblock
+        else:
+            self._logger.warning('Fallback to blocking request handler')
+        try:
+            while self.__ws_serving:
+                r, w, e = select.select(
+                    [socket_[0] for socket_ in self._sockets],
+                    [], [], poll_interval)
+                for socket_ in r:
+                    self.socket = socket_
+                    handle_request()
+                self.socket = None
+        finally:
+            self.__ws_is_shut_down.set()
+
+    def shutdown(self):
+        """Override SocketServer.BaseServer.shutdown."""
+
+        self.__ws_serving = False
+        self.__ws_is_shut_down.wait()
+
+
+class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
+    """CGIHTTPRequestHandler specialized for WebSocket."""
+
+    # Use httplib.HTTPMessage instead of mimetools.Message.
+    MessageClass = httplib.HTTPMessage
+
+    def setup(self):
+        """Override SocketServer.StreamRequestHandler.setup to wrap rfile
+        with MemorizingFile.
+
+        This method will be called by BaseRequestHandler's constructor
+        before calling BaseHTTPRequestHandler.handle.
+        BaseHTTPRequestHandler.handle will call
+        BaseHTTPRequestHandler.handle_one_request and it will call
+        WebSocketRequestHandler.parse_request.
+        """
+
+        # Call superclass's setup to prepare rfile, wfile, etc. See setup
+        # definition on the root class SocketServer.StreamRequestHandler to
+        # understand what this does.
+        CGIHTTPServer.CGIHTTPRequestHandler.setup(self)
+
+        self.rfile = memorizingfile.MemorizingFile(
+            self.rfile,
+            max_memorized_lines=_MAX_MEMORIZED_LINES)
+
+    def __init__(self, request, client_address, server):
+        self._logger = util.get_class_logger(self)
+
+        self._options = server.websocket_server_options
+
+        # Overrides CGIHTTPServerRequestHandler.cgi_directories.
+        self.cgi_directories = self._options.cgi_directories
+        # Replace CGIHTTPRequestHandler.is_executable method.
+        if self._options.is_executable_method is not None:
+            self.is_executable = self._options.is_executable_method
+
+        # This actually calls BaseRequestHandler.__init__.
+        CGIHTTPServer.CGIHTTPRequestHandler.__init__(
+            self, request, client_address, server)
+
+    def parse_request(self):
+        """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
+
+        Return True to continue processing for HTTP(S), False otherwise.
+
+        See BaseHTTPRequestHandler.handle_one_request method which calls
+        this method to understand how the return value will be handled.
+        """
+
+        # We hook parse_request method, but also call the original
+        # CGIHTTPRequestHandler.parse_request since when we return False,
+        # CGIHTTPRequestHandler.handle_one_request continues processing and
+        # it needs variables set by CGIHTTPRequestHandler.parse_request.
+        #
+        # Variables set by this method will be also used by WebSocket request
+        # handling (self.path, self.command, self.requestline, etc. See also
+        # how _StandaloneRequest's members are implemented using these
+        # attributes).
+        if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self):
+            return False
+        host, port, resource = http_header_util.parse_uri(self.path)
+        if resource is None:
+            self._logger.info('Invalid URI: %r', self.path)
+            self._logger.info('Fallback to CGIHTTPRequestHandler')
+            return True
+        server_options = self.server.websocket_server_options
+        if host is not None:
+            validation_host = server_options.validation_host
+            if validation_host is not None and host != validation_host:
+                self._logger.info('Invalid host: %r (expected: %r)',
+                                  host,
+                                  validation_host)
+                self._logger.info('Fallback to CGIHTTPRequestHandler')
+                return True
+        if port is not None:
+            validation_port = server_options.validation_port
+            if validation_port is not None and port != validation_port:
+                self._logger.info('Invalid port: %r (expected: %r)',
+                                  port,
+                                  validation_port)
+                self._logger.info('Fallback to CGIHTTPRequestHandler')
+                return True
+        self.path = resource
+
+        request = _StandaloneRequest(self, self._options.use_tls)
+
+        try:
+            # Fallback to default http handler for request paths for which
+            # we don't have request handlers.
+            if not self._options.dispatcher.get_handler_suite(self.path):
+                self._logger.info('No handler for resource: %r',
+                                  self.path)
+                self._logger.info('Fallback to CGIHTTPRequestHandler')
+                return True
+        except dispatch.DispatchException, 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(
+                    request,
+                    self._options.dispatcher,
+                    allowDraft75=self._options.allow_draft75,
+                    strict=self._options.strict)
+            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.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."""
+
+        self._logger.info('"%s" %s %s',
+                          self.requestline, str(code), str(size))
+
+    def log_error(self, *args):
+        """Override BaseHTTPServer.log_error."""
+
+        # Despite the name, this method is for warnings than for errors.
+        # For example, HTTP status code is logged by this method.
+        self._logger.warning('%s - %s',
+                             self.address_string(),
+                             args[0] % args[1:])
+
+    def is_cgi(self):
+        """Test whether self.path corresponds to a CGI script.
+
+        Add extra check that self.path doesn't contains ..
+        Also check if the file is a executable file or not.
+        If the file is not executable, it is handled as static file or dir
+        rather than a CGI script.
+        """
+
+        if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self):
+            if '..' in self.path:
+                return False
+            # strip query parameter from request path
+            resource_name = self.path.split('?', 2)[0]
+            # convert resource_name into real path name in filesystem.
+            scriptfile = self.translate_path(resource_name)
+            if not os.path.isfile(scriptfile):
+                return False
+            if not self.is_executable(scriptfile):
+                return False
+            return True
+        return False
+
+
+def _configure_logging(options):
+    logger = logging.getLogger()
+    logger.setLevel(logging.getLevelName(options.log_level.upper()))
+    if options.log_file:
+        handler = logging.handlers.RotatingFileHandler(
+                options.log_file, 'a', options.log_max, options.log_count)
+    else:
+        handler = logging.StreamHandler()
+    formatter = logging.Formatter(
+            '[%(asctime)s] [%(levelname)s] %(name)s: %(message)s')
+    handler.setFormatter(formatter)
+    logger.addHandler(handler)
+
+
+def _alias_handlers(dispatcher, websock_handlers_map_file):
+    """Set aliases specified in websock_handler_map_file in dispatcher.
+
+    Args:
+        dispatcher: dispatch.Dispatcher instance
+        websock_handler_map_file: alias map file
+    """
+
+    fp = open(websock_handlers_map_file)
+    try:
+        for line in fp:
+            if line[0] == '#' or line.isspace():
+                continue
+            m = re.match('(\S+)\s+(\S+)', line)
+            if not m:
+                logging.warning('Wrong format in map file:' + line)
+                continue
+            try:
+                dispatcher.add_resource_path_alias(
+                    m.group(1), m.group(2))
+            except dispatch.DispatchException, e:
+                logging.error(str(e))
+    finally:
+        fp.close()
+
+
+def _build_option_parser():
+    parser = optparse.OptionParser()
+
+    parser.add_option('--config', dest='config_file', type='string',
+                      default=None,
+                      help=('Path to configuration file. See the file comment '
+                            'at the top of this file for the configuration '
+                            'file format'))
+    parser.add_option('-H', '--server-host', '--server_host',
+                      dest='server_host',
+                      default='',
+                      help='server hostname to listen to')
+    parser.add_option('-V', '--validation-host', '--validation_host',
+                      dest='validation_host',
+                      default=None,
+                      help='server hostname to validate in absolute path.')
+    parser.add_option('-p', '--port', dest='port', type='int',
+                      default=common.DEFAULT_WEB_SOCKET_PORT,
+                      help='port to listen to')
+    parser.add_option('-P', '--validation-port', '--validation_port',
+                      dest='validation_port', type='int',
+                      default=None,
+                      help='server port to validate in absolute path.')
+    parser.add_option('-w', '--websock-handlers', '--websock_handlers',
+                      dest='websock_handlers',
+                      default='.',
+                      help='WebSocket handlers root directory.')
+    parser.add_option('-m', '--websock-handlers-map-file',
+                      '--websock_handlers_map_file',
+                      dest='websock_handlers_map_file',
+                      default=None,
+                      help=('WebSocket handlers map file. '
+                            'Each line consists of alias_resource_path and '
+                            'existing_resource_path, separated by spaces.'))
+    parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir',
+                      default=None,
+                      help=('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 '
+                            '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', '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')
+
+    return parser
+
+
+class ThreadMonitor(threading.Thread):
+    daemon = True
+
+    def __init__(self, interval_in_sec):
+        threading.Thread.__init__(self, name='ThreadMonitor')
+
+        self._logger = util.get_class_logger(self)
+
+        self._interval_in_sec = interval_in_sec
+
+    def run(self):
+        while True:
+            thread_name_list = []
+            for thread in threading.enumerate():
+                thread_name_list.append(thread.name)
+            self._logger.info(
+                "%d active threads: %s",
+                threading.active_count(),
+                ', '.join(thread_name_list))
+            time.sleep(self._interval_in_sec)
+
+
+def _parse_args_and_config(args):
+    parser = _build_option_parser()
+
+    # First, parse options without configuration file.
+    temporary_options, temporary_args = parser.parse_args(args=args)
+    if temporary_args:
+        logging.critical(
+            'Unrecognized positional arguments: %r', temporary_args)
+        sys.exit(1)
+
+    if temporary_options.config_file:
+        try:
+            config_fp = open(temporary_options.config_file, 'r')
+        except IOError, e:
+            logging.critical(
+                'Failed to open configuration file %r: %r',
+                temporary_options.config_file,
+                e)
+            sys.exit(1)
+
+        config_parser = ConfigParser.SafeConfigParser()
+        config_parser.readfp(config_fp)
+        config_fp.close()
+
+        args_from_config = []
+        for name, value in config_parser.items('pywebsocket'):
+            args_from_config.append('--' + name)
+            args_from_config.append(value)
+        if args is None:
+            args = args_from_config
+        else:
+            args = args_from_config + args
+        return parser.parse_args(args=args)
+    else:
+        return temporary_options, temporary_args
+
+
+def _main(args=None):
+    options, args = _parse_args_and_config(args=args)
+
+    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 = []
+    options.is_executable_method = None
+    if options.cgi_paths:
+        options.cgi_directories = options.cgi_paths.split(',')
+        if sys.platform in ('cygwin', 'win32'):
+            cygwin_path = None
+            # For Win32 Python, it is expected that CYGWIN_PATH
+            # is set to a directory of cygwin binaries.
+            # For example, websocket_server.py in Chromium sets CYGWIN_PATH to
+            # full path of third_party/cygwin/bin.
+            if 'CYGWIN_PATH' in os.environ:
+                cygwin_path = os.environ['CYGWIN_PATH']
+            util.wrap_popen3_for_win(cygwin_path)
+
+            def __check_script(scriptpath):
+                return util.get_script_interp(scriptpath, cygwin_path)
+
+            options.is_executable_method = __check_script
+
+    if options.use_tls:
+        if not (_HAS_SSL or _HAS_OPEN_SSL):
+            logging.critical('TLS support requires ssl or 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:
+        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.allow_handlers_outside_root_dir)
+        if options.websock_handlers_map_file:
+            _alias_handlers(options.dispatcher,
+                            options.websock_handlers_map_file)
+        warnings = options.dispatcher.source_warnings()
+        if warnings:
+            for warning in warnings:
+                logging.warning('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)
+
+
+if __name__ == '__main__':
+    _main(sys.argv[1:])
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/stream.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/stream.py
new file mode 100644 (file)
index 0000000..d051eee
--- /dev/null
@@ -0,0 +1,56 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""This file exports public symbols.
+"""
+
+
+from mod_pywebsocket._stream_base import BadOperationException
+from mod_pywebsocket._stream_base import ConnectionTerminatedException
+from mod_pywebsocket._stream_base import InvalidFrameException
+from mod_pywebsocket._stream_base import InvalidUTF8Exception
+from mod_pywebsocket._stream_base import UnsupportedFrameException
+from mod_pywebsocket._stream_hixie75 import StreamHixie75
+from mod_pywebsocket._stream_hybi import Frame
+from mod_pywebsocket._stream_hybi import Stream
+from mod_pywebsocket._stream_hybi import StreamOptions
+
+# These methods are intended to be used by WebSocket client developers to have
+# their implementations receive broken data in tests.
+from mod_pywebsocket._stream_hybi import create_close_frame
+from mod_pywebsocket._stream_hybi import create_header
+from mod_pywebsocket._stream_hybi import create_length_header
+from mod_pywebsocket._stream_hybi import create_ping_frame
+from mod_pywebsocket._stream_hybi import create_pong_frame
+from mod_pywebsocket._stream_hybi import create_binary_frame
+from mod_pywebsocket._stream_hybi import create_text_frame
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py
new file mode 100644 (file)
index 0000000..9a0ab5d
--- /dev/null
@@ -0,0 +1,489 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""WebSocket utilities.
+"""
+
+
+import array
+import errno
+
+# Import hash classes from a module available and recommended for each Python
+# version and re-export those symbol. Use sha and md5 module in Python 2.4, and
+# hashlib module in Python 2.6.
+try:
+    import hashlib
+    md5_hash = hashlib.md5
+    sha1_hash = hashlib.sha1
+except ImportError:
+    import md5
+    import sha
+    md5_hash = md5.md5
+    sha1_hash = sha.sha
+
+import StringIO
+import logging
+import os
+import re
+import socket
+import traceback
+import zlib
+
+
+def get_stack_trace():
+    """Get the current stack trace as string.
+
+    This is needed to support Python 2.3.
+    TODO: Remove this when we only support Python 2.4 and above.
+          Use traceback.format_exc instead.
+    """
+
+    out = StringIO.StringIO()
+    traceback.print_exc(file=out)
+    return out.getvalue()
+
+
+def prepend_message_to_exception(message, exc):
+    """Prepend message to the exception."""
+
+    exc.args = (message + str(exc),)
+    return
+
+
+def __translate_interp(interp, cygwin_path):
+    """Translate interp program path for Win32 python to run cygwin program
+    (e.g. perl).  Note that it doesn't support path that contains space,
+    which is typically true for Unix, where #!-script is written.
+    For Win32 python, cygwin_path is a directory of cygwin binaries.
+
+    Args:
+      interp: interp command line
+      cygwin_path: directory name of cygwin binary, or None
+    Returns:
+      translated interp command line.
+    """
+    if not cygwin_path:
+        return interp
+    m = re.match('^[^ ]*/([^ ]+)( .*)?', interp)
+    if m:
+        cmd = os.path.join(cygwin_path, m.group(1))
+        return cmd + m.group(2)
+    return interp
+
+
+def get_script_interp(script_path, cygwin_path=None):
+    """Gets #!-interpreter command line from the script.
+
+    It also fixes command path.  When Cygwin Python is used, e.g. in WebKit,
+    it could run "/usr/bin/perl -wT hello.pl".
+    When Win32 Python is used, e.g. in Chromium, it couldn't.  So, fix
+    "/usr/bin/perl" to "<cygwin_path>\perl.exe".
+
+    Args:
+      script_path: pathname of the script
+      cygwin_path: directory name of cygwin binary, or None
+    Returns:
+      #!-interpreter command line, or None if it is not #!-script.
+    """
+    fp = open(script_path)
+    line = fp.readline()
+    fp.close()
+    m = re.match('^#!(.*)', line)
+    if m:
+        return __translate_interp(m.group(1), cygwin_path)
+    return None
+
+
+def wrap_popen3_for_win(cygwin_path):
+    """Wrap popen3 to support #!-script on Windows.
+
+    Args:
+      cygwin_path:  path for cygwin binary if command path is needed to be
+                    translated.  None if no translation required.
+    """
+
+    __orig_popen3 = os.popen3
+
+    def __wrap_popen3(cmd, mode='t', bufsize=-1):
+        cmdline = cmd.split(' ')
+        interp = get_script_interp(cmdline[0], cygwin_path)
+        if interp:
+            cmd = interp + ' ' + cmd
+        return __orig_popen3(cmd, mode, bufsize)
+
+    os.popen3 = __wrap_popen3
+
+
+def hexify(s):
+    return ' '.join(map(lambda x: '%02x' % ord(x), s))
+
+
+def get_class_logger(o):
+    return logging.getLogger(
+        '%s.%s' % (o.__class__.__module__, o.__class__.__name__))
+
+
+class NoopMasker(object):
+    """A masking object that has the same interface as RepeatedXorMasker but
+    just returns the string passed in without making any change.
+    """
+
+    def __init__(self):
+        pass
+
+    def mask(self, s):
+        return s
+
+
+class RepeatedXorMasker(object):
+    """A masking object that applies XOR on the string given to mask method
+    with the masking bytes given to the constructor repeatedly. This object
+    remembers the position in the masking bytes the last mask method call
+    ended and resumes from that point on the next mask method call.
+    """
+
+    def __init__(self, mask):
+        self._mask = map(ord, mask)
+        self._mask_size = len(self._mask)
+        self._count = 0
+
+    def mask(self, s):
+        result = array.array('B')
+        result.fromstring(s)
+        for i in xrange(len(result)):
+            result[i] ^= self._mask[self._count]
+            self._count = (self._count + 1) % self._mask_size
+        return result.tostring()
+
+
+class DeflateRequest(object):
+    """A wrapper class for request object to intercept send and recv to perform
+    deflate compression and decompression transparently.
+    """
+
+    def __init__(self, request):
+        self._request = request
+        self.connection = DeflateConnection(request.connection)
+
+    def __getattribute__(self, name):
+        if name in ('_request', 'connection'):
+            return object.__getattribute__(self, name)
+        return self._request.__getattribute__(name)
+
+    def __setattr__(self, name, value):
+        if name in ('_request', 'connection'):
+            return object.__setattr__(self, name, value)
+        return self._request.__setattr__(name, value)
+
+
+# By making wbits option negative, we can suppress CMF/FLG (2 octet) and
+# ADLER32 (4 octet) fields of zlib so that we can use zlib module just as
+# deflate library. DICTID won't be added as far as we don't set dictionary.
+# LZ77 window of 32K will be used for both compression and decompression.
+# For decompression, we can just use 32K to cover any windows size. For
+# compression, we use 32K so receivers must use 32K.
+#
+# Compression level is Z_DEFAULT_COMPRESSION. We don't have to match level
+# to decode.
+#
+# See zconf.h, deflate.cc, inflate.cc of zlib library, and zlibmodule.c of
+# Python. See also RFC1950 (ZLIB 3.3).
+
+
+class _Deflater(object):
+
+    def __init__(self, window_bits):
+        self._logger = get_class_logger(self)
+
+        self._compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -window_bits)
+
+    def compress_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):
+        if not (size == -1 or size > 0):
+            raise Exception('size must be -1 or positive')
+
+        data = ''
+
+        while True:
+            if size == -1:
+                data += self._decompress.decompress(self._unconsumed)
+                # See Python bug http://bugs.python.org/issue12050 to
+                # understand why the same code cannot be used for updating
+                # self._unconsumed for here and else block.
+                self._unconsumed = ''
+            else:
+                data += self._decompress.decompress(
+                    self._unconsumed, size - len(data))
+                self._unconsumed = self._decompress.unconsumed_tail
+            if self._decompress.unused_data:
+                # Encountered a last block (i.e. a block with BFINAL = 1) and
+                # found a new stream (unused_data). We cannot use the same
+                # zlib.Decompress object for the new stream. Create a new
+                # Decompress object to decompress the new one.
+                #
+                # It's fine to ignore unconsumed_tail if unused_data is not
+                # empty.
+                self._unconsumed = self._decompress.unused_data
+                self.reset()
+                if size >= 0 and len(data) == size:
+                    # data is filled. Don't call decompress again.
+                    break
+                else:
+                    # Re-invoke Decompress.decompress to try to decompress all
+                    # available bytes before invoking read which blocks until
+                    # any new byte is available.
+                    continue
+            else:
+                # Here, since unused_data is empty, even if unconsumed_tail is
+                # not empty, bytes of requested length are already in data. We
+                # don't have to "continue" here.
+                break
+
+        if data:
+            self._logger.debug('Decompressed %r', data)
+        return data
+
+    def append(self, data):
+        self._logger.debug('Appended %r', data)
+        self._unconsumed += data
+
+    def reset(self):
+        self._logger.debug('Reset')
+        self._decompress = zlib.decompressobj(-zlib.MAX_WBITS)
+
+
+# Compresses/decompresses given octets using the method introduced in RFC1979.
+
+
+class _RFC1979Deflater(object):
+    """A compressor class that applies DEFLATE to given byte sequence and
+    flushes using the algorithm described in the RFC1979 section 2.1.
+    """
+
+    def __init__(self, window_bits, no_context_takeover):
+        self._deflater = None
+        if window_bits is None:
+            window_bits = zlib.MAX_WBITS
+        self._window_bits = window_bits
+        self._no_context_takeover = no_context_takeover
+
+    def filter(self, bytes):
+        if self._deflater is None or self._no_context_takeover:
+            self._deflater = _Deflater(self._window_bits)
+
+        # Strip last 4 octets which is LEN and NLEN field of a non-compressed
+        # block added for Z_SYNC_FLUSH.
+        return self._deflater.compress_and_flush(bytes)[:-4]
+
+
+class _RFC1979Inflater(object):
+    """A decompressor class for byte sequence compressed and flushed following
+    the algorithm described in the RFC1979 section 2.1.
+    """
+
+    def __init__(self):
+        self._inflater = _Inflater()
+
+    def filter(self, bytes):
+        # Restore stripped LEN and NLEN field of a non-compressed block added
+        # for Z_SYNC_FLUSH.
+        self._inflater.append(bytes + '\x00\x00\xff\xff')
+        return self._inflater.decompress(-1)
+
+
+class DeflateSocket(object):
+    """A wrapper class for socket object to intercept send and recv to perform
+    deflate compression and decompression transparently.
+    """
+
+    # Size of the buffer passed to recv to receive compressed data.
+    _RECV_SIZE = 4096
+
+    def __init__(self, socket):
+        self._socket = socket
+
+        self._logger = get_class_logger(self)
+
+        self._deflater = _Deflater(zlib.MAX_WBITS)
+        self._inflater = _Inflater()
+
+    def recv(self, size):
+        """Receives data from the socket specified on the construction up
+        to the specified size. Once any data is available, returns it even
+        if it's smaller than the specified size.
+        """
+
+        # TODO(tyoshino): Allow call with size=0. It should block until any
+        # decompressed data is available.
+        if size <= 0:
+            raise Exception('Non-positive size passed')
+        while True:
+            data = self._inflater.decompress(size)
+            if len(data) != 0:
+                return data
+
+            read_data = self._socket.recv(DeflateSocket._RECV_SIZE)
+            if not read_data:
+                return ''
+            self._inflater.append(read_data)
+
+    def sendall(self, bytes):
+        self.send(bytes)
+
+    def send(self, bytes):
+        self._socket.sendall(self._deflater.compress_and_flush(bytes))
+        return len(bytes)
+
+
+class DeflateConnection(object):
+    """A wrapper class for request object to intercept write and read to
+    perform deflate compression and decompression transparently.
+    """
+
+    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.
+        """
+
+        # TODO(tyoshino): Allow call with size=0.
+        if not (size == -1 or size > 0):
+            raise Exception('size must be -1 or positive')
+
+        data = ''
+        while True:
+            if size == -1:
+                data += self._inflater.decompress(-1)
+            else:
+                data += self._inflater.decompress(size - len(data))
+
+            if size >= 0 and len(data) != 0:
+                break
+
+            # TODO(tyoshino): Make this read efficient by some workaround.
+            #
+            # In 3.0.3 and prior of mod_python, read blocks until length bytes
+            # was read. We don't know the exact size to read while using
+            # deflate, so read byte-by-byte.
+            #
+            # _StandaloneRequest.read that ultimately performs
+            # socket._fileobject.read also blocks until length bytes was read
+            read_data = self._connection.read(1)
+            if not read_data:
+                break
+            self._inflater.append(read_data)
+        return data
+
+    def write(self, bytes):
+        self._connection.write(self._deflater.compress_and_flush(bytes))
+
+
+def _is_ewouldblock_errno(error_number):
+    """Returns True iff error_number indicates that receive operation would
+    block. To make this portable, we check availability of errno and then
+    compare them.
+    """
+
+    for error_name in ['WSAEWOULDBLOCK', 'EWOULDBLOCK', 'EAGAIN']:
+        if (error_name in dir(errno) and
+            error_number == getattr(errno, error_name)):
+            return True
+    return False
+
+
+def drain_received_data(raw_socket):
+    # Set the socket non-blocking.
+    original_timeout = raw_socket.gettimeout()
+    raw_socket.settimeout(0.0)
+
+    drained_data = []
+
+    # Drain until the socket is closed or no data is immediately
+    # available for read.
+    while True:
+        try:
+            data = raw_socket.recv(1)
+            if not data:
+                break
+            drained_data.append(data)
+        except socket.error, e:
+            # e can be either a pair (errno, string) or just a string (or
+            # something else) telling what went wrong. We suppress only
+            # the errors that indicates that the socket blocks. Those
+            # exceptions can be parsed as a pair (errno, string).
+            try:
+                error_number, message = e
+            except:
+                # Failed to parse socket.error.
+                raise e
+
+            if _is_ewouldblock_errno(error_number):
+                break
+            else:
+                raise e
+
+    # Rollback timeout value.
+    raw_socket.settimeout(original_timeout)
+
+    return ''.join(drained_data)
+
+
+# vi:sts=4 sw=4 et