1from xdg import Mime 2import unittest 3import os.path 4import tempfile, shutil 5 6import resources 7 8example_dir = os.path.join(os.path.dirname(__file__), 'example') 9def example_file(filename): 10 return os.path.join(example_dir, filename) 11 12class MimeTestBase(unittest.TestCase): 13 def check_mimetype(self, mimetype, media, subtype): 14 self.assertEqual(mimetype.media, media) 15 self.assertEqual(mimetype.subtype, subtype) 16 17class MimeTest(MimeTestBase): 18 def test_create_mimetype(self): 19 mt1 = Mime.MIMEtype('application', 'pdf') 20 mt2 = Mime.MIMEtype('application', 'pdf') 21 self.assertEqual(id(mt1), id(mt2)) # Check caching 22 23 amr = Mime.MIMEtype('audio', 'AMR') 24 self.check_mimetype(amr, 'audio', 'amr') # Check lowercase 25 26 ogg = Mime.MIMEtype('audio/ogg') 27 self.check_mimetype(ogg, 'audio', 'ogg') # Check split on / 28 29 self.assertRaises(Exception, Mime.MIMEtype, 'audio/foo/bar') 30 31 def test_get_type_by_name(self): 32 appzip = Mime.get_type_by_name("foo.zip") 33 self.check_mimetype(appzip, 'application', 'zip') 34 35 def test_get_type_by_data(self): 36 imgpng = Mime.get_type_by_data(resources.png_data) 37 self.check_mimetype(imgpng, 'image', 'png') 38 39 def test_mimetype_repr(self): 40 mt = Mime.lookup('application', 'zip') 41 repr(mt) # Just check that this doesn't throw an error. 42 43 def test_get_type_by_contents(self): 44 tmpdir = tempfile.mkdtemp() 45 try: 46 test_file = os.path.join(tmpdir, "test") 47 with open(test_file, "wb") as f: 48 f.write(resources.png_data) 49 50 imgpng = Mime.get_type_by_contents(test_file) 51 self.check_mimetype(imgpng, 'image', 'png') 52 53 finally: 54 shutil.rmtree(tmpdir) 55 56 def test_get_type(self): 57 # File that doesn't exist - get type by name 58 imgpng = Mime.get_type(example_file("test.gif")) 59 self.check_mimetype(imgpng, 'image', 'gif') 60 61 # File that does exist - get type by contents 62 imgpng = Mime.get_type(example_file("png_file")) 63 self.check_mimetype(imgpng, 'image', 'png') 64 65 # Directory - special case 66 inodedir = Mime.get_type(example_file("subdir")) 67 self.check_mimetype(inodedir, 'inode', 'directory') 68 69 # Mystery files 70 mystery_text = Mime.get_type(example_file('mystery_text')) 71 self.check_mimetype(mystery_text, 'text', 'plain') 72 mystery_exe = Mime.get_type(example_file('mystery_exe')) 73 self.check_mimetype(mystery_exe, 'application', 'executable') 74 75 # Symlink 76 self.check_mimetype(Mime.get_type(example_file("png_symlink")), 77 'image', 'png') 78 self.check_mimetype(Mime.get_type(example_file("png_symlink"), follow=False), 79 'inode', 'symlink') 80 81 def test_get_type2(self): 82 # File that doesn't exist - use the name 83 self.check_mimetype(Mime.get_type2(example_file('test.gif')), 'image', 'gif') 84 85 # File that does exist - use the contents 86 self.check_mimetype(Mime.get_type2(example_file('png_file')), 'image', 'png') 87 88 # Does exist - use name before contents 89 self.check_mimetype(Mime.get_type2(example_file('file.png')), 'image', 'png') 90 self.check_mimetype(Mime.get_type2(example_file('word.doc')), 'application', 'msword') 91 92 # Ambiguous file extension 93 glade_mime = Mime.get_type2(example_file('glade.ui')) 94 self.assertEqual(glade_mime.media, 'application') 95 # Grumble, this is still ambiguous on some systems 96 self.assertIn(glade_mime.subtype, {'x-gtk-builder', 'x-glade'}) 97 self.check_mimetype(Mime.get_type2(example_file('qtdesigner.ui')), 'application', 'x-designer') 98 99 # text/x-python has greater weight than text/x-readme 100 self.check_mimetype(Mime.get_type2(example_file('README.py')), 'text', 'x-python') 101 102 # Directory - special filesystem object 103 self.check_mimetype(Mime.get_type2(example_file('subdir')), 'inode', 'directory') 104 105 # Mystery files: 106 mystery_missing = Mime.get_type2(example_file('mystery_missing')) 107 self.check_mimetype(mystery_missing, 'application', 'octet-stream') 108 mystery_binary = Mime.get_type2(example_file('mystery_binary')) 109 self.check_mimetype(mystery_binary, 'application', 'octet-stream') 110 mystery_text = Mime.get_type2(example_file('mystery_text')) 111 self.check_mimetype(mystery_text, 'text', 'plain') 112 mystery_exe = Mime.get_type2(example_file('mystery_exe')) 113 self.check_mimetype(mystery_exe, 'application', 'executable') 114 115 # Symlink 116 self.check_mimetype(Mime.get_type2(example_file("png_symlink")), 117 'image', 'png') 118 self.check_mimetype(Mime.get_type2(example_file("png_symlink"), follow=False), 119 'inode', 'symlink') 120 121 def test_lookup(self): 122 pdf1 = Mime.lookup("application/pdf") 123 pdf2 = Mime.lookup("application", "pdf") 124 self.assertEqual(pdf1, pdf2) 125 self.check_mimetype(pdf1, 'application', 'pdf') 126 127 def test_get_comment(self): 128 # Check these don't throw an error. One that is likely to exist: 129 Mime.MIMEtype("application", "pdf").get_comment() 130 # And one that's unlikely to exist: 131 Mime.MIMEtype("application", "ierjg").get_comment() 132 133 def test_by_name(self): 134 dot_c = Mime.get_type_by_name('foo.c') 135 self.check_mimetype(dot_c, 'text', 'x-csrc') 136 dot_C = Mime.get_type_by_name('foo.C') 137 self.check_mimetype(dot_C, 'text', 'x-c++src') 138 139 # But most names should be case insensitive 140 dot_GIF = Mime.get_type_by_name('IMAGE.GIF') 141 self.check_mimetype(dot_GIF, 'image', 'gif') 142 143 def test_canonical(self): 144 text_xml = Mime.lookup('text/xml') 145 self.check_mimetype(text_xml, 'text', 'xml') 146 self.check_mimetype(text_xml.canonical(), 'application', 'xml') 147 148 # Already is canonical 149 python = Mime.lookup('text/x-python') 150 self.check_mimetype(python.canonical(), 'text', 'x-python') 151 152 def test_inheritance(self): 153 text_python = Mime.lookup('text/x-python') 154 self.check_mimetype(text_python, 'text', 'x-python') 155 text_plain = Mime.lookup('text/plain') 156 app_executable = Mime.lookup('application/x-executable') 157 self.assertEqual(text_python.inherits_from(), set([text_plain, app_executable])) 158 159 def test_is_text(self): 160 assert Mime._is_text(b'abcdef \n') 161 assert not Mime._is_text(b'abcdef\x08') 162 assert not Mime._is_text(b'abcdef\x0e') 163 assert not Mime._is_text(b'abcdef\x1f') 164 assert not Mime._is_text(b'abcdef\x7f') 165 166 # Check nonexistant file. 167 assert not Mime.is_text_file('/fwoijorij') 168 169class MagicDBTest(MimeTestBase): 170 def setUp(self): 171 self.tmpdir = tempfile.mkdtemp() 172 self.path = os.path.join(self.tmpdir, 'mimemagic') 173 with open(self.path, 'wb') as f: 174 f.write(resources.mime_magic_db) 175 176 self.path2 = os.path.join(self.tmpdir, 'mimemagic2') 177 with open(self.path2, 'wb') as f: 178 f.write(resources.mime_magic_db2) 179 180 # Read the files 181 self.magic = Mime.MagicDB() 182 self.magic.merge_file(self.path) 183 self.magic.merge_file(self.path2) 184 self.magic.finalise() 185 186 def tearDown(self): 187 shutil.rmtree(self.tmpdir) 188 189 def test_parsing(self): 190 self.assertEqual(len(self.magic.bytype), 9) 191 192 # Check repr() doesn't throw an error 193 repr(self.magic) 194 195 prio, png = self.magic.bytype[Mime.lookup('image', 'png')][0] 196 self.assertEqual(prio, 50) 197 assert isinstance(png, Mime.MagicRule), type(png) 198 repr(png) # Check this doesn't throw an error. 199 self.assertEqual(png.start, 0) 200 self.assertEqual(png.value, b'\x89PNG') 201 self.assertEqual(png.mask, None) 202 self.assertEqual(png.also, None) 203 204 prio, jpeg = self.magic.bytype[Mime.lookup('image', 'jpeg')][0] 205 assert isinstance(jpeg, Mime.MagicMatchAny), type(jpeg) 206 self.assertEqual(len(jpeg.rules), 2) 207 self.assertEqual(jpeg.rules[0].value, b'\xff\xd8\xff') 208 209 prio, ora = self.magic.bytype[Mime.lookup('image', 'openraster')][0] 210 assert isinstance(ora, Mime.MagicRule), type(ora) 211 self.assertEqual(ora.value, b'PK\x03\x04') 212 ora1 = ora.also 213 assert ora1 is not None 214 self.assertEqual(ora1.start, 30) 215 ora2 = ora1.also 216 assert ora2 is not None 217 self.assertEqual(ora2.start, 38) 218 self.assertEqual(ora2.value, b'image/openraster') 219 220 prio, svg = self.magic.bytype[Mime.lookup('image', 'svg+xml')][0] 221 self.assertEqual(len(svg.rules), 2) 222 self.assertEqual(svg.rules[0].value, b'<!DOCTYPE svg') 223 self.assertEqual(svg.rules[0].range, 257) 224 225 prio, psd = self.magic.bytype[Mime.lookup('image', 'vnd.adobe.photoshop')][0] 226 self.assertEqual(psd.value, b'8BPS \0\0\0\0') 227 self.assertEqual(psd.mask, b'\xff\xff\xff\xff\0\0\xff\xff\xff\xff') 228 229 prio, elf = self.magic.bytype[Mime.lookup('application', 'x-executable')][0] 230 self.assertEqual(elf.value, b'\x01\x11') 231 self.assertEqual(elf.word, 2) 232 233 # Test that a newline within the value doesn't break parsing. 234 prio, madeup = self.magic.bytype[Mime.lookup('application', 'madeup')][0] 235 self.assertEqual(madeup.rules[0].value, b'ab\ncd') 236 self.assertEqual(madeup.rules[1].mask, b'\xff\xff\n\xff\xff') 237 238 prio, replaced = self.magic.bytype[Mime.lookup('application', 'tobereplaced')][0] 239 self.assertEqual(replaced.value, b'jkl') 240 241 addedrules = self.magic.bytype[Mime.lookup('application', 'tobeaddedto')] 242 self.assertEqual(len(addedrules), 2) 243 self.assertEqual(addedrules[1][1].value, b'pqr') 244 245 def test_match_data(self): 246 res = self.magic.match_data(resources.png_data) 247 self.check_mimetype(res, 'image', 'png') 248 249 # Denied by min or max priority 250 notpng_max40 = self.magic.match_data(resources.png_data, max_pri=40) 251 assert notpng_max40 is None, notpng_max40 252 notpng_min60 = self.magic.match_data(resources.png_data, min_pri=60) 253 assert notpng_min60 is None, notpng_min60 254 255 # With list of options 256 options = [Mime.lookup('image', 'nonexistant'), # Missing MIMEtype should be dropped 257 Mime.lookup('image','png'), Mime.lookup('image', 'jpeg')] 258 res = self.magic.match_data(resources.png_data, possible=options) 259 self.check_mimetype(res, 'image', 'png') 260 261 # Non matching 262 res = self.magic.match_data(b'oiejgoethetrkjgnwefergoijekngjekg') 263 assert res is None, res 264 265 def test_match_nested(self): 266 data = b'PK\x03\x04' + (b' ' * 26) + b'mimetype' + b'image/openraster' 267 res = self.magic.match_data(data) 268 self.check_mimetype(res, 'image', 'openraster') 269 270 def test_match_file(self): 271 png_file = os.path.join(self.tmpdir, 'image') 272 with open(png_file, 'wb') as f: 273 f.write(resources.png_data) 274 275 res = self.magic.match(png_file) 276 self.check_mimetype(res, 'image', 'png') 277 278 # With list of options 279 options = [Mime.lookup('image','png'), Mime.lookup('image', 'jpeg'), 280 Mime.lookup('image', 'nonexistant')] # Missing MIMEtype should be dropped 281 res = self.magic.match(png_file, possible=options) 282 self.check_mimetype(res, 'image', 'png') 283 284 # Nonexistant file 285 path = os.path.join(self.tmpdir, 'nonexistant') 286 self.assertRaises(IOError, self.magic.match, path) 287 288_l = Mime.lookup 289 290class GlobDBTest(MimeTestBase): 291 allglobs = {_l('text/x-makefile'): [(50, 'makefile', [])], 292 _l('application/x-core'): [(50, 'core', ['cs']), (50, 'core', [])], 293 _l('text/x-c++src'): [(50, '*.C', ['cs'])], 294 _l('text/x-csrc'): [(50, '*.c', ['cs'])], 295 _l('text/x-python'): [(50, '*.py', [])], 296 _l('text/x-python'): [(50, '*.py', [])], # Check not added 2x 297 _l('video/x-anim'): [(50, '*.anim[1-9j]', [])], 298 _l('text/x-readme'): [(10, 'readme*', [])], 299 _l('text/x-readme2'): [(20, 'readme2*', [])], 300 _l('image/jpeg'): [(50, '*.jpg', []), (50, '*.jpeg', [])], 301 } 302 303 def setUp(self): 304 self.globs = Mime.GlobDB() 305 self.globs.allglobs = self.allglobs 306 self.globs.finalise() 307 308 def test_build_globdb(self): 309 globs = self.globs 310 311 self.assertEqual(len(globs.cased_literals), 1) 312 assert 'core' in globs.cased_literals, globs.cased_literals 313 314 literals = globs.literals 315 self.assertEqual(len(literals), 2) 316 assert 'core' in literals, literals 317 assert 'makefile' in literals, literals 318 319 cexts = globs.cased_exts 320 self.assertEqual(len(cexts), 2) 321 assert 'C' in cexts, cexts 322 assert 'c' in cexts, cexts 323 324 exts = globs.exts 325 self.assertEqual(len(exts), 3) 326 assert 'py' in exts, exts 327 self.assertEqual(exts['py'], [(_l('text/x-python'), 50)] ) 328 assert 'jpeg' in exts, exts 329 assert 'jpg' in exts, exts 330 331 pats = globs.globs 332 self.assertEqual(len(pats), 3) 333 self.assertEqual(pats[0][1], _l('video', 'x-anim')) 334 self.assertEqual(pats[1][1], _l('text', 'x-readme2')) 335 336 def test_first_match(self): 337 g = self.globs 338 339 self.check_mimetype(g.first_match('Makefile'), 'text', 'x-makefile') 340 self.check_mimetype(g.first_match('core'), 'application', 'x-core') 341 self.check_mimetype(g.first_match('foo.C'), 'text', 'x-c++src') 342 self.check_mimetype(g.first_match('foo.c'), 'text', 'x-csrc') 343 self.check_mimetype(g.first_match('foo.py'), 'text', 'x-python') 344 self.check_mimetype(g.first_match('foo.Anim4'), 'video', 'x-anim') 345 self.check_mimetype(g.first_match('README.txt'), 'text', 'x-readme') 346 self.check_mimetype(g.first_match('README2.txt'), 'text', 'x-readme2') 347 self.check_mimetype(g.first_match('README'), 'text', 'x-readme') 348 349 qrte = g.first_match('qrte') 350 assert qrte is None, qrte 351 352 def test_all_matches(self): 353 g = self.globs 354 355 self.assertEqual(g.all_matches('qrte'), []) 356 357 self.assertEqual(g.all_matches('Makefile'), 358 [(_l('text', 'x-makefile'), 50)]) 359 360 self.assertEqual(g.all_matches('readme2.rst'), 361 [(_l('text', 'x-readme2'), 20), 362 (_l('text', 'x-readme'), 10)] 363 ) 364 365 def test_get_extensions(self): 366 Mime.globs = self.globs 367 Mime._cache_uptodate = True 368 369 try: 370 get_ext = Mime.get_extensions 371 self.assertEqual(get_ext(_l('text/x-python')), set(['py'])) 372 self.assertEqual(get_ext(_l('image/jpeg')), set(['jpg', 'jpeg'])) 373 self.assertEqual(get_ext(_l('image/inary')), set()) 374 finally: 375 # Ensure that future tests will re-cache the database. 376 Mime._cache_uptodate = False 377 378 379class GlobsParsingTest(MimeTestBase): 380 def setUp(self): 381 self.tmpdir = tempfile.mkdtemp() 382 383 def tearDown(self): 384 shutil.rmtree(self.tmpdir) 385 386 def test_parsing(self): 387 p1 = os.path.join(self.tmpdir, 'globs2a') 388 with open(p1, 'w') as f: 389 f.write(resources.mime_globs2_a) 390 391 p2 = os.path.join(self.tmpdir, 'globs2b') 392 with open(p2, 'w') as f: 393 f.write(resources.mime_globs2_b) 394 395 globs = Mime.GlobDB() 396 globs.merge_file(p1) 397 globs.merge_file(p2) 398 399 ag = globs.allglobs 400 self.assertEqual(ag[_l('text', 'x-diff')], 401 set([(55, '*.patch', ()), (50, '*.diff', ())]) ) 402 self.assertEqual(ag[_l('text', 'x-c++src')], set([(50, '*.C', ('cs',))]) ) 403 self.assertEqual(ag[_l('text', 'x-readme')], set([(20, 'RDME', ('cs',))]) ) 404 assert _l('text', 'x-python') not in ag, ag 405