mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
229 lines
8.6 KiB
Python
229 lines
8.6 KiB
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.
|
|
|
|
|
|
"""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
|