testing/mochitest/pywebsocket/mod_pywebsocket/util.py

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 # Copyright 2011, Google Inc.
michael@0 2 # All rights reserved.
michael@0 3 #
michael@0 4 # Redistribution and use in source and binary forms, with or without
michael@0 5 # modification, are permitted provided that the following conditions are
michael@0 6 # met:
michael@0 7 #
michael@0 8 # * Redistributions of source code must retain the above copyright
michael@0 9 # notice, this list of conditions and the following disclaimer.
michael@0 10 # * Redistributions in binary form must reproduce the above
michael@0 11 # copyright notice, this list of conditions and the following disclaimer
michael@0 12 # in the documentation and/or other materials provided with the
michael@0 13 # distribution.
michael@0 14 # * Neither the name of Google Inc. nor the names of its
michael@0 15 # contributors may be used to endorse or promote products derived from
michael@0 16 # this software without specific prior written permission.
michael@0 17 #
michael@0 18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
michael@0 19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
michael@0 20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
michael@0 21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
michael@0 22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
michael@0 23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
michael@0 24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
michael@0 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
michael@0 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
michael@0 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
michael@0 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
michael@0 29
michael@0 30
michael@0 31 """WebSocket utilities.
michael@0 32 """
michael@0 33
michael@0 34
michael@0 35 import array
michael@0 36 import errno
michael@0 37
michael@0 38 # Import hash classes from a module available and recommended for each Python
michael@0 39 # version and re-export those symbol. Use sha and md5 module in Python 2.4, and
michael@0 40 # hashlib module in Python 2.6.
michael@0 41 try:
michael@0 42 import hashlib
michael@0 43 md5_hash = hashlib.md5
michael@0 44 sha1_hash = hashlib.sha1
michael@0 45 except ImportError:
michael@0 46 import md5
michael@0 47 import sha
michael@0 48 md5_hash = md5.md5
michael@0 49 sha1_hash = sha.sha
michael@0 50
michael@0 51 import StringIO
michael@0 52 import logging
michael@0 53 import os
michael@0 54 import re
michael@0 55 import socket
michael@0 56 import traceback
michael@0 57 import zlib
michael@0 58
michael@0 59
michael@0 60 def get_stack_trace():
michael@0 61 """Get the current stack trace as string.
michael@0 62
michael@0 63 This is needed to support Python 2.3.
michael@0 64 TODO: Remove this when we only support Python 2.4 and above.
michael@0 65 Use traceback.format_exc instead.
michael@0 66 """
michael@0 67
michael@0 68 out = StringIO.StringIO()
michael@0 69 traceback.print_exc(file=out)
michael@0 70 return out.getvalue()
michael@0 71
michael@0 72
michael@0 73 def prepend_message_to_exception(message, exc):
michael@0 74 """Prepend message to the exception."""
michael@0 75
michael@0 76 exc.args = (message + str(exc),)
michael@0 77 return
michael@0 78
michael@0 79
michael@0 80 def __translate_interp(interp, cygwin_path):
michael@0 81 """Translate interp program path for Win32 python to run cygwin program
michael@0 82 (e.g. perl). Note that it doesn't support path that contains space,
michael@0 83 which is typically true for Unix, where #!-script is written.
michael@0 84 For Win32 python, cygwin_path is a directory of cygwin binaries.
michael@0 85
michael@0 86 Args:
michael@0 87 interp: interp command line
michael@0 88 cygwin_path: directory name of cygwin binary, or None
michael@0 89 Returns:
michael@0 90 translated interp command line.
michael@0 91 """
michael@0 92 if not cygwin_path:
michael@0 93 return interp
michael@0 94 m = re.match('^[^ ]*/([^ ]+)( .*)?', interp)
michael@0 95 if m:
michael@0 96 cmd = os.path.join(cygwin_path, m.group(1))
michael@0 97 return cmd + m.group(2)
michael@0 98 return interp
michael@0 99
michael@0 100
michael@0 101 def get_script_interp(script_path, cygwin_path=None):
michael@0 102 """Gets #!-interpreter command line from the script.
michael@0 103
michael@0 104 It also fixes command path. When Cygwin Python is used, e.g. in WebKit,
michael@0 105 it could run "/usr/bin/perl -wT hello.pl".
michael@0 106 When Win32 Python is used, e.g. in Chromium, it couldn't. So, fix
michael@0 107 "/usr/bin/perl" to "<cygwin_path>\perl.exe".
michael@0 108
michael@0 109 Args:
michael@0 110 script_path: pathname of the script
michael@0 111 cygwin_path: directory name of cygwin binary, or None
michael@0 112 Returns:
michael@0 113 #!-interpreter command line, or None if it is not #!-script.
michael@0 114 """
michael@0 115 fp = open(script_path)
michael@0 116 line = fp.readline()
michael@0 117 fp.close()
michael@0 118 m = re.match('^#!(.*)', line)
michael@0 119 if m:
michael@0 120 return __translate_interp(m.group(1), cygwin_path)
michael@0 121 return None
michael@0 122
michael@0 123
michael@0 124 def wrap_popen3_for_win(cygwin_path):
michael@0 125 """Wrap popen3 to support #!-script on Windows.
michael@0 126
michael@0 127 Args:
michael@0 128 cygwin_path: path for cygwin binary if command path is needed to be
michael@0 129 translated. None if no translation required.
michael@0 130 """
michael@0 131
michael@0 132 __orig_popen3 = os.popen3
michael@0 133
michael@0 134 def __wrap_popen3(cmd, mode='t', bufsize=-1):
michael@0 135 cmdline = cmd.split(' ')
michael@0 136 interp = get_script_interp(cmdline[0], cygwin_path)
michael@0 137 if interp:
michael@0 138 cmd = interp + ' ' + cmd
michael@0 139 return __orig_popen3(cmd, mode, bufsize)
michael@0 140
michael@0 141 os.popen3 = __wrap_popen3
michael@0 142
michael@0 143
michael@0 144 def hexify(s):
michael@0 145 return ' '.join(map(lambda x: '%02x' % ord(x), s))
michael@0 146
michael@0 147
michael@0 148 def get_class_logger(o):
michael@0 149 return logging.getLogger(
michael@0 150 '%s.%s' % (o.__class__.__module__, o.__class__.__name__))
michael@0 151
michael@0 152
michael@0 153 class NoopMasker(object):
michael@0 154 """A masking object that has the same interface as RepeatedXorMasker but
michael@0 155 just returns the string passed in without making any change.
michael@0 156 """
michael@0 157
michael@0 158 def __init__(self):
michael@0 159 pass
michael@0 160
michael@0 161 def mask(self, s):
michael@0 162 return s
michael@0 163
michael@0 164
michael@0 165 class RepeatedXorMasker(object):
michael@0 166 """A masking object that applies XOR on the string given to mask method
michael@0 167 with the masking bytes given to the constructor repeatedly. This object
michael@0 168 remembers the position in the masking bytes the last mask method call
michael@0 169 ended and resumes from that point on the next mask method call.
michael@0 170 """
michael@0 171
michael@0 172 def __init__(self, mask):
michael@0 173 self._mask = map(ord, mask)
michael@0 174 self._mask_size = len(self._mask)
michael@0 175 self._count = 0
michael@0 176
michael@0 177 def mask(self, s):
michael@0 178 result = array.array('B')
michael@0 179 result.fromstring(s)
michael@0 180 # Use temporary local variables to eliminate the cost to access
michael@0 181 # attributes
michael@0 182 count = self._count
michael@0 183 mask = self._mask
michael@0 184 mask_size = self._mask_size
michael@0 185 for i in xrange(len(result)):
michael@0 186 result[i] ^= mask[count]
michael@0 187 count = (count + 1) % mask_size
michael@0 188 self._count = count
michael@0 189
michael@0 190 return result.tostring()
michael@0 191
michael@0 192
michael@0 193 class DeflateRequest(object):
michael@0 194 """A wrapper class for request object to intercept send and recv to perform
michael@0 195 deflate compression and decompression transparently.
michael@0 196 """
michael@0 197
michael@0 198 def __init__(self, request):
michael@0 199 self._request = request
michael@0 200 self.connection = DeflateConnection(request.connection)
michael@0 201
michael@0 202 def __getattribute__(self, name):
michael@0 203 if name in ('_request', 'connection'):
michael@0 204 return object.__getattribute__(self, name)
michael@0 205 return self._request.__getattribute__(name)
michael@0 206
michael@0 207 def __setattr__(self, name, value):
michael@0 208 if name in ('_request', 'connection'):
michael@0 209 return object.__setattr__(self, name, value)
michael@0 210 return self._request.__setattr__(name, value)
michael@0 211
michael@0 212
michael@0 213 # By making wbits option negative, we can suppress CMF/FLG (2 octet) and
michael@0 214 # ADLER32 (4 octet) fields of zlib so that we can use zlib module just as
michael@0 215 # deflate library. DICTID won't be added as far as we don't set dictionary.
michael@0 216 # LZ77 window of 32K will be used for both compression and decompression.
michael@0 217 # For decompression, we can just use 32K to cover any windows size. For
michael@0 218 # compression, we use 32K so receivers must use 32K.
michael@0 219 #
michael@0 220 # Compression level is Z_DEFAULT_COMPRESSION. We don't have to match level
michael@0 221 # to decode.
michael@0 222 #
michael@0 223 # See zconf.h, deflate.cc, inflate.cc of zlib library, and zlibmodule.c of
michael@0 224 # Python. See also RFC1950 (ZLIB 3.3).
michael@0 225
michael@0 226
michael@0 227 class _Deflater(object):
michael@0 228
michael@0 229 def __init__(self, window_bits):
michael@0 230 self._logger = get_class_logger(self)
michael@0 231
michael@0 232 self._compress = zlib.compressobj(
michael@0 233 zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -window_bits)
michael@0 234
michael@0 235 def compress_and_flush(self, bytes):
michael@0 236 compressed_bytes = self._compress.compress(bytes)
michael@0 237 compressed_bytes += self._compress.flush(zlib.Z_SYNC_FLUSH)
michael@0 238 self._logger.debug('Compress input %r', bytes)
michael@0 239 self._logger.debug('Compress result %r', compressed_bytes)
michael@0 240 return compressed_bytes
michael@0 241
michael@0 242
michael@0 243 class _Inflater(object):
michael@0 244
michael@0 245 def __init__(self):
michael@0 246 self._logger = get_class_logger(self)
michael@0 247
michael@0 248 self._unconsumed = ''
michael@0 249
michael@0 250 self.reset()
michael@0 251
michael@0 252 def decompress(self, size):
michael@0 253 if not (size == -1 or size > 0):
michael@0 254 raise Exception('size must be -1 or positive')
michael@0 255
michael@0 256 data = ''
michael@0 257
michael@0 258 while True:
michael@0 259 if size == -1:
michael@0 260 data += self._decompress.decompress(self._unconsumed)
michael@0 261 # See Python bug http://bugs.python.org/issue12050 to
michael@0 262 # understand why the same code cannot be used for updating
michael@0 263 # self._unconsumed for here and else block.
michael@0 264 self._unconsumed = ''
michael@0 265 else:
michael@0 266 data += self._decompress.decompress(
michael@0 267 self._unconsumed, size - len(data))
michael@0 268 self._unconsumed = self._decompress.unconsumed_tail
michael@0 269 if self._decompress.unused_data:
michael@0 270 # Encountered a last block (i.e. a block with BFINAL = 1) and
michael@0 271 # found a new stream (unused_data). We cannot use the same
michael@0 272 # zlib.Decompress object for the new stream. Create a new
michael@0 273 # Decompress object to decompress the new one.
michael@0 274 #
michael@0 275 # It's fine to ignore unconsumed_tail if unused_data is not
michael@0 276 # empty.
michael@0 277 self._unconsumed = self._decompress.unused_data
michael@0 278 self.reset()
michael@0 279 if size >= 0 and len(data) == size:
michael@0 280 # data is filled. Don't call decompress again.
michael@0 281 break
michael@0 282 else:
michael@0 283 # Re-invoke Decompress.decompress to try to decompress all
michael@0 284 # available bytes before invoking read which blocks until
michael@0 285 # any new byte is available.
michael@0 286 continue
michael@0 287 else:
michael@0 288 # Here, since unused_data is empty, even if unconsumed_tail is
michael@0 289 # not empty, bytes of requested length are already in data. We
michael@0 290 # don't have to "continue" here.
michael@0 291 break
michael@0 292
michael@0 293 if data:
michael@0 294 self._logger.debug('Decompressed %r', data)
michael@0 295 return data
michael@0 296
michael@0 297 def append(self, data):
michael@0 298 self._logger.debug('Appended %r', data)
michael@0 299 self._unconsumed += data
michael@0 300
michael@0 301 def reset(self):
michael@0 302 self._logger.debug('Reset')
michael@0 303 self._decompress = zlib.decompressobj(-zlib.MAX_WBITS)
michael@0 304
michael@0 305
michael@0 306 # Compresses/decompresses given octets using the method introduced in RFC1979.
michael@0 307
michael@0 308
michael@0 309 class _RFC1979Deflater(object):
michael@0 310 """A compressor class that applies DEFLATE to given byte sequence and
michael@0 311 flushes using the algorithm described in the RFC1979 section 2.1.
michael@0 312 """
michael@0 313
michael@0 314 def __init__(self, window_bits, no_context_takeover):
michael@0 315 self._deflater = None
michael@0 316 if window_bits is None:
michael@0 317 window_bits = zlib.MAX_WBITS
michael@0 318 self._window_bits = window_bits
michael@0 319 self._no_context_takeover = no_context_takeover
michael@0 320
michael@0 321 def filter(self, bytes):
michael@0 322 if self._deflater is None or self._no_context_takeover:
michael@0 323 self._deflater = _Deflater(self._window_bits)
michael@0 324
michael@0 325 # Strip last 4 octets which is LEN and NLEN field of a non-compressed
michael@0 326 # block added for Z_SYNC_FLUSH.
michael@0 327 return self._deflater.compress_and_flush(bytes)[:-4]
michael@0 328
michael@0 329
michael@0 330 class _RFC1979Inflater(object):
michael@0 331 """A decompressor class for byte sequence compressed and flushed following
michael@0 332 the algorithm described in the RFC1979 section 2.1.
michael@0 333 """
michael@0 334
michael@0 335 def __init__(self):
michael@0 336 self._inflater = _Inflater()
michael@0 337
michael@0 338 def filter(self, bytes):
michael@0 339 # Restore stripped LEN and NLEN field of a non-compressed block added
michael@0 340 # for Z_SYNC_FLUSH.
michael@0 341 self._inflater.append(bytes + '\x00\x00\xff\xff')
michael@0 342 return self._inflater.decompress(-1)
michael@0 343
michael@0 344
michael@0 345 class DeflateSocket(object):
michael@0 346 """A wrapper class for socket object to intercept send and recv to perform
michael@0 347 deflate compression and decompression transparently.
michael@0 348 """
michael@0 349
michael@0 350 # Size of the buffer passed to recv to receive compressed data.
michael@0 351 _RECV_SIZE = 4096
michael@0 352
michael@0 353 def __init__(self, socket):
michael@0 354 self._socket = socket
michael@0 355
michael@0 356 self._logger = get_class_logger(self)
michael@0 357
michael@0 358 self._deflater = _Deflater(zlib.MAX_WBITS)
michael@0 359 self._inflater = _Inflater()
michael@0 360
michael@0 361 def recv(self, size):
michael@0 362 """Receives data from the socket specified on the construction up
michael@0 363 to the specified size. Once any data is available, returns it even
michael@0 364 if it's smaller than the specified size.
michael@0 365 """
michael@0 366
michael@0 367 # TODO(tyoshino): Allow call with size=0. It should block until any
michael@0 368 # decompressed data is available.
michael@0 369 if size <= 0:
michael@0 370 raise Exception('Non-positive size passed')
michael@0 371 while True:
michael@0 372 data = self._inflater.decompress(size)
michael@0 373 if len(data) != 0:
michael@0 374 return data
michael@0 375
michael@0 376 read_data = self._socket.recv(DeflateSocket._RECV_SIZE)
michael@0 377 if not read_data:
michael@0 378 return ''
michael@0 379 self._inflater.append(read_data)
michael@0 380
michael@0 381 def sendall(self, bytes):
michael@0 382 self.send(bytes)
michael@0 383
michael@0 384 def send(self, bytes):
michael@0 385 self._socket.sendall(self._deflater.compress_and_flush(bytes))
michael@0 386 return len(bytes)
michael@0 387
michael@0 388
michael@0 389 class DeflateConnection(object):
michael@0 390 """A wrapper class for request object to intercept write and read to
michael@0 391 perform deflate compression and decompression transparently.
michael@0 392 """
michael@0 393
michael@0 394 def __init__(self, connection):
michael@0 395 self._connection = connection
michael@0 396
michael@0 397 self._logger = get_class_logger(self)
michael@0 398
michael@0 399 self._deflater = _Deflater(zlib.MAX_WBITS)
michael@0 400 self._inflater = _Inflater()
michael@0 401
michael@0 402 def get_remote_addr(self):
michael@0 403 return self._connection.remote_addr
michael@0 404 remote_addr = property(get_remote_addr)
michael@0 405
michael@0 406 def put_bytes(self, bytes):
michael@0 407 self.write(bytes)
michael@0 408
michael@0 409 def read(self, size=-1):
michael@0 410 """Reads at most size bytes. Blocks until there's at least one byte
michael@0 411 available.
michael@0 412 """
michael@0 413
michael@0 414 # TODO(tyoshino): Allow call with size=0.
michael@0 415 if not (size == -1 or size > 0):
michael@0 416 raise Exception('size must be -1 or positive')
michael@0 417
michael@0 418 data = ''
michael@0 419 while True:
michael@0 420 if size == -1:
michael@0 421 data += self._inflater.decompress(-1)
michael@0 422 else:
michael@0 423 data += self._inflater.decompress(size - len(data))
michael@0 424
michael@0 425 if size >= 0 and len(data) != 0:
michael@0 426 break
michael@0 427
michael@0 428 # TODO(tyoshino): Make this read efficient by some workaround.
michael@0 429 #
michael@0 430 # In 3.0.3 and prior of mod_python, read blocks until length bytes
michael@0 431 # was read. We don't know the exact size to read while using
michael@0 432 # deflate, so read byte-by-byte.
michael@0 433 #
michael@0 434 # _StandaloneRequest.read that ultimately performs
michael@0 435 # socket._fileobject.read also blocks until length bytes was read
michael@0 436 read_data = self._connection.read(1)
michael@0 437 if not read_data:
michael@0 438 break
michael@0 439 self._inflater.append(read_data)
michael@0 440 return data
michael@0 441
michael@0 442 def write(self, bytes):
michael@0 443 self._connection.write(self._deflater.compress_and_flush(bytes))
michael@0 444
michael@0 445
michael@0 446 def _is_ewouldblock_errno(error_number):
michael@0 447 """Returns True iff error_number indicates that receive operation would
michael@0 448 block. To make this portable, we check availability of errno and then
michael@0 449 compare them.
michael@0 450 """
michael@0 451
michael@0 452 for error_name in ['WSAEWOULDBLOCK', 'EWOULDBLOCK', 'EAGAIN']:
michael@0 453 if (error_name in dir(errno) and
michael@0 454 error_number == getattr(errno, error_name)):
michael@0 455 return True
michael@0 456 return False
michael@0 457
michael@0 458
michael@0 459 def drain_received_data(raw_socket):
michael@0 460 # Set the socket non-blocking.
michael@0 461 original_timeout = raw_socket.gettimeout()
michael@0 462 raw_socket.settimeout(0.0)
michael@0 463
michael@0 464 drained_data = []
michael@0 465
michael@0 466 # Drain until the socket is closed or no data is immediately
michael@0 467 # available for read.
michael@0 468 while True:
michael@0 469 try:
michael@0 470 data = raw_socket.recv(1)
michael@0 471 if not data:
michael@0 472 break
michael@0 473 drained_data.append(data)
michael@0 474 except socket.error, e:
michael@0 475 # e can be either a pair (errno, string) or just a string (or
michael@0 476 # something else) telling what went wrong. We suppress only
michael@0 477 # the errors that indicates that the socket blocks. Those
michael@0 478 # exceptions can be parsed as a pair (errno, string).
michael@0 479 try:
michael@0 480 error_number, message = e
michael@0 481 except:
michael@0 482 # Failed to parse socket.error.
michael@0 483 raise e
michael@0 484
michael@0 485 if _is_ewouldblock_errno(error_number):
michael@0 486 break
michael@0 487 else:
michael@0 488 raise e
michael@0 489
michael@0 490 # Rollback timeout value.
michael@0 491 raw_socket.settimeout(original_timeout)
michael@0 492
michael@0 493 return ''.join(drained_data)
michael@0 494
michael@0 495
michael@0 496 # vi:sts=4 sw=4 et

mercurial