|
8 | 8 |
|
9 | 9 | from ..exceptions import ( |
10 | 10 | DuplicateParameter, InvalidParameterName, InvalidParameterValue, |
11 | | - NegotiationError |
| 11 | + NegotiationError, PayloadTooBig |
12 | 12 | ) |
13 | 13 | from ..framing import CTRL_OPCODES, OP_CONT |
14 | 14 |
|
@@ -463,7 +463,7 @@ def __repr__(self): |
463 | 463 | self.local_max_window_bits), |
464 | 464 | ])) |
465 | 465 |
|
466 | | - def decode(self, frame): |
| 466 | + def decode(self, frame, *, max_size=None): |
467 | 467 | """ |
468 | 468 | Decode an incoming frame. |
469 | 469 |
|
@@ -495,11 +495,18 @@ def decode(self, frame): |
495 | 495 | self.decoder = zlib.decompressobj( |
496 | 496 | wbits=-self.remote_max_window_bits) |
497 | 497 |
|
498 | | - # Uncompress compressed frames. |
| 498 | + # Uncompress compressed frames. Protect against zip bombs by |
| 499 | + # preventing zlib from decompressing more than max_length bytes |
| 500 | + # (except when the limit is disabled with max_size = None). |
499 | 501 | data = frame.data |
500 | 502 | if frame.fin: |
501 | 503 | data += _EMPTY_UNCOMPRESSED_BLOCK |
502 | | - data = self.decoder.decompress(data) |
| 504 | + max_length = 0 if max_size is None else max_size |
| 505 | + data = self.decoder.decompress(data, max_length) |
| 506 | + if self.decoder.unconsumed_tail: |
| 507 | + raise PayloadTooBig( |
| 508 | + "Uncompressed payload length exceeds size limit (? > {} bytes)" |
| 509 | + .format(max_size)) |
503 | 510 |
|
504 | 511 | # Allow garbage collection of the decoder if it won't be reused. |
505 | 512 | if frame.fin and self.remote_no_context_takeover: |
|
0 commit comments