Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #18796 -- Refactored conversion to bytes in HttpResponse

Thanks mrmachine for the review.
  • Loading branch information...
commit da56e1bac6449daef9aeab8d076d2594d9fd5b44 1 parent ce1eb32
Aymeric Augustin authored October 24, 2012
57  django/http/response.py
@@ -16,6 +16,7 @@
16 16
 from django.utils import six, timezone
17 17
 from django.utils.encoding import force_bytes, iri_to_uri
18 18
 from django.utils.http import cookie_date
  19
+from django.utils.six.moves import map
19 20
 
20 21
 
21 22
 class BadHeaderError(ValueError):
@@ -191,18 +192,33 @@ def delete_cookie(self, key, path='/', domain=None):
191 192
 
192 193
     def make_bytes(self, value):
193 194
         """Turn a value into a bytestring encoded in the output charset."""
194  
-        # For backwards compatibility, this method supports values that are
195  
-        # unlikely to occur in real applications. It has grown complex and
196  
-        # should be refactored. It also overlaps __next__. See #18796.
  195
+        # Per PEP 3333, this response body must be bytes. To avoid returning
  196
+        # an instance of a subclass, this function returns `bytes(value)`.
  197
+        # This doesn't make a copy when `value` already contains bytes.
  198
+
  199
+        # If content is already encoded (eg. gzip), assume bytes.
197 200
         if self.has_header('Content-Encoding'):
198  
-            if isinstance(value, int):
199  
-                value = six.text_type(value)
200  
-            if isinstance(value, six.text_type):
201  
-                value = value.encode('ascii')
202  
-            # force conversion to bytes in case chunk is a subclass
203 201
             return bytes(value)
204  
-        else:
205  
-            return force_bytes(value, self._charset)
  202
+
  203
+        # Handle string types -- we can't rely on force_bytes here because:
  204
+        # - under Python 3 it attemps str conversion first
  205
+        # - when self._charset != 'utf-8' it re-encodes the content
  206
+        if isinstance(value, bytes):
  207
+            return bytes(value)
  208
+        if isinstance(value, six.text_type):
  209
+            return bytes(value.encode(self._charset))
  210
+
  211
+        # Handle non-string types (#16494)
  212
+        return force_bytes(value, self._charset)
  213
+
  214
+    def __iter__(self):
  215
+        return self
  216
+
  217
+    def __next__(self):
  218
+        # Subclasses must define self._iterator for this function.
  219
+        return self.make_bytes(next(self._iterator))
  220
+
  221
+    next = __next__             # Python 2 compatibility
206 222
 
207 223
     # These methods partially implement the file-like object interface.
208 224
     # See http://docs.python.org/lib/bltin-file-objects.html
@@ -287,17 +303,6 @@ def __iter__(self):
287 303
             self._iterator = iter(self._container)
288 304
         return self
289 305
 
290  
-    def __next__(self):
291  
-        chunk = next(self._iterator)
292  
-        if isinstance(chunk, int):
293  
-            chunk = six.text_type(chunk)
294  
-        if isinstance(chunk, six.text_type):
295  
-            chunk = chunk.encode(self._charset)
296  
-        # force conversion to bytes in case chunk is a subclass
297  
-        return bytes(chunk)
298  
-
299  
-    next = __next__             # Python 2 compatibility
300  
-
301 306
     def write(self, content):
302 307
         self._consume_content()
303 308
         self._container.append(content)
@@ -331,7 +336,7 @@ def content(self):
331 336
 
332 337
     @property
333 338
     def streaming_content(self):
334  
-        return self._iterator
  339
+        return map(self.make_bytes, self._iterator)
335 340
 
336 341
     @streaming_content.setter
337 342
     def streaming_content(self, value):
@@ -340,14 +345,6 @@ def streaming_content(self, value):
340 345
         if hasattr(value, 'close'):
341 346
             self._closable_objects.append(value)
342 347
 
343  
-    def __iter__(self):
344  
-        return self
345  
-
346  
-    def __next__(self):
347  
-        return self.make_bytes(next(self._iterator))
348  
-
349  
-    next = __next__             # Python 2 compatibility
350  
-
351 348
 
352 349
 class CompatibleStreamingHttpResponse(StreamingHttpResponse):
353 350
     """
9  tests/regressiontests/httpwrappers/tests.py
@@ -330,11 +330,12 @@ def test_iter_content(self):
330 330
         self.assertEqual(r.content, b'123\xde\x9e')
331 331
 
332 332
         #with Content-Encoding header
333  
-        r = HttpResponse([1,1,2,4,8])
  333
+        r = HttpResponse()
334 334
         r['Content-Encoding'] = 'winning'
335  
-        self.assertEqual(r.content, b'11248')
336  
-        r.content = ['\u079e',]
337  
-        self.assertRaises(UnicodeEncodeError,
  335
+        r.content = [b'abc', b'def']
  336
+        self.assertEqual(r.content, b'abcdef')
  337
+        r.content = ['\u079e']
  338
+        self.assertRaises(TypeError if six.PY3 else UnicodeEncodeError,
338 339
                           getattr, r, 'content')
339 340
 
340 341
         # .content can safely be accessed multiple times.

0 notes on commit da56e1b

Please sign in to comment.
Something went wrong with that request. Please try again.