diff --git a/CHANGELOG.md b/CHANGELOG.md index 25712421..897c9de9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ## Improvements - Rename VictorOps -> Splunk On-Call +- Implement email body decoding in the "Ping Details" dialog ## Bug Fixes - Fix downtime summary to handle months when the check didn't exist yet (#472) diff --git a/hc/front/tests/test_ping_details.py b/hc/front/tests/test_ping_details.py index ef14ea9f..ddf94d3a 100644 --- a/hc/front/tests/test_ping_details.py +++ b/hc/front/tests/test_ping_details.py @@ -1,6 +1,39 @@ from hc.api.models import Check, Ping from hc.test import BaseTestCase +PLAINTEXT_EMAIL = """Content-Type: multipart/alternative; boundary=bbb + +--bbb +Content-Type: text/plain;charset=utf-8 +Content-Transfer-Encoding: base64 + +aGVsbG8gd29ybGQ= + +--bbb +""" + +BAD_BASE64_EMAIL = """Content-Type: multipart/alternative; boundary=bbb + +--bbb +Content-Type: text/plain;charset=utf-8 +Content-Transfer-Encoding: base64 + +!!! + +--bbb +""" + +HTML_EMAIL = """Content-Type: multipart/alternative; boundary=bbb + +--bbb +Content-Type: text/html;charset=utf-8 +Content-Transfer-Encoding: base64 + +PGI+aGVsbG88L2I+ + +--bbb +""" + class PingDetailsTestCase(BaseTestCase): def setUp(self): @@ -73,3 +106,39 @@ class PingDetailsTestCase(BaseTestCase): self.client.login(username="alice@example.org", password="password") r = self.client.get(self.url) self.assertContains(r, "(exit status 0)", status_code=200) + + def test_it_decodes_plaintext_email_body(self): + Ping.objects.create(owner=self.check, scheme="email", body=PLAINTEXT_EMAIL) + + self.client.login(username="alice@example.org", password="password") + r = self.client.get(self.url) + + self.assertContains(r, "email-body-plain", status_code=200) + self.assertNotContains(r, "email-body-html") + + # aGVsbG8gd29ybGQ= is base64("hello world") + self.assertContains(r, "aGVsbG8gd29ybGQ=") + self.assertContains(r, "hello world") + + def test_it_handles_bad_base64_in_email_body(self): + Ping.objects.create(owner=self.check, scheme="email", body=BAD_BASE64_EMAIL) + + self.client.login(username="alice@example.org", password="password") + r = self.client.get(self.url) + + self.assertContains(r, "!!!", status_code=200) + self.assertNotContains(r, "email-body-plain") + self.assertNotContains(r, "email-body-html") + + def test_it_decodes_html_email_body(self): + Ping.objects.create(owner=self.check, scheme="email", body=HTML_EMAIL) + + self.client.login(username="alice@example.org", password="password") + r = self.client.get(self.url) + + self.assertNotContains(r, "email-body-plain", status_code=200) + self.assertContains(r, "email-body-html") + + # PGI+aGVsbG88L2I+ is base64("hello") + self.assertContains(r, "PGI+aGVsbG88L2I+") + self.assertContains(r, "<b>hello</b>") diff --git a/hc/front/views.py b/hc/front/views.py index acf6d70f..3cb12002 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta as td +import email import json import os import re @@ -507,7 +508,18 @@ def ping_details(request, code, n=None): except Ping.DoesNotExist: return render(request, "front/ping_details_not_found.html") - ctx = {"check": check, "ping": ping} + ctx = {"check": check, "ping": ping, "plain": None, "html": None} + + if ping.scheme == "email": + parsed = email.message_from_string(ping.body, policy=email.policy.SMTP) + + plain_mime_part = parsed.get_body(("plain",)) + if plain_mime_part: + ctx["plain"] = plain_mime_part.get_content() + + html_mime_part = parsed.get_body(("html",)) + if html_mime_part: + ctx["html"] = html_mime_part.get_content() return render(request, "front/ping_details.html", ctx) diff --git a/templates/front/ping_details.html b/templates/front/ping_details.html index c6da368b..ea0a8416 100644 --- a/templates/front/ping_details.html +++ b/templates/front/ping_details.html @@ -66,6 +66,42 @@ {% if ping.body %}

Request Body

+ + {% if plain or html %} + +
+
+
{{ ping.body }}
+
+ + {% if plain %} +
+
{{ plain }}
+
+ {% endif %} + + {% if html %} +
+
{{ html }}
+
+ {% endif %} +
+ {% else %}
{{ ping.body }}
+ {% endif %} {% endif %}