summaryrefslogtreecommitdiff
path: root/main.py
diff options
context:
space:
mode:
authorjfriedli2019-09-21 05:58:05 -0700
committerjvoisin2019-09-21 05:58:05 -0700
commit70978f7db245e30206486fc4c0605cc992005aac (patch)
treefd7317f4603deac5a3e8440f0c4c0c9a143c1437 /main.py
parentc96d3b8178a007af747b9a5922e03c2ab2c47a6f (diff)
Api/bulk download for frontend
Diffstat (limited to 'main.py')
-rw-r--r--main.py108
1 files changed, 84 insertions, 24 deletions
diff --git a/main.py b/main.py
index b059bfe..9349ec1 100644
--- a/main.py
+++ b/main.py
@@ -1,12 +1,15 @@
1import os 1import os
2import hmac 2import hmac
3import mimetypes as mtype 3import mimetypes as mtype
4from uuid import uuid4
4import jinja2 5import jinja2
5import base64 6import base64
6import io 7import io
7import binascii 8import binascii
8import utils 9import zipfile
9 10
11from cerberus import Validator
12import utils
10from libmat2 import parser_factory 13from libmat2 import parser_factory
11from flask import Flask, flash, request, redirect, url_for, render_template, send_from_directory, after_this_request 14from flask import Flask, flash, request, redirect, url_for, render_template, send_from_directory, after_this_request
12from flask_restful import Resource, Api, reqparse, abort 15from flask_restful import Resource, Api, reqparse, abort
@@ -119,6 +122,19 @@ def create_app(test_config=None):
119 complete_path = os.path.join(app.config['UPLOAD_FOLDER'], filepath) 122 complete_path = os.path.join(app.config['UPLOAD_FOLDER'], filepath)
120 return complete_path, filepath 123 return complete_path, filepath
121 124
125 def is_valid_api_download_file(filename, key):
126 if filename != secure_filename(filename):
127 abort(400, message='Insecure filename')
128
129 complete_path, filepath = get_file_paths(filename)
130
131 if not os.path.exists(complete_path):
132 abort(404, message='File not found')
133
134 if hmac.compare_digest(utils.hash_file(complete_path), key) is False:
135 abort(400, message='The file hash does not match')
136 return complete_path, filepath
137
122 class APIUpload(Resource): 138 class APIUpload(Resource):
123 139
124 def post(self): 140 def post(self):
@@ -145,30 +161,18 @@ def create_app(test_config=None):
145 abort(500, message='Unable to clean %s' % mime) 161 abort(500, message='Unable to clean %s' % mime)
146 162
147 key, meta_after, output_filename = cleanup(parser, filepath) 163 key, meta_after, output_filename = cleanup(parser, filepath)
148 return { 164 return utils.return_file_created_response(
149 'output_filename': output_filename, 165 output_filename,
150 'mime': mime, 166 mime,
151 'key': key, 167 key,
152 'meta': meta, 168 meta,
153 'meta_after': meta_after, 169 meta_after,
154 'download_link': urljoin(request.host_url, '%s/%s/%s/%s' % ('api', 'download', key, output_filename)) 170 urljoin(request.host_url, '%s/%s/%s/%s' % ('api', 'download', key, output_filename))
155 } 171 )
156 172
157 class APIDownload(Resource): 173 class APIDownload(Resource):
158 def get(self, key: str, filename: str): 174 def get(self, key: str, filename: str):
159 175 complete_path, filepath = is_valid_api_download_file(filename, key)
160 if filename != secure_filename(filename):
161 abort(400, message='Insecure filename')
162
163 complete_path, filepath = get_file_paths(filename)
164
165 if not os.path.exists(complete_path):
166 abort(404, message='File not found')
167 return redirect(url_for('upload_file'))
168
169 if hmac.compare_digest(utils.hash_file(complete_path), key) is False:
170 abort(400, message='The file hash does not match')
171 return redirect(url_for('upload_file'))
172 176
173 @after_this_request 177 @after_this_request
174 def remove_file(response): 178 def remove_file(response):
@@ -177,16 +181,72 @@ def create_app(test_config=None):
177 181
178 return send_from_directory(app.config['UPLOAD_FOLDER'], filepath) 182 return send_from_directory(app.config['UPLOAD_FOLDER'], filepath)
179 183
180 class APIMSupportedExtensions(Resource): 184 class APIBulkDownloadCreator(Resource):
185 schema = {
186 'download_list': {
187 'type': 'list',
188 'minlength': 2,
189 'maxlength': int(os.environ.get('MAT2_MAX_FILES_BULK_DOWNLOAD', 10)),
190 'schema': {
191 'type': 'dict',
192 'schema': {
193 'key': {'type': 'string'},
194 'file_name': {'type': 'string'}
195 }
196 }
197 }
198 }
199 v = Validator(schema)
200
201 def post(self):
202 utils.check_upload_folder(app.config['UPLOAD_FOLDER'])
203 data = request.json
204 if not self.v.validate(data):
205 abort(400, message=self.v.errors)
206 # prevent the zip file from being overwritten
207 zip_filename = 'files.' + str(uuid4()) + '.zip'
208 zip_path = os.path.join(app.config['UPLOAD_FOLDER'], zip_filename)
209 cleaned_files_zip = zipfile.ZipFile(zip_path, 'w')
210 with cleaned_files_zip:
211 for file_candidate in data['download_list']:
212 complete_path, file_path = is_valid_api_download_file(
213 file_candidate['file_name'],
214 file_candidate['key']
215 )
216 try:
217 cleaned_files_zip.write(complete_path)
218 except ValueError:
219 abort(400, message='Creating the archive failed')
220
221 try:
222 cleaned_files_zip.testzip()
223 except ValueError as e:
224 abort(400, message=str(e))
225
226 parser, mime = get_file_parser(zip_path)
227 if not parser.remove_all():
228 abort(500, message='Unable to clean %s' % mime)
229 key, meta_after, output_filename = cleanup(parser, zip_path)
230 return {
231 'output_filename': output_filename,
232 'mime': mime,
233 'key': key,
234 'meta_after': meta_after,
235 'download_link': urljoin(request.host_url, '%s/%s/%s/%s' % ('api', 'download', key, output_filename))
236 }, 201
237
238 class APISupportedExtensions(Resource):
181 def get(self): 239 def get(self):
182 return get_supported_extensions() 240 return get_supported_extensions()
183 241
184 api.add_resource(APIUpload, '/api/upload') 242 api.add_resource(APIUpload, '/api/upload')
185 api.add_resource(APIDownload, '/api/download/<string:key>/<string:filename>') 243 api.add_resource(APIDownload, '/api/download/<string:key>/<string:filename>')
186 api.add_resource(APIMSupportedExtensions, '/api/extension') 244 api.add_resource(APIBulkDownloadCreator, '/api/download/bulk')
245 api.add_resource(APISupportedExtensions, '/api/extension')
187 246
188 return app 247 return app
189 248
249
190app = create_app() 250app = create_app()
191 251
192if __name__ == '__main__': # pragma: no cover 252if __name__ == '__main__': # pragma: no cover