1 """Some rfc822 functions taken from the new (python2.3) "email" module.
2 """
3 __docformat__ = 'restructuredtext'
5 import re
6 from string import letters, digits
7 from binascii import b2a_base64, a2b_base64
9 ecre = re.compile(r'''
10 =\? # literal =?
11 (?P<charset>[^?]*?) # non-greedy up to the next ? is the charset
12 \? # literal ?
13 (?P<encoding>[qb]) # either a "q" or a "b", case insensitive
14 \? # literal ?
15 (?P<encoded>.*?) # non-greedy up to the next ?= is the encoded string
16 \?= # literal ?=
17 ''', re.VERBOSE | re.IGNORECASE)
19 hqre = re.compile(r'^[A-z0-9!"#$%%&\'()*+,-./:;<=>?@\[\]^_`{|}~ ]+$')
21 def base64_decode(s, convert_eols=None):
22 """Decode a raw base64 string.
24 If convert_eols is set to a string value, all canonical email linefeeds,
25 e.g. "\\r\\n", in the decoded text will be converted to the value of
26 convert_eols. os.linesep is a good choice for convert_eols if you are
27 decoding a text attachment.
29 This function does not parse a full MIME header value encoded with
30 base64 (like =?iso-8895-1?b?bmloISBuaWgh?=) -- please use the high
31 level email.Header class for that functionality.
33 Taken from 'email' module
34 """
35 if not s:
36 return s
38 dec = a2b_base64(s)
39 if convert_eols:
40 return dec.replace(CRLF, convert_eols)
41 return dec
43 def unquote_match(match):
44 """Turn a match in the form ``=AB`` to the ASCII character with value
45 0xab.
47 Taken from 'email' module
48 """
49 s = match.group(0)
50 return chr(int(s[1:3], 16))
52 def qp_decode(s):
53 """Decode a string encoded with RFC 2045 MIME header 'Q' encoding.
55 This function does not parse a full MIME header value encoded with
56 quoted-printable (like =?iso-8895-1?q?Hello_World?=) -- please use
57 the high level email.Header class for that functionality.
59 Taken from 'email' module
60 """
61 s = s.replace('_', ' ')
62 return re.sub(r'=\w{2}', unquote_match, s)
64 def _decode_header(header):
65 """Decode a message header value without converting charset.
67 Returns a list of (decoded_string, charset) pairs containing each of the
68 decoded parts of the header. Charset is None for non-encoded parts of the
69 header, otherwise a lower-case string containing the name of the character
70 set specified in the encoded string.
72 Taken from 'email' module
73 """
74 # If no encoding, just return the header
75 header = str(header)
76 if not ecre.search(header):
77 return [(header, None)]
79 decoded = []
80 dec = ''
81 for line in header.splitlines():
82 # This line might not have an encoding in it
83 if not ecre.search(line):
84 decoded.append((line, None))
85 continue
87 parts = ecre.split(line)
88 while parts:
89 unenc = parts.pop(0)
90 if unenc:
91 if unenc.strip():
92 decoded.append((unenc, None))
93 if parts:
94 charset, encoding = [s.lower() for s in parts[0:2]]
95 encoded = parts[2]
96 dec = ''
97 if encoding == 'q':
98 dec = qp_decode(encoded)
99 elif encoding == 'b':
100 dec = base64_decode(encoded)
101 else:
102 dec = encoded
104 if decoded and decoded[-1][1] == charset:
105 decoded[-1] = (decoded[-1][0] + dec, decoded[-1][1])
106 else:
107 decoded.append((dec, charset))
108 del parts[0:3]
109 return decoded
111 def decode_header(hdr):
112 """ Decodes rfc2822 encoded header and return utf-8 encoded string
113 """
114 if not hdr:
115 return None
116 outs = u""
117 for section in _decode_header(hdr):
118 charset = unaliasCharset(section[1])
119 outs += unicode(section[0], charset or 'iso-8859-1', 'replace')
120 return outs.encode('utf-8')
122 def encode_header(header, charset='utf-8'):
123 """ Will encode in quoted-printable encoding only if header
124 contains non latin characters
125 """
127 # Return empty headers unchanged
128 if not header:
129 return header
131 # return plain header if it is not contains non-ascii characters
132 if hqre.match(header):
133 return header
135 quoted = ''
136 #max_encoded = 76 - len(charset) - 7
137 for c in header:
138 # Space may be represented as _ instead of =20 for readability
139 if c == ' ':
140 quoted += '_'
141 # These characters can be included verbatim
142 elif hqre.match(c):
143 quoted += c
144 # Otherwise, replace with hex value like =E2
145 else:
146 quoted += "=%02X" % ord(c)
147 plain = 0
149 return '=?%s?q?%s?=' % (charset, quoted)
151 def unaliasCharset(charset):
152 if charset:
153 return charset.lower().replace("windows-", 'cp')
154 #return charset_table.get(charset.lower(), charset)
155 return None
157 def test():
158 print encode_header("Contrary, Mary")
159 #print unaliasCharset('Windows-1251')
161 if __name__ == '__main__':
162 test()
164 # vim: et