1""" 2This module is based on a rox module (LGPL): 3 4http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/mime.py?rev=1.21&view=log 5 6This module provides access to the shared MIME database. 7 8types is a dictionary of all known MIME types, indexed by the type name, e.g. 9types['application/x-python'] 10 11Applications can install information about MIME types by storing an 12XML file as <MIME>/packages/<application>.xml and running the 13update-mime-database command, which is provided by the freedesktop.org 14shared mime database package. 15 16See http://www.freedesktop.org/standards/shared-mime-info-spec/ for 17information about the format of these files. 18 19(based on version 0.13) 20""" 21 22import os 23import re 24import stat 25import sys 26import fnmatch 27 28from xdg import BaseDirectory 29import xdg.Locale 30 31from xml.dom import minidom, XML_NAMESPACE 32from collections import defaultdict 33 34FREE_NS = 'http://www.freedesktop.org/standards/shared-mime-info' 35 36types = {} # Maps MIME names to type objects 37 38exts = None # Maps extensions to types 39globs = None # List of (glob, type) pairs 40literals = None # Maps liternal names to types 41magic = None 42 43PY3 = (sys.version_info[0] >= 3) 44 45def _get_node_data(node): 46 """Get text of XML node""" 47 return ''.join([n.nodeValue for n in node.childNodes]).strip() 48 49def lookup(media, subtype = None): 50 """Get the MIMEtype object for the given type. 51 52 This remains for backwards compatibility; calling MIMEtype now does 53 the same thing. 54 55 The name can either be passed as one part ('text/plain'), or as two 56 ('text', 'plain'). 57 """ 58 return MIMEtype(media, subtype) 59 60class MIMEtype(object): 61 """Class holding data about a MIME type. 62 63 Calling the class will return a cached instance, so there is only one 64 instance for each MIME type. The name can either be passed as one part 65 ('text/plain'), or as two ('text', 'plain'). 66 """ 67 def __new__(cls, media, subtype=None): 68 if subtype is None and '/' in media: 69 media, subtype = media.split('/', 1) 70 assert '/' not in subtype 71 media = media.lower() 72 subtype = subtype.lower() 73 74 try: 75 return types[(media, subtype)] 76 except KeyError: 77 mtype = super(MIMEtype, cls).__new__(cls) 78 mtype._init(media, subtype) 79 types[(media, subtype)] = mtype 80 return mtype 81 82 # If this is done in __init__, it is automatically called again each time 83 # the MIMEtype is returned by __new__, which we don't want. So we call it 84 # explicitly only when we construct a new instance. 85 def _init(self, media, subtype): 86 self.media = media 87 self.subtype = subtype 88 self._comment = None 89 90 def _load(self): 91 "Loads comment for current language. Use get_comment() instead." 92 resource = os.path.join('mime', self.media, self.subtype + '.xml') 93 for path in BaseDirectory.load_data_paths(resource): 94 doc = minidom.parse(path) 95 if doc is None: 96 continue 97 for comment in doc.documentElement.getElementsByTagNameNS(FREE_NS, 'comment'): 98 lang = comment.getAttributeNS(XML_NAMESPACE, 'lang') or 'en' 99 goodness = 1 + (lang in xdg.Locale.langs) 100 if goodness > self._comment[0]: 101 self._comment = (goodness, _get_node_data(comment)) 102 if goodness == 2: return 103 104 # FIXME: add get_icon method 105 def get_comment(self): 106 """Returns comment for current language, loading it if needed.""" 107 # Should we ever reload? 108 if self._comment is None: 109 self._comment = (0, str(self)) 110 self._load() 111 return self._comment[1] 112 113 def canonical(self): 114 """Returns the canonical MimeType object if this is an alias.""" 115 update_cache() 116 s = str(self) 117 if s in aliases: 118 return lookup(aliases[s]) 119 return self 120 121 def inherits_from(self): 122 """Returns a set of Mime types which this inherits from.""" 123 update_cache() 124 return set(lookup(t) for t in inheritance[str(self)]) 125 126 def __str__(self): 127 return self.media + '/' + self.subtype 128 129 def __repr__(self): 130 return 'MIMEtype(%r, %r)' % (self.media, self.subtype) 131 132 def __hash__(self): 133 return hash(self.media) ^ hash(self.subtype) 134 135class UnknownMagicRuleFormat(ValueError): 136 pass 137 138class DiscardMagicRules(Exception): 139 "Raised when __NOMAGIC__ is found, and caught to discard previous rules." 140 pass 141 142class MagicRule: 143 also = None 144 145 def __init__(self, start, value, mask, word, range): 146 self.start = start 147 self.value = value 148 self.mask = mask 149 self.word = word 150 self.range = range 151 152 rule_ending_re = re.compile(br'(?:~(\d+))?(?:\+(\d+))?\n$') 153 154 @classmethod 155 def from_file(cls, f): 156 """Read a rule from the binary magics file. Returns a 2-tuple of 157 the nesting depth and the MagicRule.""" 158 line = f.readline() 159 #print line 160 161 # [indent] '>' 162 nest_depth, line = line.split(b'>', 1) 163 nest_depth = int(nest_depth) if nest_depth else 0 164 165 # start-offset '=' 166 start, line = line.split(b'=', 1) 167 start = int(start) 168 169 if line == b'__NOMAGIC__\n': 170 raise DiscardMagicRules 171 172 # value length (2 bytes, big endian) 173 if sys.version_info[0] >= 3: 174 lenvalue = int.from_bytes(line[:2], byteorder='big') 175 else: 176 lenvalue = (ord(line[0])<<8)+ord(line[1]) 177 line = line[2:] 178 179 # value 180 # This can contain newlines, so we may need to read more lines 181 while len(line) <= lenvalue: 182 line += f.readline() 183 value, line = line[:lenvalue], line[lenvalue:] 184 185 # ['&' mask] 186 if line.startswith(b'&'): 187 # This can contain newlines, so we may need to read more lines 188 while len(line) <= lenvalue: 189 line += f.readline() 190 mask, line = line[1:lenvalue+1], line[lenvalue+1:] 191 else: 192 mask = None 193 194 # ['~' word-size] ['+' range-length] 195 ending = cls.rule_ending_re.match(line) 196 if not ending: 197 # Per the spec, this will be caught and ignored, to allow 198 # for future extensions. 199 raise UnknownMagicRuleFormat(repr(line)) 200 201 word, range = ending.groups() 202 word = int(word) if (word is not None) else 1 203 range = int(range) if (range is not None) else 1 204 205 return nest_depth, cls(start, value, mask, word, range) 206 207 def maxlen(self): 208 l = self.start + len(self.value) + self.range 209 if self.also: 210 return max(l, self.also.maxlen()) 211 return l 212 213 def match(self, buffer): 214 if self.match0(buffer): 215 if self.also: 216 return self.also.match(buffer) 217 return True 218 219 def match0(self, buffer): 220 l=len(buffer) 221 lenvalue = len(self.value) 222 for o in range(self.range): 223 s=self.start+o 224 e=s+lenvalue 225 if l<e: 226 return False 227 if self.mask: 228 test='' 229 for i in range(lenvalue): 230 if PY3: 231 c = buffer[s+i] & self.mask[i] 232 else: 233 c = ord(buffer[s+i]) & ord(self.mask[i]) 234 test += chr(c) 235 else: 236 test = buffer[s:e] 237 238 if test==self.value: 239 return True 240 241 def __repr__(self): 242 return 'MagicRule(start=%r, value=%r, mask=%r, word=%r, range=%r)' %( 243 self.start, 244 self.value, 245 self.mask, 246 self.word, 247 self.range) 248 249 250class MagicMatchAny(object): 251 """Match any of a set of magic rules. 252 253 This has a similar interface to MagicRule objects (i.e. its match() and 254 maxlen() methods), to allow for duck typing. 255 """ 256 def __init__(self, rules): 257 self.rules = rules 258 259 def match(self, buffer): 260 return any(r.match(buffer) for r in self.rules) 261 262 def maxlen(self): 263 return max(r.maxlen() for r in self.rules) 264 265 @classmethod 266 def from_file(cls, f): 267 """Read a set of rules from the binary magic file.""" 268 c=f.read(1) 269 f.seek(-1, 1) 270 depths_rules = [] 271 while c and c != b'[': 272 try: 273 depths_rules.append(MagicRule.from_file(f)) 274 except UnknownMagicRuleFormat: 275 # Ignored to allow for extensions to the rule format. 276 pass 277 c=f.read(1) 278 if c: 279 f.seek(-1, 1) 280 281 # Build the rule tree 282 tree = [] # (rule, [(subrule,[subsubrule,...]), ...]) 283 insert_points = {0:tree} 284 for depth, rule in depths_rules: 285 subrules = [] 286 insert_points[depth].append((rule, subrules)) 287 insert_points[depth+1] = subrules 288 289 return cls.from_rule_tree(tree) 290 291 @classmethod 292 def from_rule_tree(cls, tree): 293 """From a nested list of (rule, subrules) pairs, build a MagicMatchAny 294 instance, recursing down the tree. 295 296 Where there's only one top-level rule, this is returned directly, 297 to simplify the nested structure. Returns None if no rules were read. 298 """ 299 rules = [] 300 for rule, subrules in tree: 301 if subrules: 302 rule.also = cls.from_rule_tree(subrules) 303 rules.append(rule) 304 305 if len(rules)==0: 306 return None 307 if len(rules)==1: 308 return rules[0] 309 return cls(rules) 310 311class MagicDB: 312 def __init__(self): 313 self.bytype = defaultdict(list) # mimetype -> [(priority, rule), ...] 314 315 def merge_file(self, fname): 316 """Read a magic binary file, and add its rules to this MagicDB.""" 317 with open(fname, 'rb') as f: 318 line = f.readline() 319 if line != b'MIME-Magic\0\n': 320 raise IOError('Not a MIME magic file') 321 322 while True: 323 shead = f.readline().decode('ascii') 324 #print(shead) 325 if not shead: 326 break 327 if shead[0] != '[' or shead[-2:] != ']\n': 328 raise ValueError('Malformed section heading', shead) 329 pri, tname = shead[1:-2].split(':') 330 #print shead[1:-2] 331 pri = int(pri) 332 mtype = lookup(tname) 333 try: 334 rule = MagicMatchAny.from_file(f) 335 except DiscardMagicRules: 336 self.bytype.pop(mtype, None) 337 rule = MagicMatchAny.from_file(f) 338 if rule is None: 339 continue 340 #print rule 341 342 self.bytype[mtype].append((pri, rule)) 343 344 def finalise(self): 345 """Prepare the MagicDB for matching. 346 347 This should be called after all rules have been merged into it. 348 """ 349 maxlen = 0 350 self.alltypes = [] # (priority, mimetype, rule) 351 352 for mtype, rules in self.bytype.items(): 353 for pri, rule in rules: 354 self.alltypes.append((pri, mtype, rule)) 355 maxlen = max(maxlen, rule.maxlen()) 356 357 self.maxlen = maxlen # Number of bytes to read from files 358 self.alltypes.sort(key=lambda x: x[0], reverse=True) 359 360 def match_data(self, data, max_pri=100, min_pri=0, possible=None): 361 """Do magic sniffing on some bytes. 362 363 max_pri & min_pri can be used to specify the maximum & minimum priority 364 rules to look for. possible can be a list of mimetypes to check, or None 365 (the default) to check all mimetypes until one matches. 366 367 Returns the MIMEtype found, or None if no entries match. 368 """ 369 if possible is not None: 370 types = [] 371 for mt in possible: 372 for pri, rule in self.bytype[mt]: 373 types.append((pri, mt, rule)) 374 types.sort(key=lambda x: x[0]) 375 else: 376 types = self.alltypes 377 378 for priority, mimetype, rule in types: 379 #print priority, max_pri, min_pri 380 if priority > max_pri: 381 continue 382 if priority < min_pri: 383 break 384 385 if rule.match(data): 386 return mimetype 387 388 def match(self, path, max_pri=100, min_pri=0, possible=None): 389 """Read data from the file and do magic sniffing on it. 390 391 max_pri & min_pri can be used to specify the maximum & minimum priority 392 rules to look for. possible can be a list of mimetypes to check, or None 393 (the default) to check all mimetypes until one matches. 394 395 Returns the MIMEtype found, or None if no entries match. Raises IOError 396 if the file can't be opened. 397 """ 398 with open(path, 'rb') as f: 399 buf = f.read(self.maxlen) 400 return self.match_data(buf, max_pri, min_pri, possible) 401 402 def __repr__(self): 403 return '<MagicDB (%d types)>' % len(self.alltypes) 404 405class GlobDB(object): 406 def __init__(self): 407 """Prepare the GlobDB. It can't actually be used until .finalise() is 408 called, but merge_file() can be used to add data before that. 409 """ 410 # Maps mimetype to {(weight, glob, flags), ...} 411 self.allglobs = defaultdict(set) 412 413 def merge_file(self, path): 414 """Loads name matching information from a globs2 file."""# 415 allglobs = self.allglobs 416 with open(path) as f: 417 for line in f: 418 if line.startswith('#'): continue # Comment 419 420 fields = line[:-1].split(':') 421 weight, type_name, pattern = fields[:3] 422 weight = int(weight) 423 mtype = lookup(type_name) 424 if len(fields) > 3: 425 flags = fields[3].split(',') 426 else: 427 flags = () 428 429 if pattern == '__NOGLOBS__': 430 # This signals to discard any previous globs 431 allglobs.pop(mtype, None) 432 continue 433 434 allglobs[mtype].add((weight, pattern, tuple(flags))) 435 436 def finalise(self): 437 """Prepare the GlobDB for matching. 438 439 This should be called after all files have been merged into it. 440 """ 441 self.exts = defaultdict(list) # Maps extensions to [(type, weight),...] 442 self.cased_exts = defaultdict(list) 443 self.globs = [] # List of (regex, type, weight) triplets 444 self.literals = {} # Maps literal names to (type, weight) 445 self.cased_literals = {} 446 447 for mtype, globs in self.allglobs.items(): 448 mtype = mtype.canonical() 449 for weight, pattern, flags in globs: 450 451 cased = 'cs' in flags 452 453 if pattern.startswith('*.'): 454 # *.foo -- extension pattern 455 rest = pattern[2:] 456 if not ('*' in rest or '[' in rest or '?' in rest): 457 if cased: 458 self.cased_exts[rest].append((mtype, weight)) 459 else: 460 self.exts[rest.lower()].append((mtype, weight)) 461 continue 462 463 if ('*' in pattern or '[' in pattern or '?' in pattern): 464 # Translate the glob pattern to a regex & compile it 465 re_flags = 0 if cased else re.I 466 pattern = re.compile(fnmatch.translate(pattern), flags=re_flags) 467 self.globs.append((pattern, mtype, weight)) 468 else: 469 # No wildcards - literal pattern 470 if cased: 471 self.cased_literals[pattern] = (mtype, weight) 472 else: 473 self.literals[pattern.lower()] = (mtype, weight) 474 475 # Sort globs by weight & length 476 self.globs.sort(reverse=True, key=lambda x: (x[2], len(x[0].pattern)) ) 477 478 def first_match(self, path): 479 """Return the first match found for a given path, or None if no match 480 is found.""" 481 try: 482 return next(self._match_path(path))[0] 483 except StopIteration: 484 return None 485 486 def all_matches(self, path): 487 """Return a list of (MIMEtype, glob weight) pairs for the path.""" 488 return list(self._match_path(path)) 489 490 def _match_path(self, path): 491 """Yields pairs of (mimetype, glob weight).""" 492 leaf = os.path.basename(path) 493 494 # Literals (no wildcards) 495 if leaf in self.cased_literals: 496 yield self.cased_literals[leaf] 497 498 lleaf = leaf.lower() 499 if lleaf in self.literals: 500 yield self.literals[lleaf] 501 502 # Extensions 503 ext = leaf 504 while 1: 505 p = ext.find('.') 506 if p < 0: break 507 ext = ext[p + 1:] 508 if ext in self.cased_exts: 509 for res in self.cased_exts[ext]: 510 yield res 511 ext = lleaf 512 while 1: 513 p = ext.find('.') 514 if p < 0: break 515 ext = ext[p+1:] 516 if ext in self.exts: 517 for res in self.exts[ext]: 518 yield res 519 520 # Other globs 521 for (regex, mime_type, weight) in self.globs: 522 if regex.match(leaf): 523 yield (mime_type, weight) 524 525# Some well-known types 526text = lookup('text', 'plain') 527octet_stream = lookup('application', 'octet-stream') 528inode_block = lookup('inode', 'blockdevice') 529inode_char = lookup('inode', 'chardevice') 530inode_dir = lookup('inode', 'directory') 531inode_fifo = lookup('inode', 'fifo') 532inode_socket = lookup('inode', 'socket') 533inode_symlink = lookup('inode', 'symlink') 534inode_door = lookup('inode', 'door') 535app_exe = lookup('application', 'executable') 536 537_cache_uptodate = False 538 539def _cache_database(): 540 global globs, magic, aliases, inheritance, _cache_uptodate 541 542 _cache_uptodate = True 543 544 aliases = {} # Maps alias Mime types to canonical names 545 inheritance = defaultdict(set) # Maps to sets of parent mime types. 546 547 # Load aliases 548 for path in BaseDirectory.load_data_paths(os.path.join('mime', 'aliases')): 549 with open(path, 'r') as f: 550 for line in f: 551 alias, canonical = line.strip().split(None, 1) 552 aliases[alias] = canonical 553 554 # Load filename patterns (globs) 555 globs = GlobDB() 556 for path in BaseDirectory.load_data_paths(os.path.join('mime', 'globs2')): 557 globs.merge_file(path) 558 globs.finalise() 559 560 # Load magic sniffing data 561 magic = MagicDB() 562 for path in BaseDirectory.load_data_paths(os.path.join('mime', 'magic')): 563 magic.merge_file(path) 564 magic.finalise() 565 566 # Load subclasses 567 for path in BaseDirectory.load_data_paths(os.path.join('mime', 'subclasses')): 568 with open(path, 'r') as f: 569 for line in f: 570 sub, parent = line.strip().split(None, 1) 571 inheritance[sub].add(parent) 572 573def update_cache(): 574 if not _cache_uptodate: 575 _cache_database() 576 577def get_type_by_name(path): 578 """Returns type of file by its name, or None if not known""" 579 update_cache() 580 return globs.first_match(path) 581 582def get_type_by_contents(path, max_pri=100, min_pri=0): 583 """Returns type of file by its contents, or None if not known""" 584 update_cache() 585 586 return magic.match(path, max_pri, min_pri) 587 588def get_type_by_data(data, max_pri=100, min_pri=0): 589 """Returns type of the data, which should be bytes.""" 590 update_cache() 591 592 return magic.match_data(data, max_pri, min_pri) 593 594def _get_type_by_stat(st_mode): 595 """Match special filesystem objects to Mimetypes.""" 596 if stat.S_ISDIR(st_mode): return inode_dir 597 elif stat.S_ISCHR(st_mode): return inode_char 598 elif stat.S_ISBLK(st_mode): return inode_block 599 elif stat.S_ISFIFO(st_mode): return inode_fifo 600 elif stat.S_ISLNK(st_mode): return inode_symlink 601 elif stat.S_ISSOCK(st_mode): return inode_socket 602 return inode_door 603 604def get_type(path, follow=True, name_pri=100): 605 """Returns type of file indicated by path. 606 607 This function is *deprecated* - :func:`get_type2` is more accurate. 608 609 :param path: pathname to check (need not exist) 610 :param follow: when reading file, follow symbolic links 611 :param name_pri: Priority to do name matches. 100=override magic 612 613 This tries to use the contents of the file, and falls back to the name. It 614 can also handle special filesystem objects like directories and sockets. 615 """ 616 update_cache() 617 618 try: 619 if follow: 620 st = os.stat(path) 621 else: 622 st = os.lstat(path) 623 except: 624 t = get_type_by_name(path) 625 return t or text 626 627 if stat.S_ISREG(st.st_mode): 628 # Regular file 629 t = get_type_by_contents(path, min_pri=name_pri) 630 if not t: t = get_type_by_name(path) 631 if not t: t = get_type_by_contents(path, max_pri=name_pri) 632 if t is None: 633 if stat.S_IMODE(st.st_mode) & 0o111: 634 return app_exe 635 else: 636 return text 637 return t 638 else: 639 return _get_type_by_stat(st.st_mode) 640 641def get_type2(path, follow=True): 642 """Find the MIMEtype of a file using the XDG recommended checking order. 643 644 This first checks the filename, then uses file contents if the name doesn't 645 give an unambiguous MIMEtype. It can also handle special filesystem objects 646 like directories and sockets. 647 648 :param path: file path to examine (need not exist) 649 :param follow: whether to follow symlinks 650 651 :rtype: :class:`MIMEtype` 652 653 .. versionadded:: 1.0 654 """ 655 update_cache() 656 657 try: 658 st = os.stat(path) if follow else os.lstat(path) 659 except OSError: 660 return get_type_by_name(path) or octet_stream 661 662 if not stat.S_ISREG(st.st_mode): 663 # Special filesystem objects 664 return _get_type_by_stat(st.st_mode) 665 666 mtypes = sorted(globs.all_matches(path), key=(lambda x: x[1]), reverse=True) 667 if mtypes: 668 max_weight = mtypes[0][1] 669 i = 1 670 for mt, w in mtypes[1:]: 671 if w < max_weight: 672 break 673 i += 1 674 mtypes = mtypes[:i] 675 if len(mtypes) == 1: 676 return mtypes[0][0] 677 678 possible = [mt for mt,w in mtypes] 679 else: 680 possible = None # Try all magic matches 681 682 try: 683 t = magic.match(path, possible=possible) 684 except IOError: 685 t = None 686 687 if t: 688 return t 689 elif mtypes: 690 return mtypes[0][0] 691 elif stat.S_IMODE(st.st_mode) & 0o111: 692 return app_exe 693 else: 694 return text if is_text_file(path) else octet_stream 695 696def is_text_file(path): 697 """Guess whether a file contains text or binary data. 698 699 Heuristic: binary if the first 32 bytes include ASCII control characters. 700 This rule may change in future versions. 701 702 .. versionadded:: 1.0 703 """ 704 try: 705 f = open(path, 'rb') 706 except IOError: 707 return False 708 709 with f: 710 return _is_text(f.read(32)) 711 712if PY3: 713 def _is_text(data): 714 return not any(b <= 0x8 or 0xe <= b < 0x20 or b == 0x7f for b in data) 715else: 716 def _is_text(data): 717 return not any(b <= '\x08' or '\x0e' <= b < '\x20' or b == '\x7f' \ 718 for b in data) 719 720_mime2ext_cache = None 721_mime2ext_cache_uptodate = False 722 723def get_extensions(mimetype): 724 """Retrieve the set of filename extensions matching a given MIMEtype. 725 726 Extensions are returned without a leading dot, e.g. 'py'. If no extensions 727 are registered for the MIMEtype, returns an empty set. 728 729 The extensions are stored in a cache the first time this is called. 730 731 .. versionadded:: 1.0 732 """ 733 global _mime2ext_cache, _mime2ext_cache_uptodate 734 update_cache() 735 if not _mime2ext_cache_uptodate: 736 _mime2ext_cache = defaultdict(set) 737 for ext, mtypes in globs.exts.items(): 738 for mtype, prio in mtypes: 739 _mime2ext_cache[mtype].add(ext) 740 _mime2ext_cache_uptodate = True 741 742 return _mime2ext_cache[mimetype] 743 744 745def install_mime_info(application, package_file): 746 """Copy 'package_file' as ``~/.local/share/mime/packages/<application>.xml.`` 747 If package_file is None, install ``<app_dir>/<application>.xml``. 748 If already installed, does nothing. May overwrite an existing 749 file with the same name (if the contents are different)""" 750 application += '.xml' 751 752 with open(package_file) as f: 753 new_data = f.read() 754 755 # See if the file is already installed 756 package_dir = os.path.join('mime', 'packages') 757 resource = os.path.join(package_dir, application) 758 for x in BaseDirectory.load_data_paths(resource): 759 try: 760 with open(x) as f: 761 old_data = f.read() 762 except: 763 continue 764 if old_data == new_data: 765 return # Already installed 766 767 global _cache_uptodate 768 _cache_uptodate = False 769 770 # Not already installed; add a new copy 771 # Create the directory structure... 772 new_file = os.path.join(BaseDirectory.save_data_path(package_dir), application) 773 774 # Write the file... 775 with open(new_file, 'w') as f: 776 f.write(new_data) 777 778 # Update the database... 779 command = 'update-mime-database' 780 if os.spawnlp(os.P_WAIT, command, command, BaseDirectory.save_data_path('mime')): 781 os.unlink(new_file) 782 raise Exception("The '%s' command returned an error code!\n" \ 783 "Make sure you have the freedesktop.org shared MIME package:\n" \ 784 "http://standards.freedesktop.org/shared-mime-info/" % command) 785