Skip to content

Commit

Permalink
Add templatetags to clean HTML within django templates (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
wes-otf authored Dec 19, 2023
1 parent 273edb2 commit ee4f688
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 0 deletions.
Empty file.
32 changes: 32 additions & 0 deletions src/django_nh3/templatetags/nh3_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import nh3
from django import template
from django.utils.safestring import SafeText, mark_safe

from django_nh3.utils import get_nh3_default_options

register = template.Library()


@register.filter(name="nh3")
def nh3_value(value: str | None, tags: str | None = None) -> SafeText:
"""
Takes an input HTML value and sanitizes it utilizing nh3,
returning a SafeText object that can be rendered by Django.
Accepts an optional argument of allowed tags. Should be a comma delimited
string (ie. "img,span" or "img")
"""
if value is None:
return None

args = {}

nh3_args = get_nh3_default_options()
if tags is not None:
args = nh3_args.copy()
args["tags"] = set(tags.split(","))
else:
args = nh3_args

nh3_value = nh3.clean(value, **args)
return mark_safe(nh3_value)
50 changes: 50 additions & 0 deletions src/django_nh3/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import logging
from typing import Any

from django.conf import settings

logger = logging.getLogger(__name__)


def get_nh3_default_options() -> dict[str, Any]:
"""
Pull the django-nh3 settings similarly to how django-bleach handled them.
Some django-bleach settings can be mapped to django-nh3 settings without
any changes:
BLEACH_ALLOWED_TAGS -> NH3_ALLOWED_TAGS
BLEACH_ALLOWED_ATTRIBUTES -> NH3_ALLOWED_ATTRIBUTES
BLEACH_STRIP_COMMENTS -> NH3_STRIP_COMMENTS
While other settings are have no current support in nh3:
BLEACH_ALLOWED_STYLES -> There is no support for styling
BLEACH_ALLOWED_PROTOCOLS -> There is no suport for protocols
BLEACH_STRIP_TAGS -> This is the default behavior of nh3
"""
nh3_args: dict[str, Any] = {}

nh3_settings = {
"NH3_ALLOWED_TAGS": "tags",
"NH3_ALLOWED_ATTRIBUTES": "attributes",
"NH3_STRIP_COMMENTS": "strip_comments",
}

for setting, kwarg in nh3_settings.items():
if hasattr(settings, setting):
attr = getattr(settings, setting)

# Convert from general iterables to sets
if setting == "NH3_ALLOWED_TAGS":
attr = set(attr)
elif setting == "NH3_ALLOWED_ATTRIBUTES":
copy_dict = attr.copy()
for tag, attributes in attr.items():
copy_dict[tag] = set(attributes)
attr = copy_dict

nh3_args[kwarg] = attr

return nh3_args
5 changes: 5 additions & 0 deletions tests/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALLOWED_ATTRIBUTES = {"*": {"class", "style"}, "a": {"href", "title"}}

ALLOWED_TAGS = {"a", "li", "ul"}

STRIP_COMMENTS = True
32 changes: 32 additions & 0 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from unittest.mock import patch

from django.test import TestCase
from django_nh3.utils import get_nh3_default_options

from .constants import ALLOWED_ATTRIBUTES, ALLOWED_TAGS, STRIP_COMMENTS


class TestBleachOptions(TestCase):
@patch(
"django_nh3.utils.settings",
NH3_ALLOWED_ATTRIBUTES=ALLOWED_ATTRIBUTES,
)
def test_custom_attrs(self, settings):
nh3_args = get_nh3_default_options()
self.assertEqual(nh3_args["attributes"], ALLOWED_ATTRIBUTES)

@patch(
"django_nh3.utils.settings",
NH3_ALLOWED_TAGS=ALLOWED_TAGS,
)
def test_custom_tags(self, settings):
nh3_args = get_nh3_default_options()
self.assertEqual(nh3_args["tags"], ALLOWED_TAGS)

@patch(
"django_nh3.utils.settings",
NH3_STRIP_COMMENTS=STRIP_COMMENTS,
)
def test_strip_comments(self, settings):
nh3_args = get_nh3_default_options()
self.assertEqual(nh3_args["strip_comments"], STRIP_COMMENTS)
45 changes: 45 additions & 0 deletions tests/test_templatetags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from django.template import Context, Template
from django.test import TestCase


class TestBleachTemplates(TestCase):
"""Test template tags"""

def test_bleaching(self):
"""Test that unsafe tags are sanitised"""
context = Context(
{"some_unsafe_content": '<script>alert("Hello World!")</script>'},
)
template_to_render = Template(
"{% load nh3_tags %}" "{{ some_unsafe_content|nh3 }}"
)
rendered_template = template_to_render.render(context)
self.assertEqual("", rendered_template)

def test_bleaching_none(self):
"""Test that None is handled properly as an input"""
context = Context({"none_value": None})
template_to_render = Template(
"{% load nh3_tags %}" "{{ none_value|nh3 }}"
)
rendered_template = template_to_render.render(context)
self.assertEqual("None", rendered_template)

def test_bleaching_tags(self):
"""Test provided tags are kept"""
context = Context(
{
"some_unsafe_content": (
"<b><img src='' "
"onerror='alert(\\'hax\\')'>"
"I'm not trying to XSS you</b>"
)
}
)
template_to_render = Template(
"{% load nh3_tags %}" '{{ some_unsafe_content|nh3:"img" }}'
)
rendered_template = template_to_render.render(context)
self.assertInHTML(
'<img src="">I\'m not trying to XSS you', rendered_template
)

0 comments on commit ee4f688

Please sign in to comment.