-
-
Notifications
You must be signed in to change notification settings - Fork 1k
/
alerts.py
151 lines (127 loc) · 5 KB
/
alerts.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
from html.parser import HTMLParser
from django.utils.translation import gettext_lazy as _
from debug_toolbar.panels import Panel
class FormParser(HTMLParser):
"""
HTML form parser, used to check for invalid configurations of forms that
take file inputs.
"""
def __init__(self):
super().__init__()
self.in_form = False
self.current_form = {}
self.forms = []
self.form_ids = []
self.referenced_file_inputs = []
def handle_starttag(self, tag, attrs):
attrs = dict(attrs)
if tag == "form":
self.in_form = True
form_id = attrs.get("id")
if form_id:
self.form_ids.append(form_id)
self.current_form = {
"file_form": False,
"form_attrs": attrs,
"submit_element_attrs": [],
}
elif (
self.in_form
and tag == "input"
and attrs.get("type") == "file"
and (not attrs.get("form") or attrs.get("form") == "")
):
self.current_form["file_form"] = True
elif (
self.in_form
and (
(tag == "input" and attrs.get("type") in {"submit", "image"})
or tag == "button"
)
and (not attrs.get("form") or attrs.get("form") == "")
):
self.current_form["submit_element_attrs"].append(attrs)
elif tag == "input" and attrs.get("form"):
self.referenced_file_inputs.append(attrs)
def handle_endtag(self, tag):
if tag == "form" and self.in_form:
self.forms.append(self.current_form)
self.in_form = False
class AlertsPanel(Panel):
"""
A panel to alert users to issues.
"""
messages = {
"form_id_missing_enctype": _(
'Form with id "{form_id}" contains file input, but does not have the attribute enctype="multipart/form-data".'
),
"form_missing_enctype": _(
'Form contains file input, but does not have the attribute enctype="multipart/form-data".'
),
"input_refs_form_missing_enctype": _(
'Input element references form with id "{form_id}", but the form does not have the attribute enctype="multipart/form-data".'
),
}
title = _("Alerts")
template = "debug_toolbar/panels/alerts.html"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.alerts = []
@property
def nav_subtitle(self):
if alerts := self.get_stats().get("alerts"):
alert_text = "alert" if len(alerts) == 1 else "alerts"
return f"{len(alerts)} {alert_text}"
else:
return ""
def add_alert(self, alert):
self.alerts.append(alert)
def check_invalid_file_form_configuration(self, html_content):
"""
Inspects HTML content for a form that includes a file input but does
not have the encoding type set to multipart/form-data, and warns the
user if so.
"""
parser = FormParser()
parser.feed(html_content)
# Check for file inputs directly inside a form that do not reference
# any form through the form attribute
for form in parser.forms:
if (
form["file_form"]
and form["form_attrs"].get("enctype") != "multipart/form-data"
and not any(
elem.get("formenctype") == "multipart/form-data"
for elem in form["submit_element_attrs"]
)
):
if form_id := form["form_attrs"].get("id"):
alert = self.messages["form_id_missing_enctype"].format(
form_id=form_id
)
else:
alert = self.messages["form_missing_enctype"]
self.add_alert({"alert": alert})
# Check for file inputs that reference a form
form_attrs_by_id = {
form["form_attrs"].get("id"): form["form_attrs"] for form in parser.forms
}
for attrs in parser.referenced_file_inputs:
form_id = attrs.get("form")
if form_id and attrs.get("type") == "file":
form_attrs = form_attrs_by_id.get(form_id)
if form_attrs and form_attrs.get("enctype") != "multipart/form-data":
alert = self.messages["input_refs_form_missing_enctype"].format(
form_id=form_id
)
self.add_alert({"alert": alert})
return self.alerts
def generate_stats(self, request, response):
# check if streaming response
if getattr(response, "streaming", True):
return
html_content = response.content.decode(response.charset)
self.check_invalid_file_form_configuration(html_content)
# Further alert checks can go here
# Write all alerts to record_stats
self.record_stats({"alerts": self.alerts})