Ticket #15785: limited_stream_lazy_plus_extra_tests.diff
File limited_stream_lazy_plus_extra_tests.diff, 17.3 KB (added by , 14 years ago) |
---|
-
tests/regressiontests/file_uploads/tests.py
8 8 9 9 from django.core.files import temp as tempfile 10 10 from django.core.files.uploadedfile import SimpleUploadedFile 11 from django.http.multipartparser import MultiPartParser 11 from django.http.multipartparser import MultiPartParser, MultiPartParserError 12 12 from django.test import TestCase, client 13 13 from django.utils import simplejson 14 14 from django.utils import unittest … … 151 151 got = simplejson.loads(self.client.request(**r).content) 152 152 self.assertTrue(len(got['file']) < 256, "Got a long file name (%s characters)." % len(got['file'])) 153 153 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 154 194 def test_custom_upload_handler(self): 155 195 # A small file (under the 5M quota) 156 196 smallfile = tempfile.NamedTemporaryFile() -
tests/regressiontests/test_client_regress/views.py
96 96 "A view that is requested with GET and accesses request.raw_post_data. Refs #14753." 97 97 return HttpResponse(request.raw_post_data) 98 98 99 def read_all(request): 100 "A view that is requested with accesses request.read()." 101 return HttpResponse(request.read()) 102 103 def read_buffer(request): 104 "A view that is requested with accesses request.read(LARGE_BUFFER)." 105 return HttpResponse(request.read(99999)) 106 99 107 def request_context_view(request): 100 108 # Special attribute that won't be present on a plain HttpRequest 101 109 request.special_path = request.path -
tests/regressiontests/test_client_regress/models.py
900 900 response = self.client.get("/test_client_regress/request_methods/") 901 901 self.assertEqual(response.template, None) 902 902 903 class RawPostDataTest(TestCase): 904 "Access to request.raw_post_data from the test client." 905 def test_raw_post_data(self): 906 # Refs #14753 907 try: 908 response = self.client.get("/test_client_regress/raw_post_data/") 909 except AssertionError: 910 self.fail("Accessing request.raw_post_data from a view fetched with GET by the test client shouldn't fail.") 903 904 class ReadLimitedStreamTest(TestCase): 905 """ 906 Tests that ensure that HttpRequest.raw_post_data, HttpRequest.read() and 907 HttpRequest.read(BUFFER) have proper LimitedStream behaviour. 908 909 Refs #14753, #15785 910 """ 911 def test_raw_post_data_from_empty_request(self): 912 """HttpRequest.raw_post_data on a test client GET request should return 913 the empty string.""" 914 self.assertEquals(self.client.get("/test_client_regress/raw_post_data/").content, '') 915 916 def test_read_from_empty_request(self): 917 """HttpRequest.read() on a test client GET request should return the 918 empty string.""" 919 self.assertEquals(self.client.get("/test_client_regress/read_all/").content, '') 920 921 def test_read_numbytes_from_empty_request(self): 922 """HttpRequest.read(LARGE_BUFFER) on a test client GET request should 923 return the empty string.""" 924 self.assertEquals(self.client.get("/test_client_regress/read_buffer/").content, '') 925 926 def test_read_from_nonempty_request(self): 927 """HttpRequest.read() on a test client PUT request with some payload 928 should return that payload.""" 929 payload = 'foobar' 930 self.assertEquals(self.client.put("/test_client_regress/read_all/", 931 data=payload, 932 content_type='text/plain').content, payload) 933 934 def test_read_numbytes_from_nonempty_request(self): 935 """HttpRequest.read(LARGE_BUFFER) on a test client PUT request with 936 some payload should return that payload.""" 937 payload = 'foobar' 938 self.assertEquals(self.client.put("/test_client_regress/read_buffer/", 939 data=payload, 940 content_type='text/plain').content, payload) -
tests/regressiontests/test_client_regress/urls.py
27 27 (r'^check_headers/$', views.check_headers), 28 28 (r'^check_headers_redirect/$', RedirectView.as_view(url='/test_client_regress/check_headers/')), 29 29 (r'^raw_post_data/$', views.raw_post_data), 30 (r'^read_all/$', views.read_all), 31 (r'^read_buffer/$', views.read_buffer), 30 32 (r'^request_context_view/$', views.request_context_view), 31 33 ) -
tests/regressiontests/requests/tests.py
156 156 self.assertEqual(stream.read(), '') 157 157 158 158 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)}) 160 163 self.assertEqual(request.read(), 'name=value') 161 164 162 165 def test_read_after_value(self): … … 164 167 Reading from request is allowed after accessing request contents as 165 168 POST or raw_post_data. 166 169 """ 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)}) 168 174 self.assertEqual(request.POST, {u'name': [u'value']}) 169 175 self.assertEqual(request.raw_post_data, 'name=value') 170 176 self.assertEqual(request.read(), 'name=value') … … 174 180 Construction of POST or raw_post_data is not allowed after reading 175 181 from request. 176 182 """ 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)}) 178 187 self.assertEqual(request.read(2), 'na') 179 188 self.assertRaises(Exception, lambda: request.raw_post_data) 180 189 self.assertEqual(request.POST, {}) … … 201 210 self.assertRaises(Exception, lambda: request.raw_post_data) 202 211 203 212 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)}) 205 217 self.assertEqual(list(request), ['name=value']) 206 218 207 219 def test_POST_after_raw_post_data_read(self): 208 220 """ 209 221 POST should be populated even if raw_post_data is read first 210 222 """ 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)}) 212 227 raw_data = request.raw_post_data 213 228 self.assertEqual(request.POST, {u'name': [u'value']}) 214 229 … … 217 232 POST should be populated even if raw_post_data is read first, and then 218 233 the stream is read second. 219 234 """ 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)}) 221 239 raw_data = request.raw_post_data 222 240 self.assertEqual(request.read(1), u'n') 223 241 self.assertEqual(request.POST, {u'name': [u'value']}) -
django/http/multipartparser.py
33 33 A rfc2388 multipart/form-data parser. 34 34 35 35 ``MultiValueDict.parse()`` reads the input stream in ``chunk_size`` chunks 36 and returns a tuple of ``(MultiValueDict(POST), MultiValueDict(FILES))``. If36 and returns a tuple of ``(MultiValueDict(POST), MultiValueDict(FILES))``. 37 37 """ 38 38 def __init__(self, META, input_data, upload_handlers, encoding=None): 39 39 """ … … 65 65 raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary) 66 66 67 67 68 #69 68 # Content-Length should contain the length of the body we are about 70 69 # to receive. 71 #72 70 try: 73 71 content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH',0))) 74 72 except (ValueError, TypeError): 75 # For now set it to 0; we'll try again later on down.76 73 content_length = 0 77 74 78 75 if content_length <= 0: … … 105 102 encoding = self._encoding 106 103 handlers = self._upload_handlers 107 104 108 limited_input_data = LimitBytes(self._input_data, self._content_length)109 110 105 # See if the handler will want to take care of the parsing. 111 106 # This allows overriding everything if somebody wants it. 112 107 for handler in handlers: 113 result = handler.handle_raw_input( limited_input_data,108 result = handler.handle_raw_input(self._input_data, 114 109 self._meta, 115 110 self._content_length, 116 111 self._boundary, … … 123 118 self._files = MultiValueDict() 124 119 125 120 # 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)) 127 122 128 123 # Whether or not to signal a file-completion at the beginning of the loop. 129 124 old_field_name = None … … 218 213 exhaust(stream) 219 214 except StopUpload, e: 220 215 if not e.connection_reset: 221 exhaust( limited_input_data)216 exhaust(self._input_data) 222 217 else: 223 218 # Make sure that the request data is all fed 224 exhaust( limited_input_data)219 exhaust(self._input_data) 225 220 226 221 # Signal that the upload has completed. 227 222 for handler in handlers: … … 383 378 def __iter__(self): 384 379 return self 385 380 386 class LimitBytes(object):387 """ Limit bytes for a file object. """388 def __init__(self, fileobject, length):389 self._file = fileobject390 self.remaining = length391 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.remaining402 else:403 num_bytes = min(num_bytes, self.remaining)404 self.remaining -= num_bytes405 return self._file.read(num_bytes)406 407 381 class InterBoundaryIter(object): 408 382 """ 409 383 A Producer that will iterate over boundaries. -
django/http/__init__.py
237 237 if not hasattr(self, '_raw_post_data'): 238 238 if self._read_started: 239 239 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() 251 241 self._stream = StringIO(self._raw_post_data) 252 242 return self._raw_post_data 253 243 raw_post_data = property(_get_raw_post_data) -
django/core/handlers/wsgi.py
135 135 self.META['SCRIPT_NAME'] = script_name 136 136 self.method = environ['REQUEST_METHOD'].upper() 137 137 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 of140 # socket._fileobject which hangs indefinitely on reading bytes past141 # available count. To prevent this it's wrapped in LimitedStream142 # that doesn't read past Content-Length bytes.143 #144 # This is not done for other kinds of inputs (like flup's FastCGI145 # streams) beacuse they don't suffer from this problem and we can146 # avoid using another wrapper with its own .read and .readline147 # implementation.148 #149 # The type check is done because for some reason, AppEngine150 # 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 = 0155 self._stream = LimitedStream(self.environ['wsgi.input'], content_length)156 else:157 self._stream = self.environ['wsgi.input']158 138 self._read_started = False 159 139 160 140 def __repr__(self): … … 191 171 return 'wsgi.url_scheme' in self.environ \ 192 172 and self.environ['wsgi.url_scheme'] == 'https' 193 173 174 def _get_stream(self): 175 if not hasattr(self, '_limited_stream'): 176 try: 177 content_length = int(self.environ.get('CONTENT_LENGTH', 0)) 178 except (ValueError, TypeError): 179 content_length = 0 180 181 self._limited_stream = LimitedStream(self.environ['wsgi.input'], content_length) 182 183 return self._limited_stream 184 185 def _set_stream(self, stream): 186 self._limited_stream = stream 187 194 188 def _get_request(self): 195 189 if not hasattr(self, '_request'): 196 190 self._request = datastructures.MergeDict(self.POST, self.GET) … … 226 220 self._load_post_and_files() 227 221 return self._files 228 222 223 _stream = property(_get_stream, _set_stream) 229 224 GET = property(_get_get, _set_get) 230 225 POST = property(_get_post, _set_post) 231 226 COOKIES = property(_get_cookies, _set_cookies)