diff --git a/remail/maillist.py b/remail/maillist.py index b399ff7..c87a986 100644 --- a/remail/maillist.py +++ b/remail/maillist.py @@ -14,7 +14,7 @@ from remail.gpg import gpg_crypt, RemailGPGException from remail.tracking import account_tracking from remail.config import accounts_config, gpg_config, smime_config -from email.utils import make_msgid, formatdate, getaddresses +from email.utils import make_msgid, formatdate, getaddresses, parseaddr from email.policy import EmailPolicy from flufl.bounce import all_failures @@ -22,6 +22,10 @@ from ruamel.yaml import YAML import mailbox import os +import re + +mustquote = re.compile(r'[][\\()<>@,:;".]') +escapedquote = re.compile(r'[\\"]') class maillist(object): ''' @@ -228,24 +232,52 @@ class maillist(object): dest.toadmins = True dest.accounts = self.config.admins - def mangle_from(self, msg, mfrom): + def mangle_from(self, msg, name, mfrom): ''' - Build 'From' string so the original 'From' is 'visible': - From: $LISTNAME for $ORIGINAL_FROM <$LISTADDRESS> + If @name is non-empty then replace the 'From:' header with: - If $ORIGINAL_FROM does not contain a name, mangle the email - address by replacing @ with _at_ + '@name via list-name' + + If @name is empty mangle the email address @mfrom by replacing + '@' with at and setting the 'From:' header to: + + '@mfrom via list-name' ''' - mfrom = mfrom.replace('@','_at_') - return '%s for %s <%s>' % (self.config.name, mfrom, - self.config.listaddrs.post) + if not len(name): + name = mfrom.replace('@',' at ') + + # Compose the display name + name = '%s via %s' %(name, self.config.name) + + # Check whether it must be quoted + # + # email.utils.formataddr() fails to do so for non-ascii display + # names which causes some email-clients to display completely + # nonsensical names. Especially for the typical big corporate + # "name, surname" patterns as the resulting header entry becomes + # + # From: =?utf-8?q?NAME?=, =?utf-8?q?SURNAME?= + # which some clients render as: + # NAME@$SOMEMADEUPDOMAIN, SURNAME + # + # instead of + # From: =?utf-8?q?NAME=2C_SURNAME?= + # which they correctly render as: + # "NAME, SURNAME" + # + if mustquote.search(name): + name = escapedquote.sub(r'\\\g<0>', name) + name = '"%s"' %name + + # The result is correcly handled by msg['From'] = result + return '%s <%s>' %(name, self.config.listaddrs.post) def do_process_mail(self, msg, dest): msgid = msg.get('Message-Id', '') msgto = msg.get('To') origfrom = msg.get('From') - msgfrom = get_raw_email_addr(origfrom) + name, msgfrom = parseaddr(origfrom) sinfo = sender_info(msg) # Archive the incoming mail @@ -264,7 +296,8 @@ class maillist(object): self.archive_mail(msg_plain, admin=dest.toadmin) - mfrom = self.mangle_from(msg, msgfrom) + mfrom = self.mangle_from(msg, name, msgfrom) + # Save sender information in the outgoing message? if self.config.attach_sender_info: # Only do so for non-subscribers