mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	This was vendored without any reference of the vendored version, no update script and a patch bundled into a README. Vendor it using mach vendor, updating to the latest version. Differential Revision: https://phabricator.services.mozilla.com/D190584
		
			
				
	
	
		
			386 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			386 lines
		
	
	
	
		
			13 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.
 | 
						|
"""WebSocket utilities."""
 | 
						|
 | 
						|
from __future__ import absolute_import
 | 
						|
import array
 | 
						|
import errno
 | 
						|
import logging
 | 
						|
import os
 | 
						|
import re
 | 
						|
import six
 | 
						|
from six.moves import map
 | 
						|
from six.moves import range
 | 
						|
import socket
 | 
						|
import struct
 | 
						|
import zlib
 | 
						|
 | 
						|
try:
 | 
						|
    from mod_pywebsocket import fast_masking
 | 
						|
except ImportError:
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
def prepend_message_to_exception(message, exc):
 | 
						|
    """Prepend message to the exception."""
 | 
						|
    exc.args = (message + str(exc), )
 | 
						|
    return
 | 
						|
 | 
						|
 | 
						|
def __translate_interp(interp, cygwin_path):
 | 
						|
    """Translate interp program path for Win32 python to run cygwin program
 | 
						|
    (e.g. perl).  Note that it doesn't support path that contains space,
 | 
						|
    which is typically true for Unix, where #!-script is written.
 | 
						|
    For Win32 python, cygwin_path is a directory of cygwin binaries.
 | 
						|
 | 
						|
    Args:
 | 
						|
      interp: interp command line
 | 
						|
      cygwin_path: directory name of cygwin binary, or None
 | 
						|
    Returns:
 | 
						|
      translated interp command line.
 | 
						|
    """
 | 
						|
    if not cygwin_path:
 | 
						|
        return interp
 | 
						|
    m = re.match('^[^ ]*/([^ ]+)( .*)?', interp)
 | 
						|
    if m:
 | 
						|
        cmd = os.path.join(cygwin_path, m.group(1))
 | 
						|
        return cmd + m.group(2)
 | 
						|
    return interp
 | 
						|
 | 
						|
 | 
						|
def get_script_interp(script_path, cygwin_path=None):
 | 
						|
    r"""Get #!-interpreter command line from the script.
 | 
						|
 | 
						|
    It also fixes command path.  When Cygwin Python is used, e.g. in WebKit,
 | 
						|
    it could run "/usr/bin/perl -wT hello.pl".
 | 
						|
    When Win32 Python is used, e.g. in Chromium, it couldn't.  So, fix
 | 
						|
    "/usr/bin/perl" to "<cygwin_path>\perl.exe".
 | 
						|
 | 
						|
    Args:
 | 
						|
      script_path: pathname of the script
 | 
						|
      cygwin_path: directory name of cygwin binary, or None
 | 
						|
    Returns:
 | 
						|
      #!-interpreter command line, or None if it is not #!-script.
 | 
						|
    """
 | 
						|
    fp = open(script_path)
 | 
						|
    line = fp.readline()
 | 
						|
    fp.close()
 | 
						|
    m = re.match('^#!(.*)', line)
 | 
						|
    if m:
 | 
						|
        return __translate_interp(m.group(1), cygwin_path)
 | 
						|
    return None
 | 
						|
 | 
						|
 | 
						|
def hexify(s):
 | 
						|
    return ' '.join(['%02x' % x for x in six.iterbytes(s)])
 | 
						|
 | 
						|
 | 
						|
def get_class_logger(o):
 | 
						|
    """Return the logging class information."""
 | 
						|
    return logging.getLogger('%s.%s' %
 | 
						|
                             (o.__class__.__module__, o.__class__.__name__))
 | 
						|
 | 
						|
 | 
						|
def pack_byte(b):
 | 
						|
    """Pack an integer to network-ordered byte"""
 | 
						|
    return struct.pack('!B', b)
 | 
						|
 | 
						|
 | 
						|
class NoopMasker(object):
 | 
						|
    """A NoOp masking object.
 | 
						|
 | 
						|
    This has the same interface as RepeatedXorMasker but just returns
 | 
						|
    the string passed in without making any change.
 | 
						|
    """
 | 
						|
    def __init__(self):
 | 
						|
        """NoOp."""
 | 
						|
        pass
 | 
						|
 | 
						|
    def mask(self, s):
 | 
						|
        """NoOp."""
 | 
						|
        return s
 | 
						|
 | 
						|
 | 
						|
