annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/email/generator.py @ 68:5028fdace37b

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