2019-03-21 10:24:56 +01:00
|
|
|
import re
|
|
|
|
|
|
|
|
from django.conf import settings
|
|
|
|
from django.utils.text import slugify
|
|
|
|
|
|
|
|
from zerver.models import Stream
|
|
|
|
|
2019-05-26 16:25:23 +02:00
|
|
|
from typing import Dict, Tuple
|
|
|
|
|
2019-07-14 03:51:53 +02:00
|
|
|
optional_address_tokens = ["show-sender", "include-footer", "include-quotes"]
|
2019-03-21 10:24:56 +01:00
|
|
|
|
2019-03-21 11:28:14 +01:00
|
|
|
class ZulipEmailForwardError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def get_email_gateway_message_string_from_address(address: str) -> str:
|
2019-03-21 10:24:56 +01:00
|
|
|
pattern_parts = [re.escape(part) for part in settings.EMAIL_GATEWAY_PATTERN.split('%s')]
|
|
|
|
if settings.EMAIL_GATEWAY_EXTRA_PATTERN_HACK:
|
|
|
|
# Accept mails delivered to any Zulip server
|
|
|
|
pattern_parts[-1] = settings.EMAIL_GATEWAY_EXTRA_PATTERN_HACK
|
|
|
|
match_email_re = re.compile("(.*?)".join(pattern_parts))
|
|
|
|
match = match_email_re.match(address)
|
|
|
|
|
|
|
|
if not match:
|
2019-03-21 11:28:14 +01:00
|
|
|
raise ZulipEmailForwardError('Address not recognized by gateway.')
|
2019-03-21 10:24:56 +01:00
|
|
|
msg_string = match.group(1)
|
|
|
|
|
|
|
|
return msg_string
|
|
|
|
|
|
|
|
def encode_email_address(stream: Stream) -> str:
|
|
|
|
return encode_email_address_helper(stream.name, stream.email_token)
|
|
|
|
|
|
|
|
def encode_email_address_helper(name: str, email_token: str) -> str:
|
|
|
|
# Some deployments may not use the email gateway
|
|
|
|
if settings.EMAIL_GATEWAY_PATTERN == '':
|
|
|
|
return ''
|
|
|
|
|
|
|
|
# Given the fact that we have almost no restrictions on stream names and
|
|
|
|
# that what characters are allowed in e-mail addresses is complicated and
|
|
|
|
# dependent on context in the address, we opt for a simple scheme:
|
|
|
|
# 1. Replace all substrings of non-alphanumeric characters with a single hyphen.
|
|
|
|
# 2. Use Django's slugify to convert the resulting name to ascii.
|
|
|
|
# 3. If the resulting name is shorter than the name we got in step 1,
|
|
|
|
# it means some letters can't be reasonably turned to ascii and have to be dropped,
|
|
|
|
# which would mangle the name, so we just skip the name part of the address.
|
|
|
|
name = re.sub(r"\W+", '-', name)
|
|
|
|
slug_name = slugify(name)
|
|
|
|
encoded_name = slug_name if len(slug_name) == len(name) else ''
|
|
|
|
|
|
|
|
# If encoded_name ends up empty, we just skip this part of the address:
|
|
|
|
if encoded_name:
|
2019-07-14 03:03:47 +02:00
|
|
|
encoded_token = "%s.%s" % (encoded_name, email_token)
|
2019-03-21 10:24:56 +01:00
|
|
|
else:
|
|
|
|
encoded_token = email_token
|
|
|
|
|
|
|
|
return settings.EMAIL_GATEWAY_PATTERN % (encoded_token,)
|
|
|
|
|
2019-05-26 16:25:23 +02:00
|
|
|
def decode_email_address(email: str) -> Tuple[str, Dict[str, bool]]:
|
2019-03-21 10:24:56 +01:00
|
|
|
# Perform the reverse of encode_email_address. Returns a tuple of
|
2019-05-26 16:25:23 +02:00
|
|
|
# (email_token, options)
|
2019-03-21 10:24:56 +01:00
|
|
|
msg_string = get_email_gateway_message_string_from_address(email)
|
|
|
|
|
2019-07-14 03:00:36 +02:00
|
|
|
# Support both + and . as separators. For background, the `+` is
|
|
|
|
# more aesthetically pleasing, but because Google groups silently
|
|
|
|
# drops the use of `+` in email addresses, which would completely
|
|
|
|
# break the integration, we now favor `.` as the separator between
|
|
|
|
# tokens in the email addresses we generate.
|
|
|
|
#
|
|
|
|
# We need to keep supporting `+` indefinitely for backwards
|
|
|
|
# compatibility with older versions of Zulip that offered users
|
|
|
|
# email addresses prioritizing using `+` for better aesthetics.
|
|
|
|
msg_string = msg_string.replace('.', '+')
|
|
|
|
|
|
|
|
parts = msg_string.split('+')
|
2019-05-26 16:25:23 +02:00
|
|
|
options = {} # type: Dict[str, bool]
|
|
|
|
for part in parts:
|
|
|
|
if part in optional_address_tokens:
|
|
|
|
options[part.replace('-', '_')] = True
|
|
|
|
|
|
|
|
for key in options.keys():
|
|
|
|
parts.remove(key.replace('_', '-'))
|
|
|
|
|
|
|
|
# At this point, there should be one or two parts left:
|
2019-03-21 10:24:56 +01:00
|
|
|
# [stream_name, email_token] or just [email_token]
|
|
|
|
if len(parts) == 1:
|
|
|
|
token = parts[0]
|
|
|
|
else:
|
|
|
|
token = parts[1]
|
|
|
|
|
2019-05-26 16:25:23 +02:00
|
|
|
return token, options
|