diff --git a/homeassistant/components/smtp/notify.py b/homeassistant/components/smtp/notify.py index 6b960409305..02a5a6408b6 100644 --- a/homeassistant/components/smtp/notify.py +++ b/homeassistant/components/smtp/notify.py @@ -8,6 +8,7 @@ from email.mime.text import MIMEText import email.utils import logging import os +from pathlib import Path import smtplib import voluptuous as vol @@ -193,10 +194,15 @@ class MailNotificationService(BaseNotificationService): if data := kwargs.get(ATTR_DATA): if ATTR_HTML in data: msg = _build_html_msg( - message, data[ATTR_HTML], images=data.get(ATTR_IMAGES, []) + self.hass, + message, + data[ATTR_HTML], + images=data.get(ATTR_IMAGES, []), ) else: - msg = _build_multipart_msg(message, images=data.get(ATTR_IMAGES, [])) + msg = _build_multipart_msg( + self.hass, message, images=data.get(ATTR_IMAGES, []) + ) else: msg = _build_text_msg(message) @@ -241,13 +247,21 @@ def _build_text_msg(message): return MIMEText(message) -def _attach_file(atch_name, content_id=""): +def _attach_file(hass, atch_name, content_id=""): """Create a message attachment. If MIMEImage is successful and content_id is passed (HTML), add images in-line. Otherwise add them as attachments. """ try: + file_path = Path(atch_name).parent + if not hass.config.is_allowed_path(str(file_path)): + _LOGGER.warning( + "'%s' is not secure to load data from, ignoring attachment '%s'!", + file_path, + atch_name, + ) + return with open(atch_name, "rb") as attachment_file: file_bytes = attachment_file.read() except FileNotFoundError: @@ -277,22 +291,22 @@ def _attach_file(atch_name, content_id=""): return attachment -def _build_multipart_msg(message, images): +def _build_multipart_msg(hass, message, images): """Build Multipart message with images as attachments.""" - _LOGGER.debug("Building multipart email with image attachment(s)") + _LOGGER.debug("Building multipart email with image attachme_build_html_msgnt(s)") msg = MIMEMultipart() body_txt = MIMEText(message) msg.attach(body_txt) for atch_name in images: - attachment = _attach_file(atch_name) + attachment = _attach_file(hass, atch_name) if attachment: msg.attach(attachment) return msg -def _build_html_msg(text, html, images): +def _build_html_msg(hass, text, html, images): """Build Multipart message with in-line images and rich HTML (UTF-8).""" _LOGGER.debug("Building HTML rich email") msg = MIMEMultipart("related") @@ -303,7 +317,7 @@ def _build_html_msg(text, html, images): for atch_name in images: name = os.path.basename(atch_name) - attachment = _attach_file(atch_name, name) + attachment = _attach_file(hass, atch_name, name) if attachment: msg.attach(attachment) return msg diff --git a/tests/components/smtp/test_notify.py b/tests/components/smtp/test_notify.py index bca5a5674df..06110a3e5dc 100644 --- a/tests/components/smtp/test_notify.py +++ b/tests/components/smtp/test_notify.py @@ -1,4 +1,5 @@ """The tests for the notify smtp platform.""" +from pathlib import Path import re from unittest.mock import patch @@ -132,15 +133,44 @@ EMAIL_DATA = [ ], ) def test_send_message( - message_data, data, content_type, hass: HomeAssistant, message + hass: HomeAssistant, message_data, data, content_type, message ) -> None: """Verify if we can send messages of all types correctly.""" sample_email = "" + message.hass = hass + hass.config.allowlist_external_dirs.add(Path("tests/testing_config").resolve()) with patch("email.utils.make_msgid", return_value=sample_email): result, _ = message.send_message(message_data, data=data) assert content_type in result +@pytest.mark.parametrize( + ("message_data", "data", "content_type"), + [ + ( + "Test msg", + {"images": ["tests/testing_config/notify/test.jpg"]}, + "Content-Type: multipart/mixed", + ), + ], +) +def test_sending_insecure_files_fails( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + message_data, + data, + content_type, + message, +) -> None: + """Verify if we cannot send messages with insecure attachments.""" + sample_email = "" + message.hass = hass + with patch("email.utils.make_msgid", return_value=sample_email): + result, _ = message.send_message(message_data, data=data) + assert content_type in result + assert "test.jpg' is not secure to load data from, ignoring attachment" + + def test_send_text_message(hass: HomeAssistant, message) -> None: """Verify if we can send simple text message.""" expected = (