diff options
-rw-r--r-- | Doc/c-api/init.rst | 2 | ||||
-rw-r--r-- | Doc/library/email.header.rst | 14 | ||||
-rw-r--r-- | Doc/library/logging.config.rst | 23 | ||||
-rw-r--r-- | Lib/email/header.py | 289 | ||||
-rw-r--r-- | Lib/email/test/test_email.py | 149 | ||||
-rw-r--r-- | Lib/test/test_startfile.py | 5 | ||||
-rw-r--r-- | Misc/NEWS | 8 | ||||
-rw-r--r-- | Modules/signalmodule.c | 26 |
8 files changed, 299 insertions, 217 deletions
diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 623bd7cea3..535bf12364 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -908,7 +908,7 @@ a worker thread and the actual call than made at the earliest convenience by the main thread where it has possession of the global interpreter lock and can perform any Python API calls. -.. c:function:: void Py_AddPendingCall( int (*func)(void *, void *arg) ) +.. c:function:: void Py_AddPendingCall(int (*func)(void *), void *arg) .. index:: single: Py_AddPendingCall() diff --git a/Doc/library/email.header.rst b/Doc/library/email.header.rst index 29752c4b13..c385cf31c8 100644 --- a/Doc/library/email.header.rst +++ b/Doc/library/email.header.rst @@ -109,9 +109,17 @@ Here is the :class:`Header` class description: Encode a message header into an RFC-compliant format, possibly wrapping long lines and encapsulating non-ASCII parts in base64 or quoted-printable - encodings. Optional *splitchars* is a string containing characters to - split long ASCII lines on, in rough support of :rfc:`2822`'s *highest - level syntactic breaks*. This doesn't affect :rfc:`2047` encoded lines. + encodings. + + Optional *splitchars* is a string containing characters which should be + given extra weight by the splitting algorithm during normal header + wrapping. This is in very rough support of :RFC:`2822`\'s 'higher level + syntactic breaks': split points preceded by a splitchar are preferred + during line splitting, with the characters preferred in the order in + which they appear in the string. Space and tab may be included in the + string to indicate whether preference should be given to one over the + other as a split point when other split chars do not appear in the line + being split. Splitchars does not affect :RFC:`2047` encoded lines. *maxlinelen*, if given, overrides the instance's value for the maximum line length. diff --git a/Doc/library/logging.config.rst b/Doc/library/logging.config.rst index 143916fcc7..bb80a7f0fe 100644 --- a/Doc/library/logging.config.rst +++ b/Doc/library/logging.config.rst @@ -74,14 +74,25 @@ in :mod:`logging` itself) and defining handlers which are declared either in .. versionadded:: 3.2 -.. function:: fileConfig(fname[, defaults]) +.. function:: fileConfig(fname, defaults=None, disable_existing_loggers=True) - Reads the logging configuration from a :mod:`configparser`\-format file named - *fname*. This function can be called several times from an application, - allowing an end user to select from various pre-canned + Reads the logging configuration from a :mod:`configparser`\-format file + named *fname*. This function can be called several times from an + application, allowing an end user to select from various pre-canned configurations (if the developer provides a mechanism to present the choices - and load the chosen configuration). Defaults to be passed to the ConfigParser - can be specified in the *defaults* argument. + and load the chosen configuration). + + :param defaults: Defaults to be passed to the ConfigParser can be specified + in this argument. + + :param disable_existing_loggers: If specified as ``False``, loggers which + exist when this call is made are left + alone. The default is ``True`` because this + enables old behaviour in a backward- + compatible way. This behaviour is to + disable any existing loggers unless they or + their ancestors are explicitly named in the + logging configuration. .. function:: listen(port=DEFAULT_LOGGING_CONFIG_PORT) diff --git a/Lib/email/header.py b/Lib/email/header.py index 7e235df5a1..0a66df54ae 100644 --- a/Lib/email/header.py +++ b/Lib/email/header.py @@ -26,6 +26,7 @@ BSPACE = b' ' SPACE8 = ' ' * 8 EMPTYSTRING = '' MAXLINELEN = 78 +FWS = ' \t' USASCII = Charset('us-ascii') UTF8 = Charset('utf-8') @@ -299,9 +300,15 @@ class Header: name was specified at Header construction time. The default value for maxlinelen is determined at header construction time. - Optional splitchars is a string containing characters to split long - ASCII lines on, in rough support of RFC 2822's `highest level - syntactic breaks'. This doesn't affect RFC 2047 encoded lines. + Optional splitchars is a string containing characters which should be + given extra weight by the splitting algorithm during normal header + wrapping. This is in very rough support of RFC 2822's `higher level + syntactic breaks': split points preceded by a splitchar are preferred + during line splitting, with the characters preferred in the order in + which they appear in the string. Space and tab may be included in the + string to indicate whether preference should be given to one over the + other as a split point when other split chars do not appear in the line + being split. Splitchars does not affect RFC 2047 encoded lines. Optional linesep is a string to be used to separate the lines of the value. The default value is the most useful for typical @@ -320,13 +327,19 @@ class Header: self._continuation_ws, splitchars) for string, charset in self._chunks: lines = string.splitlines() - formatter.feed(lines[0] if lines else '', charset) + if lines: + formatter.feed('', lines[0], charset) + else: + formatter.feed('', '', charset) for line in lines[1:]: formatter.newline() if charset.header_encoding is not None: - formatter.feed(self._continuation_ws, USASCII) - line = ' ' + line.lstrip() - formatter.feed(line, charset) + formatter.feed(self._continuation_ws, ' ' + line.lstrip(), + charset) + else: + sline = line.lstrip() + fws = line[:len(line)-len(sline)] + formatter.feed(fws, sline, charset) if len(lines) > 1: formatter.newline() formatter.add_transition() @@ -360,7 +373,7 @@ class _ValueFormatter: def __init__(self, headerlen, maxlen, continuation_ws, splitchars): self._maxlen = maxlen self._continuation_ws = continuation_ws - self._continuation_ws_len = len(continuation_ws.replace('\t', SPACE8)) + self._continuation_ws_len = len(continuation_ws) self._splitchars = splitchars self._lines = [] self._current_line = _Accumulator(headerlen) @@ -374,43 +387,26 @@ class _ValueFormatter: def newline(self): end_of_line = self._current_line.pop() - if end_of_line is not None: - self._current_line.push(end_of_line) + if end_of_line != (' ', ''): + self._current_line.push(*end_of_line) if len(self._current_line) > 0: - self._lines.append(str(self._current_line)) + if self._current_line.is_onlyws(): + self._lines[-1] += str(self._current_line) + else: + self._lines.append(str(self._current_line)) self._current_line.reset() def add_transition(self): - self._current_line.push(None) - - def feed(self, string, charset): - # If the string itself fits on the current line in its encoded format, - # then add it now and be done with it. - encoded_string = charset.header_encode(string) - if len(encoded_string) + len(self._current_line) <= self._maxlen: - self._current_line.push(encoded_string) - return + self._current_line.push(' ', '') + + def feed(self, fws, string, charset): # If the charset has no header encoding (i.e. it is an ASCII encoding) # then we must split the header at the "highest level syntactic break" # possible. Note that we don't have a lot of smarts about field # syntax; we just try to break on semi-colons, then commas, then # whitespace. Eventually, this should be pluggable. if charset.header_encoding is None: - for ch in self._splitchars: - if ch in string: - break - else: - ch = None - # If there's no available split character then regardless of - # whether the string fits on the line, we have to put it on a line - # by itself. - if ch is None: - if not self._current_line.is_onlyws(): - self._lines.append(str(self._current_line)) - self._current_line.reset(self._continuation_ws) - self._current_line.push(encoded_string) - else: - self._ascii_split(string, ch) + self._ascii_split(fws, string, self._splitchars) return # Otherwise, we're doing either a Base64 or a quoted-printable # encoding which means we don't need to split the line on syntactic @@ -428,15 +424,14 @@ class _ValueFormatter: # There are no encoded lines, so we're done. return if first_line is not None: - self._current_line.push(first_line) - self._lines.append(str(self._current_line)) - self._current_line.reset(self._continuation_ws) + self._append_chunk(fws, first_line) try: last_line = encoded_lines.pop() except IndexError: # There was only one line. return - self._current_line.push(last_line) + self.newline() + self._current_line.push(self._continuation_ws, last_line) # Everything else are full lines in themselves. for line in encoded_lines: self._lines.append(self._continuation_ws + line) @@ -447,162 +442,96 @@ class _ValueFormatter: while True: yield self._maxlen - self._continuation_ws_len - def _ascii_split(self, string, ch): - holding = _Accumulator() - # Split the line on the split character, preserving it. If the split - # character is whitespace RFC 2822 $2.2.3 requires us to fold on the - # whitespace, so that the line leads with the original whitespace we - # split on. However, if a higher syntactic break is used instead - # (e.g. comma or semicolon), the folding should happen after the split - # character. But then in that case, we need to add our own - # continuation whitespace -- although won't that break unfolding? - for part, splitpart, nextpart in _spliterator(ch, string): - if not splitpart: - # No splitpart means this is the last chunk. Put this part - # either on the current line or the next line depending on - # whether it fits. - holding.push(part) - if len(holding) + len(self._current_line) <= self._maxlen: - # It fits, but we're done. - self._current_line.push(str(holding)) - else: - # It doesn't fit, but we're done. Before pushing a new - # line, watch out for the current line containing only - # whitespace. - holding.pop() - if self._current_line.is_onlyws() and holding.is_onlyws(): - # Don't start a new line. - holding.push(part) - part = None - self._current_line.push(str(holding)) - self._lines.append(str(self._current_line)) - if part is None: - self._current_line.reset() - else: - holding.reset(part) - self._current_line.reset(str(holding)) - return - elif not nextpart: - # There must be some trailing or duplicated split characters - # because we - # found a split character but no next part. In this case we - # must treat the thing to fit as the part + splitpart because - # if splitpart is whitespace it's not allowed to be the only - # thing on the line, and if it's not whitespace we must split - # after the syntactic break. - holding_prelen = len(holding) - holding.push(part + splitpart) - if len(holding) + len(self._current_line) <= self._maxlen: - self._current_line.push(str(holding)) - elif holding_prelen == 0: - # This is the only chunk left so it has to go on the - # current line. - self._current_line.push(str(holding)) - else: - save_part = holding.pop() - self._current_line.push(str(holding)) - self._lines.append(str(self._current_line)) - holding.reset(save_part) - self._current_line.reset(str(holding)) - holding.reset() - elif not part: - # We're leading with a split character. See if the splitpart - # and nextpart fits on the current line. - holding.push(splitpart + nextpart) - holding_len = len(holding) - # We know we're not leaving the nextpart on the stack. - holding.pop() - if holding_len + len(self._current_line) <= self._maxlen: - holding.push(splitpart) + def _ascii_split(self, fws, string, splitchars): + # The RFC 2822 header folding algorithm is simple in principle but + # complex in practice. Lines may be folded any place where "folding + # white space" appears by inserting a linesep character in front of the + # FWS. The complication is that not all spaces or tabs qualify as FWS, + # and we are also supposed to prefer to break at "higher level + # syntactic breaks". We can't do either of these without intimate + # knowledge of the structure of structured headers, which we don't have + # here. So the best we can do here is prefer to break at the specified + # splitchars, and hope that we don't choose any spaces or tabs that + # aren't legal FWS. (This is at least better than the old algorithm, + # where we would sometimes *introduce* FWS after a splitchar, or the + # algorithm before that, where we would turn all white space runs into + # single spaces or tabs.) + parts = re.split("(["+FWS+"]+)", fws+string) + if parts[0]: + parts[:0] = [''] + else: + parts.pop(0) + for fws, part in zip(*[iter(parts)]*2): + self._append_chunk(fws, part) + + def _append_chunk(self, fws, string): + self._current_line.push(fws, string) + if len(self._current_line) > self._maxlen: + # Find the best split point, working backward from the end. + # There might be none, on a long first line. + for ch in self._splitchars: + for i in range(self._current_line.part_count()-1, 0, -1): + if ch.isspace(): + fws = self._current_line[i][0] + if fws and fws[0]==ch: + break + prevpart = self._current_line[i-1][1] + if prevpart and prevpart[-1]==ch: + break else: - # It doesn't fit. Since there's no current part really - # the best we can do is start a new line and push the - # split part onto it. - self._current_line.push(str(holding)) - holding.reset() - if len(self._current_line) > 0 and self._lines: - self._lines.append(str(self._current_line)) - self._current_line.reset() - holding.push(splitpart) + continue + break else: - # All three parts are present. First let's see if all three - # parts will fit on the current line. If so, we don't need to - # split it. - holding.push(part + splitpart + nextpart) - holding_len = len(holding) - # Pop the part because we'll push nextpart on the next - # iteration through the loop. - holding.pop() - if holding_len + len(self._current_line) <= self._maxlen: - holding.push(part + splitpart) - else: - # The entire thing doesn't fit. See if we need to split - # before or after the split characters. - if splitpart.isspace(): - # Split before whitespace. Remember that the - # whitespace becomes the continuation whitespace of - # the next line so it goes to current_line not holding. - holding.push(part) - self._current_line.push(str(holding)) - holding.reset() - self._lines.append(str(self._current_line)) - self._current_line.reset(splitpart) - else: - # Split after non-whitespace. The continuation - # whitespace comes from the instance variable. - holding.push(part + splitpart) - self._current_line.push(str(holding)) - holding.reset() - self._lines.append(str(self._current_line)) - if nextpart[0].isspace(): - self._current_line.reset() - else: - self._current_line.reset(self._continuation_ws) - # Get the last of the holding part - self._current_line.push(str(holding)) + fws, part = self._current_line.pop() + if self._current_line._initial_size > 0: + # There will be a header, so leave it on a line by itself. + self.newline() + if not fws: + # We don't use continuation_ws here because the whitespace + # after a header should always be a space. + fws = ' ' + self._current_line.push(fws, part) + return + remainder = self._current_line.pop_from(i) + self._lines.append(str(self._current_line)) + self._current_line.reset(remainder) - -def _spliterator(character, string): - parts = list(reversed(re.split('(%s)' % character, string))) - while parts: - part = parts.pop() - splitparts = (parts.pop() if parts else None) - nextpart = (parts.pop() if parts else None) - yield (part, splitparts, nextpart) - if nextpart is not None: - parts.append(nextpart) - - -class _Accumulator: +class _Accumulator(list): + def __init__(self, initial_size=0): self._initial_size = initial_size - self._current = [] + super().__init__() - def push(self, string): - self._current.append(string) + def push(self, fws, string): + self.append((fws, string)) + + def pop_from(self, i=0): + popped = self[i:] + self[i:] = [] + return popped def pop(self): - if not self._current: - return None - return self._current.pop() + if self.part_count()==0: + return ('', '') + return super().pop() def __len__(self): - return sum(((1 if string is None else len(string)) - for string in self._current), + return sum((len(fws)+len(part) for fws, part in self), self._initial_size) def __str__(self): - if self._current and self._current[-1] is None: - self._current.pop() - return EMPTYSTRING.join((' ' if string is None else string) - for string in self._current) + return EMPTYSTRING.join((EMPTYSTRING.join((fws, part)) + for fws, part in self)) - def reset(self, string=None): - self._current = [] + def reset(self, startval=None): + if startval is None: + startval = [] + self[:] = startval self._initial_size = 0 - if string is not None: - self.push(string) def is_onlyws(self): - return len(self) == 0 or str(self).isspace() + return self._initial_size==0 and (not self or str(self).isspace()) + + def part_count(self): + return super().__len__() diff --git a/Lib/email/test/test_email.py b/Lib/email/test/test_email.py index 51fb2295a6..281a65a6da 100644 --- a/Lib/email/test/test_email.py +++ b/Lib/email/test/test_email.py @@ -660,6 +660,9 @@ class TestEncoders(unittest.TestCase): # Test long header wrapping class TestLongHeaders(TestEmailBase): + + maxDiff = None + def test_split_long_continuation(self): eq = self.ndiffAssertEqual msg = email.message_from_string("""\ @@ -868,14 +871,12 @@ Subject: the first part of this is short, eq = self.ndiffAssertEqual h = Header('; ' 'this_part_does_not_fit_within_maxlinelen_and_thus_should_' - 'be_on_a_line_all_by_itself;') + 'be_on_a_line_all_by_itself; ') eq(h.encode(), """\ ; - this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself;""") + this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself; """) def test_long_header_with_multiple_sequential_split_chars(self): - # Issue 11492 - eq = self.ndiffAssertEqual h = Header('This is a long line that has two whitespaces in a row. ' 'This used to cause truncation of the header when folded') @@ -883,6 +884,105 @@ Subject: the first part of this is short, This is a long line that has two whitespaces in a row. This used to cause truncation of the header when folded""") + def test_splitter_split_on_punctuation_only_if_fws(self): + eq = self.ndiffAssertEqual + h = Header('thisverylongheaderhas;semicolons;and,commas,but' + 'they;arenotlegal;fold,points') + eq(h.encode(), "thisverylongheaderhas;semicolons;and,commas,butthey;" + "arenotlegal;fold,points") + + def test_leading_splittable_in_the_middle_just_before_overlong_last_part(self): + eq = self.ndiffAssertEqual + h = Header('this is a test where we need to have more than one line ' + 'before; our final line that is just too big to fit;; ' + 'this_part_does_not_fit_within_maxlinelen_and_thus_should_' + 'be_on_a_line_all_by_itself;') + eq(h.encode(), """\ +this is a test where we need to have more than one line before; + our final line that is just too big to fit;; + this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself;""") + + def test_overlong_last_part_followed_by_split_point(self): + eq = self.ndiffAssertEqual + h = Header('this_part_does_not_fit_within_maxlinelen_and_thus_should_' + 'be_on_a_line_all_by_itself ') + eq(h.encode(), "this_part_does_not_fit_within_maxlinelen_and_thus_" + "should_be_on_a_line_all_by_itself ") + + def test_multiline_with_overlong_parts_separated_by_two_split_points(self): + eq = self.ndiffAssertEqual + h = Header('this_is_a__test_where_we_need_to_have_more_than_one_line_' + 'before_our_final_line_; ; ' + 'this_part_does_not_fit_within_maxlinelen_and_thus_should_' + 'be_on_a_line_all_by_itself; ') + eq(h.encode(), """\ +this_is_a__test_where_we_need_to_have_more_than_one_line_before_our_final_line_; + ; + this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself; """) + + def test_multiline_with_overlong_last_part_followed_by_split_point(self): + eq = self.ndiffAssertEqual + h = Header('this is a test where we need to have more than one line ' + 'before our final line; ; ' + 'this_part_does_not_fit_within_maxlinelen_and_thus_should_' + 'be_on_a_line_all_by_itself; ') + eq(h.encode(), """\ +this is a test where we need to have more than one line before our final line; + ; + this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself; """) + + def test_long_header_with_whitespace_runs(self): + eq = self.ndiffAssertEqual + msg = Message() + msg['From'] = 'test@dom.ain' + msg['References'] = SPACE.join(['<foo@dom.ain> '] * 10) + msg.set_payload('Test') + sfp = StringIO() + g = Generator(sfp) + g.flatten(msg) + eq(sfp.getvalue(), """\ +From: test@dom.ain +References: <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> + <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> + <foo@dom.ain> <foo@dom.ain>\x20\x20 + +Test""") + + def test_long_run_with_semi_header_splitter(self): + eq = self.ndiffAssertEqual + msg = Message() + msg['From'] = 'test@dom.ain' + msg['References'] = SPACE.join(['<foo@dom.ain>'] * 10) + '; abc' + msg.set_payload('Test') + sfp = StringIO() + g = Generator(sfp) + g.flatten(msg) + eq(sfp.getvalue(), """\ +From: test@dom.ain +References: <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> + <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> + <foo@dom.ain>; abc + +Test""") + + def test_splitter_split_on_punctuation_only_if_fws(self): + eq = self.ndiffAssertEqual + msg = Message() + msg['From'] = 'test@dom.ain' + msg['References'] = ('thisverylongheaderhas;semicolons;and,commas,but' + 'they;arenotlegal;fold,points') + msg.set_payload('Test') + sfp = StringIO() + g = Generator(sfp) + g.flatten(msg) + # XXX the space after the header should not be there. + eq(sfp.getvalue(), """\ +From: test@dom.ain +References:\x20 + thisverylongheaderhas;semicolons;and,commas,butthey;arenotlegal;fold,points + +Test""") + def test_no_split_long_header(self): eq = self.ndiffAssertEqual hstr = 'References: ' + 'x' * 80 @@ -973,7 +1073,7 @@ Reply-To: =?utf-8?q?Britische_Regierung_gibt_gr=C3=BCnes_Licht_f=C3=BCr_Offs?= def test_long_to_header(self): eq = self.ndiffAssertEqual to = ('"Someone Test #A" <someone@eecs.umich.edu>,' - '<someone@eecs.umich.edu>,' + '<someone@eecs.umich.edu>, ' '"Someone Test #B" <someone@umich.edu>, ' '"Someone Test #C" <someone@eecs.umich.edu>, ' '"Someone Test #D" <someone@eecs.umich.edu>') @@ -1028,9 +1128,11 @@ This is an example of string which has almost the limit of header length. msg['Received-2'] = h # This should be splitting on spaces not semicolons. self.ndiffAssertEqual(msg.as_string(maxheaderlen=78), """\ -Received-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; +Received-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by + hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; Wed, 05 Mar 2003 18:10:18 -0700 -Received-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; +Received-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by + hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; Wed, 05 Mar 2003 18:10:18 -0700 """) @@ -1043,12 +1145,14 @@ Received-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by hrothgar.la.m msg['Received-1'] = Header(h, header_name='Received-1', continuation_ws='\t') msg['Received-2'] = h - # XXX This should be splitting on spaces not commas. + # XXX The space after the ':' should not be there. self.ndiffAssertEqual(msg.as_string(maxheaderlen=78), """\ -Received-1: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner's message of \"Thu, - 6 Mar 2003 13:58:21 +0100\") -Received-2: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner's message of \"Thu, - 6 Mar 2003 13:58:21 +0100\") +Received-1:\x20 + <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David + Bremner's message of \"Thu, 6 Mar 2003 13:58:21 +0100\") +Received-2:\x20 + <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David + Bremner's message of \"Thu, 6 Mar 2003 13:58:21 +0100\") """) @@ -1060,8 +1164,9 @@ iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp""" msg['Face-1'] = t msg['Face-2'] = Header(t, header_name='Face-2') + msg['Face-3'] = ' ' + t # XXX This splitting is all wrong. It the first value line should be - # snug against the field name. + # snug against the field name or the space after the header not there. eq(msg.as_string(maxheaderlen=78), """\ Face-1:\x20 iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9 @@ -1069,6 +1174,9 @@ Face-1:\x20 Face-2:\x20 iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp +Face-3:\x20 + iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9 + locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp """) @@ -1080,8 +1188,8 @@ Face-2:\x20 'Wed, 16 Oct 2002 07:41:11 -0700') msg = email.message_from_string(m) eq(msg.as_string(maxheaderlen=78), '''\ -Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with Microsoft SMTPSVC(5.0.2195.4905); - Wed, 16 Oct 2002 07:41:11 -0700 +Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with + Microsoft SMTPSVC(5.0.2195.4905); Wed, 16 Oct 2002 07:41:11 -0700 ''') @@ -1095,9 +1203,11 @@ Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with Microsof msg['List'] = h msg['List'] = Header(h, header_name='List') eq(msg.as_string(maxheaderlen=78), """\ -List: List-Unsubscribe: <http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>, +List: List-Unsubscribe: + <http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>, <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe> -List: List-Unsubscribe: <http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>, +List: List-Unsubscribe: + <http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>, <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe> """) @@ -4113,6 +4223,11 @@ A very long line that must get split to something other than at the msg = email.message_from_string("EmptyHeader:") self.assertEqual(str(msg), "EmptyHeader: \n\n") + def test_encode_preserves_leading_ws_on_value(self): + msg = Message() + msg['SomeHeader'] = ' value with leading ws' + self.assertEqual(str(msg), "SomeHeader: value with leading ws\n\n") + # Test RFC 2231 header parameters (en/de)coding diff --git a/Lib/test/test_startfile.py b/Lib/test/test_startfile.py index 7a003eb54b..dd505bf8ce 100644 --- a/Lib/test/test_startfile.py +++ b/Lib/test/test_startfile.py @@ -11,6 +11,7 @@ import unittest from test import support import os from os import path +from time import sleep startfile = support.get_attribute(os, 'startfile') @@ -23,6 +24,10 @@ class TestCase(unittest.TestCase): empty = path.join(path.dirname(__file__), "empty.vbs") startfile(empty) startfile(empty, "open") + # Give the child process some time to exit before we finish. + # Otherwise the cleanup code will not be able to delete the cwd, + # because it is still in use. + sleep(0.1) def test_main(): support.run_unittest(TestCase) @@ -60,6 +60,12 @@ Core and Builtins Library ------- +- Issue #11768: The signal handler of the signal module only calls + Py_AddPendingCall() for the first signal to fix a deadlock on reentrant or + parallel calls. PyErr_SetInterrupt() writes also into the wake up file. + +- Issue #11492: fix several issues with header folding in the email package. + - Issue #11852: Add missing imports and update tests. - Issue #11875: collections.OrderedDict's __reduce__ was temporarily @@ -748,6 +754,8 @@ Tools/Demos Tests ----- +- Fix test_startfile to wait for child process to terminate before finishing. + - Issue #10822: Fix test_posix:test_getgroups failure under Solaris. Patch by Ross Lagerwall. diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index a93caadb05..d34f13246e 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -166,6 +166,20 @@ checksignals_witharg(void * unused) } static void +trip_signal(int sig_num) +{ + Handlers[sig_num].tripped = 1; + if (is_tripped) + return; + /* Set is_tripped after setting .tripped, as it gets + cleared in PyErr_CheckSignals() before .tripped. */ + is_tripped = 1; + Py_AddPendingCall(checksignals_witharg, NULL); + if (wakeup_fd != -1) + write(wakeup_fd, "\0", 1); +} + +static void signal_handler(int sig_num) { int save_errno = errno; @@ -182,13 +196,7 @@ signal_handler(int sig_num) if (getpid() == main_pid) #endif { - Handlers[sig_num].tripped = 1; - /* Set is_tripped after setting .tripped, as it gets - cleared in PyErr_CheckSignals() before .tripped. */ - is_tripped = 1; - Py_AddPendingCall(checksignals_witharg, NULL); - if (wakeup_fd != -1) - write(wakeup_fd, "\0", 1); + trip_signal(sig_num); } #ifndef HAVE_SIGACTION @@ -946,9 +954,7 @@ PyErr_CheckSignals(void) void PyErr_SetInterrupt(void) { - is_tripped = 1; - Handlers[SIGINT].tripped = 1; - Py_AddPendingCall((int (*)(void *))PyErr_CheckSignals, NULL); + trip_signal(SIGINT); } void |