summaryrefslogtreecommitdiffstats
path: root/WebKitTools/pywebsocket/mod_pywebsocket
diff options
context:
space:
mode:
Diffstat (limited to 'WebKitTools/pywebsocket/mod_pywebsocket')
-rw-r--r--WebKitTools/pywebsocket/mod_pywebsocket/dispatch.py17
-rw-r--r--WebKitTools/pywebsocket/mod_pywebsocket/handshake.py50
-rw-r--r--WebKitTools/pywebsocket/mod_pywebsocket/memorizingfile.py81
-rw-r--r--WebKitTools/pywebsocket/mod_pywebsocket/standalone.py61
4 files changed, 202 insertions, 7 deletions
diff --git a/WebKitTools/pywebsocket/mod_pywebsocket/dispatch.py b/WebKitTools/pywebsocket/mod_pywebsocket/dispatch.py
index bf9a856..c52e9eb 100644
--- a/WebKitTools/pywebsocket/mod_pywebsocket/dispatch.py
+++ b/WebKitTools/pywebsocket/mod_pywebsocket/dispatch.py
@@ -142,6 +142,23 @@ class Dispatcher(object):
'root_dir:%s.' % (scan_dir, root_dir))
self._source_files_in_dir(root_dir, scan_dir)
+ def add_resource_path_alias(self,
+ alias_resource_path, existing_resource_path):
+ """Add resource path alias.
+
+ Once added, request to alias_resource_path would be handled by
+ handler registered for existing_resource_path.
+
+ Args:
+ alias_resource_path: alias resource path
+ existing_resource_path: existing resource path
+ """
+ try:
+ handler = self._handlers[existing_resource_path]
+ self._handlers[alias_resource_path] = handler
+ except KeyError:
+ raise DispatchError('No handler for: %r' % existing_resource_path)
+
def source_warnings(self):
"""Return warnings in sourcing handlers."""
diff --git a/WebKitTools/pywebsocket/mod_pywebsocket/handshake.py b/WebKitTools/pywebsocket/mod_pywebsocket/handshake.py
index a67aadd..b86278e 100644
--- a/WebKitTools/pywebsocket/mod_pywebsocket/handshake.py
+++ b/WebKitTools/pywebsocket/mod_pywebsocket/handshake.py
@@ -39,14 +39,14 @@ not suitable because they don't allow direct raw bytes writing/reading.
import re
+import util
+
_DEFAULT_WEB_SOCKET_PORT = 80
_DEFAULT_WEB_SOCKET_SECURE_PORT = 443
_WEB_SOCKET_SCHEME = 'ws'
_WEB_SOCKET_SECURE_SCHEME = 'wss'
-_METHOD_LINE = re.compile(r'^GET ([^ ]+) HTTP/1.1\r\n$')
-
_MANDATORY_HEADERS = [
# key, expected value or None
['Upgrade', 'WebSocket'],
@@ -55,6 +55,22 @@ _MANDATORY_HEADERS = [
['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')
+
def _default_port(is_secure):
if is_secure:
@@ -75,19 +91,22 @@ def _validate_protocol(protocol):
if not protocol:
raise HandshakeError('Invalid WebSocket-Protocol: empty')
for c in protocol:
- if not 0x21 <= ord(c) <= 0x7e:
+ if not 0x20 <= ord(c) <= 0x7e:
raise HandshakeError('Illegal character in protocol: %r' % c)
class Handshaker(object):
"""This class performs Web Socket handshake."""
- def __init__(self, request, dispatcher):
+ 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.
@@ -95,6 +114,7 @@ class Handshaker(object):
self._request = request
self._dispatcher = dispatcher
+ self._strict = strict
def do_handshake(self):
"""Perform Web Socket Handshake."""
@@ -173,6 +193,28 @@ class Handshaker(object):
if actual_value != expected_value:
raise HandshakeError('Illegal value for header %s: %s' %
(key, actual_value))
+ if self._strict:
+ try:
+ lines = self._request.connection.get_memorized_lines()
+ except AttributeError, e:
+ util.prepend_message_to_exception(
+ 'Strict handshake is specified but the connection '
+ 'doesn\'t provide get_memorized_lines()', e)
+ raise
+ self._check_first_lines(lines)
+
+ def _check_first_lines(self, lines):
+ if len(lines) < len(_FIRST_FIVE_LINES):
+ raise HandshakeError('Too few header lines: %d' % len(lines))
+ for line, regexp in zip(lines, _FIRST_FIVE_LINES):
+ if not regexp.search(line):
+ raise HandshakeError('Unexpected header: %r doesn\'t match %r'
+ % (line, regexp.pattern))
+ sixth_and_later = ''.join(lines[5:])
+ if not _SIXTH_AND_LATER.search(sixth_and_later):
+ raise HandshakeError('Unexpected header: %r doesn\'t match %r'
+ % (sixth_and_later,
+ _SIXTH_AND_LATER.pattern))
# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/mod_pywebsocket/memorizingfile.py b/WebKitTools/pywebsocket/mod_pywebsocket/memorizingfile.py
new file mode 100644
index 0000000..2f8a54e
--- /dev/null
+++ b/WebKitTools/pywebsocket/mod_pywebsocket/memorizingfile.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+#
+# Copyright 2009, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Memorizing file.
+
+A memorizing file wraps a file and memorizes lines read by readline.
+"""
+
+
+import sys
+
+
+class MemorizingFile(object):
+ """MemorizingFile wraps a file and memorizes lines read by readline.
+
+ Note that data read by other methods are not memorized. This behavior
+ is good enough for memorizing lines SimpleHTTPServer reads before
+ the control reaches WebSocketRequestHandler.
+ """
+ def __init__(self, file_, max_memorized_lines=sys.maxint):
+ """Construct an instance.
+
+ Args:
+ file_: the file object to wrap.
+ max_memorized_lines: the maximum number of lines to memorize.
+ Only the first max_memorized_lines are memorized.
+ Default: sys.maxint.
+ """
+ self._file = file_
+ self._memorized_lines = []
+ self._max_memorized_lines = max_memorized_lines
+
+ def __getattribute__(self, name):
+ if name in ('_file', '_memorized_lines', '_max_memorized_lines',
+ 'readline', 'get_memorized_lines'):
+ return object.__getattribute__(self, name)
+ return self._file.__getattribute__(name)
+
+ def readline(self):
+ """Override file.readline and memorize the line read."""
+
+ line = self._file.readline()
+ if line and len(self._memorized_lines) < self._max_memorized_lines:
+ self._memorized_lines.append(line)
+ return line
+
+ def get_memorized_lines(self):
+ """Get lines memorized so far."""
+ return self._memorized_lines
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/mod_pywebsocket/standalone.py b/WebKitTools/pywebsocket/mod_pywebsocket/standalone.py
index 6217585..0e6a349 100644
--- a/WebKitTools/pywebsocket/mod_pywebsocket/standalone.py
+++ b/WebKitTools/pywebsocket/mod_pywebsocket/standalone.py
@@ -38,6 +38,7 @@ 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.
@@ -63,6 +64,7 @@ import logging
import logging.handlers
import optparse
import os
+import re
import socket
import sys
@@ -75,6 +77,7 @@ except ImportError:
import dispatch
import handshake
+import memorizingfile
import util
@@ -88,6 +91,10 @@ _LOG_LEVELS = {
_DEFAULT_LOG_MAX_BYTES = 1024 * 256
_DEFAULT_LOG_BACKUP_COUNT = 5
+_DEFAULT_REQUEST_QUEUE_SIZE = 128
+
+# 1024 is practically large enough to contain WebSocket handshake lines.
+_MAX_MEMORIZED_LINES = 1024
def _print_warnings_if_any(dispatcher):
warnings = dispatcher.source_warnings()
@@ -129,6 +136,10 @@ class _StandaloneConnection(object):
"""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."""
@@ -198,7 +209,9 @@ class WebSocketRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
"""Override SocketServer.StreamRequestHandler.setup."""
self.connection = self.request
- self.rfile = socket._fileobject(self.request, 'rb', self.rbufsize)
+ self.rfile = memorizingfile.MemorizingFile(
+ socket._fileobject(self.request, 'rb', self.rbufsize),
+ max_memorized_lines=_MAX_MEMORIZED_LINES)
self.wfile = socket._fileobject(self.request, 'wb', self.wbufsize)
def __init__(self, *args, **keywords):
@@ -206,8 +219,9 @@ class WebSocketRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
self, WebSocketRequestHandler.options.use_tls)
self._dispatcher = WebSocketRequestHandler.options.dispatcher
self._print_warnings_if_any()
- self._handshaker = handshake.Handshaker(self._request,
- self._dispatcher)
+ self._handshaker = handshake.Handshaker(
+ self._request, self._dispatcher,
+ WebSocketRequestHandler.options.strict)
SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(
self, *args, **keywords)
@@ -268,6 +282,31 @@ def _configure_logging(options):
handler.setFormatter(formatter)
logger.addHandler(handler)
+def _alias_handlers(dispatcher, websock_handlers_map_file):
+ """Set aliases specified in websock_handler_map_file in dispatcher.
+
+ Args:
+ dispatcher: dispatch.Dispatcher instance
+ websock_handler_map_file: alias map file
+ """
+ fp = open(websock_handlers_map_file)
+ try:
+ for line in fp:
+ if line[0] == '#' or line.isspace():
+ continue
+ m = re.match('(\S+)\s+(\S+)', line)
+ if not m:
+ logging.warning('Wrong format in map file:' + line)
+ continue
+ try:
+ dispatcher.add_resource_path_alias(
+ m.group(1), m.group(2))
+ except dispatch.DispatchError, e:
+ logging.error(str(e))
+ finally:
+ fp.close()
+
+
def _main():
parser = optparse.OptionParser()
@@ -277,6 +316,12 @@ def _main():
parser.add_option('-w', '--websock_handlers', dest='websock_handlers',
default='.',
help='Web Socket handlers root directory.')
+ parser.add_option('-m', '--websock_handlers_map_file',
+ dest='websock_handlers_map_file',
+ default=None,
+ help=('Web Socket handlers map file. '
+ 'Each line consists of alias_resource_path and '
+ 'existing_resource_path, separated by spaces.'))
parser.add_option('-s', '--scan_dir', dest='scan_dir',
default=None,
help=('Web Socket handlers scan directory. '
@@ -302,12 +347,19 @@ def _main():
parser.add_option('--log_count', dest='log_count', type='int',
default=_DEFAULT_LOG_BACKUP_COUNT,
help='Log backup count')
+ parser.add_option('--strict', dest='strict', action='store_true',
+ default=False, help='Strictly check handshake request')
+ parser.add_option('-q', '--queue', dest='request_queue_size', type='int',
+ default=_DEFAULT_REQUEST_QUEUE_SIZE,
+ help='request queue size')
options = parser.parse_args()[0]
os.chdir(options.document_root)
_configure_logging(options)
+ SocketServer.TCPServer.request_queue_size = options.request_queue_size
+
if options.use_tls:
if not _HAS_OPEN_SSL:
logging.critical('To use TLS, install pyOpenSSL.')
@@ -325,6 +377,9 @@ def _main():
# instantiation. Dispatcher can be shared because it is thread-safe.
options.dispatcher = dispatch.Dispatcher(options.websock_handlers,
options.scan_dir)
+ if options.websock_handlers_map_file:
+ _alias_handlers(options.dispatcher,
+ options.websock_handlers_map_file)
_print_warnings_if_any(options.dispatcher)
WebSocketRequestHandler.options = options