]>
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 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 |
105 | class 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 | }) |