]> git.ipfire.org Git - pbs.git/blame - src/web/uploads.py
web: Send better errors for failed upload requests
[pbs.git] / src / web / uploads.py
CommitLineData
a097e88a
MT
1#!/usr/bin/python3
2###############################################################################
3# #
4# Pakfire - The IPFire package management system #
5# Copyright (C) 2011 Pakfire development team #
6# #
7# This program is free software: you can redistribute it and/or modify #
8# it under the terms of the GNU General Public License as published by #
9# the Free Software Foundation, either version 3 of the License, or #
10# (at your option) any later version. #
11# #
12# This program is distributed in the hope that it will be useful, #
13# but WITHOUT ANY WARRANTY; without even the implied warranty of #
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15# GNU General Public License for more details. #
16# #
17# You should have received a copy of the GNU General Public License #
18# along with this program. If not, see <http://www.gnu.org/licenses/>. #
19# #
20###############################################################################
21
fe59762c 22import io
a097e88a
MT
23import tornado.web
24
f062b044 25from . import base
bd686a79 26from .. import uploads
a097e88a
MT
27from .. import users
28
f062b044
MT
29class APIv1IndexHandler(base.APIMixin, tornado.web.RequestHandler):
30 # Allow users to perform uploads
31 allow_users = True
32
bd686a79 33 @base.negotiate
962e472b
MT
34 def get(self):
35 uploads = []
36
37 for upload in self.current_user.uploads:
38 uploads.append({
f0355119 39 "id" : "%s" % upload.uuid,
962e472b
MT
40 "filename" : upload.filename,
41 "size" : upload.size,
42
43 "created_at" : upload.created_at.isoformat(),
44 "expires_at" : upload.expires_at.isoformat(),
45 })
46
47 self.finish({
48 "status" : "ok",
49 "uploads" : uploads,
50 })
51
bd686a79
MT
52 @base.negotiate
53 async def post(self):
fe59762c 54 """
bd686a79 55 Creates a new upload and returns its UUID
fe59762c 56 """
a097e88a 57 # Fetch the filename
7ef2c528 58 filename = self.get_argument("filename")
a097e88a
MT
59
60 # Fetch file size
7ef2c528 61 size = self.get_argument_int("size")
a097e88a 62
bd686a79
MT
63 # Fetch the digest algorithm
64 digest_algo = self.get_argument("hexdigest_algo")
fe59762c 65
bd686a79
MT
66 # Fetch the digest
67 hexdigest = self.get_argument("hexdigest")
fe59762c 68
bd686a79
MT
69 # Convert hexdigest
70 try:
71 digest = bytes.fromhex(hexdigest)
72 except ValueError as e:
73 raise tornado.web.HTTPError(400, "Invalid hexdigest") from e
fe59762c 74
7ef2c528
MT
75 # Create a new upload
76 with self.db.transaction():
a097e88a 77 try:
73986414 78 upload = await self.backend.uploads.create(
7ef2c528
MT
79 filename,
80 size=size,
e5910b93 81 owner=self.current_user,
bd686a79
MT
82 digest_algo=digest_algo,
83 digest=digest,
7ef2c528 84 )
fe59762c 85
bd686a79 86 except uploads.UnsupportedDigestException as e:
c27e7f37 87 raise base.APIError(400, "Unsupported digest %s" % digest_algo) from e
bd686a79 88
a097e88a 89 except users.QuotaExceededError as e:
c27e7f37 90 raise base.APIError(400, "Quota exceeded for %s" % self.current_user) from e
a097e88a 91
bd686a79 92 except ValueError as e:
c27e7f37 93 raise base.APIError(400, "%s" % e) from e
a097e88a
MT
94
95 # Send the ID of the upload back to the client
96 self.finish({
afc35be4 97 "id" : "%s" % upload.uuid,
fe59762c 98 "expires_at" : upload.expires_at.isoformat(),
a097e88a 99 })
9eaf98bf 100
f062b044 101
bd686a79 102@tornado.web.stream_request_body
f062b044
MT
103class APIv1DetailHandler(base.APIMixin, tornado.web.RequestHandler):
104 # Allow users to perform uploads
105 allow_users = True
106
bd686a79
MT
107 def initialize(self):
108 # Buffer to cache the uploaded content
109 self.buffer = io.BytesIO()
110
111 def data_received(self, data):
112 """
113 Called when some data is being received
114 """
115 self.buffer.write(data)
116
117 # Yes, this does not require authentication. You have seen this correctly.
118 # This is because of us using SPNEGO which might cause a request being sent
119 # more than once, which therefore means that the payload is being transferred
120 # more than once.
121 # To avoid this, we request the digest when the upload is being created, we
122 # then generate a unique ID which an attacker would have to guess first and
123 # then have to upload a file which's hash collides with the original file.
124 async def put(self, uuid):
125 """
126 Called to store the received payload
127 """
128 # Fetch the upload
129 upload = self.backend.uploads.get_by_uuid(uuid)
130 if not upload:
131 raise tornado.web.HTTPError(404, "Could not find upload %s" % uuid)
132
133 # Import the payload from the buffer
134 with self.db.transaction():
135 try:
136 await upload.copyfrom(self.buffer)
137
138 except ValueError as e:
c27e7f37 139 raise base.APIError(400, "%s" % e) from e
bd686a79
MT
140
141 @base.negotiate
f062b044 142 async def delete(self, uuid):
9eaf98bf
MT
143 """
144 Deletes an upload with a certain UUID
145 """
9eaf98bf
MT
146 # Fetch the upload
147 upload = self.backend.uploads.get_by_uuid(uuid)
148 if not upload:
149 raise tornado.web.HTTPError(404, "Could not find upload %s" % uuid)
150
151 # Check for permissions
152 if not upload.has_perm(self.current_user):
153 raise tornado.web.HTTPError(403, "%s has no permission to delete %s" \
154 % (self.current_user, upload))
155
156 # Delete the upload
157 with self.db.transaction():
158 await upload.delete()
159
160 self.finish({
161 "status" : "ok",
162 })