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