Skip to content

Commit f7cccef

Browse files
Merge pull request #28 from mochic/feature/25_upload-checksum-ext-5
add support for tus upload-checksum header
2 parents 2b95c6b + 711020a commit f7cccef

File tree

4 files changed

+67
-4
lines changed

4 files changed

+67
-4
lines changed

tests/test_request.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import mock
2+
import base64
3+
import hashlib
24

35
from tusclient import client, request
46
from tests import mixin
@@ -10,7 +12,6 @@ def setUp(self):
1012
self.request = request.TusRequest(self.uploader)
1113

1214
def test_perform(self):
13-
1415
with mock.patch.object(self.request, 'handle') as mock_:
1516
self.request.handle = mock_
1617
self.request.perform()
@@ -24,6 +25,25 @@ def test_perform(self):
2425
'PATCH', '/files/15acd89eabdf5738ffc',
2526
f.read(), headers)
2627

28+
def test_perform_checksum(self):
29+
self.uploader.upload_checksum = True
30+
self.request_checksum = request.TusRequest(self.uploader)
31+
with mock.patch.object(self.request_checksum, 'handle') as mock_:
32+
self.request_checksum.handle = mock_
33+
self.request_checksum.perform()
34+
with open('LICENSE', 'rb') as f:
35+
license = f.read()
36+
headers = {
37+
'upload-offset': 0,
38+
'Content-Type': 'application/offset+octet-stream'
39+
}
40+
headers.update(self.uploader.headers)
41+
headers["upload-checksum"] = "sha1 " + \
42+
base64.standard_b64encode(hashlib.sha1(license).digest()).decode("ascii")
43+
mock_.request.assert_called_with(
44+
'PATCH', '/files/15acd89eabdf5738ffc',
45+
license, headers)
46+
2747
def test_close(self):
2848
with mock.patch.object(self.request, 'handle') as mock_:
2949
self.request.handle = mock_

tests/test_uploader.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,10 @@ def test_upload_retry(self, request_mock):
152152
with pytest.raises(exceptions.TusCommunicationError):
153153
self.uploader.upload_chunk()
154154
self.assertEqual(self.uploader._retried, NUM_OF_RETRIES)
155+
156+
@mock.patch('tusclient.uploader.TusRequest')
157+
def test_upload_checksum(self, request_mock):
158+
self.mock_pycurl(request_mock)
159+
self.uploader.upload_checksum = True
160+
self.uploader.upload()
161+
self.assertEqual(self.uploader.offset, self.uploader.file_size)

tusclient/request.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import http.client
22
import errno
3+
import base64
34
from future.moves.urllib.parse import urlparse
45

56
from tusclient.exceptions import TusUploadFailed
@@ -40,6 +41,9 @@ def __init__(self, uploader):
4041
}
4142
self._request_headers.update(uploader.headers)
4243
self._content_length = uploader.request_length
44+
self._upload_checksum = uploader.upload_checksum
45+
self._checksum_algorithm = uploader.checksum_algorithm
46+
self._checksum_algorithm_name = uploader.checksum_algorithm_name
4347
self._response = None
4448

4549
@property
@@ -57,7 +61,16 @@ def perform(self):
5761
host = '{}://{}'.format(self._url.scheme, self._url.netloc)
5862
path = self._url.geturl().replace(host, '', 1)
5963

60-
self.handle.request("PATCH", path, self.file.read(self._content_length), self._request_headers)
64+
chunk = self.file.read(self._content_length)
65+
if self._upload_checksum:
66+
self._request_headers["upload-checksum"] = \
67+
" ".join((
68+
self._checksum_algorithm_name,
69+
base64.b64encode(
70+
self._checksum_algorithm(chunk).digest()
71+
).decode("ascii"),
72+
))
73+
self.handle.request("PATCH", path, chunk, self._request_headers)
6174
self._response = self.handle.getresponse()
6275
self.status_code = self._response.status
6376
self.response_headers = {k.lower(): v for k, v in self._response.getheaders()}
@@ -74,4 +87,4 @@ def close(self):
7487
"""
7588
close request handle and end request session
7689
"""
77-
self.handle.close()
90+
self.handle.close()

tusclient/uploader.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from six import iteritems, b, wraps, MAXSIZE
88
from six.moves.urllib.parse import urljoin
99
import requests
10+
import hashlib
1011

1112
from tusclient.exceptions import TusUploadFailed, TusCommunicationError
1213
from tusclient.request import TusRequest
@@ -76,6 +77,9 @@ class Uploader(object):
7677
would be used. But you can set your own custom fingerprint module by passing it to the constructor.
7778
- log_func (<function>):
7879
A logging function to be passed diagnostic messages during file uploads
80+
- upload_checksum (bool):
81+
Whether or not to supply the Upload-Checksum header along with each
82+
chunk. Defaults to False.
7983
8084
:Constructor Args:
8185
- file_path (str)
@@ -90,13 +94,16 @@ class Uploader(object):
9094
- url_storage (Optinal [<tusclient.storage.interface.Storage>])
9195
- fingerprinter (Optional [<tusclient.fingerprint.interface.Fingerprint>])
9296
- log_func (Optional [<function>])
97+
- upload_checksum (Optional[bool])
9398
"""
9499
DEFAULT_HEADERS = {"Tus-Resumable": "1.0.0"}
95100
DEFAULT_CHUNK_SIZE = MAXSIZE
101+
CHECKSUM_ALGORITHM_PAIR = ("sha1", hashlib.sha1, )
96102

97103
def __init__(self, file_path=None, file_stream=None, url=None, client=None,
98104
chunk_size=None, metadata=None, retries=0, retry_delay=30,
99-
store_url=False, url_storage=None, fingerprinter=None, log_func=None):
105+
store_url=False, url_storage=None, fingerprinter=None,
106+
log_func=None, upload_checksum=False):
100107
if file_path is None and file_stream is None:
101108
raise ValueError("Either 'file_path' or 'file_stream' cannot be None.")
102109

@@ -122,6 +129,9 @@ def __init__(self, file_path=None, file_stream=None, url=None, client=None,
122129
self._retried = 0
123130
self.retry_delay = retry_delay
124131
self.log_func = log_func
132+
self.upload_checksum = upload_checksum
133+
self.__checksum_algorithm_name, self.__checksum_algorithm = \
134+
self.CHECKSUM_ALGORITHM_PAIR
125135

126136
# it is important to have this as a @property so it gets
127137
# updated client headers.
@@ -142,6 +152,19 @@ def headers_as_list(self):
142152
headers = self.headers
143153
headers_list = ['{}: {}'.format(key, value) for key, value in iteritems(headers)]
144154
return headers_list
155+
156+
@property
157+
def checksum_algorithm(self):
158+
"""The checksum algorithm to be used for the Upload-Checksum extension.
159+
"""
160+
return self.__checksum_algorithm
161+
162+
@property
163+
def checksum_algorithm_name(self):
164+
"""The name of the checksum algorithm to be used for the Upload-Checksum
165+
extension.
166+
"""
167+
return self.__checksum_algorithm_name
145168

146169
@_catch_requests_error
147170
def get_offset(self):

0 commit comments

Comments
 (0)