blob: e4a86d49d838842724f78015280564da0be7dd2c [file] [log] [blame]
Benjamin Peterson46a99002010-01-09 18:45:30 +00001# Copyright (C) 2001-2010 Python Software Foundation
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002# Author: Barry Warsaw
3# Contact: email-sig@python.org
4
5"""Classes to generate plain text from a message object tree."""
6
R David Murray1b6c7242012-03-16 22:43:05 -04007__all__ = ['Generator', 'DecodedGenerator', 'BytesGenerator']
Guido van Rossum8b3febe2007-08-30 01:15:14 +00008
9import re
10import sys
11import time
12import random
13import warnings
14
R David Murray905c8c32014-02-08 11:48:20 -050015from copy import deepcopy
R. David Murray96fd54e2010-10-08 15:55:28 +000016from io import StringIO, BytesIO
R David Murrayc27e5222012-05-25 15:01:48 -040017from email._policybase import compat32
Guido van Rossum8b3febe2007-08-30 01:15:14 +000018from email.header import Header
R David Murrayc27e5222012-05-25 15:01:48 -040019from email.utils import _has_surrogates
R David Murray3edd22a2011-04-18 13:59:37 -040020import email.charset as _charset
Guido van Rossum8b3febe2007-08-30 01:15:14 +000021
22UNDERSCORE = '_'
R. David Murray8451c4b2010-10-23 22:19:56 +000023NL = '\n' # XXX: no longer used by the code below.
Guido van Rossum8b3febe2007-08-30 01:15:14 +000024
25fcre = re.compile(r'^From ', re.MULTILINE)
26
27
28
29class Generator:
30 """Generates output from a Message object tree.
31
32 This basic generator writes the message to the given file object as plain
33 text.
34 """
35 #
36 # Public interface
37 #
38
R David Murray3edd22a2011-04-18 13:59:37 -040039 def __init__(self, outfp, mangle_from_=True, maxheaderlen=None, *,
R David Murrayc27e5222012-05-25 15:01:48 -040040 policy=None):
Guido van Rossum8b3febe2007-08-30 01:15:14 +000041 """Create the generator for message flattening.
42
43 outfp is the output file-like object for writing the message to. It
44 must have a write() method.
45
46 Optional mangle_from_ is a flag that, when True (the default), escapes
47 From_ lines in the body of the message by putting a `>' in front of
48 them.
49
50 Optional maxheaderlen specifies the longest length for a non-continued
51 header. When a header line is longer (in characters, with tabs
52 expanded to 8 spaces) than maxheaderlen, the header will split as
53 defined in the Header class. Set maxheaderlen to zero to disable
54 header wrapping. The default is 78, as recommended (but not required)
55 by RFC 2822.
R David Murray3edd22a2011-04-18 13:59:37 -040056
57 The policy keyword specifies a policy object that controls a number of
58 aspects of the generator's operation. The default policy maintains
59 backward compatibility.
60
Guido van Rossum8b3febe2007-08-30 01:15:14 +000061 """
62 self._fp = outfp
63 self._mangle_from_ = mangle_from_
R David Murrayc27e5222012-05-25 15:01:48 -040064 self.maxheaderlen = maxheaderlen
R David Murray3edd22a2011-04-18 13:59:37 -040065 self.policy = policy
Guido van Rossum8b3febe2007-08-30 01:15:14 +000066
67 def write(self, s):
68 # Just delegate to the file object
69 self._fp.write(s)
70
R David Murray3edd22a2011-04-18 13:59:37 -040071 def flatten(self, msg, unixfrom=False, linesep=None):
R David Murraycd37dfc2011-03-14 18:35:56 -040072 r"""Print the message object tree rooted at msg to the output file
Guido van Rossum8b3febe2007-08-30 01:15:14 +000073 specified when the Generator instance was created.
74
75 unixfrom is a flag that forces the printing of a Unix From_ delimiter
76 before the first object in the message tree. If the original message
77 has no From_ delimiter, a `standard' one is crafted. By default, this
78 is False to inhibit the printing of any From_ delimiter.
79
80 Note that for subobjects, no From_ line is printed.
R. David Murray8451c4b2010-10-23 22:19:56 +000081
82 linesep specifies the characters used to indicate a new line in
R David Murray3edd22a2011-04-18 13:59:37 -040083 the output. The default value is determined by the policy.
R David Murraycd37dfc2011-03-14 18:35:56 -040084
Guido van Rossum8b3febe2007-08-30 01:15:14 +000085 """
R. David Murray8451c4b2010-10-23 22:19:56 +000086 # We use the _XXX constants for operating on data that comes directly
87 # from the msg, and _encoded_XXX constants for operating on data that
88 # has already been converted (to bytes in the BytesGenerator) and
89 # inserted into a temporary buffer.
R David Murrayc27e5222012-05-25 15:01:48 -040090 policy = msg.policy if self.policy is None else self.policy
91 if linesep is not None:
92 policy = policy.clone(linesep=linesep)
93 if self.maxheaderlen is not None:
94 policy = policy.clone(max_line_length=self.maxheaderlen)
95 self._NL = policy.linesep
R David Murray3edd22a2011-04-18 13:59:37 -040096 self._encoded_NL = self._encode(self._NL)
R. David Murray8451c4b2010-10-23 22:19:56 +000097 self._EMPTY = ''
98 self._encoded_EMTPY = self._encode('')
R David Murray0b6f6c82012-05-25 18:42:14 -040099 # Because we use clone (below) when we recursively process message
100 # subparts, and because clone uses the computed policy (not None),
101 # submessages will automatically get set to the computed policy when
102 # they are processed by this code.
103 old_gen_policy = self.policy
104 old_msg_policy = msg.policy
R David Murrayc27e5222012-05-25 15:01:48 -0400105 try:
106 self.policy = policy
R David Murray0b6f6c82012-05-25 18:42:14 -0400107 msg.policy = policy
R David Murrayc27e5222012-05-25 15:01:48 -0400108 if unixfrom:
109 ufrom = msg.get_unixfrom()
110 if not ufrom:
111 ufrom = 'From nobody ' + time.ctime(time.time())
112 self.write(ufrom + self._NL)
113 self._write(msg)
114 finally:
R David Murray0b6f6c82012-05-25 18:42:14 -0400115 self.policy = old_gen_policy
116 msg.policy = old_msg_policy
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000117
118 def clone(self, fp):
119 """Clone this generator with the exact same options."""
R David Murrayc27e5222012-05-25 15:01:48 -0400120 return self.__class__(fp,
121 self._mangle_from_,
122 None, # Use policy setting, which we've adjusted
123 policy=self.policy)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000124
125 #
126 # Protected interface - undocumented ;/
127 #
128
R. David Murray96fd54e2010-10-08 15:55:28 +0000129 # Note that we use 'self.write' when what we are writing is coming from
130 # the source, and self._fp.write when what we are writing is coming from a
131 # buffer (because the Bytes subclass has already had a chance to transform
132 # the data in its write method in that case). This is an entirely
133 # pragmatic split determined by experiment; we could be more general by
134 # always using write and having the Bytes subclass write method detect when
135 # it has already transformed the input; but, since this whole thing is a
136 # hack anyway this seems good enough.
137
R. David Murray8451c4b2010-10-23 22:19:56 +0000138 # Similarly, we have _XXX and _encoded_XXX attributes that are used on
139 # source and buffer data, respectively.
140 _encoded_EMPTY = ''
R. David Murray96fd54e2010-10-08 15:55:28 +0000141
142 def _new_buffer(self):
143 # BytesGenerator overrides this to return BytesIO.
144 return StringIO()
145
R. David Murray8451c4b2010-10-23 22:19:56 +0000146 def _encode(self, s):
147 # BytesGenerator overrides this to encode strings to bytes.
148 return s
149
R David Murraye67c6c52013-03-07 16:38:03 -0500150 def _write_lines(self, lines):
151 # We have to transform the line endings.
152 if not lines:
153 return
154 lines = lines.splitlines(True)
155 for line in lines[:-1]:
156 self.write(line.rstrip('\r\n'))
157 self.write(self._NL)
158 laststripped = lines[-1].rstrip('\r\n')
159 self.write(laststripped)
R David Murrayb9534f42013-03-07 18:15:13 -0500160 if len(lines[-1]) != len(laststripped):
R David Murraye67c6c52013-03-07 16:38:03 -0500161 self.write(self._NL)
162
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000163 def _write(self, msg):
164 # We can't write the headers yet because of the following scenario:
165 # say a multipart message includes the boundary string somewhere in
166 # its body. We'd have to calculate the new boundary /before/ we write
167 # the headers so that we can write the correct Content-Type:
168 # parameter.
169 #
170 # The way we do this, so as to make the _handle_*() methods simpler,
R. David Murray96fd54e2010-10-08 15:55:28 +0000171 # is to cache any subpart writes into a buffer. The we write the
172 # headers and the buffer contents. That way, subpart handlers can
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000173 # Do The Right Thing, and can still modify the Content-Type: header if
174 # necessary.
175 oldfp = self._fp
176 try:
R David Murray905c8c32014-02-08 11:48:20 -0500177 self._munge_cte = None
R. David Murray96fd54e2010-10-08 15:55:28 +0000178 self._fp = sfp = self._new_buffer()
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000179 self._dispatch(msg)
180 finally:
181 self._fp = oldfp
R David Murray905c8c32014-02-08 11:48:20 -0500182 munge_cte = self._munge_cte
183 del self._munge_cte
184 # If we munged the cte, copy the message again and re-fix the CTE.
185 if munge_cte:
186 msg = deepcopy(msg)
187 msg.replace_header('content-transfer-encoding', munge_cte[0])
188 msg.replace_header('content-type', munge_cte[1])
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000189 # Write the headers. First we see if the message object wants to
190 # handle that itself. If not, we'll do it generically.
191 meth = getattr(msg, '_write_headers', None)
192 if meth is None:
193 self._write_headers(msg)
194 else:
195 meth(self)
196 self._fp.write(sfp.getvalue())
197
198 def _dispatch(self, msg):
199 # Get the Content-Type: for the message, then try to dispatch to
200 # self._handle_<maintype>_<subtype>(). If there's no handler for the
201 # full MIME type, then dispatch to self._handle_<maintype>(). If
202 # that's missing too, then dispatch to self._writeBody().
203 main = msg.get_content_maintype()
204 sub = msg.get_content_subtype()
205 specific = UNDERSCORE.join((main, sub)).replace('-', '_')
206 meth = getattr(self, '_handle_' + specific, None)
207 if meth is None:
208 generic = main.replace('-', '_')
209 meth = getattr(self, '_handle_' + generic, None)
210 if meth is None:
211 meth = self._writeBody
212 meth(msg)
213
214 #
215 # Default handlers
216 #
217
218 def _write_headers(self, msg):
R David Murrayc27e5222012-05-25 15:01:48 -0400219 for h, v in msg.raw_items():
220 self.write(self.policy.fold(h, v))
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000221 # A blank line always separates headers from body
R. David Murray8451c4b2010-10-23 22:19:56 +0000222 self.write(self._NL)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000223
224 #
225 # Handlers for writing types and subtypes
226 #
227
228 def _handle_text(self, msg):
229 payload = msg.get_payload()
230 if payload is None:
231 return
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000232 if not isinstance(payload, str):
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000233 raise TypeError('string payload expected: %s' % type(payload))
R. David Murray96fd54e2010-10-08 15:55:28 +0000234 if _has_surrogates(msg._payload):
235 charset = msg.get_param('charset')
236 if charset is not None:
R David Murray905c8c32014-02-08 11:48:20 -0500237 # XXX: This copy stuff is an ugly hack to avoid modifying the
238 # existing message.
239 msg = deepcopy(msg)
R. David Murray96fd54e2010-10-08 15:55:28 +0000240 del msg['content-transfer-encoding']
241 msg.set_payload(payload, charset)
242 payload = msg.get_payload()
R David Murray905c8c32014-02-08 11:48:20 -0500243 self._munge_cte = (msg['content-transfer-encoding'],
244 msg['content-type'])
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000245 if self._mangle_from_:
246 payload = fcre.sub('>From ', payload)
R David Murraye67c6c52013-03-07 16:38:03 -0500247 self._write_lines(payload)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000248
249 # Default body handler
250 _writeBody = _handle_text
251
252 def _handle_multipart(self, msg):
253 # The trick here is to write out each part separately, merge them all
254 # together, and then make sure that the boundary we've chosen isn't
255 # present in the payload.
256 msgtexts = []
257 subparts = msg.get_payload()
258 if subparts is None:
259 subparts = []
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000260 elif isinstance(subparts, str):
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000261 # e.g. a non-strict parse of a message with no starting boundary.
R. David Murray96fd54e2010-10-08 15:55:28 +0000262 self.write(subparts)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000263 return
264 elif not isinstance(subparts, list):
265 # Scalar payload
266 subparts = [subparts]
267 for part in subparts:
R. David Murray96fd54e2010-10-08 15:55:28 +0000268 s = self._new_buffer()
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000269 g = self.clone(s)
R. David Murray8451c4b2010-10-23 22:19:56 +0000270 g.flatten(part, unixfrom=False, linesep=self._NL)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000271 msgtexts.append(s.getvalue())
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000272 # BAW: What about boundaries that are wrapped in double-quotes?
R. David Murray5260a9b2010-12-12 20:06:19 +0000273 boundary = msg.get_boundary()
274 if not boundary:
275 # Create a boundary that doesn't appear in any of the
276 # message texts.
277 alltext = self._encoded_NL.join(msgtexts)
R. David Murray73a559d2010-12-21 18:07:59 +0000278 boundary = self._make_boundary(alltext)
279 msg.set_boundary(boundary)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000280 # If there's a preamble, write it out, with a trailing CRLF
281 if msg.preamble is not None:
R David Murray6a31bc62012-07-22 21:47:53 -0400282 if self._mangle_from_:
283 preamble = fcre.sub('>From ', msg.preamble)
284 else:
285 preamble = msg.preamble
R David Murraye67c6c52013-03-07 16:38:03 -0500286 self._write_lines(preamble)
287 self.write(self._NL)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000288 # dash-boundary transport-padding CRLF
R. David Murray8451c4b2010-10-23 22:19:56 +0000289 self.write('--' + boundary + self._NL)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000290 # body-part
291 if msgtexts:
292 self._fp.write(msgtexts.pop(0))
293 # *encapsulation
294 # --> delimiter transport-padding
295 # --> CRLF body-part
296 for body_part in msgtexts:
297 # delimiter transport-padding CRLF
R. David Murray8451c4b2010-10-23 22:19:56 +0000298 self.write(self._NL + '--' + boundary + self._NL)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000299 # body-part
300 self._fp.write(body_part)
301 # close-delimiter transport-padding
R David Murraye9c31472014-02-08 17:54:56 -0500302 self.write(self._NL + '--' + boundary + '--' + self._NL)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000303 if msg.epilogue is not None:
R David Murray6a31bc62012-07-22 21:47:53 -0400304 if self._mangle_from_:
305 epilogue = fcre.sub('>From ', msg.epilogue)
306 else:
307 epilogue = msg.epilogue
R David Murraye67c6c52013-03-07 16:38:03 -0500308 self._write_lines(epilogue)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000309
R. David Murraya8f480f2010-01-16 18:30:03 +0000310 def _handle_multipart_signed(self, msg):
311 # The contents of signed parts has to stay unmodified in order to keep
312 # the signature intact per RFC1847 2.1, so we disable header wrapping.
313 # RDM: This isn't enough to completely preserve the part, but it helps.
R David Murrayc27e5222012-05-25 15:01:48 -0400314 p = self.policy
315 self.policy = p.clone(max_line_length=0)
R. David Murraya8f480f2010-01-16 18:30:03 +0000316 try:
R. David Murraya8f480f2010-01-16 18:30:03 +0000317 self._handle_multipart(msg)
318 finally:
R David Murrayc27e5222012-05-25 15:01:48 -0400319 self.policy = p
R. David Murraya8f480f2010-01-16 18:30:03 +0000320
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000321 def _handle_message_delivery_status(self, msg):
322 # We can't just write the headers directly to self's file object
323 # because this will leave an extra newline between the last header
324 # block and the boundary. Sigh.
325 blocks = []
326 for part in msg.get_payload():
R. David Murray96fd54e2010-10-08 15:55:28 +0000327 s = self._new_buffer()
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000328 g = self.clone(s)
R. David Murray719a4492010-11-21 16:53:48 +0000329 g.flatten(part, unixfrom=False, linesep=self._NL)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000330 text = s.getvalue()
R. David Murray8451c4b2010-10-23 22:19:56 +0000331 lines = text.split(self._encoded_NL)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000332 # Strip off the unnecessary trailing empty line
R. David Murray8451c4b2010-10-23 22:19:56 +0000333 if lines and lines[-1] == self._encoded_EMPTY:
334 blocks.append(self._encoded_NL.join(lines[:-1]))
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000335 else:
336 blocks.append(text)
337 # Now join all the blocks with an empty line. This has the lovely
338 # effect of separating each block with an empty line, but not adding
339 # an extra one after the last one.
R. David Murray8451c4b2010-10-23 22:19:56 +0000340 self._fp.write(self._encoded_NL.join(blocks))
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000341
342 def _handle_message(self, msg):
R. David Murray96fd54e2010-10-08 15:55:28 +0000343 s = self._new_buffer()
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000344 g = self.clone(s)
345 # The payload of a message/rfc822 part should be a multipart sequence
346 # of length 1. The zeroth element of the list should be the Message
347 # object for the subpart. Extract that object, stringify it, and
348 # write it out.
R. David Murray57c45ac2010-02-21 04:39:40 +0000349 # Except, it turns out, when it's a string instead, which happens when
350 # and only when HeaderParser is used on a message of mime type
351 # message/rfc822. Such messages are generated by, for example,
352 # Groupwise when forwarding unadorned messages. (Issue 7970.) So
353 # in that case we just emit the string body.
R David Murrayb35c8502011-04-13 16:46:05 -0400354 payload = msg._payload
R. David Murray57c45ac2010-02-21 04:39:40 +0000355 if isinstance(payload, list):
R. David Murray719a4492010-11-21 16:53:48 +0000356 g.flatten(msg.get_payload(0), unixfrom=False, linesep=self._NL)
R. David Murray57c45ac2010-02-21 04:39:40 +0000357 payload = s.getvalue()
R David Murrayb35c8502011-04-13 16:46:05 -0400358 else:
359 payload = self._encode(payload)
R. David Murray57c45ac2010-02-21 04:39:40 +0000360 self._fp.write(payload)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000361
R. David Murray96fd54e2010-10-08 15:55:28 +0000362 # This used to be a module level function; we use a classmethod for this
363 # and _compile_re so we can continue to provide the module level function
364 # for backward compatibility by doing
Ezio Melotti2af76da2013-08-10 18:47:07 +0300365 # _make_boundary = Generator._make_boundary
R. David Murray96fd54e2010-10-08 15:55:28 +0000366 # at the end of the module. It *is* internal, so we could drop that...
367 @classmethod
368 def _make_boundary(cls, text=None):
369 # Craft a random boundary. If text is given, ensure that the chosen
370 # boundary doesn't appear in the text.
371 token = random.randrange(sys.maxsize)
372 boundary = ('=' * 15) + (_fmt % token) + '=='
373 if text is None:
374 return boundary
375 b = boundary
376 counter = 0
377 while True:
378 cre = cls._compile_re('^--' + re.escape(b) + '(--)?$', re.MULTILINE)
379 if not cre.search(text):
380 break
381 b = boundary + '.' + str(counter)
382 counter += 1
383 return b
384
385 @classmethod
386 def _compile_re(cls, s, flags):
387 return re.compile(s, flags)
388
389
390class BytesGenerator(Generator):
391 """Generates a bytes version of a Message object tree.
392
393 Functionally identical to the base Generator except that the output is
394 bytes and not string. When surrogates were used in the input to encode
R David Murray3edd22a2011-04-18 13:59:37 -0400395 bytes, these are decoded back to bytes for output. If the policy has
R David Murrayc27e5222012-05-25 15:01:48 -0400396 cte_type set to 7bit, then the message is transformed such that the
397 non-ASCII bytes are properly content transfer encoded, using the charset
398 unknown-8bit.
R. David Murray96fd54e2010-10-08 15:55:28 +0000399
400 The outfp object must accept bytes in its write method.
401 """
402
R. David Murray8451c4b2010-10-23 22:19:56 +0000403 # Bytes versions of this constant for use in manipulating data from
R. David Murray96fd54e2010-10-08 15:55:28 +0000404 # the BytesIO buffer.
R. David Murray8451c4b2010-10-23 22:19:56 +0000405 _encoded_EMPTY = b''
R. David Murray96fd54e2010-10-08 15:55:28 +0000406
407 def write(self, s):
408 self._fp.write(s.encode('ascii', 'surrogateescape'))
409
410 def _new_buffer(self):
411 return BytesIO()
412
R. David Murray8451c4b2010-10-23 22:19:56 +0000413 def _encode(self, s):
414 return s.encode('ascii')
415
R. David Murray96fd54e2010-10-08 15:55:28 +0000416 def _write_headers(self, msg):
417 # This is almost the same as the string version, except for handling
418 # strings with 8bit bytes.
R David Murrayc27e5222012-05-25 15:01:48 -0400419 for h, v in msg.raw_items():
420 self._fp.write(self.policy.fold_binary(h, v))
R. David Murray96fd54e2010-10-08 15:55:28 +0000421 # A blank line always separates headers from body
R. David Murray8451c4b2010-10-23 22:19:56 +0000422 self.write(self._NL)
R. David Murray96fd54e2010-10-08 15:55:28 +0000423
424 def _handle_text(self, msg):
425 # If the string has surrogates the original source was bytes, so
426 # just write it back out.
R. David Murray7372a072011-01-26 21:21:32 +0000427 if msg._payload is None:
428 return
R David Murrayc27e5222012-05-25 15:01:48 -0400429 if _has_surrogates(msg._payload) and not self.policy.cte_type=='7bit':
R David Murray638d40b2012-08-24 11:14:13 -0400430 if self._mangle_from_:
431 msg._payload = fcre.sub(">From ", msg._payload)
R David Murraye67c6c52013-03-07 16:38:03 -0500432 self._write_lines(msg._payload)
R. David Murray96fd54e2010-10-08 15:55:28 +0000433 else:
434 super(BytesGenerator,self)._handle_text(msg)
435
R David Murrayceaa8b12013-02-09 13:02:58 -0500436 # Default body handler
437 _writeBody = _handle_text
438
R. David Murray96fd54e2010-10-08 15:55:28 +0000439 @classmethod
440 def _compile_re(cls, s, flags):
441 return re.compile(s.encode('ascii'), flags)
442
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000443
444
445_FMT = '[Non-text (%(type)s) part of message omitted, filename %(filename)s]'
446
447class DecodedGenerator(Generator):
R. David Murray70a99932010-10-01 20:38:33 +0000448 """Generates a text representation of a message.
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000449
450 Like the Generator base class, except that non-text parts are substituted
451 with a format string representing the part.
452 """
453 def __init__(self, outfp, mangle_from_=True, maxheaderlen=78, fmt=None):
454 """Like Generator.__init__() except that an additional optional
455 argument is allowed.
456
457 Walks through all subparts of a message. If the subpart is of main
458 type `text', then it prints the decoded payload of the subpart.
459
460 Otherwise, fmt is a format string that is used instead of the message
461 payload. fmt is expanded with the following keywords (in
462 %(keyword)s format):
463
464 type : Full MIME type of the non-text part
465 maintype : Main MIME type of the non-text part
466 subtype : Sub-MIME type of the non-text part
467 filename : Filename of the non-text part
468 description: Description associated with the non-text part
469 encoding : Content transfer encoding of the non-text part
470
471 The default value for fmt is None, meaning
472
473 [Non-text (%(type)s) part of message omitted, filename %(filename)s]
474 """
475 Generator.__init__(self, outfp, mangle_from_, maxheaderlen)
476 if fmt is None:
477 self._fmt = _FMT
478 else:
479 self._fmt = fmt
480
481 def _dispatch(self, msg):
482 for part in msg.walk():
483 maintype = part.get_content_maintype()
484 if maintype == 'text':
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000485 print(part.get_payload(decode=False), file=self)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000486 elif maintype == 'multipart':
487 # Just skip this
488 pass
489 else:
490 print(self._fmt % {
491 'type' : part.get_content_type(),
492 'maintype' : part.get_content_maintype(),
493 'subtype' : part.get_content_subtype(),
494 'filename' : part.get_filename('[no filename]'),
495 'description': part.get('Content-Description',
496 '[no description]'),
497 'encoding' : part.get('Content-Transfer-Encoding',
498 '[no encoding]'),
499 }, file=self)
500
501
502
R. David Murray96fd54e2010-10-08 15:55:28 +0000503# Helper used by Generator._make_boundary
Christian Heimesa37d4c62007-12-04 23:02:19 +0000504_width = len(repr(sys.maxsize-1))
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000505_fmt = '%%0%dd' % _width
506
R. David Murray96fd54e2010-10-08 15:55:28 +0000507# Backward compatibility
508_make_boundary = Generator._make_boundary