xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/email/header.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker# Copyright (C) 2002-2007 Python Software Foundation
2*cda5da8dSAndroid Build Coastguard Worker# Author: Ben Gertzfield, Barry Warsaw
3*cda5da8dSAndroid Build Coastguard Worker# Contact: [email protected]
4*cda5da8dSAndroid Build Coastguard Worker
5*cda5da8dSAndroid Build Coastguard Worker"""Header encoding and decoding functionality."""
6*cda5da8dSAndroid Build Coastguard Worker
7*cda5da8dSAndroid Build Coastguard Worker__all__ = [
8*cda5da8dSAndroid Build Coastguard Worker    'Header',
9*cda5da8dSAndroid Build Coastguard Worker    'decode_header',
10*cda5da8dSAndroid Build Coastguard Worker    'make_header',
11*cda5da8dSAndroid Build Coastguard Worker    ]
12*cda5da8dSAndroid Build Coastguard Worker
13*cda5da8dSAndroid Build Coastguard Workerimport re
14*cda5da8dSAndroid Build Coastguard Workerimport binascii
15*cda5da8dSAndroid Build Coastguard Worker
16*cda5da8dSAndroid Build Coastguard Workerimport email.quoprimime
17*cda5da8dSAndroid Build Coastguard Workerimport email.base64mime
18*cda5da8dSAndroid Build Coastguard Worker
19*cda5da8dSAndroid Build Coastguard Workerfrom email.errors import HeaderParseError
20*cda5da8dSAndroid Build Coastguard Workerfrom email import charset as _charset
21*cda5da8dSAndroid Build Coastguard WorkerCharset = _charset.Charset
22*cda5da8dSAndroid Build Coastguard Worker
23*cda5da8dSAndroid Build Coastguard WorkerNL = '\n'
24*cda5da8dSAndroid Build Coastguard WorkerSPACE = ' '
25*cda5da8dSAndroid Build Coastguard WorkerBSPACE = b' '
26*cda5da8dSAndroid Build Coastguard WorkerSPACE8 = ' ' * 8
27*cda5da8dSAndroid Build Coastguard WorkerEMPTYSTRING = ''
28*cda5da8dSAndroid Build Coastguard WorkerMAXLINELEN = 78
29*cda5da8dSAndroid Build Coastguard WorkerFWS = ' \t'
30*cda5da8dSAndroid Build Coastguard Worker
31*cda5da8dSAndroid Build Coastguard WorkerUSASCII = Charset('us-ascii')
32*cda5da8dSAndroid Build Coastguard WorkerUTF8 = Charset('utf-8')
33*cda5da8dSAndroid Build Coastguard Worker
34*cda5da8dSAndroid Build Coastguard Worker# Match encoded-word strings in the form =?charset?q?Hello_World?=
35*cda5da8dSAndroid Build Coastguard Workerecre = re.compile(r'''
36*cda5da8dSAndroid Build Coastguard Worker  =\?                   # literal =?
37*cda5da8dSAndroid Build Coastguard Worker  (?P<charset>[^?]*?)   # non-greedy up to the next ? is the charset
38*cda5da8dSAndroid Build Coastguard Worker  \?                    # literal ?
39*cda5da8dSAndroid Build Coastguard Worker  (?P<encoding>[qQbB])  # either a "q" or a "b", case insensitive
40*cda5da8dSAndroid Build Coastguard Worker  \?                    # literal ?
41*cda5da8dSAndroid Build Coastguard Worker  (?P<encoded>.*?)      # non-greedy up to the next ?= is the encoded string
42*cda5da8dSAndroid Build Coastguard Worker  \?=                   # literal ?=
43*cda5da8dSAndroid Build Coastguard Worker  ''', re.VERBOSE | re.MULTILINE)
44*cda5da8dSAndroid Build Coastguard Worker
45*cda5da8dSAndroid Build Coastguard Worker# Field name regexp, including trailing colon, but not separating whitespace,
46*cda5da8dSAndroid Build Coastguard Worker# according to RFC 2822.  Character range is from tilde to exclamation mark.
47*cda5da8dSAndroid Build Coastguard Worker# For use with .match()
48*cda5da8dSAndroid Build Coastguard Workerfcre = re.compile(r'[\041-\176]+:$')
49*cda5da8dSAndroid Build Coastguard Worker
50*cda5da8dSAndroid Build Coastguard Worker# Find a header embedded in a putative header value.  Used to check for
51*cda5da8dSAndroid Build Coastguard Worker# header injection attack.
52*cda5da8dSAndroid Build Coastguard Worker_embedded_header = re.compile(r'\n[^ \t]+:')
53*cda5da8dSAndroid Build Coastguard Worker
54*cda5da8dSAndroid Build Coastguard Worker
55*cda5da8dSAndroid Build Coastguard Worker
56*cda5da8dSAndroid Build Coastguard Worker# Helpers
57*cda5da8dSAndroid Build Coastguard Worker_max_append = email.quoprimime._max_append
58*cda5da8dSAndroid Build Coastguard Worker
59*cda5da8dSAndroid Build Coastguard Worker
60*cda5da8dSAndroid Build Coastguard Worker
61*cda5da8dSAndroid Build Coastguard Workerdef decode_header(header):
62*cda5da8dSAndroid Build Coastguard Worker    """Decode a message header value without converting charset.
63*cda5da8dSAndroid Build Coastguard Worker
64*cda5da8dSAndroid Build Coastguard Worker    Returns a list of (string, charset) pairs containing each of the decoded
65*cda5da8dSAndroid Build Coastguard Worker    parts of the header.  Charset is None for non-encoded parts of the header,
66*cda5da8dSAndroid Build Coastguard Worker    otherwise a lower-case string containing the name of the character set
67*cda5da8dSAndroid Build Coastguard Worker    specified in the encoded string.
68*cda5da8dSAndroid Build Coastguard Worker
69*cda5da8dSAndroid Build Coastguard Worker    header may be a string that may or may not contain RFC2047 encoded words,
70*cda5da8dSAndroid Build Coastguard Worker    or it may be a Header object.
71*cda5da8dSAndroid Build Coastguard Worker
72*cda5da8dSAndroid Build Coastguard Worker    An email.errors.HeaderParseError may be raised when certain decoding error
73*cda5da8dSAndroid Build Coastguard Worker    occurs (e.g. a base64 decoding exception).
74*cda5da8dSAndroid Build Coastguard Worker    """
75*cda5da8dSAndroid Build Coastguard Worker    # If it is a Header object, we can just return the encoded chunks.
76*cda5da8dSAndroid Build Coastguard Worker    if hasattr(header, '_chunks'):
77*cda5da8dSAndroid Build Coastguard Worker        return [(_charset._encode(string, str(charset)), str(charset))
78*cda5da8dSAndroid Build Coastguard Worker                    for string, charset in header._chunks]
79*cda5da8dSAndroid Build Coastguard Worker    # If no encoding, just return the header with no charset.
80*cda5da8dSAndroid Build Coastguard Worker    if not ecre.search(header):
81*cda5da8dSAndroid Build Coastguard Worker        return [(header, None)]
82*cda5da8dSAndroid Build Coastguard Worker    # First step is to parse all the encoded parts into triplets of the form
83*cda5da8dSAndroid Build Coastguard Worker    # (encoded_string, encoding, charset).  For unencoded strings, the last
84*cda5da8dSAndroid Build Coastguard Worker    # two parts will be None.
85*cda5da8dSAndroid Build Coastguard Worker    words = []
86*cda5da8dSAndroid Build Coastguard Worker    for line in header.splitlines():
87*cda5da8dSAndroid Build Coastguard Worker        parts = ecre.split(line)
88*cda5da8dSAndroid Build Coastguard Worker        first = True
89*cda5da8dSAndroid Build Coastguard Worker        while parts:
90*cda5da8dSAndroid Build Coastguard Worker            unencoded = parts.pop(0)
91*cda5da8dSAndroid Build Coastguard Worker            if first:
92*cda5da8dSAndroid Build Coastguard Worker                unencoded = unencoded.lstrip()
93*cda5da8dSAndroid Build Coastguard Worker                first = False
94*cda5da8dSAndroid Build Coastguard Worker            if unencoded:
95*cda5da8dSAndroid Build Coastguard Worker                words.append((unencoded, None, None))
96*cda5da8dSAndroid Build Coastguard Worker            if parts:
97*cda5da8dSAndroid Build Coastguard Worker                charset = parts.pop(0).lower()
98*cda5da8dSAndroid Build Coastguard Worker                encoding = parts.pop(0).lower()
99*cda5da8dSAndroid Build Coastguard Worker                encoded = parts.pop(0)
100*cda5da8dSAndroid Build Coastguard Worker                words.append((encoded, encoding, charset))
101*cda5da8dSAndroid Build Coastguard Worker    # Now loop over words and remove words that consist of whitespace
102*cda5da8dSAndroid Build Coastguard Worker    # between two encoded strings.
103*cda5da8dSAndroid Build Coastguard Worker    droplist = []
104*cda5da8dSAndroid Build Coastguard Worker    for n, w in enumerate(words):
105*cda5da8dSAndroid Build Coastguard Worker        if n>1 and w[1] and words[n-2][1] and words[n-1][0].isspace():
106*cda5da8dSAndroid Build Coastguard Worker            droplist.append(n-1)
107*cda5da8dSAndroid Build Coastguard Worker    for d in reversed(droplist):
108*cda5da8dSAndroid Build Coastguard Worker        del words[d]
109*cda5da8dSAndroid Build Coastguard Worker
110*cda5da8dSAndroid Build Coastguard Worker    # The next step is to decode each encoded word by applying the reverse
111*cda5da8dSAndroid Build Coastguard Worker    # base64 or quopri transformation.  decoded_words is now a list of the
112*cda5da8dSAndroid Build Coastguard Worker    # form (decoded_word, charset).
113*cda5da8dSAndroid Build Coastguard Worker    decoded_words = []
114*cda5da8dSAndroid Build Coastguard Worker    for encoded_string, encoding, charset in words:
115*cda5da8dSAndroid Build Coastguard Worker        if encoding is None:
116*cda5da8dSAndroid Build Coastguard Worker            # This is an unencoded word.
117*cda5da8dSAndroid Build Coastguard Worker            decoded_words.append((encoded_string, charset))
118*cda5da8dSAndroid Build Coastguard Worker        elif encoding == 'q':
119*cda5da8dSAndroid Build Coastguard Worker            word = email.quoprimime.header_decode(encoded_string)
120*cda5da8dSAndroid Build Coastguard Worker            decoded_words.append((word, charset))
121*cda5da8dSAndroid Build Coastguard Worker        elif encoding == 'b':
122*cda5da8dSAndroid Build Coastguard Worker            paderr = len(encoded_string) % 4   # Postel's law: add missing padding
123*cda5da8dSAndroid Build Coastguard Worker            if paderr:
124*cda5da8dSAndroid Build Coastguard Worker                encoded_string += '==='[:4 - paderr]
125*cda5da8dSAndroid Build Coastguard Worker            try:
126*cda5da8dSAndroid Build Coastguard Worker                word = email.base64mime.decode(encoded_string)
127*cda5da8dSAndroid Build Coastguard Worker            except binascii.Error:
128*cda5da8dSAndroid Build Coastguard Worker                raise HeaderParseError('Base64 decoding error')
129*cda5da8dSAndroid Build Coastguard Worker            else:
130*cda5da8dSAndroid Build Coastguard Worker                decoded_words.append((word, charset))
131*cda5da8dSAndroid Build Coastguard Worker        else:
132*cda5da8dSAndroid Build Coastguard Worker            raise AssertionError('Unexpected encoding: ' + encoding)
133*cda5da8dSAndroid Build Coastguard Worker    # Now convert all words to bytes and collapse consecutive runs of
134*cda5da8dSAndroid Build Coastguard Worker    # similarly encoded words.
135*cda5da8dSAndroid Build Coastguard Worker    collapsed = []
136*cda5da8dSAndroid Build Coastguard Worker    last_word = last_charset = None
137*cda5da8dSAndroid Build Coastguard Worker    for word, charset in decoded_words:
138*cda5da8dSAndroid Build Coastguard Worker        if isinstance(word, str):
139*cda5da8dSAndroid Build Coastguard Worker            word = bytes(word, 'raw-unicode-escape')
140*cda5da8dSAndroid Build Coastguard Worker        if last_word is None:
141*cda5da8dSAndroid Build Coastguard Worker            last_word = word
142*cda5da8dSAndroid Build Coastguard Worker            last_charset = charset
143*cda5da8dSAndroid Build Coastguard Worker        elif charset != last_charset:
144*cda5da8dSAndroid Build Coastguard Worker            collapsed.append((last_word, last_charset))
145*cda5da8dSAndroid Build Coastguard Worker            last_word = word
146*cda5da8dSAndroid Build Coastguard Worker            last_charset = charset
147*cda5da8dSAndroid Build Coastguard Worker        elif last_charset is None:
148*cda5da8dSAndroid Build Coastguard Worker            last_word += BSPACE + word
149*cda5da8dSAndroid Build Coastguard Worker        else:
150*cda5da8dSAndroid Build Coastguard Worker            last_word += word
151*cda5da8dSAndroid Build Coastguard Worker    collapsed.append((last_word, last_charset))
152*cda5da8dSAndroid Build Coastguard Worker    return collapsed
153*cda5da8dSAndroid Build Coastguard Worker
154*cda5da8dSAndroid Build Coastguard Worker
155*cda5da8dSAndroid Build Coastguard Worker
156*cda5da8dSAndroid Build Coastguard Workerdef make_header(decoded_seq, maxlinelen=None, header_name=None,
157*cda5da8dSAndroid Build Coastguard Worker                continuation_ws=' '):
158*cda5da8dSAndroid Build Coastguard Worker    """Create a Header from a sequence of pairs as returned by decode_header()
159*cda5da8dSAndroid Build Coastguard Worker
160*cda5da8dSAndroid Build Coastguard Worker    decode_header() takes a header value string and returns a sequence of
161*cda5da8dSAndroid Build Coastguard Worker    pairs of the format (decoded_string, charset) where charset is the string
162*cda5da8dSAndroid Build Coastguard Worker    name of the character set.
163*cda5da8dSAndroid Build Coastguard Worker
164*cda5da8dSAndroid Build Coastguard Worker    This function takes one of those sequence of pairs and returns a Header
165*cda5da8dSAndroid Build Coastguard Worker    instance.  Optional maxlinelen, header_name, and continuation_ws are as in
166*cda5da8dSAndroid Build Coastguard Worker    the Header constructor.
167*cda5da8dSAndroid Build Coastguard Worker    """
168*cda5da8dSAndroid Build Coastguard Worker    h = Header(maxlinelen=maxlinelen, header_name=header_name,
169*cda5da8dSAndroid Build Coastguard Worker               continuation_ws=continuation_ws)
170*cda5da8dSAndroid Build Coastguard Worker    for s, charset in decoded_seq:
171*cda5da8dSAndroid Build Coastguard Worker        # None means us-ascii but we can simply pass it on to h.append()
172*cda5da8dSAndroid Build Coastguard Worker        if charset is not None and not isinstance(charset, Charset):
173*cda5da8dSAndroid Build Coastguard Worker            charset = Charset(charset)
174*cda5da8dSAndroid Build Coastguard Worker        h.append(s, charset)
175*cda5da8dSAndroid Build Coastguard Worker    return h
176*cda5da8dSAndroid Build Coastguard Worker
177*cda5da8dSAndroid Build Coastguard Worker
178*cda5da8dSAndroid Build Coastguard Worker
179*cda5da8dSAndroid Build Coastguard Workerclass Header:
180*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, s=None, charset=None,
181*cda5da8dSAndroid Build Coastguard Worker                 maxlinelen=None, header_name=None,
182*cda5da8dSAndroid Build Coastguard Worker                 continuation_ws=' ', errors='strict'):
183*cda5da8dSAndroid Build Coastguard Worker        """Create a MIME-compliant header that can contain many character sets.
184*cda5da8dSAndroid Build Coastguard Worker
185*cda5da8dSAndroid Build Coastguard Worker        Optional s is the initial header value.  If None, the initial header
186*cda5da8dSAndroid Build Coastguard Worker        value is not set.  You can later append to the header with .append()
187*cda5da8dSAndroid Build Coastguard Worker        method calls.  s may be a byte string or a Unicode string, but see the
188*cda5da8dSAndroid Build Coastguard Worker        .append() documentation for semantics.
189*cda5da8dSAndroid Build Coastguard Worker
190*cda5da8dSAndroid Build Coastguard Worker        Optional charset serves two purposes: it has the same meaning as the
191*cda5da8dSAndroid Build Coastguard Worker        charset argument to the .append() method.  It also sets the default
192*cda5da8dSAndroid Build Coastguard Worker        character set for all subsequent .append() calls that omit the charset
193*cda5da8dSAndroid Build Coastguard Worker        argument.  If charset is not provided in the constructor, the us-ascii
194*cda5da8dSAndroid Build Coastguard Worker        charset is used both as s's initial charset and as the default for
195*cda5da8dSAndroid Build Coastguard Worker        subsequent .append() calls.
196*cda5da8dSAndroid Build Coastguard Worker
197*cda5da8dSAndroid Build Coastguard Worker        The maximum line length can be specified explicitly via maxlinelen. For
198*cda5da8dSAndroid Build Coastguard Worker        splitting the first line to a shorter value (to account for the field
199*cda5da8dSAndroid Build Coastguard Worker        header which isn't included in s, e.g. `Subject') pass in the name of
200*cda5da8dSAndroid Build Coastguard Worker        the field in header_name.  The default maxlinelen is 78 as recommended
201*cda5da8dSAndroid Build Coastguard Worker        by RFC 2822.
202*cda5da8dSAndroid Build Coastguard Worker
203*cda5da8dSAndroid Build Coastguard Worker        continuation_ws must be RFC 2822 compliant folding whitespace (usually
204*cda5da8dSAndroid Build Coastguard Worker        either a space or a hard tab) which will be prepended to continuation
205*cda5da8dSAndroid Build Coastguard Worker        lines.
206*cda5da8dSAndroid Build Coastguard Worker
207*cda5da8dSAndroid Build Coastguard Worker        errors is passed through to the .append() call.
208*cda5da8dSAndroid Build Coastguard Worker        """
209*cda5da8dSAndroid Build Coastguard Worker        if charset is None:
210*cda5da8dSAndroid Build Coastguard Worker            charset = USASCII
211*cda5da8dSAndroid Build Coastguard Worker        elif not isinstance(charset, Charset):
212*cda5da8dSAndroid Build Coastguard Worker            charset = Charset(charset)
213*cda5da8dSAndroid Build Coastguard Worker        self._charset = charset
214*cda5da8dSAndroid Build Coastguard Worker        self._continuation_ws = continuation_ws
215*cda5da8dSAndroid Build Coastguard Worker        self._chunks = []
216*cda5da8dSAndroid Build Coastguard Worker        if s is not None:
217*cda5da8dSAndroid Build Coastguard Worker            self.append(s, charset, errors)
218*cda5da8dSAndroid Build Coastguard Worker        if maxlinelen is None:
219*cda5da8dSAndroid Build Coastguard Worker            maxlinelen = MAXLINELEN
220*cda5da8dSAndroid Build Coastguard Worker        self._maxlinelen = maxlinelen
221*cda5da8dSAndroid Build Coastguard Worker        if header_name is None:
222*cda5da8dSAndroid Build Coastguard Worker            self._headerlen = 0
223*cda5da8dSAndroid Build Coastguard Worker        else:
224*cda5da8dSAndroid Build Coastguard Worker            # Take the separating colon and space into account.
225*cda5da8dSAndroid Build Coastguard Worker            self._headerlen = len(header_name) + 2
226*cda5da8dSAndroid Build Coastguard Worker
227*cda5da8dSAndroid Build Coastguard Worker    def __str__(self):
228*cda5da8dSAndroid Build Coastguard Worker        """Return the string value of the header."""
229*cda5da8dSAndroid Build Coastguard Worker        self._normalize()
230*cda5da8dSAndroid Build Coastguard Worker        uchunks = []
231*cda5da8dSAndroid Build Coastguard Worker        lastcs = None
232*cda5da8dSAndroid Build Coastguard Worker        lastspace = None
233*cda5da8dSAndroid Build Coastguard Worker        for string, charset in self._chunks:
234*cda5da8dSAndroid Build Coastguard Worker            # We must preserve spaces between encoded and non-encoded word
235*cda5da8dSAndroid Build Coastguard Worker            # boundaries, which means for us we need to add a space when we go
236*cda5da8dSAndroid Build Coastguard Worker            # from a charset to None/us-ascii, or from None/us-ascii to a
237*cda5da8dSAndroid Build Coastguard Worker            # charset.  Only do this for the second and subsequent chunks.
238*cda5da8dSAndroid Build Coastguard Worker            # Don't add a space if the None/us-ascii string already has
239*cda5da8dSAndroid Build Coastguard Worker            # a space (trailing or leading depending on transition)
240*cda5da8dSAndroid Build Coastguard Worker            nextcs = charset
241*cda5da8dSAndroid Build Coastguard Worker            if nextcs == _charset.UNKNOWN8BIT:
242*cda5da8dSAndroid Build Coastguard Worker                original_bytes = string.encode('ascii', 'surrogateescape')
243*cda5da8dSAndroid Build Coastguard Worker                string = original_bytes.decode('ascii', 'replace')
244*cda5da8dSAndroid Build Coastguard Worker            if uchunks:
245*cda5da8dSAndroid Build Coastguard Worker                hasspace = string and self._nonctext(string[0])
246*cda5da8dSAndroid Build Coastguard Worker                if lastcs not in (None, 'us-ascii'):
247*cda5da8dSAndroid Build Coastguard Worker                    if nextcs in (None, 'us-ascii') and not hasspace:
248*cda5da8dSAndroid Build Coastguard Worker                        uchunks.append(SPACE)
249*cda5da8dSAndroid Build Coastguard Worker                        nextcs = None
250*cda5da8dSAndroid Build Coastguard Worker                elif nextcs not in (None, 'us-ascii') and not lastspace:
251*cda5da8dSAndroid Build Coastguard Worker                    uchunks.append(SPACE)
252*cda5da8dSAndroid Build Coastguard Worker            lastspace = string and self._nonctext(string[-1])
253*cda5da8dSAndroid Build Coastguard Worker            lastcs = nextcs
254*cda5da8dSAndroid Build Coastguard Worker            uchunks.append(string)
255*cda5da8dSAndroid Build Coastguard Worker        return EMPTYSTRING.join(uchunks)
256*cda5da8dSAndroid Build Coastguard Worker
257*cda5da8dSAndroid Build Coastguard Worker    # Rich comparison operators for equality only.  BAW: does it make sense to
258*cda5da8dSAndroid Build Coastguard Worker    # have or explicitly disable <, <=, >, >= operators?
259*cda5da8dSAndroid Build Coastguard Worker    def __eq__(self, other):
260*cda5da8dSAndroid Build Coastguard Worker        # other may be a Header or a string.  Both are fine so coerce
261*cda5da8dSAndroid Build Coastguard Worker        # ourselves to a unicode (of the unencoded header value), swap the
262*cda5da8dSAndroid Build Coastguard Worker        # args and do another comparison.
263*cda5da8dSAndroid Build Coastguard Worker        return other == str(self)
264*cda5da8dSAndroid Build Coastguard Worker
265*cda5da8dSAndroid Build Coastguard Worker    def append(self, s, charset=None, errors='strict'):
266*cda5da8dSAndroid Build Coastguard Worker        """Append a string to the MIME header.
267*cda5da8dSAndroid Build Coastguard Worker
268*cda5da8dSAndroid Build Coastguard Worker        Optional charset, if given, should be a Charset instance or the name
269*cda5da8dSAndroid Build Coastguard Worker        of a character set (which will be converted to a Charset instance).  A
270*cda5da8dSAndroid Build Coastguard Worker        value of None (the default) means that the charset given in the
271*cda5da8dSAndroid Build Coastguard Worker        constructor is used.
272*cda5da8dSAndroid Build Coastguard Worker
273*cda5da8dSAndroid Build Coastguard Worker        s may be a byte string or a Unicode string.  If it is a byte string
274*cda5da8dSAndroid Build Coastguard Worker        (i.e. isinstance(s, str) is false), then charset is the encoding of
275*cda5da8dSAndroid Build Coastguard Worker        that byte string, and a UnicodeError will be raised if the string
276*cda5da8dSAndroid Build Coastguard Worker        cannot be decoded with that charset.  If s is a Unicode string, then
277*cda5da8dSAndroid Build Coastguard Worker        charset is a hint specifying the character set of the characters in
278*cda5da8dSAndroid Build Coastguard Worker        the string.  In either case, when producing an RFC 2822 compliant
279*cda5da8dSAndroid Build Coastguard Worker        header using RFC 2047 rules, the string will be encoded using the
280*cda5da8dSAndroid Build Coastguard Worker        output codec of the charset.  If the string cannot be encoded to the
281*cda5da8dSAndroid Build Coastguard Worker        output codec, a UnicodeError will be raised.
282*cda5da8dSAndroid Build Coastguard Worker
283*cda5da8dSAndroid Build Coastguard Worker        Optional `errors' is passed as the errors argument to the decode
284*cda5da8dSAndroid Build Coastguard Worker        call if s is a byte string.
285*cda5da8dSAndroid Build Coastguard Worker        """
286*cda5da8dSAndroid Build Coastguard Worker        if charset is None:
287*cda5da8dSAndroid Build Coastguard Worker            charset = self._charset
288*cda5da8dSAndroid Build Coastguard Worker        elif not isinstance(charset, Charset):
289*cda5da8dSAndroid Build Coastguard Worker            charset = Charset(charset)
290*cda5da8dSAndroid Build Coastguard Worker        if not isinstance(s, str):
291*cda5da8dSAndroid Build Coastguard Worker            input_charset = charset.input_codec or 'us-ascii'
292*cda5da8dSAndroid Build Coastguard Worker            if input_charset == _charset.UNKNOWN8BIT:
293*cda5da8dSAndroid Build Coastguard Worker                s = s.decode('us-ascii', 'surrogateescape')
294*cda5da8dSAndroid Build Coastguard Worker            else:
295*cda5da8dSAndroid Build Coastguard Worker                s = s.decode(input_charset, errors)
296*cda5da8dSAndroid Build Coastguard Worker        # Ensure that the bytes we're storing can be decoded to the output
297*cda5da8dSAndroid Build Coastguard Worker        # character set, otherwise an early error is raised.
298*cda5da8dSAndroid Build Coastguard Worker        output_charset = charset.output_codec or 'us-ascii'
299*cda5da8dSAndroid Build Coastguard Worker        if output_charset != _charset.UNKNOWN8BIT:
300*cda5da8dSAndroid Build Coastguard Worker            try:
301*cda5da8dSAndroid Build Coastguard Worker                s.encode(output_charset, errors)
302*cda5da8dSAndroid Build Coastguard Worker            except UnicodeEncodeError:
303*cda5da8dSAndroid Build Coastguard Worker                if output_charset!='us-ascii':
304*cda5da8dSAndroid Build Coastguard Worker                    raise
305*cda5da8dSAndroid Build Coastguard Worker                charset = UTF8
306*cda5da8dSAndroid Build Coastguard Worker        self._chunks.append((s, charset))
307*cda5da8dSAndroid Build Coastguard Worker
308*cda5da8dSAndroid Build Coastguard Worker    def _nonctext(self, s):
309*cda5da8dSAndroid Build Coastguard Worker        """True if string s is not a ctext character of RFC822.
310*cda5da8dSAndroid Build Coastguard Worker        """
311*cda5da8dSAndroid Build Coastguard Worker        return s.isspace() or s in ('(', ')', '\\')
312*cda5da8dSAndroid Build Coastguard Worker
313*cda5da8dSAndroid Build Coastguard Worker    def encode(self, splitchars=';, \t', maxlinelen=None, linesep='\n'):
314*cda5da8dSAndroid Build Coastguard Worker        r"""Encode a message header into an RFC-compliant format.
315*cda5da8dSAndroid Build Coastguard Worker
316*cda5da8dSAndroid Build Coastguard Worker        There are many issues involved in converting a given string for use in
317*cda5da8dSAndroid Build Coastguard Worker        an email header.  Only certain character sets are readable in most
318*cda5da8dSAndroid Build Coastguard Worker        email clients, and as header strings can only contain a subset of
319*cda5da8dSAndroid Build Coastguard Worker        7-bit ASCII, care must be taken to properly convert and encode (with
320*cda5da8dSAndroid Build Coastguard Worker        Base64 or quoted-printable) header strings.  In addition, there is a
321*cda5da8dSAndroid Build Coastguard Worker        75-character length limit on any given encoded header field, so
322*cda5da8dSAndroid Build Coastguard Worker        line-wrapping must be performed, even with double-byte character sets.
323*cda5da8dSAndroid Build Coastguard Worker
324*cda5da8dSAndroid Build Coastguard Worker        Optional maxlinelen specifies the maximum length of each generated
325*cda5da8dSAndroid Build Coastguard Worker        line, exclusive of the linesep string.  Individual lines may be longer
326*cda5da8dSAndroid Build Coastguard Worker        than maxlinelen if a folding point cannot be found.  The first line
327*cda5da8dSAndroid Build Coastguard Worker        will be shorter by the length of the header name plus ": " if a header
328*cda5da8dSAndroid Build Coastguard Worker        name was specified at Header construction time.  The default value for
329*cda5da8dSAndroid Build Coastguard Worker        maxlinelen is determined at header construction time.
330*cda5da8dSAndroid Build Coastguard Worker
331*cda5da8dSAndroid Build Coastguard Worker        Optional splitchars is a string containing characters which should be
332*cda5da8dSAndroid Build Coastguard Worker        given extra weight by the splitting algorithm during normal header
333*cda5da8dSAndroid Build Coastguard Worker        wrapping.  This is in very rough support of RFC 2822's `higher level
334*cda5da8dSAndroid Build Coastguard Worker        syntactic breaks':  split points preceded by a splitchar are preferred
335*cda5da8dSAndroid Build Coastguard Worker        during line splitting, with the characters preferred in the order in
336*cda5da8dSAndroid Build Coastguard Worker        which they appear in the string.  Space and tab may be included in the
337*cda5da8dSAndroid Build Coastguard Worker        string to indicate whether preference should be given to one over the
338*cda5da8dSAndroid Build Coastguard Worker        other as a split point when other split chars do not appear in the line
339*cda5da8dSAndroid Build Coastguard Worker        being split.  Splitchars does not affect RFC 2047 encoded lines.
340*cda5da8dSAndroid Build Coastguard Worker
341*cda5da8dSAndroid Build Coastguard Worker        Optional linesep is a string to be used to separate the lines of
342*cda5da8dSAndroid Build Coastguard Worker        the value.  The default value is the most useful for typical
343*cda5da8dSAndroid Build Coastguard Worker        Python applications, but it can be set to \r\n to produce RFC-compliant
344*cda5da8dSAndroid Build Coastguard Worker        line separators when needed.
345*cda5da8dSAndroid Build Coastguard Worker        """
346*cda5da8dSAndroid Build Coastguard Worker        self._normalize()
347*cda5da8dSAndroid Build Coastguard Worker        if maxlinelen is None:
348*cda5da8dSAndroid Build Coastguard Worker            maxlinelen = self._maxlinelen
349*cda5da8dSAndroid Build Coastguard Worker        # A maxlinelen of 0 means don't wrap.  For all practical purposes,
350*cda5da8dSAndroid Build Coastguard Worker        # choosing a huge number here accomplishes that and makes the
351*cda5da8dSAndroid Build Coastguard Worker        # _ValueFormatter algorithm much simpler.
352*cda5da8dSAndroid Build Coastguard Worker        if maxlinelen == 0:
353*cda5da8dSAndroid Build Coastguard Worker            maxlinelen = 1000000
354*cda5da8dSAndroid Build Coastguard Worker        formatter = _ValueFormatter(self._headerlen, maxlinelen,
355*cda5da8dSAndroid Build Coastguard Worker                                    self._continuation_ws, splitchars)
356*cda5da8dSAndroid Build Coastguard Worker        lastcs = None
357*cda5da8dSAndroid Build Coastguard Worker        hasspace = lastspace = None
358*cda5da8dSAndroid Build Coastguard Worker        for string, charset in self._chunks:
359*cda5da8dSAndroid Build Coastguard Worker            if hasspace is not None:
360*cda5da8dSAndroid Build Coastguard Worker                hasspace = string and self._nonctext(string[0])
361*cda5da8dSAndroid Build Coastguard Worker                if lastcs not in (None, 'us-ascii'):
362*cda5da8dSAndroid Build Coastguard Worker                    if not hasspace or charset not in (None, 'us-ascii'):
363*cda5da8dSAndroid Build Coastguard Worker                        formatter.add_transition()
364*cda5da8dSAndroid Build Coastguard Worker                elif charset not in (None, 'us-ascii') and not lastspace:
365*cda5da8dSAndroid Build Coastguard Worker                    formatter.add_transition()
366*cda5da8dSAndroid Build Coastguard Worker            lastspace = string and self._nonctext(string[-1])
367*cda5da8dSAndroid Build Coastguard Worker            lastcs = charset
368*cda5da8dSAndroid Build Coastguard Worker            hasspace = False
369*cda5da8dSAndroid Build Coastguard Worker            lines = string.splitlines()
370*cda5da8dSAndroid Build Coastguard Worker            if lines:
371*cda5da8dSAndroid Build Coastguard Worker                formatter.feed('', lines[0], charset)
372*cda5da8dSAndroid Build Coastguard Worker            else:
373*cda5da8dSAndroid Build Coastguard Worker                formatter.feed('', '', charset)
374*cda5da8dSAndroid Build Coastguard Worker            for line in lines[1:]:
375*cda5da8dSAndroid Build Coastguard Worker                formatter.newline()
376*cda5da8dSAndroid Build Coastguard Worker                if charset.header_encoding is not None:
377*cda5da8dSAndroid Build Coastguard Worker                    formatter.feed(self._continuation_ws, ' ' + line.lstrip(),
378*cda5da8dSAndroid Build Coastguard Worker                                   charset)
379*cda5da8dSAndroid Build Coastguard Worker                else:
380*cda5da8dSAndroid Build Coastguard Worker                    sline = line.lstrip()
381*cda5da8dSAndroid Build Coastguard Worker                    fws = line[:len(line)-len(sline)]
382*cda5da8dSAndroid Build Coastguard Worker                    formatter.feed(fws, sline, charset)
383*cda5da8dSAndroid Build Coastguard Worker            if len(lines) > 1:
384*cda5da8dSAndroid Build Coastguard Worker                formatter.newline()
385*cda5da8dSAndroid Build Coastguard Worker        if self._chunks:
386*cda5da8dSAndroid Build Coastguard Worker            formatter.add_transition()
387*cda5da8dSAndroid Build Coastguard Worker        value = formatter._str(linesep)
388*cda5da8dSAndroid Build Coastguard Worker        if _embedded_header.search(value):
389*cda5da8dSAndroid Build Coastguard Worker            raise HeaderParseError("header value appears to contain "
390*cda5da8dSAndroid Build Coastguard Worker                "an embedded header: {!r}".format(value))
391*cda5da8dSAndroid Build Coastguard Worker        return value
392*cda5da8dSAndroid Build Coastguard Worker
393*cda5da8dSAndroid Build Coastguard Worker    def _normalize(self):
394*cda5da8dSAndroid Build Coastguard Worker        # Step 1: Normalize the chunks so that all runs of identical charsets
395*cda5da8dSAndroid Build Coastguard Worker        # get collapsed into a single unicode string.
396*cda5da8dSAndroid Build Coastguard Worker        chunks = []
397*cda5da8dSAndroid Build Coastguard Worker        last_charset = None
398*cda5da8dSAndroid Build Coastguard Worker        last_chunk = []
399*cda5da8dSAndroid Build Coastguard Worker        for string, charset in self._chunks:
400*cda5da8dSAndroid Build Coastguard Worker            if charset == last_charset:
401*cda5da8dSAndroid Build Coastguard Worker                last_chunk.append(string)
402*cda5da8dSAndroid Build Coastguard Worker            else:
403*cda5da8dSAndroid Build Coastguard Worker                if last_charset is not None:
404*cda5da8dSAndroid Build Coastguard Worker                    chunks.append((SPACE.join(last_chunk), last_charset))
405*cda5da8dSAndroid Build Coastguard Worker                last_chunk = [string]
406*cda5da8dSAndroid Build Coastguard Worker                last_charset = charset
407*cda5da8dSAndroid Build Coastguard Worker        if last_chunk:
408*cda5da8dSAndroid Build Coastguard Worker            chunks.append((SPACE.join(last_chunk), last_charset))
409*cda5da8dSAndroid Build Coastguard Worker        self._chunks = chunks
410*cda5da8dSAndroid Build Coastguard Worker
411*cda5da8dSAndroid Build Coastguard Worker
412*cda5da8dSAndroid Build Coastguard Worker
413*cda5da8dSAndroid Build Coastguard Workerclass _ValueFormatter:
414*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, headerlen, maxlen, continuation_ws, splitchars):
415*cda5da8dSAndroid Build Coastguard Worker        self._maxlen = maxlen
416*cda5da8dSAndroid Build Coastguard Worker        self._continuation_ws = continuation_ws
417*cda5da8dSAndroid Build Coastguard Worker        self._continuation_ws_len = len(continuation_ws)
418*cda5da8dSAndroid Build Coastguard Worker        self._splitchars = splitchars
419*cda5da8dSAndroid Build Coastguard Worker        self._lines = []
420*cda5da8dSAndroid Build Coastguard Worker        self._current_line = _Accumulator(headerlen)
421*cda5da8dSAndroid Build Coastguard Worker
422*cda5da8dSAndroid Build Coastguard Worker    def _str(self, linesep):
423*cda5da8dSAndroid Build Coastguard Worker        self.newline()
424*cda5da8dSAndroid Build Coastguard Worker        return linesep.join(self._lines)
425*cda5da8dSAndroid Build Coastguard Worker
426*cda5da8dSAndroid Build Coastguard Worker    def __str__(self):
427*cda5da8dSAndroid Build Coastguard Worker        return self._str(NL)
428*cda5da8dSAndroid Build Coastguard Worker
429*cda5da8dSAndroid Build Coastguard Worker    def newline(self):
430*cda5da8dSAndroid Build Coastguard Worker        end_of_line = self._current_line.pop()
431*cda5da8dSAndroid Build Coastguard Worker        if end_of_line != (' ', ''):
432*cda5da8dSAndroid Build Coastguard Worker            self._current_line.push(*end_of_line)
433*cda5da8dSAndroid Build Coastguard Worker        if len(self._current_line) > 0:
434*cda5da8dSAndroid Build Coastguard Worker            if self._current_line.is_onlyws() and self._lines:
435*cda5da8dSAndroid Build Coastguard Worker                self._lines[-1] += str(self._current_line)
436*cda5da8dSAndroid Build Coastguard Worker            else:
437*cda5da8dSAndroid Build Coastguard Worker                self._lines.append(str(self._current_line))
438*cda5da8dSAndroid Build Coastguard Worker        self._current_line.reset()
439*cda5da8dSAndroid Build Coastguard Worker
440*cda5da8dSAndroid Build Coastguard Worker    def add_transition(self):
441*cda5da8dSAndroid Build Coastguard Worker        self._current_line.push(' ', '')
442*cda5da8dSAndroid Build Coastguard Worker
443*cda5da8dSAndroid Build Coastguard Worker    def feed(self, fws, string, charset):
444*cda5da8dSAndroid Build Coastguard Worker        # If the charset has no header encoding (i.e. it is an ASCII encoding)
445*cda5da8dSAndroid Build Coastguard Worker        # then we must split the header at the "highest level syntactic break"
446*cda5da8dSAndroid Build Coastguard Worker        # possible. Note that we don't have a lot of smarts about field
447*cda5da8dSAndroid Build Coastguard Worker        # syntax; we just try to break on semi-colons, then commas, then
448*cda5da8dSAndroid Build Coastguard Worker        # whitespace.  Eventually, this should be pluggable.
449*cda5da8dSAndroid Build Coastguard Worker        if charset.header_encoding is None:
450*cda5da8dSAndroid Build Coastguard Worker            self._ascii_split(fws, string, self._splitchars)
451*cda5da8dSAndroid Build Coastguard Worker            return
452*cda5da8dSAndroid Build Coastguard Worker        # Otherwise, we're doing either a Base64 or a quoted-printable
453*cda5da8dSAndroid Build Coastguard Worker        # encoding which means we don't need to split the line on syntactic
454*cda5da8dSAndroid Build Coastguard Worker        # breaks.  We can basically just find enough characters to fit on the
455*cda5da8dSAndroid Build Coastguard Worker        # current line, minus the RFC 2047 chrome.  What makes this trickier
456*cda5da8dSAndroid Build Coastguard Worker        # though is that we have to split at octet boundaries, not character
457*cda5da8dSAndroid Build Coastguard Worker        # boundaries but it's only safe to split at character boundaries so at
458*cda5da8dSAndroid Build Coastguard Worker        # best we can only get close.
459*cda5da8dSAndroid Build Coastguard Worker        encoded_lines = charset.header_encode_lines(string, self._maxlengths())
460*cda5da8dSAndroid Build Coastguard Worker        # The first element extends the current line, but if it's None then
461*cda5da8dSAndroid Build Coastguard Worker        # nothing more fit on the current line so start a new line.
462*cda5da8dSAndroid Build Coastguard Worker        try:
463*cda5da8dSAndroid Build Coastguard Worker            first_line = encoded_lines.pop(0)
464*cda5da8dSAndroid Build Coastguard Worker        except IndexError:
465*cda5da8dSAndroid Build Coastguard Worker            # There are no encoded lines, so we're done.
466*cda5da8dSAndroid Build Coastguard Worker            return
467*cda5da8dSAndroid Build Coastguard Worker        if first_line is not None:
468*cda5da8dSAndroid Build Coastguard Worker            self._append_chunk(fws, first_line)
469*cda5da8dSAndroid Build Coastguard Worker        try:
470*cda5da8dSAndroid Build Coastguard Worker            last_line = encoded_lines.pop()
471*cda5da8dSAndroid Build Coastguard Worker        except IndexError:
472*cda5da8dSAndroid Build Coastguard Worker            # There was only one line.
473*cda5da8dSAndroid Build Coastguard Worker            return
474*cda5da8dSAndroid Build Coastguard Worker        self.newline()
475*cda5da8dSAndroid Build Coastguard Worker        self._current_line.push(self._continuation_ws, last_line)
476*cda5da8dSAndroid Build Coastguard Worker        # Everything else are full lines in themselves.
477*cda5da8dSAndroid Build Coastguard Worker        for line in encoded_lines:
478*cda5da8dSAndroid Build Coastguard Worker            self._lines.append(self._continuation_ws + line)
479*cda5da8dSAndroid Build Coastguard Worker
480*cda5da8dSAndroid Build Coastguard Worker    def _maxlengths(self):
481*cda5da8dSAndroid Build Coastguard Worker        # The first line's length.
482*cda5da8dSAndroid Build Coastguard Worker        yield self._maxlen - len(self._current_line)
483*cda5da8dSAndroid Build Coastguard Worker        while True:
484*cda5da8dSAndroid Build Coastguard Worker            yield self._maxlen - self._continuation_ws_len
485*cda5da8dSAndroid Build Coastguard Worker
486*cda5da8dSAndroid Build Coastguard Worker    def _ascii_split(self, fws, string, splitchars):
487*cda5da8dSAndroid Build Coastguard Worker        # The RFC 2822 header folding algorithm is simple in principle but
488*cda5da8dSAndroid Build Coastguard Worker        # complex in practice.  Lines may be folded any place where "folding
489*cda5da8dSAndroid Build Coastguard Worker        # white space" appears by inserting a linesep character in front of the
490*cda5da8dSAndroid Build Coastguard Worker        # FWS.  The complication is that not all spaces or tabs qualify as FWS,
491*cda5da8dSAndroid Build Coastguard Worker        # and we are also supposed to prefer to break at "higher level
492*cda5da8dSAndroid Build Coastguard Worker        # syntactic breaks".  We can't do either of these without intimate
493*cda5da8dSAndroid Build Coastguard Worker        # knowledge of the structure of structured headers, which we don't have
494*cda5da8dSAndroid Build Coastguard Worker        # here.  So the best we can do here is prefer to break at the specified
495*cda5da8dSAndroid Build Coastguard Worker        # splitchars, and hope that we don't choose any spaces or tabs that
496*cda5da8dSAndroid Build Coastguard Worker        # aren't legal FWS.  (This is at least better than the old algorithm,
497*cda5da8dSAndroid Build Coastguard Worker        # where we would sometimes *introduce* FWS after a splitchar, or the
498*cda5da8dSAndroid Build Coastguard Worker        # algorithm before that, where we would turn all white space runs into
499*cda5da8dSAndroid Build Coastguard Worker        # single spaces or tabs.)
500*cda5da8dSAndroid Build Coastguard Worker        parts = re.split("(["+FWS+"]+)", fws+string)
501*cda5da8dSAndroid Build Coastguard Worker        if parts[0]:
502*cda5da8dSAndroid Build Coastguard Worker            parts[:0] = ['']
503*cda5da8dSAndroid Build Coastguard Worker        else:
504*cda5da8dSAndroid Build Coastguard Worker            parts.pop(0)
505*cda5da8dSAndroid Build Coastguard Worker        for fws, part in zip(*[iter(parts)]*2):
506*cda5da8dSAndroid Build Coastguard Worker            self._append_chunk(fws, part)
507*cda5da8dSAndroid Build Coastguard Worker
508*cda5da8dSAndroid Build Coastguard Worker    def _append_chunk(self, fws, string):
509*cda5da8dSAndroid Build Coastguard Worker        self._current_line.push(fws, string)
510*cda5da8dSAndroid Build Coastguard Worker        if len(self._current_line) > self._maxlen:
511*cda5da8dSAndroid Build Coastguard Worker            # Find the best split point, working backward from the end.
512*cda5da8dSAndroid Build Coastguard Worker            # There might be none, on a long first line.
513*cda5da8dSAndroid Build Coastguard Worker            for ch in self._splitchars:
514*cda5da8dSAndroid Build Coastguard Worker                for i in range(self._current_line.part_count()-1, 0, -1):
515*cda5da8dSAndroid Build Coastguard Worker                    if ch.isspace():
516*cda5da8dSAndroid Build Coastguard Worker                        fws = self._current_line[i][0]
517*cda5da8dSAndroid Build Coastguard Worker                        if fws and fws[0]==ch:
518*cda5da8dSAndroid Build Coastguard Worker                            break
519*cda5da8dSAndroid Build Coastguard Worker                    prevpart = self._current_line[i-1][1]
520*cda5da8dSAndroid Build Coastguard Worker                    if prevpart and prevpart[-1]==ch:
521*cda5da8dSAndroid Build Coastguard Worker                        break
522*cda5da8dSAndroid Build Coastguard Worker                else:
523*cda5da8dSAndroid Build Coastguard Worker                    continue
524*cda5da8dSAndroid Build Coastguard Worker                break
525*cda5da8dSAndroid Build Coastguard Worker            else:
526*cda5da8dSAndroid Build Coastguard Worker                fws, part = self._current_line.pop()
527*cda5da8dSAndroid Build Coastguard Worker                if self._current_line._initial_size > 0:
528*cda5da8dSAndroid Build Coastguard Worker                    # There will be a header, so leave it on a line by itself.
529*cda5da8dSAndroid Build Coastguard Worker                    self.newline()
530*cda5da8dSAndroid Build Coastguard Worker                    if not fws:
531*cda5da8dSAndroid Build Coastguard Worker                        # We don't use continuation_ws here because the whitespace
532*cda5da8dSAndroid Build Coastguard Worker                        # after a header should always be a space.
533*cda5da8dSAndroid Build Coastguard Worker                        fws = ' '
534*cda5da8dSAndroid Build Coastguard Worker                self._current_line.push(fws, part)
535*cda5da8dSAndroid Build Coastguard Worker                return
536*cda5da8dSAndroid Build Coastguard Worker            remainder = self._current_line.pop_from(i)
537*cda5da8dSAndroid Build Coastguard Worker            self._lines.append(str(self._current_line))
538*cda5da8dSAndroid Build Coastguard Worker            self._current_line.reset(remainder)
539*cda5da8dSAndroid Build Coastguard Worker
540*cda5da8dSAndroid Build Coastguard Worker
541*cda5da8dSAndroid Build Coastguard Workerclass _Accumulator(list):
542*cda5da8dSAndroid Build Coastguard Worker
543*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, initial_size=0):
544*cda5da8dSAndroid Build Coastguard Worker        self._initial_size = initial_size
545*cda5da8dSAndroid Build Coastguard Worker        super().__init__()
546*cda5da8dSAndroid Build Coastguard Worker
547*cda5da8dSAndroid Build Coastguard Worker    def push(self, fws, string):
548*cda5da8dSAndroid Build Coastguard Worker        self.append((fws, string))
549*cda5da8dSAndroid Build Coastguard Worker
550*cda5da8dSAndroid Build Coastguard Worker    def pop_from(self, i=0):
551*cda5da8dSAndroid Build Coastguard Worker        popped = self[i:]
552*cda5da8dSAndroid Build Coastguard Worker        self[i:] = []
553*cda5da8dSAndroid Build Coastguard Worker        return popped
554*cda5da8dSAndroid Build Coastguard Worker
555*cda5da8dSAndroid Build Coastguard Worker    def pop(self):
556*cda5da8dSAndroid Build Coastguard Worker        if self.part_count()==0:
557*cda5da8dSAndroid Build Coastguard Worker            return ('', '')
558*cda5da8dSAndroid Build Coastguard Worker        return super().pop()
559*cda5da8dSAndroid Build Coastguard Worker
560*cda5da8dSAndroid Build Coastguard Worker    def __len__(self):
561*cda5da8dSAndroid Build Coastguard Worker        return sum((len(fws)+len(part) for fws, part in self),
562*cda5da8dSAndroid Build Coastguard Worker                   self._initial_size)
563*cda5da8dSAndroid Build Coastguard Worker
564*cda5da8dSAndroid Build Coastguard Worker    def __str__(self):
565*cda5da8dSAndroid Build Coastguard Worker        return EMPTYSTRING.join((EMPTYSTRING.join((fws, part))
566*cda5da8dSAndroid Build Coastguard Worker                                for fws, part in self))
567*cda5da8dSAndroid Build Coastguard Worker
568*cda5da8dSAndroid Build Coastguard Worker    def reset(self, startval=None):
569*cda5da8dSAndroid Build Coastguard Worker        if startval is None:
570*cda5da8dSAndroid Build Coastguard Worker            startval = []
571*cda5da8dSAndroid Build Coastguard Worker        self[:] = startval
572*cda5da8dSAndroid Build Coastguard Worker        self._initial_size = 0
573*cda5da8dSAndroid Build Coastguard Worker
574*cda5da8dSAndroid Build Coastguard Worker    def is_onlyws(self):
575*cda5da8dSAndroid Build Coastguard Worker        return self._initial_size==0 and (not self or str(self).isspace())
576*cda5da8dSAndroid Build Coastguard Worker
577*cda5da8dSAndroid Build Coastguard Worker    def part_count(self):
578*cda5da8dSAndroid Build Coastguard Worker        return super().__len__()
579