def info(self, request, *args, **kwargs): # Validate tus header if not has_required_tus_header(request): return Response('Missing "{}" header.'.format('Tus-Resumable'), status=status.HTTP_400_BAD_REQUEST) try: image = self.get_object() except Http404: # Instead of simply trowing a 404, we need to add a cache-control header to the response return Response('Not found.', headers={'Cache-Control': 'no-store'}, status=status.HTTP_404_NOT_FOUND) headers = { 'Upload-Offset': self.get_cached_property("offset", image), 'Cache-Control': 'no-store' } if self.get_cached_property("upload-length", image) >= 0: headers['Upload-Length'] = image.upload_length if self.get_cached_property("metadata", image): headers['Upload-Metadata'] = encode_upload_metadata(json.loads(image.upload_metadata)) # Add upload expiry to headers expiration = self.get_cached_property("expires", image) add_expiry_header(expiration, headers) return Response(headers=headers, status=status.HTTP_200_OK)
def head(self, request, *args, **kwargs): # Validate tus header if not has_required_tus_header(request): msg = 'Missing "{}" header.'.format('Tus-Resumable') log.warning("Chunked uploader (%d): %s" % (request.user.pk, msg)) return Response(msg, status=status.HTTP_400_BAD_REQUEST) try: object = self.get_object() except Http404: # Instead of simply throwing a 404, we need to add a cache-control header to the response msg = 'Not found.' log.warning("Chunked uploader (%d): %s" % (request.user.pk, msg)) return Response(msg, headers={'Cache-Control': 'no-store'}, status=status.HTTP_404_NOT_FOUND) offset = self.get_cached_property("offset", object) if offset is None: offset = 0 self.set_cached_property("offset", object, offset) headers = { 'Upload-Offset': offset, 'Cache-Control': 'no-store' } upload_metadata = self.get_cached_property("metadata", object) if upload_metadata: headers['Upload-Metadata'] = encode_upload_metadata(upload_metadata) # Add upload expiry to headers expiration = self.get_cached_property("expires", object) add_expiry_header(expiration, headers) return Response(headers=headers, status=status.HTTP_200_OK)
def partial_update(self, request, *args, **kwargs): # Validate tus header if not has_required_tus_header(request): msg = 'Missing "{}" header.'.format('Tus-Resumable') log.warning("Chunked uploader (%d): %s" % (request.user.pk, msg)) return HttpResponse(msg, status=status.HTTP_400_BAD_REQUEST) # Validate content type if not self._is_valid_content_type(request): msg = 'Invalid value for "Content-Type" header: {}. Expected "{}".'.format( request.META['CONTENT_TYPE'], TusUploadStreamParser.media_type) log.warning("Chunked uploader (%d): %s" % (request.user.pk, msg)) return HttpResponse(msg, status=status.HTTP_400_BAD_REQUEST) # Retrieve object object = self.get_object() # Get upload_offset upload_offset = int(request.META.get(constants.UPLOAD_OFFSET_NAME, 0)) # Validate upload_offset if upload_offset != self.get_cached_property("offset", object): log.warning("Chunked uploader (%d) (%d): offset conflict" % (request.user.pk, object.pk)) raise Conflict temporary_file = get_or_create_temporary_file(object) if not os.path.isfile(temporary_file): # Initial request in the series of PATCH request was handled on a different server instance. msg = 'Previous chunks not found on this server.' log.warning("Chunked uploader (%d) (%d): %s" % (request.user.pk, object.pk, msg)) return HttpResponse(msg, status=status.HTTP_400_BAD_REQUEST) # Get chunk from request chunk_bytes = self.get_chunk(request) # Check for data if not chunk_bytes: msg = 'No data.' log.warning("Chunked uploader (%d) (%d): %s" % (request.user.pk, object.pk, msg)) return HttpResponse(msg, status=status.HTTP_400_BAD_REQUEST) # Check checksum (http://tus.io/protocols/resumable-upload.html#checksum) upload_checksum = request.META.get( constants.UPLOAD_CHECKSUM_FIELD_NAME, None) if upload_checksum is not None: if upload_checksum[0] not in TUS_API_CHECKSUM_ALGORITHMS: msg = 'Unsupported Checksum Algorithm: {}.'.format( upload_checksum[0]) log.warning("Chunked uploader (%d) (%d): %s" % (request.user.pk, object.pk, msg)) return HttpResponse(msg, status=status.HTTP_400_BAD_REQUEST) elif not checksum_matches(upload_checksum[0], upload_checksum[1], chunk_bytes): msg = 'Checksum Mismatch.' log.warning("Chunked uploader (%d) (%d) : %s" % (request.user.pk, object.pk, msg)) return HttpResponse(msg, status=460) # Run chunk validator chunk_bytes = self.validate_chunk(upload_offset, chunk_bytes) # Check for data if not chunk_bytes: msg = 'No data. Make sure "validate_chunk" returns data.' log.warning("Chunked uploader (%d) (%d): %s" % (request.user.pk, object.pk, msg)) return HttpResponse(msg, status=status.HTTP_400_BAD_REQUEST) # Write file try: write_data(object, chunk_bytes) log.debug("Chunked uploader (%d) (%d): wrote %d bytes" % (request.user.pk, object.pk, len(chunk_bytes))) except Exception as e: msg = str(e) log.warning( "Chunked uploader (%d) (%d): exception writing data: %s" % (request.user.pk, object.pk, msg)) return HttpResponse(msg, status=status.HTTP_400_BAD_REQUEST) headers = {'Upload-Offset': self.get_cached_property("offset", object)} if self.get_cached_property("upload-length", object) == self.get_cached_property( "offset", object): log.debug("Chunked uploader (%d) (%d): chunks completed" % (request.user.pk, object.pk)) # Trigger signal signals.saving.send(object) # Save file temporary_file = get_or_create_temporary_file(object) if not self.verify_file(temporary_file): os.remove(temporary_file) msg = "file verification failed" log.warning("Chunked uploader (%d) (%d): %s" % (request.user.pk, object.pk, msg)) return HttpResponse( msg, status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE) log.debug( "Chunked uploader (%d) (%d): saving object to temporary file %s" % (request.user.pk, object.pk, temporary_file)) try: getattr(object, self.get_file_field_name()).save( self.get_upload_path_function()(object, self.get_cached_property( "name", object)), File(open(temporary_file))) except Exception as e: log.error("Chunked uploader (%d) (%d): exception: %s" % (request.user.pk, object.pk, e.message)) os.remove(temporary_file) delete_kwargs = {} if issubclass(type(object), SafeDeleteModel): delete_kwargs['force_policy'] = HARD_DELETE object.delete(**delete_kwargs) return HttpResponse( e.message, status=status.HTTP_500_INTERNAL_SERVER_ERROR) signals.saved.send(object) # Clean up os.remove(temporary_file) signals.finished.send(object) if isinstance(object, Image): from astrobin.tasks import retrieve_primary_thumbnails retrieve_primary_thumbnails.delay(object.pk, {'revision_label': '0'}) log.debug("Chunked uploader (%d) (%d): finished" % (request.user.pk, object.pk)) # Add upload expiry to headers add_expiry_header(self.get_cached_property("expires", object), headers) # By default, don't include a response body if not constants.TUS_RESPONSE_BODY_ENABLED: response = HttpResponse(content_type='application/javascript', status=status.HTTP_201_CREATED) response = apply_headers_to_response(response, headers) return response # Create serializer serializer = self.get_serializer(instance=object) response = HttpResponse(simplejson.dumps(serializer.data), content_type='application/javascript', status=status.HTTP_201_CREATED) response = apply_headers_to_response(response, headers) return response
def create(self, request, *args, **kwargs): # Validate tus header if not has_required_tus_header(request): log.warning("Chunked uploader (%d): missing Tus-Resumable header" % request.user.pk) return HttpResponse('Missing "{}" header.'.format('Tus-Resumable'), status=status.HTTP_400_BAD_REQUEST) # Get file size from request upload_length = int( request.META.get(constants.UPLOAD_LENGTH_FIELD_NAME, -1)) log.info( "Chunked uploader (%d): initiated upload of file with size %d" % (request.user.pk, upload_length)) # Validate upload_length max_file_size = min( premium_get_max_allowed_image_size(request.user), getattr(self, 'max_file_size', constants.TUS_MAX_FILE_SIZE)) if upload_length > max_file_size: msg = 'Invalid "Upload-Length". Maximum value: {}.'.format( max_file_size) log.warning("Chunked uploader (%d): %s" % (request.user.pk, msg)) return HttpResponse( msg, status=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE) # If upload_length is not given, we expect the defer header! if not upload_length or upload_length < 0: if getattr(request, constants.UPLOAD_DEFER_LENGTH_FIELD_NAME, -1) != 1: msg = 'Missing "{Upload-Defer-Length}" header.' log.warning("Chunked uploader (%d): %s" % (request.user.pk, msg)) return HttpResponse(msg, status=status.HTTP_400_BAD_REQUEST) # Get metadata from request upload_metadata = decode_upload_metadata( request.META.get(constants.UPLOAD_METADATA_FIELD_NAME, {})) # Get data from metadata name = upload_metadata.get(constants.TUS_NAME_METADATA_FIELD, '') filename = upload_metadata.get(constants.TUS_FILENAME_METADATA_FIELD, '') # Validate the filename filename = self.validate_filename(filename) # Retrieve serializer try: serializer = self.get_object_serializer(request, filename, upload_length, upload_metadata) except KeyError as e: msg = 'Invalid data: %s' % e.message log.warning("Chunked uploader (%d): %s" % (request.user.pk, msg)) return HttpResponse(msg, status=status.HTTP_400_BAD_REQUEST) # Validate serializer serializer.is_valid(raise_exception=True) # Create upload object self.perform_create(serializer) # Get upload from serializer object = serializer.instance log.debug("Chunked uploader (%d) (%d): created" % (request.user.pk, object.pk)) signals.receiving.send(object) # Prepare response headers headers = self.get_success_headers(serializer.data) expiration = timezone.now() + constants.TUS_UPLOAD_EXPIRES self.set_cached_property("name", object, name) self.set_cached_property("filename", object, filename) self.set_cached_property("upload-length", object, upload_length) self.set_cached_property("offset", object, 0) self.set_cached_property("expires", object, expiration) self.set_cached_property("metadata", object, upload_metadata) # Add upload expiry to headers add_expiry_header(expiration, headers) # Validate headers headers = self.validate_success_headers(headers) # By default, don't include a response body if not constants.TUS_RESPONSE_BODY_ENABLED: response = HttpResponse(content_type='application/javascript', status=status.HTTP_201_CREATED) response = apply_headers_to_response(response, headers) return response response = HttpResponse(simplejson.dumps(serializer.data), content_type='application/javascript', status=status.HTTP_201_CREATED) response = apply_headers_to_response(response, headers) signals.received.send(object) return response
def partial_update(self, request, *args, **kwargs): # Validate tus header if not has_required_tus_header(request): return HttpResponse('Missing "{}" header.'.format('Tus-Resumable'), status=status.HTTP_400_BAD_REQUEST) # Validate content type if not self._is_valid_content_type(request): return HttpResponse( 'Invalid value for "Content-Type" header: {}. Expected "{}".'. format(request.META['CONTENT_TYPE'], TusUploadStreamParser.media_type), status=status.HTTP_400_BAD_REQUEST) # Retrieve object image = self.get_object() # Get upload_offset upload_offset = int(request.META.get(constants.UPLOAD_OFFSET_NAME, 0)) # Validate upload_offset if upload_offset != self.get_cached_property("offset", image): raise Conflict # Make sure there is a tempfile for the upload assert get_or_create_temporary_file(image) # Get chunk from request chunk_bytes = self.get_chunk(request) # Check for data if not chunk_bytes: return HttpResponse('No data.', status=status.HTTP_400_BAD_REQUEST) # Check checksum (http://tus.io/protocols/resumable-upload.html#checksum) upload_checksum = request.META.get( constants.UPLOAD_CHECKSUM_FIELD_NAME, None) if upload_checksum is not None: if upload_checksum[0] not in TUS_API_CHECKSUM_ALGORITHMS: return HttpResponse( 'Unsupported Checksum Algorithm: {}.'.format( upload_checksum[0]), status=status.HTTP_400_BAD_REQUEST) elif not checksum_matches(upload_checksum[0], upload_checksum[1], chunk_bytes): return HttpResponse('Checksum Mismatch.', status=460) # Run chunk validator chunk_bytes = self.validate_chunk(upload_offset, chunk_bytes) # Check for data if not chunk_bytes: return HttpResponse( 'No data. Make sure "validate_chunk" returns data.', status=status.HTTP_400_BAD_REQUEST) # Write file try: write_data(image, chunk_bytes) except Exception as e: return HttpResponse(str(e), status=status.HTTP_400_BAD_REQUEST) headers = {'Upload-Offset': self.get_cached_property("offset", image)} if self.get_cached_property("upload-length", image) == self.get_cached_property( "offset", image): # Trigger signal signals.received.send(sender=image.__class__, instance=image) # Save file data = get_or_create_temporary_file(image) image.image_file.save( image_upload_path(image, self.get_cached_property("name", image)), File(open(data))) # Clean up temporary_file_path = get_or_create_temporary_file(image) os.remove(temporary_file_path) headers["Image-ID"] = image.get_id() # Add upload expiry to headers add_expiry_header(self.get_cached_property("expires", image), headers) # By default, don't include a response body if not constants.TUS_RESPONSE_BODY_ENABLED: response = HttpResponse(content_type='application/javascript', status=status.HTTP_201_CREATED) response = apply_headers_to_response(response, headers) return response # Create serializer serializer = self.get_serializer(instance=image) response = HttpResponse(simplejson.dumps(serializer.data), content_type='application/javascript', status=status.HTTP_201_CREATED) response = apply_headers_to_response(response, headers) return response
def create(self, request, *args, **kwargs): # Validate tus header if not has_required_tus_header(request): return HttpResponse('Missing "{}" header.'.format('Tus-Resumable'), status=status.HTTP_400_BAD_REQUEST) # Get file size from request upload_length = int( request.META.get(constants.UPLOAD_LENGTH_FIELD_NAME, -1)) # Validate upload_length max_file_size = getattr(self, 'max_file_size', constants.TUS_MAX_FILE_SIZE) if upload_length > max_file_size: return HttpResponse( 'Invalid "Upload-Length". Maximum value: {}.'.format( max_file_size), status=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE) # If upload_length is not given, we expect the defer header! if not upload_length or upload_length < 0: if getattr(request, constants.UPLOAD_DEFER_LENGTH_FIELD_NAME, -1) != 1: return HttpResponse('Missing "{Upload-Defer-Length}" header.', status=status.HTTP_400_BAD_REQUEST) # Get metadata from request upload_metadata = decode_upload_metadata( request.META.get(constants.UPLOAD_METADATA_FIELD_NAME, {})) # Get data from metadata name = upload_metadata.get(constants.TUS_NAME_METADATA_FIELD, '') filename = upload_metadata.get(constants.TUS_FILENAME_METADATA_FIELD, '') # Validate the filename filename = self.validate_filename(filename) # Retrieve serializer serializer = self.get_serializer( data={ 'upload_length': upload_length, 'upload_metadata': json.dumps(upload_metadata), 'filename': filename, 'title': upload_metadata['title'], 'is_wip': upload_metadata['is_wip'] if 'is_wip' in upload_metadata else False, 'skip_notifications': upload_metadata['skip_notifications'] if 'skip_notifications' in upload_metadata else False, 'user_id': request.user.id, }) # Validate serializer serializer.is_valid(raise_exception=True) # Create upload object self.perform_create(serializer) # Get upload from serializer image = serializer.instance # Prepare response headers headers = self.get_success_headers(serializer.data) expiration = timezone.now() + constants.TUS_UPLOAD_EXPIRES self.set_cached_property("name", image, name) self.set_cached_property("filename", image, filename) self.set_cached_property("upload-length", image, upload_length) self.set_cached_property("offset", image, 0) self.set_cached_property("expires", image, expiration) self.set_cached_property("metadata", image, upload_metadata) # Add upload expiry to headers add_expiry_header(expiration, headers) # Validate headers headers = self.validate_success_headers(headers) # By default, don't include a response body if not constants.TUS_RESPONSE_BODY_ENABLED: response = HttpResponse(content_type='application/javascript', status=status.HTTP_201_CREATED) response = apply_headers_to_response(response, headers) return response response = HttpResponse(simplejson.dumps(serializer.data), content_type='application/javascript', status=status.HTTP_201_CREATED) response = apply_headers_to_response(response, headers) return response