header.py 23.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# Copyright (C) 2002-2007 Python Software Foundation
# Author: Ben Gertzfield, Barry Warsaw
# Contact: email-sig@python.org

"""Header encoding and decoding functionality."""

__all__ = [
    'Header',
    'decode_header',
    'make_header',
    ]

import re
import binascii

import email.quoprimime
import email.base64mime

from email.errors import HeaderParseError
20 21
from email import charset as _charset
Charset = _charset.Charset
22 23 24 25 26 27

NL = '\n'
SPACE = ' '
BSPACE = b' '
SPACE8 = ' ' * 8
EMPTYSTRING = ''
28
MAXLINELEN = 78
29
FWS = ' \t'
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

USASCII = Charset('us-ascii')
UTF8 = Charset('utf-8')

# Match encoded-word strings in the form =?charset?q?Hello_World?=
ecre = re.compile(r'''
  =\?                   # literal =?
  (?P<charset>[^?]*?)   # non-greedy up to the next ? is the charset
  \?                    # literal ?
  (?P<encoding>[qb])    # either a "q" or a "b", case insensitive
  \?                    # literal ?
  (?P<encoded>.*?)      # non-greedy up to the next ?= is the encoded string
  \?=                   # literal ?=
  ''', re.VERBOSE | re.IGNORECASE | re.MULTILINE)

# Field name regexp, including trailing colon, but not separating whitespace,
# according to RFC 2822.  Character range is from tilde to exclamation mark.
# For use with .match()
fcre = re.compile(r'[\041-\176]+:$')

50
# Find a header embedded in a putative header value.  Used to check for
51
# header injection attack.
52
_embedded_header = re.compile(r'\n[^ \t]+:')
53

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68


# Helpers
_max_append = email.quoprimime._max_append



def decode_header(header):
    """Decode a message header value without converting charset.

    Returns a list of (string, charset) pairs containing each of the decoded
    parts of the header.  Charset is None for non-encoded parts of the header,
    otherwise a lower-case string containing the name of the character set
    specified in the encoded string.

69 70 71
    header may be a string that may or may not contain RFC2047 encoded words,
    or it may be a Header object.

72
    An email.errors.HeaderParseError may be raised when certain decoding error
73 74
    occurs (e.g. a base64 decoding exception).
    """
75
    # If it is a Header object, we can just return the encoded chunks.
76
    if hasattr(header, '_chunks'):
77 78
        return [(_charset._encode(string, str(charset)), str(charset))
                    for string, charset in header._chunks]
79 80 81 82 83 84 85 86 87
    # If no encoding, just return the header with no charset.
    if not ecre.search(header):
        return [(header, None)]
    # First step is to parse all the encoded parts into triplets of the form
    # (encoded_string, encoding, charset).  For unencoded strings, the last
    # two parts will be None.
    words = []
    for line in header.splitlines():
        parts = ecre.split(line)
88
        first = True
89
        while parts:
90 91 92 93
            unencoded = parts.pop(0)
            if first:
                unencoded = unencoded.lstrip()
                first = False
94 95 96 97 98 99 100
            if unencoded:
                words.append((unencoded, None, None))
            if parts:
                charset = parts.pop(0).lower()
                encoding = parts.pop(0).lower()
                encoded = parts.pop(0)
                words.append((encoded, encoding, charset))
101 102 103 104 105 106 107 108 109
    # Now loop over words and remove words that consist of whitespace
    # between two encoded strings.
    droplist = []
    for n, w in enumerate(words):
        if n>1 and w[1] and words[n-2][1] and words[n-1][0].isspace():
            droplist.append(n-1)
    for d in reversed(droplist):
        del words[d]

110 111 112 113 114 115 116 117 118 119 120 121
    # The next step is to decode each encoded word by applying the reverse
    # base64 or quopri transformation.  decoded_words is now a list of the
    # form (decoded_word, charset).
    decoded_words = []
    for encoded_string, encoding, charset in words:
        if encoding is None:
            # This is an unencoded word.
            decoded_words.append((encoded_string, charset))
        elif encoding == 'q':
            word = email.quoprimime.header_decode(encoded_string)
            decoded_words.append((word, charset))
        elif encoding == 'b':
122 123 124
            paderr = len(encoded_string) % 4   # Postel's law: add missing padding
            if paderr:
                encoded_string += '==='[:4 - paderr]
