summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2021-11-20 13:32:44 +0100
committerAnton Khirnov <anton@khirnov.net>2021-11-20 14:54:11 +0100
commitd64e41150f6ba53a9361e8560d374bc69974ca84 (patch)
treeda10addbc7dcb4a7adcd64c92f6ba5e7b668eb23
parent69d88aa3c882cfc28ff6f4f2e558e8058d331884 (diff)
mail/envelope: add a special class for headers
Handle multiple headers with ordering and case-insensitive operations.
-rw-r--r--alot/buffers/envelope.py9
-rw-r--r--alot/commands/envelope.py4
-rw-r--r--alot/mail/envelope.py136
3 files changed, 106 insertions, 43 deletions
diff --git a/alot/buffers/envelope.py b/alot/buffers/envelope.py
index 0bc7696f..871e88b5 100644
--- a/alot/buffers/envelope.py
+++ b/alot/buffers/envelope.py
@@ -119,13 +119,12 @@ class EnvelopeBuffer(Buffer):
def rebuild(self):
displayed_widgets = []
- hidden = settings.get('envelope_headers_blacklist')
+ hidden = set(map(str.lower, settings.get('envelope_headers_blacklist')))
# build lines
lines = []
- for (k, vlist) in self.envelope.headers.items():
- if (k not in hidden) or self.all_headers:
- for value in vlist:
- lines.append((k, value))
+ for k, v in self.envelope.headers.items():
+ if (k.lower() not in hidden) or self.all_headers:
+ lines.append((k, v))
# sign/encrypt lines
if self.envelope.sign:
diff --git a/alot/commands/envelope.py b/alot/commands/envelope.py
index 4b485425..5dce6b44 100644
--- a/alot/commands/envelope.py
+++ b/alot/commands/envelope.py
@@ -369,9 +369,9 @@ class EditCommand(Command):
headertext = ''
for key in edit_headers:
vlist = self.envelope.get_all(key)
- if not vlist:
+ if len(vlist) == 0:
# ensure editable headers are present in template
- vlist = ['']
+ vlist.append('')
else:
# remove to be edited lines from envelope
del self.envelope[key]
diff --git a/alot/mail/envelope.py b/alot/mail/envelope.py
index 55b3f7ff..655b5fe6 100644
--- a/alot/mail/envelope.py
+++ b/alot/mail/envelope.py
@@ -22,6 +22,94 @@ from ..errors import GPGProblem, GPGCode
charset.add_charset('utf-8', charset.QP, charset.QP, 'utf-8')
+class _EnvelopeHeaders:
+ """
+ A dict-like container for envelope headers.
+
+ Handles header-name case insensitivity and multiple headers.
+ """
+
+ _keys_lower = None
+ _keys = None
+ _vals = None
+
+ def __init__(self, data = None):
+ self.clear()
+
+ if data:
+ for key, val in data.items():
+ self.add(key, val)
+
+ def _key_to_idx(self, key):
+ key = key.lower()
+ if not key in self._keys_lower:
+ raise KeyError('No such header: ' + key)
+ return self._keys_lower.index(key)
+
+ def __setitem__(self, key, val):
+ try:
+ idx = self._key_to_idx(key)
+ self._keys[idx] = key
+ self._vals[idx] = val
+ except KeyError:
+ self.add(key, val)
+
+ def __getitem__(self, key):
+ idx = self._key_to_idx(key)
+ return self._vals[idx][0]
+
+ def __delitem__(self, key):
+ idx = self._key_to_idx(key)
+ del self._keys_lower[idx]
+ del self._keys[idx]
+ del self._vals[idx]
+
+ def __contains__(self, key):
+ return key.lower() in self._keys_lower
+
+ def get(self, key, fallback = None):
+ return self[key] if key in self else fallback
+
+ def get_all(self, key):
+ kl = key.lower()
+ ret = []
+
+ for idx in range(len(self._keys)):
+ if kl == self._keys_lower[idx]:
+ ret.append(self._vals[idx])
+
+ return ret
+
+ def add(self, key, val):
+ self._keys_lower.append(key.lower())
+ self._keys.append(key)
+ self._vals.append(val)
+
+ def __iter__(self):
+ return iter(self._keys)
+
+ def keys(self):
+ return self._keys
+ def values(self):
+ return self._vals
+ def items(self):
+ return zip(self.keys(), self.values())
+
+ def clear(self):
+ self._keys_lower = []
+ self._keys = []
+ self._vals = []
+
+ def copy(self):
+ ret = self.__class__()
+ for k, v in self.items():
+ ret.add(k, v)
+ return ret
+
+ def __str__(self):
+ return '\n'.join((k + ': ' + v\
+ for k, v in self.items()))
+
class Envelope:
"""
a message that is not yet sent and still editable.
@@ -77,9 +165,9 @@ class Envelope:
logging.debug('PARSED TEMPLATE: %s', template)
logging.debug('BODY: %s', self.body)
self.body = bodytext or ''
- # TODO: if this was as collections.defaultdict a number of methods
- # could be simplified.
- self.headers = headers or {}
+
+ self.headers = _EnvelopeHeaders(headers)
+
self.attachments = list(attachments) if attachments is not None else []
self.sign = sign
self.sign_key = sign_key
@@ -97,12 +185,7 @@ class Envelope:
return "Envelope (%s)\n%s" % (self.headers, self.body)
def __setitem__(self, name, val):
- """setter for header values. This allows adding header like so:
- envelope['Subject'] = 'sm\xf8rebr\xf8d'
- """
- if name not in self.headers:
- self.headers[name] = []
- self.headers[name].append(val)
+ self.headers[name] = val
if self.sent_time:
self.modified_since_sent = True
@@ -111,7 +194,7 @@ class Envelope:
"""getter for header values.
:raises: KeyError if undefined
"""
- return self.headers[name][0]
+ return self.headers[name]
def __delitem__(self, name):
del self.headers[name]
@@ -121,30 +204,12 @@ class Envelope:
def __contains__(self, name):
return name in self.headers
-
def get(self, key, fallback=None):
- """secure getter for header values that allows specifying a `fallback`
- return string (defaults to None). This returns the first matching value
- and doesn't raise KeyErrors"""
- if key in self.headers:
- value = self.headers[key][0]
- else:
- value = fallback
- return value
-
- def get_all(self, key, fallback=None):
- """returns all header values for given key"""
- if key in self.headers:
- value = self.headers[key]
- else:
- value = fallback or []
- return value
-
+ return self.headers.get(key, fallback)
+ def get_all(self, key):
+ return self.headers.get_all(key)
def add(self, key, value):
- """add header value"""
- if key not in self.headers:
- self.headers[key] = []
- self.headers[key].append(value)
+ self.headers.add(key, value)
if self.sent_time:
self.modified_since_sent = True
@@ -302,9 +367,8 @@ class Envelope:
headers['User-Agent'] = [uastring]
# copy headers from envelope to mail
- for k, vlist in headers.items():
- for v in vlist:
- mail.add_header(k, v)
+ for k, v in headers.items():
+ mail.add_header(k, v)
# as we are using MIMEPart instead of EmailMessage, set the
# MIME version manually
@@ -329,7 +393,7 @@ class Envelope:
self.modified_since_sent = True
if reset:
- self.headers = {}
+ self.headers.clear()
headerEndPos = 0
if not only_body: