@@ -476,8 +476,11 @@ def parse(self, chunk: t_ByteString) -> Generator[t_ParserEvent, None, None]:
476476 bufferlen = len (buffer )
477477 offset = 0
478478
479- while True :
479+ while offset < bufferlen :
480480 if self ._state is _PREAMBLE :
481+ if bufferlen < d_len :
482+ break # Not enough data to find the initial delimiter
483+
481484 # Scan for first delimiter (CRLF prefix is optional here)
482485 index = buffer .find (delimiter [2 :], offset )
483486
@@ -502,16 +505,21 @@ def parse(self, chunk: t_ByteString) -> Generator[t_ParserEvent, None, None]:
502505 break # parsing complete
503506 elif tail [0 :1 ] == b"\n " : # Broken client or legacy test case
504507 raise ParserError ("Invalid line break after first boundary" )
505- elif len ( tail ) == 2 :
508+ elif next_start <= bufferlen :
506509 raise ParserError ("Unexpected byte after first boundary" )
510+ else : # 2-byte tail not in buffer
511+ offset = max (0 , index - 2 )
512+ break # wait for more data
507513
508514 elif self .strict and bufferlen >= d_len :
509515 # No boundary in first chunk -> Fail fast in strict mode
510516 # and do not waste time consuming a legacy preamble.
511517 raise StrictParserError ("Boundary not found in first chunk" )
512518
513- # Delimiter not found, skip data until we find one
514- offset = max (0 , bufferlen - (d_len + 1 ))
519+ # Boundary not found. Skip the preamble, but keep any bytes that may
520+ # belong to a partial boundary at the end of the buffer.
521+ index = buffer .rfind (b"\r " , bufferlen - (d_len - 1 ))
522+ offset = bufferlen if index == - 1 else index
515523 break # wait for more data
516524
517525 elif self ._state is _HEADER :
@@ -538,34 +546,52 @@ def parse(self, chunk: t_ByteString) -> Generator[t_ParserEvent, None, None]:
538546 break # wait for more data
539547
540548 elif self ._state is _BODY :
541- # Scan for delimiter : CRLF + boundary + (CRLF or '--')
549+ # Scan for next boundary : CRLF + boundary
542550 index = buffer .find (delimiter , offset )
543551 if index > - 1 :
552+ # Emit everything up to the boundary
553+ if index > offset :
554+ yield self ._on_segment_payload (buffer [offset :index ])
555+ offset = index
556+
544557 next_start = index + d_len + 2
545558 tail = buffer [next_start - 2 : next_start ]
546559
547- if tail == b"\r \n " or tail == b"--" :
548- if index > offset :
549- yield self ._on_segment_payload (buffer [offset :index ])
550-
551- offset = next_start
560+ if tail == b"\r \n " :
561+ # Normal boundary: CRLF + boundary + CRLF
562+ self ._on_segment_complete ()
563+ yield None # end of segment
564+ offset += d_len + 2
565+ self ._on_segment_start ()
566+ self ._state = _HEADER
567+ continue
568+ elif tail == b"--" :
569+ # Final boundary: CRLF + boundary + '--'
552570 self ._on_segment_complete ()
553571 yield None # end of segment
572+ offset += d_len + 2
573+ self ._state = _COMPLETE
574+ break
575+ elif next_start > bufferlen : # 2-byte tail not in buffer
576+ break # wait for more data
577+ else :
578+ raise ParserError ("Unexpected bytes after boundary" )
579+
580+ # Boundary not found. Emit as much data as we can, but keep any bytes
581+ # that may belong to a partial boundary at the end of the buffer.
582+ index = buffer .rfind (b"\r " , max (offset , bufferlen - (d_len - 1 )))
583+ if index == - 1 or not delimiter .startswith (buffer [index :]):
584+ # No partail boundary found, emit everything
585+ # This is a huge deal because it avoids buffer stitching next round
586+ yield self ._on_segment_payload (
587+ buffer [offset :] if offset else buffer
588+ )
589+ offset = bufferlen
590+ elif index > offset :
591+ # Potential partial boundary found. Emit data up to that point
592+ yield self ._on_segment_payload (buffer [offset :index ])
593+ offset = index
554594
555- if tail == b"--" : # Last delimiter
556- self ._state = _COMPLETE
557- break
558- else : # Normal delimiter
559- self ._on_segment_start ()
560- self ._state = _HEADER
561- continue
562-
563- # Only consume bytes that cannot be part of a partial delimiter at
564- # the end of the buffer.
565- flush_until = bufferlen - (d_len + 1 )
566- if flush_until > offset :
567- yield self ._on_segment_payload (buffer [offset :flush_until ])
568- offset = flush_until
569595 break # wait for more data
570596
571597 else : # pragma: no cover
0 commit comments