1
"""Guess the MIME type of a file.
3
This module defines two useful functions:
5
guess_type(url, strict=True) -- guess the MIME type and encoding of a URL.
7
guess_extension(type, strict=True) -- guess the extension for a given MIME type.
9
It also contains the following, for tuning the behavior:
13
knownfiles -- list of files to parse
14
inited -- flag set when init() has been called
15
suffix_map -- dictionary mapping suffixes to suffixes
16
encodings_map -- dictionary mapping suffixes to encodings
17
types_map -- dictionary mapping suffixes to types
21
init([files]) -- parse a list of files, default knownfiles (on Windows, the
22
default values are taken from the registry)
23
read_mime_types(file) -- parse one file, return a dictionary or None
27
from _winapi import _mimetypes_read_windows_registry
29
_mimetypes_read_windows_registry = None
32
import winreg as _winreg
37
"knownfiles", "inited", "MimeTypes",
38
"guess_type", "guess_file_type", "guess_all_extensions", "guess_extension",
39
"add_type", "init", "read_mime_types",
40
"suffix_map", "encodings_map", "types_map", "common_types"
45
"/etc/httpd/mime.types", # Mac OS X
46
"/etc/httpd/conf/mime.types", # Apache
47
"/etc/apache/mime.types", # Apache 1
48
"/etc/apache2/mime.types", # Apache 2
49
"/usr/local/etc/httpd/conf/mime.types",
50
"/usr/local/lib/netscape/mime.types",
51
"/usr/local/etc/httpd/conf/mime.types", # Apache 1.2
52
"/usr/local/etc/mime.types", # Apache 1.3
60
"""MIME-types datastore.
62
This datastore can handle information from mime.types-style files
63
and supports basic determination of MIME type from a filename or
64
URL, and can guess a reasonable extension given a MIME type.
67
def __init__(self, filenames=(), strict=True):
70
self.encodings_map = _encodings_map_default.copy()
71
self.suffix_map = _suffix_map_default.copy()
72
self.types_map = ({}, {}) # dict for (non-strict, strict)
73
self.types_map_inv = ({}, {})
74
for (ext, type) in _types_map_default.items():
75
self.add_type(type, ext, True)
76
for (ext, type) in _common_types_default.items():
77
self.add_type(type, ext, False)
78
for name in filenames:
79
self.read(name, strict)
81
def add_type(self, type, ext, strict=True):
82
"""Add a mapping between a type and an extension.
84
When the extension is already known, the new
85
type will replace the old one. When the type
86
is already known the extension will be added
87
to the list of known extensions.
89
If strict is true, information will be added to
90
list of standard types, else to the list of non-standard
93
Valid extensions are empty or start with a '.'.
95
if ext and not ext.startswith('.'):
96
from warnings import _deprecated
99
"Undotted extensions",
100
"Using undotted extensions is deprecated and "
101
"will raise a ValueError in Python {remove}",
107
self.types_map[strict][ext] = type
108
exts = self.types_map_inv[strict].setdefault(type, [])
112
def guess_type(self, url, strict=True):
113
"""Guess the type of a file which is either a URL or a path-like object.
115
Return value is a tuple (type, encoding) where type is None if
116
the type can't be guessed (no or unknown suffix) or a string
117
of the form type/subtype, usable for a MIME Content-type
118
header; and encoding is None for no encoding or the name of
119
the program used to encode (e.g. compress or gzip). The
120
mappings are table driven. Encoding suffixes are case
121
sensitive; type suffixes are first tried case sensitive, then
124
The suffixes .tgz, .taz and .tz (case sensitive!) are all
125
mapped to '.tar.gz'. (This is table-driven too, using the
126
dictionary suffix_map.)
128
Optional 'strict' argument when False adds a bunch of commonly found,
129
but non-standard types.
131
# Lazy import to improve module import time
135
# TODO: Deprecate accepting file paths (in particular path-like objects).
137
p = urllib.parse.urlparse(url)
138
if p.scheme and len(p.scheme) > 1:
142
2
6
return self.guess_file_type(url, strict=strict)
144
# syntax of data URLs:
145
# dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
146
# mediatype := [ type "/" subtype ] *( ";" parameter )
148
# parameter := attribute "=" value
149
# type/subtype defaults to "text/plain"
150
comma = url.find(',')
154
semi = url.find(';', 0, comma)
159
if '=' in type or '/' not in type:
161
return type, None # never compressed, so encoding is None
163
# Lazy import to improve module import time
166
return self._guess_file_type(url, strict, posixpath.splitext)
168
def guess_file_type(self, path, *, strict=True):
169
"""Guess the type of a file based on its path.
171
Similar to guess_type(), but takes file path instead of URL.
173
# Lazy import to improve module import time
176
path = os.fsdecode(path)
177
path = os.path.splitdrive(path)[1]
178
4
return self._guess_file_type(path, strict, os.path.splitext)
180
def _guess_file_type(self, path, strict, splitext):
181
3
base, ext = splitext(path)
182
while (ext_lower := ext.lower()) in self.suffix_map:
183
base, ext = splitext(base + self.suffix_map[ext_lower])
184
# encodings_map is case sensitive
185
if ext in self.encodings_map:
186
encoding = self.encodings_map[ext]
187
base, ext = splitext(base)
191
types_map = self.types_map[True]
193
return types_map[ext], encoding
195
return None, encoding
196
types_map = self.types_map[False]
198
return types_map[ext], encoding
200
return None, encoding
202
def guess_all_extensions(self, type, strict=True):
203
"""Guess the extensions for a file based on its MIME type.
205
Return value is a list of strings giving the possible filename
206
extensions, including the leading dot ('.'). The extension is not
207
guaranteed to have been associated with any particular data stream,
208
but would be mapped to the MIME type 'type' by guess_type().
210
Optional 'strict' argument when false adds a bunch of commonly found,
211
but non-standard types.
214
extensions = list(self.types_map_inv[True].get(type, []))
216
for ext in self.types_map_inv[False].get(type, []):
217
if ext not in extensions:
218
extensions.append(ext)
221
def guess_extension(self, type, strict=True):
222
"""Guess the extension for a file based on its MIME type.
224
Return value is a string giving a filename extension,
225
including the leading dot ('.'). The extension is not
226
guaranteed to have been associated with any particular data
227
stream, but would be mapped to the MIME type 'type' by
228
guess_type(). If no extension can be guessed for 'type', None
231
Optional 'strict' argument when false adds a bunch of commonly found,
232
but non-standard types.
234
extensions = self.guess_all_extensions(type, strict)
239
def read(self, filename, strict=True):
241
Read a single mime.types-format file, specified by pathname.
243
If strict is true, information will be added to
244
list of standard types, else to the list of non-standard
247
with open(filename, encoding='utf-8') as fp:
248
self.readfp(fp, strict)
250
def readfp(self, fp, strict=True):
252
Read a single mime.types-format file.
254
If strict is true, information will be added to
255
list of standard types, else to the list of non-standard
258
while line := fp.readline():
260
for i in range(len(words)):
261
if words[i][0] == '#':
266
type, suffixes = words[0], words[1:]
267
for suff in suffixes:
268
self.add_type(type, '.' + suff, strict)
270
def read_windows_registry(self, strict=True):
272
Load the MIME types database from Windows registry.
274
If strict is true, information will be added to
275
list of standard types, else to the list of non-standard
279
if not _mimetypes_read_windows_registry and not _winreg:
282
add_type = self.add_type
284
add_type = lambda type, ext: self.add_type(type, ext, True)
286
# Accelerated function if it is available
287
if _mimetypes_read_windows_registry:
288
_mimetypes_read_windows_registry(add_type)
290
self._read_windows_registry(add_type)
293
def _read_windows_registry(cls, add_type):
294
def enum_types(mimedb):
298
ctype = _winreg.EnumKey(mimedb, i)
302
if '\0' not in ctype:
306
with _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT, '') as hkcr:
307
for subkeyname in enum_types(hkcr):
309
with _winreg.OpenKey(hkcr, subkeyname) as subkey:
310
# Only check file extensions
311
if not subkeyname.startswith("."):
313
# raises OSError if no 'Content Type' value
314
mimetype, datatype = _winreg.QueryValueEx(
315
subkey, 'Content Type')
316
if datatype != _winreg.REG_SZ:
318
add_type(mimetype, subkeyname)
322
def guess_type(url, strict=True):
323
"""Guess the type of a file based on its URL.
325
Return value is a tuple (type, encoding) where type is None if the
326
type can't be guessed (no or unknown suffix) or a string of the
327
form type/subtype, usable for a MIME Content-type header; and
328
encoding is None for no encoding or the name of the program used
329
to encode (e.g. compress or gzip). The mappings are table
330
driven. Encoding suffixes are case sensitive; type suffixes are
331
first tried case sensitive, then case insensitive.
333
The suffixes .tgz, .taz and .tz (case sensitive!) are all mapped
334
to ".tar.gz". (This is table-driven too, using the dictionary
337
Optional 'strict' argument when false adds a bunch of commonly found, but
342
6
return _db.guess_type(url, strict)
345
def guess_file_type(path, *, strict=True):
346
"""Guess the type of a file based on its path.
348
Similar to guess_type(), but takes file path instead of URL.
352
return _db.guess_file_type(path, strict=strict)
355
def guess_all_extensions(type, strict=True):
356
"""Guess the extensions for a file based on its MIME type.
358
Return value is a list of strings giving the possible filename
359
extensions, including the leading dot ('.'). The extension is not
360
guaranteed to have been associated with any particular data
361
stream, but would be mapped to the MIME type 'type' by
362
guess_type(). If no extension can be guessed for 'type', None
365
Optional 'strict' argument when false adds a bunch of commonly found,
366
but non-standard types.
370
return _db.guess_all_extensions(type, strict)
372
def guess_extension(type, strict=True):
373
"""Guess the extension for a file based on its MIME type.
375
Return value is a string giving a filename extension, including the
376
leading dot ('.'). The extension is not guaranteed to have been
377
associated with any particular data stream, but would be mapped to the
378
MIME type 'type' by guess_type(). If no extension can be guessed for
379
'type', None is returned.
381
Optional 'strict' argument when false adds a bunch of commonly found,
382
but non-standard types.
386
return _db.guess_extension(type, strict)
388
def add_type(type, ext, strict=True):
389
"""Add a mapping between a type and an extension.
391
When the extension is already known, the new
392
type will replace the old one. When the type
393
is already known the extension will be added
394
to the list of known extensions.
396
If strict is true, information will be added to
397
list of standard types, else to the list of non-standard
402
return _db.add_type(type, ext, strict)
405
def init(files=None):
406
global suffix_map, types_map, encodings_map, common_types
408
inited = True # so that MimeTypes.__init__() doesn't call us again
410
if files is None or _db is None:
412
# Quick return if not supported
413
db.read_windows_registry()
418
files = knownfiles + list(files)
422
# Lazy import to improve module import time
426
if os.path.isfile(file):
428
encodings_map = db.encodings_map
429
suffix_map = db.suffix_map
430
types_map = db.types_map[True]
431
common_types = db.types_map[False]
432
# Make the DB a global variable now that it is fully initialized
436
def read_mime_types(file):
438
f = open(file, encoding='utf-8')
444
return db.types_map[True]
447
def _default_mime_types():
448
global suffix_map, _suffix_map_default
449
global encodings_map, _encodings_map_default
450
global types_map, _types_map_default
451
global common_types, _common_types_default
453
suffix_map = _suffix_map_default = {
462
encodings_map = _encodings_map_default = {
470
# Before adding new types, make sure they are either registered with IANA,
471
# at https://www.iana.org/assignments/media-types/media-types.xhtml
472
# or extensions, i.e. using the x- prefix
474
# If you add to these, please keep them sorted by mime type.
475
# Make sure the entry with the preferred file extension for a particular mime type
476
# appears before any others of the same mimetype.
477
types_map = _types_map_default = {
478
'.js' : 'text/javascript',
479
'.mjs' : 'text/javascript',
480
'.dcm' : 'application/dicom',
481
'.efi' : 'application/efi',
482
'.epub' : 'application/epub+zip',
483
'.gz' : 'application/gzip',
484
'.json' : 'application/json',
485
'.webmanifest': 'application/manifest+json',
486
'.doc' : 'application/msword',
487
'.dot' : 'application/msword',
488
'.wiz' : 'application/msword',
489
'.nq' : 'application/n-quads',
490
'.nt' : 'application/n-triples',
491
'.cjs' : 'application/node',
492
'.bin' : 'application/octet-stream',
493
'.a' : 'application/octet-stream',
494
'.o' : 'application/octet-stream',
495
'.obj' : 'application/octet-stream',
496
'.so' : 'application/octet-stream',
497
'.oda' : 'application/oda',
498
'.ogx' : 'application/ogg',
499
'.pdf' : 'application/pdf',
500
'.ai' : 'application/pdf',
501
'.p7c' : 'application/pkcs7-mime',
502
'.ps' : 'application/postscript',
503
'.eps' : 'application/postscript',
504
'.rtf' : 'application/rtf',
505
'.sql' : 'application/sql',
506
'.texi' : 'application/texinfo',
507
'.texinfo': 'application/texinfo',
508
'.toml' : 'application/toml',
509
'.trig' : 'application/trig',
510
'.m3u' : 'application/vnd.apple.mpegurl',
511
'.m3u8' : 'application/vnd.apple.mpegurl',
512
'.dll' : 'application/vnd.microsoft.portable-executable',
513
'.exe' : 'application/vnd.microsoft.portable-executable',
514
'.cab' : 'application/vnd.ms-cab-compressed',
515
'.xls' : 'application/vnd.ms-excel',
516
'.xlb' : 'application/vnd.ms-excel',
517
'.eot' : 'application/vnd.ms-fontobject',
518
'.chm' : 'application/vnd.ms-htmlhelp',
519
'.thmx' : 'application/vnd.ms-officetheme',
520
'.ppt' : 'application/vnd.ms-powerpoint',
521
'.pot' : 'application/vnd.ms-powerpoint',
522
'.ppa' : 'application/vnd.ms-powerpoint',
523
'.pps' : 'application/vnd.ms-powerpoint',
524
'.pwz' : 'application/vnd.ms-powerpoint',
525
'.odg' : 'application/vnd.oasis.opendocument.graphics',
526
'.odp' : 'application/vnd.oasis.opendocument.presentation',
527
'.ods' : 'application/vnd.oasis.opendocument.spreadsheet',
528
'.odt' : 'application/vnd.oasis.opendocument.text',
529
'.pptx' : 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
530
'.xlsx' : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
531
'.docx' : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
532
'.rar' : 'application/vnd.rar',
533
'.sqlite3': 'application/vnd.sqlite3',
534
'.sqlite' : 'application/vnd.sqlite3',
535
'.wasm' : 'application/wasm',
536
'.7z' : 'application/x-7z-compressed',
537
'.bcpio' : 'application/x-bcpio',
538
'.cpio' : 'application/x-cpio',
539
'.csh' : 'application/x-csh',
540
'.deb' : 'application/x-debian-package',
541
'.dvi' : 'application/x-dvi',
542
'.gtar' : 'application/x-gtar',
543
'.hdf' : 'application/x-hdf',
544
'.h5' : 'application/x-hdf5',
545
'.latex' : 'application/x-latex',
546
'.mif' : 'application/x-mif',
547
'.cdf' : 'application/x-netcdf',
548
'.nc' : 'application/x-netcdf',
549
'.p12' : 'application/x-pkcs12',
550
'.php' : 'application/x-httpd-php',
551
'.pfx' : 'application/x-pkcs12',
552
'.ram' : 'application/x-pn-realaudio',
553
'.pyc' : 'application/x-python-code',
554
'.pyo' : 'application/x-python-code',
555
'.rpm' : 'application/x-rpm',
556
'.sh' : 'application/x-sh',
557
'.shar' : 'application/x-shar',
558
'.swf' : 'application/x-shockwave-flash',
559
'.sv4cpio': 'application/x-sv4cpio',
560
'.sv4crc' : 'application/x-sv4crc',
561
'.tar' : 'application/x-tar',
562
'.tcl' : 'application/x-tcl',
563
'.tex' : 'application/x-tex',
564
'.roff' : 'application/x-troff',
565
'.t' : 'application/x-troff',
566
'.tr' : 'application/x-troff',
567
'.man' : 'application/x-troff-man',
568
'.me' : 'application/x-troff-me',
569
'.ms' : 'application/x-troff-ms',
570
'.ustar' : 'application/x-ustar',
571
'.src' : 'application/x-wais-source',
572
'.xsl' : 'application/xml',
573
'.rdf' : 'application/xml',
574
'.wsdl' : 'application/xml',
575
'.xpdl' : 'application/xml',
576
'.yaml' : 'application/yaml',
577
'.yml' : 'application/yaml',
578
'.zip' : 'application/zip',
579
'.3gp' : 'audio/3gpp',
580
'.3gpp' : 'audio/3gpp',
581
'.3g2' : 'audio/3gpp2',
582
'.3gpp2' : 'audio/3gpp2',
583
'.aac' : 'audio/aac',
584
'.adts' : 'audio/aac',
585
'.loas' : 'audio/aac',
586
'.ass' : 'audio/aac',
587
'.au' : 'audio/basic',
588
'.snd' : 'audio/basic',
589
'.flac' : 'audio/flac',
590
'.mka' : 'audio/matroska',
591
'.m4a' : 'audio/mp4',
592
'.mp3' : 'audio/mpeg',
593
'.mp2' : 'audio/mpeg',
594
'.ogg' : 'audio/ogg',
595
'.opus' : 'audio/opus',
596
'.aif' : 'audio/x-aiff',
597
'.aifc' : 'audio/x-aiff',
598
'.aiff' : 'audio/x-aiff',
599
'.ra' : 'audio/x-pn-realaudio',
600
'.wav' : 'audio/vnd.wave',
601
'.weba' : 'audio/webm',
602
'.ttc' : 'font/collection',
605
'.woff' : 'font/woff',
606
'.woff2' : 'font/woff2',
607
'.hjif' : 'haptics/hjif',
608
'.hmpg' : 'haptics/hmpg',
609
'.ivs' : 'haptics/ivs',
610
'.avif' : 'image/avif',
611
'.bmp' : 'image/bmp',
612
'.emf' : 'image/emf',
613
'.fits' : 'image/fits',
614
'.g3' : 'image/g3fax',
615
'.gif' : 'image/gif',
616
'.ief' : 'image/ief',
617
'.jp2' : 'image/jp2',
618
'.jpg' : 'image/jpeg',
619
'.jpe' : 'image/jpeg',
620
'.jpeg' : 'image/jpeg',
621
'.jpm' : 'image/jpm',
622
'.jpx' : 'image/jpx',
623
'.jxl' : 'image/jxl',
624
'.heic' : 'image/heic',
625
'.heif' : 'image/heif',
626
'.png' : 'image/png',
627
'.svg' : 'image/svg+xml',
628
'.t38' : 'image/t38',
629
'.tiff' : 'image/tiff',
630
'.tif' : 'image/tiff',
631
'.tfx' : 'image/tiff-fx',
632
'.ico' : 'image/vnd.microsoft.icon',
633
'.webp' : 'image/webp',
634
'.wmf' : 'image/wmf',
635
'.ras' : 'image/x-cmu-raster',
636
'.pnm' : 'image/x-portable-anymap',
637
'.pbm' : 'image/x-portable-bitmap',
638
'.pgm' : 'image/x-portable-graymap',
639
'.ppm' : 'image/x-portable-pixmap',
640
'.rgb' : 'image/x-rgb',
641
'.xbm' : 'image/x-xbitmap',
642
'.xpm' : 'image/x-xpixmap',
643
'.xwd' : 'image/x-xwindowdump',
644
'.eml' : 'message/rfc822',
645
'.mht' : 'message/rfc822',
646
'.mhtml' : 'message/rfc822',
647
'.nws' : 'message/rfc822',
648
'.gltf' : 'model/gltf+json',
649
'.glb' : 'model/gltf-binary',
650
'.stl' : 'model/stl',
653
'.html' : 'text/html',
654
'.htm' : 'text/html',
655
'.md' : 'text/markdown',
656
'.markdown': 'text/markdown',
658
'.txt' : 'text/plain',
659
'.bat' : 'text/plain',
662
'.ksh' : 'text/plain',
663
'.pl' : 'text/plain',
664
'.srt' : 'text/plain',
665
'.rtx' : 'text/richtext',
666
'.tsv' : 'text/tab-separated-values',
668
'.py' : 'text/x-python',
669
'.rst' : 'text/x-rst',
670
'.etx' : 'text/x-setext',
671
'.sgm' : 'text/x-sgml',
672
'.sgml' : 'text/x-sgml',
673
'.vcf' : 'text/x-vcard',
675
'.mkv' : 'video/matroska',
676
'.mk3d' : 'video/matroska-3d',
677
'.mp4' : 'video/mp4',
678
'.mpeg' : 'video/mpeg',
679
'.m1v' : 'video/mpeg',
680
'.mpa' : 'video/mpeg',
681
'.mpe' : 'video/mpeg',
682
'.mpg' : 'video/mpeg',
683
'.ogv' : 'video/ogg',
684
'.mov' : 'video/quicktime',
685
'.qt' : 'video/quicktime',
686
'.webm' : 'video/webm',
687
'.avi' : 'video/vnd.avi',
688
'.m4v' : 'video/x-m4v',
689
'.wmv' : 'video/x-ms-wmv',
690
'.movie' : 'video/x-sgi-movie',
693
# These are non-standard types, commonly found in the wild. They will
694
# only match if strict=0 flag is given to the API methods.
696
# Please sort these too
697
common_types = _common_types_default = {
698
'.apk' : 'application/vnd.android.package-archive',
699
'.midi': 'audio/midi',
700
'.mid' : 'audio/midi',
701
'.pict': 'image/pict',
702
'.pct' : 'image/pict',
703
'.pic' : 'image/pict',
708
_default_mime_types()
711
def _parse_args(args):
712
from argparse import ArgumentParser
714
parser = ArgumentParser(
715
description='map filename extensions to MIME types',
720
help='guess extension instead of type'
725
help='additionally search for common but non-standard types'
727
parser.add_argument('type', nargs='+', help='a type to search')
728
args = parser.parse_args(args)
729
return args, parser.format_help()
732
def _main(args=None):
733
"""Run the mimetypes command-line interface and return a text to print."""
734
args, help_text = _parse_args(args)
738
for gtype in args.type:
739
guess = guess_extension(gtype, not args.lenient)
741
results.append(str(guess))
743
results.append(f"error: unknown type {gtype}")
746
for gtype in args.type:
747
guess, encoding = guess_type(gtype, not args.lenient)
749
results.append(f"type: {guess} encoding: {encoding}")
751
results.append(f"error: media type unknown for {gtype}")
755
if __name__ == '__main__':
759
print("\n".join(results))
760
sys.exit(any(result.startswith("error: ") for result in results))