Skip to content
This repository has been archived by the owner on Aug 26, 2022. It is now read-only.

Fix bug 792955: Build file upload filter #638

Merged
merged 2 commits into from Oct 5, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 17 additions & 6 deletions apps/wiki/forms.py
@@ -1,5 +1,4 @@
import json
import mimetypes
import re

from django import forms
Expand All @@ -9,6 +8,9 @@
from tower import ugettext_lazy as _lazy
from tower import ugettext as _

import constance.config
import magic

from sumo.form_fields import StrippedCharField
from tags import forms as tag_forms

Expand Down Expand Up @@ -58,6 +60,7 @@

MIDAIR_COLLISION = _lazy(u'This document was modified while you were '
'editing it.')
MIME_TYPE_INVALID = _lazy(u'Files of this type are not permitted.')


class DocumentForm(forms.ModelForm):
Expand Down Expand Up @@ -420,21 +423,29 @@ class Meta:
model = AttachmentRevision
fields = ('file', 'title', 'description', 'comment')

def clean_file(self):
uploaded_file = self.cleaned_data['file']
m_mime = magic.Magic(mime=True)
mime_type = m_mime.from_buffer(uploaded_file.read(1024)).split(';')[0]
uploaded_file.seek(0)

if mime_type not in \
constance.config.WIKI_ATTACHMENT_ALLOWED_TYPES.split():
raise forms.ValidationError(MIME_TYPE_INVALID)
return self.cleaned_data['file']

def save(self, commit=True):
if commit:
raise NotImplementedError
rev = super(AttachmentRevisionForm, self).save(commit=False)

uploaded_file = self.cleaned_data['file']
m_mime = magic.Magic(mime=True)
mime_type = m_mime.from_buffer(uploaded_file.read(1024)).split(';')[0]
rev.slug = uploaded_file.name

# guess_type() is usually pretty good at coming up with the
# right thing, but if it can't give us an answer, we fall back
# to a default of application/octet-stream.
#
# TODO: we probably want a "manually fix the mime-type"
# ability in the admin.
mime_type = mimetypes.guess_type(uploaded_file.name)[0]
if mime_type is None:
mime_type = 'application/octet-stream'
rev.mime_type = mime_type
Expand Down
54 changes: 54 additions & 0 deletions apps/wiki/tests/test_views.py
Expand Up @@ -2235,6 +2235,13 @@ def test_schedule_render_on_edit(self, mock_kumascript_get, mock_document_schedu
class AttachmentTests(TestCaseBase):
fixtures = ['test_users.json']

def setUp(self):
self.old_allowed_types = constance.config.WIKI_ATTACHMENT_ALLOWED_TYPES
constance.config.WIKI_ATTACHMENT_ALLOWED_TYPES = 'text/plain'

def tearDown(self):
constance.config.WIKI_ATTACHMENT_ALLOWED_TYPES = self.old_allowed_types

def test_legacy_redirect(self):
self.client = Client() # file views don't need LocalizingClient
test_user = User.objects.get(username='testuser2')
Expand Down Expand Up @@ -2417,3 +2424,50 @@ def test_get_previous(self):
r2.make_current()

eq_(r, r2.get_previous())

def test_mime_type_filtering(self):
"""Don't allow uploads outside of the explicitly-permitted
mime-types."""
#SLIGHT HACK: this requires the default set of allowed
#mime-types specified in settings.py. Specifically, adding
#'text/html' to that set will make this test fail.
test_user = User.objects.get(username='testuser2')
a = Attachment(title='Test attachment for file type filter',
slug='test-attachment-for-file-type-filter')
a.save()
r = AttachmentRevision(
attachment=a,
mime_type='text/plain',
title=a.title,
slug=a.slug,
description='',
comment='Initial revision.',
created=datetime.datetime.now() - datetime.timedelta(seconds=30),
creator=test_user,
is_approved=True)
r.file.save('mime_type_filter_test_file.txt',
ContentFile('I am a test file for mime-type filtering'))

self.client = Client() # file views don't need LocalizingClient
self.client.login(username='admin', password='testpass')

# Shamelessly stolen from Django's own file-upload tests.
tdir = tempfile.gettempdir()
file_for_upload = tempfile.NamedTemporaryFile(suffix=".html",
dir=tdir)
file_for_upload.write('<html>I am a file that tests'
'mime-type filtering.</html>.')
file_for_upload.seek(0)

post_data = {
'title': 'Test disallowed file type',
'description': 'A file kuma should disallow on type.',
'comment': 'Initial upload',
'file': file_for_upload,
}

resp = self.client.post(reverse('wiki.edit_attachment',
kwargs={'attachment_id': a.id}),
data=post_data)
eq_(200, resp.status_code)
ok_('Files of this type are not permitted.' in resp.content)
8 changes: 6 additions & 2 deletions apps/wiki/views.py
Expand Up @@ -1787,7 +1787,10 @@ def new_attachment(request):
}
response = jingo.render(request, 'wiki/includes/attachment_upload_results.html',
{ 'result': json.dumps([error_obj]) })

else:
response = jingo.render(request, 'wiki/edit_attachment.html',
{'form': form})

response['x-frame-options'] = 'SAMEORIGIN'
return response

Expand All @@ -1809,6 +1812,7 @@ def edit_attachment(request, attachment_id):
rev.attachment = attachment
rev.save()
return HttpResponseRedirect(attachment.get_absolute_url())
form = AttachmentRevisionForm()
else:
form = AttachmentRevisionForm()
return jingo.render(request, 'wiki/edit_attachment.html',
{'form': form})
5 changes: 5 additions & 0 deletions settings.py
Expand Up @@ -1008,6 +1008,11 @@ def read_only_mode(env):
3,
'Number of lines of context to show in feed diff display.',
),

WIKI_ATTACHMENT_ALLOWED_TYPES = (
'image/gif image/jpeg image/png image/svg+xml text/html',
'Allowed file types for wiki file attachments',
),
)

BROWSERID_VERIFICATION_URL = 'https://verifier.login.persona.org/verify'
Expand Down