1 # Copyright 2012, Google Inc. 2 # All rights reserved. 3 # 4 # Redistribution and use in source and binary forms, with or without 5 # modification, are permitted provided that the following conditions are 6 # met: 7 # 8 # * Redistributions of source code must retain the above copyright 9 # notice, this list of conditions and the following disclaimer. 10 # * Redistributions in binary form must reproduce the above 11 # copyright notice, this list of conditions and the following disclaimer 12 # in the documentation and/or other materials provided with the 13 # distribution. 14 # * Neither the name of Google Inc. nor the names of its 15 # contributors may be used to endorse or promote products derived from 16 # this software without specific prior written permission. 17 # 18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30 31 from mod_pywebsocket import common 32 from mod_pywebsocket import util 33 from mod_pywebsocket.http_header_util import quote_if_necessary 34 35 36 # The list of available server side extension processor classes. 37 _available_processors = {} 38 _compression_extension_names = [] 39 40 41 class ExtensionProcessorInterface(object): 42 43 def __init__(self, request): 44 self._logger = util.get_class_logger(self) 45 46 self._request = request 47 self._active = True 48 49 def request(self): 50 return self._request 51 52 def name(self): 53 return None 54 55 def check_consistency_with_other_processors(self, processors): 56 pass 57 58 def set_active(self, active): 59 self._active = active 60 61 def is_active(self): 62 return self._active 63 64 def _get_extension_response_internal(self): 65 return None 66 67 def get_extension_response(self): 68 if not self._active: 69 self._logger.debug('Extension %s is deactivated', self.name()) 70 return None 71 72 response = self._get_extension_response_internal() 73 if response is None: 74 self._active = False 75 return response 76 77 def _setup_stream_options_internal(self, stream_options): 78 pass 79 80 def setup_stream_options(self, stream_options): 81 if self._active: 82 self._setup_stream_options_internal(stream_options) 83 84 85 def _log_outgoing_compression_ratio( 86 logger, original_bytes, filtered_bytes, average_ratio): 87 # Print inf when ratio is not available. 88 ratio = float('inf') 89 if original_bytes != 0: 90 ratio = float(filtered_bytes) / original_bytes 91 92 logger.debug('Outgoing compression ratio: %f (average: %f)' % 93 (ratio, average_ratio)) 94 95 96 def _log_incoming_compression_ratio( 97 logger, received_bytes, filtered_bytes, average_ratio): 98 # Print inf when ratio is not available. 99 ratio = float('inf') 100 if filtered_bytes != 0: 101 ratio = float(received_bytes) / filtered_bytes 102 103 logger.debug('Incoming compression ratio: %f (average: %f)' % 104 (ratio, average_ratio)) 105 106 107 def _parse_window_bits(bits): 108 """Return parsed integer value iff the given string conforms to the 109 grammar of the window bits extension parameters. 110 """ 111 112 if bits is None: 113 raise ValueError('Value is required') 114 115 # For non integer values such as "10.0", ValueError will be raised. 116 int_bits = int(bits) 117 118 # First condition is to drop leading zero case e.g. "08". 119 if bits != str(int_bits) or int_bits < 8 or int_bits > 15: 120 raise ValueError('Invalid value: %r' % bits) 121 122 return int_bits 123 124 125 class _AverageRatioCalculator(object): 126 """Stores total bytes of original and result data, and calculates average 127 result / original ratio. 128 """ 129 130 def __init__(self): 131 self._total_original_bytes = 0 132 self._total_result_bytes = 0 133 134 def add_original_bytes(self, value): 135 self._total_original_bytes += value 136 137 def add_result_bytes(self, value): 138 self._total_result_bytes += value 139 140 def get_average_ratio(self): 141 if self._total_original_bytes != 0: 142 return (float(self._total_result_bytes) / 143 self._total_original_bytes) 144 else: 145 return float('inf') 146 147 148 class DeflateFrameExtensionProcessor(ExtensionProcessorInterface): 149 """deflate-frame extension processor. 150 151 Specification: 152 http://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate 153 """ 154 155 _WINDOW_BITS_PARAM = 'max_window_bits' 156 _NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover' 157 158 def __init__(self, request): 159 ExtensionProcessorInterface.__init__(self, request) 160 self._logger = util.get_class_logger(self) 161 162 self._response_window_bits = None 163 self._response_no_context_takeover = False 164 self._bfinal = False 165 166 # Calculates 167 # (Total outgoing bytes supplied to this filter) / 168 # (Total bytes sent to the network after applying this filter) 169 self._outgoing_average_ratio_calculator = _AverageRatioCalculator() 170 171 # Calculates 172 # (Total bytes received from the network) / 173 # (Total incoming bytes obtained after applying this filter) 174 self._incoming_average_ratio_calculator = _AverageRatioCalculator() 175 176 def name(self): 177 return common.DEFLATE_FRAME_EXTENSION 178 179 def _get_extension_response_internal(self): 180 # Any unknown parameter will be just ignored. 181 182 window_bits = None 183 if self._request.has_parameter(self._WINDOW_BITS_PARAM): 184 window_bits = self._request.get_parameter_value( 185 self._WINDOW_BITS_PARAM) 186 try: 187 window_bits = _parse_window_bits(window_bits) 188 except ValueError, e: 189 return None 190 191 no_context_takeover = self._request.has_parameter( 192 self._NO_CONTEXT_TAKEOVER_PARAM) 193 if (no_context_takeover and 194 self._request.get_parameter_value( 195 self._NO_CONTEXT_TAKEOVER_PARAM) is not None): 196 return None 197 198 self._rfc1979_deflater = util._RFC1979Deflater( 199 window_bits, no_context_takeover) 200 201 self._rfc1979_inflater = util._RFC1979Inflater() 202 203 self._compress_outgoing = True 204 205 response = common.ExtensionParameter(self._request.name()) 206 207 if self._response_window_bits is not None: 208 response.add_parameter( 209 self._WINDOW_BITS_PARAM, str(self._response_window_bits)) 210 if self._response_no_context_takeover: 211 response.add_parameter( 212 self._NO_CONTEXT_TAKEOVER_PARAM, None) 213 214 self._logger.debug( 215 'Enable %s extension (' 216 'request: window_bits=%s; no_context_takeover=%r, ' 217 'response: window_wbits=%s; no_context_takeover=%r)' % 218 (self._request.name(), 219 window_bits, 220 no_context_takeover, 221 self._response_window_bits, 222 self._response_no_context_takeover)) 223 224 return response 225 226 def _setup_stream_options_internal(self, stream_options): 227 228 class _OutgoingFilter(object): 229 230 def __init__(self, parent): 231 self._parent = parent 232 233 def filter(self, frame): 234 self._parent._outgoing_filter(frame) 235 236 class _IncomingFilter(object): 237 238 def __init__(self, parent): 239 self._parent = parent 240 241 def filter(self, frame): 242 self._parent._incoming_filter(frame) 243 244 stream_options.outgoing_frame_filters.append( 245 _OutgoingFilter(self)) 246 stream_options.incoming_frame_filters.insert( 247 0, _IncomingFilter(self)) 248 249 def set_response_window_bits(self, value): 250 self._response_window_bits = value 251 252 def set_response_no_context_takeover(self, value): 253 self._response_no_context_takeover = value 254 255 def set_bfinal(self, value): 256 self._bfinal = value 257 258 def enable_outgoing_compression(self): 259 self._compress_outgoing = True 260 261 def disable_outgoing_compression(self): 262 self._compress_outgoing = False 263 264 def _outgoing_filter(self, frame): 265 """Transform outgoing frames. This method is called only by 266 an _OutgoingFilter instance. 267 """ 268 269 original_payload_size = len(frame.payload) 270 self._outgoing_average_ratio_calculator.add_original_bytes( 271 original_payload_size) 272 273 if (not self._compress_outgoing or 274 common.is_control_opcode(frame.opcode)): 275 self._outgoing_average_ratio_calculator.add_result_bytes( 276 original_payload_size) 277 return 278 279 frame.payload = self._rfc1979_deflater.filter( 280 frame.payload, bfinal=self._bfinal) 281 frame.rsv1 = 1 282 283 filtered_payload_size = len(frame.payload) 284 self._outgoing_average_ratio_calculator.add_result_bytes( 285 filtered_payload_size) 286 287 _log_outgoing_compression_ratio( 288 self._logger, 289 original_payload_size, 290 filtered_payload_size, 291 self._outgoing_average_ratio_calculator.get_average_ratio()) 292 293 def _incoming_filter(self, frame): 294 """Transform incoming frames. This method is called only by 295 an _IncomingFilter instance. 296 """ 297 298 received_payload_size = len(frame.payload) 299 self._incoming_average_ratio_calculator.add_result_bytes( 300 received_payload_size) 301 302 if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode): 303 self._incoming_average_ratio_calculator.add_original_bytes( 304 received_payload_size) 305 return 306 307 frame.payload = self._rfc1979_inflater.filter(frame.payload) 308 frame.rsv1 = 0 309 310 filtered_payload_size = len(frame.payload) 311 self._incoming_average_ratio_calculator.add_original_bytes( 312 filtered_payload_size) 313 314 _log_incoming_compression_ratio( 315 self._logger, 316 received_payload_size, 317 filtered_payload_size, 318 self._incoming_average_ratio_calculator.get_average_ratio()) 319 320 321 _available_processors[common.DEFLATE_FRAME_EXTENSION] = ( 322 DeflateFrameExtensionProcessor) 323 _compression_extension_names.append(common.DEFLATE_FRAME_EXTENSION) 324 325 _available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = ( 326 DeflateFrameExtensionProcessor) 327 _compression_extension_names.append(common.X_WEBKIT_DEFLATE_FRAME_EXTENSION) 328 329 330 def _parse_compression_method(data): 331 """Parses the value of "method" extension parameter.""" 332 333 return common.parse_extensions(data) 334 335 336 def _create_accepted_method_desc(method_name, method_params): 337 """Creates accepted-method-desc from given method name and parameters""" 338 339 extension = common.ExtensionParameter(method_name) 340 for name, value in method_params: 341 extension.add_parameter(name, value) 342 return common.format_extension(extension) 343 344 345 class CompressionExtensionProcessorBase(ExtensionProcessorInterface): 346 """Base class for perframe-compress and permessage-compress extension.""" 347 348 _METHOD_PARAM = 'method' 349 350 def __init__(self, request): 351 ExtensionProcessorInterface.__init__(self, request) 352 self._logger = util.get_class_logger(self) 353 self._compression_method_name = None 354 self._compression_processor = None 355 self._compression_processor_hook = None 356 357 def name(self): 358 return '' 359 360 def _lookup_compression_processor(self, method_desc): 361 return None 362 363 def _get_compression_processor_response(self): 364 """Looks up the compression processor based on the self._request and 365 returns the compression processor's response. 366 """ 367 368 method_list = self._request.get_parameter_value(self._METHOD_PARAM) 369 if method_list is None: 370 return None 371 methods = _parse_compression_method(method_list) 372 if methods is None: 373 return None 374 comression_processor = None 375 # The current implementation tries only the first method that matches 376 # supported algorithm. Following methods aren't tried even if the 377 # first one is rejected. 378 # TODO(bashi): Need to clarify this behavior. 379 for method_desc in methods: 380 compression_processor = self._lookup_compression_processor( 381 method_desc) 382 if compression_processor is not None: 383 self._compression_method_name = method_desc.name() 384 break 385 if compression_processor is None: 386 return None 387 388 if self._compression_processor_hook: 389 self._compression_processor_hook(compression_processor) 390 391 processor_response = compression_processor.get_extension_response() 392 if processor_response is None: 393 return None 394 self._compression_processor = compression_processor 395 return processor_response 396 397 def _get_extension_response_internal(self): 398 processor_response = self._get_compression_processor_response() 399 if processor_response is None: 400 return None 401 402 response = common.ExtensionParameter(self._request.name()) 403 accepted_method_desc = _create_accepted_method_desc( 404 self._compression_method_name, 405 processor_response.get_parameters()) 406 response.add_parameter(self._METHOD_PARAM, accepted_method_desc) 407 self._logger.debug( 408 'Enable %s extension (method: %s)' % 409 (self._request.name(), self._compression_method_name)) 410 return response 411 412 def _setup_stream_options_internal(self, stream_options): 413 if self._compression_processor is None: 414 return 415 self._compression_processor.setup_stream_options(stream_options) 416 417 def set_compression_processor_hook(self, hook): 418 self._compression_processor_hook = hook 419 420 def get_compression_processor(self): 421 return self._compression_processor 422 423 424 class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface): 425 """permessage-deflate extension processor. It's also used for 426 permessage-compress extension when the deflate method is chosen. 427 428 Specification: 429 http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-08 430 """ 431 432 _SERVER_MAX_WINDOW_BITS_PARAM = 'server_max_window_bits' 433 _SERVER_NO_CONTEXT_TAKEOVER_PARAM = 'server_no_context_takeover' 434 _CLIENT_MAX_WINDOW_BITS_PARAM = 'client_max_window_bits' 435 _CLIENT_NO_CONTEXT_TAKEOVER_PARAM = 'client_no_context_takeover' 436 437 def __init__(self, request, draft08=True): 438 """Construct PerMessageDeflateExtensionProcessor 439 440 Args: 441 draft08: Follow the constraints on the parameters that were not 442 specified for permessage-compress but are specified for 443 permessage-deflate as on 444 draft-ietf-hybi-permessage-compression-08. 445 """ 446 447 ExtensionProcessorInterface.__init__(self, request) 448 self._logger = util.get_class_logger(self) 449 450 self._preferred_client_max_window_bits = None 451 self._client_no_context_takeover = False 452 453 self._draft08 = draft08 454 455 def name(self): 456 return 'deflate' 457 458 def _get_extension_response_internal(self): 459 if self._draft08: 460 for name in self._request.get_parameter_names(): 461 if name not in [self._SERVER_MAX_WINDOW_BITS_PARAM, 462 self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, 463 self._CLIENT_MAX_WINDOW_BITS_PARAM]: 464 self._logger.debug('Unknown parameter: %r', name) 465 return None 466 else: 467 # Any unknown parameter will be just ignored. 468 pass 469 470 server_max_window_bits = None 471 if self._request.has_parameter(self._SERVER_MAX_WINDOW_BITS_PARAM): 472 server_max_window_bits = self._request.get_parameter_value( 473 self._SERVER_MAX_WINDOW_BITS_PARAM) 474 try: 475 server_max_window_bits = _parse_window_bits( 476 server_max_window_bits) 477 except ValueError, e: 478 self._logger.debug('Bad %s parameter: %r', 479 self._SERVER_MAX_WINDOW_BITS_PARAM, 480 e) 481 return None 482 483 server_no_context_takeover = self._request.has_parameter( 484 self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) 485 if (server_no_context_takeover and 486 self._request.get_parameter_value( 487 self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) is not None): 488 self._logger.debug('%s parameter must not have a value: %r', 489 self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, 490 server_no_context_takeover) 491 return None 492 493 # client_max_window_bits from a client indicates whether the client can 494 # accept client_max_window_bits from a server or not. 495 client_client_max_window_bits = self._request.has_parameter( 496 self._CLIENT_MAX_WINDOW_BITS_PARAM) 497 if (self._draft08 and 498 client_client_max_window_bits and 499 self._request.get_parameter_value( 500 self._CLIENT_MAX_WINDOW_BITS_PARAM) is not None): 501 self._logger.debug('%s parameter must not have a value in a ' 502 'client\'s opening handshake: %r', 503 self._CLIENT_MAX_WINDOW_BITS_PARAM, 504 client_client_max_window_bits) 505 return None 506 507 self._rfc1979_deflater = util._RFC1979Deflater( 508 server_max_window_bits, server_no_context_takeover) 509 510 # Note that we prepare for incoming messages compressed with window 511 # bits upto 15 regardless of the client_max_window_bits value to be 512 # sent to the client. 513 self._rfc1979_inflater = util._RFC1979Inflater() 514 515 self._framer = _PerMessageDeflateFramer( 516 server_max_window_bits, server_no_context_takeover) 517 self._framer.set_bfinal(False) 518 self._framer.set_compress_outgoing_enabled(True) 519 520 response = common.ExtensionParameter(self._request.name()) 521 522 if server_max_window_bits is not None: 523 response.add_parameter( 524 self._SERVER_MAX_WINDOW_BITS_PARAM, 525 str(server_max_window_bits)) 526 527 if server_no_context_takeover: 528 response.add_parameter( 529 self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, None) 530 531 if self._preferred_client_max_window_bits is not None: 532 if self._draft08 and not client_client_max_window_bits: 533 self._logger.debug('Processor is configured to use %s but ' 534 'the client cannot accept it', 535 self._CLIENT_MAX_WINDOW_BITS_PARAM) 536 return None 537 response.add_parameter( 538 self._CLIENT_MAX_WINDOW_BITS_PARAM, 539 str(self._preferred_client_max_window_bits)) 540 541 if self._client_no_context_takeover: 542 response.add_parameter( 543 self._CLIENT_NO_CONTEXT_TAKEOVER_PARAM, None) 544 545 self._logger.debug( 546 'Enable %s extension (' 547 'request: server_max_window_bits=%s; ' 548 'server_no_context_takeover=%r, ' 549 'response: client_max_window_bits=%s; ' 550 'client_no_context_takeover=%r)' % 551 (self._request.name(), 552 server_max_window_bits, 553 server_no_context_takeover, 554 self._preferred_client_max_window_bits, 555 self._client_no_context_takeover)) 556 557 return response 558 559 def _setup_stream_options_internal(self, stream_options): 560 self._framer.setup_stream_options(stream_options) 561 562 def set_client_max_window_bits(self, value): 563 """If this option is specified, this class adds the 564 client_max_window_bits extension parameter to the handshake response, 565 but doesn't reduce the LZ77 sliding window size of its inflater. 566 I.e., you can use this for testing client implementation but cannot 567 reduce memory usage of this class. 568 569 If this method has been called with True and an offer without the 570 client_max_window_bits extension parameter is received, 571 - (When processing the permessage-deflate extension) this processor 572 declines the request. 573 - (When processing the permessage-compress extension) this processor 574 accepts the request. 575 """ 576 577 self._preferred_client_max_window_bits = value 578 579 def set_client_no_context_takeover(self, value): 580 """If this option is specified, this class adds the 581 client_no_context_takeover extension parameter to the handshake 582 response, but doesn't reset inflater for each message. I.e., you can 583 use this for testing client implementation but cannot reduce memory 584 usage of this class. 585 """ 586 587 self._client_no_context_takeover = value 588 589 def set_bfinal(self, value): 590 self._framer.set_bfinal(value) 591 592 def enable_outgoing_compression(self): 593 self._framer.set_compress_outgoing_enabled(True) 594 595 def disable_outgoing_compression(self): 596 self._framer.set_compress_outgoing_enabled(False) 597 598 599 class _PerMessageDeflateFramer(object): 600 """A framer for extensions with per-message DEFLATE feature.""" 601 602 def __init__(self, deflate_max_window_bits, deflate_no_context_takeover): 603 self._logger = util.get_class_logger(self) 604 605 self._rfc1979_deflater = util._RFC1979Deflater( 606 deflate_max_window_bits, deflate_no_context_takeover) 607 608 self._rfc1979_inflater = util._RFC1979Inflater() 609 610 self._bfinal = False 611 612 self._compress_outgoing_enabled = False 613 614 # True if a message is fragmented and compression is ongoing. 615 self._compress_ongoing = False 616 617 # Calculates 618 # (Total outgoing bytes supplied to this filter) / 619 # (Total bytes sent to the network after applying this filter) 620 self._outgoing_average_ratio_calculator = _AverageRatioCalculator() 621 622 # Calculates 623 # (Total bytes received from the network) / 624 # (Total incoming bytes obtained after applying this filter) 625 self._incoming_average_ratio_calculator = _AverageRatioCalculator() 626 627 def set_bfinal(self, value): 628 self._bfinal = value 629 630 def set_compress_outgoing_enabled(self, value): 631 self._compress_outgoing_enabled = value 632 633 def _process_incoming_message(self, message, decompress): 634 if not decompress: 635 return message 636 637 received_payload_size = len(message) 638 self._incoming_average_ratio_calculator.add_result_bytes( 639 received_payload_size) 640 641 message = self._rfc1979_inflater.filter(message) 642 643 filtered_payload_size = len(message) 644 self._incoming_average_ratio_calculator.add_original_bytes( 645 filtered_payload_size) 646 647 _log_incoming_compression_ratio( 648 self._logger, 649 received_payload_size, 650 filtered_payload_size, 651 self._incoming_average_ratio_calculator.get_average_ratio()) 652 653 return message 654 655 def _process_outgoing_message(self, message, end, binary): 656 if not binary: 657 message = message.encode('utf-8') 658 659 if not self._compress_outgoing_enabled: 660 return message 661 662 original_payload_size = len(message) 663 self._outgoing_average_ratio_calculator.add_original_bytes( 664 original_payload_size) 665 666 message = self._rfc1979_deflater.filter( 667 message, end=end, bfinal=self._bfinal) 668 669 filtered_payload_size = len(message) 670 self._outgoing_average_ratio_calculator.add_result_bytes( 671 filtered_payload_size) 672 673 _log_outgoing_compression_ratio( 674 self._logger, 675 original_payload_size, 676 filtered_payload_size, 677 self._outgoing_average_ratio_calculator.get_average_ratio()) 678 679 if not self._compress_ongoing: 680 self._outgoing_frame_filter.set_compression_bit() 681 self._compress_ongoing = not end 682 return message 683 684 def _process_incoming_frame(self, frame): 685 if frame.rsv1 == 1 and not common.is_control_opcode(frame.opcode): 686 self._incoming_message_filter.decompress_next_message() 687 frame.rsv1 = 0 688 689 def _process_outgoing_frame(self, frame, compression_bit): 690 if (not compression_bit or 691 common.is_control_opcode(frame.opcode)): 692 return 693 694 frame.rsv1 = 1 695 696 def setup_stream_options(self, stream_options): 697 """Creates filters and sets them to the StreamOptions.""" 698 699 class _OutgoingMessageFilter(object): 700 701 def __init__(self, parent): 702 self._parent = parent 703 704 def filter(self, message, end=True, binary=False): 705 return self._parent._process_outgoing_message( 706 message, end, binary) 707 708 class _IncomingMessageFilter(object): 709 710 def __init__(self, parent): 711 self._parent = parent 712 self._decompress_next_message = False 713 714 def decompress_next_message(self): 715 self._decompress_next_message = True 716 717 def filter(self, message): 718 message = self._parent._process_incoming_message( 719 message, self._decompress_next_message) 720 self._decompress_next_message = False 721 return message 722 723 self._outgoing_message_filter = _OutgoingMessageFilter(self) 724 self._incoming_message_filter = _IncomingMessageFilter(self) 725 stream_options.outgoing_message_filters.append( 726 self._outgoing_message_filter) 727 stream_options.incoming_message_filters.append( 728 self._incoming_message_filter) 729 730 class _OutgoingFrameFilter(object): 731 732 def __init__(self, parent): 733 self._parent = parent 734 self._set_compression_bit = False 735 736 def set_compression_bit(self): 737 self._set_compression_bit = True 738 739 def filter(self, frame): 740 self._parent._process_outgoing_frame( 741 frame, self._set_compression_bit) 742 self._set_compression_bit = False 743 744 class _IncomingFrameFilter(object): 745 746 def __init__(self, parent): 747 self._parent = parent 748 749 def filter(self, frame): 750 self._parent._process_incoming_frame(frame) 751 752 self._outgoing_frame_filter = _OutgoingFrameFilter(self) 753 self._incoming_frame_filter = _IncomingFrameFilter(self) 754 stream_options.outgoing_frame_filters.append( 755 self._outgoing_frame_filter) 756 stream_options.incoming_frame_filters.append( 757 self._incoming_frame_filter) 758 759 stream_options.encode_text_message_to_utf8 = False 760 761 762 _available_processors[common.PERMESSAGE_DEFLATE_EXTENSION] = ( 763 PerMessageDeflateExtensionProcessor) 764 # TODO(tyoshino): Reorganize class names. 765 _compression_extension_names.append('deflate') 766 767 768 class PerMessageCompressExtensionProcessor( 769 CompressionExtensionProcessorBase): 770 """permessage-compress extension processor. 771 772 Specification: 773 http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression 774 """ 775 776 _DEFLATE_METHOD = 'deflate' 777 778 def __init__(self, request): 779 CompressionExtensionProcessorBase.__init__(self, request) 780 781 def name(self): 782 return common.PERMESSAGE_COMPRESSION_EXTENSION 783 784 def _lookup_compression_processor(self, method_desc): 785 if method_desc.name() == self._DEFLATE_METHOD: 786 return PerMessageDeflateExtensionProcessor(method_desc, False) 787 return None 788 789 790 _available_processors[common.PERMESSAGE_COMPRESSION_EXTENSION] = ( 791 PerMessageCompressExtensionProcessor) 792 _compression_extension_names.append(common.PERMESSAGE_COMPRESSION_EXTENSION) 793 794 795 class MuxExtensionProcessor(ExtensionProcessorInterface): 796 """WebSocket multiplexing extension processor.""" 797 798 _QUOTA_PARAM = 'quota' 799 800 def __init__(self, request): 801 ExtensionProcessorInterface.__init__(self, request) 802 self._quota = 0 803 self._extensions = [] 804 805 def name(self): 806 return common.MUX_EXTENSION 807 808 def check_consistency_with_other_processors(self, processors): 809 before_mux = True 810 for processor in processors: 811 name = processor.name() 812 if name == self.name(): 813 before_mux = False 814 continue 815 if not processor.is_active(): 816 continue 817 if before_mux: 818 # Mux extension cannot be used after extensions 819 # that depend on frame boundary, extension data field, or any 820 # reserved bits which are attributed to each frame. 821 if (name == common.DEFLATE_FRAME_EXTENSION or 822 name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION): 823 self.set_active(False) 824 return 825 else: 826 # Mux extension should not be applied before any history-based 827 # compression extension. 828 if (name == common.DEFLATE_FRAME_EXTENSION or 829 name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION or 830 name == common.PERMESSAGE_COMPRESSION_EXTENSION or 831 name == common.X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION): 832 self.set_active(False) 833 return 834 835 def _get_extension_response_internal(self): 836 self._active = False 837 quota = self._request.get_parameter_value(self._QUOTA_PARAM) 838 if quota is not None: 839 try: 840 quota = int(quota) 841 except ValueError, e: 842 return None 843 if quota < 0 or quota >= 2 ** 32: 844 return None 845 self._quota = quota 846 847 self._active = True 848 return common.ExtensionParameter(common.MUX_EXTENSION) 849 850 def _setup_stream_options_internal(self, stream_options): 851 pass 852 853 def set_quota(self, quota): 854 self._quota = quota 855 856 def quota(self): 857 return self._quota 858 859 def set_extensions(self, extensions): 860 self._extensions = extensions 861 862 def extensions(self): 863 return self._extensions 864 865 866 _available_processors[common.MUX_EXTENSION] = MuxExtensionProcessor 867 868 869 def get_extension_processor(extension_request): 870 """Given an ExtensionParameter representing an extension offer received 871 from a client, configures and returns an instance of the corresponding 872 extension processor class. 873 """ 874 875 processor_class = _available_processors.get(extension_request.name()) 876 if processor_class is None: 877 return None 878 return processor_class(extension_request) 879 880 881 def is_compression_extension(extension_name): 882 return extension_name in _compression_extension_names 883 884 885 # vi:sts=4 sw=4 et 886