55import zlib
66
77from pymysqlreplication .constants .STATUS_VAR_KEY import *
8- from pymysqlreplication .exceptions import StatusVariableMismatch
8+ from pymysqlreplication .exceptions import MalformedBinLogEvent , StatusVariableMismatch
99from pymysqlreplication .util .bytes import parse_decimal_from_bytes
1010from pymysqlreplication .logger import logger
1111from typing import Union , Optional
@@ -27,15 +27,18 @@ def __init__(
2727 freeze_schema = False ,
2828 ignore_decode_errors = False ,
2929 verify_checksum = False ,
30+ use_checksum = False ,
3031 optional_meta_data = False ,
3132 enable_logging = False ,
3233 use_column_name_cache = False ,
34+ post_header_lengths = None ,
3335 ):
3436 self .packet = from_packet
3537 self .table_map = table_map
3638 self .event_type = self .packet .event_type
3739 self .timestamp = self .packet .timestamp
3840 self .event_size = event_size
41+ self ._post_header_lengths = post_header_lengths
3942 self ._ctl_connection = ctl_connection
4043 self .mysql_version = mysql_version
4144 self ._ignore_decode_errors = ignore_decode_errors
@@ -48,6 +51,38 @@ def __init__(
4851 self ._verify_event ()
4952 self .dbms = self ._ctl_connection ._get_dbms ()
5053
54+ def _declared_post_header_len (self , default ):
55+ if not self ._post_header_lengths :
56+ return default
57+
58+ index = self .event_type - 1
59+ if index < 0 or index >= len (self ._post_header_lengths ):
60+ return default
61+
62+ return self ._post_header_lengths [index ]
63+
64+ def _advance_to_payload (self , known_post_header_len ):
65+ declared_post_header_len = self ._declared_post_header_len (known_post_header_len )
66+
67+ if declared_post_header_len < known_post_header_len :
68+ raise MalformedBinLogEvent (
69+ "Event type %s declares post-header length %s, shorter than "
70+ "the supported minimum %s"
71+ % (self .event_type , declared_post_header_len , known_post_header_len )
72+ )
73+
74+ extra_len = declared_post_header_len - known_post_header_len
75+ if self .packet .read_bytes + extra_len > self .event_size :
76+ raise MalformedBinLogEvent (
77+ "Event type %s declares post-header length %s beyond event size %s"
78+ % (self .event_type , declared_post_header_len , self .event_size )
79+ )
80+
81+ if extra_len :
82+ self .packet .advance (extra_len )
83+
84+ return declared_post_header_len
85+
5186 def _read_table_id (self ):
5287 # Table ID is 6 byte
5388 # pad little-endian number
@@ -389,15 +424,37 @@ def __init__(self, from_packet, event_size, table_map, ctl_connection, **kwargs)
389424 self .mysql_version = tuple (map (int , numbers .split ("." )))
390425 self .created = struct .unpack ("<I" , self .packet .read (4 ))[0 ]
391426 self .common_header_len = struct .unpack ("<B" , self .packet .read (1 ))[0 ]
392- offset = (
393- 4 + 2 + 50 + 1
394- ) # created + binlog_version + mysql_version_str + common_header_len
395- checksum_algorithm = 1
396- checksum = 4
397- n = event_size - offset - self .common_header_len - checksum_algorithm - checksum
398- self .post_header_len = struct .unpack (f"<{ n } B" , self .packet .read (n ))
399- self .server_version_split = struct .unpack ("<3B" , self .packet .read (3 ))
400- self .number_of_event_types = struct .unpack ("<B" , self .packet .read (1 ))[0 ]
427+
428+ fixed_part_len = 2 + 50 + 4 + 1
429+ if event_size < fixed_part_len :
430+ raise MalformedBinLogEvent (
431+ "FormatDescriptionEvent size %s is shorter than fixed part %s"
432+ % (event_size , fixed_part_len )
433+ )
434+
435+ remaining_len = event_size - fixed_part_len
436+ checksum_aware = self .mysql_version >= (5 , 6 ) or self .mysql_version [0 ] >= 10
437+ checksum_algorithm_len = 1 if checksum_aware else 0
438+ checksum_len = 0 if kwargs .get ("use_checksum" ) else 4 if checksum_aware else 0
439+ post_header_len = remaining_len - checksum_algorithm_len - checksum_len
440+
441+ if post_header_len < 0 :
442+ raise MalformedBinLogEvent (
443+ "FormatDescriptionEvent size %s is too short for checksum metadata"
444+ % event_size
445+ )
446+
447+ self .post_header_len = struct .unpack (
448+ f"<{ post_header_len } B" , self .packet .read (post_header_len )
449+ )
450+ self .checksum_algorithm = (
451+ self .packet .read_uint8 () if checksum_algorithm_len else None
452+ )
453+ if checksum_len :
454+ self .packet .advance (checksum_len )
455+
456+ self .server_version_split = self .mysql_version [:3 ]
457+ self .number_of_event_types = len (self .post_header_len )
401458
402459 def _dump (self ):
403460 print (f"Binlog version: { self .binlog_version } " )
@@ -488,6 +545,7 @@ def __init__(self, from_packet, event_size, table_map, ctl_connection, **kwargs)
488545 self .schema_length = struct .unpack ("!B" , self .packet .read (1 ))[0 ]
489546 self .error_code = self .packet .read_uint16 ()
490547 self .status_vars_length = self .packet .read_uint16 ()
548+ post_header_len = self ._advance_to_payload (13 )
491549
492550 # Payload
493551 status_vars_end_pos = self .packet .read_bytes + self .status_vars_length
@@ -499,9 +557,19 @@ def __init__(self, from_packet, event_size, table_map, ctl_connection, **kwargs)
499557
500558 self .schema = self .packet .read (self .schema_length )
501559 self .packet .advance (1 )
502- query = self .packet .read (
503- event_size - 13 - self .status_vars_length - self .schema_length - 1
560+ query_len = (
561+ event_size
562+ - post_header_len
563+ - self .status_vars_length
564+ - self .schema_length
565+ - 1
504566 )
567+ if query_len < 0 :
568+ raise MalformedBinLogEvent (
569+ "QueryEvent payload length is negative after applying post-header "
570+ "length %s" % post_header_len
571+ )
572+ query = self .packet .read (query_len )
505573 self .query = query .decode ("utf-8" , errors = "backslashreplace" )
506574 # string[EOF] query
507575
0 commit comments