annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/email/generator.py @ 69:33d812a61356

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 17:55:14 -0400
parents
children
rev   line source
jpayne@69 1 # Copyright (C) 2001-2010 Python Software Foundation
jpayne@69 2 # Author: Barry Warsaw
jpayne@69 3 # Contact: email-sig@python.org
jpayne@69 4
jpayne@69 5 """Classes to generate plain text from a message object tree."""
jpayne@69 6
jpayne@69 7 __all__ = ['Generator', 'DecodedGenerator', 'BytesGenerator']
jpayne@69 8
jpayne@69 9 import re
jpayne@69 10 import sys
jpayne@69 11 import time
jpayne@69 12 import random
jpayne@69 13
jpayne@69 14 from copy import deepcopy
jpayne@69 15 from io import StringIO, BytesIO
jpayne@69 16 from email.utils import _has_surrogates
jpayne@69 17
jpayne@69 18 UNDERSCORE = '_'
jpayne@69 19 NL = '\n' # XXX: no longer used by the code below.
jpayne@69 20
jpayne@69 21 NLCRE = re.compile(r'\r\n|\r|\n')
jpayne@69 22 fcre = re.compile(r'^From ', re.MULTILINE)
jpayne@69 23
jpayne@69 24
jpayne@69 25
jpayne@69 26 class Generator:
jpayne@69 27 """Generates output from a Message object tree.
jpayne@69 28
jpayne@69 29 This basic generator writes the message to the given file object as plain
jpayne@69 30 text.
jpayne@69 31 """
jpayne@69 32 #
jpayne@69 33 # Public interface
jpayne@69 34 #
jpayne@69 35
jpayne@69 36 def __init__(self, outfp, mangle_from_=None, maxheaderlen=None, *,
jpayne@69 37 policy=None):
jpayne@69 38 """Create the generator for message flattening.
jpayne@69 39
jpayne@69 40 outfp is the output file-like object for writing the message to. It
jpayne@69 41 must have a write() method.
jpayne@69 42
jpayne@69 43 Optional mangle_from_ is a flag that, when True (the default if policy
jpayne@69 44 is not set), escapes From_ lines in the body of the message by putting
jpayne@69 45 a `>' in front of them.
jpayne@69 46
jpayne@69 47 Optional maxheaderlen specifies the longest length for a non-continued
jpayne@69 48 header. When a header line is longer (in characters, with tabs
jpayne@69 49 expanded to 8 spaces) than maxheaderlen, the header will split as
jpayne@69 50 defined in the Header class. Set maxheaderlen to zero to disable
jpayne@69 51 header wrapping. The default is 78, as recommended (but not required)
jpayne@69 52 by RFC 2822.
jpayne@69 53
jpayne@69 54 The policy keyword specifies a policy object that controls a number of
jpayne@69 55 aspects of the generator's operation. If no policy is specified,
jpayne@69 56 the policy associated with the Message object passed to the
jpayne@69 57 flatten method is used.
jpayne@69 58
jpayne@69 59 """
jpayne@69 60
jpayne@69 61 if mangle_from_ is None:
jpayne@69 62 mangle_from_ = True if policy is None else policy.mangle_from_
jpayne@69 63 self._fp = outfp
jpayne@69 64 self._mangle_from_ = mangle_from_
jpayne@69 65 self.maxheaderlen = maxheaderlen
jpayne@69 66 self.policy = policy
jpayne@69 67
jpayne@69 68 def write(self, s):
jpayne@69 69 # Just delegate to the file object
jpayne@69 70 self._fp.write(s)
jpayne@69 71
jpayne@69 72 def flatten(self, msg, unixfrom=False, linesep=None):
jpayne@69 73 r"""Print the message object tree rooted at msg to the output file
jpayne@69 74 specified when the Generator instance was created.
jpayne@69 75
jpayne@69 76 unixfrom is a flag that forces the printing of a Unix From_ delimiter
jpayne@69 77 before the first object in the message tree. If the original message
jpayne@69 78 has no From_ delimiter, a `standard' one is crafted. By default, this
jpayne@69 79 is False to inhibit the printing of any From_ delimiter.
jpayne@69 80
jpayne@69 81 Note that for subobjects, no From_ line is printed.
jpayne@69 82
jpayne@69 83 linesep specifies the characters used to indicate a new line in
jpayne@69 84 the output. The default value is determined by the policy specified
jpayne@69 85 when the Generator instance was created or, if none was specified,
jpayne@69 86 from the policy associated with the msg.
jpayne@69 87
jpayne@69 88 """
jpayne@69 89 # We use the _XXX constants for operating on data that comes directly
jpayne@69 90 # from the msg, and _encoded_XXX constants for operating on data that
jpayne@69 91 # has already been converted (to bytes in the BytesGenerator) and
jpayne@69 92 # inserted into a temporary buffer.
jpayne@69 93 policy = msg.policy if self.policy is None else self.policy
jpayne@69 94 if linesep is not None:
jpayne@69 95 policy = policy.clone(linesep=linesep)
jpayne@69 96 if self.maxheaderlen is not None:
jpayne@69 97 policy = policy.clone(max_line_length=self.maxheaderlen)
jpayne@69 98 self._NL = policy.linesep
jpayne@69 99 self._encoded_NL = self._encode(self._NL)
jpayne@69 100 self._EMPTY = ''
jpayne@69 101 self._encoded_EMPTY = self._encode(self._EMPTY)
jpayne@69 102 # Because we use clone (below) when we recursively process message
jpayne@69 103 # subparts, and because clone uses the computed policy (not None),
jpayne@69 104 # submessages will automatically get set to the computed policy when
jpayne@69 105 # they are processed by this code.
jpayne@69 106 old_gen_policy = self.policy
jpayne@69 107 old_msg_policy = msg.policy
jpayne@69 108 try:
jpayne@69 109 self.policy = policy
jpayne@69 110 msg.policy = policy
jpayne@69 111 if unixfrom:
jpayne@69 112 ufrom = msg.get_unixfrom()
jpayne@69 113 if not ufrom:
jpayne@69 114 ufrom = 'From nobody ' + time.ctime(time.time())
jpayne@69 115 self.write(ufrom + self._NL)
jpayne@69 116 self._write(msg)
jpayne@69 117 finally:
jpayne@69 118 self.policy = old_gen_policy
jpayne@69 119 msg.policy = old_msg_policy
jpayne@69 120
jpayne@69 121 def clone(self, fp):
jpayne@69 122 """Clone this generator with the exact same options."""
jpayne@69 123 return self.__class__(fp,
jpayne@69 124 self._mangle_from_,
jpayne@69 125 None, # Use policy setting, which we've adjusted
jpayne@69 126 policy=self.policy)
jpayne@69 127
jpayne@69 128 #
jpayne@69 129 # Protected interface - undocumented ;/
jpayne@69 130 #
jpayne@69 131
jpayne@69 132 # Note that we use 'self.write' when what we are writing is coming from
jpayne@69 133 # the source, and self._fp.write when what we are writing is coming from a
jpayne@69 134 # buffer (because the Bytes subclass has already had a chance to transform
jpayne@69 135 # the data in its write method in that case). This is an entirely
jpayne@69 136 # pragmatic split determined by experiment; we could be more general by
jpayne@69 137 # always using write and having the Bytes subclass write method detect when
jpayne@69 138 # it has already transformed the input; but, since this whole thing is a
jpayne@69 139 # hack anyway this seems good enough.
jpayne@69 140
jpayne@69 141 def _new_buffer(self):
jpayne@69 142 # BytesGenerator overrides this to return BytesIO.
jpayne@69 143 return StringIO()
jpayne@69 144
jpayne@69 145 def _encode(self, s):
jpayne@69 146 # BytesGenerator overrides this to encode strings to bytes.
jpayne@69 147 return s
jpayne@69 148
jpayne@69 149 def _write_lines(self, lines):
jpayne@69 150 # We have to transform the line endings.
jpayne@69 151 if not lines:
jpayne@69 152 return
jpayne@69 153 lines = NLCRE.split(lines)
jpayne@69 154 for line in lines[:-1]:
jpayne@69 155 self.write(line)
jpayne@69 156 self.write(self._NL)
jpayne@69 157 if lines[-1]:
jpayne@69 158 self.write(lines[-1])
jpayne@69 159 # XXX logic tells me this else should be needed, but the tests fail
jpayne@69 160 # with it and pass without it. (NLCRE.split ends with a blank element
jpayne@69 161 # if and only if there was a trailing newline.)
jpayne@69 162 #else:
jpayne@69 163 # self.write(self._NL)
jpayne@69 164
jpayne@69 165 def _write(self, msg):
jpayne@69 166 # We can't write the headers yet because of the following scenario:
jpayne@69 167 # say a multipart message includes the boundary string somewhere in
jpayne@69 168 # its body. We'd have to calculate the new boundary /before/ we write
jpayne@69 169 # the headers so that we can write the correct Content-Type:
jpayne@69 170 # parameter.
jpayne@69 171 #
jpayne@69 172 # The way we do this, so as to make the _handle_*() methods simpler,
jpayne@69 173 # is to cache any subpart writes into a buffer. The we write the
jpayne@69 174 # headers and the buffer contents. That way, subpart handlers can
jpayne@69 175 # Do The Right Thing, and can still modify the Content-Type: header if
jpayne@69 176 # necessary.
jpayne@69 177 oldfp = self._fp
jpayne@69 178 try:
jpayne@69 179 self._munge_cte = None
jpayne@69 180 self._fp = sfp = self._new_buffer()
jpayne@69 181 self._dispatch(msg)
jpayne@69 182 finally:
jpayne@69 183 self._fp = oldfp
jpayne@69 184 munge_cte = self._munge_cte
jpayne@69 185 del self._munge_cte
jpayne@69 186 # If we munged the cte, copy the message again and re-fix the CTE.
jpayne@69 187 if munge_cte:
jpayne@69 188 msg = deepcopy(msg)
jpayne@69 189 msg.replace_header('content-transfer-encoding', munge_cte[0])
jpayne@69 190 msg.replace_header('content-type', munge_cte[1])
jpayne@69 191 # Write the headers. First we see if the message object wants to
jpayne@69 192 # handle that itself. If not, we'll do it generically.
jpayne@69 193 meth = getattr(msg, '_write_headers', None)
jpayne@69 194 if meth is None:
jpayne@69 195 self._write_headers(msg)
jpayne@69 196 else:
jpayne@69 197 meth(self)
jpayne@69 198 self._fp.write(sfp.getvalue())
jpayne@69 199
jpayne@69 200 def _dispatch(self, msg):
jpayne@69 201 # Get the Content-Type: for the message, then try to dispatch to
jpayne@69 202 # self._handle_<maintype>_<subtype>(). If there's no handler for the
jpayne@69 203 # full MIME type, then dispatch to self._handle_<maintype>(). If
jpayne@69 204 # that's missing too, then dispatch to self._writeBody().
jpayne@69 205 main = msg.get_content_maintype()
jpayne@69 206 sub = msg.get_content_subtype()
jpayne@69 207 specific = UNDERSCORE.join((main, sub)).replace('-', '_')
jpayne@69 208 meth = getattr(self, '_handle_' + specific, None)
jpayne@69 209 if meth is None:
jpayne@69 210 generic = main.replace('-', '_')
jpayne@69 211 meth = getattr(self, '_handle_' + generic, None)
jpayne@69 212 if meth is None:
jpayne@69 213 meth = self._writeBody
jpayne@69 214 meth(msg)
jpayne@69 215
jpayne@69 216 #
jpayne@69 217 # Default handlers
jpayne@69 218 #
jpayne@69 219
jpayne@69 220 def _write_headers(self, msg):
jpayne@69 221 for h, v in msg.raw_items():
jpayne@69 222 self.write(self.policy.fold(h, v))
jpayne@69 223 # A blank line always separates headers from body
jpayne@69 224 self.write(self._NL)
jpayne@69 225
jpayne@69 226 #
jpayne@69 227 # Handlers for writing types and subtypes
jpayne@69 228 #
jpayne@69 229
jpayne@69 230 def _handle_text(self, msg):
jpayne@69 231 payload = msg.get_payload()
jpayne@69 232 if payload is None:
jpayne@69 233 return
jpayne@69 234 if not isinstance(payload, str):
jpayne@69 235 raise TypeError('string payload expected: %s' % type(payload))
jpayne@69 236 if _has_surrogates(msg._payload):
jpayne@69 237 charset = msg.get_param('charset')
jpayne@69 238 if charset is not None:
jpayne@69 239 # XXX: This copy stuff is an ugly hack to avoid modifying the
jpayne@69 240 # existing message.
jpayne@69 241 msg = deepcopy(msg)
jpayne@69 242 del msg['content-transfer-encoding']
jpayne@69 243 msg.set_payload(payload, charset)
jpayne@69 244 payload = msg.get_payload()
jpayne@69 245 self._munge_cte = (msg['content-transfer-encoding'],
jpayne@69 246 msg['content-type'])
jpayne@69 247 if self._mangle_from_:
jpayne@69 248 payload = fcre.sub('>From ', payload)
jpayne@69 249 self._write_lines(payload)
jpayne@69 250
jpayne@69 251 # Default body handler
jpayne@69 252 _writeBody = _handle_text
jpayne@69 253
jpayne@69 254 def _handle_multipart(self, msg):
jpayne@69 255 # The trick here is to write out each part separately, merge them all
jpayne@69 256 # together, and then make sure that the boundary we've chosen isn't
jpayne@69 257 # present in the payload.
jpayne@69 258 msgtexts = []
jpayne@69 259 subparts = msg.get_payload()
jpayne@69 260 if subparts is None:
jpayne@69 261 subparts = []
jpayne@69 262 elif isinstance(subparts, str):
jpayne@69 263 # e.g. a non-strict parse of a message with no starting boundary.
jpayne@69 264 self.write(subparts)
jpayne@69 265 return
jpayne@69 266 elif not isinstance(subparts, list):
jpayne@69 267 # Scalar payload
jpayne@69 268 subparts = [subparts]
jpayne@69 269 for part in subparts:
jpayne@69 270 s = self._new_buffer()
jpayne@69 271 g = self.clone(s)
jpayne@69 272 g.flatten(part, unixfrom=False, linesep=self._NL)
jpayne@69 273 msgtexts.append(s.getvalue())
jpayne@69 274 # BAW: What about boundaries that are wrapped in double-quotes?
jpayne@69 275 boundary = msg.get_boundary()
jpayne@69 276 if not boundary:
jpayne@69 277 # Create a boundary that doesn't appear in any of the
jpayne@69 278 # message texts.
jpayne@69 279 alltext = self._encoded_NL.join(msgtexts)
jpayne@69 280 boundary = self._make_boundary(alltext)
jpayne@69 281 msg.set_boundary(boundary)
jpayne@69 282 # If there's a preamble, write it out, with a trailing CRLF
jpayne@69 283 if msg.preamble is not None:
jpayne@69 284 if self._mangle_from_:
jpayne@69 285 preamble = fcre.sub('>From ', msg.preamble)
jpayne@69 286 else:
jpayne@69 287 preamble = msg.preamble
jpayne@69 288 self._write_lines(preamble)
jpayne@69 289 self.write(self._NL)
jpayne@69 290 # dash-boundary transport-padding CRLF
jpayne@69 291 self.write('--' + boundary + self._NL)
jpayne@69 292 # body-part
jpayne@69 293 if msgtexts:
jpayne@69 294 self._fp.write(msgtexts.pop(0))
jpayne@69 295 # *encapsulation
jpayne@69 296 # --> delimiter transport-padding
jpayne@69 297 # --> CRLF body-part
jpayne@69 298 for body_part in msgtexts:
jpayne@69 299 # delimiter transport-padding CRLF
jpayne@69 300 self.write(self._NL + '--' + boundary + self._NL)
jpayne@69 301 # body-part
jpayne@69 302 self._fp.write(body_part)
jpayne@69 303 # close-delimiter transport-padding
jpayne@69 304 self.write(self._NL + '--' + boundary + '--' + self._NL)
jpayne@69 305 if msg.epilogue is not None:
jpayne@69 306 if self._mangle_from_:
jpayne@69 307 epilogue = fcre.sub('>From ', msg.epilogue)
jpayne@69 308 else:
jpayne@69 309 epilogue = msg.epilogue
jpayne@69 310 self._write_lines(epilogue)
jpayne@69 311
jpayne@69 312 def _handle_multipart_signed(self, msg):
jpayne@69 313 # The contents of signed parts has to stay unmodified in order to keep
jpayne@69 314 # the signature intact per RFC1847 2.1, so we disable header wrapping.
jpayne@69 315 # RDM: This isn't enough to completely preserve the part, but it helps.
jpayne@69 316 p = self.policy
jpayne@69 317 self.policy = p.clone(max_line_length=0)
jpayne@69 318 try:
jpayne@69 319 self._handle_multipart(msg)
jpayne@69 320 finally:
jpayne@69 321 self.policy = p
jpayne@69 322
jpayne@69 323 def _handle_message_delivery_status(self, msg):
jpayne@69 324 # We can't just write the headers directly to self's file object
jpayne@69 325 # because this will leave an extra newline between the last header
jpayne@69 326 # block and the boundary. Sigh.
jpayne@69 327 blocks = []
jpayne@69 328 for part in msg.get_payload():
jpayne@69 329 s = self._new_buffer()
jpayne@69 330 g = self.clone(s)
jpayne@69 331 g.flatten(part, unixfrom=False, linesep=self._NL)
jpayne@69 332 text = s.getvalue()
jpayne@69 333 lines = text.split(self._encoded_NL)
jpayne@69 334 # Strip off the unnecessary trailing empty line
jpayne@69 335 if lines and lines[-1] == self._encoded_EMPTY:
jpayne@69 336 blocks.append(self._encoded_NL.join(lines[:-1]))
jpayne@69 337 else:
jpayne@69 338 blocks.append(text)
jpayne@69 339 # Now join all the blocks with an empty line. This has the lovely
jpayne@69 340 # effect of separating each block with an empty line, but not adding
jpayne@69 341 # an extra one after the last one.
jpayne@69 342 self._fp.write(self._encoded_NL.join(blocks))
jpayne@69 343
jpayne@69 344 def _handle_message(self, msg):
jpayne@69 345 s = self._new_buffer()
jpayne@69 346 g = self.clone(s)
jpayne@69 347 # The payload of a message/rfc822 part should be a multipart sequence
jpayne@69 348 # of length 1. The zeroth element of the list should be the Message
jpayne@69 349 # object for the subpart. Extract that object, stringify it, and
jpayne@69 350 # write it out.
jpayne@69 351 # Except, it turns out, when it's a string instead, which happens when
jpayne@69 352 # and only when HeaderParser is used on a message of mime type
jpayne@69 353 # message/rfc822. Such messages are generated by, for example,
jpayne@69 354 # Groupwise when forwarding unadorned messages. (Issue 7970.) So
jpayne@69 355 # in that case we just emit the string body.
jpayne@69 356 payload = msg._payload
jpayne@69 357 if isinstance(payload, list):
jpayne@69 358 g.flatten(msg.get_payload(0), unixfrom=False, linesep=self._NL)
jpayne@69 359 payload = s.getvalue()
jpayne@69 360 else:
jpayne@69 361 payload = self._encode(payload)
jpayne@69 362 self._fp.write(payload)
jpayne@69 363
jpayne@69 364 # This used to be a module level function; we use a classmethod for this
jpayne@69 365 # and _compile_re so we can continue to provide the module level function
jpayne@69 366 # for backward compatibility by doing
jpayne@69 367 # _make_boundary = Generator._make_boundary
jpayne@69 368 # at the end of the module. It *is* internal, so we could drop that...
jpayne@69 369 @classmethod
jpayne@69 370 def _make_boundary(cls, text=None):
jpayne@69 371 # Craft a random boundary. If text is given, ensure that the chosen
jpayne@69 372 # boundary doesn't appear in the text.
jpayne@69 373 token = random.randrange(sys.maxsize)
jpayne@69 374 boundary = ('=' * 15) + (_fmt % token) + '=='
jpayne@69 375 if text is None:
jpayne@69 376 return boundary
jpayne@69 377 b = boundary
jpayne@69 378 counter = 0
jpayne@69 379 while True:
jpayne@69 380 cre = cls._compile_re('^--' + re.escape(b) + '(--)?$', re.MULTILINE)
jpayne@69 381 if not cre.search(text):
jpayne@69 382 break
jpayne@69 383 b = boundary + '.' + str(counter)
jpayne@69 384 counter += 1
jpayne@69 385 return b
jpayne@69 386
jpayne@69 387 @classmethod
jpayne@69 388 def _compile_re(cls, s, flags):
jpayne@69 389 return re.compile(s, flags)
jpayne@69 390
jpayne@69 391
jpayne@69 392 class BytesGenerator(Generator):
jpayne@69 393 """Generates a bytes version of a Message object tree.
jpayne@69 394
jpayne@69 395 Functionally identical to the base Generator except that the output is
jpayne@69 396 bytes and not string. When surrogates were used in the input to encode
jpayne@69 397 bytes, these are decoded back to bytes for output. If the policy has
jpayne@69 398 cte_type set to 7bit, then the message is transformed such that the
jpayne@69 399 non-ASCII bytes are properly content transfer encoded, using the charset
jpayne@69 400 unknown-8bit.
jpayne@69 401
jpayne@69 402 The outfp object must accept bytes in its write method.
jpayne@69 403 """
jpayne@69 404
jpayne@69 405 def write(self, s):
jpayne@69 406 self._fp.write(s.encode('ascii', 'surrogateescape'))
jpayne@69 407
jpayne@69 408 def _new_buffer(self):
jpayne@69 409 return BytesIO()
jpayne@69 410
jpayne@69 411 def _encode(self, s):
jpayne@69 412 return s.encode('ascii')
jpayne@69 413
jpayne@69 414 def _write_headers(self, msg):
jpayne@69 415 # This is almost the same as the string version, except for handling
jpayne@69 416 # strings with 8bit bytes.
jpayne@69 417 for h, v in msg.raw_items():
jpayne@69 418 self._fp.write(self.policy.fold_binary(h, v))
jpayne@69 419 # A blank line always separates headers from body
jpayne@69 420 self.write(self._NL)
jpayne@69 421
jpayne@69 422 def _handle_text(self, msg):
jpayne@69 423 # If the string has surrogates the original source was bytes, so
jpayne@69 424 # just write it back out.
jpayne@69 425 if msg._payload is None:
jpayne@69 426 return
jpayne@69 427 if _has_surrogates(msg._payload) and not self.policy.cte_type=='7bit':
jpayne@69 428 if self._mangle_from_:
jpayne@69 429 msg._payload = fcre.sub(">From ", msg._payload)
jpayne@69 430 self._write_lines(msg._payload)
jpayne@69 431 else:
jpayne@69 432 super(BytesGenerator,self)._handle_text(msg)
jpayne@69 433
jpayne@69 434 # Default body handler
jpayne@69 435 _writeBody = _handle_text
jpayne@69 436
jpayne@69 437 @classmethod
jpayne@69 438 def _compile_re(cls, s, flags):
jpayne@69 439 return re.compile(s.encode('ascii'), flags)
jpayne@69 440
jpayne@69 441
jpayne@69 442
jpayne@69 443 _FMT = '[Non-text (%(type)s) part of message omitted, filename %(filename)s]'
jpayne@69 444
jpayne@69 445 class DecodedGenerator(Generator):
jpayne@69 446 """Generates a text representation of a message.
jpayne@69 447
jpayne@69 448 Like the Generator base class, except that non-text parts are substituted
jpayne@69 449 with a format string representing the part.
jpayne@69 450 """
jpayne@69 451 def __init__(self, outfp, mangle_from_=None, maxheaderlen=None, fmt=None, *,
jpayne@69 452 policy=None):
jpayne@69 453 """Like Generator.__init__() except that an additional optional
jpayne@69 454 argument is allowed.
jpayne@69 455
jpayne@69 456 Walks through all subparts of a message. If the subpart is of main
jpayne@69 457 type `text', then it prints the decoded payload of the subpart.
jpayne@69 458
jpayne@69 459 Otherwise, fmt is a format string that is used instead of the message
jpayne@69 460 payload. fmt is expanded with the following keywords (in
jpayne@69 461 %(keyword)s format):
jpayne@69 462
jpayne@69 463 type : Full MIME type of the non-text part
jpayne@69 464 maintype : Main MIME type of the non-text part
jpayne@69 465 subtype : Sub-MIME type of the non-text part
jpayne@69 466 filename : Filename of the non-text part
jpayne@69 467 description: Description associated with the non-text part
jpayne@69 468 encoding : Content transfer encoding of the non-text part
jpayne@69 469
jpayne@69 470 The default value for fmt is None, meaning
jpayne@69 471
jpayne@69 472 [Non-text (%(type)s) part of message omitted, filename %(filename)s]
jpayne@69 473 """
jpayne@69 474 Generator.__init__(self, outfp, mangle_from_, maxheaderlen,
jpayne@69 475 policy=policy)
jpayne@69 476 if fmt is None:
jpayne@69 477 self._fmt = _FMT
jpayne@69 478 else:
jpayne@69 479 self._fmt = fmt
jpayne@69 480
jpayne@69 481 def _dispatch(self, msg):
jpayne@69 482 for part in msg.walk():
jpayne@69 483 maintype = part.get_content_maintype()
jpayne@69 484 if maintype == 'text':
jpayne@69 485 print(part.get_payload(decode=False), file=self)
jpayne@69 486 elif maintype == 'multipart':
jpayne@69 487 # Just skip this
jpayne@69 488 pass
jpayne@69 489 else:
jpayne@69 490 print(self._fmt % {
jpayne@69 491 'type' : part.get_content_type(),
jpayne@69 492 'maintype' : part.get_content_maintype(),
jpayne@69 493 'subtype' : part.get_content_subtype(),
jpayne@69 494 'filename' : part.get_filename('[no filename]'),
jpayne@69 495 'description': part.get('Content-Description',
jpayne@69 496 '[no description]'),
jpayne@69 497 'encoding' : part.get('Content-Transfer-Encoding',
jpayne@69 498 '[no encoding]'),
jpayne@69 499 }, file=self)
jpayne@69 500
jpayne@69 501
jpayne@69 502
jpayne@69 503 # Helper used by Generator._make_boundary
jpayne@69 504 _width = len(repr(sys.maxsize-1))
jpayne@69 505 _fmt = '%%0%dd' % _width
jpayne@69 506
jpayne@69 507 # Backward compatibility
jpayne@69 508 _make_boundary = Generator._make_boundary