125 126 127 128 129 130 131 132 133 134 135 136 137 138
            try:
                word = email.base64mime.decode(encoded_string)
            except binascii.Error:
                raise HeaderParseError('Base64 decoding error')
            else:
                decoded_words.append((word, charset))
        else:
            raise AssertionError('Unexpected encoding: ' + encoding)
    # Now convert all words to bytes and collapse consecutive runs of
    # similarly encoded words.
    collapsed = []
    last_word = last_charset = None
    for word, charset in decoded_words:
        if isinstance(word, str):
139
            word = bytes(word, 'raw-unicode-escape')
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
        if last_word is None:
            last_word = word
            last_charset = charset
        elif charset != last_charset:
            collapsed.append((last_word, last_charset))
            last_word = word
            last_charset = charset
        elif last_charset is None:
            last_word += BSPACE + word
        else:
            last_word += word
    collapsed.append((last_word, last_charset))
    return collapsed



def make_header(decoded_seq, maxlinelen=None, header_name=None,
                continuation_ws=' '):
    """Create a Header from a sequence of pairs as returned by decode_header()

    decode_header() takes a header value string and returns a sequence of
    pairs of the format (decoded_string, charset) where charset is the string
    name of the character set.

    This function takes one of those sequence of pairs and returns a Header
    instance.  Optional maxlinelen, header_name, and continuation_ws are as in
    the Header constructor.
    """
    h = Header(maxlinelen=maxlinelen, header_name=header_name,
               continuation_ws=continuation_ws)
    for s, charset in decoded_seq:
        # None means us-ascii but we can simply pass it on to h.append()
        if charset is not None and not isinstance(charset, Charset):
            charset = Charset(charset)
        h.append(s, charset)
    return h



class Header:
    def __init__(self, s=None, charset=None,
                 maxlinelen=None, header_name=None,
                 continuation_ws=' ', errors='strict'):
        """Create a MIME-compliant header that can contain many character sets.

        Optional s is the initial header value.  If None, the initial header
        value is not set.  You can later append to the header with .append()
        method calls.  s may be a byte string or a Unicode string, but see the
        .append() documentation for semantics.

        Optional charset serves two purposes: it has the same meaning as the
        charset argument to the .append() method.  It also sets the default
        character set for all subsequent .append() calls that omit the charset
        argument.  If charset is not provided in the constructor, the us-ascii
        charset is used both as s's initial charset and as the default for
        subsequent .append() calls.

197
        The maximum line length can be specified explicitly via maxlinelen. For
198 199
        splitting the first line to a shorter value (to account for the field
        header which isn't included in s, e.g. `Subject') pass in the name of
200 201
        the field in header_name.  The default maxlinelen is 78 as recommended
        by RFC 2822.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228

        continuation_ws must be RFC 2822 compliant folding whitespace (usually
        either a space or a hard tab) which will be prepended to continuation
        lines.

        errors is passed through to the .append() call.
        """
        if charset is None:
            charset = USASCII
        elif not isinstance(charset, Charset):
            charset = Charset(charset)
        self._charset = charset
        self._continuation_ws = continuation_ws
        self._chunks = []
        if s is not None:
            self.append(s, charset, errors)
        if maxlinelen is None:
            maxlinelen = MAXLINELEN
        self._maxlinelen = maxlinelen
        if header_name is None:
            self._headerlen = 0
        else:
            # Take the separating colon and space into account.
            self._headerlen = len(header_name) + 2

    def __str__(self):
        """Return the string value of the header."""
229
        self._normalize()
230 231
        uchunks = []
        lastcs = None
232
        lastspace = None
233
        for string, charset in self._chunks:
234 235 236 237
            # We must preserve spaces between encoded and non-encoded word
            # boundaries, which means for us we need to add a space when we go
            # from a charset to None/us-ascii, or from None/us-ascii to a
            # charset.  Only do this for the second and subsequent chunks.
238 239
            # Don't add a space if the None/us-ascii string already has
            # a space (trailing or leading depending on transition)
240
            nextcs = charset
241 242 243
            if nextcs == _charset.UNKNOWN8BIT:
                original_bytes = string.encode('ascii', 'surrogateescape')
                string = original_bytes.decode('ascii', 'replace')
244
            if uchunks:
245
                hasspace = string and self._nonctext(string[0])
246
                if lastcs not in (None, 'us-ascii'):
247
                    if nextcs in (None, 'us-ascii') and not hasspace:
248 249
                        uchunks.append(SPACE)
                        nextcs = None
