Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2720dbd
gh-92455: Respect case-sensitive mimetype suffixes
yuanx749 Apr 19, 2026
547a64e
Add NEWS entry
yuanx749 Apr 19, 2026
992a6e3
Merge remote-tracking branch 'upstream/main' into fix-mimetypes-case-…
yuanx749 Apr 19, 2026
561a0aa
Merge remote-tracking branch 'upstream/main' into fix-mimetypes-case-…
yuanx749 May 16, 2026
90b7360
Merge remote-tracking branch 'upstream/main' into fix-mimetypes-case-…
yuanx749 May 20, 2026
268d57c
Merge remote-tracking branch 'upstream/main' into fix-mimetypes-case-…
yuanx749 Jun 3, 2026
47b2b6b
Add non-strict mimetype case test
yuanx749 Jun 3, 2026
fe1096a
Merge branch 'main' into fix-mimetypes-case-sensitive-add-type
yuanx749 Jun 4, 2026
e554892
Test strict lookup excludes non-strict types
yuanx749 Jun 6, 2026
3085bb9
Merge remote-tracking branch 'upstream/main' into fix-mimetypes-case-…
yuanx749 Jun 6, 2026
206be36
Test suffix map case lookup
yuanx749 Jun 8, 2026
6f639f2
Respect suffix map case lookup
yuanx749 Jun 8, 2026
4886f13
Document mimetypes case lookup
yuanx749 Jun 8, 2026
046dc95
Merge remote-tracking branch 'upstream/main' into fix-mimetypes-case-…
yuanx749 Jun 8, 2026
430a329
Merge remote-tracking branch 'upstream/main' into fix-mimetypes-case-…
yuanx749 Jun 8, 2026
2ed828a
Merge branch 'main' into fix-mimetypes-case-sensitive-add-type
yuanx749 Jun 9, 2026
50552fa
Merge remote-tracking branch 'upstream/main' into fix-mimetypes-case-…
yuanx749 Jun 9, 2026
0874c07
Update documentation
yuanx749 Jun 14, 2026
5c56f96
Merge remote-tracking branch 'upstream/main' into fix-mimetypes-case-…
yuanx749 Jun 14, 2026
44a5da7
Merge remote-tracking branch 'upstream/main' into fix-mimetypes-case-…
yuanx749 Jun 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Doc/library/mimetypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ the information :func:`init` sets up.
(e.g. :program:`compress` or :program:`gzip`). The encoding is suitable for use
as a :mailheader:`Content-Encoding` header, **not** as a
:mailheader:`Content-Transfer-Encoding` header. The mappings are table driven.
Encoding suffixes are case sensitive; type suffixes are first tried case
sensitively, then case insensitively.
Encoding suffixes are case-sensitive. Suffix mappings and type suffixes are
first tried case-sensitively, then case-insensitively.

The optional *strict* argument is a flag specifying whether the list of known MIME types
is limited to only the official types `registered with IANA
Expand Down Expand Up @@ -131,6 +131,8 @@ behavior of the module.
is already known the extension will be added to the list of known extensions.
Valid extensions are empty or start with a ``'.'``.

Registered lower-case extensions are matched case-insensitively.

When *strict* is ``True`` (the default), the mapping will be added to the
official MIME types, otherwise to the non-standard ones.

Expand Down Expand Up @@ -312,6 +314,8 @@ than one MIME-type database; it provides an interface similar to the one of 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.

Registered lower-case extensions are matched case-insensitively.

When *strict* is ``True`` (the default), the mapping will be added to the
official MIME types, otherwise to the non-standard ones.

Expand Down
22 changes: 19 additions & 3 deletions Lib/mimetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ def add_type(self, type, ext, strict=True):
is already known the extension will be added
to the list of known extensions.

Registered lower-case extensions are matched
case-insensitively.

If strict is true, information will be added to
list of standard types, else to the list of non-standard
types.
Expand Down Expand Up @@ -172,23 +175,33 @@ def guess_file_type(self, path, *, strict=True):

def _guess_file_type(self, path, strict, splitext):
base, ext = splitext(path)
while (ext_lower := ext.lower()) in self.suffix_map:
base, ext = splitext(base + self.suffix_map[ext_lower])
while True:
if ext in self.suffix_map:
suffix = self.suffix_map[ext]
elif (ext_lower := ext.lower()) in self.suffix_map:
suffix = self.suffix_map[ext_lower]
else:
break
base, ext = splitext(base + suffix)
# encodings_map is case sensitive
if ext in self.encodings_map:
encoding = self.encodings_map[ext]
base, ext = splitext(base)
else:
encoding = None
ext = ext.lower()
ext_lower = ext.lower()
types_map = self.types_map[True]
if ext in types_map:
return types_map[ext], encoding
if 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
if ext_lower in types_map:
return types_map[ext_lower], encoding
else:
return None, encoding

Expand Down Expand Up @@ -386,6 +399,9 @@ def add_type(type, ext, strict=True):
is already known the extension will be added
to the list of known extensions.

Registered lower-case extensions are matched
case-insensitively.

If strict is true, information will be added to
list of standard types, else to the list of non-standard
types.
Expand Down
44 changes: 44 additions & 0 deletions Lib/test/test_mimetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,50 @@ def test_case_sensitivity(self):
eq(self.db.guess_file_type("foobar.tar.z"), (None, None))
eq(self.db.guess_type("scheme:foobar.tar.z"), (None, None))

def test_suffix_map_case_sensitive_preferred(self):
self.db.suffix_map[".TEST-SUFFIX"] = ".tar.gz"
self.db.suffix_map[".test-suffix"] = ".tar.xz"
self.assertEqual(
self.db.guess_file_type("example.TEST-SUFFIX"),
("application/x-tar", "gzip"),
)
self.assertEqual(
self.db.guess_file_type("example.test-suffix"),
("application/x-tar", "xz"),
)

def test_added_types_case_sensitive_preferred(self):
self.db.add_type("text/x-test-uppercase-r", ".R")
self.db.add_type("text/x-test-lowercase-r", ".r")
self.assertEqual(
self.db.guess_file_type("example.R"),
("text/x-test-uppercase-r", None),
)
self.assertEqual(
self.db.guess_file_type("example.r"),
("text/x-test-lowercase-r", None),
)
self.db.add_type("text/x-test-uppercase-non-strict",
".NON-STRICT-EXT", strict=False)
self.db.add_type("text/x-test-lowercase-non-strict",
".non-strict-ext", strict=False)
self.assertEqual(
self.db.guess_file_type("example.NON-STRICT-EXT"),
(None, None),
)
self.assertEqual(
self.db.guess_file_type("example.non-strict-ext"),
(None, None),
)
self.assertEqual(
self.db.guess_file_type("example.NON-STRICT-EXT", strict=False),
("text/x-test-uppercase-non-strict", None),
)
self.assertEqual(
self.db.guess_file_type("example.non-strict-ext", strict=False),
("text/x-test-lowercase-non-strict", None),
)

def test_default_data(self):
eq = self.assertEqual
eq(self.db.guess_file_type("foo.html"), ("text/html", None))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix :mod:`mimetypes` to prefer case-sensitive matches for suffix mappings and
MIME type suffixes before falling back to case-insensitive matches.
Contributed by Xiao Yuan.
Loading