class RepeatedXorMasker(object):
 | 
						|
    """A masking object that applies XOR on the string.
 | 
						|
 | 
						|
    Applies XOR on the byte string given to mask method with the masking bytes
 | 
						|
    given to the constructor repeatedly. This object remembers the position
 | 
						|
    in the masking bytes the last mask method call ended and resumes from
 | 
						|
    that point on the next mask method call.
 | 
						|
    """
 | 
						|
    def __init__(self, masking_key):
 | 
						|
        self._masking_key = masking_key
 | 
						|
        self._masking_key_index = 0
 | 
						|
 | 
						|
    def _mask_using_swig(self, s):
 | 
						|
        """Perform the mask via SWIG."""
 | 
						|
        masked_data = fast_masking.mask(s, self._masking_key,
 | 
						|
                                        self._masking_key_index)
 | 
						|
        self._masking_key_index = ((self._masking_key_index + len(s)) %
 | 
						|
                                   len(self._masking_key))
 | 
						|
        return masked_data
 | 
						|
 | 
						|
    def _mask_using_array(self, s):
 | 
						|
        """Perform the mask via python."""
 | 
						|
        if isinstance(s, six.text_type):
 | 
						|
            raise Exception(
 | 
						|
                'Masking Operation should not process unicode strings')
 | 
						|
 | 
						|
        result = bytearray(s)
 | 
						|
 | 
						|
        # Use temporary local variables to eliminate the cost to access
 | 
						|
        # attributes
 | 
						|
        masking_key = [c for c in six.iterbytes(self._masking_key)]
 | 
						|
        masking_key_size = len(masking_key)
 | 
						|
        masking_key_index = self._masking_key_index
 | 
						|
 | 
						|
        for i in range(len(result)):
 | 
						|
            result[i] ^= masking_key[masking_key_index]
 | 
						|
            masking_key_index = (masking_key_index + 1) % masking_key_size
 | 
						|
 | 
						|
        self._masking_key_index = masking_key_index
 | 
						|
 | 
						|
        return bytes(result)
 | 
						|
 | 
						|
    if 'fast_masking' in globals():
 | 
						|
        mask = _mask_using_swig
 | 
						|
    else:
 | 
						|
        mask = _mask_using_array
 | 
						|
 | 
						|
 | 
						|
# By making wbits option negative, we can suppress CMF/FLG (2 octet) and
 | 
						|
# ADLER32 (4 octet) fields of zlib so that we can use zlib module just as
 | 
						|
# deflate library. DICTID won't be added as far as we don't set dictionary.
 | 
						|
# LZ77 window of 32K will be used for both compression and decompression.
 | 
						|
# For decompression, we can just use 32K to cover any windows size. For
 | 
						|
# compression, we use 32K so receivers must use 32K.
 | 
						|
#
 | 
						|
# Compression level is Z_DEFAULT_COMPRESSION. We don't have to match level
 | 
						|
# to decode.
 | 
						|
#
 | 
						|
# See zconf.h, deflate.cc, inflate.cc of zlib library, and zlibmodule.c of
 | 
						|
# Python. See also RFC1950 (ZLIB 3.3).
 | 
						|
 | 
						|
 | 
						|
