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