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