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