blob: 3d48287237817eb55ecbfc6e42e6e4bbf442073e [file] [log] [blame]
Barry Warsawba925802001-09-23 03:17:28 +00001# Copyright (C) 2001 Python Software Foundation
2# Author: barry@zope.com (Barry Warsaw)
3
4"""Miscellaneous utilities.
5"""
6
Barry Warsaw9aa64352001-11-09 17:45:48 +00007import time
Barry Warsawba925802001-09-23 03:17:28 +00008import re
9
10from rfc822 import unquote, quote, parseaddr
11from rfc822 import dump_address_pair
12from rfc822 import AddrlistClass as _AddrlistClass
Barry Warsawaa79f4d2001-11-09 16:59:56 +000013from rfc822 import parsedate_tz, parsedate, mktime_tz
Barry Warsawba925802001-09-23 03:17:28 +000014
15from quopri import decodestring as _qdecode
16import base64
17
18# Intrapackage imports
19from Encoders import _bencode, _qencode
20
21COMMASPACE = ', '
22UEMPTYSTRING = u''
23
24
Barry Warsawe968ead2001-10-04 17:05:11 +000025
Barry Warsawba925802001-09-23 03:17:28 +000026# Helpers
27
28def _identity(s):
29 return s
30
31
32def _bdecode(s):
33 if not s:
34 return s
35 # We can't quite use base64.encodestring() since it tacks on a "courtesy
36 # newline". Blech!
37 if not s:
38 return s
39 hasnewline = (s[-1] == '\n')
40 value = base64.decodestring(s)
41 if not hasnewline and value[-1] == '\n':
42 return value[:-1]
43 return value
44
45
Barry Warsawe968ead2001-10-04 17:05:11 +000046
Barry Warsawba925802001-09-23 03:17:28 +000047def getaddresses(fieldvalues):
48 """Return a list of (REALNAME, EMAIL) for each fieldvalue."""
49 all = COMMASPACE.join(fieldvalues)
50 a = _AddrlistClass(all)
51 return a.getaddrlist()
52
53
Barry Warsawe968ead2001-10-04 17:05:11 +000054
Barry Warsawba925802001-09-23 03:17:28 +000055ecre = re.compile(r'''
56 =\? # literal =?
57 (?P<charset>[^?]*?) # non-greedy up to the next ? is the charset
58 \? # literal ?
59 (?P<encoding>[qb]) # either a "q" or a "b", case insensitive
60 \? # literal ?
61 (?P<atom>.*?) # non-greedy up to the next ?= is the atom
62 \?= # literal ?=
63 ''', re.VERBOSE | re.IGNORECASE)
64
65
66def decode(s):
67 """Return a decoded string according to RFC 2047, as a unicode string."""
68 rtn = []
69 parts = ecre.split(s, 1)
70 while parts:
71 # If there are less than 4 parts, it can't be encoded and we're done
72 if len(parts) < 5:
73 rtn.extend(parts)
74 break
75 # The first element is any non-encoded leading text
76 rtn.append(parts[0])
77 charset = parts[1]
Barry Warsawc44d2c52001-12-03 19:26:40 +000078 encoding = parts[2].lower()
Barry Warsawba925802001-09-23 03:17:28 +000079 atom = parts[3]
80 # The next chunk to decode should be in parts[4]
81 parts = ecre.split(parts[4])
82 # The encoding must be either `q' or `b', case-insensitive
Barry Warsawc44d2c52001-12-03 19:26:40 +000083 if encoding == 'q':
Barry Warsawba925802001-09-23 03:17:28 +000084 func = _qdecode
Barry Warsawc44d2c52001-12-03 19:26:40 +000085 elif encoding == 'b':
Barry Warsawba925802001-09-23 03:17:28 +000086 func = _bdecode
87 else:
88 func = _identity
89 # Decode and get the unicode in the charset
90 rtn.append(unicode(func(atom), charset))
91 # Now that we've decoded everything, we just need to join all the parts
92 # together into the final string.
93 return UEMPTYSTRING.join(rtn)
94
95
Barry Warsawe968ead2001-10-04 17:05:11 +000096
Barry Warsawba925802001-09-23 03:17:28 +000097def encode(s, charset='iso-8859-1', encoding='q'):
98 """Encode a string according to RFC 2047."""
Barry Warsawc44d2c52001-12-03 19:26:40 +000099 encoding = encoding.lower()
100 if encoding == 'q':
Barry Warsawba925802001-09-23 03:17:28 +0000101 estr = _qencode(s)
Barry Warsawc44d2c52001-12-03 19:26:40 +0000102 elif encoding == 'b':
Barry Warsawba925802001-09-23 03:17:28 +0000103 estr = _bencode(s)
104 else:
105 raise ValueError, 'Illegal encoding code: ' + encoding
Barry Warsawc44d2c52001-12-03 19:26:40 +0000106 return '=?%s?%s?%s?=' % (charset.lower(), encoding, estr)
Barry Warsawaa79f4d2001-11-09 16:59:56 +0000107
108
109
110def formatdate(timeval=None, localtime=0):
Barry Warsaw9cff0e62001-11-09 17:07:28 +0000111 """Returns a date string as specified by RFC 2822, e.g.:
Barry Warsawaa79f4d2001-11-09 16:59:56 +0000112
113 Fri, 09 Nov 2001 01:08:47 -0000
114
Barry Warsaw9cff0e62001-11-09 17:07:28 +0000115 Optional timeval if given is a floating point time value as accepted by
116 gmtime() and localtime(), otherwise the current time is used.
117
118 Optional localtime is a flag that when true, interprets timeval, and
119 returns a date relative to the local timezone instead of UTC, properly
120 taking daylight savings time into account.
Barry Warsawaa79f4d2001-11-09 16:59:56 +0000121 """
122 # Note: we cannot use strftime() because that honors the locale and RFC
123 # 2822 requires that day and month names be the English abbreviations.
124 if timeval is None:
125 timeval = time.time()
126 if localtime:
127 now = time.localtime(timeval)
128 # Calculate timezone offset, based on whether the local zone has
129 # daylight savings time, and whether DST is in effect.
130 if time.daylight and now[-1]:
131 offset = time.altzone
132 else:
133 offset = time.timezone
Barry Warsawe5739a62001-11-19 18:36:43 +0000134 hours, minutes = divmod(abs(offset), 3600)
135 # Remember offset is in seconds west of UTC, but the timezone is in
136 # minutes east of UTC, so the signs differ.
137 if offset > 0:
138 sign = '-'
139 else:
140 sign = '+'
141 zone = '%s%02d%02d' % (sign, hours, minutes / 60)
Barry Warsawaa79f4d2001-11-09 16:59:56 +0000142 else:
143 now = time.gmtime(timeval)
144 # Timezone offset is always -0000
145 zone = '-0000'
146 return '%s, %02d %s %04d %02d:%02d:%02d %s' % (
147 ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][now[6]],
148 now[2],
149 ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
150 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now[1] - 1],
151 now[0], now[3], now[4], now[5],
152 zone)