mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-09-13 06:36:58 -04:00
fix: escape notification HTML device fields
Co-authored-by: jokob-sk <96159884+jokob-sk@users.noreply.github.com>
This commit is contained in:
1 parent
208fa928ea
commit
0197e7c2cf
2 files changed
+92
-15
No files matched your search
@@ -1,8 +1,10 @@
|
||||
import html
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
import socket
|
||||
from yattag import indent
|
||||
from yattag.indentation import XMLTokenError
|
||||
from json2table import convert
|
||||
|
||||
# Register NetAlertX modules
|
||||
@@ -146,20 +148,7 @@ class NotificationInstance:
|
||||
mail_html, conf.REPORT_DASHBOARD_URL + "/deviceDetails.php?mac="
|
||||
)
|
||||
|
||||
# Add preheader for inbox preview after all links have been generated.
|
||||
# Invisible padding prevents email clients from showing the start of the email body.
|
||||
preheader = " • ".join(preheaders)
|
||||
|
||||
padding = (" ‌ " * 47)
|
||||
|
||||
mail_html = mail_html.replace(
|
||||
"PREHEADER",
|
||||
preheader + padding,
|
||||
)
|
||||
|
||||
final_html = indent(
|
||||
mail_html, indentation=" ", newline="\r\n", indent_text=True
|
||||
)
|
||||
final_html = finalize_html(mail_html, preheaders)
|
||||
|
||||
send_api(self.JSON, final_text, final_html)
|
||||
|
||||
@@ -335,8 +324,9 @@ def construct_notifications(JSON, section):
|
||||
text = tableTitle + "\n---------\n"
|
||||
|
||||
# Convert a JSON into an HTML table
|
||||
html_data = escape_html_rows(jsn)
|
||||
html = convert(
|
||||
{"data": jsn},
|
||||
{"data": html_data},
|
||||
build_direction=build_direction,
|
||||
table_attributes=table_attributes,
|
||||
)
|
||||
@@ -398,6 +388,43 @@ def format_table(html, thValue, props, newThValue=""):
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Escape free-text values before embedding them into notification HTML
|
||||
def escape_html_rows(rows):
|
||||
return [
|
||||
{
|
||||
key: html.escape(value) if isinstance(value, str) else value
|
||||
for key, value in row.items()
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Finalize HTML and tolerate pretty-print failures
|
||||
def finalize_html(mail_html, preheaders):
|
||||
# Add preheader for inbox preview after all links have been generated.
|
||||
# Invisible padding prevents email clients from showing the start of the email body.
|
||||
preheader = html.escape(" • ".join(preheaders))
|
||||
padding = (" ‌ " * 47)
|
||||
|
||||
mail_html = mail_html.replace(
|
||||
"PREHEADER",
|
||||
preheader + padding,
|
||||
)
|
||||
|
||||
try:
|
||||
return indent(
|
||||
mail_html, indentation=" ", newline="\r\n", indent_text=True
|
||||
)
|
||||
except XMLTokenError as err:
|
||||
mylog(
|
||||
"warn",
|
||||
f"[Notification] Failed to pretty-print HTML report, sending unindented HTML instead: {err}",
|
||||
)
|
||||
return mail_html
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Pre-header Preview
|
||||
def build_preheader(tableTitle, jsn, headers):
|
||||
|
||||
@@ -295,6 +295,56 @@ class TestConstructNotificationsTemplates(unittest.TestCase):
|
||||
|
||||
self.assertEqual(html_without, html_with)
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# HTML output escapes free-text device values while text stays raw
|
||||
# -----------------------------------------------------------------
|
||||
@patch("models.notification_instance.get_setting_value")
|
||||
def test_html_escapes_free_text_values(self, mock_setting):
|
||||
from models.notification_instance import construct_notifications
|
||||
|
||||
mock_setting.side_effect = self._setting_factory({
|
||||
"NTFPRCS_TEXT_SECTION_HEADERS": True,
|
||||
"NTFPRCS_TEXT_TEMPLATE_new_devices": "",
|
||||
})
|
||||
|
||||
devices = [
|
||||
{
|
||||
"devName": "Meta Quest <Pro",
|
||||
"eveMac": "aa:bb:cc:dd:ee:ff",
|
||||
"devVendor": "Meta",
|
||||
"eveIp": "192.168.1.42",
|
||||
"eveDateTime": "2025-01-15 10:30:00",
|
||||
"eveEventType": "New Device",
|
||||
"devComments": "values <=2 break things",
|
||||
}
|
||||
]
|
||||
json_data = _make_json(
|
||||
"new_devices", devices, NEW_DEVICE_COLUMNS, "🆕 New devices"
|
||||
)
|
||||
|
||||
html, text, _ = construct_notifications(json_data, "new_devices")
|
||||
|
||||
self.assertIn("Meta Quest <Pro", html)
|
||||
self.assertIn("values <=2 break things", html)
|
||||
self.assertNotIn("Meta Quest <Pro", html)
|
||||
self.assertNotIn("values <=2 break things</td>", html)
|
||||
self.assertIn("Meta Quest <Pro", text)
|
||||
self.assertIn("values <=2 break things", text)
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Final HTML escapes preheaders and tolerates indent failures
|
||||
# -----------------------------------------------------------------
|
||||
def test_finalize_html_escapes_preheader_and_falls_back(self):
|
||||
from models.notification_instance import finalize_html
|
||||
|
||||
final_html = finalize_html(
|
||||
"<html><body><span>PREHEADER</span>broken < content</body></html>",
|
||||
["Meta Quest <Pro"],
|
||||
)
|
||||
|
||||
self.assertIn("Meta Quest <Pro", final_html)
|
||||
self.assertIn("broken < content", final_html)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user