-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Check for for StreamingHttpResponse when generating stats in Alert (#…
…1946) * Check for for StreamingHttpResponse when generating stats in Alert panel.
- Loading branch information
Showing
3 changed files
with
269 additions
and
249 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,148 +1,152 @@ | ||
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): | ||
alerts = self.get_stats()["alerts"] | ||
if 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): | ||
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}) | ||
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): | ||
alerts = self.get_stats()["alerts"] | ||
if 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}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.