class _Deflater(object):
 | 
						|
    def __init__(self, window_bits):
 | 
						|
        self._logger = get_class_logger(self)
 | 
						|
 | 
						|
        # Using the smallest window bits of 9 for generating input frames.
 | 
						|
        # On WebSocket spec, the smallest window bit is 8. However, zlib does
 | 
						|
        # not accept window_bit = 8.
 | 
						|
        #
 | 
						|
        # Because of a zlib deflate quirk, back-references will not use the
 | 
						|
        # entire range of 1 << window_bits, but will instead use a restricted
 | 
						|
        # range of (1 << window_bits) - 262. With an increased window_bits = 9,
 | 
						|
        # back-references will be within a range of 250. These can still be
 | 
						|
        # decompressed with window_bits = 8 and the 256-byte window used there.
 | 
						|
        #
 | 
						|
        # Similar disscussions can be found in https://crbug.com/691074
 | 
						|
        window_bits = max(window_bits, 9)
 | 
						|
 | 
						|
        self._compress = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
 | 
						|
                                          zlib.DEFLATED, -window_bits)
 | 
						|
 | 
						|
    def compress(self, bytes):
 | 
						|
        compressed_bytes = self._compress.compress(bytes)
 | 
						|
        self._logger.debug('Compress input %r', bytes)
 | 
						|
        self._logger.debug('Compress result %r', compressed_bytes)
 | 
						|
        return compressed_bytes
 | 
						|
 | 
						|
    def compress_and_flush(self, bytes):
 | 
						|
        compressed_bytes = self._compress.compress(bytes)
 | 
						|
        compressed_bytes += self._compress.flush(zlib.Z_SYNC_FLUSH)
 | 
						|
        self._logger.debug('Compress input %r', bytes)
 | 
						|
        self._logger.debug('Compress result %r', compressed_bytes)
 | 
						|
        return compressed_bytes
 | 
						|
 | 
						|
    def compress_and_finish(self, bytes):
 | 
						|
        compressed_bytes = self._compress.compress(bytes)
 | 
						|
        compressed_bytes += self._compress.flush(zlib.Z_FINISH)
 | 
						|
        self._logger.debug('Compress input %r', bytes)
 | 
						|
        self._logger.debug('Compress result %r', compressed_bytes)
 | 
						|
        return compressed_bytes
 | 
						|
 | 
						|
 | 
						|
class _Inflater(object):
 | 
						|
    def __init__(self, window_bits):
 | 
						|
        self._logger = get_class_logger(self)
 | 
						|
        self._window_bits = window_bits
 | 
						|
 | 
						|
        self._unconsumed = b''
 | 
						|
 | 
						|
        self.reset()
 | 
						|
 | 
						|
    def decompress(self, size):
 | 
						|
        if not (size == -1 or size > 0):
 | 
						|
            raise Exception('size must be -1 or positive')
 | 
						|
 | 
						|
        data = b''
 | 
						|
 | 
						|
        while True:
 | 
						|
            data += self._decompress.decompress(self._unconsumed,
 | 
						|
                                                max(0, size - len(data)))
 | 
						|
            self._unconsumed = self._decompress.unconsumed_tail
 | 
						|
            if self._decompress.unused_data:
 | 
						|
                # Encountered a last block (i.e. a block with BFINAL = 1) and
 | 
						|
                # found a new stream (unused_data). We cannot use the same
 | 
						|
                # zlib.Decompress object for the new stream. Create a new
 | 
						|
                # Decompress object to decompress the new one.
 | 
						|
                #
 | 
						|
                # It's fine to ignore unconsumed_tail if unused_data is not
 | 
						|
                # empty.
 | 
						|
                self._unconsumed = self._decompress.unused_data
 | 
						|
                self.reset()
 | 
						|
                if size >= 0 and len(data) == size:
 | 
						|
                    # data is filled. Don't call decompress again.
 | 
						|
                    break
 | 
						|
                else:
 | 
						|
                    # Re-invoke Decompress.decompress to try to decompress all
 | 
						|
                    # available bytes before invoking read which blocks until
 | 
						|
                    # any new byte is available.
 | 
						|
                    continue
 | 
						|
            else:
 | 
						|
                # Here, since unused_data is empty, even if unconsumed_tail is
 | 
						|
                # not empty, bytes of requested length are already in data. We
 | 
						|
                # don't have to "continue" here.
 | 
						|
                break
 | 
						|
 | 
						|
        if data:
 | 
						|
            self._logger.debug('Decompressed %r', data)
 | 
						|
        return data
 | 
						|
 | 
						|
    def append(self, data):
 | 
						|
        self._logger.debug('Appended %r', data)
 | 
						|
        self._unconsumed += data
 | 
						|
 | 
						|
    def reset(self):
 | 
						|
        self._logger.debug('Reset')
 | 
						|
        self._decompress = zlib.decompressobj(-self._window_bits)
 | 
						|
 | 
						|
 | 
						|
# Compresses/decompresses given octets using the method introduced in RFC1979.
 | 
						|
 | 
						|
 | 
						|
