Skip to content

Commit ae5e7db

Browse files
blagKozyrevIvanmarkswebCopilot
authored
chore: Add allowed_ prefix to some model and form field arguments (#57)
Co-authored-by: ivan <[email protected]> Co-authored-by: Mark Walker <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent e88cd26 commit ae5e7db

File tree

5 files changed

+415
-50
lines changed

5 files changed

+415
-50
lines changed

src/django_nh3/forms.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from django import forms
88
from django.utils.safestring import mark_safe
99

10+
from .utils import get_nh3_options
11+
1012

1113
class Nh3Field(forms.CharField):
1214
"""nh3 form field"""
@@ -15,27 +17,35 @@ class Nh3Field(forms.CharField):
1517

1618
def __init__(
1719
self,
18-
attributes: dict[str, set[str]] = {},
19-
attribute_filter: Callable[[str, str, str], str] | None = None,
20-
clean_content_tags: set[str] = set(),
20+
*args: Any,
21+
allowed_attributes: dict[str, set[str]] | None = None,
22+
allowed_attribute_filter: Callable[[str, str, str], str] | None = None,
23+
allowed_tags: set[str] | None = None,
24+
clean_content_tags: set[str] | None = None,
2125
empty_value: Any | None = "",
26+
generic_attribute_prefixes: set[str] | None = None,
2227
link_rel: str = "",
28+
set_tag_attribute_values: dict[str, dict[str, str]] | None = None,
2329
strip_comments: bool = False,
24-
tags: set[str] = set(),
25-
*args: Any,
30+
tag_attribute_values: dict[str, dict[str, set[str]]] | None = None,
31+
url_schemes: set[str] | None = None,
2632
**kwargs: dict[Any, Any],
2733
):
2834
super().__init__(*args, **kwargs)
2935

3036
self.empty_value = empty_value
31-
self.nh3_options = {
32-
"attributes": attributes,
33-
"attribute_filter": attribute_filter,
34-
"clean_content_tags": clean_content_tags,
35-
"link_rel": link_rel,
36-
"strip_comments": strip_comments,
37-
"tags": tags,
38-
}
37+
self.nh3_options = get_nh3_options(
38+
attributes=allowed_attributes,
39+
attribute_filter=allowed_attribute_filter,
40+
clean_content_tags=clean_content_tags,
41+
generic_attribute_prefixes=generic_attribute_prefixes,
42+
link_rel=link_rel,
43+
set_tag_attribute_values=set_tag_attribute_values,
44+
strip_comments=strip_comments,
45+
tags=allowed_tags,
46+
tag_attribute_values=tag_attribute_values,
47+
url_schemes=url_schemes,
48+
)
3949

4050
def to_python(self, value: Any) -> Any:
4151
"""

src/django_nh3/models.py

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,39 @@
1313
from typing_extensions import deprecated
1414

1515
from . import forms
16+
from .utils import get_nh3_options
1617

1718

1819
class Nh3FieldMixin:
1920
def __init__(
2021
self,
21-
attributes: dict[str, set[str]] = {},
22-
attribute_filter: Callable[[str, str, str], str] | None = None,
23-
clean_content_tags: set[str] = set(),
22+
*args: Any,
23+
allowed_attributes: dict[str, set[str]] | None = None,
24+
allowed_attribute_filter: Callable[[str, str, str], str] | None = None,
25+
allowed_tags: set[str] | None = None,
26+
clean_content_tags: set[str] | None = None,
27+
generic_attribute_prefixes: set[str] | None = None,
2428
link_rel: str = "",
29+
set_tag_attribute_values: dict[str, dict[str, str]] | None = None,
2530
strip_comments: bool = False,
26-
tags: set[str] = set(),
27-
*args: Any,
31+
tag_attribute_values: dict[str, dict[str, set[str]]] | None = None,
32+
url_schemes: set[str] | None = None,
2833
**kwargs: Any,
2934
) -> None:
3035
super().__init__(*args, **kwargs)
3136

32-
self.nh3_options = {
33-
"attributes": attributes,
34-
"attribute_filter": attribute_filter,
35-
"clean_content_tags": clean_content_tags,
36-
"link_rel": link_rel,
37-
"strip_comments": strip_comments,
38-
"tags": tags,
39-
}
37+
self.nh3_options = get_nh3_options(
38+
attributes=allowed_attributes,
39+
attribute_filter=allowed_attribute_filter,
40+
clean_content_tags=clean_content_tags,
41+
generic_attribute_prefixes=generic_attribute_prefixes,
42+
link_rel=link_rel,
43+
set_tag_attribute_values=set_tag_attribute_values,
44+
strip_comments=strip_comments,
45+
tags=allowed_tags,
46+
tag_attribute_values=tag_attribute_values,
47+
url_schemes=url_schemes,
48+
)
4049

4150
def formfield(
4251
self, form_class: FormField = forms.Nh3Field, **kwargs: Any
@@ -47,14 +56,26 @@ def formfield(
4756
if not self.choices: # type: ignore[attr-defined]
4857
kwargs.update(
4958
{
50-
"max_length": self.max_length, # type: ignore[attr-defined]
51-
"attributes": self.nh3_options.get("attributes"),
52-
"attribute_filter": self.nh3_options.get("attribute_filter"),
59+
"allowed_attributes": self.nh3_options.get("attributes"),
60+
"allowed_attribute_filter": self.nh3_options.get(
61+
"attribute_filter"
62+
),
63+
"allowed_tags": self.nh3_options.get("tags"),
5364
"clean_content_tags": self.nh3_options.get("clean_content_tags"),
65+
"generic_attribute_prefixes": self.nh3_options.get(
66+
"generic_attribute_prefixes"
67+
),
5468
"link_rel": self.nh3_options.get("link_rel"),
55-
"strip_comments": self.nh3_options.get("strip_comments"),
56-
"tags": self.nh3_options.get("tags"),
69+
"max_length": self.max_length, # type: ignore[attr-defined]
5770
"required": not self.blank, # type: ignore[attr-defined]
71+
"set_tag_attribute_values": self.nh3_options.get(
72+
"set_tag_attribute_values"
73+
),
74+
"strip_comments": self.nh3_options.get("strip_comments"),
75+
"tag_attribute_values": self.nh3_options.get(
76+
"tag_attribute_values"
77+
),
78+
"url_schemes": self.nh3_options.get("url_schemes"),
5879
}
5980
)
6081

src/django_nh3/templatetags/nh3_tags.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
@register.filter(name="nh3")
11-
def nh3_value(value: str | None, tags: str | None = None) -> SafeText | None:
11+
def nh3_value(value: str | None, allowed_tags: str | None = None) -> SafeText | None:
1212
"""
1313
Takes an input HTML value and sanitizes it utilizing nh3,
1414
returning a SafeText object that can be rendered by Django.
@@ -20,9 +20,9 @@ def nh3_value(value: str | None, tags: str | None = None) -> SafeText | None:
2020
return None
2121

2222
nh3_args = get_nh3_default_options()
23-
if tags is not None:
23+
if allowed_tags is not None:
2424
args = nh3_args.copy()
25-
args["tags"] = set(tags.split(","))
25+
args["tags"] = set(allowed_tags.split(","))
2626
else:
2727
args = nh3_args
2828

src/django_nh3/utils.py

Lines changed: 159 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import logging
2+
from collections.abc import Callable
23
from typing import Any
34

45
from django.conf import settings
6+
from django.utils.module_loading import import_string
57

68
logger = logging.getLogger(__name__)
79

810

9-
def get_nh3_default_options() -> dict[str, Any]:
11+
def get_nh3_configured_default_options() -> dict[str, Any]:
1012
"""
1113
Pull the django-nh3 settings similarly to how django-bleach handled them.
1214
@@ -16,35 +18,177 @@ def get_nh3_default_options() -> dict[str, Any]:
1618
BLEACH_ALLOWED_TAGS -> NH3_ALLOWED_TAGS
1719
BLEACH_ALLOWED_ATTRIBUTES -> NH3_ALLOWED_ATTRIBUTES
1820
BLEACH_STRIP_COMMENTS -> NH3_STRIP_COMMENTS
21+
BLEACH_ALLOWED_PROTOCOLS -> NH3_ALLOWED_URL_SCHEMES
1922
2023
While other settings have no current support in nh3:
2124
2225
BLEACH_ALLOWED_STYLES -> There is no support for styling
23-
BLEACH_ALLOWED_PROTOCOLS -> There is no support for protocols
2426
BLEACH_STRIP_TAGS -> This is the default behavior of nh3
2527
2628
"""
27-
nh3_args: dict[str, Any] = {}
2829

2930
nh3_settings = {
31+
# Sets the tags that are allowed (eg: allowlist)
32+
# Ensure that no tags in this are also in NH3_CLEAN_CONTENT_TAGS or
33+
# NH3_ALLOWED_ATTRIBUTES
3034
"NH3_ALLOWED_TAGS": "tags",
35+
# Sets the tags whose contents will be completely removed from the
36+
# output (eg: blocklist)
37+
# Ensure that no tags in this are also in NH3_ALLOWED_TAGS or
38+
# NH3_ALLOWED_ATTRIBUTES
39+
# Default: script, style
3140
"NH3_ALLOWED_ATTRIBUTES": "attributes",
41+
# Sets the HTML attributes that are allowed on specific tags, * key
42+
# means the attributes are allowed on any tag (eg: allowlist)
43+
# Ensure that no tags in this are also in NH3_CLEAN_CONTENT_TAGS
44+
"NH3_CLEAN_CONTENT_TAGS": "clean_content_tags",
45+
# Dotted path to a callback that allows rewriting of all attributes.
46+
# The callback takes name of the element, attribute and its value.
47+
# Returns None to remove the attribute, or a value to use
48+
"NH3_ALLOWED_ATTRIBUTES_FILTER": "attribute_filter",
49+
# Configures the handling of HTML comments, defaults to True
3250
"NH3_STRIP_COMMENTS": "strip_comments",
51+
# Configures a rel attribute that will be added on links, defaults to
52+
# noopener noreferrer. To turn on rel-insertion, pass a space-separated
53+
# list. If rel is in the generic or tag attributes, this must be set to
54+
# None
55+
# Common rel values to include:
56+
# noopener
57+
# noreferrer
58+
# nofollow
59+
"NH3_LINK_REL": "link_rel",
60+
# Sets the prefix of attributes that are allowed on any tag
61+
"NH3_ALLOWED_GENERIC_ATTRIBUTE_PREFIXES": "generic_attribute_prefixes",
62+
# Sets the values of HTML attributes that are allowed on specific tags.
63+
# The value is structured as a map from tag names to a map from
64+
# attribute names to a set of attribute values. If a tag is not itself
65+
# whitelisted, adding entries to this map will do nothing.
66+
"NH3_ALLOWED_TAG_ATTRIBUTE_VALUES": "tag_attribute_values",
67+
# Sets the values of HTML attributes that are to be set on specific
68+
# tags. The value is structured as a map from tag names to a map from
69+
# attribute names to an attribute value. If a tag is not itself
70+
# whitelisted, adding entries to this map will do nothing.
71+
"NH3_SET_TAG_ATTRIBUTE_VALUES": "set_tag_attribute_values",
72+
# Sets the URL schemes permitted on href and src attributes
73+
"NH3_ALLOWED_URL_SCHEMES": "url_schemes",
3374
}
3475

35-
for setting, kwarg in nh3_settings.items():
36-
if hasattr(settings, setting):
37-
attr = getattr(settings, setting)
76+
return {
77+
kwarg: getattr(settings, setting_name)
78+
for setting_name, kwarg in nh3_settings.items()
79+
if hasattr(settings, setting_name)
80+
}
81+
82+
83+
def normalize_nh3_options( # noqa: C901, PLR0912
84+
options: dict[str, Any],
85+
) -> dict[str, Any]:
86+
nh3_args: dict[str, Any] = {}
87+
for kwarg_name, kwarg_value in options.items():
88+
value = kwarg_value
89+
90+
# Convert from general iterables to sets
91+
if kwarg_name in [
92+
"tags",
93+
"clean_content_tags",
94+
"generic_attribute_prefixes",
95+
"url_schemes",
96+
]:
97+
value = set(value)
98+
99+
elif kwarg_name == "attributes":
100+
copy_dict = value.copy()
101+
for tag, attributes in value.items():
102+
copy_dict[tag] = set(attributes)
103+
value = copy_dict
104+
105+
elif kwarg_name == "attribute_filter":
106+
if callable(value):
107+
pass
108+
elif isinstance(value, str):
109+
value = import_string(value)
110+
111+
elif kwarg_name == "strip_comments":
112+
value = bool(value)
38113

39-
# Convert from general iterables to sets
40-
if setting == "NH3_ALLOWED_TAGS":
41-
attr = set(attr)
42-
elif setting == "NH3_ALLOWED_ATTRIBUTES":
43-
copy_dict = attr.copy()
44-
for tag, attributes in attr.items():
45-
copy_dict[tag] = set(attributes)
46-
attr = copy_dict
114+
elif kwarg_name == "link_rel":
115+
value = str(value)
47116

48-
nh3_args[kwarg] = attr
117+
elif kwarg_name == "tag_attribute_values":
118+
# The value is structured as a map from tag names to a map from
119+
# attribute names to a set of attribute values.
120+
allowed_tag_attr_dict: dict[str, dict[str, set[str]]] = {}
121+
for tag_name, attribute_dict in value.items():
122+
allowed_tag_attr_dict[tag_name] = {}
123+
for attr_name, attr_value in attribute_dict.items():
124+
allowed_tag_attr_dict[tag_name][attr_name] = set(attr_value)
125+
value = allowed_tag_attr_dict
126+
127+
elif kwarg_name == "set_tag_attribute_values":
128+
# The value is structured as a map from tag names to a map from
129+
# attribute names to an attribute value.
130+
set_tag_attr_dict: dict[str, dict[str, str]] = {}
131+
for tag_name, attribute_dict in value.items():
132+
set_tag_attr_dict[tag_name] = {}
133+
for attr_name, attr_value in attribute_dict.items():
134+
set_tag_attr_dict[tag_name][attr_name] = str(attr_value)
135+
value = set_tag_attr_dict
136+
137+
nh3_args[kwarg_name] = value
49138

50139
return nh3_args
140+
141+
142+
def get_nh3_default_options() -> dict[str, Any]:
143+
return normalize_nh3_options(get_nh3_configured_default_options())
144+
145+
146+
def get_nh3_options(
147+
tags: set[str] | None = None,
148+
clean_content_tags: set[str] | None = None,
149+
attributes: dict[str, set[str]] | None = None,
150+
attribute_filter: Callable[[str, str, str], str] | None = None,
151+
strip_comments: bool = False,
152+
link_rel: str = "",
153+
generic_attribute_prefixes: set[str] | None = None,
154+
tag_attribute_values: dict[str, dict[str, set[str]]] | None = None,
155+
set_tag_attribute_values: dict[str, dict[str, str]] | None = None,
156+
url_schemes: set[str] | None = None,
157+
) -> dict[str, Any]:
158+
defaults = get_nh3_configured_default_options()
159+
160+
tags = tags or defaults.get("tags", None) or set()
161+
attributes = attributes or defaults.get("attributes", {})
162+
clean_content_tags = (
163+
clean_content_tags or defaults.get("clean_content_tags", None) or set()
164+
)
165+
attribute_filter = attribute_filter or defaults.get("attribute_filter", None)
166+
strip_comments = strip_comments or defaults.get("strip_comments", False)
167+
link_rel = link_rel or defaults.get("link_rel", "")
168+
generic_attribute_prefixes = (
169+
generic_attribute_prefixes
170+
or defaults.get("generic_attribute_prefixes", None)
171+
or set()
172+
)
173+
tag_attribute_values = (
174+
tag_attribute_values or defaults.get("tag_attribute_values", None) or {}
175+
)
176+
set_tag_attribute_values = (
177+
set_tag_attribute_values or defaults.get("set_tag_attribute_values", None) or {}
178+
)
179+
url_schemes = url_schemes or defaults.get("url_schemes", None) or set()
180+
181+
return normalize_nh3_options(
182+
{
183+
"tags": tags,
184+
"clean_content_tags": clean_content_tags,
185+
"attributes": attributes,
186+
"attribute_filter": attribute_filter,
187+
"strip_comments": strip_comments,
188+
"link_rel": link_rel,
189+
"generic_attribute_prefixes": generic_attribute_prefixes,
190+
"tag_attribute_values": tag_attribute_values,
191+
"set_tag_attribute_values": set_tag_attribute_values,
192+
"url_schemes": url_schemes,
193+
}
194+
)

0 commit comments

Comments
 (0)