diff --git a/email_handler.py b/email_handler.py index da273f9d..d5e03b31 100644 --- a/email_handler.py +++ b/email_handler.py @@ -590,15 +590,25 @@ def handle_forward(envelope, msg: Message, rcpt_to: str) -> List[Tuple[bool, str contact.alias ) # In case the Session was closed in the get_or_create we re-fetch the alias - reply_to_contact = None + reply_to_contact = [] if msg[headers.REPLY_TO]: - reply_to = get_header_unicode(msg[headers.REPLY_TO]) - LOG.d("Create or get contact for reply_to_header:%s", reply_to) - # ignore when reply-to = alias - if reply_to == alias.email: - LOG.i("Reply-to same as alias %s", alias) - else: - reply_to_contact = get_or_create_reply_to_contact(reply_to, alias, msg) + reply_to_header_contents = get_header_unicode(msg[headers.REPLY_TO]) + if reply_to_header_contents: + LOG.d( + "Create or get contact for reply_to_header:%s", reply_to_header_contents + ) + for reply_to in [ + reply_to.strip() + for reply_to in reply_to_header_contents.split(",") + if reply_to.strip() + ]: + reply_to_name, reply_to_email = parse_full_address(reply_to) + if reply_to_email == alias.email: + LOG.i("Reply-to same as alias %s", alias) + else: + reply_to_contact.append( + get_or_create_reply_to_contact(reply_to_email, alias, msg) + ) if alias.user.delete_on is not None: LOG.d(f"user {user} is pending to be deleted. Do not forward") @@ -701,7 +711,7 @@ def forward_email_to_mailbox( envelope, mailbox, user, - reply_to_contact: Optional[Contact], + reply_to_contacts: list[Contact], ) -> (bool, str): LOG.d("Forward %s -> %s -> %s", contact, alias, mailbox) @@ -884,11 +894,13 @@ def forward_email_to_mailbox( add_or_replace_header(msg, "From", new_from_header) LOG.d("From header, new:%s, old:%s", new_from_header, old_from_header) - if reply_to_contact: - reply_to_header = msg[headers.REPLY_TO] - new_reply_to_header = reply_to_contact.new_addr() + if len(reply_to_contacts) > 0: + original_reply_to = get_header_unicode(msg[headers.REPLY_TO]) + new_reply_to_header = ", ".join( + [reply_to_contact.new_addr() for reply_to_contact in reply_to_contacts][:5] + ) add_or_replace_header(msg, "Reply-To", new_reply_to_header) - LOG.d("Reply-To header, new:%s, old:%s", new_reply_to_header, reply_to_header) + LOG.d("Reply-To header, new:%s, old:%s", new_reply_to_header, original_reply_to) # replace CC & To emails by reverse-alias for all emails that are not alias try: diff --git a/tests/example_emls/multi_reply_to.eml b/tests/example_emls/multi_reply_to.eml new file mode 100644 index 00000000..40677a50 --- /dev/null +++ b/tests/example_emls/multi_reply_to.eml @@ -0,0 +1,21 @@ +X-SimpleLogin-Client-IP: 54.39.200.130 +Received-SPF: Softfail (mailfrom) identity=mailfrom; client-ip=34.59.200.130; + helo=relay.somewhere.net; envelope-from=everwaste@gmail.com; + receiver= +Received: from relay.somewhere.net (relay.somewhere.net [34.59.200.130]) + (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) + (No client certificate requested) + by mx1.sldev.ovh (Postfix) with ESMTPS id 6D8C13F069 + for ; Thu, 17 Mar 2022 16:50:20 +0000 (UTC) +Date: Thu, 17 Mar 2022 16:50:18 +0000 +To: {{ alias_email }} +From: somewhere@rainbow.com +Reply-To: 666-Mail Test , 777-Mail Test , + 888-Mail Test , - 5 at mailcstest.com" +Subject: test Thu, 17 Mar 2022 16:50:18 +0000 +Message-Id: <20220317165018.000191@somewhere-5488dd4b6b-7crp6> +X-Mailer: swaks v20201014.0 jetmore.org/john/code/swaks/ +X-Rspamd-Queue-Id: 6D8C13F069 +X-Rspamd-Server: staging1 + +This is a test mailing diff --git a/tests/handler/test_reply_to.py b/tests/handler/test_reply_to.py new file mode 100644 index 00000000..3dc3b01c --- /dev/null +++ b/tests/handler/test_reply_to.py @@ -0,0 +1,33 @@ +from aiosmtpd.smtp import Envelope + +import email_handler +from app.email import status, headers +from app.email_utils import get_header_unicode, parse_full_address + +from app.mail_sender import mail_sender +from app.models import Alias, Contact +from tests.utils import create_new_user, load_eml_file + + +@mail_sender.store_emails_test_decorator +def test_multi_reply_to(): + user = create_new_user() + alias = Alias.create_new_random(user) + envelope = Envelope() + envelope.mail_from = "env.somewhere" + envelope.rcpt_tos = [alias.email] + msg = load_eml_file("multi_reply_to.eml", {"alias_email": alias.email}) + alias_id = alias.id + result = email_handler.MailHandler()._handle(envelope, msg) + assert result == status.E200 + sent_emails = mail_sender.get_stored_emails() + assert 1 == len(sent_emails) + msg = sent_emails[0].msg + reply_to = get_header_unicode(msg[headers.REPLY_TO]) + entries = reply_to.split(",") + assert 4 == len(entries) + for entry in entries: + dummy, email = parse_full_address(entry) + contact = Contact.get_by(reply_email=email) + assert contact is not None + assert contact.alias_id == alias_id