Changeset 52296 in webkit


Ignore:
Timestamp:
Dec 17, 2009 6:38:01 PM (14 years ago)
Author:
ukai@chromium.org
Message:

2009-12-17 Yuzo Fujishima <yuzo@google.com>

Reviewed by Alexey Proskuryakov.

Update pywebsocket to 0.4.5 and make handshake checking stricter
https://bugs.webkit.org/show_bug.cgi?id=32249

  • Scripts/run-webkit-tests:
  • pywebsocket/mod_pywebsocket/handshake.py:
  • pywebsocket/mod_pywebsocket/memorizingfile.py: Added.
  • pywebsocket/mod_pywebsocket/standalone.py:
  • pywebsocket/setup.py:
  • pywebsocket/test/test_handshake.py:
  • pywebsocket/test/test_memorizingfile.py: Added.
Location:
trunk/WebKitTools
Files:
6 edited
2 copied

Legend:

Unmodified
Added
Removed
  • trunk/WebKitTools/ChangeLog

    r52295 r52296  
     12009-12-17  Yuzo Fujishima  <yuzo@google.com>
     2
     3        Reviewed by Alexey Proskuryakov.
     4
     5        Update pywebsocket to 0.4.5 and make handshake checking stricter
     6        https://bugs.webkit.org/show_bug.cgi?id=32249
     7
     8        * Scripts/run-webkit-tests:
     9        * pywebsocket/mod_pywebsocket/handshake.py:
     10        * pywebsocket/mod_pywebsocket/memorizingfile.py: Added.
     11        * pywebsocket/mod_pywebsocket/standalone.py:
     12        * pywebsocket/setup.py:
     13        * pywebsocket/test/test_handshake.py:
     14        * pywebsocket/test/test_memorizingfile.py: Added.
     15
    1162009-12-17  Eric Seidel  <eric@webkit.org>
    217
  • trunk/WebKitTools/Scripts/run-webkit-tests

    r51550 r52296  
    14561456        "-s", "$webSocketHandlerScanDir",
    14571457        "-l", "$logFile",
     1458        "--strict",
    14581459    );
    14591460    # wss is disabled until all platforms support pyOpenSSL.
  • trunk/WebKitTools/pywebsocket/mod_pywebsocket/handshake.py

    r49672 r52296  
    4040import re
    4141
     42import util
     43
    4244
    4345_DEFAULT_WEB_SOCKET_PORT = 80
     
    4547_WEB_SOCKET_SCHEME = 'ws'
    4648_WEB_SOCKET_SECURE_SCHEME = 'wss'
    47 
    48 _METHOD_LINE = re.compile(r'^GET ([^ ]+) HTTP/1.1\r\n$')
    4949
    5050_MANDATORY_HEADERS = [
     
    5656]
    5757
     58_FIRST_FIVE_LINES = map(re.compile, [
     59    r'^GET /[\S]+ HTTP/1.1\r\n$',
     60    r'^Upgrade: WebSocket\r\n$',
     61    r'^Connection: Upgrade\r\n$',
     62    r'^Host: [\S]+\r\n$',
     63    r'^Origin: [\S]+\r\n$',
     64])
     65
     66# FIXME: Cookie headers also being in restricted WebSocket syntax.
     67_SIXTH_AND_LATER = re.compile(
     68    r'^'
     69    r'(WebSocket-Protocol: [\x20-\x7e]+\r\n)?'
     70    r'([Cc][Oo][Oo][Kk][Ii][Ee]:[^\r]*\r\n)*'
     71    r'([Cc][Oo][Oo][Kk][Ii][Ee]2:[^\r]*\r\n)?'
     72    r'([Cc][Oo][Oo][Kk][Ii][Ee]:[^\r]*\r\n)*'
     73    r'\r\n')
     74
     75
    5876
    5977def _default_port(is_secure):
     
    7694        raise HandshakeError('Invalid WebSocket-Protocol: empty')
    7795    for c in protocol:
    78         if not 0x21 <= ord(c) <= 0x7e:
     96        if not 0x20 <= ord(c) <= 0x7e:
    7997            raise HandshakeError('Illegal character in protocol: %r' % c)
    8098
     
    83101    """This class performs Web Socket handshake."""
    84102
    85     def __init__(self, request, dispatcher):
     103    def __init__(self, request, dispatcher, strict=False):
    86104        """Construct an instance.
    87105
     
    89107            request: mod_python request.
    90108            dispatcher: Dispatcher (dispatch.Dispatcher).
     109            strict: Strictly check handshake request. Default: False.
     110                If True, request.connection must provide get_memorized_lines
     111                method.
    91112
    92113        Handshaker will add attributes such as ws_resource in performing
     
    96117        self._request = request
    97118        self._dispatcher = dispatcher
     119        self._strict = strict
    98120
    99121    def do_handshake(self):
     
    174196                    raise HandshakeError('Illegal value for header %s: %s' %
    175197                                         (key, actual_value))
     198        if self._strict:
     199            try:
     200                lines = self._request.connection.get_memorized_lines()
     201            except AttributeError, e:
     202                util.prepend_message_to_exception(
     203                    'Strict handshake is specified but the connection '
     204                    'doesn\'t provide get_memorized_lines()', e)
     205                raise
     206            self._check_first_lines(lines)
     207
     208    def _check_first_lines(self, lines):
     209        if len(lines) < len(_FIRST_FIVE_LINES):
     210            raise HandshakeError('Too few header lines: %d' % len(lines))
     211        for line, regexp in zip(lines, _FIRST_FIVE_LINES):
     212            if not regexp.search(line):
     213                raise HandshakeError('Unexpected header: %r doesn\'t match %r'
     214                                     % (line, regexp.pattern))
     215        sixth_and_later = ''.join(lines[5:])
     216        if not _SIXTH_AND_LATER.search(sixth_and_later):
     217            raise HandshakeError('Unexpected header: %r doesn\'t match %r'
     218                                 % (sixth_and_later,
     219                                    _SIXTH_AND_LATER.pattern))
    176220
    177221
  • trunk/WebKitTools/pywebsocket/mod_pywebsocket/memorizingfile.py

    r52295 r52296  
    3131
    3232
    33 """Set up script for mod_pywebsocket.
     33"""Memorizing file.
     34
     35A memorizing file wraps a file and memorizes lines read by readline.
    3436"""
    3537
    3638
    37 from distutils.core import setup
    3839import sys
    3940
    4041
    41 _PACKAGE_NAME = 'mod_pywebsocket'
     42class MemorizingFile(object):
     43    """MemorizingFile wraps a file and memorizes lines read by readline.
    4244
    43 if sys.version < '2.3':
    44     print >>sys.stderr, '%s requires Python 2.3 or later.' % _PACKAGE_NAME
    45     sys.exit(1)
     45    Note that data read by other methods are not memorized. This behavior
     46    is good enough for memorizing lines SimpleHTTPServer reads before
     47    the control reaches WebSocketRequestHandler.
     48    """
     49    def __init__(self, file_, max_memorized_lines=sys.maxint):
     50        """Construct an instance.
    4651
    47 setup(author='Yuzo Fujishima',
    48       author_email='yuzo@chromium.org',
    49       description='Web Socket extension for Apache HTTP Server.',
    50       long_description=(
    51               'mod_pywebsocket is an Apache HTTP Server extension for '
    52               'Web Socket (http://tools.ietf.org/html/'
    53               'draft-hixie-thewebsocketprotocol). '
    54               'See mod_pywebsocket/__init__.py for more detail.'),
    55       license='See COPYING',
    56       name=_PACKAGE_NAME,
    57       packages=[_PACKAGE_NAME],
    58       url='http://code.google.com/p/pywebsocket/',
    59       version='0.4.3',
    60       )
     52        Args:
     53            file_: the file object to wrap.
     54            max_memorized_lines: the maximum number of lines to memorize.
     55                Only the first max_memorized_lines are memorized.
     56                Default: sys.maxint.
     57        """
     58        self._file = file_
     59        self._memorized_lines = []
     60        self._max_memorized_lines = max_memorized_lines
     61
     62    def __getattribute__(self, name):
     63        if name in ('_file', '_memorized_lines', '_max_memorized_lines',
     64                    'readline', 'get_memorized_lines'):
     65            return object.__getattribute__(self, name)
     66        return self._file.__getattribute__(name)
     67
     68    def readline(self):
     69        """Override file.readline and memorize the line read."""
     70
     71        line = self._file.readline()
     72        if line and len(self._memorized_lines) < self._max_memorized_lines:
     73            self._memorized_lines.append(line)
     74        return line
     75
     76    def get_memorized_lines(self):
     77        """Get lines memorized so far."""
     78        return self._memorized_lines
    6179
    6280
  • trunk/WebKitTools/pywebsocket/mod_pywebsocket/standalone.py

    r51661 r52296  
    7676import dispatch
    7777import handshake
     78import memorizingfile
    7879import util
    7980
     
    8990_DEFAULT_LOG_BACKUP_COUNT = 5
    9091
     92# 1024 is practically large enough to contain WebSocket handshake lines.
     93_MAX_MEMORIZED_LINES = 1024
    9194
    9295def _print_warnings_if_any(dispatcher):
     
    129132        """Mimic mp_conn.read()."""
    130133        return self._request_handler.rfile.read(length)
     134
     135    def get_memorized_lines(self):
     136        """Get memorized lines."""
     137        return self._request_handler.rfile.get_memorized_lines()
    131138
    132139
     
    199206
    200207        self.connection = self.request
    201         self.rfile = socket._fileobject(self.request, 'rb', self.rbufsize)
     208        self.rfile = memorizingfile.MemorizingFile(
     209                socket._fileobject(self.request, 'rb', self.rbufsize),
     210                max_memorized_lines=_MAX_MEMORIZED_LINES)
    202211        self.wfile = socket._fileobject(self.request, 'wb', self.wbufsize)
    203212
     
    207216        self._dispatcher = WebSocketRequestHandler.options.dispatcher
    208217        self._print_warnings_if_any()
    209         self._handshaker = handshake.Handshaker(self._request,
    210                                                 self._dispatcher)
     218        self._handshaker = handshake.Handshaker(
     219                self._request, self._dispatcher,
     220                WebSocketRequestHandler.options.strict)
    211221        SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(
    212222                self, *args, **keywords)
     
    303313                      default=_DEFAULT_LOG_BACKUP_COUNT,
    304314                      help='Log backup count')
     315    parser.add_option('--strict', dest='strict', action='store_true',
     316                      default=False, help='Strictly check handshake request')
    305317    options = parser.parse_args()[0]
    306318
  • trunk/WebKitTools/pywebsocket/setup.py

    r51661 r52296  
    5757      packages=[_PACKAGE_NAME],
    5858      url='http://code.google.com/p/pywebsocket/',
    59       version='0.4.3',
     59      version='0.4.5',
    6060      )
    6161
  • trunk/WebKitTools/pywebsocket/test/test_handshake.py

    r49672 r52296  
    215215            'Host':'example.com',
    216216            'Origin':'http://example.com',
    217             'WebSocket-Protocol':'illegal protocol',
    218         }
     217            'WebSocket-Protocol':'illegal\x09protocol',
     218        }
     219    ),
     220)
     221
     222_STRICTLY_GOOD_REQUESTS = (
     223    (
     224        'GET /demo HTTP/1.1\r\n',
     225        'Upgrade: WebSocket\r\n',
     226        'Connection: Upgrade\r\n',
     227        'Host: example.com\r\n',
     228        'Origin: http://example.com\r\n',
     229        '\r\n',
     230    ),
     231    (  # WebSocket-Protocol
     232        'GET /demo HTTP/1.1\r\n',
     233        'Upgrade: WebSocket\r\n',
     234        'Connection: Upgrade\r\n',
     235        'Host: example.com\r\n',
     236        'Origin: http://example.com\r\n',
     237        'WebSocket-Protocol: sample\r\n',
     238        '\r\n',
     239    ),
     240    (  # WebSocket-Protocol and Cookie
     241        'GET /demo HTTP/1.1\r\n',
     242        'Upgrade: WebSocket\r\n',
     243        'Connection: Upgrade\r\n',
     244        'Host: example.com\r\n',
     245        'Origin: http://example.com\r\n',
     246        'WebSocket-Protocol: sample\r\n',
     247        'Cookie: xyz\r\n'
     248        '\r\n',
     249    ),
     250    (  # Cookie
     251        'GET /demo HTTP/1.1\r\n',
     252        'Upgrade: WebSocket\r\n',
     253        'Connection: Upgrade\r\n',
     254        'Host: example.com\r\n',
     255        'Origin: http://example.com\r\n',
     256        'Cookie: abc/xyz\r\n'
     257        'Cookie2: $Version=1\r\n'
     258        'Cookie: abc\r\n'
     259        '\r\n',
     260    ),
     261)
     262
     263_NOT_STRICTLY_GOOD_REQUESTS = (
     264    (  # Extra space after GET
     265        'GET  /demo HTTP/1.1\r\n',
     266        'Upgrade: WebSocket\r\n',
     267        'Connection: Upgrade\r\n',
     268        'Host: example.com\r\n',
     269        'Origin: http://example.com\r\n',
     270        '\r\n',
     271    ),
     272    (  # Resource name doesn't stat with '/'
     273        'GET demo HTTP/1.1\r\n',
     274        'Upgrade: WebSocket\r\n',
     275        'Connection: Upgrade\r\n',
     276        'Host: example.com\r\n',
     277        'Origin: http://example.com\r\n',
     278        '\r\n',
     279    ),
     280    (  # No space after :
     281        'GET /demo HTTP/1.1\r\n',
     282        'Upgrade:WebSocket\r\n',
     283        'Connection: Upgrade\r\n',
     284        'Host: example.com\r\n',
     285        'Origin: http://example.com\r\n',
     286        '\r\n',
     287    ),
     288    (  # Lower case Upgrade header
     289        'GET /demo HTTP/1.1\r\n',
     290        'upgrade: WebSocket\r\n',
     291        'Connection: Upgrade\r\n',
     292        'Host: example.com\r\n',
     293        'Origin: http://example.com\r\n',
     294        '\r\n',
     295    ),
     296    (  # Connection comes before Upgrade
     297        'GET /demo HTTP/1.1\r\n',
     298        'Connection: Upgrade\r\n',
     299        'Upgrade: WebSocket\r\n',
     300        'Host: example.com\r\n',
     301        'Origin: http://example.com\r\n',
     302        '\r\n',
     303    ),
     304    (  # Origin comes before Host
     305        'GET /demo HTTP/1.1\r\n',
     306        'Upgrade: WebSocket\r\n',
     307        'Connection: Upgrade\r\n',
     308        'Origin: http://example.com\r\n',
     309        'Host: example.com\r\n',
     310        '\r\n',
     311    ),
     312    (  # Host continued to the next line
     313        'GET /demo HTTP/1.1\r\n',
     314        'Upgrade: WebSocket\r\n',
     315        'Connection: Upgrade\r\n',
     316        'Host: example\r\n',
     317        ' .com\r\n',
     318        'Origin: http://example.com\r\n',
     319        '\r\n',
     320    ),
     321    ( # Cookie comes before WebSocket-Protocol
     322        'GET /demo HTTP/1.1\r\n',
     323        'Upgrade: WebSocket\r\n',
     324        'Connection: Upgrade\r\n',
     325        'Host: example.com\r\n',
     326        'Origin: http://example.com\r\n',
     327        'Cookie: xyz\r\n'
     328        'WebSocket-Protocol: sample\r\n',
     329        '\r\n',
     330    ),
     331    (  # Unknown header
     332        'GET /demo HTTP/1.1\r\n',
     333        'Upgrade: WebSocket\r\n',
     334        'Connection: Upgrade\r\n',
     335        'Host: example.com\r\n',
     336        'Origin: http://example.com\r\n',
     337        'Content-Type: text/html\r\n'
     338        '\r\n',
     339    ),
     340    (  # Cookie with continuation lines
     341        'GET /demo HTTP/1.1\r\n',
     342        'Upgrade: WebSocket\r\n',
     343        'Connection: Upgrade\r\n',
     344        'Host: example.com\r\n',
     345        'Origin: http://example.com\r\n',
     346        'Cookie: xyz\r\n',
     347        ' abc\r\n',
     348        ' defg\r\n',
     349        '\r\n',
    219350    ),
    220351)
     
    230361
    231362
     363def _create_get_memorized_lines(lines):
     364    def get_memorized_lines():
     365        return lines
     366    return get_memorized_lines
     367
     368
     369def _create_requests_with_lines(request_lines_set):
     370    requests = []
     371    for lines in request_lines_set:
     372        request = _create_request(_GOOD_REQUEST)
     373        request.connection.get_memorized_lines = _create_get_memorized_lines(
     374                lines)
     375        requests.append(request)
     376    return requests
     377
     378
    232379class HandshakerTest(unittest.TestCase):
    233380    def test_validate_protocol(self):
    234381        handshake._validate_protocol('sample')  # should succeed.
    235382        handshake._validate_protocol('Sample')  # should succeed.
     383        handshake._validate_protocol('sample\x20protocol')  # should succeed.
     384        handshake._validate_protocol('sample\x7eprotocol')  # should succeed.
    236385        self.assertRaises(handshake.HandshakeError,
    237386                          handshake._validate_protocol,
    238                           'sample protocol')
     387                          '')
     388        self.assertRaises(handshake.HandshakeError,
     389                          handshake._validate_protocol,
     390                          'sample\x19protocol')
     391        self.assertRaises(handshake.HandshakeError,
     392                          handshake._validate_protocol,
     393                          'sample\x7fprotocol')
    239394        self.assertRaises(handshake.HandshakeError,
    240395                          handshake._validate_protocol,
     
    309464            self.assertRaises(handshake.HandshakeError, handshaker.do_handshake)
    310465
     466    def test_strictly_good_requests(self):
     467        for request in _create_requests_with_lines(_STRICTLY_GOOD_REQUESTS):
     468            strict_handshaker = handshake.Handshaker(request,
     469                                                     mock.MockDispatcher(),
     470                                                     True)
     471            strict_handshaker.do_handshake()
     472
     473    def test_not_strictly_good_requests(self):
     474        for request in _create_requests_with_lines(_NOT_STRICTLY_GOOD_REQUESTS):
     475            strict_handshaker = handshake.Handshaker(request,
     476                                                     mock.MockDispatcher(),
     477                                                     True)
     478            self.assertRaises(handshake.HandshakeError,
     479                              strict_handshaker.do_handshake)
     480
     481
    311482
    312483if __name__ == '__main__':
  • trunk/WebKitTools/pywebsocket/test/test_memorizingfile.py

    r52295 r52296  
    3131
    3232
    33 """Set up script for mod_pywebsocket.
    34 """
     33"""Tests for memorizingfile module."""
    3534
    3635
    37 from distutils.core import setup
    38 import sys
     36import StringIO
     37import unittest
     38
     39import config  # This must be imported before mod_pywebsocket.
     40from mod_pywebsocket import memorizingfile
    3941
    4042
    41 _PACKAGE_NAME = 'mod_pywebsocket'
     43class UtilTest(unittest.TestCase):
     44    def check(self, memorizing_file, num_read, expected_list):
     45        for unused in range(num_read):
     46            memorizing_file.readline()
     47        actual_list = memorizing_file.get_memorized_lines()
     48        self.assertEqual(len(expected_list), len(actual_list))
     49        for expected, actual in zip(expected_list, actual_list):
     50            self.assertEqual(expected, actual)
    4251
    43 if sys.version < '2.3':
    44     print >>sys.stderr, '%s requires Python 2.3 or later.' % _PACKAGE_NAME
    45     sys.exit(1)
     52    def test_get_memorized_lines(self):
     53        memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO(
     54                'Hello\nWorld\nWelcome'))
     55        self.check(memorizing_file, 3, ['Hello\n', 'World\n', 'Welcome'])
    4656
    47 setup(author='Yuzo Fujishima',
    48       author_email='yuzo@chromium.org',
    49       description='Web Socket extension for Apache HTTP Server.',
    50       long_description=(
    51               'mod_pywebsocket is an Apache HTTP Server extension for '
    52               'Web Socket (http://tools.ietf.org/html/'
    53               'draft-hixie-thewebsocketprotocol). '
    54               'See mod_pywebsocket/__init__.py for more detail.'),
    55       license='See COPYING',
    56       name=_PACKAGE_NAME,
    57       packages=[_PACKAGE_NAME],
    58       url='http://code.google.com/p/pywebsocket/',
    59       version='0.4.3',
    60       )
     57    def test_get_memorized_lines_limit_memorized_lines(self):
     58        memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO(
     59                'Hello\nWorld\nWelcome'), 2)
     60        self.check(memorizing_file, 3, ['Hello\n', 'World\n'])
     61
     62    def test_get_memorized_lines_empty_file(self):
     63        memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO(
     64                ''))
     65        self.check(memorizing_file, 10, [])
     66
     67
     68if __name__ == '__main__':
     69    unittest.main()
    6170
    6271
Note: See TracChangeset for help on using the changeset viewer.