Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added html5lib powered html cleaning to text plugin (in form) and tes…

…ts for it
  • Loading branch information...
commit 9ca7738bc1cef827765589c5b254810370a0fc0b 1 parent c3880b6
@ojii ojii authored
View
34 cms/plugins/text/forms.py
@@ -1,11 +1,41 @@
-from django.forms.models import ModelForm
from cms.plugins.text.models import Text
from django import forms
+from django.forms.models import ModelForm
+import html5lib
+from html5lib import sanitizer
+
+def _get_inner_body(doc):
+ # find 'body'
+ def _rec(node):
+ if node.type == 5: # Element Type
+ if node.name == 'body': # the body element
+ return node
+ for child in node.childNodes:
+ childfound = _rec(child)
+ if childfound:
+ return childfound
+ return None
+ body = _rec(doc)
+ # if the first element after <body> is a html tag, this returns an Element
+ # instance, otherwise a (unicode) string, this is why we need to check
+ # the output of this and potentially call .toxml() again.
+ out = reduce(lambda x,y:x.toxml()+y.toxml(), body.childNodes)
+ if isinstance(out, basestring):
+ return out
+ return out.toxml()
class TextForm(ModelForm):
body = forms.CharField()
class Meta:
model = Text
- exclude = ('page', 'position', 'placeholder', 'language', 'plugin_type')
+ exclude = ('page', 'position', 'placeholder', 'language', 'plugin_type')
+
+ parser = html5lib.HTMLParser(tokenizer=sanitizer.HTMLSanitizer)
+
+ def clean_body(self):
+ data = self.cleaned_data['body']
+ doc = self.parser.parse(data)
+ html = _get_inner_body(doc)
+ return html
View
39 cms/tests/security.py
@@ -1,8 +1,10 @@
+from cms.models.pagemodel import Page
from cms.models.pluginmodel import CMSPlugin
from cms.plugins.text.models import Text
from cms.test.testcases import (CMSTestCase, URL_CMS_PLUGIN_ADD,
- URL_CMS_PLUGIN_EDIT, URL_CMS_PLUGIN_REMOVE)
+ URL_CMS_PLUGIN_EDIT, URL_CMS_PLUGIN_REMOVE, URL_CMS_PAGE_ADD)
from django.conf import settings
+from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
@@ -172,4 +174,37 @@ def test_delete_ph(self):
response = self.client.post(url, plugin_data)
# the user is logged in and the security check fails, so it should 404.
self.assertEqual(response.status_code, 404)
- self.assertEqual(CMSPlugin.objects.count(), 1)
+ self.assertEqual(CMSPlugin.objects.count(), 1)
+
+ def test_text_plugin_xss(self):
+ super_user = User(username="test", is_staff=True, is_active=True, is_superuser=True)
+ super_user.set_password("test")
+ super_user.save()
+
+ self.login_user(super_user)
+
+ page_data = self.get_new_page_data()
+ self.client.post(URL_CMS_PAGE_ADD, page_data)
+ page = Page.objects.all()[0]
+ plugin_data = {
+ 'plugin_type': "TextPlugin",
+ 'language': settings.LANGUAGES[0][0],
+ 'placeholder': page.placeholders.get(slot="body").pk,
+ }
+ response = self.client.post(URL_CMS_PLUGIN_ADD, plugin_data)
+ self.assertEquals(response.status_code, 200)
+ self.assertEquals(int(response.content), CMSPlugin.objects.all()[0].pk)
+ edit_url = URL_CMS_PLUGIN_EDIT + response.content + "/"
+ response = self.client.get(edit_url)
+ self.assertEquals(response.status_code, 200)
+ # ACTUAL TEST STARTS HERE.
+ data = {
+ "body": "<div onload='do_evil_stuff();'>divcontent</div><a href='javascript:do_evil_stuff()'>acontent</a>"
+ }
+ response = self.client.post(edit_url, data)
+ self.assertEquals(response.status_code, 200)
+ txt = Text.objects.all()[0]
+ self.assertEquals(txt.body, '<div>divcontent</div><a>acontent</a>')
+
+ self.client.logout()
+ self.user = None
View
1  setup.py
@@ -49,6 +49,7 @@
'Django>=1.2',
'django-classy-tags>=0.2.2',
'south>=0.7.2',
+ 'html5lib',
],
packages=find_packages(exclude=["example", "example.*","testdata","testdata.*"]),
package_data={
View
10 tests/buildout.cfg
@@ -3,6 +3,7 @@ parts =
python
django
eggs =
+ django
django-cms
coverage
unittest-xml-reporting
@@ -11,6 +12,8 @@ eggs =
django
South
sphinx
+extra-paths =
+ testapp
develop =
../
versions = versions
@@ -24,13 +27,14 @@ scripts =
[django]
-recipe = djangorecipe
-version = 1.2.5
-project = testapp
+recipe = djangoprojectrecipe
+project = project
settings = settings
eggs = ${buildout:eggs}
+extra-paths = ${buildout:extra-paths}
[versions]
coverage = 3.4
unittest-xml-reporting = 1.0.3
django-reversion = 1.3.3
+django = 1.2.5
Please sign in to comment.
Something went wrong with that request. Please try again.