@@ -208,6 +208,9 @@ def _string_to_bytes(self, message):
208208 raise ValueError ("String input must be ASCII-only; "
209209 "use bytes or a Message instead" )
210210
211+ # Whether each message must end in a newline
212+ _append_newline = False
213+
211214 def _dump_message (self , message , target , mangle_from_ = False ):
212215 # This assumes the target file is open in binary mode.
213216 """Dump message contents to target file."""
@@ -219,6 +222,9 @@ def _dump_message(self, message, target, mangle_from_=False):
219222 data = buffer .read ()
220223 data = data .replace (b'\n ' , linesep )
221224 target .write (data )
225+ if self ._append_newline and not data .endswith (linesep ):
226+ # Make sure the message ends with a newline
227+ target .write (linesep )
222228 elif isinstance (message , (str , bytes , io .StringIO )):
223229 if isinstance (message , io .StringIO ):
224230 warnings .warn ("Use of StringIO input is deprecated, "
@@ -230,11 +236,15 @@ def _dump_message(self, message, target, mangle_from_=False):
230236 message = message .replace (b'\n From ' , b'\n >From ' )
231237 message = message .replace (b'\n ' , linesep )
232238 target .write (message )
239+ if self ._append_newline and not message .endswith (linesep ):
240+ # Make sure the message ends with a newline
241+ target .write (linesep )
233242 elif hasattr (message , 'read' ):
234243 if hasattr (message , 'buffer' ):
235244 warnings .warn ("Use of text mode files is deprecated, "
236245 "use a binary mode file instead" , DeprecationWarning , 3 )
237246 message = message .buffer
247+ lastline = None
238248 while True :
239249 line = message .readline ()
240250 # Universal newline support.
@@ -248,6 +258,10 @@ def _dump_message(self, message, target, mangle_from_=False):
248258 line = b'>From ' + line [5 :]
249259 line = line .replace (b'\n ' , linesep )
250260 target .write (line )
261+ lastline = line
262+ if self ._append_newline and lastline and not lastline .endswith (linesep ):
263+ # Make sure the message ends with a newline
264+ target .write (linesep )
251265 else :
252266 raise TypeError ('Invalid message type: %s' % type (message ))
253267
@@ -833,30 +847,48 @@ class mbox(_mboxMMDF):
833847
834848 _mangle_from_ = True
835849
850+ # All messages must end in a newline character, and
851+ # _post_message_hooks outputs an empty line between messages.
852+ _append_newline = True
853+
836854 def __init__ (self , path , factory = None , create = True ):
837855 """Initialize an mbox mailbox."""
838856 self ._message_factory = mboxMessage
839857 _mboxMMDF .__init__ (self , path , factory , create )
840858
841- def _pre_message_hook (self , f ):
842- """Called before writing each message to file f."""
843- if f .tell () != 0 :
844- f .write (linesep )
859+ def _post_message_hook (self , f ):
860+ """Called after writing each message to file f."""
861+ f .write (linesep )
845862
846863 def _generate_toc (self ):
847864 """Generate key-to-(start, stop) table of contents."""
848865 starts , stops = [], []
866+ last_was_empty = False
849867 self ._file .seek (0 )
850868 while True :
851869 line_pos = self ._file .tell ()
852870 line = self ._file .readline ()
853871 if line .startswith (b'From ' ):
854872 if len (stops ) < len (starts ):
855- stops .append (line_pos - len (linesep ))
873+ if last_was_empty :
874+ stops .append (line_pos - len (linesep ))
875+ else :
876+ # The last line before the "From " line wasn't
877+ # blank, but we consider it a start of a
878+ # message anyway.
879+ stops .append (line_pos )
856880 starts .append (line_pos )
881+ last_was_empty = False
857882 elif not line :
858- stops .append (line_pos )
883+ if last_was_empty :
884+ stops .append (line_pos - len (linesep ))
885+ else :
886+ stops .append (line_pos )
859887 break
888+ elif line == linesep :
889+ last_was_empty = True
890+ else :
891+ last_was_empty = False
860892 self ._toc = dict (enumerate (zip (starts , stops )))
861893 self ._next_key = len (self ._toc )
862894 self ._file_length = self ._file .tell ()
0 commit comments