250
                elif nextcs not in (None, 'us-ascii') and not lastspace:
251
                    uchunks.append(SPACE)
252
            lastspace = string and self._nonctext(string[-1])
253
            lastcs = nextcs
254
            uchunks.append(string)
255 256 257 258 259 260
        return EMPTYSTRING.join(uchunks)

    # Rich comparison operators for equality only.  BAW: does it make sense to
    # have or explicitly disable <, <=, >, >= operators?
    def __eq__(self, other):
        # other may be a Header or a string.  Both are fine so coerce
261 262 263
        # ourselves to a unicode (of the unencoded header value), swap the
        # args and do another comparison.
        return other == str(self)
264 265 266 267 268 269 270 271 272 273

    def append(self, s, charset=None, errors='strict'):
        """Append a string to the MIME header.

        Optional charset, if given, should be a Charset instance or the name
        of a character set (which will be converted to a Charset instance).  A
        value of None (the default) means that the charset given in the
        constructor is used.

        s may be a byte string or a Unicode string.  If it is a byte string
274
        (i.e. isinstance(s, str) is false), then charset is the encoding of
275 276 277
        that byte string, and a UnicodeError will be raised if the string
        cannot be decoded with that charset.  If s is a Unicode string, then
        charset is a hint specifying the character set of the characters in
278 279 280 281
        the string.  In either case, when producing an RFC 2822 compliant
        header using RFC 2047 rules, the string will be encoded using the
        output codec of the charset.  If the string cannot be encoded to the
        output codec, a UnicodeError will be raised.
282

283 284
        Optional `errors' is passed as the errors argument to the decode
        call if s is a byte string.
285 286 287 288 289
        """
        if charset is None:
            charset = self._charset
        elif not isinstance(charset, Charset):
            charset = Charset(charset)
290
        if not isinstance(s, str):
291
            input_charset = charset.input_codec or 'us-ascii'
292 293 294 295
            if input_charset == _charset.UNKNOWN8BIT:
                s = s.decode('us-ascii', 'surrogateescape')
            else:
                s = s.decode(input_charset, errors)
296
        # Ensure that the bytes we're storing can be decoded to the output
297
        # character set, otherwise an early error is raised.
298
        output_charset = charset.output_codec or 'us-ascii'
299
        if output_charset != _charset.UNKNOWN8BIT:
300 301 302 303 304 305
            try:
                s.encode(output_charset, errors)
            except UnicodeEncodeError:
                if output_charset!='us-ascii':
                    raise
                charset = UTF8
306
        self._chunks.append((s, charset))
307

308 309 310 311 312
    def _nonctext(self, s):
        """True if string s is not a ctext character of RFC822.
        """
        return s.isspace() or s in ('(', ')', '\\')

313
    def encode(self, splitchars=';, \t', maxlinelen=None, linesep='\n'):
314
        r"""Encode a message header into an RFC-compliant format.
315 316 317 318 319 320 321 322 323

        There are many issues involved in converting a given string for use in
        an email header.  Only certain character sets are readable in most
        email clients, and as header strings can only contain a subset of
        7-bit ASCII, care must be taken to properly convert and encode (with
        Base64 or quoted-printable) header strings.  In addition, there is a
        75-character length limit on any given encoded header field, so
        line-wrapping must be performed, even with double-byte character sets.

Ezio Melotti's avatar
Ezio Melotti committed
324
        Optional maxlinelen specifies the maximum length of each generated
325 326 327 328 329
        line, exclusive of the linesep string.  Individual lines may be longer
        than maxlinelen if a folding point cannot be found.  The first line
        will be shorter by the length of the header name plus ": " if a header
        name was specified at Header construction time.  The default value for
        maxlinelen is determined at header construction time.
330

331 332 333 334 335 336 337 338 339
        Optional splitchars is a string containing characters which should be
        given extra weight by the splitting algorithm during normal header
        wrapping.  This is in very rough support of RFC 2822's `higher level
        syntactic breaks':  split points preceded by a splitchar are preferred
        during line splitting, with the characters preferred in the order in
        which they appear in the string.  Space and tab may be included in the
        string to indicate whether preference should be given to one over the
        other as a split point when other split chars do not appear in the line
        being split.  Splitchars does not affect RFC 2047 encoded lines.
340 341 342 343 344

        Optional linesep is a string to be used to separate the lines of
        the value.  The default value is the most useful for typical
        Python applications, but it can be set to \r\n to produce RFC-compliant
        line separators when needed.
345 346
        """
        self._normalize()
