def test_truncated_multipart_handled_gracefully(self): """ If passed an incomplete multipart message, MultiPartParser does not attempt to read beyond the end of the stream, and simply will handle the part that can be parsed gracefully. """ payload_str = "\r\n".join([ '--' + client.BOUNDARY, 'Content-Disposition: form-data; name="file"; filename="foo.txt"', 'Content-Type: application/octet-stream', '', 'file contents' '--' + client.BOUNDARY + '--', '', ]) payload = client.FakePayload(payload_str[:-10]) r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': '/echo/', 'REQUEST_METHOD': 'POST', 'wsgi.input': payload, } got = json.loads(self.client.request(**r).content.decode('utf-8')) self.assertEqual(got, {})
def test_blank_filenames(self): """ Receiving file upload when filename is blank (before and after sanitization) should be okay. """ # The second value is normalized to an empty name by # MultiPartParser.IE_sanitize() filenames = ['', 'C:\\Windows\\'] payload = client.FakePayload() for i, name in enumerate(filenames): payload.write('\r\n'.join([ '--' + client.BOUNDARY, 'Content-Disposition: form-data; name="file%s"; filename="%s"' % (i, name), 'Content-Type: application/octet-stream', '', 'You got pwnd.\r\n' ])) payload.write('\r\n--' + client.BOUNDARY + '--\r\n') r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': '/echo/', 'REQUEST_METHOD': 'POST', 'wsgi.input': payload, } response = self.client.request(**r) self.assertEqual(response.status_code, 200) # Empty filenames should be ignored received = json.loads(response.content.decode('utf-8')) for i, name in enumerate(filenames): self.assertIsNone(received.get('file%s' % i))
def test_filename_overflow(self): """File names over 256 characters (dangerous on some platforms) get fixed up.""" long_str = 'f' * 300 cases = [ # field name, filename, expected ('long_filename', '%s.txt' % long_str, '%s.txt' % long_str[:251]), ('long_extension', 'foo.%s' % long_str, '.%s' % long_str[:254]), ('no_extension', long_str, long_str[:255]), ('no_filename', '.%s' % long_str, '.%s' % long_str[:254]), ('long_everything', '%s.%s' % (long_str, long_str), '.%s' % long_str[:254]), ] payload = client.FakePayload() for name, filename, _ in cases: payload.write("\r\n".join([ '--' + client.BOUNDARY, 'Content-Disposition: form-data; name="{}"; filename="{}"', 'Content-Type: application/octet-stream', '', 'Oops.', '' ]).format(name, filename)) payload.write('\r\n--' + client.BOUNDARY + '--\r\n') r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': "/echo/", 'REQUEST_METHOD': 'POST', 'wsgi.input': payload, } response = self.client.request(**r) result = json.loads(response.content.decode('utf-8')) for name, _, expected in cases: got = result[name] self.assertEqual(expected, got, 'Mismatch for {}'.format(name)) self.assertLess(len(got), 256, "Got a long file name (%s characters)." % len(got))
def test_file_error_blocking(self): """ The server should not block when there are upload errors (bug #8622). This can happen if something -- i.e. an exception handler -- tries to access POST while handling an error in parsing POST. This shouldn't cause an infinite loop! """ class POSTAccessingHandler(client.ClientHandler): """A handler that'll access POST during an exception.""" def handle_uncaught_exception(self, request, resolver, exc_info): ret = super(POSTAccessingHandler, self).handle_uncaught_exception( request, resolver, exc_info) p = request.POST return ret post_data = { 'name': 'Ringo', 'file_field': open(__file__), } # Maybe this is a little more complicated that it needs to be; but if # the django.test.client.FakePayload.read() implementation changes then # this test would fail. So we need to know exactly what kind of error # it raises when there is an attempt to read more than the available bytes: try: client.FakePayload('a').read(2) except Exception, reference_error: pass
def test_dangerous_file_names(self): """Uploaded file names should be sanitized before ever reaching the view.""" # This test simulates possible directory traversal attacks by a # malicious uploader We have to do some monkeybusiness here to construct # a malicious payload with an invalid file name (containing os.sep or # os.pardir). This similar to what an attacker would need to do when # trying such an attack. payload = client.FakePayload() for i, name in enumerate(CANDIDATE_TRAVERSAL_FILE_NAMES): payload.write('\r\n'.join([ '--' + client.BOUNDARY, 'Content-Disposition: form-data; name="file%s"; filename="%s"' % (i, name), 'Content-Type: application/octet-stream', '', 'You got pwnd.\r\n' ])) payload.write('\r\n--' + client.BOUNDARY + '--\r\n') r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': "/echo/", 'REQUEST_METHOD': 'POST', 'wsgi.input': payload, } response = self.client.request(**r) # The filenames should have been sanitized by the time it got to the view. received = response.json() for i, name in enumerate(CANDIDATE_TRAVERSAL_FILE_NAMES): got = received["file%s" % i] self.assertEqual(got, "hax0rd.txt")
def test_unicode_name_rfc2231(self): """ Test receiving file upload when filename is encoded with RFC2231 (#22971). """ payload = client.FakePayload() payload.write( '\r\n'.join([ '--' + client.BOUNDARY, 'Content-Disposition: form-data; name*=UTF-8\'\'file_unicode; filename*=UTF-8\'\'%s' % urlquote( UNICODE_FILENAME ), 'Content-Type: application/octet-stream', '', 'You got pwnd.\r\n', '\r\n--' + client.BOUNDARY + '--\r\n' ]) ) r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': "/unicode_name/", 'REQUEST_METHOD': 'POST', 'wsgi.input': payload, } response = self.client.request(**r) self.assertEqual(response.status_code, 200)
def test_empty_multipart_handled_gracefully(self): """ If passed an empty multipart message, MultiPartParser will return an empty QueryDict. """ r = { 'CONTENT_LENGTH': 0, 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': '/echo/', 'REQUEST_METHOD': 'POST', 'wsgi.input': client.FakePayload(b''), } self.assertEqual(self.client.request(**r).json(), {})
def test_empty_multipart_handled_gracefully(self): """ If passed an empty multipart message, MultiPartParser will return an empty QueryDict. """ r = { "CONTENT_LENGTH": 0, "CONTENT_TYPE": client.MULTIPART_CONTENT, "PATH_INFO": "/echo/", "REQUEST_METHOD": "POST", "wsgi.input": client.FakePayload(b""), } self.assertEqual(self.client.request(**r).json(), {})
def test_dangerous_file_names(self): """Uploaded file names should be sanitized before ever reaching the view.""" # This test simulates possible directory traversal attacks by a # malicious uploader We have to do some monkeybusiness here to construct # a malicious payload with an invalid file name (containing os.sep or # os.pardir). This similar to what an attacker would need to do when # trying such an attack. scary_file_names = [ "/tmp/hax0rd.txt", # Absolute path, *nix-style. "C:\\Windows\\hax0rd.txt", # Absolute path, win-syle. "C:/Windows/hax0rd.txt", # Absolute path, broken-style. "\\tmp\\hax0rd.txt", # Absolute path, broken in a different way. "/tmp\\hax0rd.txt", # Absolute path, broken by mixing. "subdir/hax0rd.txt", # Descendant path, *nix-style. "subdir\\hax0rd.txt", # Descendant path, win-style. "sub/dir\\hax0rd.txt", # Descendant path, mixed. "../../hax0rd.txt", # Relative path, *nix-style. "..\\..\\hax0rd.txt", # Relative path, win-style. "../..\\hax0rd.txt" # Relative path, mixed. ] payload = [] for i, name in enumerate(scary_file_names): payload.extend([ '--' + client.BOUNDARY, 'Content-Disposition: form-data; name="file%s"; filename="%s"' % (i, name), 'Content-Type: application/octet-stream', '', 'You got pwnd.' ]) payload.extend([ '--' + client.BOUNDARY + '--', '', ]) payload = "\r\n".join(payload).encode('utf-8') r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': "/file_uploads/echo/", 'REQUEST_METHOD': 'POST', 'wsgi.input': client.FakePayload(payload), } response = self.client.request(**r) # The filenames should have been sanitized by the time it got to the view. recieved = json.loads(response.content.decode('utf-8')) for i, name in enumerate(scary_file_names): got = recieved["file%s" % i] self.assertEqual(got, "hax0rd.txt")
def test_fake_file_upload(admin_user, admin_client): foo_ct = ContentType.objects.get_for_model(Foo) clear_uploads() payload = client_module.FakePayload() def form_value_list(key, value): return [ '--' + client_module.BOUNDARY, 'Content-Disposition: form-data; name="%s"' % key, "", value ] form_vals = [] file_data = 'foo bar foo bar.' file_size = str(len(file_data)) form_vals += form_value_list("resumableChunkNumber", "1") form_vals += form_value_list("resumableChunkSize", file_size) form_vals += form_value_list("resumableType", "text/plain") form_vals += form_value_list("resumableIdentifier", file_size + "-foobar") form_vals += form_value_list("resumableFilename", "foo.bar") form_vals += form_value_list("resumableTotalChunks", "1") form_vals += form_value_list("resumableTotalSize", file_size) form_vals += form_value_list("content_type_id", str(foo_ct.id)) form_vals += form_value_list("field_name", "foo") payload.write('\r\n'.join(form_vals + [ '--' + client_module.BOUNDARY, 'Content-Disposition: form-data; name="file"; filename=foo.bar', 'Content-Type: application/octet-stream', '', file_data, '--' + client_module.BOUNDARY + '--\r\n' ])) r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client_module.MULTIPART_CONTENT, 'PATH_INFO': "/admin_resumable/admin_resumable/", 'REQUEST_METHOD': 'POST', 'wsgi.input': payload, } response = admin_client.request(**r) assert response.status_code == 200 upload_filename = file_size + "_foo.bar" upload_path = os.path.join(settings.MEDIA_ROOT, 'admin_uploaded', upload_filename) f = open(upload_path, 'r') uploaded_contents = f.read() assert file_data == uploaded_contents
def test_file_error_blocking(self): """ The server should not block when there are upload errors (bug #8622). This can happen if something -- i.e. an exception handler -- tries to access POST while handling an error in parsing POST. This shouldn't cause an infinite loop! """ class POSTAccessingHandler(client.ClientHandler): """A handler that'll access POST during an exception.""" def handle_uncaught_exception(self, request, resolver, exc_info): ret = super(POSTAccessingHandler, self).handle_uncaught_exception( request, resolver, exc_info) p = request.POST return ret # Maybe this is a little more complicated that it needs to be; but if # the django.test.client.FakePayload.read() implementation changes then # this test would fail. So we need to know exactly what kind of error # it raises when there is an attempt to read more than the available bytes: try: client.FakePayload(b'a').read(2) except Exception as reference_error: pass # install the custom handler that tries to access request.POST self.client.handler = POSTAccessingHandler() with open(__file__, 'rb') as fp: post_data = { 'name': 'Ringo', 'file_field': fp, } try: response = self.client.post('/file_uploads/upload_errors/', post_data) except reference_error.__class__ as err: self.assertFalse( str(err) == str(reference_error), "Caught a repeated exception that'll cause an infinite loop in file uploads." ) except Exception as err: # CustomUploadError is the error that should have been raised self.assertEqual(err.__class__, uploadhandler.CustomUploadError)
def _test_base64_upload(self, content, encode=base64.b64encode): payload = client.FakePayload("\r\n".join([ '--' + client.BOUNDARY, 'Content-Disposition: form-data; name="file"; filename="test.txt"', 'Content-Type: application/octet-stream', 'Content-Transfer-Encoding: base64', '' ])) payload.write(b'\r\n' + encode(content.encode()) + b'\r\n') payload.write('--' + client.BOUNDARY + '--\r\n') r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': "/echo_content/", 'REQUEST_METHOD': 'POST', 'wsgi.input': payload, } response = self.client.request(**r) self.assertEqual(response.json()['file'], content)
def test_unicode_name_rfc2231_with_double_quotes(self): payload = client.FakePayload() payload.write('\r\n'.join([ '--' + client.BOUNDARY, 'Content-Disposition: form-data; name*="UTF-8\'\'file_unicode"; ' 'filename*="UTF-8\'\'%s"' % quote(UNICODE_FILENAME), 'Content-Type: application/octet-stream', '', 'You got pwnd.\r\n', '\r\n--' + client.BOUNDARY + '--\r\n' ])) r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': '/unicode_name/', 'REQUEST_METHOD': 'POST', 'wsgi.input': payload, } response = self.client.request(**r) self.assertEqual(response.status_code, 200)
def requestfactory_patch(self, path, data=None, content_type=client.MULTIPART_CONTENT, **extra): """ Construct a PATCH request. """ data = data or {} patch_data = self._encode_data(data, content_type) parsed = urllib.parse.urlparse(path) request = { 'CONTENT_LENGTH': len(patch_data), 'CONTENT_TYPE': content_type, 'PATH_INFO': self._get_path(parsed), 'QUERY_STRING': parsed[4], 'REQUEST_METHOD': 'PATCH', 'wsgi.input': client.FakePayload(patch_data), } request.update(extra) return self.request(**request)
def test_base64_invalid_upload(self): payload = client.FakePayload("\r\n".join([ "--" + client.BOUNDARY, 'Content-Disposition: form-data; name="file"; filename="test.txt"', "Content-Type: application/octet-stream", "Content-Transfer-Encoding: base64", "", ])) payload.write(b"\r\n!\r\n") payload.write("--" + client.BOUNDARY + "--\r\n") r = { "CONTENT_LENGTH": len(payload), "CONTENT_TYPE": client.MULTIPART_CONTENT, "PATH_INFO": "/echo_content/", "REQUEST_METHOD": "POST", "wsgi.input": payload, } response = self.client.request(**r) self.assertEqual(response.json()["file"], "")
def test_blank_filenames(self): """ Receiving file upload when filename is blank (before and after sanitization) should be okay. """ filenames = [ "", # Normalized by MultiPartParser.IE_sanitize(). "C:\\Windows\\", # Normalized by os.path.basename(). "/", "ends-with-slash/", ] payload = client.FakePayload() for i, name in enumerate(filenames): payload.write( "\r\n".join( [ "--" + client.BOUNDARY, 'Content-Disposition: form-data; name="file%s"; filename="%s"' % (i, name), "Content-Type: application/octet-stream", "", "You got pwnd.\r\n", ] ) ) payload.write("\r\n--" + client.BOUNDARY + "--\r\n") r = { "CONTENT_LENGTH": len(payload), "CONTENT_TYPE": client.MULTIPART_CONTENT, "PATH_INFO": "/echo/", "REQUEST_METHOD": "POST", "wsgi.input": payload, } response = self.client.request(**r) self.assertEqual(response.status_code, 200) # Empty filenames should be ignored received = response.json() for i, name in enumerate(filenames): self.assertIsNone(received.get("file%s" % i))
def _test_base64_upload(self, content): payload = client.FakePayload("\r\n".join([ '--' + client.BOUNDARY, 'Content-Disposition: form-data; name="file"; filename="test.txt"', 'Content-Type: application/octet-stream', 'Content-Transfer-Encoding: base64', '',])) payload.write(b"\r\n" + base64.b64encode(force_bytes(content)) + b"\r\n") payload.write('--' + client.BOUNDARY + '--\r\n') r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': "/file_uploads/echo_content/", 'REQUEST_METHOD': 'POST', 'wsgi.input': payload, } response = self.client.request(**r) received = json.loads(response.content.decode('utf-8')) self.assertEqual(received['file'], content)
def test_unicode_name_rfc2231_with_double_quotes(self): payload = client.FakePayload() payload.write("\r\n".join([ "--" + client.BOUNDARY, "Content-Disposition: form-data; name*=\"UTF-8''file_unicode\"; " "filename*=\"UTF-8''%s\"" % quote(UNICODE_FILENAME), "Content-Type: application/octet-stream", "", "You got pwnd.\r\n", "\r\n--" + client.BOUNDARY + "--\r\n", ])) r = { "CONTENT_LENGTH": len(payload), "CONTENT_TYPE": client.MULTIPART_CONTENT, "PATH_INFO": "/unicode_name/", "REQUEST_METHOD": "POST", "wsgi.input": payload, } response = self.client.request(**r) self.assertEqual(response.status_code, 200)
def test_non_printable_chars_in_file_names(self): file_name = 'non-\x00printable\x00\n_chars.txt\x00' payload = client.FakePayload() payload.write('\r\n'.join([ '--' + client.BOUNDARY, f'Content-Disposition: form-data; name="file"; filename="{file_name}"', 'Content-Type: application/octet-stream', '', 'You got pwnd.\r\n' ])) payload.write('\r\n--' + client.BOUNDARY + '--\r\n') r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': '/echo/', 'REQUEST_METHOD': 'POST', 'wsgi.input': payload, } response = self.client.request(**r) # Non-printable chars are sanitized. received = response.json() self.assertEqual(received['file'], 'non-printable_chars.txt')
def test_filename_traversal_upload(self): os.makedirs(UPLOAD_TO, exist_ok=True) tests = [ "../test.txt", "../test.txt", ] for file_name in tests: with self.subTest(file_name=file_name): payload = client.FakePayload() payload.write( "\r\n".join( [ "--" + client.BOUNDARY, 'Content-Disposition: form-data; name="my_file"; ' 'filename="%s";' % file_name, "Content-Type: text/plain", "", "file contents.\r\n", "\r\n--" + client.BOUNDARY + "--\r\n", ] ), ) r = { "CONTENT_LENGTH": len(payload), "CONTENT_TYPE": client.MULTIPART_CONTENT, "PATH_INFO": "/upload_traversal/", "REQUEST_METHOD": "POST", "wsgi.input": payload, } response = self.client.request(**r) result = response.json() self.assertEqual(response.status_code, 200) self.assertEqual(result["file_name"], "test.txt") self.assertIs( os.path.exists(os.path.join(MEDIA_ROOT, "test.txt")), False, ) self.assertIs( os.path.exists(os.path.join(UPLOAD_TO, "test.txt")), True, )
def test_filename_overflow(self): """File names over 256 characters (dangerous on some platforms) get fixed up.""" long_str = "f" * 300 cases = [ # field name, filename, expected ("long_filename", "%s.txt" % long_str, "%s.txt" % long_str[:251]), ("long_extension", "foo.%s" % long_str, ".%s" % long_str[:254]), ("no_extension", long_str, long_str[:255]), ("no_filename", ".%s" % long_str, ".%s" % long_str[:254]), ("long_everything", "%s.%s" % (long_str, long_str), ".%s" % long_str[:254]), ] payload = client.FakePayload() for name, filename, _ in cases: payload.write( "\r\n".join( [ "--" + client.BOUNDARY, 'Content-Disposition: form-data; name="{}"; filename="{}"', "Content-Type: application/octet-stream", "", "Oops.", "", ] ).format(name, filename) ) payload.write("\r\n--" + client.BOUNDARY + "--\r\n") r = { "CONTENT_LENGTH": len(payload), "CONTENT_TYPE": client.MULTIPART_CONTENT, "PATH_INFO": "/echo/", "REQUEST_METHOD": "POST", "wsgi.input": payload, } response = self.client.request(**r) result = response.json() for name, _, expected in cases: got = result[name] self.assertEqual(expected, got, "Mismatch for {}".format(name)) self.assertLess( len(got), 256, "Got a long file name (%s characters)." % len(got) )
def test_filename_overflow(self): """File names over 256 characters (dangerous on some platforms) get fixed up.""" name = "%s.txt" % ("f"*500) payload = "\r\n".join([ '--' + client.BOUNDARY, 'Content-Disposition: form-data; name="file"; filename="%s"' % name, 'Content-Type: application/octet-stream', '', 'Oops.' '--' + client.BOUNDARY + '--', '', ]).encode('utf-8') r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': "/file_uploads/echo/", 'REQUEST_METHOD': 'POST', 'wsgi.input': client.FakePayload(payload), } got = json.loads(self.client.request(**r).content.decode('utf-8')) self.assertTrue(len(got['file']) < 256, "Got a long file name (%s characters)." % len(got['file']))
def test_filename_traversal_upload(self): os.makedirs(UPLOAD_TO, exist_ok=True) tests = [ '../test.txt', '../test.txt', ] for file_name in tests: with self.subTest(file_name=file_name): payload = client.FakePayload() payload.write( '\r\n'.join([ '--' + client.BOUNDARY, 'Content-Disposition: form-data; name="my_file"; ' 'filename="%s";' % file_name, 'Content-Type: text/plain', '', 'file contents.\r\n', '\r\n--' + client.BOUNDARY + '--\r\n', ]), ) r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': '/upload_traversal/', 'REQUEST_METHOD': 'POST', 'wsgi.input': payload, } response = self.client.request(**r) result = response.json() self.assertEqual(response.status_code, 200) self.assertEqual(result['file_name'], 'test.txt') self.assertIs( os.path.exists(os.path.join(MEDIA_ROOT, 'test.txt')), False, ) self.assertIs( os.path.exists(os.path.join(UPLOAD_TO, 'test.txt')), True, )
def request(self, **request): # Figure out parameters from request['QUERY_STRING'] and FakePayload params = {} if request['REQUEST_METHOD'] in ('POST', 'PUT'): if request['CONTENT_TYPE'] == URLENCODED_FORM_CONTENT: payload = request['wsgi.input'].read() request['wsgi.input'] = client.FakePayload(payload) params = cgi.parse_qs(payload) url = "http://testserver" + request['PATH_INFO'] req = oauth.OAuthRequest.from_consumer_and_token( self.consumer, token=self.token, http_method=request['REQUEST_METHOD'], http_url=url, parameters=params ) req.sign_request(self.signature, self.consumer, self.token) headers = req.to_header() request['HTTP_AUTHORIZATION'] = headers['Authorization'] return super(OAuthClient, self).request(**request)
def put(self, path, data={}, content_type=client.MULTIPART_CONTENT, **extra): """ Requests a response from the server using PUT. """ if content_type is client.MULTIPART_CONTENT: put_data = client.encode_multipart(BOUNDARY, data) else: put_data = data r = { 'CONTENT_LENGTH': len(put_data), 'CONTENT_TYPE': content_type, 'PATH_INFO': urllib.unquote(path), 'REQUEST_METHOD': 'PUT', 'wsgi.input': client.FakePayload(put_data), } r.update(extra) return self.request(**r)
def test_base64_upload(self): test_string = "This data will be transmitted base64-encoded." payload = "\r\n".join([ '--' + client.BOUNDARY, 'Content-Disposition: form-data; name="file"; filename="test.txt"', 'Content-Type: application/octet-stream', 'Content-Transfer-Encoding: base64', '', base64.b64encode(test_string), '--' + client.BOUNDARY + '--', '', ]).encode('utf-8') r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': "/file_uploads/echo_content/", 'REQUEST_METHOD': 'POST', 'wsgi.input': client.FakePayload(payload), } response = self.client.request(**r) received = json.loads(response.content) self.assertEqual(received['file'], test_string)
def test_non_printable_chars_in_file_names(self): file_name = "non-\x00printable\x00\n_chars.txt\x00" payload = client.FakePayload() payload.write("\r\n".join([ "--" + client.BOUNDARY, f'Content-Disposition: form-data; name="file"; ' f'filename="{file_name}"', "Content-Type: application/octet-stream", "", "You got pwnd.\r\n", ])) payload.write("\r\n--" + client.BOUNDARY + "--\r\n") r = { "CONTENT_LENGTH": len(payload), "CONTENT_TYPE": client.MULTIPART_CONTENT, "PATH_INFO": "/echo/", "REQUEST_METHOD": "POST", "wsgi.input": payload, } response = self.client.request(**r) # Non-printable chars are sanitized. received = response.json() self.assertEqual(received["file"], "non-printable_chars.txt")
def test_truncated_multipart_handled_gracefully(self): """ If passed an incomplete multipart message, MultiPartParser does not attempt to read beyond the end of the stream, and simply will handle the part that can be parsed gracefully. """ payload_str = "\r\n".join([ "--" + client.BOUNDARY, 'Content-Disposition: form-data; name="file"; filename="foo.txt"', "Content-Type: application/octet-stream", "", "file contents" "--" + client.BOUNDARY + "--", "", ]) payload = client.FakePayload(payload_str[:-10]) r = { "CONTENT_LENGTH": len(payload), "CONTENT_TYPE": client.MULTIPART_CONTENT, "PATH_INFO": "/echo/", "REQUEST_METHOD": "POST", "wsgi.input": payload, } self.assertEqual(self.client.request(**r).json(), {})
def test_fake_file_upload_incomplete_chunk(admin_user, admin_client): foo_ct = ContentType.objects.get_for_model(Foo) clear_uploads() payload = client_module.FakePayload() def form_value_list(key, value): return [ '--' + client_module.BOUNDARY, 'Content-Disposition: form-data; name="%s"' % key, "", value ] form_vals = [] file_data = 'foo bar foo bar.' file_size = str(len(file_data)) form_vals += form_value_list("resumableChunkNumber", "1") form_vals += form_value_list("resumableChunkSize", "3") form_vals += form_value_list("resumableType", "text/plain") form_vals += form_value_list("resumableIdentifier", file_size + "-foobar") form_vals += form_value_list("resumableFilename", "foo.bar") form_vals += form_value_list("resumableTotalChunks", "6") form_vals += form_value_list("resumableTotalSize", file_size) form_vals += form_value_list("content_type_id", str(foo_ct.id)) form_vals += form_value_list("field_name", "foo") payload.write('\r\n'.join(form_vals + [ '--' + client_module.BOUNDARY, 'Content-Disposition: form-data; name="file"; filename=foo.bar', 'Content-Type: application/octet-stream', '', file_data[0:1], # missing final boundary to simulate failure ])) r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client_module.MULTIPART_CONTENT, 'PATH_INFO': "/admin_resumable/admin_resumable/", 'REQUEST_METHOD': 'POST', 'wsgi.input': payload, } try: admin_client.request(**r) except AttributeError: pass # we're not worried that this would 500 get_url = "/admin_resumable/admin_resumable/?" get_args = { 'resumableChunkNumber': '1', 'resumableChunkSize': '3', 'resumableCurrentChunkSize': '3', 'resumableTotalSize': file_size, 'resumableType': "text/plain", 'resumableIdentifier': file_size + "-foobar", 'resumableFilename': "foo.bar", 'resumableRelativePath': "foo.bar", 'content_type_id': str(foo_ct.id), 'field_name': "foo", } # we need a fresh client because client.request breaks things fresh_client = client_module.Client() fresh_client.login(username=admin_user.username, password='******') get_response = fresh_client.get(get_url, get_args) # should be a 404 because we uploaded an incomplete chunk assert get_response.status_code == 404