]>
Commit | Line | Data |
---|---|---|
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 | 22 | import io |
a097e88a MT |
23 | import tornado.web |
24 | ||
f062b044 | 25 | from . import base |
bd686a79 | 26 | from .. import uploads |
a097e88a MT |
27 | from .. import users |
28 | ||
f062b044 MT |
29 | class 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 |
103 | class 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 | }) |