Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #11 #13

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions chunked_upload/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,14 @@ class http_status:
(COMPLETE, _('Complete')),
(FAILED, _('Failed')),
)

SUPPORTED_CHUCKSUM_ALGORITHMS = (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo - s/CHUCKSUM/CHECKSUM

'md5',
'sha1',
'sha224'
'sha256',
'sha384',
'sha512',
'crc32',
'adler32',
)
6 changes: 3 additions & 3 deletions chunked_upload/management/commands/delete_expired_uploads.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ def handle(self, *args, **options):
# Deleting objects individually to call delete method explicitly
chunked_upload.delete()

print '%i complete uploads were deleted.' % count[COMPLETE]
print '%i incomplete uploads were deleted.' % count[UPLOADING]
print '%i failed uploads were deleted.' % count[FAILED]
print('%i complete uploads were deleted.' % count[COMPLETE])
print('%i incomplete uploads were deleted.' % count[UPLOADING])
print('%i failed uploads were deleted.' % count[FAILED])
84 changes: 82 additions & 2 deletions chunked_upload/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import time
import os.path
import hashlib
import zlib
import uuid

from django.db import models
Expand All @@ -9,7 +10,7 @@
from django.utils import timezone

from .settings import EXPIRATION_DELTA, UPLOAD_PATH, STORAGE, ABSTRACT_MODEL
from .constants import CHUNKED_UPLOAD_CHOICES, UPLOADING
from .constants import CHUNKED_UPLOAD_CHOICES, UPLOADING, SUPPORTED_CHUCKSUM_ALGORITHMS

AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')

Expand Down Expand Up @@ -53,6 +54,81 @@ def md5(self):
self._md5 = md5.hexdigest()
return self._md5

@property
def sha1(self):
print(self.__dict__)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left over debug statements? These should probably be removed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I will fix them soon!

print(123)
if getattr(self, '_sha1', None) is None:
sha1 = hashlib.sha1()
for chunk in self.file.chunks():
sha1.update(chunk)
self._sha1 = sha1.hexdigest()
return self._sha1

@property
def sha224(self):
if getattr(self, '_sha224', None) is None:
sha224 = hashlib.sha224()
for chunk in self.file.chunks():
sha224.update(chunk)
self._sha224 = sha224.hexdigest()
return self._sha224

@property
def sha256(self):
if getattr(self, '_sha256', None) is None:
sha256 = hashlib.sha256()
for chunk in self.file.chunks():
sha256.update(chunk)
self._sha256 = sha256.hexdigest()
return self._sha256

@property
def sha384(self):
if getattr(self, '_sha384', None) is None:
sha384 = hashlib.sha384()
for chunk in self.file.chunks():
sha384.update(chunk)
self._sha384 = sha384.hexdigest()
return self._sha384

@property
def sha512(self):
if getattr(self, '_sha512', None) is None:
sha512 = hashlib.sha512()
for chunk in self.file.chunks():
sha512.update(chunk)
self._sha512 = sha512.hexdigest()
return self._sha512

@property
def sha512(self):
if getattr(self, '_sha512', None) is None:
sha512 = hashlib.sha512()
for chunk in self.file.chunks():
sha512.update(chunk)
self._sha512 = sha512.hexdigest()
return self._sha512

@property
def adler32(self):
if getattr(self, "_adler32", None) is None:
adler32 = 0
for chunk in self.file.chunks():
adler32 = zlib.adler32(chunk, adler32)
self._adler32 = str(adler32)
return self._adler32

@property
def crc32(self):
if getattr(self, "_crc32", None) is None:
crc32 = 0
for chunk in self.file.chunks():
crc32 = zlib.crc32(chunk, crc32)
self._crc32 = str(crc32)
return self._crc32


def delete(self, delete_file=True, *args, **kwargs):
storage, path = self.file.storage, self.file.path
super(ChunkedUpload, self).delete(*args, **kwargs)
Expand Down Expand Up @@ -85,7 +161,11 @@ def append_chunk(self, chunk, chunk_size=None, save=True):
self.offset += chunk.size
else:
self.offset = self.file.size
self._md5 = None # Clear cached md5
# clear cached checksum
for checksum_type in SUPPORTED_CHUCKSUM_ALGORITHMS:
checksum = getattr(self, "_" + checksum_type, None)
if checksum is not None:
checksum = None
if save:
self.save()
self.close_file() # Flush
Expand Down
40 changes: 26 additions & 14 deletions chunked_upload/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
from .settings import MAX_BYTES
from .models import ChunkedUpload
from .response import Response
from .constants import http_status, COMPLETE, FAILED
from .constants import (http_status, COMPLETE, FAILED,
SUPPORTED_CHUCKSUM_ALGORITHMS)
from .exceptions import ChunkedUploadError


Expand Down Expand Up @@ -227,9 +228,13 @@ class ChunkedUploadCompleteView(ChunkedUploadBaseView):
define what to do when upload is complete.
"""

# I wouldn't recommend to turn off the md5 check, unless is really
# I wouldn't recommend to turn off the checksum check, unless is really
# impacting your performance. Proceed at your own risk.
do_md5_check = True
do_checksum_check = True

# You can choose any checksum type in 'SUPPORTED_CHECKSUM_ALGORITHM'.
# And multiple checksums check is supoorted.
supported_checksums = SUPPORTED_CHUCKSUM_ALGORITHMS

def on_completion(self, uploaded_file, request):
"""
Expand All @@ -245,25 +250,23 @@ def is_valid_chunked_upload(self, chunked_upload):
return ChunkedUploadError(status=http_status.HTTP_400_BAD_REQUEST,
detail=error_msg)

def md5_check(self, chunked_upload, md5):
def checksum_check(self, checksum_alg, chunked_upload, checksum):
"""
Verify if md5 checksum sent by client matches generated md5.
Verify if checksum sent by client matches generated checksum.
"""
if chunked_upload.md5 != md5:
if getattr(chunked_upload, checksum_alg, None) != checksum:
chunked_upload.status = FAILED
self._save(chunked_upload)
raise ChunkedUploadError(status=http_status.HTTP_400_BAD_REQUEST,
detail='md5 checksum does not match')
detail='%s check does not match. '
'%s expected.' %
(checksum_alg, getattr(chunked_upload, checksum_alg, None)))

def _post(self, request, *args, **kwargs):
upload_id = request.POST.get('upload_id')
md5 = request.POST.get('md5')

error_msg = None
if self.do_md5_check:
if not upload_id or not md5:
error_msg = "Both 'upload_id' and 'md5' are required"
elif not upload_id:
if not upload_id:
error_msg = "'upload_id' is required"
if error_msg:
raise ChunkedUploadError(status=http_status.HTTP_400_BAD_REQUEST,
Expand All @@ -274,8 +277,17 @@ def _post(self, request, *args, **kwargs):

self.validate(request)
self.is_valid_chunked_upload(chunked_upload)
if self.do_md5_check:
self.md5_check(chunked_upload, md5)

if self.do_checksum_check:
for checksum_alg in self.supported_checksums:
if checksum_alg in request.POST:
self.checksum_check(checksum_alg, chunked_upload, request.POST[checksum_alg])
break
else: # Didn't find any checksum
raise ChunkedUploadError(status=http_status.HTTP_400_BAD_REQUEST,
detail="Didn't find any checksum, "
"checksum in %s is required." %
self.supported_checksums)

chunked_upload.status = COMPLETE
chunked_upload.completed_on = timezone.now()
Expand Down