# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE import base64 import os import frappe from frappe import safe_decode from frappe.email.doctype.email_queue.email_queue import QueueBuilder, SendMailContext from frappe.email.email_body import ( get_email, get_header, inline_style_in_html, replace_filename_with_cid, ) from frappe.email.receive import Email from frappe.tests.utils import FrappeTestCase class TestEmailBody(FrappeTestCase): def setUp(self): email_html = """

Hey John Doe!

This is embedded image you asked for

""" email_text = """ Hey John Doe! This is the text version of this email """ img_path = os.path.abspath("assets/frappe/images/frappe-favicon.svg") with open(img_path, "rb") as f: img_content = f.read() img_base64 = base64.b64encode(img_content).decode() # email body keeps 76 characters on one line self.img_base64 = fixed_column_width(img_base64, 76) self.email_string = ( get_email( recipients=["test@example.com"], sender="me@example.com", subject="Test Subject", content=email_html, text_content=email_text, ) .as_string() .replace("\r\n", "\n") ) def test_prepare_message_returns_already_encoded_string(self): uni_chr1 = chr(40960) uni_chr2 = chr(1972) QueueBuilder( recipients=["test@example.com"], sender="me@example.com", subject="Test Subject", message=f"

{uni_chr1}abcd{uni_chr2}

", text_content="whatever", ).process() queue_doc = frappe.get_last_doc("Email Queue") mail_ctx = SendMailContext(queue_doc=queue_doc) result = mail_ctx.build_message(recipient_email="test@test.com") self.assertTrue(b"

=EA=80=80abcd=DE=B4

" in result) def test_prepare_message_returns_cr_lf(self): QueueBuilder( recipients=["test@example.com"], sender="me@example.com", subject="Test Subject", message="

\n this is a test of newlines\n" + "

", text_content="whatever", ).process() queue_doc = frappe.get_last_doc("Email Queue") mail_ctx = SendMailContext(queue_doc=queue_doc) result = safe_decode(mail_ctx.build_message(recipient_email="test@test.com")) self.assertTrue(result.count("\n") == result.count("\r")) def test_image(self): img_signature = """ Content-Type: image/svg+xml MIME-Version: 1.0 Content-Transfer-Encoding: base64 Content-Disposition: inline; filename="frappe-favicon.svg" """ self.assertTrue(img_signature in self.email_string) self.assertTrue(self.img_base64 in self.email_string) def test_text_content(self): text_content = """ Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Hey John Doe! This is the text version of this email """ self.assertTrue(text_content in self.email_string) def test_email_content(self): html_head = """ Content-Type: text/html; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable """ html = """

Hey John Doe!

""" self.assertTrue(html_head in self.email_string) self.assertTrue(html in self.email_string) def test_replace_filename_with_cid(self): original_message = """
test
""" message, inline_images = replace_filename_with_cid(original_message) processed_message = """
test
""".format(inline_images[0].get("content_id")) self.assertEqual(message, processed_message) def test_inline_styling(self): html = """

Hi John

This is a test email

""" transformed_html = """

Hi John

This is a test email

""" self.assertTrue(transformed_html in inline_style_in_html(html)) def test_email_header(self): email_html = """

Hey John Doe!

This is embedded image you asked for

""" email_string = get_email( recipients=["test@example.com"], sender="me@example.com", subject="Test Subject\u2028, with line break, \nand Line feed \rand carriage return.", content=email_html, header=["Email Title", "green"], ).as_string() # REDESIGN-TODO: Add style for indicators in email self.assertTrue("""""" in email_string) self.assertTrue("Email Title" in email_string) self.assertIn( "Subject: Test Subject, with line break, and Line feed and carriage return.", email_string ) def test_get_email_header(self): html = get_header(["This is test", "orange"]) self.assertTrue('' in html) self.assertTrue("This is test" in html) html = get_header(["This is another test"]) self.assertTrue("This is another test" in html) html = get_header("This is string") self.assertTrue("This is string" in html) def test_8bit_utf_8_decoding(self): text_content_bytes = b"\xed\x95\x9c\xea\xb8\x80\xe1\xa5\xa1\xe2\x95\xa5\xe0\xba\xaa\xe0\xa4\x8f" text_content = text_content_bytes.decode("utf-8") content_bytes = ( b"""MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit From: test1_@erpnext.com Reply-To: test2_@erpnext.com """ + text_content_bytes ) mail = Email(content_bytes) self.assertEqual(mail.text_content, text_content) def test_poorly_encoded_messages(self): mail = Email.decode_email( "=?iso-2022-jp?B?VEFLQVlBTUEgS2FvcnUgWxskQnxiOzMbKEIgGyRCNzAbKEJd?=\n\t" ) self.assertIn("user@example.com", mail) def test_poorly_encoded_messages2(self): mail = Email.decode_email(" =?UTF-8?B?X\xe0\xe0Y?= ") self.assertIn("xy@example.com", mail) def fixed_column_width(string, chunk_size): parts = [string[0 + i : chunk_size + i] for i in range(0, len(string), chunk_size)] return "\n".join(parts)