testing/mochitest/pywebsocket/mod_pywebsocket/util.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/util.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,496 @@
     1.4 +# Copyright 2011, Google Inc.
     1.5 +# All rights reserved.
     1.6 +#
     1.7 +# Redistribution and use in source and binary forms, with or without
     1.8 +# modification, are permitted provided that the following conditions are
     1.9 +# met:
    1.10 +#
    1.11 +#     * Redistributions of source code must retain the above copyright
    1.12 +# notice, this list of conditions and the following disclaimer.
    1.13 +#     * Redistributions in binary form must reproduce the above
    1.14 +# copyright notice, this list of conditions and the following disclaimer
    1.15 +# in the documentation and/or other materials provided with the
    1.16 +# distribution.
    1.17 +#     * Neither the name of Google Inc. nor the names of its
    1.18 +# contributors may be used to endorse or promote products derived from
    1.19 +# this software without specific prior written permission.
    1.20 +#
    1.21 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    1.22 +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    1.23 +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    1.24 +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    1.25 +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    1.26 +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    1.27 +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    1.28 +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    1.29 +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    1.30 +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    1.31 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    1.32 +
    1.33 +
    1.34 +"""WebSocket utilities.
    1.35 +"""
    1.36 +
    1.37 +
    1.38 +import array
    1.39 +import errno
    1.40 +
    1.41 +# Import hash classes from a module available and recommended for each Python
    1.42 +# version and re-export those symbol. Use sha and md5 module in Python 2.4, and
    1.43 +# hashlib module in Python 2.6.
    1.44 +try:
    1.45 +    import hashlib
    1.46 +    md5_hash = hashlib.md5
    1.47 +    sha1_hash = hashlib.sha1
    1.48 +except ImportError:
    1.49 +    import md5
    1.50 +    import sha
    1.51 +    md5_hash = md5.md5
    1.52 +    sha1_hash = sha.sha
    1.53 +
    1.54 +import StringIO
    1.55 +import logging
    1.56 +import os
    1.57 +import re
    1.58 +import socket
    1.59 +import traceback
    1.60 +import zlib
    1.61 +
    1.62 +
    1.63 +def get_stack_trace():
    1.64 +    """Get the current stack trace as string.
    1.65 +
    1.66 +    This is needed to support Python 2.3.
    1.67 +    TODO: Remove this when we only support Python 2.4 and above.
    1.68 +          Use traceback.format_exc instead.
    1.69 +    """
    1.70 +
    1.71 +    out = StringIO.StringIO()
    1.72 +    traceback.print_exc(file=out)
    1.73 +    return out.getvalue()
    1.74 +
    1.75 +
    1.76 +def prepend_message_to_exception(message, exc):
    1.77 +    """Prepend message to the exception."""
    1.78 +
    1.79 +    exc.args = (message + str(exc),)
    1.80 +    return
    1.81 +
    1.82 +
    1.83 +def __translate_interp(interp, cygwin_path):
    1.84 +    """Translate interp program path for Win32 python to run cygwin program
    1.85 +    (e.g. perl).  Note that it doesn't support path that contains space,
    1.86 +    which is typically true for Unix, where #!-script is written.
    1.87 +    For Win32 python, cygwin_path is a directory of cygwin binaries.
    1.88 +
    1.89 +    Args:
    1.90 +      interp: interp command line
    1.91 +      cygwin_path: directory name of cygwin binary, or None
    1.92 +    Returns:
    1.93 +      translated interp command line.
    1.94 +    """
    1.95 +    if not cygwin_path:
    1.96 +        return interp
    1.97 +    m = re.match('^[^ ]*/([^ ]+)( .*)?', interp)
    1.98 +    if m:
    1.99 +        cmd = os.path.join(cygwin_path, m.group(1))
   1.100 +        return cmd + m.group(2)
   1.101 +    return interp
   1.102 +
   1.103 +
   1.104 +def get_script_interp(script_path, cygwin_path=None):
   1.105 +    """Gets #!-interpreter command line from the script.
   1.106 +
   1.107 +    It also fixes command path.  When Cygwin Python is used, e.g. in WebKit,
   1.108 +    it could run "/usr/bin/perl -wT hello.pl".
   1.109 +    When Win32 Python is used, e.g. in Chromium, it couldn't.  So, fix
   1.110 +    "/usr/bin/perl" to "<cygwin_path>\perl.exe".
   1.111 +
   1.112 +    Args:
   1.113 +      script_path: pathname of the script
   1.114 +      cygwin_path: directory name of cygwin binary, or None
   1.115 +    Returns:
   1.116 +      #!-interpreter command line, or None if it is not #!-script.
   1.117 +    """
   1.118 +    fp = open(script_path)
   1.119 +    line = fp.readline()
   1.120 +    fp.close()
   1.121 +    m = re.match('^#!(.*)', line)
   1.122 +    if m:
   1.123 +        return __translate_interp(m.group(1), cygwin_path)
   1.124 +    return None
   1.125 +
   1.126 +
   1.127 +def wrap_popen3_for_win(cygwin_path):
   1.128 +    """Wrap popen3 to support #!-script on Windows.
   1.129 +
   1.130 +    Args:
   1.131 +      cygwin_path:  path for cygwin binary if command path is needed to be
   1.132 +                    translated.  None if no translation required.
   1.133 +    """
   1.134 +
   1.135 +    __orig_popen3 = os.popen3
   1.136 +
   1.137 +    def __wrap_popen3(cmd, mode='t', bufsize=-1):
   1.138 +        cmdline = cmd.split(' ')
   1.139 +        interp = get_script_interp(cmdline[0], cygwin_path)
   1.140 +        if interp:
   1.141 +            cmd = interp + ' ' + cmd
   1.142 +        return __orig_popen3(cmd, mode, bufsize)
   1.143 +
   1.144 +    os.popen3 = __wrap_popen3
   1.145 +
   1.146 +
   1.147 +def hexify(s):
   1.148 +    return ' '.join(map(lambda x: '%02x' % ord(x), s))
   1.149 +
   1.150 +
   1.151 +def get_class_logger(o):
   1.152 +    return logging.getLogger(
   1.153 +        '%s.%s' % (o.__class__.__module__, o.__class__.__name__))
   1.154 +
   1.155 +
   1.156 +class NoopMasker(object):
   1.157 +    """A masking object that has the same interface as RepeatedXorMasker but
   1.158 +    just returns the string passed in without making any change.
   1.159 +    """
   1.160 +
   1.161 +    def __init__(self):
   1.162 +        pass
   1.163 +
   1.164 +    def mask(self, s):
   1.165 +        return s
   1.166 +
   1.167 +
   1.168 +class RepeatedXorMasker(object):
   1.169 +    """A masking object that applies XOR on the string given to mask method
   1.170 +    with the masking bytes given to the constructor repeatedly. This object
   1.171 +    remembers the position in the masking bytes the last mask method call
   1.172 +    ended and resumes from that point on the next mask method call.
   1.173 +    """
   1.174 +
   1.175 +    def __init__(self, mask):
   1.176 +        self._mask = map(ord, mask)
   1.177 +        self._mask_size = len(self._mask)
   1.178 +        self._count = 0
   1.179 +
   1.180 +    def mask(self, s):
   1.181 +        result = array.array('B')
   1.182 +        result.fromstring(s)
   1.183 +        # Use temporary local variables to eliminate the cost to access
   1.184 +        # attributes
   1.185 +        count = self._count
   1.186 +        mask = self._mask
   1.187 +        mask_size = self._mask_size
   1.188 +        for i in xrange(len(result)):
   1.189 +            result[i] ^= mask[count]
   1.190 +            count = (count + 1) % mask_size
   1.191 +        self._count = count
   1.192 +
   1.193 +        return result.tostring()
   1.194 +
   1.195 +
   1.196 +class DeflateRequest(object):
   1.197 +    """A wrapper class for request object to intercept send and recv to perform
   1.198 +    deflate compression and decompression transparently.
   1.199 +    """
   1.200 +
   1.201 +    def __init__(self, request):
   1.202 +        self._request = request
   1.203 +        self.connection = DeflateConnection(request.connection)
   1.204 +
   1.205 +    def __getattribute__(self, name):
   1.206 +        if name in ('_request', 'connection'):
   1.207 +            return object.__getattribute__(self, name)
   1.208 +        return self._request.__getattribute__(name)
   1.209 +
   1.210 +    def __setattr__(self, name, value):
   1.211 +        if name in ('_request', 'connection'):
   1.212 +            return object.__setattr__(self, name, value)
   1.213 +        return self._request.__setattr__(name, value)
   1.214 +
   1.215 +
   1.216 +# By making wbits option negative, we can suppress CMF/FLG (2 octet) and
   1.217 +# ADLER32 (4 octet) fields of zlib so that we can use zlib module just as
   1.218 +# deflate library. DICTID won't be added as far as we don't set dictionary.
   1.219 +# LZ77 window of 32K will be used for both compression and decompression.
   1.220 +# For decompression, we can just use 32K to cover any windows size. For
   1.221 +# compression, we use 32K so receivers must use 32K.
   1.222 +#
   1.223 +# Compression level is Z_DEFAULT_COMPRESSION. We don't have to match level
   1.224 +# to decode.
   1.225 +#
   1.226 +# See zconf.h, deflate.cc, inflate.cc of zlib library, and zlibmodule.c of
   1.227 +# Python. See also RFC1950 (ZLIB 3.3).
   1.228 +
   1.229 +
   1.230 +class _Deflater(object):
   1.231 +
   1.232 +    def __init__(self, window_bits):
   1.233 +        self._logger = get_class_logger(self)
   1.234 +
   1.235 +        self._compress = zlib.compressobj(
   1.236 +            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -window_bits)
   1.237 +
   1.238 +    def compress_and_flush(self, bytes):
   1.239 +        compressed_bytes = self._compress.compress(bytes)
   1.240 +        compressed_bytes += self._compress.flush(zlib.Z_SYNC_FLUSH)
   1.241 +        self._logger.debug('Compress input %r', bytes)
   1.242 +        self._logger.debug('Compress result %r', compressed_bytes)
   1.243 +        return compressed_bytes
   1.244 +
   1.245 +
   1.246 +class _Inflater(object):
   1.247 +
   1.248 +    def __init__(self):
   1.249 +        self._logger = get_class_logger(self)
   1.250 +
   1.251 +        self._unconsumed = ''
   1.252 +
   1.253 +        self.reset()
   1.254 +
   1.255 +    def decompress(self, size):
   1.256 +        if not (size == -1 or size > 0):
   1.257 +            raise Exception('size must be -1 or positive')
   1.258 +
   1.259 +        data = ''
   1.260 +
   1.261 +        while True:
   1.262 +            if size == -1:
   1.263 +                data += self._decompress.decompress(self._unconsumed)
   1.264 +                # See Python bug http://bugs.python.org/issue12050 to
   1.265 +                # understand why the same code cannot be used for updating
   1.266 +                # self._unconsumed for here and else block.
   1.267 +                self._unconsumed = ''
   1.268 +            else:
   1.269 +                data += self._decompress.decompress(
   1.270 +                    self._unconsumed, size - len(data))
   1.271 +                self._unconsumed = self._decompress.unconsumed_tail
   1.272 +            if self._decompress.unused_data:
   1.273 +                # Encountered a last block (i.e. a block with BFINAL = 1) and
   1.274 +                # found a new stream (unused_data). We cannot use the same
   1.275 +                # zlib.Decompress object for the new stream. Create a new
   1.276 +                # Decompress object to decompress the new one.
   1.277 +                #
   1.278 +                # It's fine to ignore unconsumed_tail if unused_data is not
   1.279 +                # empty.
   1.280 +                self._unconsumed = self._decompress.unused_data
   1.281 +                self.reset()
   1.282 +                if size >= 0 and len(data) == size:
   1.283 +                    # data is filled. Don't call decompress again.
   1.284 +                    break
   1.285 +                else:
   1.286 +                    # Re-invoke Decompress.decompress to try to decompress all
   1.287 +                    # available bytes before invoking read which blocks until
   1.288 +                    # any new byte is available.
   1.289 +                    continue
   1.290 +            else:
   1.291 +                # Here, since unused_data is empty, even if unconsumed_tail is
   1.292 +                # not empty, bytes of requested length are already in data. We
   1.293 +                # don't have to "continue" here.
   1.294 +                break
   1.295 +
   1.296 +        if data:
   1.297 +            self._logger.debug('Decompressed %r', data)
   1.298 +        return data
   1.299 +
   1.300 +    def append(self, data):
   1.301 +        self._logger.debug('Appended %r', data)
   1.302 +        self._unconsumed += data
   1.303 +
   1.304 +    def reset(self):
   1.305 +        self._logger.debug('Reset')
   1.306 +        self._decompress = zlib.decompressobj(-zlib.MAX_WBITS)
   1.307 +
   1.308 +
   1.309 +# Compresses/decompresses given octets using the method introduced in RFC1979.
   1.310 +
   1.311 +
   1.312 +class _RFC1979Deflater(object):
   1.313 +    """A compressor class that applies DEFLATE to given byte sequence and
   1.314 +    flushes using the algorithm described in the RFC1979 section 2.1.
   1.315 +    """
   1.316 +
   1.317 +    def __init__(self, window_bits, no_context_takeover):
   1.318 +        self._deflater = None
   1.319 +        if window_bits is None:
   1.320 +            window_bits = zlib.MAX_WBITS
   1.321 +        self._window_bits = window_bits
   1.322 +        self._no_context_takeover = no_context_takeover
   1.323 +
   1.324 +    def filter(self, bytes):
   1.325 +        if self._deflater is None or self._no_context_takeover:
   1.326 +            self._deflater = _Deflater(self._window_bits)
   1.327 +
   1.328 +        # Strip last 4 octets which is LEN and NLEN field of a non-compressed
   1.329 +        # block added for Z_SYNC_FLUSH.
   1.330 +        return self._deflater.compress_and_flush(bytes)[:-4]
   1.331 +
   1.332 +
   1.333 +class _RFC1979Inflater(object):
   1.334 +    """A decompressor class for byte sequence compressed and flushed following
   1.335 +    the algorithm described in the RFC1979 section 2.1.
   1.336 +    """
   1.337 +
   1.338 +    def __init__(self):
   1.339 +        self._inflater = _Inflater()
   1.340 +
   1.341 +    def filter(self, bytes):
   1.342 +        # Restore stripped LEN and NLEN field of a non-compressed block added
   1.343 +        # for Z_SYNC_FLUSH.
   1.344 +        self._inflater.append(bytes + '\x00\x00\xff\xff')
   1.345 +        return self._inflater.decompress(-1)
   1.346 +
   1.347 +
   1.348 +class DeflateSocket(object):
   1.349 +    """A wrapper class for socket object to intercept send and recv to perform
   1.350 +    deflate compression and decompression transparently.
   1.351 +    """
   1.352 +
   1.353 +    # Size of the buffer passed to recv to receive compressed data.
   1.354 +    _RECV_SIZE = 4096
   1.355 +
   1.356 +    def __init__(self, socket):
   1.357 +        self._socket = socket
   1.358 +
   1.359 +        self._logger = get_class_logger(self)
   1.360 +
   1.361 +        self._deflater = _Deflater(zlib.MAX_WBITS)
   1.362 +        self._inflater = _Inflater()
   1.363 +
   1.364 +    def recv(self, size):
   1.365 +        """Receives data from the socket specified on the construction up
   1.366 +        to the specified size. Once any data is available, returns it even
   1.367 +        if it's smaller than the specified size.
   1.368 +        """
   1.369 +
   1.370 +        # TODO(tyoshino): Allow call with size=0. It should block until any
   1.371 +        # decompressed data is available.
   1.372 +        if size <= 0:
   1.373 +            raise Exception('Non-positive size passed')
   1.374 +        while True:
   1.375 +            data = self._inflater.decompress(size)
   1.376 +            if len(data) != 0:
   1.377 +                return data
   1.378 +
   1.379 +            read_data = self._socket.recv(DeflateSocket._RECV_SIZE)
   1.380 +            if not read_data:
   1.381 +                return ''
   1.382 +            self._inflater.append(read_data)
   1.383 +
   1.384 +    def sendall(self, bytes):
   1.385 +        self.send(bytes)
   1.386 +
   1.387 +    def send(self, bytes):
   1.388 +        self._socket.sendall(self._deflater.compress_and_flush(bytes))
   1.389 +        return len(bytes)
   1.390 +
   1.391 +
   1.392 +class DeflateConnection(object):
   1.393 +    """A wrapper class for request object to intercept write and read to
   1.394 +    perform deflate compression and decompression transparently.
   1.395 +    """
   1.396 +
   1.397 +    def __init__(self, connection):
   1.398 +        self._connection = connection
   1.399 +
   1.400 +        self._logger = get_class_logger(self)
   1.401 +
   1.402 +        self._deflater = _Deflater(zlib.MAX_WBITS)
   1.403 +        self._inflater = _Inflater()
   1.404 +
   1.405 +    def get_remote_addr(self):
   1.406 +        return self._connection.remote_addr
   1.407 +    remote_addr = property(get_remote_addr)
   1.408 +
   1.409 +    def put_bytes(self, bytes):
   1.410 +        self.write(bytes)
   1.411 +
   1.412 +    def read(self, size=-1):
   1.413 +        """Reads at most size bytes. Blocks until there's at least one byte
   1.414 +        available.
   1.415 +        """
   1.416 +
   1.417 +        # TODO(tyoshino): Allow call with size=0.
   1.418 +        if not (size == -1 or size > 0):
   1.419 +            raise Exception('size must be -1 or positive')
   1.420 +
   1.421 +        data = ''
   1.422 +        while True:
   1.423 +            if size == -1:
   1.424 +                data += self._inflater.decompress(-1)
   1.425 +            else:
   1.426 +                data += self._inflater.decompress(size - len(data))
   1.427 +
   1.428 +            if size >= 0 and len(data) != 0:
   1.429 +                break
   1.430 +
   1.431 +            # TODO(tyoshino): Make this read efficient by some workaround.
   1.432 +            #
   1.433 +            # In 3.0.3 and prior of mod_python, read blocks until length bytes
   1.434 +            # was read. We don't know the exact size to read while using
   1.435 +            # deflate, so read byte-by-byte.
   1.436 +            #
   1.437 +            # _StandaloneRequest.read that ultimately performs
   1.438 +            # socket._fileobject.read also blocks until length bytes was read
   1.439 +            read_data = self._connection.read(1)
   1.440 +            if not read_data:
   1.441 +                break
   1.442 +            self._inflater.append(read_data)
   1.443 +        return data
   1.444 +
   1.445 +    def write(self, bytes):
   1.446 +        self._connection.write(self._deflater.compress_and_flush(bytes))
   1.447 +
   1.448 +
   1.449 +def _is_ewouldblock_errno(error_number):
   1.450 +    """Returns True iff error_number indicates that receive operation would
   1.451 +    block. To make this portable, we check availability of errno and then
   1.452 +    compare them.
   1.453 +    """
   1.454 +
   1.455 +    for error_name in ['WSAEWOULDBLOCK', 'EWOULDBLOCK', 'EAGAIN']:
   1.456 +        if (error_name in dir(errno) and
   1.457 +            error_number == getattr(errno, error_name)):
   1.458 +            return True
   1.459 +    return False
   1.460 +
   1.461 +
   1.462 +def drain_received_data(raw_socket):
   1.463 +    # Set the socket non-blocking.
   1.464 +    original_timeout = raw_socket.gettimeout()
   1.465 +    raw_socket.settimeout(0.0)
   1.466 +
   1.467 +    drained_data = []
   1.468 +
   1.469 +    # Drain until the socket is closed or no data is immediately
   1.470 +    # available for read.
   1.471 +    while True:
   1.472 +        try:
   1.473 +            data = raw_socket.recv(1)
   1.474 +            if not data:
   1.475 +                break
   1.476 +            drained_data.append(data)
   1.477 +        except socket.error, e:
   1.478 +            # e can be either a pair (errno, string) or just a string (or
   1.479 +            # something else) telling what went wrong. We suppress only
   1.480 +            # the errors that indicates that the socket blocks. Those
   1.481 +            # exceptions can be parsed as a pair (errno, string).
   1.482 +            try:
   1.483 +                error_number, message = e
   1.484 +            except:
   1.485 +                # Failed to parse socket.error.
   1.486 +                raise e
   1.487 +
   1.488 +            if _is_ewouldblock_errno(error_number):
   1.489 +                break
   1.490 +            else:
   1.491 +                raise e
   1.492 +
   1.493 +    # Rollback timeout value.
   1.494 +    raw_socket.settimeout(original_timeout)
   1.495 +
   1.496 +    return ''.join(drained_data)
   1.497 +
   1.498 +
   1.499 +# vi:sts=4 sw=4 et

mercurial