summaryrefslogtreecommitdiff
path: root/main.py
diff options
context:
space:
mode:
authorJF2019-07-09 14:56:21 -0700
committerjvoisin2019-07-09 14:56:21 -0700
commit06346e19464c376c0c2ca13ef4218559f9df4212 (patch)
tree94db98bcfbcd68dffdec0fa60239baef278510a8 /main.py
parent9d155d171e916cd3c2c34f6c50955745f8929e79 (diff)
added a docker dev environment
Signed-off-by: Jan Friedli <jan.friedli@immerda.ch>
Diffstat (limited to '')
-rw-r--r--main.py232
1 files changed, 158 insertions, 74 deletions
diff --git a/main.py b/main.py
index ab2ba41..24a15ba 100644
--- a/main.py
+++ b/main.py
@@ -1,107 +1,191 @@
1import os 1import os
2import hashlib
3import hmac 2import hmac
4import mimetypes as mtype 3import mimetypes as mtype
4import jinja2
5import base64
6import io
7import binascii
8import utils
5 9
6from libmat2 import parser_factory 10from libmat2 import parser_factory
11from flask import Flask, flash, request, redirect, url_for, render_template, send_from_directory, after_this_request
12from flask_restful import Resource, Api, reqparse, abort
13from werkzeug.utils import secure_filename
14from werkzeug.datastructures import FileStorage
15from flask_cors import CORS
16from urllib.parse import urljoin
7 17
8from flask import Flask, flash, request, redirect, url_for, render_template
9from flask import send_from_directory, after_this_request
10import jinja2
11 18
12from werkzeug.utils import secure_filename 19def create_app(test_config=None):
20 app = Flask(__name__)
21 app.config['SECRET_KEY'] = os.urandom(32)
22 app.config['UPLOAD_FOLDER'] = './uploads/'
23 app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB
24 app.config['CUSTOM_TEMPLATES_DIR'] = 'custom_templates'
13 25
26 app.jinja_loader = jinja2.ChoiceLoader([ # type: ignore
27 jinja2.FileSystemLoader(app.config['CUSTOM_TEMPLATES_DIR']),
28 app.jinja_loader,
29 ])
14 30
15app = Flask(__name__) 31 api = Api(app)
16app.config['SECRET_KEY'] = os.urandom(32) 32 CORS(app, resources={r"/api/*": {"origins": utils.get_allow_origin_header_value()}})
17app.config['UPLOAD_FOLDER'] = './uploads/'
18app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB
19app.config['CUSTOM_TEMPLATES_DIR'] = 'custom_templates'
20 33
21app.jinja_loader = jinja2.ChoiceLoader([ # type: ignore 34 @app.route('/download/<string:key>/<string:filename>')
22 jinja2.FileSystemLoader(app.config['CUSTOM_TEMPLATES_DIR']), 35 def download_file(key:str, filename:str):
23 app.jinja_loader, 36 if filename != secure_filename(filename):
24 ]) 37 return redirect(url_for('upload_file'))
38
39 complete_path, filepath = get_file_paths(filename)
40
41 if not os.path.exists(complete_path):
42 return redirect(url_for('upload_file'))
43 if hmac.compare_digest(utils.hash_file(complete_path), key) is False:
44 return redirect(url_for('upload_file'))
25 45
26def __hash_file(filepath: str) -> str: 46 @after_this_request
27 sha256 = hashlib.sha256() 47 def remove_file(response):
28 with open(filepath, 'rb') as f: 48 os.remove(complete_path)
29 while True: 49 return response
30 data = f.read(65536) # read the file by chunk of 64k 50 return send_from_directory(app.config['UPLOAD_FOLDER'], filepath)
31 if not data:
32 break
33 sha256.update(data)
34 return sha256.hexdigest()
35 51
52 @app.route('/', methods=['GET', 'POST'])
53 def upload_file():
54 utils.check_upload_folder(app.config['UPLOAD_FOLDER'])
55 mimetypes = get_supported_extensions()
36 56
37@app.route('/download/<string:key>/<string:filename>') 57 if request.method == 'POST':
38def download_file(key:str, filename:str): 58 if 'file' not in request.files: # check if the post request has the file part
39 if filename != secure_filename(filename): 59 flash('No file part')
40 return redirect(url_for('upload_file')) 60 return redirect(request.url)
41 61
42 filepath = secure_filename(filename) 62 uploaded_file = request.files['file']
63 if not uploaded_file.filename:
64 flash('No selected file')
65 return redirect(request.url)
43 66
44 complete_path = os.path.join(app.config['UPLOAD_FOLDER'], filepath) 67 filename, filepath = save_file(uploaded_file)
45 if not os.path.exists(complete_path): 68 parser, mime = get_file_parser(filepath)
46 return redirect(url_for('upload_file'))
47 if hmac.compare_digest(__hash_file(complete_path), key) is False:
48 print('hash: %s, key: %s' % (__hash_file(complete_path), key))
49 return redirect(url_for('upload_file'))
50 69
51 @after_this_request 70 if parser is None:
52 def remove_file(response): 71 flash('The type %s is not supported' % mime)
53 os.remove(complete_path) 72 return redirect(url_for('upload_file'))
54 return response
55 return send_from_directory(app.config['UPLOAD_FOLDER'], filepath)
56 73
57@app.route('/', methods=['GET', 'POST']) 74 meta = parser.get_meta()
58def upload_file():
59 if not os.path.exists(app.config['UPLOAD_FOLDER']):
60 os.mkdir(app.config['UPLOAD_FOLDER'])
61 75
62 mimetypes = set() 76 if parser.remove_all() is not True:
63 for parser in parser_factory._get_parsers(): 77 flash('Unable to clean %s' % mime)
64 for m in parser.mimetypes: 78 return redirect(url_for('upload_file'))
65 mimetypes |= set(mtype.guess_all_extensions(m, strict=False))
66 # since `guess_extension` might return `None`, we need to filter it out
67 mimetypes = sorted(filter(None, mimetypes))
68 79
69 if request.method == 'POST': 80 key, meta_after, output_filename = cleanup(parser, filepath)
70 if 'file' not in request.files: # check if the post request has the file part 81
71 flash('No file part') 82 return render_template(
72 return redirect(request.url) 83 'download.html', mimetypes=mimetypes, meta=meta, filename=output_filename, meta_after=meta_after, key=key
73 uploaded_file = request.files['file'] 84 )
74 if not uploaded_file.filename: 85
75 flash('No selected file') 86 max_file_size = int(app.config['MAX_CONTENT_LENGTH'] / 1024 / 1024)
76 return redirect(request.url) 87 return render_template('index.html', max_file_size=max_file_size, mimetypes=mimetypes)
77 filename = secure_filename(uploaded_file.filename) 88
89 def get_supported_extensions():
90 extensions = set()
91 for parser in parser_factory._get_parsers():
92 for m in parser.mimetypes:
93 extensions |= set(mtype.guess_all_extensions(m, strict=False))
94 # since `guess_extension` might return `None`, we need to filter it out
95 return sorted(filter(None, extensions))
96
97 def save_file(file):
98 filename = secure_filename(file.filename)
78 filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) 99 filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
79 uploaded_file.save(os.path.join(filepath)) 100 file.save(os.path.join(filepath))
101 return filename, filepath
80 102
103 def get_file_parser(filepath: str):
81 parser, mime = parser_factory.get_parser(filepath) 104 parser, mime = parser_factory.get_parser(filepath)
82 if parser is None: 105 return parser, mime
83 flash('The type %s is not supported' % mime)
84 return redirect(url_for('upload_file'))
85
86 meta = parser.get_meta()
87 106
88 if parser.remove_all() is not True: 107 def cleanup(parser, filepath):
89 flash('Unable to clean %s' % mime)
90 return redirect(url_for('upload_file'))
91 output_filename = os.path.basename(parser.output_filename) 108 output_filename = os.path.basename(parser.output_filename)
92
93 # Get metadata after cleanup
94 parser, _ = parser_factory.get_parser(parser.output_filename) 109 parser, _ = parser_factory.get_parser(parser.output_filename)
95 meta_after = parser.get_meta() 110 meta_after = parser.get_meta()
96 os.remove(filepath) 111 os.remove(filepath)
97 112
98 key = __hash_file(os.path.join(app.config['UPLOAD_FOLDER'], output_filename)) 113 key = utils.hash_file(os.path.join(app.config['UPLOAD_FOLDER'], output_filename))
114 return key, meta_after, output_filename
115
116 def get_file_paths(filename):
117 filepath = secure_filename(filename)
118
119 complete_path = os.path.join(app.config['UPLOAD_FOLDER'], filepath)
120 return complete_path, filepath
121
122 class APIUpload(Resource):
123
124 def post(self):
125 utils.check_upload_folder(app.config['UPLOAD_FOLDER'])
126 req_parser = reqparse.RequestParser()
127 req_parser.add_argument('file_name', type=str, required=True, help='Post parameter is not specified: file_name')
128 req_parser.add_argument('file', type=str, required=True, help='Post parameter is not specified: file')
129
130 args = req_parser.parse_args()
131 try:
132 file_data = base64.b64decode(args['file'])
133 except binascii.Error as err:
134 abort(400, message='Failed decoding file: ' + str(err))
135
136 file = FileStorage(stream=io.BytesIO(file_data), filename=args['file_name'])
137 filename, filepath = save_file(file)
138 parser, mime = get_file_parser(filepath)
139
140 if parser is None:
141 abort(415, message='The type %s is not supported' % mime)
142
143 meta = parser.get_meta()
144 if not parser.remove_all():
145 abort(500, message='Unable to clean %s' % mime)
146
147 key, meta_after, output_filename = cleanup(parser, filepath)
148 return {
149 'output_filename': output_filename,
150 'key': key,
151 'meta': meta,
152 'meta_after': meta_after,
153 'download_link': urljoin(request.host_url, '%s/%s/%s/%s' % ('api', 'download', key, output_filename))
154 }
155
156 class APIDownload(Resource):
157 def get(self, key: str, filename: str):
158
159 if filename != secure_filename(filename):
160 abort(400, message='Insecure filename')
161
162 complete_path, filepath = get_file_paths(filename)
163
164 if not os.path.exists(complete_path):
165 abort(404, message='File not found')
166 return redirect(url_for('upload_file'))
167
168 if hmac.compare_digest(utils.hash_file(complete_path), key) is False:
169 abort(400, message='The file hash does not match')
170 return redirect(url_for('upload_file'))
171
172 @after_this_request
173 def remove_file(response):
174 os.remove(complete_path)
175 return response
176
177 return send_from_directory(app.config['UPLOAD_FOLDER'], filepath)
178
179 class APIMSupportedExtensions(Resource):
180 def get(self):
181 return get_supported_extensions()
99 182
100 return render_template('download.html', mimetypes=mimetypes, meta=meta, filename=output_filename, meta_after=meta_after, key=key) 183 api.add_resource(APIUpload, '/api/upload')
184 api.add_resource(APIDownload, '/api/download/<string:key>/<string:filename>')
185 api.add_resource(APIMSupportedExtensions, '/api/extension')
101 186
102 max_file_size = int(app.config['MAX_CONTENT_LENGTH'] / 1024 / 1024) 187 return app
103 return render_template('index.html', max_file_size=max_file_size, mimetypes=mimetypes)
104 188
105 189
106if __name__ == '__main__': # pragma: no cover 190if __name__ == '__main__': # pragma: no cover
107 app.run() 191 create_app().run()