2727from email import message
2828from email ._policybase import compat32
2929from collections import deque
30+ from io import StringIO
3031
3132NLCRE = re .compile ('\r \n |\r |\n ' )
3233NLCRE_bol = re .compile ('(\r \n |\r |\n )' )
@@ -51,8 +52,9 @@ class BufferedSubFile(object):
5152 simple abstraction -- it parses until EOF closes the current message.
5253 """
5354 def __init__ (self ):
54- # Chunks of the last partial line pushed into this object.
55- self ._partial = []
55+ # Text stream of the last partial line pushed into this object.
56+ # See issue 22233 for why this is a text stream and not a list.
57+ self ._partial = StringIO (newline = '' )
5658 # A deque of full, pushed lines
5759 self ._lines = deque ()
5860 # The stack of false-EOF checking predicates.
@@ -68,8 +70,10 @@ def pop_eof_matcher(self):
6870
6971 def close (self ):
7072 # Don't forget any trailing partial line.
71- self .pushlines ('' .join (self ._partial ).splitlines (True ))
72- self ._partial = []
73+ self ._partial .seek (0 )
74+ self .pushlines (self ._partial .readlines ())
75+ self ._partial .seek (0 )
76+ self ._partial .truncate ()
7377 self ._closed = True
7478
7579 def readline (self ):
@@ -97,26 +101,23 @@ def unreadline(self, line):
97101
98102 def push (self , data ):
99103 """Push some new data into this object."""
100- # Crack into lines, but preserve the linesep characters on the end of each
101- parts = data .splitlines (True )
102-
103- if not parts or not parts [0 ].endswith (('\n ' , '\r ' )):
104- # No new complete lines, so just accumulate partials
105- self ._partial += parts
104+ self ._partial .write (data )
105+ if '\n ' not in data and '\r ' not in data :
106+ # No new complete lines, wait for more.
106107 return
107108
108- if self . _partial :
109- # If there are previous leftovers, complete them now
110- self ._partial .append ( parts [ 0 ] )
111- parts [ 0 : 1 ] = '' . join ( self ._partial ). splitlines ( True )
112- del self ._partial [:]
109+ # Crack into lines, preserving the linesep characters.
110+ self . _partial . seek ( 0 )
111+ parts = self ._partial .readlines ( )
112+ self ._partial . seek ( 0 )
113+ self ._partial . truncate ()
113114
114115 # If the last element of the list does not end in a newline, then treat
115116 # it as a partial line. We only check for '\n' here because a line
116117 # ending with '\r' might be a line that was split in the middle of a
117118 # '\r\n' sequence (see bugs 1555570 and 1721862).
118119 if not parts [- 1 ].endswith ('\n ' ):
119- self ._partial = [ parts .pop ()]
120+ self ._partial . write ( parts .pop ())
120121 self .pushlines (parts )
121122
122123 def pushlines (self , lines ):
0 commit comments