347 348 349 350 351 352 353 354
        if maxlinelen is None:
            maxlinelen = self._maxlinelen
        # A maxlinelen of 0 means don't wrap.  For all practical purposes,
        # choosing a huge number here accomplishes that and makes the
        # _ValueFormatter algorithm much simpler.
        if maxlinelen == 0:
            maxlinelen = 1000000
        formatter = _ValueFormatter(self._headerlen, maxlinelen,
355
                                    self._continuation_ws, splitchars)
356 357
        lastcs = None
        hasspace = lastspace = None
358
        for string, charset in self._chunks:
359 360 361 362 363 364 365 366 367 368
            if hasspace is not None:
                hasspace = string and self._nonctext(string[0])
                if lastcs not in (None, 'us-ascii'):
                    if not hasspace or charset not in (None, 'us-ascii'):
                        formatter.add_transition()
                elif charset not in (None, 'us-ascii') and not lastspace:
                    formatter.add_transition()
            lastspace = string and self._nonctext(string[-1])
            lastcs = charset
            hasspace = False
369
            lines = string.splitlines()
370 371 372 373
            if lines:
                formatter.feed('', lines[0], charset)
            else:
                formatter.feed('', '', charset)
374 375 376
            for line in lines[1:]:
                formatter.newline()
                if charset.header_encoding is not None:
377 378 379 380 381 382
                    formatter.feed(self._continuation_ws, ' ' + line.lstrip(),
                                   charset)
                else:
                    sline = line.lstrip()
                    fws = line[:len(line)-len(sline)]
                    formatter.feed(fws, sline, charset)
383 384
            if len(lines) > 1:
                formatter.newline()
385
        if self._chunks:
Barry Warsaw's avatar
Barry Warsaw committed
386
            formatter.add_transition()
387
        value = formatter._str(linesep)
388
        if _embedded_header.search(value):
389 390 391
            raise HeaderParseError("header value appears to contain "
                "an embedded header: {!r}".format(value))
        return value
392 393

    def _normalize(self):
394 395
        # Step 1: Normalize the chunks so that all runs of identical charsets
        # get collapsed into a single unicode string.
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
        chunks = []
        last_charset = None
        last_chunk = []
        for string, charset in self._chunks:
            if charset == last_charset:
                last_chunk.append(string)
            else:
                if last_charset is not None:
                    chunks.append((SPACE.join(last_chunk), last_charset))
                last_chunk = [string]
                last_charset = charset
        if last_chunk:
            chunks.append((SPACE.join(last_chunk), last_charset))
        self._chunks = chunks



class _ValueFormatter:
    def __init__(self, headerlen, maxlen, continuation_ws, splitchars):
        self._maxlen = maxlen
        self._continuation_ws = continuation_ws
417
        self._continuation_ws_len = len(continuation_ws)
418 419 420 421
        self._splitchars = splitchars
        self._lines = []
        self._current_line = _Accumulator(headerlen)

422
    def _str(self, linesep):
423
        self.newline()
424 425 426 427
        return linesep.join(self._lines)

    def __str__(self):
        return self._str(NL)
428 429

    def newline(self):
Barry Warsaw's avatar
Barry Warsaw committed
430
        end_of_line = self._current_line.pop()
431 432
        if end_of_line != (' ', ''):
            self._current_line.push(*end_of_line)
433
        if len(self._current_line) > 0:
434 435 436 437
            if self._current_line.is_onlyws():
                self._lines[-1] += str(self._current_line)
            else:
                self._lines.append(str(self._current_line))
438 439
        self._current_line.reset()

Barry Warsaw's avatar
Barry Warsaw committed
440
    def add_transition(self):
441 442 443
        self._current_line.push(' ', '')

    def feed(self, fws, string, charset):
444 445 446
        # If the charset has no header encoding (i.e. it is an ASCII encoding)
        # then we must split the header at the "highest level syntactic break"
        # possible. Note that we don't have a lot of smarts about field
447
        # syntax; we just try to break on semi-colons, then commas, then
448 449
        # whitespace.  Eventually, this should be pluggable.
        if charset.header_encoding is None:
450
            self._ascii_split(fws, string, self._splitchars)
