• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import io
2import mimetypes
3import pathlib
4import sys
5import unittest.mock
6
7from test import support
8from test.support import os_helper
9from platform import win32_edition
10
11try:
12    import _winapi
13except ImportError:
14    _winapi = None
15
16
17def 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
27def tearDownModule():
28    # Restore knownfiles to its initial state
29    mimetypes.knownfiles = knownfiles
30
31
32class MimeTypesTestCase(unittest.TestCase):
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")
241class Win32MimeTypesTestCase(unittest.TestCase):
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
281class MiscTestCase(unittest.TestCase):
282    def test__all__(self):
283        support.check__all__(self, mimetypes)
284
285
286class MimetypesCliTestCase(unittest.TestCase):
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
333if __name__ == "__main__":
334    unittest.main()
335