17db96d56Sopenharmony_ci# Copyright (C) 2001-2010 Python Software Foundation 27db96d56Sopenharmony_ci# Author: Barry Warsaw 37db96d56Sopenharmony_ci# Contact: email-sig@python.org 47db96d56Sopenharmony_ci 57db96d56Sopenharmony_ci"""Classes to generate plain text from a message object tree.""" 67db96d56Sopenharmony_ci 77db96d56Sopenharmony_ci__all__ = ['Generator', 'DecodedGenerator', 'BytesGenerator'] 87db96d56Sopenharmony_ci 97db96d56Sopenharmony_ciimport re 107db96d56Sopenharmony_ciimport sys 117db96d56Sopenharmony_ciimport time 127db96d56Sopenharmony_ciimport random 137db96d56Sopenharmony_ci 147db96d56Sopenharmony_cifrom copy import deepcopy 157db96d56Sopenharmony_cifrom io import StringIO, BytesIO 167db96d56Sopenharmony_cifrom email.utils import _has_surrogates 177db96d56Sopenharmony_ci 187db96d56Sopenharmony_ciUNDERSCORE = '_' 197db96d56Sopenharmony_ciNL = '\n' # XXX: no longer used by the code below. 207db96d56Sopenharmony_ci 217db96d56Sopenharmony_ciNLCRE = re.compile(r'\r\n|\r|\n') 227db96d56Sopenharmony_cifcre = re.compile(r'^From ', re.MULTILINE) 237db96d56Sopenharmony_ci 247db96d56Sopenharmony_ci 257db96d56Sopenharmony_ci 267db96d56Sopenharmony_ciclass Generator: 277db96d56Sopenharmony_ci """Generates output from a Message object tree. 287db96d56Sopenharmony_ci 297db96d56Sopenharmony_ci This basic generator writes the message to the given file object as plain 307db96d56Sopenharmony_ci text. 317db96d56Sopenharmony_ci """ 327db96d56Sopenharmony_ci # 337db96d56Sopenharmony_ci # Public interface 347db96d56Sopenharmony_ci # 357db96d56Sopenharmony_ci 367db96d56Sopenharmony_ci def __init__(self, outfp, mangle_from_=None, maxheaderlen=None, *, 377db96d56Sopenharmony_ci policy=None): 387db96d56Sopenharmony_ci """Create the generator for message flattening. 397db96d56Sopenharmony_ci 407db96d56Sopenharmony_ci outfp is the output file-like object for writing the message to. It 417db96d56Sopenharmony_ci must have a write() method. 427db96d56Sopenharmony_ci 437db96d56Sopenharmony_ci Optional mangle_from_ is a flag that, when True (the default if policy 447db96d56Sopenharmony_ci is not set), escapes From_ lines in the body of the message by putting 457db96d56Sopenharmony_ci a `>' in front of them. 467db96d56Sopenharmony_ci 477db96d56Sopenharmony_ci Optional maxheaderlen specifies the longest length for a non-continued 487db96d56Sopenharmony_ci header. When a header line is longer (in characters, with tabs 497db96d56Sopenharmony_ci expanded to 8 spaces) than maxheaderlen, the header will split as 507db96d56Sopenharmony_ci defined in the Header class. Set maxheaderlen to zero to disable 517db96d56Sopenharmony_ci header wrapping. The default is 78, as recommended (but not required) 527db96d56Sopenharmony_ci by RFC 2822. 537db96d56Sopenharmony_ci 547db96d56Sopenharmony_ci The policy keyword specifies a policy object that controls a number of 557db96d56Sopenharmony_ci aspects of the generator's operation. If no policy is specified, 567db96d56Sopenharmony_ci the policy associated with the Message object passed to the 577db96d56Sopenharmony_ci flatten method is used. 587db96d56Sopenharmony_ci 597db96d56Sopenharmony_ci """ 607db96d56Sopenharmony_ci 617db96d56Sopenharmony_ci if mangle_from_ is None: 627db96d56Sopenharmony_ci mangle_from_ = True if policy is None else policy.mangle_from_ 637db96d56Sopenharmony_ci self._fp = outfp 647db96d56Sopenharmony_ci self._mangle_from_ = mangle_from_ 657db96d56Sopenharmony_ci self.maxheaderlen = maxheaderlen 667db96d56Sopenharmony_ci self.policy = policy 677db96d56Sopenharmony_ci 687db96d56Sopenharmony_ci def write(self, s): 697db96d56Sopenharmony_ci # Just delegate to the file object 707db96d56Sopenharmony_ci self._fp.write(s) 717db96d56Sopenharmony_ci 727db96d56Sopenharmony_ci def flatten(self, msg, unixfrom=False, linesep=None): 737db96d56Sopenharmony_ci r"""Print the message object tree rooted at msg to the output file 747db96d56Sopenharmony_ci specified when the Generator instance was created. 757db96d56Sopenharmony_ci 767db96d56Sopenharmony_ci unixfrom is a flag that forces the printing of a Unix From_ delimiter 777db96d56Sopenharmony_ci before the first object in the message tree. If the original message 787db96d56Sopenharmony_ci has no From_ delimiter, a `standard' one is crafted. By default, this 797db96d56Sopenharmony_ci is False to inhibit the printing of any From_ delimiter. 807db96d56Sopenharmony_ci 817db96d56Sopenharmony_ci Note that for subobjects, no From_ line is printed. 827db96d56Sopenharmony_ci 837db96d56Sopenharmony_ci linesep specifies the characters used to indicate a new line in 847db96d56Sopenharmony_ci the output. The default value is determined by the policy specified 857db96d56Sopenharmony_ci when the Generator instance was created or, if none was specified, 867db96d56Sopenharmony_ci from the policy associated with the msg. 877db96d56Sopenharmony_ci 887db96d56Sopenharmony_ci """ 897db96d56Sopenharmony_ci # We use the _XXX constants for operating on data that comes directly 907db96d56Sopenharmony_ci # from the msg, and _encoded_XXX constants for operating on data that 917db96d56Sopenharmony_ci # has already been converted (to bytes in the BytesGenerator) and 927db96d56Sopenharmony_ci # inserted into a temporary buffer. 937db96d56Sopenharmony_ci policy = msg.policy if self.policy is None else self.policy 947db96d56Sopenharmony_ci if linesep is not None: 957db96d56Sopenharmony_ci policy = policy.clone(linesep=linesep) 967db96d56Sopenharmony_ci if self.maxheaderlen is not None: 977db96d56Sopenharmony_ci policy = policy.clone(max_line_length=self.maxheaderlen) 987db96d56Sopenharmony_ci self._NL = policy.linesep 997db96d56Sopenharmony_ci self._encoded_NL = self._encode(self._NL) 1007db96d56Sopenharmony_ci self._EMPTY = '' 1017db96d56Sopenharmony_ci self._encoded_EMPTY = self._encode(self._EMPTY) 1027db96d56Sopenharmony_ci # Because we use clone (below) when we recursively process message 1037db96d56Sopenharmony_ci # subparts, and because clone uses the computed policy (not None), 1047db96d56Sopenharmony_ci # submessages will automatically get set to the computed policy when 1057db96d56Sopenharmony_ci # they are processed by this code. 1067db96d56Sopenharmony_ci old_gen_policy = self.policy 1077db96d56Sopenharmony_ci old_msg_policy = msg.policy 1087db96d56Sopenharmony_ci try: 1097db96d56Sopenharmony_ci self.policy = policy 1107db96d56Sopenharmony_ci msg.policy = policy 1117db96d56Sopenharmony_ci if unixfrom: 1127db96d56Sopenharmony_ci ufrom = msg.get_unixfrom() 1137db96d56Sopenharmony_ci if not ufrom: 1147db96d56Sopenharmony_ci ufrom = 'From nobody ' + time.ctime(time.time()) 1157db96d56Sopenharmony_ci self.write(ufrom + self._NL) 1167db96d56Sopenharmony_ci self._write(msg) 1177db96d56Sopenharmony_ci finally: 1187db96d56Sopenharmony_ci self.policy = old_gen_policy 1197db96d56Sopenharmony_ci msg.policy = old_msg_policy 1207db96d56Sopenharmony_ci 1217db96d56Sopenharmony_ci def clone(self, fp): 1227db96d56Sopenharmony_ci """Clone this generator with the exact same options.""" 1237db96d56Sopenharmony_ci return self.__class__(fp, 1247db96d56Sopenharmony_ci self._mangle_from_, 1257db96d56Sopenharmony_ci None, # Use policy setting, which we've adjusted 1267db96d56Sopenharmony_ci policy=self.policy) 1277db96d56Sopenharmony_ci 1287db96d56Sopenharmony_ci # 1297db96d56Sopenharmony_ci # Protected interface - undocumented ;/ 1307db96d56Sopenharmony_ci # 1317db96d56Sopenharmony_ci 1327db96d56Sopenharmony_ci # Note that we use 'self.write' when what we are writing is coming from 1337db96d56Sopenharmony_ci # the source, and self._fp.write when what we are writing is coming from a 1347db96d56Sopenharmony_ci # buffer (because the Bytes subclass has already had a chance to transform 1357db96d56Sopenharmony_ci # the data in its write method in that case). This is an entirely 1367db96d56Sopenharmony_ci # pragmatic split determined by experiment; we could be more general by 1377db96d56Sopenharmony_ci # always using write and having the Bytes subclass write method detect when 1387db96d56Sopenharmony_ci # it has already transformed the input; but, since this whole thing is a 1397db96d56Sopenharmony_ci # hack anyway this seems good enough. 1407db96d56Sopenharmony_ci 1417db96d56Sopenharmony_ci def _new_buffer(self): 1427db96d56Sopenharmony_ci # BytesGenerator overrides this to return BytesIO. 1437db96d56Sopenharmony_ci return StringIO() 1447db96d56Sopenharmony_ci 1457db96d56Sopenharmony_ci def _encode(self, s): 1467db96d56Sopenharmony_ci # BytesGenerator overrides this to encode strings to bytes. 1477db96d56Sopenharmony_ci return s 1487db96d56Sopenharmony_ci 1497db96d56Sopenharmony_ci def _write_lines(self, lines): 1507db96d56Sopenharmony_ci # We have to transform the line endings. 1517db96d56Sopenharmony_ci if not lines: 1527db96d56Sopenharmony_ci return 1537db96d56Sopenharmony_ci lines = NLCRE.split(lines) 1547db96d56Sopenharmony_ci for line in lines[:-1]: 1557db96d56Sopenharmony_ci self.write(line) 1567db96d56Sopenharmony_ci self.write(self._NL) 1577db96d56Sopenharmony_ci if lines[-1]: 1587db96d56Sopenharmony_ci self.write(lines[-1]) 1597db96d56Sopenharmony_ci # XXX logic tells me this else should be needed, but the tests fail 1607db96d56Sopenharmony_ci # with it and pass without it. (NLCRE.split ends with a blank element 1617db96d56Sopenharmony_ci # if and only if there was a trailing newline.) 1627db96d56Sopenharmony_ci #else: 1637db96d56Sopenharmony_ci # self.write(self._NL) 1647db96d56Sopenharmony_ci 1657db96d56Sopenharmony_ci def _write(self, msg): 1667db96d56Sopenharmony_ci # We can't write the headers yet because of the following scenario: 1677db96d56Sopenharmony_ci # say a multipart message includes the boundary string somewhere in 1687db96d56Sopenharmony_ci # its body. We'd have to calculate the new boundary /before/ we write 1697db96d56Sopenharmony_ci # the headers so that we can write the correct Content-Type: 1707db96d56Sopenharmony_ci # parameter. 1717db96d56Sopenharmony_ci # 1727db96d56Sopenharmony_ci # The way we do this, so as to make the _handle_*() methods simpler, 1737db96d56Sopenharmony_ci # is to cache any subpart writes into a buffer. The we write the 1747db96d56Sopenharmony_ci # headers and the buffer contents. That way, subpart handlers can 1757db96d56Sopenharmony_ci # Do The Right Thing, and can still modify the Content-Type: header if 1767db96d56Sopenharmony_ci # necessary. 1777db96d56Sopenharmony_ci oldfp = self._fp 1787db96d56Sopenharmony_ci try: 1797db96d56Sopenharmony_ci self._munge_cte = None 1807db96d56Sopenharmony_ci self._fp = sfp = self._new_buffer() 1817db96d56Sopenharmony_ci self._dispatch(msg) 1827db96d56Sopenharmony_ci finally: 1837db96d56Sopenharmony_ci self._fp = oldfp 1847db96d56Sopenharmony_ci munge_cte = self._munge_cte 1857db96d56Sopenharmony_ci del self._munge_cte 1867db96d56Sopenharmony_ci # If we munged the cte, copy the message again and re-fix the CTE. 1877db96d56Sopenharmony_ci if munge_cte: 1887db96d56Sopenharmony_ci msg = deepcopy(msg) 1897db96d56Sopenharmony_ci # Preserve the header order if the CTE header already exists. 1907db96d56Sopenharmony_ci if msg.get('content-transfer-encoding') is None: 1917db96d56Sopenharmony_ci msg['Content-Transfer-Encoding'] = munge_cte[0] 1927db96d56Sopenharmony_ci else: 1937db96d56Sopenharmony_ci msg.replace_header('content-transfer-encoding', munge_cte[0]) 1947db96d56Sopenharmony_ci msg.replace_header('content-type', munge_cte[1]) 1957db96d56Sopenharmony_ci # Write the headers. First we see if the message object wants to 1967db96d56Sopenharmony_ci # handle that itself. If not, we'll do it generically. 1977db96d56Sopenharmony_ci meth = getattr(msg, '_write_headers', None) 1987db96d56Sopenharmony_ci if meth is None: 1997db96d56Sopenharmony_ci self._write_headers(msg) 2007db96d56Sopenharmony_ci else: 2017db96d56Sopenharmony_ci meth(self) 2027db96d56Sopenharmony_ci self._fp.write(sfp.getvalue()) 2037db96d56Sopenharmony_ci 2047db96d56Sopenharmony_ci def _dispatch(self, msg): 2057db96d56Sopenharmony_ci # Get the Content-Type: for the message, then try to dispatch to 2067db96d56Sopenharmony_ci # self._handle_<maintype>_<subtype>(). If there's no handler for the 2077db96d56Sopenharmony_ci # full MIME type, then dispatch to self._handle_<maintype>(). If 2087db96d56Sopenharmony_ci # that's missing too, then dispatch to self._writeBody(). 2097db96d56Sopenharmony_ci main = msg.get_content_maintype() 2107db96d56Sopenharmony_ci sub = msg.get_content_subtype() 2117db96d56Sopenharmony_ci specific = UNDERSCORE.join((main, sub)).replace('-', '_') 2127db96d56Sopenharmony_ci meth = getattr(self, '_handle_' + specific, None) 2137db96d56Sopenharmony_ci if meth is None: 2147db96d56Sopenharmony_ci generic = main.replace('-', '_') 2157db96d56Sopenharmony_ci meth = getattr(self, '_handle_' + generic, None) 2167db96d56Sopenharmony_ci if meth is None: 2177db96d56Sopenharmony_ci meth = self._writeBody 2187db96d56Sopenharmony_ci meth(msg) 2197db96d56Sopenharmony_ci 2207db96d56Sopenharmony_ci # 2217db96d56Sopenharmony_ci # Default handlers 2227db96d56Sopenharmony_ci # 2237db96d56Sopenharmony_ci 2247db96d56Sopenharmony_ci def _write_headers(self, msg): 2257db96d56Sopenharmony_ci for h, v in msg.raw_items(): 2267db96d56Sopenharmony_ci self.write(self.policy.fold(h, v)) 2277db96d56Sopenharmony_ci # A blank line always separates headers from body 2287db96d56Sopenharmony_ci self.write(self._NL) 2297db96d56Sopenharmony_ci 2307db96d56Sopenharmony_ci # 2317db96d56Sopenharmony_ci # Handlers for writing types and subtypes 2327db96d56Sopenharmony_ci # 2337db96d56Sopenharmony_ci 2347db96d56Sopenharmony_ci def _handle_text(self, msg): 2357db96d56Sopenharmony_ci payload = msg.get_payload() 2367db96d56Sopenharmony_ci if payload is None: 2377db96d56Sopenharmony_ci return 2387db96d56Sopenharmony_ci if not isinstance(payload, str): 2397db96d56Sopenharmony_ci raise TypeError('string payload expected: %s' % type(payload)) 2407db96d56Sopenharmony_ci if _has_surrogates(msg._payload): 2417db96d56Sopenharmony_ci charset = msg.get_param('charset') 2427db96d56Sopenharmony_ci if charset is not None: 2437db96d56Sopenharmony_ci # XXX: This copy stuff is an ugly hack to avoid modifying the 2447db96d56Sopenharmony_ci # existing message. 2457db96d56Sopenharmony_ci msg = deepcopy(msg) 2467db96d56Sopenharmony_ci del msg['content-transfer-encoding'] 2477db96d56Sopenharmony_ci msg.set_payload(payload, charset) 2487db96d56Sopenharmony_ci payload = msg.get_payload() 2497db96d56Sopenharmony_ci self._munge_cte = (msg['content-transfer-encoding'], 2507db96d56Sopenharmony_ci msg['content-type']) 2517db96d56Sopenharmony_ci if self._mangle_from_: 2527db96d56Sopenharmony_ci payload = fcre.sub('>From ', payload) 2537db96d56Sopenharmony_ci self._write_lines(payload) 2547db96d56Sopenharmony_ci 2557db96d56Sopenharmony_ci # Default body handler 2567db96d56Sopenharmony_ci _writeBody = _handle_text 2577db96d56Sopenharmony_ci 2587db96d56Sopenharmony_ci def _handle_multipart(self, msg): 2597db96d56Sopenharmony_ci # The trick here is to write out each part separately, merge them all 2607db96d56Sopenharmony_ci # together, and then make sure that the boundary we've chosen isn't 2617db96d56Sopenharmony_ci # present in the payload. 2627db96d56Sopenharmony_ci msgtexts = [] 2637db96d56Sopenharmony_ci subparts = msg.get_payload() 2647db96d56Sopenharmony_ci if subparts is None: 2657db96d56Sopenharmony_ci subparts = [] 2667db96d56Sopenharmony_ci elif isinstance(subparts, str): 2677db96d56Sopenharmony_ci # e.g. a non-strict parse of a message with no starting boundary. 2687db96d56Sopenharmony_ci self.write(subparts) 2697db96d56Sopenharmony_ci return 2707db96d56Sopenharmony_ci elif not isinstance(subparts, list): 2717db96d56Sopenharmony_ci # Scalar payload 2727db96d56Sopenharmony_ci subparts = [subparts] 2737db96d56Sopenharmony_ci for part in subparts: 2747db96d56Sopenharmony_ci s = self._new_buffer() 2757db96d56Sopenharmony_ci g = self.clone(s) 2767db96d56Sopenharmony_ci g.flatten(part, unixfrom=False, linesep=self._NL) 2777db96d56Sopenharmony_ci msgtexts.append(s.getvalue()) 2787db96d56Sopenharmony_ci # BAW: What about boundaries that are wrapped in double-quotes? 2797db96d56Sopenharmony_ci boundary = msg.get_boundary() 2807db96d56Sopenharmony_ci if not boundary: 2817db96d56Sopenharmony_ci # Create a boundary that doesn't appear in any of the 2827db96d56Sopenharmony_ci # message texts. 2837db96d56Sopenharmony_ci alltext = self._encoded_NL.join(msgtexts) 2847db96d56Sopenharmony_ci boundary = self._make_boundary(alltext) 2857db96d56Sopenharmony_ci msg.set_boundary(boundary) 2867db96d56Sopenharmony_ci # If there's a preamble, write it out, with a trailing CRLF 2877db96d56Sopenharmony_ci if msg.preamble is not None: 2887db96d56Sopenharmony_ci if self._mangle_from_: 2897db96d56Sopenharmony_ci preamble = fcre.sub('>From ', msg.preamble) 2907db96d56Sopenharmony_ci else: 2917db96d56Sopenharmony_ci preamble = msg.preamble 2927db96d56Sopenharmony_ci self._write_lines(preamble) 2937db96d56Sopenharmony_ci self.write(self._NL) 2947db96d56Sopenharmony_ci # dash-boundary transport-padding CRLF 2957db96d56Sopenharmony_ci self.write('--' + boundary + self._NL) 2967db96d56Sopenharmony_ci # body-part 2977db96d56Sopenharmony_ci if msgtexts: 2987db96d56Sopenharmony_ci self._fp.write(msgtexts.pop(0)) 2997db96d56Sopenharmony_ci # *encapsulation 3007db96d56Sopenharmony_ci # --> delimiter transport-padding 3017db96d56Sopenharmony_ci # --> CRLF body-part 3027db96d56Sopenharmony_ci for body_part in msgtexts: 3037db96d56Sopenharmony_ci # delimiter transport-padding CRLF 3047db96d56Sopenharmony_ci self.write(self._NL + '--' + boundary + self._NL) 3057db96d56Sopenharmony_ci # body-part 3067db96d56Sopenharmony_ci self._fp.write(body_part) 3077db96d56Sopenharmony_ci # close-delimiter transport-padding 3087db96d56Sopenharmony_ci self.write(self._NL + '--' + boundary + '--' + self._NL) 3097db96d56Sopenharmony_ci if msg.epilogue is not None: 3107db96d56Sopenharmony_ci if self._mangle_from_: 3117db96d56Sopenharmony_ci epilogue = fcre.sub('>From ', msg.epilogue) 3127db96d56Sopenharmony_ci else: 3137db96d56Sopenharmony_ci epilogue = msg.epilogue 3147db96d56Sopenharmony_ci self._write_lines(epilogue) 3157db96d56Sopenharmony_ci 3167db96d56Sopenharmony_ci def _handle_multipart_signed(self, msg): 3177db96d56Sopenharmony_ci # The contents of signed parts has to stay unmodified in order to keep 3187db96d56Sopenharmony_ci # the signature intact per RFC1847 2.1, so we disable header wrapping. 3197db96d56Sopenharmony_ci # RDM: This isn't enough to completely preserve the part, but it helps. 3207db96d56Sopenharmony_ci p = self.policy 3217db96d56Sopenharmony_ci self.policy = p.clone(max_line_length=0) 3227db96d56Sopenharmony_ci try: 3237db96d56Sopenharmony_ci self._handle_multipart(msg) 3247db96d56Sopenharmony_ci finally: 3257db96d56Sopenharmony_ci self.policy = p 3267db96d56Sopenharmony_ci 3277db96d56Sopenharmony_ci def _handle_message_delivery_status(self, msg): 3287db96d56Sopenharmony_ci # We can't just write the headers directly to self's file object 3297db96d56Sopenharmony_ci # because this will leave an extra newline between the last header 3307db96d56Sopenharmony_ci # block and the boundary. Sigh. 3317db96d56Sopenharmony_ci blocks = [] 3327db96d56Sopenharmony_ci for part in msg.get_payload(): 3337db96d56Sopenharmony_ci s = self._new_buffer() 3347db96d56Sopenharmony_ci g = self.clone(s) 3357db96d56Sopenharmony_ci g.flatten(part, unixfrom=False, linesep=self._NL) 3367db96d56Sopenharmony_ci text = s.getvalue() 3377db96d56Sopenharmony_ci lines = text.split(self._encoded_NL) 3387db96d56Sopenharmony_ci # Strip off the unnecessary trailing empty line 3397db96d56Sopenharmony_ci if lines and lines[-1] == self._encoded_EMPTY: 3407db96d56Sopenharmony_ci blocks.append(self._encoded_NL.join(lines[:-1])) 3417db96d56Sopenharmony_ci else: 3427db96d56Sopenharmony_ci blocks.append(text) 3437db96d56Sopenharmony_ci # Now join all the blocks with an empty line. This has the lovely 3447db96d56Sopenharmony_ci # effect of separating each block with an empty line, but not adding 3457db96d56Sopenharmony_ci # an extra one after the last one. 3467db96d56Sopenharmony_ci self._fp.write(self._encoded_NL.join(blocks)) 3477db96d56Sopenharmony_ci 3487db96d56Sopenharmony_ci def _handle_message(self, msg): 3497db96d56Sopenharmony_ci s = self._new_buffer() 3507db96d56Sopenharmony_ci g = self.clone(s) 3517db96d56Sopenharmony_ci # The payload of a message/rfc822 part should be a multipart sequence 3527db96d56Sopenharmony_ci # of length 1. The zeroth element of the list should be the Message 3537db96d56Sopenharmony_ci # object for the subpart. Extract that object, stringify it, and 3547db96d56Sopenharmony_ci # write it out. 3557db96d56Sopenharmony_ci # Except, it turns out, when it's a string instead, which happens when 3567db96d56Sopenharmony_ci # and only when HeaderParser is used on a message of mime type 3577db96d56Sopenharmony_ci # message/rfc822. Such messages are generated by, for example, 3587db96d56Sopenharmony_ci # Groupwise when forwarding unadorned messages. (Issue 7970.) So 3597db96d56Sopenharmony_ci # in that case we just emit the string body. 3607db96d56Sopenharmony_ci payload = msg._payload 3617db96d56Sopenharmony_ci if isinstance(payload, list): 3627db96d56Sopenharmony_ci g.flatten(msg.get_payload(0), unixfrom=False, linesep=self._NL) 3637db96d56Sopenharmony_ci payload = s.getvalue() 3647db96d56Sopenharmony_ci else: 3657db96d56Sopenharmony_ci payload = self._encode(payload) 3667db96d56Sopenharmony_ci self._fp.write(payload) 3677db96d56Sopenharmony_ci 3687db96d56Sopenharmony_ci # This used to be a module level function; we use a classmethod for this 3697db96d56Sopenharmony_ci # and _compile_re so we can continue to provide the module level function 3707db96d56Sopenharmony_ci # for backward compatibility by doing 3717db96d56Sopenharmony_ci # _make_boundary = Generator._make_boundary 3727db96d56Sopenharmony_ci # at the end of the module. It *is* internal, so we could drop that... 3737db96d56Sopenharmony_ci @classmethod 3747db96d56Sopenharmony_ci def _make_boundary(cls, text=None): 3757db96d56Sopenharmony_ci # Craft a random boundary. If text is given, ensure that the chosen 3767db96d56Sopenharmony_ci # boundary doesn't appear in the text. 3777db96d56Sopenharmony_ci token = random.randrange(sys.maxsize) 3787db96d56Sopenharmony_ci boundary = ('=' * 15) + (_fmt % token) + '==' 3797db96d56Sopenharmony_ci if text is None: 3807db96d56Sopenharmony_ci return boundary 3817db96d56Sopenharmony_ci b = boundary 3827db96d56Sopenharmony_ci counter = 0 3837db96d56Sopenharmony_ci while True: 3847db96d56Sopenharmony_ci cre = cls._compile_re('^--' + re.escape(b) + '(--)?$', re.MULTILINE) 3857db96d56Sopenharmony_ci if not cre.search(text): 3867db96d56Sopenharmony_ci break 3877db96d56Sopenharmony_ci b = boundary + '.' + str(counter) 3887db96d56Sopenharmony_ci counter += 1 3897db96d56Sopenharmony_ci return b 3907db96d56Sopenharmony_ci 3917db96d56Sopenharmony_ci @classmethod 3927db96d56Sopenharmony_ci def _compile_re(cls, s, flags): 3937db96d56Sopenharmony_ci return re.compile(s, flags) 3947db96d56Sopenharmony_ci 3957db96d56Sopenharmony_ci 3967db96d56Sopenharmony_ciclass BytesGenerator(Generator): 3977db96d56Sopenharmony_ci """Generates a bytes version of a Message object tree. 3987db96d56Sopenharmony_ci 3997db96d56Sopenharmony_ci Functionally identical to the base Generator except that the output is 4007db96d56Sopenharmony_ci bytes and not string. When surrogates were used in the input to encode 4017db96d56Sopenharmony_ci bytes, these are decoded back to bytes for output. If the policy has 4027db96d56Sopenharmony_ci cte_type set to 7bit, then the message is transformed such that the 4037db96d56Sopenharmony_ci non-ASCII bytes are properly content transfer encoded, using the charset 4047db96d56Sopenharmony_ci unknown-8bit. 4057db96d56Sopenharmony_ci 4067db96d56Sopenharmony_ci The outfp object must accept bytes in its write method. 4077db96d56Sopenharmony_ci """ 4087db96d56Sopenharmony_ci 4097db96d56Sopenharmony_ci def write(self, s): 4107db96d56Sopenharmony_ci self._fp.write(s.encode('ascii', 'surrogateescape')) 4117db96d56Sopenharmony_ci 4127db96d56Sopenharmony_ci def _new_buffer(self): 4137db96d56Sopenharmony_ci return BytesIO() 4147db96d56Sopenharmony_ci 4157db96d56Sopenharmony_ci def _encode(self, s): 4167db96d56Sopenharmony_ci return s.encode('ascii') 4177db96d56Sopenharmony_ci 4187db96d56Sopenharmony_ci def _write_headers(self, msg): 4197db96d56Sopenharmony_ci # This is almost the same as the string version, except for handling 4207db96d56Sopenharmony_ci # strings with 8bit bytes. 4217db96d56Sopenharmony_ci for h, v in msg.raw_items(): 4227db96d56Sopenharmony_ci self._fp.write(self.policy.fold_binary(h, v)) 4237db96d56Sopenharmony_ci # A blank line always separates headers from body 4247db96d56Sopenharmony_ci self.write(self._NL) 4257db96d56Sopenharmony_ci 4267db96d56Sopenharmony_ci def _handle_text(self, msg): 4277db96d56Sopenharmony_ci # If the string has surrogates the original source was bytes, so 4287db96d56Sopenharmony_ci # just write it back out. 4297db96d56Sopenharmony_ci if msg._payload is None: 4307db96d56Sopenharmony_ci return 4317db96d56Sopenharmony_ci if _has_surrogates(msg._payload) and not self.policy.cte_type=='7bit': 4327db96d56Sopenharmony_ci if self._mangle_from_: 4337db96d56Sopenharmony_ci msg._payload = fcre.sub(">From ", msg._payload) 4347db96d56Sopenharmony_ci self._write_lines(msg._payload) 4357db96d56Sopenharmony_ci else: 4367db96d56Sopenharmony_ci super(BytesGenerator,self)._handle_text(msg) 4377db96d56Sopenharmony_ci 4387db96d56Sopenharmony_ci # Default body handler 4397db96d56Sopenharmony_ci _writeBody = _handle_text 4407db96d56Sopenharmony_ci 4417db96d56Sopenharmony_ci @classmethod 4427db96d56Sopenharmony_ci def _compile_re(cls, s, flags): 4437db96d56Sopenharmony_ci return re.compile(s.encode('ascii'), flags) 4447db96d56Sopenharmony_ci 4457db96d56Sopenharmony_ci 4467db96d56Sopenharmony_ci 4477db96d56Sopenharmony_ci_FMT = '[Non-text (%(type)s) part of message omitted, filename %(filename)s]' 4487db96d56Sopenharmony_ci 4497db96d56Sopenharmony_ciclass DecodedGenerator(Generator): 4507db96d56Sopenharmony_ci """Generates a text representation of a message. 4517db96d56Sopenharmony_ci 4527db96d56Sopenharmony_ci Like the Generator base class, except that non-text parts are substituted 4537db96d56Sopenharmony_ci with a format string representing the part. 4547db96d56Sopenharmony_ci """ 4557db96d56Sopenharmony_ci def __init__(self, outfp, mangle_from_=None, maxheaderlen=None, fmt=None, *, 4567db96d56Sopenharmony_ci policy=None): 4577db96d56Sopenharmony_ci """Like Generator.__init__() except that an additional optional 4587db96d56Sopenharmony_ci argument is allowed. 4597db96d56Sopenharmony_ci 4607db96d56Sopenharmony_ci Walks through all subparts of a message. If the subpart is of main 4617db96d56Sopenharmony_ci type `text', then it prints the decoded payload of the subpart. 4627db96d56Sopenharmony_ci 4637db96d56Sopenharmony_ci Otherwise, fmt is a format string that is used instead of the message 4647db96d56Sopenharmony_ci payload. fmt is expanded with the following keywords (in 4657db96d56Sopenharmony_ci %(keyword)s format): 4667db96d56Sopenharmony_ci 4677db96d56Sopenharmony_ci type : Full MIME type of the non-text part 4687db96d56Sopenharmony_ci maintype : Main MIME type of the non-text part 4697db96d56Sopenharmony_ci subtype : Sub-MIME type of the non-text part 4707db96d56Sopenharmony_ci filename : Filename of the non-text part 4717db96d56Sopenharmony_ci description: Description associated with the non-text part 4727db96d56Sopenharmony_ci encoding : Content transfer encoding of the non-text part 4737db96d56Sopenharmony_ci 4747db96d56Sopenharmony_ci The default value for fmt is None, meaning 4757db96d56Sopenharmony_ci 4767db96d56Sopenharmony_ci [Non-text (%(type)s) part of message omitted, filename %(filename)s] 4777db96d56Sopenharmony_ci """ 4787db96d56Sopenharmony_ci Generator.__init__(self, outfp, mangle_from_, maxheaderlen, 4797db96d56Sopenharmony_ci policy=policy) 4807db96d56Sopenharmony_ci if fmt is None: 4817db96d56Sopenharmony_ci self._fmt = _FMT 4827db96d56Sopenharmony_ci else: 4837db96d56Sopenharmony_ci self._fmt = fmt 4847db96d56Sopenharmony_ci 4857db96d56Sopenharmony_ci def _dispatch(self, msg): 4867db96d56Sopenharmony_ci for part in msg.walk(): 4877db96d56Sopenharmony_ci maintype = part.get_content_maintype() 4887db96d56Sopenharmony_ci if maintype == 'text': 4897db96d56Sopenharmony_ci print(part.get_payload(decode=False), file=self) 4907db96d56Sopenharmony_ci elif maintype == 'multipart': 4917db96d56Sopenharmony_ci # Just skip this 4927db96d56Sopenharmony_ci pass 4937db96d56Sopenharmony_ci else: 4947db96d56Sopenharmony_ci print(self._fmt % { 4957db96d56Sopenharmony_ci 'type' : part.get_content_type(), 4967db96d56Sopenharmony_ci 'maintype' : part.get_content_maintype(), 4977db96d56Sopenharmony_ci 'subtype' : part.get_content_subtype(), 4987db96d56Sopenharmony_ci 'filename' : part.get_filename('[no filename]'), 4997db96d56Sopenharmony_ci 'description': part.get('Content-Description', 5007db96d56Sopenharmony_ci '[no description]'), 5017db96d56Sopenharmony_ci 'encoding' : part.get('Content-Transfer-Encoding', 5027db96d56Sopenharmony_ci '[no encoding]'), 5037db96d56Sopenharmony_ci }, file=self) 5047db96d56Sopenharmony_ci 5057db96d56Sopenharmony_ci 5067db96d56Sopenharmony_ci 5077db96d56Sopenharmony_ci# Helper used by Generator._make_boundary 5087db96d56Sopenharmony_ci_width = len(repr(sys.maxsize-1)) 5097db96d56Sopenharmony_ci_fmt = '%%0%dd' % _width 5107db96d56Sopenharmony_ci 5117db96d56Sopenharmony_ci# Backward compatibility 5127db96d56Sopenharmony_ci_make_boundary = Generator._make_boundary 513