88
99"""Support for streamed reading and writing of multipart MIME content."""
1010
11+ from base64 import b64encode
1112from cgi import parse_header
13+ try :
14+ from hashlib import md5
15+ except ImportError :
16+ from md5 import new as md5
1217import sys
1318
1419__all__ = ['read_multipart' , 'write_multipart' ]
1520__docformat__ = 'restructuredtext en'
1621
1722
18- EOL = '\r \n '
23+ CRLF = '\r \n '
1924
2025
2126def read_multipart (fileobj , boundary = None ):
@@ -42,18 +47,26 @@ def read_multipart(fileobj, boundary=None):
4247 buf = []
4348 outer = in_headers = boundary is None
4449
45- next_boundary = boundary and '--' + boundary + EOL or None
46- last_boundary = boundary and '--' + boundary + '--' + EOL or None
50+ next_boundary = boundary and '--' + boundary + ' \n ' or None
51+ last_boundary = boundary and '--' + boundary + '--\n ' or None
4752
4853 def _current_part ():
4954 payload = '' .join (buf )
50- if payload .endswith (EOL ):
55+ if payload .endswith ('\r \n ' ):
56+ payload = payload [:- 2 ]
57+ elif payload .endswith ('\n ' ):
5158 payload = payload [:- 1 ]
59+ content_md5 = headers .get ('content-md5' )
60+ if content_md5 :
61+ h = b64encode (md5 (payload ).digest ())
62+ if content_md5 != h :
63+ raise ValueError ('data integrity check failed' )
5264 return headers , False , payload
5365
5466 for line in fileobj :
5567 if in_headers :
56- if line != EOL :
68+ line = line .replace (CRLF , '\n ' )
69+ if line != '\n ' :
5770 name , value = line .split (':' , 1 )
5871 headers [name .lower ().strip ()] = value .strip ()
5972 else :
@@ -71,7 +84,7 @@ def _current_part():
7184 yield part
7285 return
7386
74- elif line == next_boundary :
87+ elif line . replace ( CRLF , ' \n ' ) == next_boundary :
7588 # We've reached the start of a new part, as indicated by the
7689 # boundary
7790 if headers :
@@ -83,7 +96,7 @@ def _current_part():
8396 del buf [:]
8497 in_headers = True
8598
86- elif line == last_boundary :
99+ elif line . replace ( CRLF , ' \n ' ) == last_boundary :
87100 # We're done with this multipart envelope
88101 break
89102
@@ -111,48 +124,50 @@ def __init__(self, fileobj, headers=None, subtype='mixed', boundary=None):
111124 def open (self , headers = None , subtype = 'mixed' , boundary = None ):
112125 self .fileobj .write ('--' )
113126 self .fileobj .write (self .boundary )
114- self .fileobj .write (EOL )
127+ self .fileobj .write (CRLF )
115128 return MultipartWriter (self .fileobj , headers = headers , subtype = subtype ,
116129 boundary = boundary )
117130
118131 def add (self , mimetype , content , headers = None ):
119132 self .fileobj .write ('--' )
120133 self .fileobj .write (self .boundary )
121- self .fileobj .write (EOL )
134+ self .fileobj .write (CRLF )
122135 if headers is None :
123136 headers = {}
124137 headers ['Content-Type' ] = mimetype
125- headers ['Content-Length' ] = str (len (content ))
138+ if content :
139+ headers ['Content-Length' ] = str (len (content ))
140+ headers ['Content-MD5' ] = b64encode (md5 (content ).digest ())
126141 self ._write_headers (headers )
127142 if content :
128143 # XXX: throw an exception if a boundary appears in the content??
129144 self .fileobj .write (content )
130- self .fileobj .write (EOL )
145+ self .fileobj .write (CRLF )
131146
132147 def close (self ):
133148 self .fileobj .write ('--' )
134149 self .fileobj .write (self .boundary )
135150 self .fileobj .write ('--' )
136- self .fileobj .write (EOL )
151+ self .fileobj .write (CRLF )
137152
138153 def _make_boundary (self ):
139154 try :
140155 from uuid import uuid4
141- return uuid4 ().hex
156+ return '==' + uuid4 ().hex + '=='
142157 except ImportError :
143158 from random import randrange
144159 token = randrange (sys .maxint )
145160 format = '%%0%dd' % len (repr (sys .maxint - 1 ))
146- return ( '=' * 15 ) + (fmt % token ) + '=='
161+ return '===============' + (fmt % token ) + '=='
147162
148163 def _write_headers (self , headers ):
149164 if headers :
150165 for name in sorted (headers .keys ()):
151166 self .fileobj .write (name )
152167 self .fileobj .write (': ' )
153168 self .fileobj .write (headers [name ])
154- self .fileobj .write (EOL )
155- self .fileobj .write (EOL )
169+ self .fileobj .write (CRLF )
170+ self .fileobj .write (CRLF )
156171
157172 def __enter__ (self ):
158173 return self
0 commit comments