Esempio n. 1
0
    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)
Esempio n. 2
0
    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)
Esempio n. 3
0
    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
Esempio n. 4
0
    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
Esempio n. 5
0
    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
Esempio n. 6
0
    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