123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 |
- """Guess the MIME type of a file.
- This module defines two useful functions:
- guess_type(url, strict=1) -- guess the MIME type and encoding of a URL.
- guess_extension(type, strict=1) -- guess the extension for a given MIME type.
- It also contains the following, for tuning the behavior:
- Data:
- knownfiles -- list of files to parse
- inited -- flag set when init() has been called
- suffix_map -- dictionary mapping suffixes to suffixes
- encodings_map -- dictionary mapping suffixes to encodings
- types_map -- dictionary mapping suffixes to types
- Functions:
- init([files]) -- parse a list of files, default knownfiles
- read_mime_types(file) -- parse one file, return a dictionary or None
- """
- import os
- import posixpath
- import urllib
- __all__ = [
- "guess_type","guess_extension","guess_all_extensions",
- "add_type","read_mime_types","init"
- ]
- knownfiles = [
- "/etc/mime.types",
- "/usr/local/etc/httpd/conf/mime.types",
- "/usr/local/lib/netscape/mime.types",
- "/usr/local/etc/httpd/conf/mime.types", # Apache 1.2
- "/usr/local/etc/mime.types", # Apache 1.3
- ]
- inited = False
- class MimeTypes:
- """MIME-types datastore.
- This datastore can handle information from mime.types-style files
- and supports basic determination of MIME type from a filename or
- URL, and can guess a reasonable extension given a MIME type.
- """
- def __init__(self, filenames=(), strict=True):
- if not inited:
- init()
- self.encodings_map = encodings_map.copy()
- self.suffix_map = suffix_map.copy()
- self.types_map = ({}, {}) # dict for (non-strict, strict)
- self.types_map_inv = ({}, {})
- for (ext, type) in types_map.items():
- self.add_type(type, ext, True)
- for (ext, type) in common_types.items():
- self.add_type(type, ext, False)
- for name in filenames:
- self.read(name, strict)
- def add_type(self, type, ext, strict=True):
- """Add a mapping between a type and an extension.
- When the extension is already known, the new
- type will replace the old one. When the type
- is already known the extension will be added
- to the list of known extensions.
- If strict is true, information will be added to
- list of standard types, else to the list of non-standard
- types.
- """
- self.types_map[strict][ext] = type
- exts = self.types_map_inv[strict].setdefault(type, [])
- if ext not in exts:
- exts.append(ext)
- def guess_type(self, url, strict=True):
- """Guess the type of a file based on its URL.
- Return value is a tuple (type, encoding) where type is None if
- the type can't be guessed (no or unknown suffix) or a string
- of the form type/subtype, usable for a MIME Content-type
- header; and encoding is None for no encoding or the name of
- the program used to encode (e.g. compress or gzip). The
- mappings are table driven. Encoding suffixes are case
- sensitive; type suffixes are first tried case sensitive, then
- case insensitive.
- The suffixes .tgz, .taz and .tz (case sensitive!) are all
- mapped to '.tar.gz'. (This is table-driven too, using the
- dictionary suffix_map.)
- Optional `strict' argument when False adds a bunch of commonly found,
- but non-standard types.
- """
- scheme, url = urllib.splittype(url)
- if scheme == 'data':
- # syntax of data URLs:
- # dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
- # mediatype := [ type "/" subtype ] *( ";" parameter )
- # data := *urlchar
- # parameter := attribute "=" value
- # type/subtype defaults to "text/plain"
- comma = url.find(',')
- if comma < 0:
- # bad data URL
- return None, None
- semi = url.find(';', 0, comma)
- if semi >= 0:
- type = url[:semi]
- else:
- type = url[:comma]
- if '=' in type or '/' not in type:
- type = 'text/plain'
- return type, None # never compressed, so encoding is None
- base, ext = posixpath.splitext(url)
- while ext in self.suffix_map:
- base, ext = posixpath.splitext(base + self.suffix_map[ext])
- if ext in self.encodings_map:
- encoding = self.encodings_map[ext]
- base, ext = posixpath.splitext(base)
- else:
- encoding = None
- types_map = self.types_map[True]
- if ext in types_map:
- return types_map[ext], encoding
- elif ext.lower() in types_map:
- return types_map[ext.lower()], encoding
- elif strict:
- return None, encoding
- types_map = self.types_map[False]
- if ext in types_map:
- return types_map[ext], encoding
- elif ext.lower() in types_map:
- return types_map[ext.lower()], encoding
- else:
- return None, encoding
- def guess_all_extensions(self, type, strict=True):
- """Guess the extensions for a file based on its MIME type.
- Return value is a list of strings giving the possible filename
- extensions, including the leading dot ('.'). The extension is not
- guaranteed to have been associated with any particular data stream,
- but would be mapped to the MIME type `type' by guess_type().
- Optional `strict' argument when false adds a bunch of commonly found,
- but non-standard types.
- """
- type = type.lower()
- extensions = self.types_map_inv[True].get(type, [])
- if not strict:
- for ext in self.types_map_inv[False].get(type, []):
- if ext not in extensions:
- extensions.append(ext)
- return extensions
- def guess_extension(self, type, strict=True):
- """Guess the extension for a file based on its MIME type.
- Return value is a string giving a filename extension,
- including the leading dot ('.'). The extension is not
- guaranteed to have been associated with any particular data
- stream, but would be mapped to the MIME type `type' by
- guess_type(). If no extension can be guessed for `type', None
- is returned.
- Optional `strict' argument when false adds a bunch of commonly found,
- but non-standard types.
- """
- extensions = self.guess_all_extensions(type, strict)
- if not extensions:
- return None
- return extensions[0]
- def read(self, filename, strict=True):
- """
- Read a single mime.types-format file, specified by pathname.
- If strict is true, information will be added to
- list of standard types, else to the list of non-standard
- types.
- """
- fp = open(filename)
- self.readfp(fp, strict)
- fp.close()
- def readfp(self, fp, strict=True):
- """
- Read a single mime.types-format file.
- If strict is true, information will be added to
- list of standard types, else to the list of non-standard
- types.
- """
- while 1:
- line = fp.readline()
- if not line:
- break
- words = line.split()
- for i in range(len(words)):
- if words[i][0] == '#':
- del words[i:]
- break
- if not words:
- continue
- type, suffixes = words[0], words[1:]
- for suff in suffixes:
- self.add_type(type, '.' + suff, strict)
- def guess_type(url, strict=True):
- """Guess the type of a file based on its URL.
- Return value is a tuple (type, encoding) where type is None if the
- type can't be guessed (no or unknown suffix) or a string of the
- form type/subtype, usable for a MIME Content-type header; and
- encoding is None for no encoding or the name of the program used
- to encode (e.g. compress or gzip). The mappings are table
- driven. Encoding suffixes are case sensitive; type suffixes are
- first tried case sensitive, then case insensitive.
- The suffixes .tgz, .taz and .tz (case sensitive!) are all mapped
- to ".tar.gz". (This is table-driven too, using the dictionary
- suffix_map).
- Optional `strict' argument when false adds a bunch of commonly found, but
- non-standard types.
- """
- init()
- return guess_type(url, strict)
- def guess_all_extensions(type, strict=True):
- """Guess the extensions for a file based on its MIME type.
- Return value is a list of strings giving the possible filename
- extensions, including the leading dot ('.'). The extension is not
- guaranteed to have been associated with any particular data
- stream, but would be mapped to the MIME type `type' by
- guess_type(). If no extension can be guessed for `type', None
- is returned.
- Optional `strict' argument when false adds a bunch of commonly found,
- but non-standard types.
- """
- init()
- return guess_all_extensions(type, strict)
- def guess_extension(type, strict=True):
- """Guess the extension for a file based on its MIME type.
- Return value is a string giving a filename extension, including the
- leading dot ('.'). The extension is not guaranteed to have been
- associated with any particular data stream, but would be mapped to the
- MIME type `type' by guess_type(). If no extension can be guessed for
- `type', None is returned.
- Optional `strict' argument when false adds a bunch of commonly found,
- but non-standard types.
- """
- init()
- return guess_extension(type, strict)
- def add_type(type, ext, strict=True):
- """Add a mapping between a type and an extension.
- When the extension is already known, the new
- type will replace the old one. When the type
- is already known the extension will be added
- to the list of known extensions.
- If strict is true, information will be added to
- list of standard types, else to the list of non-standard
- types.
- """
- init()
- return add_type(type, ext, strict)
- def init(files=None):
- global guess_all_extensions, guess_extension, guess_type
- global suffix_map, types_map, encodings_map, common_types
- global add_type, inited
- inited = True
- db = MimeTypes()
- if files is None:
- files = knownfiles
- for file in files:
- if os.path.isfile(file):
- db.readfp(open(file))
- encodings_map = db.encodings_map
- suffix_map = db.suffix_map
- types_map = db.types_map[True]
- guess_all_extensions = db.guess_all_extensions
- guess_extension = db.guess_extension
- guess_type = db.guess_type
- add_type = db.add_type
- common_types = db.types_map[False]
- def read_mime_types(file):
- try:
- f = open(file)
- except IOError:
- return None
- db = MimeTypes()
- db.readfp(f, True)
- return db.types_map[True]
- def _default_mime_types():
- global suffix_map
- global encodings_map
- global types_map
- global common_types
- suffix_map = {
- '.tgz': '.tar.gz',
- '.taz': '.tar.gz',
- '.tz': '.tar.gz',
- }
- encodings_map = {
- '.gz': 'gzip',
- '.Z': 'compress',
- }
- # Before adding new types, make sure they are either registered with IANA,
- # at http://www.isi.edu/in-notes/iana/assignments/media-types
- # or extensions, i.e. using the x- prefix
- # If you add to these, please keep them sorted!
- types_map = {
- '.a' : 'application/octet-stream',
- '.ai' : 'application/postscript',
- '.aif' : 'audio/x-aiff',
- '.aifc' : 'audio/x-aiff',
- '.aiff' : 'audio/x-aiff',
- '.au' : 'audio/basic',
- '.avi' : 'video/x-msvideo',
- '.bat' : 'text/plain',
- '.bcpio' : 'application/x-bcpio',
- '.bin' : 'application/octet-stream',
- '.bmp' : 'image/x-ms-bmp',
- '.c' : 'text/plain',
- # Duplicates :(
- '.cdf' : 'application/x-cdf',
- '.cdf' : 'application/x-netcdf',
- '.cpio' : 'application/x-cpio',
- '.csh' : 'application/x-csh',
- '.css' : 'text/css',
- '.dll' : 'application/octet-stream',
- '.doc' : 'application/msword',
- '.dot' : 'application/msword',
- '.dvi' : 'application/x-dvi',
- '.eml' : 'message/rfc822',
- '.eps' : 'application/postscript',
- '.etx' : 'text/x-setext',
- '.exe' : 'application/octet-stream',
- '.gif' : 'image/gif',
- '.gtar' : 'application/x-gtar',
- '.h' : 'text/plain',
- '.hdf' : 'application/x-hdf',
- '.htm' : 'text/html',
- '.html' : 'text/html',
- '.ief' : 'image/ief',
- '.jpe' : 'image/jpeg',
- '.jpeg' : 'image/jpeg',
- '.jpg' : 'image/jpeg',
- '.js' : 'application/x-javascript',
- '.ksh' : 'text/plain',
- '.latex' : 'application/x-latex',
- '.m1v' : 'video/mpeg',
- '.man' : 'application/x-troff-man',
- '.me' : 'application/x-troff-me',
- '.mht' : 'message/rfc822',
- '.mhtml' : 'message/rfc822',
- '.mif' : 'application/x-mif',
- '.mov' : 'video/quicktime',
- '.movie' : 'video/x-sgi-movie',
- '.mp2' : 'audio/mpeg',
- '.mp3' : 'audio/mpeg',
- '.mpa' : 'video/mpeg',
- '.mpe' : 'video/mpeg',
- '.mpeg' : 'video/mpeg',
- '.mpg' : 'video/mpeg',
- '.ms' : 'application/x-troff-ms',
- '.nc' : 'application/x-netcdf',
- '.nws' : 'message/rfc822',
- '.o' : 'application/octet-stream',
- '.obj' : 'application/octet-stream',
- '.oda' : 'application/oda',
- '.p12' : 'application/x-pkcs12',
- '.p7c' : 'application/pkcs7-mime',
- '.pbm' : 'image/x-portable-bitmap',
- '.pdf' : 'application/pdf',
- '.pfx' : 'application/x-pkcs12',
- '.pgm' : 'image/x-portable-graymap',
- '.pl' : 'text/plain',
- '.png' : 'image/png',
- '.pnm' : 'image/x-portable-anymap',
- '.pot' : 'application/vnd.ms-powerpoint',
- '.ppa' : 'application/vnd.ms-powerpoint',
- '.ppm' : 'image/x-portable-pixmap',
- '.pps' : 'application/vnd.ms-powerpoint',
- '.ppt' : 'application/vnd.ms-powerpoint',
- '.ps' : 'application/postscript',
- '.pwz' : 'application/vnd.ms-powerpoint',
- '.py' : 'text/x-python',
- '.pyc' : 'application/x-python-code',
- '.pyo' : 'application/x-python-code',
- '.qt' : 'video/quicktime',
- '.ra' : 'audio/x-pn-realaudio',
- '.ram' : 'application/x-pn-realaudio',
- '.ras' : 'image/x-cmu-raster',
- '.rdf' : 'application/xml',
- '.rgb' : 'image/x-rgb',
- '.roff' : 'application/x-troff',
- '.rtx' : 'text/richtext',
- '.sgm' : 'text/x-sgml',
- '.sgml' : 'text/x-sgml',
- '.sh' : 'application/x-sh',
- '.shar' : 'application/x-shar',
- '.snd' : 'audio/basic',
- '.so' : 'application/octet-stream',
- '.src' : 'application/x-wais-source',
- '.sv4cpio': 'application/x-sv4cpio',
- '.sv4crc' : 'application/x-sv4crc',
- '.swf' : 'application/x-shockwave-flash',
- '.t' : 'application/x-troff',
- '.tar' : 'application/x-tar',
- '.tcl' : 'application/x-tcl',
- '.tex' : 'application/x-tex',
- '.texi' : 'application/x-texinfo',
- '.texinfo': 'application/x-texinfo',
- '.tif' : 'image/tiff',
- '.tiff' : 'image/tiff',
- '.tr' : 'application/x-troff',
- '.tsv' : 'text/tab-separated-values',
- '.txt' : 'text/plain',
- '.ustar' : 'application/x-ustar',
- '.vcf' : 'text/x-vcard',
- '.wav' : 'audio/x-wav',
- '.wiz' : 'application/msword',
- '.xbm' : 'image/x-xbitmap',
- '.xlb' : 'application/vnd.ms-excel',
- # Duplicates :(
- '.xls' : 'application/excel',
- '.xls' : 'application/vnd.ms-excel',
- '.xml' : 'text/xml',
- '.xpm' : 'image/x-xpixmap',
- '.xsl' : 'application/xml',
- '.xwd' : 'image/x-xwindowdump',
- '.zip' : 'application/zip',
- }
- # These are non-standard types, commonly found in the wild. They will only
- # match if strict=0 flag is given to the API methods.
- # Please sort these too
- common_types = {
- '.jpg' : 'image/jpg',
- '.mid' : 'audio/midi',
- '.midi': 'audio/midi',
- '.pct' : 'image/pict',
- '.pic' : 'image/pict',
- '.pict': 'image/pict',
- '.rtf' : 'application/rtf',
- '.xul' : 'text/xul'
- }
- _default_mime_types()
- if __name__ == '__main__':
- import sys
- import getopt
- USAGE = """\
- Usage: mimetypes.py [options] type
- Options:
- --help / -h -- print this message and exit
- --lenient / -l -- additionally search of some common, but non-standard
- types.
- --extension / -e -- guess extension instead of type
- More than one type argument may be given.
- """
- def usage(code, msg=''):
- print USAGE
- if msg: print msg
- sys.exit(code)
- try:
- opts, args = getopt.getopt(sys.argv[1:], 'hle',
- ['help', 'lenient', 'extension'])
- except getopt.error, msg:
- usage(1, msg)
- strict = 1
- extension = 0
- for opt, arg in opts:
- if opt in ('-h', '--help'):
- usage(0)
- elif opt in ('-l', '--lenient'):
- strict = 0
- elif opt in ('-e', '--extension'):
- extension = 1
- for gtype in args:
- if extension:
- guess = guess_extension(gtype, strict)
- if not guess: print "I don't know anything about type", gtype
- else: print guess
- else:
- guess, encoding = guess_type(gtype, strict)
- if not guess: print "I don't know anything about type", gtype
- else: print 'type:', guess, 'encoding:', encoding
|