Ticket #15785: limited_stream_lazy.diff

File limited_stream_lazy.diff, 14.9 KB (added by tomchristie, 4 years ago)
  • tests/regressiontests/file_uploads/tests.py

     
    88
    99from django.core.files import temp as tempfile
    1010from django.core.files.uploadedfile import SimpleUploadedFile
    11 from django.http.multipartparser import MultiPartParser
     11from django.http.multipartparser import MultiPartParser, MultiPartParserError
    1212from django.test import TestCase, client
    1313from django.utils import simplejson
    1414from django.utils import unittest
     
    151151        got = simplejson.loads(self.client.request(**r).content)
    152152        self.assertTrue(len(got['file']) < 256, "Got a long file name (%s characters)." % len(got['file']))
    153153
     154    def test_truncated_multipart_handled_gracefully(self):
     155        """
     156        If passed an incomplete multipart message, MultiPartParser does not
     157        attempt to read beyond the end of the stream, and simply will handle
     158        the part that can be parsed gracefully.
     159        """
     160        payload = "\r\n".join([
     161            '--' + client.BOUNDARY,
     162            'Content-Disposition: form-data; name="file"; filename="foo.txt"',
     163            'Content-Type: application/octet-stream',
     164            '',
     165            'file contents'
     166            '--' + client.BOUNDARY + '--',
     167            '',
     168        ])
     169        payload = payload[:-10]
     170        r = {
     171            'CONTENT_LENGTH': len(payload),
     172            'CONTENT_TYPE':   client.MULTIPART_CONTENT,
     173            'PATH_INFO':      "/file_uploads/echo/",
     174            'REQUEST_METHOD': 'POST',
     175            'wsgi.input':     client.FakePayload(payload),
     176        }
     177        got = simplejson.loads(self.client.request(**r).content)
     178        self.assertEquals(got, {})
     179
     180    def test_empty_multipart_raises_error(self):
     181        """
     182        If passed an empty multipart message, MultiPartParser will throw
     183        MultiPartParserError.
     184        """
     185        r = {
     186            'CONTENT_LENGTH': 0,
     187            'CONTENT_TYPE':   client.MULTIPART_CONTENT,
     188            'PATH_INFO':      "/file_uploads/echo/",
     189            'REQUEST_METHOD': 'POST',
     190            'wsgi.input':     client.FakePayload(''),
     191        }
     192        self.assertRaises(MultiPartParserError, lambda r: self.client.request(**r), r)
     193
    154194    def test_custom_upload_handler(self):
    155195        # A small file (under the 5M quota)
    156196        smallfile = tempfile.NamedTemporaryFile()
  • tests/regressiontests/test_client_regress/views.py

     
    9696    "A view that is requested with GET and accesses request.raw_post_data. Refs #14753."
    9797    return HttpResponse(request.raw_post_data)
    9898
     99def read_limited_stream(request):
     100    "A view that is requested with PUT and accesses request.read(LARGE_BUFFER)."
     101    return HttpResponse(request.read(99999))
     102
    99103def request_context_view(request):
    100104    # Special attribute that won't be present on a plain HttpRequest
    101105    request.special_path = request.path
  • tests/regressiontests/test_client_regress/models.py

     
    908908            response = self.client.get("/test_client_regress/raw_post_data/")
    909909        except AssertionError:
    910910            self.fail("Accessing request.raw_post_data from a view fetched with GET by the test client shouldn't fail.")
     911
     912class ReadLimitedStreamTest(TestCase):
     913    """
     914    Attempting to read beyond META["CONTENT_LENGTH"] from the test client
     915    should simply return the full content of the request.
     916    """
     917    def test_read_limited_stream(self):
     918        try:
     919            response = self.client.put("/test_client_regress/read_limited_stream/", data={'foo':'whiz'})
     920        except AssertionError:
     921            self.fail("Reading more than META['CONTENT_LENGTH'] of data from a view fetched with PUT by the test client " +
     922                      "shouldn't fail, it should simply return the full content of the request.")       
  • tests/regressiontests/test_client_regress/urls.py

     
    2727    (r'^check_headers/$', views.check_headers),
    2828    (r'^check_headers_redirect/$', RedirectView.as_view(url='/test_client_regress/check_headers/')),
    2929    (r'^raw_post_data/$', views.raw_post_data),
     30    (r'^read_limited_stream/$', views.read_limited_stream),
    3031    (r'^request_context_view/$', views.request_context_view),
    3132)
  • tests/regressiontests/requests/tests.py

     
    156156        self.assertEqual(stream.read(), '')
    157157
    158158    def test_stream(self):
    159         request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')})
     159        payload = 'name=value'
     160        request = WSGIRequest({'REQUEST_METHOD': 'POST',
     161                               'CONTENT_LENGTH': len(payload),
     162                               'wsgi.input': StringIO(payload)})
    160163        self.assertEqual(request.read(), 'name=value')
    161164
    162165    def test_read_after_value(self):
     
    164167        Reading from request is allowed after accessing request contents as
    165168        POST or raw_post_data.
    166169        """
    167         request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')})
     170        payload = 'name=value'
     171        request = WSGIRequest({'REQUEST_METHOD': 'POST',
     172                               'CONTENT_LENGTH': len(payload),
     173                               'wsgi.input': StringIO(payload)})
    168174        self.assertEqual(request.POST, {u'name': [u'value']})
    169175        self.assertEqual(request.raw_post_data, 'name=value')
    170176        self.assertEqual(request.read(), 'name=value')
     
    174180        Construction of POST or raw_post_data is not allowed after reading
    175181        from request.
    176182        """
    177         request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')})
     183        payload = 'name=value'
     184        request = WSGIRequest({'REQUEST_METHOD': 'POST',
     185                               'CONTENT_LENGTH': len(payload),
     186                               'wsgi.input': StringIO(payload)})
    178187        self.assertEqual(request.read(2), 'na')
    179188        self.assertRaises(Exception, lambda: request.raw_post_data)
    180189        self.assertEqual(request.POST, {})
     
    201210        self.assertRaises(Exception, lambda: request.raw_post_data)
    202211
    203212    def test_read_by_lines(self):
    204         request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')})
     213        payload = 'name=value'
     214        request = WSGIRequest({'REQUEST_METHOD': 'POST',
     215                               'CONTENT_LENGTH': len(payload),
     216                               'wsgi.input': StringIO(payload)})
    205217        self.assertEqual(list(request), ['name=value'])
    206218
    207219    def test_POST_after_raw_post_data_read(self):
    208220        """
    209221        POST should be populated even if raw_post_data is read first
    210222        """
    211         request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')})
     223        payload = 'name=value'
     224        request = WSGIRequest({'REQUEST_METHOD': 'POST',
     225                               'CONTENT_LENGTH': len(payload),
     226                               'wsgi.input': StringIO(payload)})
    212227        raw_data = request.raw_post_data
    213228        self.assertEqual(request.POST, {u'name': [u'value']})
    214229
     
    217232        POST should be populated even if raw_post_data is read first, and then
    218233        the stream is read second.
    219234        """
    220         request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')})
     235        payload = 'name=value'
     236        request = WSGIRequest({'REQUEST_METHOD': 'POST',
     237                               'CONTENT_LENGTH': len(payload),
     238                               'wsgi.input': StringIO(payload)})
    221239        raw_data = request.raw_post_data
    222240        self.assertEqual(request.read(1), u'n')
    223241        self.assertEqual(request.POST, {u'name': [u'value']})
  • django/http/multipartparser.py

     
    3333    A rfc2388 multipart/form-data parser.
    3434
    3535    ``MultiValueDict.parse()`` reads the input stream in ``chunk_size`` chunks
    36     and returns a tuple of ``(MultiValueDict(POST), MultiValueDict(FILES))``. If
     36    and returns a tuple of ``(MultiValueDict(POST), MultiValueDict(FILES))``.
    3737    """
    3838    def __init__(self, META, input_data, upload_handlers, encoding=None):
    3939        """
     
    6565            raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary)
    6666
    6767
    68         #
    6968        # Content-Length should contain the length of the body we are about
    7069        # to receive.
    71         #
    7270        try:
    7371            content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH',0)))
    7472        except (ValueError, TypeError):
    75             # For now set it to 0; we'll try again later on down.
    7673            content_length = 0
    7774
    7875        if content_length <= 0:
     
    105102        encoding = self._encoding
    106103        handlers = self._upload_handlers
    107104
    108         limited_input_data = LimitBytes(self._input_data, self._content_length)
    109 
    110105        # See if the handler will want to take care of the parsing.
    111106        # This allows overriding everything if somebody wants it.
    112107        for handler in handlers:
    113             result = handler.handle_raw_input(limited_input_data,
     108            result = handler.handle_raw_input(self._input_data,
    114109                                              self._meta,
    115110                                              self._content_length,
    116111                                              self._boundary,
     
    123118        self._files = MultiValueDict()
    124119
    125120        # Instantiate the parser and stream:
    126         stream = LazyStream(ChunkIter(limited_input_data, self._chunk_size))
     121        stream = LazyStream(ChunkIter(self._input_data, self._chunk_size))
    127122
    128123        # Whether or not to signal a file-completion at the beginning of the loop.
    129124        old_field_name = None
     
    218213                    exhaust(stream)
    219214        except StopUpload, e:
    220215            if not e.connection_reset:
    221                 exhaust(limited_input_data)
     216                exhaust(self._input_data)
    222217        else:
    223218            # Make sure that the request data is all fed
    224             exhaust(limited_input_data)
     219            exhaust(self._input_data)
    225220
    226221        # Signal that the upload has completed.
    227222        for handler in handlers:
     
    383378    def __iter__(self):
    384379        return self
    385380
    386 class LimitBytes(object):
    387     """ Limit bytes for a file object. """
    388     def __init__(self, fileobject, length):
    389         self._file = fileobject
    390         self.remaining = length
    391 
    392     def read(self, num_bytes=None):
    393         """
    394         Read data from the underlying file.
    395         If you ask for too much or there isn't anything left,
    396         this will raise an InputStreamExhausted error.
    397         """
    398         if self.remaining <= 0:
    399             raise InputStreamExhausted()
    400         if num_bytes is None:
    401             num_bytes = self.remaining
    402         else:
    403             num_bytes = min(num_bytes, self.remaining)
    404         self.remaining -= num_bytes
    405         return self._file.read(num_bytes)
    406 
    407381class InterBoundaryIter(object):
    408382    """
    409383    A Producer that will iterate over boundaries.
  • django/http/__init__.py

     
    237237        if not hasattr(self, '_raw_post_data'):
    238238            if self._read_started:
    239239                raise Exception("You cannot access raw_post_data after reading from request's data stream")
    240             try:
    241                 content_length = int(self.META.get('CONTENT_LENGTH', 0))
    242             except (ValueError, TypeError):
    243                 # If CONTENT_LENGTH was empty string or not an integer, don't
    244                 # error out. We've also seen None passed in here (against all
    245                 # specs, but see ticket #8259), so we handle TypeError as well.
    246                 content_length = 0
    247             if content_length:
    248                 self._raw_post_data = self.read(content_length)
    249             else:
    250                 self._raw_post_data = self.read()
     240            self._raw_post_data = self.read()
    251241            self._stream = StringIO(self._raw_post_data)
    252242        return self._raw_post_data
    253243    raw_post_data = property(_get_raw_post_data)
  • django/core/handlers/wsgi.py

     
    135135        self.META['SCRIPT_NAME'] = script_name
    136136        self.method = environ['REQUEST_METHOD'].upper()
    137137        self._post_parse_error = False
    138         if type(socket._fileobject) is type and isinstance(self.environ['wsgi.input'], socket._fileobject):
    139             # Under development server 'wsgi.input' is an instance of
    140             # socket._fileobject which hangs indefinitely on reading bytes past
    141             # available count. To prevent this it's wrapped in LimitedStream
    142             # that doesn't read past Content-Length bytes.
    143             #
    144             # This is not done for other kinds of inputs (like flup's FastCGI
    145             # streams) beacuse they don't suffer from this problem and we can
    146             # avoid using another wrapper with its own .read and .readline
    147             # implementation.
    148             #
    149             # The type check is done because for some reason, AppEngine
    150             # implements _fileobject as a function, not a class.
    151             try:
    152                 content_length = int(self.environ.get('CONTENT_LENGTH', 0))
    153             except (ValueError, TypeError):
    154                 content_length = 0
    155             self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
    156         else:
    157             self._stream = self.environ['wsgi.input']
     138        try:
     139            content_length = int(self.environ.get('CONTENT_LENGTH', 0))
     140        except (ValueError, TypeError):
     141            content_length = 0
     142        self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
    158143        self._read_started = False
    159144
    160145    def __repr__(self):
Back to Top