mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 752776 - Upgrade pywebsocket to v631. r=mcmanus
This commit is contained in:
parent
000ce5b578
commit
7898e1b3f2
@ -20,7 +20,7 @@ STEPS TO UPDATE MOZILLA TO NEWER PYWEBSOCKET VERSION
|
||||
- rsync new version into our tree, deleting files that aren't needed any more
|
||||
(NOTE: this will blow away this file! hg revert it or keep a copy.)
|
||||
|
||||
rsync -r --delete dist/ $MOZ_SRC/testing/mochitest/pywebsocket
|
||||
rsync -rv --delete dist/ $MOZ_SRC/testing/mochitest/pywebsocket
|
||||
|
||||
- Get rid of examples/test directory and some cruft:
|
||||
|
||||
|
@ -28,8 +28,11 @@
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""Stream of WebSocket protocol with the framing used by IETF HyBi 00 and
|
||||
Hixie 75. For Hixie 75 this stream doesn't perform closing handshake.
|
||||
"""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
|
||||
"""
|
||||
|
||||
|
||||
@ -43,7 +46,9 @@ from mod_pywebsocket import util
|
||||
|
||||
|
||||
class StreamHixie75(StreamBase):
|
||||
"""Stream of WebSocket messages."""
|
||||
"""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.
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright 2011, Google Inc.
|
||||
# Copyright 2012, Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
@ -28,7 +28,11 @@
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""Stream class for IETF HyBi latest WebSocket protocol.
|
||||
"""This file provides classes and helper functions for parsing/building frames
|
||||
of the WebSocket protocol (RFC 6455).
|
||||
|
||||
Specification:
|
||||
http://tools.ietf.org/html/rfc6455
|
||||
"""
|
||||
|
||||
|
||||
@ -238,7 +242,9 @@ class StreamOptions(object):
|
||||
|
||||
|
||||
class Stream(StreamBase):
|
||||
"""Stream of WebSocket messages."""
|
||||
"""A class for parsing/building frames of the WebSocket protocol
|
||||
(RFC 6455).
|
||||
"""
|
||||
|
||||
def __init__(self, request, options):
|
||||
"""Constructs an instance.
|
||||
@ -353,8 +359,8 @@ class Stream(StreamBase):
|
||||
|
||||
Raises:
|
||||
BadOperationException: when called on a server-terminated
|
||||
connection or called with inconsistent message type or binary
|
||||
parameter.
|
||||
connection or called with inconsistent message type or
|
||||
binary parameter.
|
||||
"""
|
||||
|
||||
if self._request.server_terminated:
|
||||
@ -482,7 +488,11 @@ class Stream(StreamBase):
|
||||
# - 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:
|
||||
if len(message) == 0:
|
||||
self._logger.debug('Received close frame (empty body)')
|
||||
self._request.ws_close_code = (
|
||||
common.STATUS_NO_STATUS_RECEIVED)
|
||||
elif len(message) == 1:
|
||||
raise InvalidFrameException(
|
||||
'If a close frame has status code, the length of '
|
||||
'status code must be 2 octet')
|
||||
@ -501,22 +511,28 @@ class Stream(StreamBase):
|
||||
|
||||
if self._request.server_terminated:
|
||||
self._logger.debug(
|
||||
'Received ack for server-initiated closing '
|
||||
'handshake')
|
||||
'Received ack for server-initiated closing handshake')
|
||||
return None
|
||||
|
||||
self._logger.debug(
|
||||
'Received client-initiated closing handshake')
|
||||
|
||||
code = common.STATUS_NORMAL
|
||||
code = common.STATUS_NORMAL_CLOSURE
|
||||
reason = ''
|
||||
if hasattr(self._request, '_dispatcher'):
|
||||
dispatcher = self._request._dispatcher
|
||||
code, reason = dispatcher.passive_closing_handshake(
|
||||
self._request)
|
||||
if code is None and reason is not None and len(reason) > 0:
|
||||
self._logger.warning(
|
||||
'Handler specified reason despite code being None')
|
||||
reason = ''
|
||||
if reason is None:
|
||||
reason = ''
|
||||
self._send_closing_handshake(code, reason)
|
||||
self._logger.debug(
|
||||
'Sent ack for client-initiated closing handshake')
|
||||
'Sent ack for client-initiated closing handshake '
|
||||
'(code=%r, reason=%r)', code, reason)
|
||||
return None
|
||||
elif self._original_opcode == common.OPCODE_PING:
|
||||
try:
|
||||
@ -565,17 +581,19 @@ class Stream(StreamBase):
|
||||
'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')
|
||||
body = ''
|
||||
if code is not None:
|
||||
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')
|
||||
body = struct.pack('!H', code) + encoded_reason
|
||||
|
||||
frame = create_close_frame(
|
||||
struct.pack('!H', code) + encoded_reason,
|
||||
body,
|
||||
self._options.mask_send,
|
||||
self._options.outgoing_frame_filters)
|
||||
|
||||
@ -583,16 +601,37 @@ class Stream(StreamBase):
|
||||
|
||||
self._write(frame)
|
||||
|
||||
def close_connection(self, code=common.STATUS_NORMAL, reason=''):
|
||||
"""Closes a WebSocket connection."""
|
||||
def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''):
|
||||
"""Closes a WebSocket connection.
|
||||
|
||||
Args:
|
||||
code: Status code for close frame. If code is None, a close
|
||||
frame with empty body will be sent.
|
||||
reason: string representing close reason.
|
||||
Raises:
|
||||
BadOperationException: when reason is specified with code None
|
||||
or reason is not an instance of both str and unicode.
|
||||
"""
|
||||
|
||||
if self._request.server_terminated:
|
||||
self._logger.debug(
|
||||
'Requested close_connection but server is already terminated')
|
||||
return
|
||||
|
||||
if code is None:
|
||||
if reason is not None and len(reason) > 0:
|
||||
raise BadOperationException(
|
||||
'close reason must not be specified if code is None')
|
||||
reason = ''
|
||||
else:
|
||||
if not isinstance(reason, str) and not isinstance(reason, unicode):
|
||||
raise BadOperationException(
|
||||
'close reason must be an instance of str or unicode')
|
||||
|
||||
self._send_closing_handshake(code, reason)
|
||||
self._logger.debug('Sent server-initiated closing handshake')
|
||||
self._logger.debug(
|
||||
'Sent server-initiated closing handshake (code=%r, reason=%r)',
|
||||
code, reason)
|
||||
|
||||
if (code == common.STATUS_GOING_AWAY or
|
||||
code == common.STATUS_PROTOCOL_ERROR):
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright 2011, Google Inc.
|
||||
# Copyright 2012, Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
@ -93,21 +93,41 @@ 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_CODE_NOT_AVAILABLE should not be used in actual frames. This code
|
||||
# is exposed to JavaScript API as pseudo status code which represent actual
|
||||
# frame does not have status code.
|
||||
STATUS_NORMAL = 1000
|
||||
# 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 = 1003
|
||||
STATUS_CODE_NOT_AVAILABLE = 1005
|
||||
STATUS_ABNORMAL_CLOSE = 1006
|
||||
STATUS_INVALID_FRAME_PAYLOAD = 1007
|
||||
STATUS_UNSUPPORTED_DATA = 1003
|
||||
STATUS_NO_STATUS_RECEIVED = 1005
|
||||
STATUS_ABNORMAL_CLOSURE = 1006
|
||||
STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007
|
||||
STATUS_POLICY_VIOLATION = 1008
|
||||
STATUS_MESSAGE_TOO_BIG = 1009
|
||||
STATUS_MANDATORY_EXT = 1010
|
||||
STATUS_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
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright 2011, Google Inc.
|
||||
# Copyright 2012, Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
@ -62,7 +62,7 @@ class DispatchException(Exception):
|
||||
def _default_passive_closing_handshake_handler(request):
|
||||
"""Default web_socket_passive_closing_handshake handler."""
|
||||
|
||||
return common.STATUS_NORMAL, ''
|
||||
return common.STATUS_NORMAL_CLOSURE, ''
|
||||
|
||||
|
||||
def _normalize_path(path):
|
||||
@ -292,7 +292,7 @@ class Dispatcher(object):
|
||||
raise
|
||||
except msgutil.BadOperationException, e:
|
||||
self._logger.debug('%s', e)
|
||||
request.ws_stream.close_connection(common.STATUS_ABNORMAL_CLOSE)
|
||||
request.ws_stream.close_connection(common.STATUS_ABNORMAL_CLOSURE)
|
||||
except msgutil.InvalidFrameException, e:
|
||||
# InvalidFrameException must be caught before
|
||||
# ConnectionTerminatedException that catches InvalidFrameException.
|
||||
@ -300,11 +300,11 @@ class Dispatcher(object):
|
||||
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)
|
||||
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)
|
||||
common.STATUS_INVALID_FRAME_PAYLOAD_DATA)
|
||||
except msgutil.ConnectionTerminatedException, e:
|
||||
self._logger.debug('%s', e)
|
||||
except Exception, e:
|
||||
|
@ -72,7 +72,7 @@ _available_processors[common.DEFLATE_STREAM_EXTENSION] = (
|
||||
class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
|
||||
"""WebSocket Per-frame DEFLATE extension processor."""
|
||||
|
||||
_WINDOW_BITS_PARAM = 'window_bits'
|
||||
_WINDOW_BITS_PARAM = 'max_window_bits'
|
||||
_NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover'
|
||||
|
||||
def __init__(self, request):
|
||||
@ -83,6 +83,18 @@ class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
|
||||
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.
|
||||
|
||||
@ -110,7 +122,7 @@ class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
|
||||
|
||||
self._compress_outgoing = True
|
||||
|
||||
response = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
|
||||
response = common.ExtensionParameter(self._request.name())
|
||||
|
||||
if self._response_window_bits is not None:
|
||||
response.add_parameter(
|
||||
@ -123,7 +135,7 @@ class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
|
||||
'Enable %s extension ('
|
||||
'request: window_bits=%s; no_context_takeover=%r, '
|
||||
'response: window_wbits=%s; no_context_takeover=%r)' %
|
||||
(common.DEFLATE_STREAM_EXTENSION,
|
||||
(self._request.name(),
|
||||
window_bits,
|
||||
no_context_takeover,
|
||||
self._response_window_bits,
|
||||
@ -171,29 +183,77 @@ class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
|
||||
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())
|
||||
|
@ -83,15 +83,15 @@ def do_handshake(request, dispatcher, allowDraft75=False, strict=False):
|
||||
|
||||
handshakers = []
|
||||
handshakers.append(
|
||||
('IETF HyBi latest', hybi.Handshaker(request, dispatcher)))
|
||||
('RFC 6455', hybi.Handshaker(request, dispatcher)))
|
||||
handshakers.append(
|
||||
('IETF HyBi 00', hybi00.Handshaker(request, dispatcher)))
|
||||
('HyBi 00', hybi00.Handshaker(request, dispatcher)))
|
||||
if allowDraft75:
|
||||
handshakers.append(
|
||||
('IETF Hixie 75', draft75.Handshaker(request, dispatcher, strict)))
|
||||
('Hixie 75', draft75.Handshaker(request, dispatcher, strict)))
|
||||
|
||||
for name, handshaker in handshakers:
|
||||
_LOGGER.debug('Trying %s protocol', name)
|
||||
_LOGGER.debug('Trying protocol version %s', name)
|
||||
try:
|
||||
handshaker.do_handshake()
|
||||
_LOGGER.info('Established (%s protocol)', name)
|
||||
|
@ -89,7 +89,7 @@ def validate_subprotocol(subprotocol, hixie):
|
||||
Sec-WebSocket-Protocol.
|
||||
|
||||
See
|
||||
- HyBi 10: Section 5.1. and 5.2.2.
|
||||
- RFC 6455: Section 4.1., 4.2.2., and 4.3.
|
||||
- HyBi 00: Section 4.1. Opening handshake
|
||||
- Hixie 75: Section 4.1. Handshake
|
||||
"""
|
||||
|
@ -28,7 +28,12 @@
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""WebSocket HyBi latest opening handshake processor."""
|
||||
"""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
|
||||
@ -59,7 +64,10 @@ from mod_pywebsocket.stream import StreamOptions
|
||||
from mod_pywebsocket import util
|
||||
|
||||
|
||||
_BASE64_REGEX = re.compile('^[+/0-9A-Za-z]*=*$')
|
||||
# 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
|
||||
@ -85,7 +93,7 @@ def compute_accept(key):
|
||||
|
||||
|
||||
class Handshaker(object):
|
||||
"""This class performs WebSocket handshake."""
|
||||
"""Opening handshake processor for the WebSocket protocol (RFC 6455)."""
|
||||
|
||||
def __init__(self, request, dispatcher):
|
||||
"""Construct an instance.
|
||||
@ -161,7 +169,7 @@ class Handshaker(object):
|
||||
accept,
|
||||
util.hexify(accept_binary))
|
||||
|
||||
self._logger.debug('IETF HyBi protocol')
|
||||
self._logger.debug('Protocol version is RFC 6455')
|
||||
|
||||
# Setup extension processors.
|
||||
|
||||
@ -259,10 +267,6 @@ class Handshaker(object):
|
||||
def _set_protocol(self):
|
||||
self._request.ws_protocol = None
|
||||
|
||||
# MOZILLA
|
||||
self._request.sts = None
|
||||
# /MOZILLA
|
||||
|
||||
protocol_header = self._request.headers_in.get(
|
||||
common.SEC_WEBSOCKET_PROTOCOL_HEADER)
|
||||
|
||||
@ -307,7 +311,7 @@ class Handshaker(object):
|
||||
# module. Because base64 module skips invalid characters, we have
|
||||
# to do this in advance to make this server strictly reject illegal
|
||||
# keys.
|
||||
if _BASE64_REGEX.match(key):
|
||||
if _SEC_WEBSOCKET_KEY_REGEX.match(key):
|
||||
decoded_key = base64.b64decode(key)
|
||||
if len(decoded_key) == 16:
|
||||
key_is_valid = True
|
||||
@ -355,11 +359,6 @@ class Handshaker(object):
|
||||
response.append(format_header(
|
||||
common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
|
||||
format_extensions(self._request.ws_extensions)))
|
||||
# MOZILLA: Add HSTS header if requested to
|
||||
if self._request.sts is not None:
|
||||
response.append(format_header("Strict-Transport-Security",
|
||||
self._request.sts))
|
||||
# /MOZILLA
|
||||
response.append('\r\n')
|
||||
|
||||
raw_response = ''.join(response)
|
||||
|
@ -28,7 +28,12 @@
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""WebSocket initial handshake hander for HyBi 00 protocol."""
|
||||
"""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
|
||||
@ -61,7 +66,8 @@ _MANDATORY_HEADERS = [
|
||||
|
||||
|
||||
class Handshaker(object):
|
||||
"""This class performs WebSocket handshake."""
|
||||
"""Opening handshake processor for the WebSocket protocol version HyBi 00.
|
||||
"""
|
||||
|
||||
def __init__(self, request, dispatcher):
|
||||
"""Construct an instance.
|
||||
@ -139,7 +145,7 @@ class Handshaker(object):
|
||||
(common.SEC_WEBSOCKET_DRAFT_HEADER,
|
||||
draft))
|
||||
|
||||
self._logger.debug('IETF HyBi 00 protocol')
|
||||
self._logger.debug('Protocol version is HyBi 00')
|
||||
self._request.ws_version = common.VERSION_HYBI00
|
||||
self._request.ws_stream = StreamHixie75(self._request, True)
|
||||
|
||||
|
@ -82,7 +82,15 @@ def receive_message(request):
|
||||
Args:
|
||||
request: mod_python request.
|
||||
Raises:
|
||||
BadOperationException: when client already terminated.
|
||||
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()
|
||||
|
||||
|
@ -177,9 +177,16 @@ class RepeatedXorMasker(object):
|
||||
def mask(self, s):
|
||||
result = array.array('B')
|
||||
result.fromstring(s)
|
||||
# Use temporary local variables to eliminate the cost to access
|
||||
# attributes
|
||||
count = self._count
|
||||
mask = self._mask
|
||||
mask_size = self._mask_size
|
||||
for i in xrange(len(result)):
|
||||
result[i] ^= self._mask[self._count]
|
||||
self._count = (self._count + 1) % self._mask_size
|
||||
result[i] ^= mask[count]
|
||||
count = (count + 1) % mask_size
|
||||
self._count = count
|
||||
|
||||
return result.tostring()
|
||||
|
||||
|
||||
|
@ -32,6 +32,8 @@
|
||||
|
||||
"""Standalone WebSocket server.
|
||||
|
||||
BASIC USAGE
|
||||
|
||||
Use this server to run mod_pywebsocket without Apache HTTP Server.
|
||||
|
||||
Usage:
|
||||
@ -52,11 +54,39 @@ handlers. If this path is relative, <document_root> is used as the base.
|
||||
<scan_dir> is a path under the root directory. If specified, only the
|
||||
handlers under scan_dir are scanned. This is useful in saving scan time.
|
||||
|
||||
Note:
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
"""
|
||||
@ -65,6 +95,7 @@ import BaseHTTPServer
|
||||
import CGIHTTPServer
|
||||
import SimpleHTTPServer
|
||||
import SocketServer
|
||||
import ConfigParser
|
||||
import httplib
|
||||
import logging
|
||||
import logging.handlers
|
||||
@ -77,13 +108,17 @@ import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
||||
_HAS_SSL = False
|
||||
_HAS_OPEN_SSL = False
|
||||
try:
|
||||
import OpenSSL.SSL
|
||||
_HAS_OPEN_SSL = True
|
||||
import ssl
|
||||
_HAS_SSL = True
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
import OpenSSL.SSL
|
||||
_HAS_OPEN_SSL = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from mod_pywebsocket import common
|
||||
from mod_pywebsocket import dispatch
|
||||
@ -193,6 +228,28 @@ class _StandaloneRequest(object):
|
||||
'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."""
|
||||
|
||||
@ -253,12 +310,18 @@ class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
|
||||
self._logger.info('Skip by failure: %r', e)
|
||||
continue
|
||||
if self.websocket_server_options.use_tls:
|
||||
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
|
||||
ctx.use_privatekey_file(
|
||||
self.websocket_server_options.private_key)
|
||||
ctx.use_certificate_file(
|
||||
self.websocket_server_options.certificate)
|
||||
socket_ = OpenSSL.SSL.Connection(ctx, socket_)
|
||||
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):
|
||||
@ -328,6 +391,18 @@ class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
|
||||
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."""
|
||||
|
||||
@ -413,7 +488,9 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
|
||||
# it needs variables set by CGIHTTPRequestHandler.parse_request.
|
||||
#
|
||||
# Variables set by this method will be also used by WebSocket request
|
||||
# handling. See _StandaloneRequest.get_request, etc.
|
||||
# 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)
|
||||
@ -567,6 +644,11 @@ def _alias_handlers(dispatcher, websock_handlers_map_file):
|
||||
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='',
|
||||
@ -676,14 +758,46 @@ class ThreadMonitor(threading.Thread):
|
||||
time.sleep(self._interval_in_sec)
|
||||
|
||||
|
||||
def _main(args=None):
|
||||
def _parse_args_and_config(args):
|
||||
parser = _build_option_parser()
|
||||
|
||||
options, args = parser.parse_args(args=args)
|
||||
if args:
|
||||
logging.critical('Unrecognized positional arguments: %r', args)
|
||||
# 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)
|
||||
@ -710,8 +824,8 @@ def _main(args=None):
|
||||
options.is_executable_method = __check_script
|
||||
|
||||
if options.use_tls:
|
||||
if not _HAS_OPEN_SSL:
|
||||
logging.critical('To use TLS, install pyOpenSSL.')
|
||||
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(
|
||||
@ -750,7 +864,7 @@ def _main(args=None):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
_main()
|
||||
_main(sys.argv[1:])
|
||||
|
||||
|
||||
# vi:sts=4 sw=4 et
|
||||
|
Loading…
Reference in New Issue
Block a user