class _RFC1979Deflater(object):
 | 
						|
    """A compressor class that applies DEFLATE to given byte sequence and
 | 
						|
    flushes using the algorithm described in the RFC1979 section 2.1.
 | 
						|
    """
 | 
						|
    def __init__(self, window_bits, no_context_takeover):
 | 
						|
        self._deflater = None
 | 
						|
        if window_bits is None:
 | 
						|
            window_bits = zlib.MAX_WBITS
 | 
						|
        self._window_bits = window_bits
 | 
						|
        self._no_context_takeover = no_context_takeover
 | 
						|
 | 
						|
    def filter(self, bytes, end=True, bfinal=False):
 | 
						|
        if self._deflater is None:
 | 
						|
            self._deflater = _Deflater(self._window_bits)
 | 
						|
 | 
						|
        if bfinal:
 | 
						|
            result = self._deflater.compress_and_finish(bytes)
 | 
						|
            # Add a padding block with BFINAL = 0 and BTYPE = 0.
 | 
						|
            result = result + pack_byte(0)
 | 
						|
            self._deflater = None
 | 
						|
            return result
 | 
						|
 | 
						|
        result = self._deflater.compress_and_flush(bytes)
 | 
						|
        if end:
 | 
						|
            # Strip last 4 octets which is LEN and NLEN field of a
 | 
						|
            # non-compressed block added for Z_SYNC_FLUSH.
 | 
						|
            result = result[:-4]
 | 
						|
 | 
						|
        if self._no_context_takeover and end:
 | 
						|
            self._deflater = None
 | 
						|
 | 
						|
        return result
 | 
						|
 | 
						|
 | 
						|
class _RFC1979Inflater(object):
 | 
						|
    """A decompressor class a la RFC1979.
 | 
						|
 | 
						|
    A decompressor class for byte sequence compressed and flushed following
 | 
						|
    the algorithm described in the RFC1979 section 2.1.
 | 
						|
    """
 | 
						|
    def __init__(self, window_bits=zlib.MAX_WBITS):
 | 
						|
        self._inflater = _Inflater(window_bits)
 | 
						|
 | 
						|
    def filter(self, bytes):
 | 
						|
        # Restore stripped LEN and NLEN field of a non-compressed block added
 | 
						|
        # for Z_SYNC_FLUSH.
 | 
						|
        self._inflater.append(bytes + b'\x00\x00\xff\xff')
 | 
						|
        return self._inflater.decompress(-1)
 | 
						|
 | 
						|
 | 
						|
class DeflateSocket(object):
 | 
						|
    """A wrapper class for socket object to intercept send and recv to perform
 | 
						|
    deflate compression and decompression transparently.
 | 
						|
    """
 | 
						|
 | 
						|
    # Size of the buffer passed to recv to receive compressed data.
 | 
						|
    _RECV_SIZE = 4096
 | 
						|
 | 
						|
    def __init__(self, socket):
 | 
						|
        self._socket = socket
 | 
						|
 | 
						|
        self._logger = get_class_logger(self)
 | 
						|
 | 
						|
        self._deflater = _Deflater(zlib.MAX_WBITS)
 | 
						|
        self._inflater = _Inflater(zlib.MAX_WBITS)
 | 
						|
 | 
						|
    def recv(self, size):
 | 
						|
        """Receives data from the socket specified on the construction up
 | 
						|
        to the specified size. Once any data is available, returns it even
 | 
						|
        if it's smaller than the specified size.
 | 
						|
        """
 | 
						|
 | 
						|
        # TODO(tyoshino): Allow call with size=0. It should block until any
 | 
						|
        # decompressed data is available.
 | 
						|
        if size <= 0:
 | 
						|
            raise Exception('Non-positive size passed')
 | 
						|
        while True:
 | 
						|
            data = self._inflater.decompress(size)
 | 
						|
            if len(data) != 0:
 | 
						|
                return data
 | 
						|
 | 
						|
            read_data = self._socket.recv(DeflateSocket._RECV_SIZE)
 | 
						|
            if not read_data:
 | 
						|
                return b''
 | 
						|
            self._inflater.append(read_data)
 | 
						|
 | 
						|
    def sendall(self, bytes):
 | 
						|
        self.send(bytes)
 | 
						|
 | 
						|
    def send(self, bytes):
 | 
						|
        self._socket.sendall(self._deflater.compress_and_flush(bytes))
 | 
						|
        return len(bytes)
 | 
						|
 | 
						|
 | 
						|
# vi:sts=4 sw=4 et
 |