diff -r 2b3fb2398f45 Lib/email/feedparser.py --- a/Lib/email/feedparser.py Wed Mar 16 22:11:09 2011 -0400 +++ b/Lib/email/feedparser.py Thu Mar 17 21:46:32 2011 -0400 @@ -447,7 +447,7 @@ if lastheader: # XXX reconsider the joining of folded lines lhdr = EMPTYSTRING.join(lastvalue)[:-1].rstrip('\r\n') - self._cur[lastheader] = lhdr + self._cur._raw_set(lastheader, lhdr) lastheader, lastvalue = '', [] # Check for envelope header, i.e. unix-from if line.startswith('From '): @@ -477,11 +477,11 @@ self._cur.defects.append(defect) continue lastheader = line[:i] - lastvalue = [line[i+1:].lstrip()] + lastvalue = [line[i+1:]] # Done with all the lines, so handle the last header. if lastheader: # XXX reconsider the joining of folded lines - self._cur[lastheader] = EMPTYSTRING.join(lastvalue).rstrip('\r\n') + self._cur._raw_set(lastheader, EMPTYSTRING.join(lastvalue).rstrip('\r\n')) class BytesFeedParser(FeedParser): diff -r 2b3fb2398f45 Lib/email/generator.py --- a/Lib/email/generator.py Wed Mar 16 22:11:09 2011 -0400 +++ b/Lib/email/generator.py Thu Mar 17 21:46:32 2011 -0400 @@ -167,10 +167,10 @@ # def _write_headers(self, msg): - for h, v in msg.items(): - self.write('%s: ' % h) + for h, v in msg._raw_items(): + self.write('%s:' % h) if isinstance(v, Header): - self.write(v.encode( + self.write(' ' + v.encode( maxlinelen=self._maxheaderlen, linesep=self._NL)+self._NL) else: # Header's got lots of smarts, so use it. @@ -358,7 +358,7 @@ # This is almost the same as the string version, except for handling # strings with 8bit bytes. for h, v in msg._headers: - self.write('%s: ' % h) + self.write('%s:' % h) if isinstance(v, Header): self.write(v.encode(maxlinelen=self._maxheaderlen)+NL) elif _has_surrogates(v): diff -r 2b3fb2398f45 Lib/email/header.py --- a/Lib/email/header.py Wed Mar 16 22:11:09 2011 -0400 +++ b/Lib/email/header.py Thu Mar 17 21:46:32 2011 -0400 @@ -159,7 +159,7 @@ class Header: def __init__(self, s=None, charset=None, maxlinelen=None, header_name=None, - continuation_ws=' ', errors='strict'): + continuation_ws=' ', errors='strict', *, _leader=''): """Create a MIME-compliant header that can contain many character sets. Optional s is the initial header value. If None, the initial header @@ -198,6 +198,7 @@ if maxlinelen is None: maxlinelen = MAXLINELEN self._maxlinelen = maxlinelen + self._leader = _leader if header_name is None: self._headerlen = 0 else: @@ -207,7 +208,7 @@ def __str__(self): """Return the string value of the header.""" self._normalize() - uchunks = [] + uchunks = [self._leader] lastcs = None for string, charset in self._chunks: # We must preserve spaces between encoded and non-encoded word @@ -218,7 +219,7 @@ if nextcs == _charset.UNKNOWN8BIT: original_bytes = string.encode('ascii', 'surrogateescape') string = original_bytes.decode('ascii', 'replace') - if uchunks: + if len(uchunks)>1: if lastcs not in (None, 'us-ascii'): if nextcs in (None, 'us-ascii'): uchunks.append(SPACE) diff -r 2b3fb2398f45 Lib/email/message.py --- a/Lib/email/message.py Wed Mar 16 22:11:09 2011 -0400 +++ b/Lib/email/message.py Thu Mar 17 21:46:32 2011 -0400 @@ -33,16 +33,25 @@ # Helper functions -def _sanitize_header(name, value): +def _sanitize_header(name, value, strip=True): # If the header value contains surrogates, return a Header using # the unknown-8bit charset to encode the bytes as encoded words. if not isinstance(value, str): # Assume it is already a header object return value if _has_surrogates(value): + if not strip and value[0]==' ': + value = value[1:] + leader = ' ' + else: + leader = '' + if strip and value[0]==' ': + value = value[1:] return header.Header(value, charset=_charset.UNKNOWN8BIT, - header_name=name) + header_name=name, _leader=leader) else: + if strip and value[0] == ' ': + return value[1:] return value def _splitparam(param): @@ -362,6 +371,12 @@ Note: this does not overwrite an existing header with the same field name. Use __delitem__() first to delete any existing headers. """ + if isinstance(val, header.Header): + self._raw_set(name, val) + return + self._raw_set(name, ' ' + val) + + def _raw_set(self, name, val): self._headers.append((name, val)) def __delitem__(self, name): @@ -413,7 +428,10 @@ """ return [(k, _sanitize_header(k, v)) for k, v in self._headers] - def get(self, name, failobj=None): + def _raw_items(self): + return [(k, _sanitize_header(k, v, strip=False)) for k, v in self._headers] + + def get(self, name, failobj=None, *, strip=True): """Get a header value. Like __getitem__() but return failobj instead of None when the field @@ -422,7 +440,7 @@ name = name.lower() for k, v in self._headers: if k.lower() == name: - return _sanitize_header(k, v) + return _sanitize_header(k, v, strip=strip) return failobj # @@ -475,7 +493,7 @@ parts.append(_formatparam(k.replace('_', '-'), v)) if _value is not None: parts.insert(0, _value) - self._headers.append((_name, SEMISPACE.join(parts))) + self._headers.append((_name, ' ' + SEMISPACE.join(parts))) def replace_header(self, _name, _value): """Replace a header. @@ -487,7 +505,7 @@ _name = _name.lower() for i, (k, v) in zip(range(len(self._headers)), self._headers): if k.lower() == _name: - self._headers[i] = (k, _value) + self._headers[i] = (k, ' ' + _value) break else: raise KeyError(_name) @@ -805,7 +823,7 @@ parts.append(k) else: parts.append('%s=%s' % (k, v)) - newheaders.append((h, SEMISPACE.join(parts))) + newheaders.append((h, ' ' + SEMISPACE.join(parts))) else: newheaders.append((h, v)) diff -r 2b3fb2398f45 Lib/email/test/test_email.py --- a/Lib/test/test_email/test_email.py Wed Mar 16 22:11:09 2011 -0400 +++ b/Lib/test/test_email/test_email.py Thu Mar 17 21:46:32 2011 -0400 @@ -44,6 +44,20 @@ EMPTYSTRING = '' SPACE = ' ' + #with monitor(email.message.Message, 'set_boundary'): +import contextlib +def probe(self, *args, **kw): + with open('/home/rdmurray/python/p33/debug.txt', 'a') as f: + print(repr(args), repr(kw), file=f) + res = self._save(*args, **kw) + print(res, file=f) + +@contextlib.contextmanager +def monitor(klass, method): + klass._save = getattr(klass, method) + setattr(klass, method, probe) + yield + setattr(klass, method, klass._save) def openfile(filename, *args, **kws): @@ -3762,7 +3776,7 @@ # the header doesn't reflect the input, but this is also the way # email 4.x behaved. At some point it would be nice to fix that. msg = email.message_from_string("EmptyHeader:") - self.assertEqual(str(msg), "EmptyHeader: \n\n") + self.assertEqual(str(msg), "EmptyHeader:\n\n")