1 import io
2 import mimetypes
3 import pathlib
4 import sys
5 import unittest.mock
6
7 from test import support
8 from test.support import os_helper
9 from platform import win32_edition
10
11 try:
12 import _winapi
13 except ImportError:
14 _winapi = None
15
16
17 def setUpModule():
18 global knownfiles
19 knownfiles = mimetypes.knownfiles
20
21 # Tell it we don't know about external files:
22 mimetypes.knownfiles = []
23 mimetypes.inited = False
24 mimetypes._default_mime_types()
25
26
27 def tearDownModule():
28 # Restore knownfiles to its initial state
29 mimetypes.knownfiles = knownfiles
30
31
32 class ESC[4;38;5;81mMimeTypesTestCase(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
33 def setUp(self):
34 self.db = mimetypes.MimeTypes()
35
36 def test_case_sensitivity(self):
37 eq = self.assertEqual
38 eq(self.db.guess_type("foobar.HTML"), self.db.guess_type("foobar.html"))
39 eq(self.db.guess_type("foobar.TGZ"), self.db.guess_type("foobar.tgz"))
40 eq(self.db.guess_type("foobar.tar.Z"), ("application/x-tar", "compress"))
41 eq(self.db.guess_type("foobar.tar.z"), (None, None))
42
43 def test_default_data(self):
44 eq = self.assertEqual
45 eq(self.db.guess_type("foo.html"), ("text/html", None))
46 eq(self.db.guess_type("foo.HTML"), ("text/html", None))
47 eq(self.db.guess_type("foo.tgz"), ("application/x-tar", "gzip"))
48 eq(self.db.guess_type("foo.tar.gz"), ("application/x-tar", "gzip"))
49 eq(self.db.guess_type("foo.tar.Z"), ("application/x-tar", "compress"))
50 eq(self.db.guess_type("foo.tar.bz2"), ("application/x-tar", "bzip2"))
51 eq(self.db.guess_type("foo.tar.xz"), ("application/x-tar", "xz"))
52
53 def test_data_urls(self):
54 eq = self.assertEqual
55 guess_type = self.db.guess_type
56 eq(guess_type("data:invalidDataWithoutComma"), (None, None))
57 eq(guess_type("data:,thisIsTextPlain"), ("text/plain", None))
58 eq(guess_type("data:;base64,thisIsTextPlain"), ("text/plain", None))
59 eq(guess_type("data:text/x-foo,thisIsTextXFoo"), ("text/x-foo", None))
60
61 def test_file_parsing(self):
62 eq = self.assertEqual
63 sio = io.StringIO("x-application/x-unittest pyunit\n")
64 self.db.readfp(sio)
65 eq(self.db.guess_type("foo.pyunit"),
66 ("x-application/x-unittest", None))
67 eq(self.db.guess_extension("x-application/x-unittest"), ".pyunit")
68
69 def test_read_mime_types(self):
70 eq = self.assertEqual
71
72 # Unreadable file returns None
73 self.assertIsNone(mimetypes.read_mime_types("non-existent"))
74
75 with os_helper.temp_dir() as directory:
76 data = "x-application/x-unittest pyunit\n"
77 file = pathlib.Path(directory, "sample.mimetype")
78 file.write_text(data, encoding="utf-8")
79 mime_dict = mimetypes.read_mime_types(file)
80 eq(mime_dict[".pyunit"], "x-application/x-unittest")
81
82 # bpo-41048: read_mime_types should read the rule file with 'utf-8' encoding.
83 # Not with locale encoding. _bootlocale has been imported because io.open(...)
84 # uses it.
85 data = "application/no-mans-land Fran\u00E7ais"
86 filename = "filename"
87 fp = io.StringIO(data)
88 with unittest.mock.patch.object(mimetypes, 'open',
89 return_value=fp) as mock_open:
90 mime_dict = mimetypes.read_mime_types(filename)
91 mock_open.assert_called_with(filename, encoding='utf-8')
92 eq(mime_dict[".Français"], "application/no-mans-land")
93
94 def test_non_standard_types(self):
95 eq = self.assertEqual
96 # First try strict
97 eq(self.db.guess_type('foo.xul', strict=True), (None, None))
98 eq(self.db.guess_extension('image/jpg', strict=True), None)
99 eq(self.db.guess_extension('image/webp', strict=True), None)
100 # And then non-strict
101 eq(self.db.guess_type('foo.xul', strict=False), ('text/xul', None))
102 eq(self.db.guess_type('foo.XUL', strict=False), ('text/xul', None))
103 eq(self.db.guess_type('foo.invalid', strict=False), (None, None))
104 eq(self.db.guess_extension('image/jpg', strict=False), '.jpg')
105 eq(self.db.guess_extension('image/JPG', strict=False), '.jpg')
106 eq(self.db.guess_extension('image/webp', strict=False), '.webp')
107
108 def test_filename_with_url_delimiters(self):
109 # bpo-38449: URL delimiters cases should be handled also.
110 # They would have different mime types if interpreted as URL as
111 # compared to when interpreted as filename because of the semicolon.
112 eq = self.assertEqual
113 gzip_expected = ('application/x-tar', 'gzip')
114 eq(self.db.guess_type(";1.tar.gz"), gzip_expected)
115 eq(self.db.guess_type("?1.tar.gz"), gzip_expected)
116 eq(self.db.guess_type("#1.tar.gz"), gzip_expected)
117 eq(self.db.guess_type("#1#.tar.gz"), gzip_expected)
118 eq(self.db.guess_type(";1#.tar.gz"), gzip_expected)
119 eq(self.db.guess_type(";&1=123;?.tar.gz"), gzip_expected)
120 eq(self.db.guess_type("?k1=v1&k2=v2.tar.gz"), gzip_expected)
121 eq(self.db.guess_type(r" \"\`;b&b&c |.tar.gz"), gzip_expected)
122
123 def test_guess_all_types(self):
124 # First try strict. Use a set here for testing the results because if
125 # test_urllib2 is run before test_mimetypes, global state is modified
126 # such that the 'all' set will have more items in it.
127 all = self.db.guess_all_extensions('text/plain', strict=True)
128 self.assertTrue(set(all) >= {'.bat', '.c', '.h', '.ksh', '.pl', '.txt'})
129 self.assertEqual(len(set(all)), len(all)) # no duplicates
130 # And now non-strict
131 all = self.db.guess_all_extensions('image/jpg', strict=False)
132 self.assertEqual(all, ['.jpg'])
133 # And now for no hits
134 all = self.db.guess_all_extensions('image/jpg', strict=True)
135 self.assertEqual(all, [])
136 # And now for type existing in both strict and non-strict mappings.
137 self.db.add_type('test-type', '.strict-ext')
138 self.db.add_type('test-type', '.non-strict-ext', strict=False)
139 all = self.db.guess_all_extensions('test-type', strict=False)
140 self.assertEqual(all, ['.strict-ext', '.non-strict-ext'])
141 all = self.db.guess_all_extensions('test-type')
142 self.assertEqual(all, ['.strict-ext'])
143 # Test that changing the result list does not affect the global state
144 all.append('.no-such-ext')
145 all = self.db.guess_all_extensions('test-type')
146 self.assertNotIn('.no-such-ext', all)
147
148 def test_encoding(self):
149 filename = support.findfile("mime.types")
150 mimes = mimetypes.MimeTypes([filename])
151 exts = mimes.guess_all_extensions('application/vnd.geocube+xml',
152 strict=True)
153 self.assertEqual(exts, ['.g3', '.g\xb3'])
154
155 def test_init_reinitializes(self):
156 # Issue 4936: make sure an init starts clean
157 # First, put some poison into the types table
158 mimetypes.add_type('foo/bar', '.foobar')
159 self.assertEqual(mimetypes.guess_extension('foo/bar'), '.foobar')
160 # Reinitialize
161 mimetypes.init()
162 # Poison should be gone.
163 self.assertEqual(mimetypes.guess_extension('foo/bar'), None)
164
165 @unittest.skipIf(sys.platform.startswith("win"), "Non-Windows only")
166 def test_guess_known_extensions(self):
167 # Issue 37529
168 # The test fails on Windows because Windows adds mime types from the Registry
169 # and that creates some duplicates.
170 from mimetypes import types_map
171 for v in types_map.values():
172 self.assertIsNotNone(mimetypes.guess_extension(v))
173
174 def test_preferred_extension(self):
175 def check_extensions():
176 self.assertEqual(mimetypes.guess_extension('application/octet-stream'), '.bin')
177 self.assertEqual(mimetypes.guess_extension('application/postscript'), '.ps')
178 self.assertEqual(mimetypes.guess_extension('application/vnd.apple.mpegurl'), '.m3u')
179 self.assertEqual(mimetypes.guess_extension('application/vnd.ms-excel'), '.xls')
180 self.assertEqual(mimetypes.guess_extension('application/vnd.ms-powerpoint'), '.ppt')
181 self.assertEqual(mimetypes.guess_extension('application/x-texinfo'), '.texi')
182 self.assertEqual(mimetypes.guess_extension('application/x-troff'), '.roff')
183 self.assertEqual(mimetypes.guess_extension('application/xml'), '.xsl')
184 self.assertEqual(mimetypes.guess_extension('audio/mpeg'), '.mp3')
185 self.assertEqual(mimetypes.guess_extension('image/avif'), '.avif')
186 self.assertEqual(mimetypes.guess_extension('image/jpeg'), '.jpg')
187 self.assertEqual(mimetypes.guess_extension('image/tiff'), '.tiff')
188 self.assertEqual(mimetypes.guess_extension('message/rfc822'), '.eml')
189 self.assertEqual(mimetypes.guess_extension('text/html'), '.html')
190 self.assertEqual(mimetypes.guess_extension('text/plain'), '.txt')
191 self.assertEqual(mimetypes.guess_extension('video/mpeg'), '.mpeg')
192 self.assertEqual(mimetypes.guess_extension('video/quicktime'), '.mov')
193
194 check_extensions()
195 mimetypes.init()
196 check_extensions()
197
198 def test_init_stability(self):
199 mimetypes.init()
200
201 suffix_map = mimetypes.suffix_map
202 encodings_map = mimetypes.encodings_map
203 types_map = mimetypes.types_map
204 common_types = mimetypes.common_types
205
206 mimetypes.init()
207 self.assertIsNot(suffix_map, mimetypes.suffix_map)
208 self.assertIsNot(encodings_map, mimetypes.encodings_map)
209 self.assertIsNot(types_map, mimetypes.types_map)
210 self.assertIsNot(common_types, mimetypes.common_types)
211 self.assertEqual(suffix_map, mimetypes.suffix_map)
212 self.assertEqual(encodings_map, mimetypes.encodings_map)
213 self.assertEqual(types_map, mimetypes.types_map)
214 self.assertEqual(common_types, mimetypes.common_types)
215
216 def test_path_like_ob(self):
217 filename = "LICENSE.txt"
218 filepath = pathlib.Path(filename)
219 filepath_with_abs_dir = pathlib.Path('/dir/'+filename)
220 filepath_relative = pathlib.Path('../dir/'+filename)
221 path_dir = pathlib.Path('./')
222
223 expected = self.db.guess_type(filename)
224
225 self.assertEqual(self.db.guess_type(filepath), expected)
226 self.assertEqual(self.db.guess_type(
227 filepath_with_abs_dir), expected)
228 self.assertEqual(self.db.guess_type(filepath_relative), expected)
229 self.assertEqual(self.db.guess_type(path_dir), (None, None))
230
231 def test_keywords_args_api(self):
232 self.assertEqual(self.db.guess_type(
233 url="foo.html", strict=True), ("text/html", None))
234 self.assertEqual(self.db.guess_all_extensions(
235 type='image/jpg', strict=True), [])
236 self.assertEqual(self.db.guess_extension(
237 type='image/jpg', strict=False), '.jpg')
238
239
240 @unittest.skipUnless(sys.platform.startswith("win"), "Windows only")
241 class ESC[4;38;5;81mWin32MimeTypesTestCase(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
242 def setUp(self):
243 # ensure all entries actually come from the Windows registry
244 self.original_types_map = mimetypes.types_map.copy()
245 mimetypes.types_map.clear()
246 mimetypes.init()
247 self.db = mimetypes.MimeTypes()
248
249 def tearDown(self):
250 # restore default settings
251 mimetypes.types_map.clear()
252 mimetypes.types_map.update(self.original_types_map)
253
254 @unittest.skipIf(win32_edition() in ('NanoServer', 'WindowsCoreHeadless', 'IoTEdgeOS'),
255 "MIME types registry keys unavailable")
256 def test_registry_parsing(self):
257 # the original, minimum contents of the MIME database in the
258 # Windows registry is undocumented AFAIK.
259 # Use file types that should *always* exist:
260 eq = self.assertEqual
261 eq(self.db.guess_type("foo.txt"), ("text/plain", None))
262 eq(self.db.guess_type("image.jpg"), ("image/jpeg", None))
263 eq(self.db.guess_type("image.png"), ("image/png", None))
264
265 @unittest.skipIf(not hasattr(_winapi, "_mimetypes_read_windows_registry"),
266 "read_windows_registry accelerator unavailable")
267 def test_registry_accelerator(self):
268 from_accel = {}
269 from_reg = {}
270 _winapi._mimetypes_read_windows_registry(
271 lambda v, k: from_accel.setdefault(k, set()).add(v)
272 )
273 mimetypes.MimeTypes._read_windows_registry(
274 lambda v, k: from_reg.setdefault(k, set()).add(v)
275 )
276 self.assertEqual(list(from_reg), list(from_accel))
277 for k in from_reg:
278 self.assertEqual(from_reg[k], from_accel[k])
279
280
281 class ESC[4;38;5;81mMiscTestCase(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
282 def test__all__(self):
283 support.check__all__(self, mimetypes)
284
285
286 class ESC[4;38;5;81mMimetypesCliTestCase(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
287
288 def mimetypes_cmd(self, *args, **kwargs):
289 support.patch(self, sys, "argv", [sys.executable, *args])
290 with support.captured_stdout() as output:
291 mimetypes._main()
292 return output.getvalue().strip()
293
294 def test_help_option(self):
295 support.patch(self, sys, "argv", [sys.executable, "-h"])
296 with support.captured_stdout() as output:
297 with self.assertRaises(SystemExit) as cm:
298 mimetypes._main()
299
300 self.assertIn("Usage: mimetypes.py", output.getvalue())
301 self.assertEqual(cm.exception.code, 0)
302
303 def test_invalid_option(self):
304 support.patch(self, sys, "argv", [sys.executable, "--invalid"])
305 with support.captured_stdout() as output:
306 with self.assertRaises(SystemExit) as cm:
307 mimetypes._main()
308
309 self.assertIn("Usage: mimetypes.py", output.getvalue())
310 self.assertEqual(cm.exception.code, 1)
311
312 def test_guess_extension(self):
313 eq = self.assertEqual
314
315 extension = self.mimetypes_cmd("-l", "-e", "image/jpg")
316 eq(extension, ".jpg")
317
318 extension = self.mimetypes_cmd("-e", "image/jpg")
319 eq(extension, "I don't know anything about type image/jpg")
320
321 extension = self.mimetypes_cmd("-e", "image/jpeg")
322 eq(extension, ".jpg")
323
324 def test_guess_type(self):
325 eq = self.assertEqual
326
327 type_info = self.mimetypes_cmd("-l", "foo.pic")
328 eq(type_info, "type: image/pict encoding: None")
329
330 type_info = self.mimetypes_cmd("foo.pic")
331 eq(type_info, "I don't know anything about type foo.pic")
332
333 if __name__ == "__main__":
334 unittest.main()