Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check for for StreamingHttpResponse when generating stats in Alert #1946

Merged
merged 5 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
300 changes: 152 additions & 148 deletions debug_toolbar/panels/alerts.py
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})
5 changes: 5 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ Change log
Pending
-------

4.4.3 (2024-07-05)
------------------

* Added check for StreamingHttpResponse in Alert Panel

4.4.3 (2024-07-04)
------------------

Expand Down
Loading