Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions Lib/email/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,24 +546,31 @@ class _Accumulator(list):

def __init__(self, initial_size=0):
self._initial_size = initial_size
# The value of len(): _initial_size plus the length of all the
# parts, kept up to date by push(), pop(), pop_from() and reset().
# Summing the parts on every call makes folding quadratic.
self._len = initial_size
super().__init__()

def push(self, fws, string):
self.append((fws, string))
self._len += len(fws) + len(string)

def pop_from(self, i=0):
popped = self[i:]
self[i:] = []
self._len -= sum(len(fws) + len(part) for fws, part in popped)
return popped

def pop(self):
if self.part_count()==0:
return ('', '')
return super().pop()
fws, part = super().pop()
self._len -= len(fws) + len(part)
return fws, part

def __len__(self):
return sum((len(fws)+len(part) for fws, part in self),
self._initial_size)
return self._len

def __str__(self):
return EMPTYSTRING.join((EMPTYSTRING.join((fws, part))
Expand All @@ -574,6 +581,7 @@ def reset(self, startval=None):
startval = []
self[:] = startval
self._initial_size = 0
self._len = sum(len(fws) + len(part) for fws, part in self)

def is_onlyws(self):
return self._initial_size==0 and (not self or str(self).isspace())
Expand Down
29 changes: 29 additions & 0 deletions Lib/test/test_email/test_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,35 @@ def test_no_split_long_header(self):
h = Header('x' * 80)
eq(h.encode(), 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')

def test_unfolded_long_header(self):
eq = self.ndiffAssertEqual
value = ' '.join(f'<id{i}@example.com>' for i in range(2000))
h = Header(value, header_name='References')
eq(h.encode(maxlinelen=0), value)
msg = Message()
msg['References'] = value
eq(msg.as_string(), f'References: {value}\n\n')

def test_accumulator_length(self):
from email.header import _Accumulator
acc = _Accumulator(10)
self.assertEqual(len(acc), 10)
acc.push(' ', 'abc')
acc.push('\t', 'de')
acc.push('', 'f')
self.assertEqual(len(acc), 10 + 4 + 3 + 1)
self.assertEqual(acc.pop(), ('', 'f'))
self.assertEqual(len(acc), 10 + 4 + 3)
self.assertEqual(acc.pop_from(1), [('\t', 'de')])
self.assertEqual(len(acc), 10 + 4)
acc.reset([(' ', 'gh'), ('', 'ijk')])
self.assertEqual(len(acc), 6)
self.assertEqual(acc.part_count(), 2)
acc.reset()
self.assertEqual(len(acc), 0)
self.assertEqual(acc.pop(), ('', ''))
self.assertEqual(len(acc), 0)

def test_splitting_multiple_long_lines(self):
eq = self.ndiffAssertEqual
hstr = """\
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix quadratic time complexity in :meth:`email.header.Header.encode` when
folding long headers. This also affects :meth:`email.message.Message.as_string`.
Loading