https://github.com/python/cpython/commit/46107ad9da0add7aa5c0a899e159d89c1376d6be
commit: 46107ad9da0add7aa5c0a899e159d89c1376d6be
branch: main
author: Xiao Yuan <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-06-15T15:05:29Z
summary:
gh-92455: Respect case-sensitive mimetype suffixes (GH-148782)
files:
A Misc/NEWS.d/next/Library/2026-04-20-01-24-22.gh-issue-92455.vXhmad.rst
M Doc/library/mimetypes.rst
M Lib/mimetypes.py
M Lib/test/test_mimetypes.py
diff --git a/Doc/library/mimetypes.rst b/Doc/library/mimetypes.rst
index f33098faf7d8a77..5c29fff146eef00 100644
--- a/Doc/library/mimetypes.rst
+++ b/Doc/library/mimetypes.rst
@@ -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
@@ -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.
@@ -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.
diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py
index 15e8c0a437bfd93..4339ef5a61397dd 100644
--- a/Lib/mimetypes.py
+++ b/Lib/mimetypes.py
@@ -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.
@@ -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
@@ -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.
diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py
index 1a3b49b87b121f2..19983fa3fa7628d 100644
--- a/Lib/test/test_mimetypes.py
+++ b/Lib/test/test_mimetypes.py
@@ -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))
diff --git
a/Misc/NEWS.d/next/Library/2026-04-20-01-24-22.gh-issue-92455.vXhmad.rst
b/Misc/NEWS.d/next/Library/2026-04-20-01-24-22.gh-issue-92455.vXhmad.rst
new file mode 100644
index 000000000000000..8d2a11cb7761377
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-04-20-01-24-22.gh-issue-92455.vXhmad.rst
@@ -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.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]