451
            return
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
        # Otherwise, we're doing either a Base64 or a quoted-printable
        # encoding which means we don't need to split the line on syntactic
        # breaks.  We can basically just find enough characters to fit on the
        # current line, minus the RFC 2047 chrome.  What makes this trickier
        # though is that we have to split at octet boundaries, not character
        # boundaries but it's only safe to split at character boundaries so at
        # best we can only get close.
        encoded_lines = charset.header_encode_lines(string, self._maxlengths())
        # The first element extends the current line, but if it's None then
        # nothing more fit on the current line so start a new line.
        try:
            first_line = encoded_lines.pop(0)
        except IndexError:
            # There are no encoded lines, so we're done.
            return
        if first_line is not None:
468
            self._append_chunk(fws, first_line)
469 470 471 472 473
        try:
            last_line = encoded_lines.pop()
        except IndexError:
            # There was only one line.
            return
474 475
        self.newline()
        self._current_line.push(self._continuation_ws, last_line)
476 477 478 479 480 481 482 483 484 485
        # Everything else are full lines in themselves.
        for line in encoded_lines:
            self._lines.append(self._continuation_ws + line)

    def _maxlengths(self):
        # The first line's length.
        yield self._maxlen - len(self._current_line)
        while True:
            yield self._maxlen - self._continuation_ws_len

486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
    def _ascii_split(self, fws, string, splitchars):
        # The RFC 2822 header folding algorithm is simple in principle but
        # complex in practice.  Lines may be folded any place where "folding
        # white space" appears by inserting a linesep character in front of the
        # FWS.  The complication is that not all spaces or tabs qualify as FWS,
        # and we are also supposed to prefer to break at "higher level
        # syntactic breaks".  We can't do either of these without intimate
        # knowledge of the structure of structured headers, which we don't have
        # here.  So the best we can do here is prefer to break at the specified
        # splitchars, and hope that we don't choose any spaces or tabs that
        # aren't legal FWS.  (This is at least better than the old algorithm,
        # where we would sometimes *introduce* FWS after a splitchar, or the
        # algorithm before that, where we would turn all white space runs into
        # single spaces or tabs.)
        parts = re.split("(["+FWS+"]+)", fws+string)
        if parts[0]:
            parts[:0] = ['']
        else:
            parts.pop(0)
        for fws, part in zip(*[iter(parts)]*2):
            self._append_chunk(fws, part)

    def _append_chunk(self, fws, string):
        self._current_line.push(fws, string)
        if len(self._current_line) > self._maxlen:
            # Find the best split point, working backward from the end.
            # There might be none, on a long first line.
            for ch in self._splitchars:
                for i in range(self._current_line.part_count()-1, 0, -1):
                    if ch.isspace():
                        fws = self._current_line[i][0]
                        if fws and fws[0]==ch:
                            break
                    prevpart = self._current_line[i-1][1]
                    if prevpart and prevpart[-1]==ch:
                        break
522
                else:
523 524
                    continue
                break
525
            else:
526 527 528 529 530 531 532 533 534 535 536 537 538
                fws, part = self._current_line.pop()
                if self._current_line._initial_size > 0:
                    # There will be a header, so leave it on a line by itself.
                    self.newline()
                    if not fws:
                        # We don't use continuation_ws here because the whitespace
                        # after a header should always be a space.
                        fws = ' '
                self._current_line.push(fws, part)
                return
            remainder = self._current_line.pop_from(i)
            self._lines.append(str(self._current_line))
            self._current_line.reset(remainder)
539 540


541 542
class _Accumulator(list):

543
    def __init__(self, initial_size=0):
544
        self._initial_size = initial_size
545
        super().__init__()
546

547 548 549 550 551 552 553
    def push(self, fws, string):
        self.append((fws, string))

    def pop_from(self, i=0):
        popped = self[i:]
        self[i:] = []
        return popped
554 555

    def pop(self):
556 557 558
        if self.part_count()==0:
            return ('', '')
        return super().pop()
559 560

    def __len__(self):
561
        return sum((len(fws)+len(part) for fws, part in self),
562
                   self._initial_size)
563 564

    def __str__(self):
565 566
        return EMPTYSTRING.join((EMPTYSTRING.join((fws, part))
                                for fws, part in self))
567

568 569 570 571
    def reset(self, startval=None):
        if startval is None:
            startval = []
        self[:] = startval
572
        self._initial_size = 0
573 574

    def is_onlyws(self):
575 576 577 578
        return self._initial_size==0 and (not self or str(self).isspace())

    def part_count(self):
        return super().__len__()