Skip to content
This repository was archived by the owner on Mar 23, 2023. It is now read-only.

Commit 16b7b4a

Browse files
authored
Add mimetools module (#326)
1 parent 64014f0 commit 16b7b4a

File tree

3 files changed

+306
-0
lines changed

3 files changed

+306
-0
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ STDLIB_TESTS := \
9999
test/test_dict \
100100
test/test_list \
101101
test/test_md5 \
102+
test/test_mimetools \
102103
test/test_operator \
103104
test/test_quopri \
104105
test/test_rfc822 \

third_party/stdlib/mimetools.py

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
"""Various tools used by MIME-reading or MIME-writing programs."""
2+
3+
4+
import os
5+
import sys
6+
import tempfile
7+
from warnings import filterwarnings, catch_warnings
8+
with catch_warnings():
9+
if sys.py3kwarning:
10+
filterwarnings("ignore", ".*rfc822 has been removed", DeprecationWarning)
11+
import rfc822
12+
13+
from warnings import warnpy3k
14+
warnpy3k("in 3.x, mimetools has been removed in favor of the email package",
15+
stacklevel=2)
16+
17+
__all__ = ["Message","choose_boundary","encode","decode","copyliteral",
18+
"copybinary"]
19+
20+
class Message(rfc822.Message):
21+
"""A derived class of rfc822.Message that knows about MIME headers and
22+
contains some hooks for decoding encoded and multipart messages."""
23+
24+
def __init__(self, fp, seekable = 1):
25+
rfc822.Message.__init__(self, fp, seekable)
26+
self.encodingheader = \
27+
self.getheader('content-transfer-encoding')
28+
self.typeheader = \
29+
self.getheader('content-type')
30+
self.parsetype()
31+
self.parseplist()
32+
33+
def parsetype(self):
34+
str = self.typeheader
35+
if str is None:
36+
str = 'text/plain'
37+
if ';' in str:
38+
i = str.index(';')
39+
self.plisttext = str[i:]
40+
str = str[:i]
41+
else:
42+
self.plisttext = ''
43+
fields = str.split('/')
44+
for i in range(len(fields)):
45+
fields[i] = fields[i].strip().lower()
46+
self.type = '/'.join(fields)
47+
self.maintype = fields[0]
48+
self.subtype = '/'.join(fields[1:])
49+
50+
def parseplist(self):
51+
str = self.plisttext
52+
self.plist = []
53+
while str[:1] == ';':
54+
str = str[1:]
55+
if ';' in str:
56+
# XXX Should parse quotes!
57+
end = str.index(';')
58+
else:
59+
end = len(str)
60+
f = str[:end]
61+
if '=' in f:
62+
i = f.index('=')
63+
f = f[:i].strip().lower() + \
64+
'=' + f[i+1:].strip()
65+
self.plist.append(f.strip())
66+
str = str[end:]
67+
68+
def getplist(self):
69+
return self.plist
70+
71+
def getparam(self, name):
72+
name = name.lower() + '='
73+
n = len(name)
74+
for p in self.plist:
75+
if p[:n] == name:
76+
return rfc822.unquote(p[n:])
77+
return None
78+
79+
def getparamnames(self):
80+
result = []
81+
for p in self.plist:
82+
i = p.find('=')
83+
if i >= 0:
84+
result.append(p[:i].lower())
85+
return result
86+
87+
def getencoding(self):
88+
if self.encodingheader is None:
89+
return '7bit'
90+
return self.encodingheader.lower()
91+
92+
def gettype(self):
93+
return self.type
94+
95+
def getmaintype(self):
96+
return self.maintype
97+
98+
def getsubtype(self):
99+
return self.subtype
100+
101+
102+
103+
104+
# Utility functions
105+
# -----------------
106+
107+
#try:
108+
import thread
109+
#except ImportError:
110+
# import dummy_thread as thread
111+
_counter_lock = thread.allocate_lock()
112+
del thread
113+
114+
_counter = 0
115+
def _get_next_counter():
116+
global _counter
117+
_counter_lock.acquire()
118+
_counter += 1
119+
result = _counter
120+
_counter_lock.release()
121+
return result
122+
123+
_prefix = None
124+
125+
#def choose_boundary():
126+
# """Return a string usable as a multipart boundary.
127+
#
128+
# The string chosen is unique within a single program run, and
129+
# incorporates the user id (if available), process id (if available),
130+
# and current time. So it's very unlikely the returned string appears
131+
# in message text, but there's no guarantee.
132+
#
133+
# The boundary contains dots so you have to quote it in the header."""
134+
#
135+
# global _prefix
136+
# import time
137+
# if _prefix is None:
138+
# import socket
139+
# try:
140+
# hostid = socket.gethostbyname(socket.gethostname())
141+
# except socket.gaierror:
142+
# hostid = '127.0.0.1'
143+
# try:
144+
# uid = repr(os.getuid())
145+
# except AttributeError:
146+
# uid = '1'
147+
# try:
148+
# pid = repr(os.getpid())
149+
# except AttributeError:
150+
# pid = '1'
151+
# _prefix = hostid + '.' + uid + '.' + pid
152+
# return "%s.%.3f.%d" % (_prefix, time.time(), _get_next_counter())
153+
154+
155+
# Subroutines for decoding some common content-transfer-types
156+
157+
def decode(input, output, encoding):
158+
"""Decode common content-transfer-encodings (base64, quopri, uuencode)."""
159+
if encoding == 'base64':
160+
import base64
161+
return base64.decode(input, output)
162+
if encoding == 'quoted-printable':
163+
import quopri
164+
return quopri.decode(input, output)
165+
if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
166+
import uu
167+
return uu.decode(input, output)
168+
if encoding in ('7bit', '8bit'):
169+
return output.write(input.read())
170+
if encoding in decodetab:
171+
pipethrough(input, decodetab[encoding], output)
172+
else:
173+
raise ValueError, \
174+
'unknown Content-Transfer-Encoding: %s' % encoding
175+
176+
def encode(input, output, encoding):
177+
"""Encode common content-transfer-encodings (base64, quopri, uuencode)."""
178+
if encoding == 'base64':
179+
import base64
180+
return base64.encode(input, output)
181+
if encoding == 'quoted-printable':
182+
import quopri
183+
return quopri.encode(input, output, 0)
184+
if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
185+
import uu
186+
return uu.encode(input, output)
187+
if encoding in ('7bit', '8bit'):
188+
return output.write(input.read())
189+
if encoding in encodetab:
190+
pipethrough(input, encodetab[encoding], output)
191+
else:
192+
raise ValueError, \
193+
'unknown Content-Transfer-Encoding: %s' % encoding
194+
195+
# The following is no longer used for standard encodings
196+
197+
# XXX This requires that uudecode and mmencode are in $PATH
198+
199+
uudecode_pipe = '''(
200+
TEMP=/tmp/@uu.$$
201+
sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
202+
cat $TEMP
203+
rm $TEMP
204+
)'''
205+
206+
decodetab = {
207+
'uuencode': uudecode_pipe,
208+
'x-uuencode': uudecode_pipe,
209+
'uue': uudecode_pipe,
210+
'x-uue': uudecode_pipe,
211+
'quoted-printable': 'mmencode -u -q',
212+
'base64': 'mmencode -u -b',
213+
}
214+
215+
encodetab = {
216+
'x-uuencode': 'uuencode tempfile',
217+
'uuencode': 'uuencode tempfile',
218+
'x-uue': 'uuencode tempfile',
219+
'uue': 'uuencode tempfile',
220+
'quoted-printable': 'mmencode -q',
221+
'base64': 'mmencode -b',
222+
}
223+
224+
def pipeto(input, command):
225+
pipe = os.popen(command, 'w')
226+
copyliteral(input, pipe)
227+
pipe.close()
228+
229+
def pipethrough(input, command, output):
230+
(fd, tempname) = tempfile.mkstemp()
231+
temp = os.fdopen(fd, 'w')
232+
copyliteral(input, temp)
233+
temp.close()
234+
pipe = os.popen(command + ' <' + tempname, 'r')
235+
copybinary(pipe, output)
236+
pipe.close()
237+
os.unlink(tempname)
238+
239+
def copyliteral(input, output):
240+
while 1:
241+
line = input.readline()
242+
if not line: break
243+
output.write(line)
244+
245+
def copybinary(input, output):
246+
BUFSIZE = 8192
247+
while 1:
248+
line = input.read(BUFSIZE)
249+
if not line: break
250+
output.write(line)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import unittest
2+
from test import test_support
3+
4+
import string
5+
import StringIO
6+
7+
#mimetools = test_support.import_module("mimetools", deprecated=True)
8+
import mimetools
9+
10+
msgtext1 = mimetools.Message(StringIO.StringIO(
11+
"""Content-Type: text/plain; charset=iso-8859-1; format=flowed
12+
Content-Transfer-Encoding: 8bit
13+
14+
Foo!
15+
"""))
16+
17+
class MimeToolsTest(unittest.TestCase):
18+
19+
def test_decodeencode(self):
20+
start = string.ascii_letters + "=" + string.digits + "\n"
21+
for enc in ['7bit','8bit','base64','quoted-printable',
22+
'uuencode', 'x-uuencode', 'uue', 'x-uue']:
23+
i = StringIO.StringIO(start)
24+
o = StringIO.StringIO()
25+
mimetools.encode(i, o, enc)
26+
i = StringIO.StringIO(o.getvalue())
27+
o = StringIO.StringIO()
28+
mimetools.decode(i, o, enc)
29+
self.assertEqual(o.getvalue(), start)
30+
31+
@unittest.expectedFailure
32+
def test_boundary(self):
33+
s = set([""])
34+
for i in xrange(100):
35+
nb = mimetools.choose_boundary()
36+
self.assertNotIn(nb, s)
37+
s.add(nb)
38+
39+
def test_message(self):
40+
msg = mimetools.Message(StringIO.StringIO(msgtext1))
41+
self.assertEqual(msg.gettype(), "text/plain")
42+
self.assertEqual(msg.getmaintype(), "text")
43+
self.assertEqual(msg.getsubtype(), "plain")
44+
self.assertEqual(msg.getplist(), ["charset=iso-8859-1", "format=flowed"])
45+
self.assertEqual(msg.getparamnames(), ["charset", "format"])
46+
self.assertEqual(msg.getparam("charset"), "iso-8859-1")
47+
self.assertEqual(msg.getparam("format"), "flowed")
48+
self.assertEqual(msg.getparam("spam"), None)
49+
self.assertEqual(msg.getencoding(), "8bit")
50+
51+
def test_main():
52+
test_support.run_unittest(MimeToolsTest)
53+
54+
if __name__=="__main__":
55+
test_main()

0 commit comments

Comments
 (0)