summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorvrs <none>2018-12-08 23:11:24 +0100
committerPatrick Totzke <patricktotzke@gmail.com>2018-12-10 10:25:49 +0000
commitf7c5b841568886be64695a14f341c4c7c58b3fba (patch)
tree415ff08c0d7a9b5e695c4c9e26ede7e000db1eba
parentb981bfe0c61d9aa55652b4b0a01b846aaa9c993a (diff)
match addresses against accounts, not address lists
fixes #1230, fixes an unfiled bug in clear_my_address()
-rw-r--r--alot/account.py25
-rw-r--r--alot/commands/thread.py46
-rw-r--r--alot/db/thread.py16
-rw-r--r--alot/settings/manager.py8
-rw-r--r--tests/account_test.py22
-rw-r--r--tests/commands/thread_test.py43
6 files changed, 93 insertions, 67 deletions
diff --git a/alot/account.py b/alot/account.py
index c8f3c09a..116c3417 100644
--- a/alot/account.py
+++ b/alot/account.py
@@ -9,6 +9,7 @@ import logging
import mailbox
import operator
import os
+import re
from .helper import call_cmd_async
from .helper import split_commandstring
@@ -176,7 +177,7 @@ class Account(object):
"""this accounts main email address"""
aliases = []
"""list of alternative addresses"""
- alias_regexp = []
+ alias_regexp = ""
"""regex matching alternative addresses"""
realname = None
"""real name used to format from-headers"""
@@ -243,12 +244,34 @@ class Account(object):
encrypt_by_default = u"none"
logging.info(msg)
self.encrypt_by_default = encrypt_by_default
+ # cache alias_regexp regexes
+ if self.alias_regexp != "":
+ self._alias_regexp = re.compile(
+ u'^' + str(self.alias_regexp) + u'$',
+ flags=0 if case_sensitive_username else re.IGNORECASE)
+
def get_addresses(self):
"""return all email addresses connected to this account, in order of
their importance"""
return [self.address] + self.aliases
+ def matches_address(self, address):
+ """returns whether this account knows about an email address
+
+ :param str address: address to look up
+ :rtype: bool
+ """
+ if self.address == address:
+ return True
+ for alias in self.aliases:
+ if alias == address:
+ return True
+ if self._alias_regexp is not None:
+ if self._alias_regexp.match(address):
+ return True
+ return False
+
@staticmethod
def store_mail(mbx, mail):
"""
diff --git a/alot/commands/thread.py b/alot/commands/thread.py
index f68e5405..3fbe8148 100644
--- a/alot/commands/thread.py
+++ b/alot/commands/thread.py
@@ -6,7 +6,6 @@ import argparse
import logging
import mailcap
import os
-import re
import subprocess
import tempfile
import email
@@ -69,35 +68,23 @@ def determine_sender(mail, action='reply'):
logging.debug('candidate addresses: %s', candidate_addresses)
# pick the most important account that has an address in candidates
- # and use that accounts realname and the address found here
+ # and use that account's realname and the address found here
for account in my_accounts:
- acc_addresses = [
- re.escape(str(a)) for a in account.get_addresses()]
- if account.alias_regexp is not None:
- acc_addresses.append(account.alias_regexp)
- for alias in acc_addresses:
- regex = re.compile(
- u'^' + str(alias) + u'$',
- flags=(
- re.IGNORECASE if not account.address.case_sensitive
- else 0))
- for seen_name, seen_address in candidate_addresses:
- if not regex.match(seen_address):
- continue
- logging.debug("match!: '%s' '%s'", seen_address, alias)
+ for seen_name, seen_address in candidate_addresses:
+ if account.matches_address(seen_address):
if settings.get(action + '_force_realname'):
realname = account.realname
else:
realname = seen_name
if settings.get(action + '_force_address'):
- address = account.address
+ address = str(account.address)
else:
address = seen_address
logging.debug('using realname: "%s"', realname)
logging.debug('using address: %s', address)
- from_value = formataddr((realname, str(address)))
+ from_value = formataddr((realname, address))
return from_value, account
# revert to default account if nothing found
@@ -199,24 +186,23 @@ class ReplyCommand(Command):
# set To
sender = mail['Reply-To'] or mail['From']
- my_addresses = settings.get_addresses()
sender_address = parseaddr(sender)[1]
cc = []
# check if reply is to self sent message
- if sender_address in my_addresses:
+ if account.matches_address(sender_address):
recipients = mail.get_all('To', [])
emsg = 'Replying to own message, set recipients to: %s' \
% recipients
logging.debug(emsg)
else:
- recipients = self.clear_my_address([], [sender])
+ recipients = [sender]
if self.groupreply:
# make sure that our own address is not included
# if the message was self-sent, then our address is not included
MFT = mail.get_all('Mail-Followup-To', [])
- followupto = self.clear_my_address(my_addresses, MFT)
+ followupto = self.clear_my_address(account, MFT)
if followupto and settings.get('honor_followup_to'):
logging.debug('honor followup to: %s', ', '.join(followupto))
recipients = followupto
@@ -226,15 +212,15 @@ class ReplyCommand(Command):
recipients.append(mail['From'])
# append To addresses if not replying to self sent message
- if sender_address not in my_addresses:
+ if not account.matches_address(sender_address):
cleared = self.clear_my_address(
- my_addresses, mail.get_all('To', []))
+ account, mail.get_all('To', []))
recipients.extend(cleared)
# copy cc for group-replies
if 'Cc' in mail:
cc = self.clear_my_address(
- my_addresses, mail.get_all('Cc', []))
+ account, mail.get_all('Cc', []))
envelope.add('Cc', decode_header(', '.join(cc)))
to = ', '.join(self.ensure_unique_address(recipients))
@@ -293,11 +279,11 @@ class ReplyCommand(Command):
encrypt=encrypt))
@staticmethod
- def clear_my_address(my_addresses, value):
- """return recipient header without the addresses in my_addresses
+ def clear_my_address(my_account, value):
+ """return recipient header without the addresses in my_account
- :param my_addresses: a list of my email addresses (no real name part)
- :type my_addresses: list(str)
+ :param my_account: my account
+ :type my_account: :class:`Account`
:param value: a list of recipient or sender strings (with or without
real names as taken from email headers)
:type value: list(str)
@@ -306,7 +292,7 @@ class ReplyCommand(Command):
"""
new_value = []
for name, address in getaddresses(value):
- if address not in my_addresses:
+ if not my_account.matches_address(address):
new_value.append(formataddr((name, str(address))))
return new_value
diff --git a/alot/db/thread.py b/alot/db/thread.py
index fe7ea10f..0943d815 100644
--- a/alot/db/thread.py
+++ b/alot/db/thread.py
@@ -174,14 +174,14 @@ class Thread(object):
return self._authors
- def get_authors_string(self, own_addrs=None, replace_own=None):
+ def get_authors_string(self, own_accts=None, replace_own=None):
"""
returns a string of comma-separated authors
Depending on settings, it will substitute "me" for author name if
address is user's own.
- :param own_addrs: list of own email addresses to replace
- :type own_addrs: list of str
+ :param own_accts: list of own accounts to replace
+ :type own_accts: list of :class:`Account`
:param replace_own: whether or not to actually do replacement
:type replace_own: bool
:rtype: str
@@ -189,12 +189,14 @@ class Thread(object):
if replace_own is None:
replace_own = settings.get('thread_authors_replace_me')
if replace_own:
- if own_addrs is None:
- own_addrs = settings.get_addresses()
+ if own_accts is None:
+ own_accts = settings.get_accounts()
authorslist = []
for aname, aaddress in self.get_authors():
- if aaddress in own_addrs:
- aname = settings.get('thread_authors_me')
+ for account in own_accts:
+ if account.matches_address(aaddress):
+ aname = settings.get('thread_authors_me')
+ break
if not aname:
aname = aaddress
if aname not in authorslist:
diff --git a/alot/settings/manager.py b/alot/settings/manager.py
index d1f57083..87ae14c7 100644
--- a/alot/settings/manager.py
+++ b/alot/settings/manager.py
@@ -469,16 +469,16 @@ class SettingsManager(object):
:param str address: address to look up. A realname part will be ignored.
:param bool return_default: If True and no address can be found, then
- the default account wil be returned
+ the default account wil be returned.
:rtype: :class:`Account`
:raises ~alot.settings.errors.NoMatchingAccount: If no account can be
found. This includes if return_default is True and there are no
accounts defined.
"""
_, address = email.utils.parseaddr(address)
- for myad in self.get_addresses():
- if myad == address:
- return self._accountmap[myad]
+ for account in self.get_accounts():
+ if account.matches_address(address):
+ return account
if return_default:
try:
return self.get_accounts()[0]
diff --git a/tests/account_test.py b/tests/account_test.py
index 9f6287be..9d0ac125 100644
--- a/tests/account_test.py
+++ b/tests/account_test.py
@@ -32,20 +32,30 @@ class _AccountTestClass(account.Account):
class TestAccount(unittest.TestCase):
"""Tests for the Account class."""
- def test_get_address(self):
+ def test_matches_address(self):
"""Tests address without aliases."""
acct = _AccountTestClass(address="foo@example.com")
- self.assertListEqual(acct.get_addresses(), ['foo@example.com'])
+ self.assertTrue(acct.matches_address(u"foo@example.com"))
+ self.assertFalse(acct.matches_address(u"bar@example.com"))
- def test_get_address_with_aliases(self):
+ def test_matches_address_with_aliases(self):
"""Tests address with aliases."""
acct = _AccountTestClass(address="foo@example.com",
aliases=['bar@example.com'])
- self.assertListEqual(acct.get_addresses(),
- ['foo@example.com', 'bar@example.com'])
+ self.assertTrue(acct.matches_address(u"foo@example.com"))
+ self.assertTrue(acct.matches_address(u"bar@example.com"))
+ self.assertFalse(acct.matches_address(u"baz@example.com"))
+
+ def test_matches_address_with_regex_aliases(self):
+ """Tests address with regex aliases."""
+ acct = _AccountTestClass(address=u"foo@example.com",
+ alias_regexp=r'to\+.*@example.com')
+ self.assertTrue(acct.matches_address(u"to+foo@example.com"))
+ self.assertFalse(acct.matches_address(u"to@example.com"))
+
def test_deprecated_encrypt_by_default(self):
- """Tests that depreacted values are still accepted."""
+ """Tests that deprecated values are still accepted."""
for each in ['true', 'yes', '1']:
acct = _AccountTestClass(address='foo@example.com',
encrypt_by_default=each)
diff --git a/tests/commands/thread_test.py b/tests/commands/thread_test.py
index 315273c5..634c35e8 100644
--- a/tests/commands/thread_test.py
+++ b/tests/commands/thread_test.py
@@ -45,15 +45,27 @@ class Test_ensure_unique_address(unittest.TestCase):
self.assertListEqual(actual, expected)
+class _AccountTestClass(Account):
+ """Implements stubs for ABC methods."""
+
+ def send_mail(self, mail):
+ pass
+
+
class TestClearMyAddress(unittest.TestCase):
- me1 = 'me@example.com'
- me2 = 'ME@example.com'
- me_named = 'alot team <me@example.com>'
- you = 'you@example.com'
- named = 'somebody you know <somebody@example.com>'
- imposter = 'alot team <imposter@example.com>'
- mine = [me1, me2]
+ me1 = u'me@example.com'
+ me2 = u'ME@example.com'
+ me3 = u'me+label@example.com'
+ me4 = u'ME+label@example.com'
+ me_regex = r'me\+.*@example.com'
+ me_named = u'alot team <me@example.com>'
+ you = u'you@example.com'
+ named = u'somebody you know <somebody@example.com>'
+ imposter = u'alot team <imposter@example.com>'
+ mine = _AccountTestClass(
+ address=me1, aliases=[], alias_regexp=me_regex, case_sensitive_username=True)
+
def test_empty_input_returns_empty_list(self):
self.assertListEqual(
@@ -62,7 +74,7 @@ class TestClearMyAddress(unittest.TestCase):
def test_only_my_emails_result_in_empty_list(self):
expected = []
actual = thread.ReplyCommand.clear_my_address(
- self.mine, self.mine+[self.me_named])
+ self.mine, [self.me1, self.me3, self.me_named])
self.assertListEqual(actual, expected)
def test_other_emails_are_untouched(self):
@@ -72,22 +84,15 @@ class TestClearMyAddress(unittest.TestCase):
self.assertListEqual(actual, expected)
def test_case_matters(self):
- expected = [self.me1]
- mine = [self.me2]
- actual = thread.ReplyCommand.clear_my_address(mine, expected)
+ input_ = [self.me1, self.me2, self.me3, self.me4]
+ expected = [self.me2, self.me4]
+ actual = thread.ReplyCommand.clear_my_address(self.mine, input_)
self.assertListEqual(actual, expected)
def test_same_address_with_different_real_name_is_removed(self):
input_ = [self.me_named, self.you]
- mine = [self.me1]
expected = [self.you]
- actual = thread.ReplyCommand.clear_my_address(mine, input_)
- self.assertListEqual(actual, expected)
-
- def test_real_name_is_never_considered(self):
- expected = [self.imposter]
- mine = 'alot team'
- actual = thread.ReplyCommand.clear_my_address(mine, expected)
+ actual = thread.ReplyCommand.clear_my_address(self.mine, input_)
self.assertListEqual(actual, expected)