blob: b86091b5bc848ee84603d4cef2b2ac6c760e3b61 [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]
78 encoding = parts[2]
79 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
83 if encoding.lower() == 'q':
84 func = _qdecode
85 elif encoding.lower() == 'b':
86 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."""
99 if encoding.lower() == 'q':
100 estr = _qencode(s)
101 elif encoding.lower() == 'b':
102 estr = _bencode(s)
103 else:
104 raise ValueError, 'Illegal encoding code: ' + encoding
105 return '=?%s?%s?%s?=' % (charset.lower(), encoding.lower(), estr)
Barry Warsawaa79f4d2001-11-09 16:59:56 +0000106
107
108
109def formatdate(timeval=None, localtime=0):
Barry Warsaw9cff0e62001-11-09 17:07:28 +0000110 """Returns a date string as specified by RFC 2822, e.g.:
Barry Warsawaa79f4d2001-11-09 16:59:56 +0000111
112 Fri, 09 Nov 2001 01:08:47 -0000
113
Barry Warsaw9cff0e62001-11-09 17:07:28 +0000114 Optional timeval if given is a floating point time value as accepted by
115 gmtime() and localtime(), otherwise the current time is used.
116
117 Optional localtime is a flag that when true, interprets timeval, and
118 returns a date relative to the local timezone instead of UTC, properly
119 taking daylight savings time into account.
Barry Warsawaa79f4d2001-11-09 16:59:56 +0000120 """
121 # Note: we cannot use strftime() because that honors the locale and RFC
122 # 2822 requires that day and month names be the English abbreviations.
123 if timeval is None:
124 timeval = time.time()
125 if localtime:
126 now = time.localtime(timeval)
127 # Calculate timezone offset, based on whether the local zone has
128 # daylight savings time, and whether DST is in effect.
129 if time.daylight and now[-1]:
130 offset = time.altzone
131 else:
132 offset = time.timezone
Barry Warsawe5739a62001-11-19 18:36:43 +0000133 hours, minutes = divmod(abs(offset), 3600)
134 # Remember offset is in seconds west of UTC, but the timezone is in
135 # minutes east of UTC, so the signs differ.
136 if offset > 0:
137 sign = '-'
138 else:
139 sign = '+'
140 zone = '%s%02d%02d' % (sign, hours, minutes / 60)
Barry Warsawaa79f4d2001-11-09 16:59:56 +0000141 else:
142 now = time.gmtime(timeval)
143 # Timezone offset is always -0000
144 zone = '-0000'
145 return '%s, %02d %s %04d %02d:%02d:%02d %s' % (
146 ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][now[6]],
147 now[2],
148 ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
149 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now[1] - 1],
150 now[0], now[3], now[4], now[5],
151 zone)