blob: 88fa80f57e91d7cc3a21a7f14c15960e44adc2ac [file] [log] [blame]
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001# Copyright (C) 2002-2007 Python Software Foundation
2# Author: Ben Gertzfield, Barry Warsaw
3# Contact: email-sig@python.org
4
5"""Header encoding and decoding functionality."""
6
7__all__ = [
8 'Header',
9 'decode_header',
10 'make_header',
11 ]
12
13import re
14import binascii
15
16import email.quoprimime
17import email.base64mime
18
19from email.errors import HeaderParseError
20from email.charset import Charset
21
22NL = '\n'
23SPACE = ' '
24BSPACE = b' '
25SPACE8 = ' ' * 8
26EMPTYSTRING = ''
Guido van Rossum9604e662007-08-30 03:46:43 +000027MAXLINELEN = 78
Guido van Rossum8b3febe2007-08-30 01:15:14 +000028
29USASCII = Charset('us-ascii')
30UTF8 = Charset('utf-8')
31
32# Match encoded-word strings in the form =?charset?q?Hello_World?=
33ecre = re.compile(r'''
34 =\? # literal =?
35 (?P<charset>[^?]*?) # non-greedy up to the next ? is the charset
36 \? # literal ?
37 (?P<encoding>[qb]) # either a "q" or a "b", case insensitive
38 \? # literal ?
39 (?P<encoded>.*?) # non-greedy up to the next ?= is the encoded string
40 \?= # literal ?=
41 (?=[ \t]|$) # whitespace or the end of the string
42 ''', re.VERBOSE | re.IGNORECASE | re.MULTILINE)
43
44# Field name regexp, including trailing colon, but not separating whitespace,
45# according to RFC 2822. Character range is from tilde to exclamation mark.
46# For use with .match()
47fcre = re.compile(r'[\041-\176]+:$')
48
49
50
51# Helpers
52_max_append = email.quoprimime._max_append
53
54
55
56def decode_header(header):
57 """Decode a message header value without converting charset.
58
59 Returns a list of (string, charset) pairs containing each of the decoded
60 parts of the header. Charset is None for non-encoded parts of the header,
61 otherwise a lower-case string containing the name of the character set
62 specified in the encoded string.
63
Amaury Forgeot d'Arc1c25de62009-07-12 16:43:19 +000064 An email.errors.HeaderParseError may be raised when certain decoding error
Guido van Rossum8b3febe2007-08-30 01:15:14 +000065 occurs (e.g. a base64 decoding exception).
66 """
67 # If no encoding, just return the header with no charset.
68 if not ecre.search(header):
69 return [(header, None)]
70 # First step is to parse all the encoded parts into triplets of the form
71 # (encoded_string, encoding, charset). For unencoded strings, the last
72 # two parts will be None.
73 words = []
74 for line in header.splitlines():
75 parts = ecre.split(line)
76 while parts:
77 unencoded = parts.pop(0).strip()
78 if unencoded:
79 words.append((unencoded, None, None))
80 if parts:
81 charset = parts.pop(0).lower()
82 encoding = parts.pop(0).lower()
83 encoded = parts.pop(0)
84 words.append((encoded, encoding, charset))
85 # The next step is to decode each encoded word by applying the reverse
86 # base64 or quopri transformation. decoded_words is now a list of the
87 # form (decoded_word, charset).
88 decoded_words = []
89 for encoded_string, encoding, charset in words:
90 if encoding is None:
91 # This is an unencoded word.
92 decoded_words.append((encoded_string, charset))
93 elif encoding == 'q':
94 word = email.quoprimime.header_decode(encoded_string)
95 decoded_words.append((word, charset))
96 elif encoding == 'b':
R. David Murrayc4e69cc2010-08-03 22:14:10 +000097 paderr = len(encoded_string) % 4 # Postel's law: add missing padding
98 if paderr:
99 encoded_string += '==='[:4 - paderr]
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000100 try:
101 word = email.base64mime.decode(encoded_string)
102 except binascii.Error:
103 raise HeaderParseError('Base64 decoding error')
104 else:
105 decoded_words.append((word, charset))
106 else:
107 raise AssertionError('Unexpected encoding: ' + encoding)
108 # Now convert all words to bytes and collapse consecutive runs of
109 # similarly encoded words.
110 collapsed = []
111 last_word = last_charset = None
112 for word, charset in decoded_words:
113 if isinstance(word, str):
Guido van Rossum9604e662007-08-30 03:46:43 +0000114 word = bytes(word, 'raw-unicode-escape')
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000115 if last_word is None:
116 last_word = word
117 last_charset = charset
118 elif charset != last_charset:
119 collapsed.append((last_word, last_charset))
120 last_word = word
121 last_charset = charset
122 elif last_charset is None:
123 last_word += BSPACE + word
124 else:
125 last_word += word
126 collapsed.append((last_word, last_charset))
127 return collapsed
128
129
130
131def make_header(decoded_seq, maxlinelen=None, header_name=None,
132 continuation_ws=' '):
133 """Create a Header from a sequence of pairs as returned by decode_header()
134
135 decode_header() takes a header value string and returns a sequence of
136 pairs of the format (decoded_string, charset) where charset is the string
137 name of the character set.
138
139 This function takes one of those sequence of pairs and returns a Header
140 instance. Optional maxlinelen, header_name, and continuation_ws are as in
141 the Header constructor.
142 """
143 h = Header(maxlinelen=maxlinelen, header_name=header_name,
144 continuation_ws=continuation_ws)
145 for s, charset in decoded_seq:
146 # None means us-ascii but we can simply pass it on to h.append()
147 if charset is not None and not isinstance(charset, Charset):
148 charset = Charset(charset)
149 h.append(s, charset)
150 return h
151
152
153
154class Header:
155 def __init__(self, s=None, charset=None,
156 maxlinelen=None, header_name=None,
157 continuation_ws=' ', errors='strict'):
158 """Create a MIME-compliant header that can contain many character sets.
159
160 Optional s is the initial header value. If None, the initial header
161 value is not set. You can later append to the header with .append()
162 method calls. s may be a byte string or a Unicode string, but see the
163 .append() documentation for semantics.
164
165 Optional charset serves two purposes: it has the same meaning as the
166 charset argument to the .append() method. It also sets the default
167 character set for all subsequent .append() calls that omit the charset
168 argument. If charset is not provided in the constructor, the us-ascii
169 charset is used both as s's initial charset and as the default for
170 subsequent .append() calls.
171
172 The maximum line length can be specified explicit via maxlinelen. For
173 splitting the first line to a shorter value (to account for the field
174 header which isn't included in s, e.g. `Subject') pass in the name of
Guido van Rossum9604e662007-08-30 03:46:43 +0000175 the field in header_name. The default maxlinelen is 78 as recommended
176 by RFC 2822.
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000177
178 continuation_ws must be RFC 2822 compliant folding whitespace (usually
179 either a space or a hard tab) which will be prepended to continuation
180 lines.
181
182 errors is passed through to the .append() call.
183 """
184 if charset is None:
185 charset = USASCII
186 elif not isinstance(charset, Charset):
187 charset = Charset(charset)
188 self._charset = charset
189 self._continuation_ws = continuation_ws
190 self._chunks = []
191 if s is not None:
192 self.append(s, charset, errors)
193 if maxlinelen is None:
194 maxlinelen = MAXLINELEN
195 self._maxlinelen = maxlinelen
196 if header_name is None:
197 self._headerlen = 0
198 else:
199 # Take the separating colon and space into account.
200 self._headerlen = len(header_name) + 2
201
202 def __str__(self):
203 """Return the string value of the header."""
Guido van Rossum9604e662007-08-30 03:46:43 +0000204 self._normalize()
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000205 uchunks = []
206 lastcs = None
Guido van Rossum9604e662007-08-30 03:46:43 +0000207 for string, charset in self._chunks:
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000208 # We must preserve spaces between encoded and non-encoded word
209 # boundaries, which means for us we need to add a space when we go
210 # from a charset to None/us-ascii, or from None/us-ascii to a
211 # charset. Only do this for the second and subsequent chunks.
212 nextcs = charset
213 if uchunks:
214 if lastcs not in (None, 'us-ascii'):
215 if nextcs in (None, 'us-ascii'):
216 uchunks.append(SPACE)
217 nextcs = None
218 elif nextcs not in (None, 'us-ascii'):
219 uchunks.append(SPACE)
220 lastcs = nextcs
Guido van Rossum9604e662007-08-30 03:46:43 +0000221 uchunks.append(string)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000222 return EMPTYSTRING.join(uchunks)
223
224 # Rich comparison operators for equality only. BAW: does it make sense to
225 # have or explicitly disable <, <=, >, >= operators?
226 def __eq__(self, other):
227 # other may be a Header or a string. Both are fine so coerce
Guido van Rossum9604e662007-08-30 03:46:43 +0000228 # ourselves to a unicode (of the unencoded header value), swap the
229 # args and do another comparison.
230 return other == str(self)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000231
232 def __ne__(self, other):
233 return not self == other
234
235 def append(self, s, charset=None, errors='strict'):
236 """Append a string to the MIME header.
237
238 Optional charset, if given, should be a Charset instance or the name
239 of a character set (which will be converted to a Charset instance). A
240 value of None (the default) means that the charset given in the
241 constructor is used.
242
243 s may be a byte string or a Unicode string. If it is a byte string
244 (i.e. isinstance(s, str) is true), then charset is the encoding of
245 that byte string, and a UnicodeError will be raised if the string
246 cannot be decoded with that charset. If s is a Unicode string, then
247 charset is a hint specifying the character set of the characters in
248 the string. In this case, when producing an RFC 2822 compliant header
249 using RFC 2047 rules, the Unicode string will be encoded using the
250 following charsets in order: us-ascii, the charset hint, utf-8. The
251 first character set not to provoke a UnicodeError is used.
252
253 Optional `errors' is passed as the third argument to any unicode() or
254 ustr.encode() call.
255 """
256 if charset is None:
257 charset = self._charset
258 elif not isinstance(charset, Charset):
259 charset = Charset(charset)
260 if isinstance(s, str):
261 # Convert the string from the input character set to the output
262 # character set and store the resulting bytes and the charset for
263 # composition later.
264 input_charset = charset.input_codec or 'us-ascii'
265 input_bytes = s.encode(input_charset, errors)
266 else:
267 # We already have the bytes we will store internally.
268 input_bytes = s
269 # Ensure that the bytes we're storing can be decoded to the output
270 # character set, otherwise an early error is thrown.
271 output_charset = charset.output_codec or 'us-ascii'
272 output_string = input_bytes.decode(output_charset, errors)
273 self._chunks.append((output_string, charset))
274
R. David Murray8451c4b2010-10-23 22:19:56 +0000275 def encode(self, splitchars=';, \t', maxlinelen=None, linesep='\n'):
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000276 """Encode a message header into an RFC-compliant format.
277
278 There are many issues involved in converting a given string for use in
279 an email header. Only certain character sets are readable in most
280 email clients, and as header strings can only contain a subset of
281 7-bit ASCII, care must be taken to properly convert and encode (with
282 Base64 or quoted-printable) header strings. In addition, there is a
283 75-character length limit on any given encoded header field, so
284 line-wrapping must be performed, even with double-byte character sets.
285
286 This method will do its best to convert the string to the correct
287 character set used in email, and encode and line wrap it safely with
288 the appropriate scheme for that character set.
289
290 If the given charset is not known or an error occurs during
291 conversion, this function will return the header untouched.
292
293 Optional splitchars is a string containing characters to split long
294 ASCII lines on, in rough support of RFC 2822's `highest level
295 syntactic breaks'. This doesn't affect RFC 2047 encoded lines.
R. David Murray8451c4b2010-10-23 22:19:56 +0000296
297 Optional linesep is a string to be used to separate the lines of
298 the value. The default value is the most useful for typical
299 Python applications, but it can be set to \r\n to produce RFC-compliant
300 line separators when needed.
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000301 """
302 self._normalize()
Guido van Rossum9604e662007-08-30 03:46:43 +0000303 if maxlinelen is None:
304 maxlinelen = self._maxlinelen
305 # A maxlinelen of 0 means don't wrap. For all practical purposes,
306 # choosing a huge number here accomplishes that and makes the
307 # _ValueFormatter algorithm much simpler.
308 if maxlinelen == 0:
309 maxlinelen = 1000000
310 formatter = _ValueFormatter(self._headerlen, maxlinelen,
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000311 self._continuation_ws, splitchars)
312 for string, charset in self._chunks:
313 lines = string.splitlines()
314 for line in lines:
315 formatter.feed(line, charset)
316 if len(lines) > 1:
317 formatter.newline()
Barry Warsaw00b34222007-08-31 02:35:00 +0000318 formatter.add_transition()
R. David Murray8451c4b2010-10-23 22:19:56 +0000319 return formatter._str(linesep)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000320
321 def _normalize(self):
Guido van Rossum9604e662007-08-30 03:46:43 +0000322 # Step 1: Normalize the chunks so that all runs of identical charsets
323 # get collapsed into a single unicode string.
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000324 chunks = []
325 last_charset = None
326 last_chunk = []
327 for string, charset in self._chunks:
328 if charset == last_charset:
329 last_chunk.append(string)
330 else:
331 if last_charset is not None:
332 chunks.append((SPACE.join(last_chunk), last_charset))
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000333 last_chunk = [string]
334 last_charset = charset
335 if last_chunk:
336 chunks.append((SPACE.join(last_chunk), last_charset))
337 self._chunks = chunks
338
339
340
341class _ValueFormatter:
342 def __init__(self, headerlen, maxlen, continuation_ws, splitchars):
343 self._maxlen = maxlen
344 self._continuation_ws = continuation_ws
345 self._continuation_ws_len = len(continuation_ws.replace('\t', SPACE8))
346 self._splitchars = splitchars
347 self._lines = []
348 self._current_line = _Accumulator(headerlen)
349
R. David Murray8451c4b2010-10-23 22:19:56 +0000350 def _str(self, linesep):
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000351 self.newline()
R. David Murray8451c4b2010-10-23 22:19:56 +0000352 return linesep.join(self._lines)
353
354 def __str__(self):
355 return self._str(NL)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000356
357 def newline(self):
Barry Warsaw00b34222007-08-31 02:35:00 +0000358 end_of_line = self._current_line.pop()
359 if end_of_line is not None:
360 self._current_line.push(end_of_line)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000361 if len(self._current_line) > 0:
362 self._lines.append(str(self._current_line))
363 self._current_line.reset()
364
Barry Warsaw00b34222007-08-31 02:35:00 +0000365 def add_transition(self):
366 self._current_line.push(None)
367
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000368 def feed(self, string, charset):
369 # If the string itself fits on the current line in its encoded format,
370 # then add it now and be done with it.
371 encoded_string = charset.header_encode(string)
372 if len(encoded_string) + len(self._current_line) <= self._maxlen:
373 self._current_line.push(encoded_string)
374 return
Guido van Rossum9604e662007-08-30 03:46:43 +0000375 # If the charset has no header encoding (i.e. it is an ASCII encoding)
376 # then we must split the header at the "highest level syntactic break"
377 # possible. Note that we don't have a lot of smarts about field
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000378 # syntax; we just try to break on semi-colons, then commas, then
Guido van Rossum9604e662007-08-30 03:46:43 +0000379 # whitespace. Eventually, this should be pluggable.
380 if charset.header_encoding is None:
381 for ch in self._splitchars:
382 if ch in string:
383 break
384 else:
385 ch = None
386 # If there's no available split character then regardless of
387 # whether the string fits on the line, we have to put it on a line
388 # by itself.
389 if ch is None:
390 if not self._current_line.is_onlyws():
391 self._lines.append(str(self._current_line))
392 self._current_line.reset(self._continuation_ws)
393 self._current_line.push(encoded_string)
394 else:
395 self._ascii_split(string, ch)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000396 return
Guido van Rossum9604e662007-08-30 03:46:43 +0000397 # Otherwise, we're doing either a Base64 or a quoted-printable
398 # encoding which means we don't need to split the line on syntactic
399 # breaks. We can basically just find enough characters to fit on the
400 # current line, minus the RFC 2047 chrome. What makes this trickier
401 # though is that we have to split at octet boundaries, not character
402 # boundaries but it's only safe to split at character boundaries so at
403 # best we can only get close.
404 encoded_lines = charset.header_encode_lines(string, self._maxlengths())
405 # The first element extends the current line, but if it's None then
406 # nothing more fit on the current line so start a new line.
407 try:
408 first_line = encoded_lines.pop(0)
409 except IndexError:
410 # There are no encoded lines, so we're done.
411 return
412 if first_line is not None:
413 self._current_line.push(first_line)
414 self._lines.append(str(self._current_line))
415 self._current_line.reset(self._continuation_ws)
416 try:
417 last_line = encoded_lines.pop()
418 except IndexError:
419 # There was only one line.
420 return
421 self._current_line.push(last_line)
Guido van Rossum9604e662007-08-30 03:46:43 +0000422 # Everything else are full lines in themselves.
423 for line in encoded_lines:
424 self._lines.append(self._continuation_ws + line)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000425
Guido van Rossum9604e662007-08-30 03:46:43 +0000426 def _maxlengths(self):
427 # The first line's length.
428 yield self._maxlen - len(self._current_line)
429 while True:
430 yield self._maxlen - self._continuation_ws_len
431
432 def _ascii_split(self, string, ch):
433 holding = _Accumulator()
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000434 # Split the line on the split character, preserving it. If the split
435 # character is whitespace RFC 2822 $2.2.3 requires us to fold on the
436 # whitespace, so that the line leads with the original whitespace we
437 # split on. However, if a higher syntactic break is used instead
438 # (e.g. comma or semicolon), the folding should happen after the split
439 # character. But then in that case, we need to add our own
440 # continuation whitespace -- although won't that break unfolding?
441 for part, splitpart, nextpart in _spliterator(ch, string):
442 if not splitpart:
443 # No splitpart means this is the last chunk. Put this part
444 # either on the current line or the next line depending on
445 # whether it fits.
446 holding.push(part)
447 if len(holding) + len(self._current_line) <= self._maxlen:
448 # It fits, but we're done.
449 self._current_line.push(str(holding))
450 else:
451 # It doesn't fit, but we're done. Before pushing a new
452 # line, watch out for the current line containing only
453 # whitespace.
454 holding.pop()
Guido van Rossum9604e662007-08-30 03:46:43 +0000455 if self._current_line.is_onlyws() and holding.is_onlyws():
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000456 # Don't start a new line.
457 holding.push(part)
458 part = None
459 self._current_line.push(str(holding))
460 self._lines.append(str(self._current_line))
461 if part is None:
462 self._current_line.reset()
463 else:
464 holding.reset(part)
465 self._current_line.reset(str(holding))
466 return
467 elif not nextpart:
468 # There must be some trailing split characters because we
469 # found a split character but no next part. In this case we
470 # must treat the thing to fit as the part + splitpart because
471 # if splitpart is whitespace it's not allowed to be the only
472 # thing on the line, and if it's not whitespace we must split
473 # after the syntactic break. In either case, we're done.
474 holding_prelen = len(holding)
475 holding.push(part + splitpart)
476 if len(holding) + len(self._current_line) <= self._maxlen:
477 self._current_line.push(str(holding))
478 elif holding_prelen == 0:
479 # This is the only chunk left so it has to go on the
480 # current line.
481 self._current_line.push(str(holding))
482 else:
483 save_part = holding.pop()
484 self._current_line.push(str(holding))
485 self._lines.append(str(self._current_line))
486 holding.reset(save_part)
487 self._current_line.reset(str(holding))
488 return
489 elif not part:
490 # We're leading with a split character. See if the splitpart
491 # and nextpart fits on the current line.
492 holding.push(splitpart + nextpart)
493 holding_len = len(holding)
494 # We know we're not leaving the nextpart on the stack.
495 holding.pop()
496 if holding_len + len(self._current_line) <= self._maxlen:
497 holding.push(splitpart)
498 else:
499 # It doesn't fit. Since there's no current part really
500 # the best we can do is start a new line and push the
501 # split part onto it.
502 self._current_line.push(str(holding))
503 holding.reset()
504 if len(self._current_line) > 0 and self._lines:
505 self._lines.append(str(self._current_line))
506 self._current_line.reset()
507 holding.push(splitpart)
508 else:
509 # All three parts are present. First let's see if all three
510 # parts will fit on the current line. If so, we don't need to
511 # split it.
512 holding.push(part + splitpart + nextpart)
513 holding_len = len(holding)
514 # Pop the part because we'll push nextpart on the next
515 # iteration through the loop.
516 holding.pop()
517 if holding_len + len(self._current_line) <= self._maxlen:
518 holding.push(part + splitpart)
519 else:
520 # The entire thing doesn't fit. See if we need to split
521 # before or after the split characters.
522 if splitpart.isspace():
523 # Split before whitespace. Remember that the
524 # whitespace becomes the continuation whitespace of
525 # the next line so it goes to current_line not holding.
526 holding.push(part)
527 self._current_line.push(str(holding))
528 holding.reset()
529 self._lines.append(str(self._current_line))
530 self._current_line.reset(splitpart)
531 else:
532 # Split after non-whitespace. The continuation
533 # whitespace comes from the instance variable.
534 holding.push(part + splitpart)
535 self._current_line.push(str(holding))
536 holding.reset()
537 self._lines.append(str(self._current_line))
538 if nextpart[0].isspace():
539 self._current_line.reset()
540 else:
541 self._current_line.reset(self._continuation_ws)
542 # Get the last of the holding part
543 self._current_line.push(str(holding))
544
545
546
547def _spliterator(character, string):
548 parts = list(reversed(re.split('(%s)' % character, string)))
549 while parts:
550 part = parts.pop()
551 splitparts = (parts.pop() if parts else None)
552 nextpart = (parts.pop() if parts else None)
553 yield (part, splitparts, nextpart)
554 if nextpart is not None:
555 parts.append(nextpart)
556
557
558class _Accumulator:
Guido van Rossum9604e662007-08-30 03:46:43 +0000559 def __init__(self, initial_size=0):
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000560 self._initial_size = initial_size
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000561 self._current = []
562
563 def push(self, string):
564 self._current.append(string)
565
566 def pop(self):
Barry Warsaw00b34222007-08-31 02:35:00 +0000567 if not self._current:
568 return None
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000569 return self._current.pop()
570
571 def __len__(self):
Barry Warsaw00b34222007-08-31 02:35:00 +0000572 return sum(((1 if string is None else len(string))
573 for string in self._current),
Guido van Rossum9604e662007-08-30 03:46:43 +0000574 self._initial_size)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000575
576 def __str__(self):
Barry Warsaw00b34222007-08-31 02:35:00 +0000577 if self._current and self._current[-1] is None:
578 self._current.pop()
579 return EMPTYSTRING.join((' ' if string is None else string)
580 for string in self._current)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000581
582 def reset(self, string=None):
583 self._current = []
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000584 self._initial_size = 0
585 if string is not None:
586 self.push(string)
Guido van Rossum9604e662007-08-30 03:46:43 +0000587
588 def is_onlyws(self):
589 return len(self) == 0 or str(self).isspace()