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 31from mod_pywebsocket import common 32from mod_pywebsocket import util 33from 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 41class 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 85def _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 96def _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 107def _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 125class _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 148class 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 330class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface): 331 """permessage-deflate extension processor. 332 333 Specification: 334 http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-08 335 """ 336 337 _SERVER_MAX_WINDOW_BITS_PARAM = 'server_max_window_bits' 338 _SERVER_NO_CONTEXT_TAKEOVER_PARAM = 'server_no_context_takeover' 339 _CLIENT_MAX_WINDOW_BITS_PARAM = 'client_max_window_bits' 340 _CLIENT_NO_CONTEXT_TAKEOVER_PARAM = 'client_no_context_takeover' 341 342 def __init__(self, request, draft08=True): 343 """Construct PerMessageDeflateExtensionProcessor 344 345 Args: 346 draft08: Follow the constraints on the parameters that were not 347 specified for permessage-compress but are specified for 348 permessage-deflate as on 349 draft-ietf-hybi-permessage-compression-08. 350 """ 351 352 ExtensionProcessorInterface.__init__(self, request) 353 self._logger = util.get_class_logger(self) 354 355 self._preferred_client_max_window_bits = None 356 self._client_no_context_takeover = False 357 358 self._draft08 = draft08 359 360 def name(self): 361 # This method returns "deflate" (not "permessage-deflate") for 362 # compatibility. 363 return 'deflate' 364 365 def _get_extension_response_internal(self): 366 if self._draft08: 367 for name in self._request.get_parameter_names(): 368 if name not in [self._SERVER_MAX_WINDOW_BITS_PARAM, 369 self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, 370 self._CLIENT_MAX_WINDOW_BITS_PARAM]: 371 self._logger.debug('Unknown parameter: %r', name) 372 return None 373 else: 374 # Any unknown parameter will be just ignored. 375 pass 376 377 server_max_window_bits = None 378 if self._request.has_parameter(self._SERVER_MAX_WINDOW_BITS_PARAM): 379 server_max_window_bits = self._request.get_parameter_value( 380 self._SERVER_MAX_WINDOW_BITS_PARAM) 381 try: 382 server_max_window_bits = _parse_window_bits( 383 server_max_window_bits) 384 except ValueError, e: 385 self._logger.debug('Bad %s parameter: %r', 386 self._SERVER_MAX_WINDOW_BITS_PARAM, 387 e) 388 return None 389 390 server_no_context_takeover = self._request.has_parameter( 391 self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) 392 if (server_no_context_takeover and 393 self._request.get_parameter_value( 394 self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) is not None): 395 self._logger.debug('%s parameter must not have a value: %r', 396 self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, 397 server_no_context_takeover) 398 return None 399 400 # client_max_window_bits from a client indicates whether the client can 401 # accept client_max_window_bits from a server or not. 402 client_client_max_window_bits = self._request.has_parameter( 403 self._CLIENT_MAX_WINDOW_BITS_PARAM) 404 if (self._draft08 and 405 client_client_max_window_bits and 406 self._request.get_parameter_value( 407 self._CLIENT_MAX_WINDOW_BITS_PARAM) is not None): 408 self._logger.debug('%s parameter must not have a value in a ' 409 'client\'s opening handshake: %r', 410 self._CLIENT_MAX_WINDOW_BITS_PARAM, 411 client_client_max_window_bits) 412 return None 413 414 self._rfc1979_deflater = util._RFC1979Deflater( 415 server_max_window_bits, server_no_context_takeover) 416 417 # Note that we prepare for incoming messages compressed with window 418 # bits upto 15 regardless of the client_max_window_bits value to be 419 # sent to the client. 420 self._rfc1979_inflater = util._RFC1979Inflater() 421 422 self._framer = _PerMessageDeflateFramer( 423 server_max_window_bits, server_no_context_takeover) 424 self._framer.set_bfinal(False) 425 self._framer.set_compress_outgoing_enabled(True) 426 427 response = common.ExtensionParameter(self._request.name()) 428 429 if server_max_window_bits is not None: 430 response.add_parameter( 431 self._SERVER_MAX_WINDOW_BITS_PARAM, 432 str(server_max_window_bits)) 433 434 if server_no_context_takeover: 435 response.add_parameter( 436 self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, None) 437 438 if self._preferred_client_max_window_bits is not None: 439 if self._draft08 and not client_client_max_window_bits: 440 self._logger.debug('Processor is configured to use %s but ' 441 'the client cannot accept it', 442 self._CLIENT_MAX_WINDOW_BITS_PARAM) 443 return None 444 response.add_parameter( 445 self._CLIENT_MAX_WINDOW_BITS_PARAM, 446 str(self._preferred_client_max_window_bits)) 447 448 if self._client_no_context_takeover: 449 response.add_parameter( 450 self._CLIENT_NO_CONTEXT_TAKEOVER_PARAM, None) 451 452 self._logger.debug( 453 'Enable %s extension (' 454 'request: server_max_window_bits=%s; ' 455 'server_no_context_takeover=%r, ' 456 'response: client_max_window_bits=%s; ' 457 'client_no_context_takeover=%r)' % 458 (self._request.name(), 459 server_max_window_bits, 460 server_no_context_takeover, 461 self._preferred_client_max_window_bits, 462 self._client_no_context_takeover)) 463 464 return response 465 466 def _setup_stream_options_internal(self, stream_options): 467 self._framer.setup_stream_options(stream_options) 468 469 def set_client_max_window_bits(self, value): 470 """If this option is specified, this class adds the 471 client_max_window_bits extension parameter to the handshake response, 472 but doesn't reduce the LZ77 sliding window size of its inflater. 473 I.e., you can use this for testing client implementation but cannot 474 reduce memory usage of this class. 475 476 If this method has been called with True and an offer without the 477 client_max_window_bits extension parameter is received, 478 - (When processing the permessage-deflate extension) this processor 479 declines the request. 480 - (When processing the permessage-compress extension) this processor 481 accepts the request. 482 """ 483 484 self._preferred_client_max_window_bits = value 485 486 def set_client_no_context_takeover(self, value): 487 """If this option is specified, this class adds the 488 client_no_context_takeover extension parameter to the handshake 489 response, but doesn't reset inflater for each message. I.e., you can 490 use this for testing client implementation but cannot reduce memory 491 usage of this class. 492 """ 493 494 self._client_no_context_takeover = value 495 496 def set_bfinal(self, value): 497 self._framer.set_bfinal(value) 498 499 def enable_outgoing_compression(self): 500 self._framer.set_compress_outgoing_enabled(True) 501 502 def disable_outgoing_compression(self): 503 self._framer.set_compress_outgoing_enabled(False) 504 505 506class _PerMessageDeflateFramer(object): 507 """A framer for extensions with per-message DEFLATE feature.""" 508 509 def __init__(self, deflate_max_window_bits, deflate_no_context_takeover): 510 self._logger = util.get_class_logger(self) 511 512 self._rfc1979_deflater = util._RFC1979Deflater( 513 deflate_max_window_bits, deflate_no_context_takeover) 514 515 self._rfc1979_inflater = util._RFC1979Inflater() 516 517 self._bfinal = False 518 519 self._compress_outgoing_enabled = False 520 521 # True if a message is fragmented and compression is ongoing. 522 self._compress_ongoing = False 523 524 # Calculates 525 # (Total outgoing bytes supplied to this filter) / 526 # (Total bytes sent to the network after applying this filter) 527 self._outgoing_average_ratio_calculator = _AverageRatioCalculator() 528 529 # Calculates 530 # (Total bytes received from the network) / 531 # (Total incoming bytes obtained after applying this filter) 532 self._incoming_average_ratio_calculator = _AverageRatioCalculator() 533 534 def set_bfinal(self, value): 535 self._bfinal = value 536 537 def set_compress_outgoing_enabled(self, value): 538 self._compress_outgoing_enabled = value 539 540 def _process_incoming_message(self, message, decompress): 541 if not decompress: 542 return message 543 544 received_payload_size = len(message) 545 self._incoming_average_ratio_calculator.add_result_bytes( 546 received_payload_size) 547 548 message = self._rfc1979_inflater.filter(message) 549 550 filtered_payload_size = len(message) 551 self._incoming_average_ratio_calculator.add_original_bytes( 552 filtered_payload_size) 553 554 _log_incoming_compression_ratio( 555 self._logger, 556 received_payload_size, 557 filtered_payload_size, 558 self._incoming_average_ratio_calculator.get_average_ratio()) 559 560 return message 561 562 def _process_outgoing_message(self, message, end, binary): 563 if not binary: 564 message = message.encode('utf-8') 565 566 if not self._compress_outgoing_enabled: 567 return message 568 569 original_payload_size = len(message) 570 self._outgoing_average_ratio_calculator.add_original_bytes( 571 original_payload_size) 572 573 message = self._rfc1979_deflater.filter( 574 message, end=end, bfinal=self._bfinal) 575 576 filtered_payload_size = len(message) 577 self._outgoing_average_ratio_calculator.add_result_bytes( 578 filtered_payload_size) 579 580 _log_outgoing_compression_ratio( 581 self._logger, 582 original_payload_size, 583 filtered_payload_size, 584 self._outgoing_average_ratio_calculator.get_average_ratio()) 585 586 if not self._compress_ongoing: 587 self._outgoing_frame_filter.set_compression_bit() 588 self._compress_ongoing = not end 589 return message 590 591 def _process_incoming_frame(self, frame): 592 if frame.rsv1 == 1 and not common.is_control_opcode(frame.opcode): 593 self._incoming_message_filter.decompress_next_message() 594 frame.rsv1 = 0 595 596 def _process_outgoing_frame(self, frame, compression_bit): 597 if (not compression_bit or 598 common.is_control_opcode(frame.opcode)): 599 return 600 601 frame.rsv1 = 1 602 603 def setup_stream_options(self, stream_options): 604 """Creates filters and sets them to the StreamOptions.""" 605 606 class _OutgoingMessageFilter(object): 607 608 def __init__(self, parent): 609 self._parent = parent 610 611 def filter(self, message, end=True, binary=False): 612 return self._parent._process_outgoing_message( 613 message, end, binary) 614 615 class _IncomingMessageFilter(object): 616 617 def __init__(self, parent): 618 self._parent = parent 619 self._decompress_next_message = False 620 621 def decompress_next_message(self): 622 self._decompress_next_message = True 623 624 def filter(self, message): 625 message = self._parent._process_incoming_message( 626 message, self._decompress_next_message) 627 self._decompress_next_message = False 628 return message 629 630 self._outgoing_message_filter = _OutgoingMessageFilter(self) 631 self._incoming_message_filter = _IncomingMessageFilter(self) 632 stream_options.outgoing_message_filters.append( 633 self._outgoing_message_filter) 634 stream_options.incoming_message_filters.append( 635 self._incoming_message_filter) 636 637 class _OutgoingFrameFilter(object): 638 639 def __init__(self, parent): 640 self._parent = parent 641 self._set_compression_bit = False 642 643 def set_compression_bit(self): 644 self._set_compression_bit = True 645 646 def filter(self, frame): 647 self._parent._process_outgoing_frame( 648 frame, self._set_compression_bit) 649 self._set_compression_bit = False 650 651 class _IncomingFrameFilter(object): 652 653 def __init__(self, parent): 654 self._parent = parent 655 656 def filter(self, frame): 657 self._parent._process_incoming_frame(frame) 658 659 self._outgoing_frame_filter = _OutgoingFrameFilter(self) 660 self._incoming_frame_filter = _IncomingFrameFilter(self) 661 stream_options.outgoing_frame_filters.append( 662 self._outgoing_frame_filter) 663 stream_options.incoming_frame_filters.append( 664 self._incoming_frame_filter) 665 666 stream_options.encode_text_message_to_utf8 = False 667 668 669_available_processors[common.PERMESSAGE_DEFLATE_EXTENSION] = ( 670 PerMessageDeflateExtensionProcessor) 671# TODO(tyoshino): Reorganize class names. 672_compression_extension_names.append('deflate') 673 674 675class MuxExtensionProcessor(ExtensionProcessorInterface): 676 """WebSocket multiplexing extension processor.""" 677 678 _QUOTA_PARAM = 'quota' 679 680 def __init__(self, request): 681 ExtensionProcessorInterface.__init__(self, request) 682 self._quota = 0 683 self._extensions = [] 684 685 def name(self): 686 return common.MUX_EXTENSION 687 688 def check_consistency_with_other_processors(self, processors): 689 before_mux = True 690 for processor in processors: 691 name = processor.name() 692 if name == self.name(): 693 before_mux = False 694 continue 695 if not processor.is_active(): 696 continue 697 if before_mux: 698 # Mux extension cannot be used after extensions 699 # that depend on frame boundary, extension data field, or any 700 # reserved bits which are attributed to each frame. 701 if (name == common.DEFLATE_FRAME_EXTENSION or 702 name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION): 703 self.set_active(False) 704 return 705 else: 706 # Mux extension should not be applied before any history-based 707 # compression extension. 708 if (name == 'deflate' or 709 name == common.DEFLATE_FRAME_EXTENSION or 710 name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION): 711 self.set_active(False) 712 return 713 714 def _get_extension_response_internal(self): 715 self._active = False 716 quota = self._request.get_parameter_value(self._QUOTA_PARAM) 717 if quota is not None: 718 try: 719 quota = int(quota) 720 except ValueError, e: 721 return None 722 if quota < 0 or quota >= 2 ** 32: 723 return None 724 self._quota = quota 725 726 self._active = True 727 return common.ExtensionParameter(common.MUX_EXTENSION) 728 729 def _setup_stream_options_internal(self, stream_options): 730 pass 731 732 def set_quota(self, quota): 733 self._quota = quota 734 735 def quota(self): 736 return self._quota 737 738 def set_extensions(self, extensions): 739 self._extensions = extensions 740 741 def extensions(self): 742 return self._extensions 743 744 745_available_processors[common.MUX_EXTENSION] = MuxExtensionProcessor 746 747 748def get_extension_processor(extension_request): 749 """Given an ExtensionParameter representing an extension offer received 750 from a client, configures and returns an instance of the corresponding 751 extension processor class. 752 """ 753 754 processor_class = _available_processors.get(extension_request.name()) 755 if processor_class is None: 756 return None 757 return processor_class(extension_request) 758 759 760def is_compression_extension(extension_name): 761 return extension_name in _compression_extension_names 762 763 764# vi:sts=4 sw=4 et 765