mirror of https://github.com/zulip/zulip.git
email_mirror: Add the sender at the start of stream message.
Fixes part 3 of #10612. When sending an email to the email mirror to a stream address, if "+show-sender" is added in the address, the stream message will now include "From: <sender>" at the top.
This commit is contained in:
parent
60c7467464
commit
dbff533e09
|
@ -4465,20 +4465,28 @@ def get_email_gateway_message_string_from_address(address: str) -> Optional[str]
|
||||||
|
|
||||||
return msg_string
|
return msg_string
|
||||||
|
|
||||||
def decode_email_address(email: str) -> Optional[Tuple[str, str]]:
|
def decode_email_address(email: str) -> Optional[Tuple[str, str, bool]]:
|
||||||
# Perform the reverse of encode_email_address. Returns a tuple of (streamname, email_token)
|
# Perform the reverse of encode_email_address. Returns a tuple of
|
||||||
|
# (streamname, email_token, show_sender)
|
||||||
msg_string = get_email_gateway_message_string_from_address(email)
|
msg_string = get_email_gateway_message_string_from_address(email)
|
||||||
|
|
||||||
if msg_string is None:
|
if msg_string is None:
|
||||||
return None
|
return None
|
||||||
elif '.' in msg_string:
|
|
||||||
|
if msg_string.endswith(('+show-sender', '.show-sender')):
|
||||||
|
show_sender = True
|
||||||
|
msg_string = msg_string[:-12] # strip "+show-sender"
|
||||||
|
else:
|
||||||
|
show_sender = False
|
||||||
|
|
||||||
|
if '.' in msg_string:
|
||||||
# Workaround for Google Groups and other programs that don't accept emails
|
# Workaround for Google Groups and other programs that don't accept emails
|
||||||
# that have + signs in them (see Trac #2102)
|
# that have + signs in them (see Trac #2102)
|
||||||
encoded_stream_name, token = msg_string.split('.')
|
encoded_stream_name, token = msg_string.split('.')
|
||||||
else:
|
else:
|
||||||
encoded_stream_name, token = msg_string.split('+')
|
encoded_stream_name, token = msg_string.split('+')
|
||||||
|
|
||||||
stream_name = re.sub(r"%\d{4}", lambda x: chr(int(x.group(0)[1:])), encoded_stream_name)
|
stream_name = re.sub(r"%\d{4}", lambda x: chr(int(x.group(0)[1:])), encoded_stream_name)
|
||||||
return stream_name, token
|
return stream_name, token, show_sender
|
||||||
|
|
||||||
SubHelperT = Tuple[List[Dict[str, Any]], List[Dict[str, Any]], List[Dict[str, Any]]]
|
SubHelperT = Tuple[List[Dict[str, Any]], List[Dict[str, Any]], List[Dict[str, Any]]]
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional, Tuple
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
@ -133,7 +133,8 @@ def mark_missed_message_address_as_used(address: str) -> None:
|
||||||
redis_client.delete(key)
|
redis_client.delete(key)
|
||||||
raise ZulipEmailForwardError('Missed message address has already been used')
|
raise ZulipEmailForwardError('Missed message address has already been used')
|
||||||
|
|
||||||
def construct_zulip_body(message: message.Message, realm: Realm) -> str:
|
def construct_zulip_body(message: message.Message, realm: Realm,
|
||||||
|
show_sender: bool=False) -> str:
|
||||||
body = extract_body(message)
|
body = extract_body(message)
|
||||||
# Remove null characters, since Zulip will reject
|
# Remove null characters, since Zulip will reject
|
||||||
body = body.replace("\x00", "")
|
body = body.replace("\x00", "")
|
||||||
|
@ -142,6 +143,11 @@ def construct_zulip_body(message: message.Message, realm: Realm) -> str:
|
||||||
body = body.strip()
|
body = body.strip()
|
||||||
if not body:
|
if not body:
|
||||||
body = '(No email body)'
|
body = '(No email body)'
|
||||||
|
|
||||||
|
if show_sender:
|
||||||
|
sender = message.get("From")
|
||||||
|
body = "From: %s\n%s" % (sender, body)
|
||||||
|
|
||||||
return body
|
return body
|
||||||
|
|
||||||
def send_to_missed_message_address(address: str, message: message.Message) -> None:
|
def send_to_missed_message_address(address: str, message: message.Message) -> None:
|
||||||
|
@ -282,16 +288,16 @@ def extract_and_upload_attachments(message: message.Message, realm: Realm) -> st
|
||||||
|
|
||||||
return "\n".join(attachment_links)
|
return "\n".join(attachment_links)
|
||||||
|
|
||||||
def extract_and_validate(email: str) -> Stream:
|
def extract_and_validate(email: str) -> Tuple[Stream, bool]:
|
||||||
temp = decode_email_address(email)
|
temp = decode_email_address(email)
|
||||||
if temp is None:
|
if temp is None:
|
||||||
raise ZulipEmailForwardError("Malformed email recipient " + email)
|
raise ZulipEmailForwardError("Malformed email recipient " + email)
|
||||||
stream_name, token = temp
|
stream_name, token, show_sender = temp
|
||||||
|
|
||||||
if not valid_stream(stream_name, token):
|
if not valid_stream(stream_name, token):
|
||||||
raise ZulipEmailForwardError("Bad stream token from email recipient " + email)
|
raise ZulipEmailForwardError("Bad stream token from email recipient " + email)
|
||||||
|
|
||||||
return Stream.objects.get(email_token=token)
|
return Stream.objects.get(email_token=token), show_sender
|
||||||
|
|
||||||
def find_emailgateway_recipient(message: message.Message) -> str:
|
def find_emailgateway_recipient(message: message.Message) -> str:
|
||||||
# We can't use Delivered-To; if there is a X-Gm-Original-To
|
# We can't use Delivered-To; if there is a X-Gm-Original-To
|
||||||
|
@ -322,8 +328,8 @@ def strip_from_subject(subject: str) -> str:
|
||||||
|
|
||||||
def process_stream_message(to: str, subject: str, message: message.Message,
|
def process_stream_message(to: str, subject: str, message: message.Message,
|
||||||
debug_info: Dict[str, Any]) -> None:
|
debug_info: Dict[str, Any]) -> None:
|
||||||
stream = extract_and_validate(to)
|
stream, show_sender = extract_and_validate(to)
|
||||||
body = construct_zulip_body(message, stream.realm)
|
body = construct_zulip_body(message, stream.realm, show_sender)
|
||||||
debug_info["stream"] = stream
|
debug_info["stream"] = stream
|
||||||
send_zulip(settings.EMAIL_GATEWAY_BOT, stream, subject, body)
|
send_zulip(settings.EMAIL_GATEWAY_BOT, stream, subject, body)
|
||||||
logger.info("Successfully processed email to %s (%s)" % (
|
logger.info("Successfully processed email to %s (%s)" % (
|
||||||
|
|
|
@ -54,24 +54,54 @@ class TestEncodeDecode(ZulipTestCase):
|
||||||
self.assertTrue(email_address.endswith('@testserver'))
|
self.assertTrue(email_address.endswith('@testserver'))
|
||||||
tup = decode_email_address(email_address)
|
tup = decode_email_address(email_address)
|
||||||
assert tup is not None
|
assert tup is not None
|
||||||
(decoded_stream_name, token) = tup
|
(decoded_stream_name, token, show_sender) = tup
|
||||||
|
self.assertFalse(show_sender)
|
||||||
self.assertEqual(decoded_stream_name, stream_name)
|
self.assertEqual(decoded_stream_name, stream_name)
|
||||||
self.assertEqual(token, stream.email_token)
|
self.assertEqual(token, stream.email_token)
|
||||||
|
|
||||||
email_address = email_address.replace('+', '.')
|
parts = email_address.split('@')
|
||||||
tup = decode_email_address(email_address)
|
parts[0] += "+show-sender"
|
||||||
|
email_address_show = '@'.join(parts)
|
||||||
|
tup = decode_email_address(email_address_show)
|
||||||
assert tup is not None
|
assert tup is not None
|
||||||
(decoded_stream_name, token) = tup
|
(decoded_stream_name, token, show_sender) = tup
|
||||||
|
self.assertTrue(show_sender)
|
||||||
|
self.assertEqual(decoded_stream_name, stream_name)
|
||||||
|
self.assertEqual(token, stream.email_token)
|
||||||
|
|
||||||
|
email_address_dots = email_address.replace('+', '.')
|
||||||
|
tup = decode_email_address(email_address_dots)
|
||||||
|
assert tup is not None
|
||||||
|
(decoded_stream_name, token, show_sender) = tup
|
||||||
|
self.assertFalse(show_sender)
|
||||||
|
self.assertEqual(decoded_stream_name, stream_name)
|
||||||
|
self.assertEqual(token, stream.email_token)
|
||||||
|
|
||||||
|
email_address_dots_show = email_address_show.replace('+', '.')
|
||||||
|
tup = decode_email_address(email_address_dots_show)
|
||||||
|
assert tup is not None
|
||||||
|
(decoded_stream_name, token, show_sender) = tup
|
||||||
|
self.assertTrue(show_sender)
|
||||||
self.assertEqual(decoded_stream_name, stream_name)
|
self.assertEqual(decoded_stream_name, stream_name)
|
||||||
self.assertEqual(token, stream.email_token)
|
self.assertEqual(token, stream.email_token)
|
||||||
|
|
||||||
email_address = email_address.replace('@testserver', '@zulip.org')
|
email_address = email_address.replace('@testserver', '@zulip.org')
|
||||||
|
email_address_show = email_address_show.replace('@testserver', '@zulip.org')
|
||||||
self.assertEqual(decode_email_address(email_address), None)
|
self.assertEqual(decode_email_address(email_address), None)
|
||||||
|
self.assertEqual(decode_email_address(email_address_show), None)
|
||||||
|
|
||||||
with self.settings(EMAIL_GATEWAY_EXTRA_PATTERN_HACK='@zulip.org'):
|
with self.settings(EMAIL_GATEWAY_EXTRA_PATTERN_HACK='@zulip.org'):
|
||||||
tup = decode_email_address(email_address)
|
tup = decode_email_address(email_address)
|
||||||
assert tup is not None
|
assert tup is not None
|
||||||
(decoded_stream_name, token) = tup
|
(decoded_stream_name, token, show_sender) = tup
|
||||||
|
self.assertFalse(show_sender)
|
||||||
|
self.assertEqual(decoded_stream_name, stream_name)
|
||||||
|
self.assertEqual(token, stream.email_token)
|
||||||
|
|
||||||
|
tup = decode_email_address(email_address_show)
|
||||||
|
assert tup is not None
|
||||||
|
(decoded_stream_name, token, show_sender) = tup
|
||||||
|
self.assertTrue(show_sender)
|
||||||
self.assertEqual(decoded_stream_name, stream_name)
|
self.assertEqual(decoded_stream_name, stream_name)
|
||||||
self.assertEqual(token, stream.email_token)
|
self.assertEqual(token, stream.email_token)
|
||||||
|
|
||||||
|
@ -206,6 +236,30 @@ class TestStreamEmailMessagesSuccess(ZulipTestCase):
|
||||||
self.assertEqual(get_display_recipient(message.recipient), stream.name)
|
self.assertEqual(get_display_recipient(message.recipient), stream.name)
|
||||||
self.assertEqual(message.topic_name(), incoming_valid_message['Subject'])
|
self.assertEqual(message.topic_name(), incoming_valid_message['Subject'])
|
||||||
|
|
||||||
|
def test_receive_stream_email_show_sender_success(self) -> None:
|
||||||
|
user_profile = self.example_user('hamlet')
|
||||||
|
self.login(user_profile.email)
|
||||||
|
self.subscribe(user_profile, "Denmark")
|
||||||
|
stream = get_stream("Denmark", user_profile.realm)
|
||||||
|
|
||||||
|
stream_to_address = encode_email_address(stream)
|
||||||
|
parts = stream_to_address.split('@')
|
||||||
|
parts[0] += "+show-sender"
|
||||||
|
stream_to_address = '@'.join(parts)
|
||||||
|
|
||||||
|
incoming_valid_message = MIMEText('TestStreamEmailMessages Body')
|
||||||
|
incoming_valid_message['Subject'] = 'TestStreamEmailMessages Subject'
|
||||||
|
incoming_valid_message['From'] = self.example_email('hamlet')
|
||||||
|
incoming_valid_message['To'] = stream_to_address
|
||||||
|
incoming_valid_message['Reply-to'] = self.example_email('othello')
|
||||||
|
|
||||||
|
process_message(incoming_valid_message)
|
||||||
|
message = most_recent_message(user_profile)
|
||||||
|
|
||||||
|
self.assertEqual(message.content, "From: %s\n%s" % (self.example_email('hamlet'),
|
||||||
|
"TestStreamEmailMessages Body"))
|
||||||
|
self.assertEqual(get_display_recipient(message.recipient), stream.name)
|
||||||
|
self.assertEqual(message.topic_name(), incoming_valid_message['Subject'])
|
||||||
|
|
||||||
class TestStreamEmailMessagesEmptyBody(ZulipTestCase):
|
class TestStreamEmailMessagesEmptyBody(ZulipTestCase):
|
||||||
def test_receive_stream_email_messages_empty_body(self) -> None:
|
def test_receive_stream_email_messages_empty_body(self) -> None:
|
||||||
|
|
Loading…
Reference in New Issue