mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	emails: Truncate overly-long From fields for RFC compatibility.
Amazon SES has a limit on the size of address fields, and rejects emails with too-long "From" combinations of name and address. This limit is set to 320 bytes and comes from an RFC limitation on the size of addresses. This RFC standard states that an email address should not be composed of a local part (before the '@') longer than 64 bytes and a domain part (after the '@') longer than 255 bytes. It is possible that Amazon SES misinterprets this limitation as it checks the length of the combination of the name and the email address of the sender. To ensure that this problem is not encountered in the send_email module of Zulip the length of this combination is now checked against this limit and the from_name field is removed to only keep the from_address field when it is necessary in order to stay below 320 bytes. If the from_address field alone is longer than 320 bytes the sending process will raise an SMTPDataError exception. Tests for this new check are added to the backend test suite in order to test if build_email correctly outputs an email with filled from_name and from_address fields when the total length is lower than 320 bytes and that it correctly throws the from_name field away when necessary. Fixes: #17558.
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							4f38da5ce7
						
					
				
				
					commit
					b7fa41601d
				
			@@ -11,6 +11,7 @@ from typing import Any, Dict, List, Mapping, Optional, Tuple
 | 
			
		||||
import orjson
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.core.mail import EmailMultiAlternatives
 | 
			
		||||
from django.core.mail.message import sanitize_address
 | 
			
		||||
from django.core.management import CommandError
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django.template import loader
 | 
			
		||||
@@ -148,8 +149,15 @@ def build_email(
 | 
			
		||||
    if from_address == FromAddress.support_placeholder:
 | 
			
		||||
        from_address = FromAddress.SUPPORT
 | 
			
		||||
 | 
			
		||||
    # Set the "From" that is displayed separately from the envelope-from
 | 
			
		||||
    # Set the "From" that is displayed separately from the envelope-from.
 | 
			
		||||
    extra_headers["From"] = str(Address(display_name=from_name, addr_spec=from_address))
 | 
			
		||||
    # Check ASCII encoding length.  Amazon SES rejects emails with
 | 
			
		||||
    # From names longer than 320 characters (which appears to be a
 | 
			
		||||
    # misinterpretation of the RFC); in that case we drop the name
 | 
			
		||||
    # from the From line, under the theory that it's better to send
 | 
			
		||||
    # the email with a simplified From field than not.
 | 
			
		||||
    if len(sanitize_address(extra_headers["From"], "utf-8")) > 320:
 | 
			
		||||
        extra_headers["From"] = str(Address(addr_spec=from_address))
 | 
			
		||||
 | 
			
		||||
    reply_to = None
 | 
			
		||||
    if reply_to_email is not None:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										59
									
								
								zerver/tests/test_send_email.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								zerver/tests/test_send_email.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
from django.core.mail.message import sanitize_address
 | 
			
		||||
 | 
			
		||||
from zerver.lib.send_email import FromAddress, build_email
 | 
			
		||||
from zerver.lib.test_classes import ZulipTestCase
 | 
			
		||||
 | 
			
		||||
OVERLY_LONG_NAME = "Z̷̧̙̯͙̠͇̰̲̞̙͆́͐̅̌͐̔͑̚u̷̼͎̹̻̻̣̞͈̙͛͑̽̉̾̀̅̌͜͠͞ļ̛̫̻̫̰̪̩̠̣̼̏̅́͌̊͞į̴̛̛̩̜̜͕̘̂̑̀̈p̡̛͈͖͓̟͍̿͒̍̽͐͆͂̀ͅ A̰͉̹̅̽̑̕͜͟͡c̷͚̙̘̦̞̫̭͗̋͋̾̑͆̒͟͞c̵̗̹̣̲͚̳̳̮͋̈́̾̉̂͝ͅo̠̣̻̭̰͐́͛̄̂̿̏͊u̴̱̜̯̭̞̠͋͛͐̍̄n̸̡̘̦͕͓̬͌̂̎͊͐̎͌̕ť̮͎̯͎̣̙̺͚̱̌̀́̔͢͝ S͇̯̯̙̳̝͆̊̀͒͛̕ę̛̘̬̺͎͎́̔̊̀͂̓̆̕͢ͅc̨͎̼̯̩̽͒̀̏̄̌̚u̷͉̗͕̼̮͎̬͓͋̃̀͂̈̂̈͊͛ř̶̡͔̺̱̹͓̺́̃̑̉͡͞ͅi̶̺̭͈̬̞̓̒̃͆̅̿̀̄́t͔̹̪͔̥̣̙̍̍̍̉̑̏͑́̌ͅŷ̧̗͈͚̥̗͚͊͑̀͢͜͡"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestBuildEmail(ZulipTestCase):
 | 
			
		||||
    def test_build_SES_compatible_From_field(self) -> None:
 | 
			
		||||
        hamlet = self.example_user("hamlet")
 | 
			
		||||
        from_name = FromAddress.security_email_from_name(language="en")
 | 
			
		||||
        mail = build_email(
 | 
			
		||||
            "zerver/emails/password_reset",
 | 
			
		||||
            to_emails=[hamlet],
 | 
			
		||||
            from_name=from_name,
 | 
			
		||||
            from_address=FromAddress.NOREPLY,
 | 
			
		||||
            language="en",
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            mail.extra_headers["From"], "{} <{}>".format(from_name, FromAddress.NOREPLY)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_build_SES_compatible_From_field_limit(self) -> None:
 | 
			
		||||
        hamlet = self.example_user("hamlet")
 | 
			
		||||
        limit_length_name = "a" * (320 - len(sanitize_address(FromAddress.NOREPLY, "utf-8")) - 3)
 | 
			
		||||
        mail = build_email(
 | 
			
		||||
            "zerver/emails/password_reset",
 | 
			
		||||
            to_emails=[hamlet],
 | 
			
		||||
            from_name=limit_length_name,
 | 
			
		||||
            from_address=FromAddress.NOREPLY,
 | 
			
		||||
            language="en",
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            mail.extra_headers["From"], "{} <{}>".format(limit_length_name, FromAddress.NOREPLY)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_build_SES_incompatible_From_field(self) -> None:
 | 
			
		||||
        hamlet = self.example_user("hamlet")
 | 
			
		||||
        mail = build_email(
 | 
			
		||||
            "zerver/emails/password_reset",
 | 
			
		||||
            to_emails=[hamlet],
 | 
			
		||||
            from_name=OVERLY_LONG_NAME,
 | 
			
		||||
            from_address=FromAddress.NOREPLY,
 | 
			
		||||
            language="en",
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(mail.extra_headers["From"], FromAddress.NOREPLY)
 | 
			
		||||
 | 
			
		||||
    def test_build_SES_incompatible_From_field_limit(self) -> None:
 | 
			
		||||
        hamlet = self.example_user("hamlet")
 | 
			
		||||
        limit_length_name = "a" * (321 - len(sanitize_address(FromAddress.NOREPLY, "utf-8")) - 3)
 | 
			
		||||
        mail = build_email(
 | 
			
		||||
            "zerver/emails/password_reset",
 | 
			
		||||
            to_emails=[hamlet],
 | 
			
		||||
            from_name=limit_length_name,
 | 
			
		||||
            from_address=FromAddress.NOREPLY,
 | 
			
		||||
            language="en",
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(mail.extra_headers["From"], FromAddress.NOREPLY)
 | 
			
		||||
		Reference in New Issue
	
	Block a user