23
23
__docformat__ = 'restructuredtext en'
24
24
25
25
26
- CRLF = '\r \n '
26
+ CRLF = b '\r \n '
27
27
28
28
29
29
def read_multipart (fileobj , boundary = None ):
@@ -50,16 +50,16 @@ def read_multipart(fileobj, boundary=None):
50
50
buf = []
51
51
outer = in_headers = boundary is None
52
52
53
- next_boundary = boundary and '--' + boundary + '\n ' or None
54
- last_boundary = boundary and '--' + boundary + '--\n ' or None
53
+ next_boundary = boundary and ( '--' + boundary + '\n ' ). encode ( 'ascii' ) or None
54
+ last_boundary = boundary and ( '--' + boundary + '--\n ' ). encode ( 'ascii' ) or None
55
55
56
56
def _current_part ():
57
- payload = '' .join (buf )
58
- if payload .endswith ('\r \n ' ):
57
+ payload = b '' .join (buf )
58
+ if payload .endswith (b '\r \n ' ):
59
59
payload = payload [:- 2 ]
60
- elif payload .endswith ('\n ' ):
60
+ elif payload .endswith (b '\n ' ):
61
61
payload = payload [:- 1 ]
62
- content_md5 = headers .get ('content-md5' )
62
+ content_md5 = headers .get (b 'content-md5' )
63
63
if content_md5 :
64
64
h = b64encode (md5 (payload ).digest ())
65
65
if content_md5 != h :
@@ -68,11 +68,11 @@ def _current_part():
68
68
69
69
for line in fileobj :
70
70
if in_headers :
71
- line = line .replace (CRLF , '\n ' )
72
- if line != '\n ' :
73
- name , value = [item .strip () for item in line .split (':' , 1 )]
74
- name = name .lower ()
75
- value , charset = header .decode_header (value )[0 ]
71
+ line = line .replace (CRLF , b '\n ' )
72
+ if line != b '\n ' :
73
+ name , value = [item .strip () for item in line .split (b ':' , 1 )]
74
+ name = name .lower (). decode ( 'ascii' )
75
+ value , charset = header .decode_header (value . decode ( 'utf-8' ) )[0 ]
76
76
if charset is None :
77
77
headers [name ] = value
78
78
else :
@@ -92,7 +92,7 @@ def _current_part():
92
92
yield part
93
93
return
94
94
95
- elif line .replace (CRLF , '\n ' ) == next_boundary :
95
+ elif line .replace (CRLF , b '\n ' ) == next_boundary :
96
96
# We've reached the start of a new part, as indicated by the
97
97
# boundary
98
98
if headers :
@@ -104,7 +104,7 @@ def _current_part():
104
104
del buf [:]
105
105
in_headers = True
106
106
107
- elif line .replace (CRLF , '\n ' ) == last_boundary :
107
+ elif line .replace (CRLF , b '\n ' ) == last_boundary :
108
108
# We're done with this multipart envelope
109
109
break
110
110
@@ -130,39 +130,47 @@ def __init__(self, fileobj, headers=None, subtype='mixed', boundary=None):
130
130
self ._write_headers (headers )
131
131
132
132
def open (self , headers = None , subtype = 'mixed' , boundary = None ):
133
- self .fileobj .write ('--' )
134
- self .fileobj .write (self .boundary )
133
+ self .fileobj .write (b '--' )
134
+ self .fileobj .write (self .boundary . encode ( 'utf-8' ) )
135
135
self .fileobj .write (CRLF )
136
136
return MultipartWriter (self .fileobj , headers = headers , subtype = subtype ,
137
137
boundary = boundary )
138
138
139
139
def add (self , mimetype , content , headers = None ):
140
- self .fileobj .write ('--' )
141
- self .fileobj .write (self .boundary )
140
+ self .fileobj .write (b '--' )
141
+ self .fileobj .write (self .boundary . encode ( 'utf-8' ) )
142
142
self .fileobj .write (CRLF )
143
143
if headers is None :
144
144
headers = {}
145
+
146
+ ctype , params = parse_header (mimetype )
145
147
if isinstance (content , util .utype ):
146
- ctype , params = parse_header (mimetype )
147
148
if 'charset' in params :
148
149
content = content .encode (params ['charset' ])
149
150
else :
150
151
content = content .encode ('utf-8' )
151
152
mimetype = mimetype + ';charset=utf-8'
153
+ elif 'charset' not in params :
154
+ try :
155
+ content .decode ('utf-8' )
156
+ finally :
157
+ mimetype = mimetype + ';charset=utf-8'
158
+
152
159
headers ['Content-Type' ] = mimetype
153
160
if content :
154
161
headers ['Content-Length' ] = str (len (content ))
155
- headers ['Content-MD5' ] = b64encode (md5 (content ).digest ())
162
+ hash = b64encode (md5 (content ).digest ()).decode ('ascii' )
163
+ headers ['Content-MD5' ] = hash
156
164
self ._write_headers (headers )
157
165
if content :
158
166
# XXX: throw an exception if a boundary appears in the content??
159
167
self .fileobj .write (content )
160
168
self .fileobj .write (CRLF )
161
169
162
170
def close (self ):
163
- self .fileobj .write ('--' )
164
- self .fileobj .write (self .boundary )
165
- self .fileobj .write ('--' )
171
+ self .fileobj .write (b '--' )
172
+ self .fileobj .write (self .boundary . encode ( 'ascii' ) )
173
+ self .fileobj .write (b '--' )
166
174
self .fileobj .write (CRLF )
167
175
168
176
def _make_boundary (self ):
@@ -171,19 +179,18 @@ def _make_boundary(self):
171
179
return '==' + uuid4 ().hex + '=='
172
180
except ImportError :
173
181
from random import randrange
174
- token = randrange (sys .maxsize )
175
- format = '%%0%dd' % len (repr (sys .maxsize - 1 ))
176
- return '===============' + (format % token ) + '=='
182
+ nonce = ('%%0%dd' % len (repr (sys .maxsize - 1 ))) % token
183
+ return '===============' + nonce + '=='
177
184
178
185
def _write_headers (self , headers ):
179
186
if headers :
180
187
for name in sorted (headers .keys ()):
181
188
value = headers [name ]
182
- if isinstance ( value , util . utype ):
183
- value = str ( header .make_header ([(value , 'utf-8' )]))
184
- self .fileobj .write (name )
185
- self .fileobj .write (': ' )
186
- self .fileobj .write (value )
189
+ if value . encode ( 'ascii' , 'ignore' ) != value . encode ( 'utf-8' ):
190
+ value = header .make_header ([(value , 'utf-8' )]). encode ( )
191
+ self .fileobj .write (name . encode ( 'utf-8' ) )
192
+ self .fileobj .write (b ': ' )
193
+ self .fileobj .write (value . encode ( 'utf-8' ) )
187
194
self .fileobj .write (CRLF )
188
195
self .fileobj .write (CRLF )
189
196
@@ -202,19 +209,19 @@ def write_multipart(fileobj, subtype='mixed', boundary=None):
202
209
envelope you call the ``add(mimetype, content, [headers])`` method for
203
210
every part, and finally call the ``close()`` method.
204
211
205
- >>> from StringIO import StringIO
212
+ >>> from couchdb.util import StringIO
206
213
207
214
>>> buf = StringIO()
208
215
>>> envelope = write_multipart(buf, boundary='==123456789==')
209
216
>>> envelope.add('text/plain', 'Just testing')
210
217
>>> envelope.close()
211
- >>> print(buf.getvalue().replace('\r\n', '\n'))
218
+ >>> print(buf.getvalue().replace(b '\r\n', b '\n'))
212
219
Content-Type: multipart/mixed; boundary="==123456789=="
213
220
<BLANKLINE>
214
221
--==123456789==
215
222
Content-Length: 12
216
223
Content-MD5: nHmX4a6el41B06x2uCpglQ==
217
- Content-Type: text/plain
224
+ Content-Type: text/plain;charset=utf-8
218
225
<BLANKLINE>
219
226
Just testing
220
227
--==123456789==--
@@ -233,7 +240,7 @@ def write_multipart(fileobj, subtype='mixed', boundary=None):
233
240
>>> part.add('text/plain', 'Just testing')
234
241
>>> part.close()
235
242
>>> envelope.close()
236
- >>> print(buf.getvalue().replace('\r\n', '\n')) #:doctest +ELLIPSIS
243
+ >>> print(buf.getvalue().replace(b '\r\n', b '\n')) #:doctest +ELLIPSIS
237
244
Content-Type: multipart/mixed; boundary="==123456789=="
238
245
<BLANKLINE>
239
246
--==123456789==
@@ -242,7 +249,7 @@ def write_multipart(fileobj, subtype='mixed', boundary=None):
242
249
--==abcdefghi==
243
250
Content-Length: 12
244
251
Content-MD5: nHmX4a6el41B06x2uCpglQ==
245
- Content-Type: text/plain
252
+ Content-Type: text/plain;charset=utf-8
246
253
<BLANKLINE>
247
254
Just testing
248
255
--==abcdefghi==--
0 commit comments