From 52bf45eb3ac481c08b2a6a4fe7d3c5d79bf76f9e Mon Sep 17 00:00:00 2001
From: Andreas Nilsson <nilsson.andreas.85@gmail.com>
Date: Sun, 21 Jun 2015 14:09:13 +0200
Subject: [PATCH] Added support for blacklisting/whitelisting mimetypes. Also
checks max_file_size for attachments.
I added support for blacklisting and whitelisting mimetypes in
attachments. The idea is to block for example javascript and html to be
uploaded as attachments.
The code I have written so far reads from mediagoblin.ini:
[attachments] section and "blacklist" / "whitelist" properties.
Set in mediagoblin_local.ini:
allow_attachments = true
And add section:
[attachments]
blacklist = application/javascript, text/html, somethingelse
Blacklisting is for setting up what mime types to ALWAYS deny.
Whitelisting is for setting up what mime types to ONLY allow.
Also added support for limiting attachment file size by reading
max_file_size from the .ini file:
max_file_size = 1024
That would be one Gigabyte (I think).
The solution is not very elegant but it works!
---
mediagoblin/edit/views.py | 62 ++++++++++++++++-----
mediagoblin/static/js/file_size.js | 65 ++++++++++++++++------
.../templates/mediagoblin/edit/attachments.html | 9 +++
3 files changed, 105 insertions(+), 31 deletions(-)
diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py
index 97e33e6..2ca0d9d 100644
a
|
b
|
def edit_media(request, media):
|
103 | 103 | {'media': media, |
104 | 104 | 'form': form}) |
105 | 105 | |
106 | | |
107 | | # Mimetypes that browsers parse scripts in. |
108 | | # Content-sniffing isn't taken into consideration. |
109 | | UNSAFE_MIMETYPES = [ |
110 | | 'text/html', |
111 | | 'text/svg+xml'] |
112 | | |
| 106 | def read_list_from_config(section): |
| 107 | global_config = mg_globals.global_config |
| 108 | mimelist = [] |
| 109 | if 'attachments' in global_config and section in global_config['attachments']: |
| 110 | mimelist = global_config['attachments'][section][:] # FIXME: [:] is a bit hackish |
| 111 | return mimelist |
113 | 112 | |
114 | 113 | @get_media_entry_by_id |
115 | 114 | @require_active_login |
… |
… |
def edit_attachments(request, media):
|
117 | 116 | if mg_globals.app_config['allow_attachments']: |
118 | 117 | form = forms.EditAttachmentsForm() |
119 | 118 | |
| 119 | # Mimetypes that browsers parse scripts in. |
| 120 | # Content-sniffing isn't taken into consideration. |
| 121 | UNSAFE_MIMETYPES = [] |
| 122 | SAFE_MIMETYPES = [] |
| 123 | |
| 124 | # Blacklisting is for setting up what mime types to ALWAYS deny |
| 125 | UNSAFE_MIMETYPES = read_list_from_config('blacklist') |
| 126 | |
| 127 | # Whitelisting is for setting up what mime types to ONLY allow (optional) |
| 128 | SAFE_MIMETYPES = read_list_from_config('whitelist') |
| 129 | |
| 130 | bMimeAllowed = True |
| 131 | |
120 | 132 | # Add any attachements |
121 | 133 | if 'attachment_file' in request.files \ |
122 | 134 | and request.files['attachment_file']: |
… |
… |
def edit_attachments(request, media):
|
131 | 143 | # This method isn't flawless as we do the mimetype lookup on the |
132 | 144 | # machine parsing the upload form, and not necessarily the machine |
133 | 145 | # serving the attachments. |
134 | | if mimetypes.guess_type( |
135 | | request.files['attachment_file'].filename)[0] in \ |
136 | | UNSAFE_MIMETYPES: |
137 | | public_filename = secure_filename('{0}.notsafe'.format( |
138 | | request.files['attachment_file'].filename)) |
| 146 | |
| 147 | attachment_file = request.files['attachment_file'].filename |
| 148 | |
| 149 | # Check blacklist and (optionally) whitelist, guess_type strict is set to False |
| 150 | # to include more mimetypes to be scanned |
| 151 | if (SAFE_MIMETYPES != []) and (not mimetypes.guess_type(attachment_file, False)[0] \ |
| 152 | in SAFE_MIMETYPES): |
| 153 | # We hit a mime type that is NOT whitelisted and whitelist exists |
| 154 | messages.add_message(request, messages.ERROR, "Sorry, that format is not allowed!") |
| 155 | bMimeAllowed = False |
| 156 | #raise Forbidden("Attachment mimetype is not allowed") |
| 157 | public_filename = secure_filename('{0}.notsafe'.format( |
| 158 | attachment_file)) |
| 159 | elif (UNSAFE_MIMETYPES != []) and (mimetypes.guess_type(attachment_file, False)[0] \ |
| 160 | in UNSAFE_MIMETYPES): |
| 161 | # We hit a mime type that is blacklisted and blacklist exists |
| 162 | messages.add_message(request, messages.ERROR, "Sorry, that format is not allowed!") |
| 163 | bMimeAllowed = False |
| 164 | #raise Forbidden("Attachment mimetype is not allowed") |
| 165 | public_filename = secure_filename('{0}.notsafe'.format( |
| 166 | attachment_file)) |
139 | 167 | else: |
140 | | public_filename = secure_filename( |
141 | | request.files['attachment_file'].filename) |
| 168 | public_filename = secure_filename(attachment_file) |
| 169 | |
| 170 | if not bMimeAllowed or request.form['max_size_reached'] == "true": |
| 171 | return render_to_response( |
| 172 | request, |
| 173 | 'mediagoblin/edit/attachments.html', |
| 174 | {'media': media, |
| 175 | 'form': form}) |
142 | 176 | |
143 | 177 | attachment_public_filepath \ |
144 | 178 | = mg_globals.public_store.get_unique_filepath( |
diff --git a/mediagoblin/static/js/file_size.js b/mediagoblin/static/js/file_size.js
index 2238ef8..9ba366b 100644
a
|
b
|
|
16 | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | 17 | */ |
18 | 18 | |
19 | | $(document).ready(function(){ |
20 | | var file = document.getElementById('file'); |
21 | | var uploaded = parseInt(document.getElementById('uploaded').value); |
22 | | var upload_limit = parseInt(document.getElementById('upload_limit').value); |
23 | | var max_file_size = parseInt(document.getElementById('max_file_size').value); |
| 19 | var file; |
| 20 | var uploaded; |
| 21 | var upload_limit; |
| 22 | var max_file_size; |
| 23 | |
| 24 | // FIXME: Very hackish, please fix if you can. This is because the extra HTML won't appear. |
| 25 | var popup; |
24 | 26 | |
25 | | file.onchange = function() { |
26 | | var file_size = file.files[0].size / (1024.0 * 1024); |
| 27 | function check_file_size() { |
| 28 | var file_size = file.files[0].size / (1024.0 * 1024); |
27 | 29 | |
28 | | if (file_size >= max_file_size) { |
29 | | $('#file').after('<p id="file_size_error" class="form_field_error">Sorry, the file size is too big.</p>'); |
| 30 | if (file_size >= max_file_size) { |
| 31 | if (popup) |
| 32 | { |
| 33 | alert("Sorry, the file size is too big.") |
| 34 | document.getElementById('max_size_reached').value = true; |
30 | 35 | } |
31 | | else if (document.getElementById('file_size_error')) { |
32 | | $('#file_size_error').hide(); |
| 36 | else if (document.getElementById('upload_limit_error')) { |
| 37 | $('#file').after('<p id="file_size_error" class="form_field_error">Sorry, the file size is too big.</p>'); |
33 | 38 | } |
| 39 | } |
| 40 | else if (document.getElementById('file_size_error')) { |
| 41 | $('#file_size_error').hide(); |
| 42 | } |
34 | 43 | |
35 | | if (upload_limit) { |
36 | | if ( uploaded + file_size >= upload_limit) { |
37 | | $('#file').after('<p id="upload_limit_error" class="form_field_error">Sorry, uploading this file will put you over your upload limit.</p>'); |
| 44 | if (upload_limit) { |
| 45 | if ( uploaded + file_size >= upload_limit) { |
| 46 | if (popup) |
| 47 | { |
| 48 | alert("Sorry, uploading this file will put you over your upload limit."); |
| 49 | document.getElementById('max_size_reached').value = true; |
38 | 50 | } |
39 | 51 | else if (document.getElementById('upload_limit_error')) { |
40 | | $('#upload_limit_error').hide(); |
41 | | console.log(file_size >= max_file_size); |
| 52 | $('#file').after('<p id="upload_limit_error" class="form_field_error">Sorry, uploading this file will put you over your upload limit.</p>'); |
42 | 53 | } |
43 | 54 | } |
44 | | }; |
| 55 | else if (document.getElementById('upload_limit_error')) { |
| 56 | $('#upload_limit_error').hide(); |
| 57 | console.log(file_size >= max_file_size); |
| 58 | } |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | $(document).ready(function(){ |
| 63 | popup = false; |
| 64 | file = document.getElementById('file'); |
| 65 | if (file == null) |
| 66 | { |
| 67 | popup = true; |
| 68 | file = document.getElementById('attachment_file'); |
| 69 | } |
| 70 | uploaded = parseInt(document.getElementById('uploaded').value); |
| 71 | upload_limit = parseInt(document.getElementById('upload_limit').value); |
| 72 | max_file_size = parseInt(document.getElementById('max_file_size').value); |
| 73 | |
| 74 | file.onchange = check_file_size |
| 75 | |
45 | 76 | }); |
diff --git a/mediagoblin/templates/mediagoblin/edit/attachments.html b/mediagoblin/templates/mediagoblin/edit/attachments.html
index d1e33c4..b6d213c 100644
a
|
b
|
|
19 | 19 | |
20 | 20 | {% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} |
21 | 21 | |
| 22 | {% block mediagoblin_head %} |
| 23 | <script type="text/javascript" |
| 24 | src="{{ request.staticdirect('/js/file_size.js') }}"></script> |
| 25 | {% endblock %} |
| 26 | |
22 | 27 | {% block title -%} |
23 | 28 | {% trans media_title=media.title -%} |
24 | 29 | Editing attachments for {{ media_title }} |
… |
… |
|
60 | 65 | <a class="button_action" href="{{ media.url_for_self(request.urlgen) }}"> |
61 | 66 | {%- trans %}Cancel{% endtrans -%} |
62 | 67 | </a> |
| 68 | <input type="hidden" id="uploaded" value="{{ media.get_uploader.uploaded }}" /> |
| 69 | <input type="hidden" id="upload_limit" value="{{ media.get_uploader.upload_limit }}" /> |
| 70 | <input type="hidden" id="max_file_size" value="{{ global_config['mediagoblin']['max_file_size'] }}" /> |
| 71 | <input type="hidden" name="max_size_reached" id="max_size_reached" value="false" /> |
63 | 72 | <input type="submit" value="{% trans %}Save changes{% endtrans %}" |
64 | 73 | class="button_form" /> |
65 | 74 | {{ csrf_token }} |