]> git.ipfire.org Git - pbs.git/blame - src/web/uploads.py
uploads: Rewrite the whole thing
[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
MT
86 except uploads.UnsupportedDigestException as e:
87 raise tornado.web.HTTPError(400,
88 "Unsupported digest %s" % digest_algo) from e
89
a097e88a
MT
90 except users.QuotaExceededError as e:
91 raise tornado.web.HTTPError(400,
92 "Quota exceeded for %s" % self.current_user) from e
93
bd686a79
MT
94 except ValueError as e:
95 raise tornado.web.HTTPError(400, "%s" % e) from e
a097e88a
MT
96
97 # Send the ID of the upload back to the client
98 self.finish({
afc35be4 99 "id" : "%s" % upload.uuid,
fe59762c 100 "expires_at" : upload.expires_at.isoformat(),
a097e88a 101 })
9eaf98bf 102
f062b044 103
bd686a79 104@tornado.web.stream_request_body
f062b044
MT
105class APIv1DetailHandler(base.APIMixin, tornado.web.RequestHandler):
106 # Allow users to perform uploads
107 allow_users = True
108
bd686a79
MT
109 def initialize(self):
110 # Buffer to cache the uploaded content
111 self.buffer = io.BytesIO()
112
113 def data_received(self, data):
114 """
115 Called when some data is being received
116 """
117 self.buffer.write(data)
118
119 # Yes, this does not require authentication. You have seen this correctly.
120 # This is because of us using SPNEGO which might cause a request being sent
121 # more than once, which therefore means that the payload is being transferred
122 # more than once.
123 # To avoid this, we request the digest when the upload is being created, we
124 # then generate a unique ID which an attacker would have to guess first and
125 # then have to upload a file which's hash collides with the original file.
126 async def put(self, uuid):
127 """
128 Called to store the received payload
129 """
130 # Fetch the upload
131 upload = self.backend.uploads.get_by_uuid(uuid)
132 if not upload:
133 raise tornado.web.HTTPError(404, "Could not find upload %s" % uuid)
134
135 # Import the payload from the buffer
136 with self.db.transaction():
137 try:
138 await upload.copyfrom(self.buffer)
139
140 except ValueError as e:
141 raise tornado.web.HTTPError(400, "%s" % e) from e
142
143 @base.negotiate
f062b044 144 async def delete(self, uuid):
9eaf98bf
MT
145 """
146 Deletes an upload with a certain UUID
147 """
9eaf98bf
MT
148 # Fetch the upload
149 upload = self.backend.uploads.get_by_uuid(uuid)
150 if not upload:
151 raise tornado.web.HTTPError(404, "Could not find upload %s" % uuid)
152
153 # Check for permissions
154 if not upload.has_perm(self.current_user):
155 raise tornado.web.HTTPError(403, "%s has no permission to delete %s" \
156 % (self.current_user, upload))
157
158 # Delete the upload
159 with self.db.transaction():
160 await upload.delete()
161
162 self.finish({
163 "status" : "ok",
164 })