Ticket #18916: 18916.diff

File 18916.diff, 6.5 KB (added by aaugustin, 3 years ago)
  • django/http/__init__.py

    diff --git a/django/http/__init__.py b/django/http/__init__.py
    index 2198f38..b9f29ae 100644
    a b from __future__ import absolute_import, unicode_literals 
    22
    33import copy
    44import datetime
     5from email.header import Header
    56import os
    67import re
    78import sys
    class HttpResponse(object): 
    560561    else:
    561562        __str__ = serialize
    562563
    563     def _convert_to_ascii(self, *values):
    564         """Converts all values to ascii strings."""
    565         for value in values:
    566             if not isinstance(value, six.string_types):
    567                 value = str(value)
    568             try:
    569                 if six.PY3:
    570                     # Ensure string only contains ASCII
    571                     value.encode('us-ascii')
     564    def _convert_to_charset(self, value, charset, mime_encode=False):
     565        """Converts headers key/value to ascii/latin1 native strings
     566
     567        and optionally MIME-encode if the input doesn't fit in the charset.
     568        """
     569        if not isinstance(value, six.string_types):
     570            value = str(value)
     571        try:
     572            if six.PY3:
     573                # Ensure string is valid in given charset
     574                value.encode(charset)
     575            else:
     576                if isinstance(value, str):
     577                    # Ensure string is valid in given charset
     578                    value.decode(charset)
    572579                else:
    573                     if isinstance(value, str):
    574                         # Ensure string only contains ASCII
    575                         value.decode('us-ascii')
    576                     else:
    577                         # Convert unicode to an ASCII string
    578                         value = value.encode('us-ascii')
    579             except UnicodeError as e:
    580                 e.reason += ', HTTP response headers must be in US-ASCII format'
     580                    # Convert unicode string to given charset
     581                    value = value.encode(charset)
     582        except UnicodeError as e:
     583            if mime_encode:
     584                value = Header(value, 'utf-8').encode()
     585            else:
     586                e.reason += ', HTTP response headers must be in %s format' % charset
    581587                raise
    582             if '\n' in value or '\r' in value:
    583                 raise BadHeaderError("Header values can't contain newlines (got %r)" % value)
    584             yield value
     588        if str('\n') in value or str('\r') in value:
     589            raise BadHeaderError("Header values can't contain newlines (got %r)" % value)
     590        return value
    585591
    586592    def __setitem__(self, header, value):
    587         header, value = self._convert_to_ascii(header, value)
     593        header = self._convert_to_charset(header, 'ascii')
     594        value = self._convert_to_charset(value, 'latin1', mime_encode=True)
    588595        self._headers[header.lower()] = (header, value)
    589596
    590597    def __delitem__(self, header):
  • tests/regressiontests/httpwrappers/tests.py

    diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py
    index 21ba198..4c6aed1 100644
    a b from django.http import (QueryDict, HttpResponse, HttpResponseRedirect, 
    1111                         SimpleCookie, BadHeaderError,
    1212                         parse_cookie)
    1313from django.test import TestCase
     14from django.utils.encoding import smart_str
    1415from django.utils import six
    1516from django.utils import unittest
    1617
    class QueryDictTests(unittest.TestCase): 
    228229        self.assertEqual(copy.deepcopy(q).encoding, 'iso-8859-15')
    229230
    230231class HttpResponseTests(unittest.TestCase):
    231     def test_unicode_headers(self):
    232         r = HttpResponse()
    233 
    234         # If we insert a unicode value it will be converted to an ascii
    235         r['value'] = 'test value'
    236         self.assertTrue(isinstance(r['value'], str))
    237 
    238         # An error is raised when a unicode object with non-ascii is assigned.
    239         self.assertRaises(UnicodeEncodeError, r.__setitem__, 'value', 't\xebst value')
    240232
    241         # An error is raised when  a unicode object with non-ASCII format is
    242         # passed as initial mimetype or content_type.
    243         self.assertRaises(UnicodeEncodeError, HttpResponse,
    244                 content_type='t\xebst value')
     233    def test_headers_type(self):
     234        r = HttpResponse()
    245235
    246         # HttpResponse headers must be convertible to ASCII.
    247         self.assertRaises(UnicodeEncodeError, HttpResponse,
    248                 content_type='t\xebst value')
     236        # The following tests explicitly test types in addition to values
     237        # because in Python 2 u'foo' == b'foo'.
     238
     239        # ASCII unicode or bytes values are converted to native strings.
     240        r['key'] = 'test'
     241        self.assertEqual(r['key'], str('test'))
     242        self.assertIsInstance(r['key'], str)
     243        r['key'] = 'test'.encode('ascii')
     244        self.assertEqual(r['key'], str('test'))
     245        self.assertIsInstance(r['key'], str)
     246
     247        # Latin-1 unicode or bytes values are also converted to native strings.
     248        r['key'] = 'café'
     249        self.assertEqual(r['key'], smart_str('café', 'latin-1'))
     250        self.assertIsInstance(r['key'], str)
     251        r['key'] = 'café'.encode('latin-1')
     252        self.assertEqual(r['key'], smart_str('café', 'latin-1'))
     253        self.assertIsInstance(r['key'], str)
     254
     255        # Other unicode values are MIME-encoded (there's no way to pass them as bytes).
     256        r['key'] = '†'
     257        self.assertEqual(r['key'], str('=?utf-8?b?4oCg?='))
     258        self.assertIsInstance(r['key'], str)
     259
     260        # The response also converts unicode or bytes keys to strings, but requires
     261        # them to contain ASCII
     262        r = HttpResponse()
     263        r['foo'] = 'bar'
     264        l = list(r.items())
     265        self.assertEqual(l[0], ('foo', 'bar'))
     266        self.assertIsInstance(l[0][0], str)
    249267
    250         # The response also converts unicode keys to strings.)
    251         r['test'] = 'testing key'
     268        r = HttpResponse()
     269        r[b'foo'] = 'bar'
    252270        l = list(r.items())
    253         l.sort()
    254         self.assertEqual(l[1], ('test', 'testing key'))
     271        self.assertEqual(l[0], ('foo', 'bar'))
     272        self.assertIsInstance(l[0][0], str)
     273
     274        r = HttpResponse()
     275        self.assertRaises(UnicodeError, r.__setitem__, 'føø', 'bar')
     276        self.assertRaises(UnicodeError, r.__setitem__, 'føø'.encode('utf-8'), 'bar')
    255277
    256         # It will also raise errors for keys with non-ascii data.
    257         self.assertRaises(UnicodeEncodeError, r.__setitem__, 't\xebst key', 'value')
    258278
    259279    def test_newlines_in_headers(self):
    260280        # Bug #10188: Do not allow newlines in headers (CR or LF)
Back to Top