import tornado.web
from . import base
+from .. import uploads
from .. import users
-@tornado.web.stream_request_body
class APIv1IndexHandler(base.APIMixin, tornado.web.RequestHandler):
# Allow users to perform uploads
allow_users = True
- def initialize(self):
- # Buffer to cache the uploaded content
- self.buffer = io.BytesIO()
-
- def data_received(self, data):
- """
- Called when some data is being received
- """
- self.buffer.write(data)
-
- @tornado.web.authenticated
+ @base.negotiate
def get(self):
uploads = []
"uploads" : uploads,
})
- @tornado.web.authenticated
- async def put(self):
+ @base.negotiate
+ async def post(self):
"""
- Called after the entire file has been received
+ Creates a new upload and returns its UUID
"""
# Fetch the filename
filename = self.get_argument("filename")
# Fetch file size
size = self.get_argument_int("size")
- # Fetch the digest argument
- algo, delim, hexdigest = self.get_argument("digest").partition(":")
+ # Fetch the digest algorithm
+ digest_algo = self.get_argument("hexdigest_algo")
- # Convert hexdigest
- digest = bytes.fromhex(hexdigest)
+ # Fetch the digest
+ hexdigest = self.get_argument("hexdigest")
- # Move to the beginning of the buffer
- self.buffer.seek(0)
+ # Convert hexdigest
+ try:
+ digest = bytes.fromhex(hexdigest)
+ except ValueError as e:
+ raise tornado.web.HTTPError(400, "Invalid hexdigest") from e
# Create a new upload
with self.db.transaction():
filename,
size=size,
owner=self.current_user,
+ digest_algo=digest_algo,
+ digest=digest,
)
+ except uploads.UnsupportedDigestException as e:
+ raise tornado.web.HTTPError(400,
+ "Unsupported digest %s" % digest_algo) from e
+
except users.QuotaExceededError as e:
raise tornado.web.HTTPError(400,
"Quota exceeded for %s" % self.current_user) from e
- # Import the payload from the buffer
- await upload.copyfrom(self.buffer)
-
- # Check the digest
- if not await upload.check_digest(algo, digest):
- # 422 - Unprocessable Entity
- raise tornado.web.HTTPError(422, "Digest did not match")
+ except ValueError as e:
+ raise tornado.web.HTTPError(400, "%s" % e) from e
# Send the ID of the upload back to the client
self.finish({
})
+@tornado.web.stream_request_body
class APIv1DetailHandler(base.APIMixin, tornado.web.RequestHandler):
# Allow users to perform uploads
allow_users = True
- @tornado.web.authenticated
+ def initialize(self):
+ # Buffer to cache the uploaded content
+ self.buffer = io.BytesIO()
+
+ def data_received(self, data):
+ """
+ Called when some data is being received
+ """
+ self.buffer.write(data)
+
+ # Yes, this does not require authentication. You have seen this correctly.
+ # This is because of us using SPNEGO which might cause a request being sent
+ # more than once, which therefore means that the payload is being transferred
+ # more than once.
+ # To avoid this, we request the digest when the upload is being created, we
+ # then generate a unique ID which an attacker would have to guess first and
+ # then have to upload a file which's hash collides with the original file.
+ async def put(self, uuid):
+ """
+ Called to store the received payload
+ """
+ # Fetch the upload
+ upload = self.backend.uploads.get_by_uuid(uuid)
+ if not upload:
+ raise tornado.web.HTTPError(404, "Could not find upload %s" % uuid)
+
+ # Import the payload from the buffer
+ with self.db.transaction():
+ try:
+ await upload.copyfrom(self.buffer)
+
+ except ValueError as e:
+ raise tornado.web.HTTPError(400, "%s" % e) from e
+
+ @base.negotiate
async def delete(self, uuid):
"""
Deletes an upload with a certain UUID