Commit a3337eb1 authored by Barry Warsaw's avatar Barry Warsaw

Greatly improve handling non-ascii in email commands.

parent f9e08a39
......@@ -113,6 +113,12 @@ class CommandFinder:
parts = line.strip().split()
if len(parts) == 0:
continue
# Ensure that all the parts are unicodes. Since we only accept
# ASCII commands and arguments, ignore anything else.
parts = [(part
if isinstance(part, unicode)
else part.decode('ascii', errors='ignore'))
for part in parts]
yield parts
......@@ -122,13 +128,16 @@ class Results:
implements(IEmailResults)
def __init__(self):
def __init__(self, charset='us-ascii'):
self._output = StringIO()
self.charset = charset
print >> self._output, _("""\
The results of your email command are provided below.
""")
def write(self, text):
if not isinstance(text, unicode):
text = text.decode(self.charset, errors='ignore')
self._output.write(text)
def __unicode__(self):
......@@ -160,7 +169,10 @@ class CommandRunner(Runner):
log.info('%s -request message replied and discard', message_id)
return False
# Now craft the response and process the command lines.
results = Results()
charset = msg.get_param('charset')
if charset is None:
charset = 'us-ascii'
results = Results(charset)
# Include just a few key pieces of information from the original: the
# sender, date, and message id.
print >> results, _('- Original message details:')
......@@ -174,12 +186,14 @@ class CommandRunner(Runner):
print >> results, _('\n- Results:')
finder = CommandFinder(msg, msgdata, results)
for parts in finder:
command = None
# Try to find a command on this line. There may be a Re: prefix
# (possibly internationalized) so try with the first and second
# words on the line.
command_name = parts.pop(0)
command = config.commands.get(command_name)
if command is None:
if len(parts) > 0:
command_name = parts.pop(0)
command = config.commands.get(command_name)
if command is None and len(parts) > 0:
command_name = parts.pop(0)
command = config.commands.get(command_name)
if command is None:
......@@ -210,10 +224,14 @@ class CommandRunner(Runner):
reply = UserNotification(msg.sender, mlist.bounces_address,
_('The results of your email commands'),
lang=language)
# Find a charset for the response body. Try ascii first, then
# latin-1 and finally falling back to utf-8.
cte = msg.get('content-transfer-encoding')
if cte is not None:
reply['Content-Transfer-Encoding'] = cte
# Find a charset for the response body. Try the original message's
# charset first, then ascii, then latin-1 and finally falling back to
# utf-8.
reply_body = unicode(results)
for charset in ('us-ascii', 'latin-1'):
for charset in (results.charset, 'us-ascii', 'latin-1'):
try:
reply_body.encode(charset)
break
......
......@@ -35,6 +35,7 @@ from mailman.interfaces.registrar import IRegistrar
from mailman.interfaces.usermanager import IUserManager
from mailman.runners.command import CommandRunner
from mailman.testing.helpers import (
get_queue_messages,
make_testable_runner,
specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
......@@ -94,3 +95,94 @@ To: [email protected]
self.assertEqual(address.verified_on, datetime(2005, 8, 1, 7, 49, 23))
address = manager.get_address('[email protected]')
self.assertEqual(address.email, '[email protected]')
def test_confirm_with_utf8_body(self):
# Clear out the virgin queue so that the test below only sees the
# reply to the confirmation message.
get_queue_messages('virgin')
subject = 'Re: confirm {0}'.format(self._token)
to = 'test-confirm+{0}@example.com'.format(self._token)
msg = mfs("""\
From: Anne Person <[email protected]
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
* [email protected]m <=
[email protected]m>:
> Email Address Registration Confirmation
>=20
> Hello, this is the GNU Mailman server at example.com.
>=20
> We have received a registration request for the email address
>=20
> [email protected]
>=20
> Before you can start using GNU Mailman at this site, you must first con=
firm
> that this is your email address. You can do this by replying to this m=
essage,
> keeping the Subject header intact. Or you can visit this web page
>=20
> http://example.com/confirm/90bf6ef335d92cfbbe540a5c9ebfecb107a48e48
>=20
> If you do not wish to register this email address simply disregard this
> message. If you think you are being maliciously subscribed to the list=
, or
> have any other questions, you may contact
>=20
> [email protected]
--=20
Franziskanerstra=C3=9Fe
""")
msg['Subject'] = subject
msg['To'] = to
self._commandq.enqueue(msg, dict(listname='[email protected]'))
self._runner.run()
# Anne is now a confirmed member so her user record and email address
# should exist in the database.
manager = getUtility(IUserManager)
user = manager.get_user('[email protected]')
address = list(user.addresses)[0]
self.assertEqual(address.email, '[email protected]')
self.assertEqual(address.verified_on, datetime(2005, 8, 1, 7, 49, 23))
address = manager.get_address('[email protected]')
self.assertEqual(address.email, '[email protected]')
messages = get_queue_messages('virgin')
self.assertEqual(len(messages), 1)
self.assertEqual(messages[0].msgdata['recipients'],
set(['[email protected]']))
def test_confirm_with_no_command_in_utf8_body(self):
get_queue_messages('virgin')
subject = 'Re: confirm {0}'.format(self._token)
to = 'test-confirm+{0}@example.com'.format(self._token)
msg = mfs("""\
From: Anne Person <[email protected]
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
Franziskanerstra=C3=9Fe
""")
msg['Subject'] = subject
msg['To'] = to
self._commandq.enqueue(msg, dict(listname='[email protected]'))
self._runner.run()
# Anne is now a confirmed member so her user record and email address
# should exist in the database.
manager = getUtility(IUserManager)
user = manager.get_user('[email protected]')
address = list(user.addresses)[0]
self.assertEqual(address.email, '[email protected]')
self.assertEqual(address.verified_on, datetime(2005, 8, 1, 7, 49, 23))
address = manager.get_address('[email protected]')
self.assertEqual(address.email, '[email protected]')
messages = get_queue_messages('virgin')
self.assertEqual(len(messages), 1)
self.assertEqual(messages[0].msgdata['recipients'],
set(['[email protected]']))
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment