1 import copy
2 import os
3 import sys
4 import test.support
5 import unittest
6 from test.support import os_helper
7 from test.support import warnings_helper
8
9
10 mailcap = warnings_helper.import_deprecated('mailcap')
11
12
13 # Location of mailcap file
14 MAILCAPFILE = test.support.findfile("mailcap.txt")
15
16 # Dict to act as mock mailcap entry for this test
17 # The keys and values should match the contents of MAILCAPFILE
18 MAILCAPDICT = {
19 'application/x-movie':
20 [{'compose': 'moviemaker %s',
21 'x11-bitmap': '"/usr/lib/Zmail/bitmaps/movie.xbm"',
22 'description': '"Movie"',
23 'view': 'movieplayer %s',
24 'lineno': 4}],
25 'application/*':
26 [{'copiousoutput': '',
27 'view': 'echo "This is \\"%t\\" but is 50 \\% Greek to me" \\; cat %s',
28 'lineno': 5}],
29 'audio/basic':
30 [{'edit': 'audiocompose %s',
31 'compose': 'audiocompose %s',
32 'description': '"An audio fragment"',
33 'view': 'showaudio %s',
34 'lineno': 6}],
35 'video/mpeg':
36 [{'view': 'mpeg_play %s', 'lineno': 13}],
37 'application/postscript':
38 [{'needsterminal': '', 'view': 'ps-to-terminal %s', 'lineno': 1},
39 {'compose': 'idraw %s', 'view': 'ps-to-terminal %s', 'lineno': 2}],
40 'application/x-dvi':
41 [{'view': 'xdvi %s', 'lineno': 3}],
42 'message/external-body':
43 [{'composetyped': 'extcompose %s',
44 'description': '"A reference to data stored in an external location"',
45 'needsterminal': '',
46 'view': 'showexternal %s %{access-type} %{name} %{site} %{directory} %{mode} %{server}',
47 'lineno': 10}],
48 'text/richtext':
49 [{'test': 'test "`echo %{charset} | tr \'[A-Z]\' \'[a-z]\'`" = iso-8859-8',
50 'copiousoutput': '',
51 'view': 'shownonascii iso-8859-8 -e richtext -p %s',
52 'lineno': 11}],
53 'image/x-xwindowdump':
54 [{'view': 'display %s', 'lineno': 9}],
55 'audio/*':
56 [{'view': '/usr/local/bin/showaudio %t', 'lineno': 7}],
57 'video/*':
58 [{'view': 'animate %s', 'lineno': 12}],
59 'application/frame':
60 [{'print': '"cat %s | lp"', 'view': 'showframe %s', 'lineno': 0}],
61 'image/rgb':
62 [{'view': 'display %s', 'lineno': 8}]
63 }
64
65 # For backwards compatibility, readmailcapfile() and lookup() still support
66 # the old version of mailcapdict without line numbers.
67 MAILCAPDICT_DEPRECATED = copy.deepcopy(MAILCAPDICT)
68 for entry_list in MAILCAPDICT_DEPRECATED.values():
69 for entry in entry_list:
70 entry.pop('lineno')
71
72
73 class ESC[4;38;5;81mHelperFunctionTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
74
75 def test_listmailcapfiles(self):
76 # The return value for listmailcapfiles() will vary by system.
77 # So verify that listmailcapfiles() returns a list of strings that is of
78 # non-zero length.
79 mcfiles = mailcap.listmailcapfiles()
80 self.assertIsInstance(mcfiles, list)
81 for m in mcfiles:
82 self.assertIsInstance(m, str)
83 with os_helper.EnvironmentVarGuard() as env:
84 # According to RFC 1524, if MAILCAPS env variable exists, use that
85 # and only that.
86 if "MAILCAPS" in env:
87 env_mailcaps = env["MAILCAPS"].split(os.pathsep)
88 else:
89 env_mailcaps = ["/testdir1/.mailcap", "/testdir2/mailcap"]
90 env["MAILCAPS"] = os.pathsep.join(env_mailcaps)
91 mcfiles = mailcap.listmailcapfiles()
92 self.assertEqual(env_mailcaps, mcfiles)
93
94 def test_readmailcapfile(self):
95 # Test readmailcapfile() using test file. It should match MAILCAPDICT.
96 with open(MAILCAPFILE, 'r') as mcf:
97 with self.assertWarns(DeprecationWarning):
98 d = mailcap.readmailcapfile(mcf)
99 self.assertDictEqual(d, MAILCAPDICT_DEPRECATED)
100
101 def test_lookup(self):
102 # Test without key
103 expected = [{'view': 'animate %s', 'lineno': 12},
104 {'view': 'mpeg_play %s', 'lineno': 13}]
105 actual = mailcap.lookup(MAILCAPDICT, 'video/mpeg')
106 self.assertListEqual(expected, actual)
107
108 # Test with key
109 key = 'compose'
110 expected = [{'edit': 'audiocompose %s',
111 'compose': 'audiocompose %s',
112 'description': '"An audio fragment"',
113 'view': 'showaudio %s',
114 'lineno': 6}]
115 actual = mailcap.lookup(MAILCAPDICT, 'audio/basic', key)
116 self.assertListEqual(expected, actual)
117
118 # Test on user-defined dicts without line numbers
119 expected = [{'view': 'mpeg_play %s'}, {'view': 'animate %s'}]
120 actual = mailcap.lookup(MAILCAPDICT_DEPRECATED, 'video/mpeg')
121 self.assertListEqual(expected, actual)
122
123 def test_subst(self):
124 plist = ['id=1', 'number=2', 'total=3']
125 # test case: ([field, MIMEtype, filename, plist=[]], <expected string>)
126 test_cases = [
127 (["", "audio/*", "foo.txt"], ""),
128 (["echo foo", "audio/*", "foo.txt"], "echo foo"),
129 (["echo %s", "audio/*", "foo.txt"], "echo foo.txt"),
130 (["echo %t", "audio/wav", "foo.txt"], "echo audio/wav"),
131 (["echo \\%t", "audio/*", "foo.txt"], "echo %t"),
132 (["echo foo", "audio/*", "foo.txt", plist], "echo foo"),
133 (["echo %{total}", "audio/*", "foo.txt", plist], "echo 3")
134 ]
135 for tc in test_cases:
136 self.assertEqual(mailcap.subst(*tc[0]), tc[1])
137
138
139 class ESC[4;38;5;81mGetcapsTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
140
141 def test_mock_getcaps(self):
142 # Test mailcap.getcaps() using mock mailcap file in this dir.
143 # Temporarily override any existing system mailcap file by pointing the
144 # MAILCAPS environment variable to our mock file.
145 with os_helper.EnvironmentVarGuard() as env:
146 env["MAILCAPS"] = MAILCAPFILE
147 caps = mailcap.getcaps()
148 self.assertDictEqual(caps, MAILCAPDICT)
149
150 def test_system_mailcap(self):
151 # Test mailcap.getcaps() with mailcap file(s) on system, if any.
152 caps = mailcap.getcaps()
153 self.assertIsInstance(caps, dict)
154 mailcapfiles = mailcap.listmailcapfiles()
155 existingmcfiles = [mcf for mcf in mailcapfiles if os.path.exists(mcf)]
156 if existingmcfiles:
157 # At least 1 mailcap file exists, so test that.
158 for (k, v) in caps.items():
159 self.assertIsInstance(k, str)
160 self.assertIsInstance(v, list)
161 for e in v:
162 self.assertIsInstance(e, dict)
163 else:
164 # No mailcap files on system. getcaps() should return empty dict.
165 self.assertEqual({}, caps)
166
167
168 class ESC[4;38;5;81mFindmatchTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
169
170 def test_findmatch(self):
171
172 # default findmatch arguments
173 c = MAILCAPDICT
174 fname = "foo.txt"
175 plist = ["access-type=default", "name=john", "site=python.org",
176 "directory=/tmp", "mode=foo", "server=bar"]
177 audio_basic_entry = {
178 'edit': 'audiocompose %s',
179 'compose': 'audiocompose %s',
180 'description': '"An audio fragment"',
181 'view': 'showaudio %s',
182 'lineno': 6
183 }
184 audio_entry = {"view": "/usr/local/bin/showaudio %t", 'lineno': 7}
185 video_entry = {'view': 'animate %s', 'lineno': 12}
186 message_entry = {
187 'composetyped': 'extcompose %s',
188 'description': '"A reference to data stored in an external location"', 'needsterminal': '',
189 'view': 'showexternal %s %{access-type} %{name} %{site} %{directory} %{mode} %{server}',
190 'lineno': 10,
191 }
192
193 # test case: (findmatch args, findmatch keyword args, expected output)
194 # positional args: caps, MIMEtype
195 # keyword args: key="view", filename="/dev/null", plist=[]
196 # output: (command line, mailcap entry)
197 cases = [
198 ([{}, "video/mpeg"], {}, (None, None)),
199 ([c, "foo/bar"], {}, (None, None)),
200 ([c, "video/mpeg"], {}, ('animate /dev/null', video_entry)),
201 ([c, "audio/basic", "edit"], {}, ("audiocompose /dev/null", audio_basic_entry)),
202 ([c, "audio/basic", "compose"], {}, ("audiocompose /dev/null", audio_basic_entry)),
203 ([c, "audio/basic", "description"], {}, ('"An audio fragment"', audio_basic_entry)),
204 ([c, "audio/basic", "foobar"], {}, (None, None)),
205 ([c, "video/*"], {"filename": fname}, ("animate %s" % fname, video_entry)),
206 ([c, "audio/basic", "compose"],
207 {"filename": fname},
208 ("audiocompose %s" % fname, audio_basic_entry)),
209 ([c, "audio/basic"],
210 {"key": "description", "filename": fname},
211 ('"An audio fragment"', audio_basic_entry)),
212 ([c, "audio/wav"],
213 {"filename": fname},
214 ("/usr/local/bin/showaudio audio/wav", audio_entry)),
215 ([c, "message/external-body"],
216 {"plist": plist},
217 ("showexternal /dev/null default john python.org /tmp foo bar", message_entry))
218 ]
219 self._run_cases(cases)
220
221 @unittest.skipUnless(os.name == "posix", "Requires 'test' command on system")
222 @unittest.skipIf(sys.platform == "vxworks", "'test' command is not supported on VxWorks")
223 @unittest.skipUnless(
224 test.support.has_subprocess_support,
225 "'test' command needs process support."
226 )
227 def test_test(self):
228 # findmatch() will automatically check any "test" conditions and skip
229 # the entry if the check fails.
230 caps = {"test/pass": [{"test": "test 1 -eq 1"}],
231 "test/fail": [{"test": "test 1 -eq 0"}]}
232 # test case: (findmatch args, findmatch keyword args, expected output)
233 # positional args: caps, MIMEtype, key ("test")
234 # keyword args: N/A
235 # output: (command line, mailcap entry)
236 cases = [
237 # findmatch will return the mailcap entry for test/pass because it evaluates to true
238 ([caps, "test/pass", "test"], {}, ("test 1 -eq 1", {"test": "test 1 -eq 1"})),
239 # findmatch will return None because test/fail evaluates to false
240 ([caps, "test/fail", "test"], {}, (None, None))
241 ]
242 self._run_cases(cases)
243
244 def test_unsafe_mailcap_input(self):
245 with self.assertWarnsRegex(mailcap.UnsafeMailcapInput,
246 'Refusing to substitute parameter.*'
247 'into a shell command'):
248 unsafe_param = mailcap.subst("echo %{total}",
249 "audio/wav",
250 "foo.txt",
251 ["total=*"])
252 self.assertEqual(unsafe_param, None)
253
254 with self.assertWarnsRegex(mailcap.UnsafeMailcapInput,
255 'Refusing to substitute MIME type'
256 '.*into a shell'):
257 unsafe_mimetype = mailcap.subst("echo %t", "audio/*", "foo.txt")
258 self.assertEqual(unsafe_mimetype, None)
259
260 with self.assertWarnsRegex(mailcap.UnsafeMailcapInput,
261 'Refusing to use mailcap with filename.*'
262 'Use a safe temporary filename.'):
263 unsafe_filename = mailcap.findmatch(MAILCAPDICT,
264 "audio/wav",
265 filename="foo*.txt")
266 self.assertEqual(unsafe_filename, (None, None))
267
268 def _run_cases(self, cases):
269 for c in cases:
270 self.assertEqual(mailcap.findmatch(*c[0], **c[1]), c[2])
271
272
273 if __name__ == '__main__':
274 unittest.main()