gh-104773: PEP 594: Remove the mailcap module (#104867)

Remove Lib/test/mailcap.txt test file.
This commit is contained in:
Victor Stinner 2023-05-24 16:16:55 +02:00 committed by GitHub
parent bbc5e5c7d7
commit 9d457e1154
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 9 additions and 715 deletions

View file

@ -1,94 +0,0 @@
:mod:`mailcap` --- Mailcap file handling
========================================
.. module:: mailcap
:synopsis: Mailcap file handling.
:deprecated:
**Source code:** :source:`Lib/mailcap.py`
.. deprecated-removed:: 3.11 3.13
The :mod:`mailcap` module is deprecated
(see :pep:`PEP 594 <594#mailcap>` for details).
The :mod:`mimetypes` module provides an alternative.
--------------
Mailcap files are used to configure how MIME-aware applications such as mail
readers and web browsers react to files with different MIME types. (The name
"mailcap" is derived from the phrase "mail capability".) For example, a mailcap
file might contain a line like ``video/mpeg; xmpeg %s``. Then, if the user
encounters an email message or web document with the MIME type
:mimetype:`video/mpeg`, ``%s`` will be replaced by a filename (usually one
belonging to a temporary file) and the :program:`xmpeg` program can be
automatically started to view the file.
The mailcap format is documented in :rfc:`1524`, "A User Agent Configuration
Mechanism For Multimedia Mail Format Information", but is not an internet
standard. However, mailcap files are supported on most Unix systems.
.. function:: findmatch(caps, MIMEtype, key='view', filename='/dev/null', plist=[])
Return a 2-tuple; the first element is a string containing the command line to
be executed (which can be passed to :func:`os.system`), and the second element
is the mailcap entry for a given MIME type. If no matching MIME type can be
found, ``(None, None)`` is returned.
*key* is the name of the field desired, which represents the type of activity to
be performed; the default value is 'view', since in the most common case you
simply want to view the body of the MIME-typed data. Other possible values
might be 'compose' and 'edit', if you wanted to create a new body of the given
MIME type or alter the existing body data. See :rfc:`1524` for a complete list
of these fields.
*filename* is the filename to be substituted for ``%s`` in the command line; the
default value is ``'/dev/null'`` which is almost certainly not what you want, so
usually you'll override it by specifying a filename.
*plist* can be a list containing named parameters; the default value is simply
an empty list. Each entry in the list must be a string containing the parameter
name, an equals sign (``'='``), and the parameter's value. Mailcap entries can
contain named parameters like ``%{foo}``, which will be replaced by the value
of the parameter named 'foo'. For example, if the command line ``showpartial
%{id} %{number} %{total}`` was in a mailcap file, and *plist* was set to
``['id=1', 'number=2', 'total=3']``, the resulting command line would be
``'showpartial 1 2 3'``.
In a mailcap file, the "test" field can optionally be specified to test some
external condition (such as the machine architecture, or the window system in
use) to determine whether or not the mailcap line applies. :func:`findmatch`
will automatically check such conditions and skip the entry if the check fails.
.. versionchanged:: 3.11
To prevent security issues with shell metacharacters (symbols that have
special effects in a shell command line), ``findmatch`` will refuse
to inject ASCII characters other than alphanumerics and ``@+=:,./-_``
into the returned command line.
If a disallowed character appears in *filename*, ``findmatch`` will always
return ``(None, None)`` as if no entry was found.
If such a character appears elsewhere (a value in *plist* or in *MIMEtype*),
``findmatch`` will ignore all mailcap entries which use that value.
A :mod:`warning <warnings>` will be raised in either case.
.. function:: getcaps()
Returns a dictionary mapping MIME types to a list of mailcap file entries. This
dictionary must be passed to the :func:`findmatch` function. An entry is stored
as a list of dictionaries, but it shouldn't be necessary to know the details of
this representation.
The information is derived from all of the mailcap files found on the system.
Settings in the user's mailcap file :file:`$HOME/.mailcap` will override
settings in the system mailcap files :file:`/etc/mailcap`,
:file:`/usr/etc/mailcap`, and :file:`/usr/local/etc/mailcap`.
An example usage::
>>> import mailcap
>>> d = mailcap.getcaps()
>>> mailcap.findmatch(d, 'video/mpeg', filename='tmp1223')
('xmpeg tmp1223', {'view': 'xmpeg %s'})

View file

@ -15,7 +15,6 @@ backwards compatibility. They have been superseded by other modules.
chunk.rst
crypt.rst
imghdr.rst
mailcap.rst
msilib.rst
nis.rst
nntplib.rst

View file

@ -1737,7 +1737,7 @@ Modules
+---------------------+---------------------+---------------------+---------------------+---------------------+
| :mod:`!cgi` | :mod:`imghdr` | :mod:`nntplib` | :mod:`spwd` | :mod:`xdrlib` |
+---------------------+---------------------+---------------------+---------------------+---------------------+
| :mod:`!cgitb` | :mod:`mailcap` | :mod:`!ossaudiodev` | :mod:`!sunau` | |
| :mod:`!cgitb` | :mod:`!mailcap` | :mod:`!ossaudiodev` | :mod:`!sunau` | |
+---------------------+---------------------+---------------------+---------------------+---------------------+
(Contributed by Brett Cannon in :issue:`47061` and Victor Stinner in

View file

@ -893,7 +893,7 @@ Modules (see :pep:`594`):
* :mod:`chunk`
* :mod:`crypt`
* :mod:`imghdr`
* :mod:`mailcap`
* :mod:`!mailcap`
* :mod:`msilib`
* :mod:`nis`
* :mod:`nntplib`

View file

@ -164,6 +164,10 @@ Removed
* :pep:`594`: Remove the :mod:`!sunau` module, deprecated in Python 3.11.
(Contributed by Victor Stinner in :gh:`104773`.)
* :pep:`594`: Remove the :mod:`!mailcap` module, deprecated in Python 3.11.
The :mod:`mimetypes` module provides an alternative.
(Contributed by Victor Stinner in :gh:`104773`.)
Porting to Python 3.13
======================

View file

@ -1,302 +0,0 @@
"""Mailcap file handling. See RFC 1524."""
import os
import warnings
import re
__all__ = ["getcaps","findmatch"]
_DEPRECATION_MSG = ('The {name} module is deprecated and will be removed in '
'Python {remove}. See the mimetypes module for an '
'alternative.')
warnings._deprecated(__name__, _DEPRECATION_MSG, remove=(3, 13))
def lineno_sort_key(entry):
# Sort in ascending order, with unspecified entries at the end
if 'lineno' in entry:
return 0, entry['lineno']
else:
return 1, 0
_find_unsafe = re.compile(r'[^\xa1-\U0010FFFF\w@+=:,./-]').search
class UnsafeMailcapInput(Warning):
"""Warning raised when refusing unsafe input"""
# Part 1: top-level interface.
def getcaps():
"""Return a dictionary containing the mailcap database.
The dictionary maps a MIME type (in all lowercase, e.g. 'text/plain')
to a list of dictionaries corresponding to mailcap entries. The list
collects all the entries for that MIME type from all available mailcap
files. Each dictionary contains key-value pairs for that MIME type,
where the viewing command is stored with the key "view".
"""
caps = {}
lineno = 0
for mailcap in listmailcapfiles():
try:
fp = open(mailcap, 'r')
except OSError:
continue
with fp:
morecaps, lineno = _readmailcapfile(fp, lineno)
for key, value in morecaps.items():
if not key in caps:
caps[key] = value
else:
caps[key] = caps[key] + value
return caps
def listmailcapfiles():
"""Return a list of all mailcap files found on the system."""
# This is mostly a Unix thing, but we use the OS path separator anyway
if 'MAILCAPS' in os.environ:
pathstr = os.environ['MAILCAPS']
mailcaps = pathstr.split(os.pathsep)
else:
if 'HOME' in os.environ:
home = os.environ['HOME']
else:
# Don't bother with getpwuid()
home = '.' # Last resort
mailcaps = [home + '/.mailcap', '/etc/mailcap',
'/usr/etc/mailcap', '/usr/local/etc/mailcap']
return mailcaps
# Part 2: the parser.
def readmailcapfile(fp):
"""Read a mailcap file and return a dictionary keyed by MIME type."""
warnings.warn('readmailcapfile is deprecated, use getcaps instead',
DeprecationWarning, 2)
caps, _ = _readmailcapfile(fp, None)
return caps
def _readmailcapfile(fp, lineno):
"""Read a mailcap file and return a dictionary keyed by MIME type.
Each MIME type is mapped to an entry consisting of a list of
dictionaries; the list will contain more than one such dictionary
if a given MIME type appears more than once in the mailcap file.
Each dictionary contains key-value pairs for that MIME type, where
the viewing command is stored with the key "view".
"""
caps = {}
while line := fp.readline():
# Ignore comments and blank lines
if line[0] == '#' or line.strip() == '':
continue
nextline = line
# Join continuation lines
while nextline[-2:] == '\\\n':
nextline = fp.readline()
if not nextline: nextline = '\n'
line = line[:-2] + nextline
# Parse the line
key, fields = parseline(line)
if not (key and fields):
continue
if lineno is not None:
fields['lineno'] = lineno
lineno += 1
# Normalize the key
types = key.split('/')
for j in range(len(types)):
types[j] = types[j].strip()
key = '/'.join(types).lower()
# Update the database
if key in caps:
caps[key].append(fields)
else:
caps[key] = [fields]
return caps, lineno
def parseline(line):
"""Parse one entry in a mailcap file and return a dictionary.
The viewing command is stored as the value with the key "view",
and the rest of the fields produce key-value pairs in the dict.
"""
fields = []
i, n = 0, len(line)
while i < n:
field, i = parsefield(line, i, n)
fields.append(field)
i = i+1 # Skip semicolon
if len(fields) < 2:
return None, None
key, view, rest = fields[0], fields[1], fields[2:]
fields = {'view': view}
for field in rest:
i = field.find('=')
if i < 0:
fkey = field
fvalue = ""
else:
fkey = field[:i].strip()
fvalue = field[i+1:].strip()
if fkey in fields:
# Ignore it
pass
else:
fields[fkey] = fvalue
return key, fields
def parsefield(line, i, n):
"""Separate one key-value pair in a mailcap entry."""
start = i
while i < n:
c = line[i]
if c == ';':
break
elif c == '\\':
i = i+2
else:
i = i+1
return line[start:i].strip(), i
# Part 3: using the database.
def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]):
"""Find a match for a mailcap entry.
Return a tuple containing the command line, and the mailcap entry
used; (None, None) if no match is found. This may invoke the
'test' command of several matching entries before deciding which
entry to use.
"""
if _find_unsafe(filename):
msg = "Refusing to use mailcap with filename %r. Use a safe temporary filename." % (filename,)
warnings.warn(msg, UnsafeMailcapInput)
return None, None
entries = lookup(caps, MIMEtype, key)
# XXX This code should somehow check for the needsterminal flag.
for e in entries:
if 'test' in e:
test = subst(e['test'], filename, plist)
if test is None:
continue
if test and os.system(test) != 0:
continue
command = subst(e[key], MIMEtype, filename, plist)
if command is not None:
return command, e
return None, None
def lookup(caps, MIMEtype, key=None):
entries = []
if MIMEtype in caps:
entries = entries + caps[MIMEtype]
MIMEtypes = MIMEtype.split('/')
MIMEtype = MIMEtypes[0] + '/*'
if MIMEtype in caps:
entries = entries + caps[MIMEtype]
if key is not None:
entries = [e for e in entries if key in e]
entries = sorted(entries, key=lineno_sort_key)
return entries
def subst(field, MIMEtype, filename, plist=[]):
# XXX Actually, this is Unix-specific
res = ''
i, n = 0, len(field)
while i < n:
c = field[i]; i = i+1
if c != '%':
if c == '\\':
c = field[i:i+1]; i = i+1
res = res + c
else:
c = field[i]; i = i+1
if c == '%':
res = res + c
elif c == 's':
res = res + filename
elif c == 't':
if _find_unsafe(MIMEtype):
msg = "Refusing to substitute MIME type %r into a shell command." % (MIMEtype,)
warnings.warn(msg, UnsafeMailcapInput)
return None
res = res + MIMEtype
elif c == '{':
start = i
while i < n and field[i] != '}':
i = i+1
name = field[start:i]
i = i+1
param = findparam(name, plist)
if _find_unsafe(param):
msg = "Refusing to substitute parameter %r (%s) into a shell command" % (param, name)
warnings.warn(msg, UnsafeMailcapInput)
return None
res = res + param
# XXX To do:
# %n == number of parts if type is multipart/*
# %F == list of alternating type and filename for parts
else:
res = res + '%' + c
return res
def findparam(name, plist):
name = name.lower() + '='
n = len(name)
for p in plist:
if p[:n].lower() == name:
return p[n:]
return ''
# Part 4: test program.
def test():
import sys
caps = getcaps()
if not sys.argv[1:]:
show(caps)
return
for i in range(1, len(sys.argv), 2):
args = sys.argv[i:i+2]
if len(args) < 2:
print("usage: mailcap [MIMEtype file] ...")
return
MIMEtype = args[0]
file = args[1]
command, e = findmatch(caps, MIMEtype, 'view', file)
if not command:
print("No viewer found for", type)
else:
print("Executing:", command)
sts = os.system(command)
sts = os.waitstatus_to_exitcode(sts)
if sts:
print("Exit status:", sts)
def show(caps):
print("Mailcap files:")
for fn in listmailcapfiles(): print("\t" + fn)
print()
if not caps: caps = getcaps()
print("Mailcap entries:")
print()
ckeys = sorted(caps)
for type in ckeys:
print(type)
entries = caps[type]
for e in entries:
keys = sorted(e)
for k in keys:
print(" %-15s" % k, e[k])
print()
if __name__ == '__main__':
test()

View file

@ -1,39 +0,0 @@
# Mailcap file for test_mailcap; based on RFC 1524
# Referred to by test_mailcap.py
#
# This is a comment.
#
application/frame; showframe %s; print="cat %s | lp"
application/postscript; ps-to-terminal %s;\
needsterminal
application/postscript; ps-to-terminal %s; \
compose=idraw %s
application/x-dvi; xdvi %s
application/x-movie; movieplayer %s; compose=moviemaker %s; \
description="Movie"; \
x11-bitmap="/usr/lib/Zmail/bitmaps/movie.xbm"
application/*; echo "This is \"%t\" but \
is 50 \% Greek to me" \; cat %s; copiousoutput
audio/basic; showaudio %s; compose=audiocompose %s; edit=audiocompose %s;\
description="An audio fragment"
audio/* ; /usr/local/bin/showaudio %t
image/rgb; display %s
#image/gif; display %s
image/x-xwindowdump; display %s
# The continuation char shouldn't \
# make a difference in a comment.
message/external-body; showexternal %s %{access-type} %{name} %{site} \
%{directory} %{mode} %{server}; needsterminal; composetyped = extcompose %s; \
description="A reference to data stored in an external location"
text/richtext; shownonascii iso-8859-8 -e richtext -p %s; test=test "`echo \
%{charset} | tr '[A-Z]' '[a-z]'`" = iso-8859-8; copiousoutput
video/*; animate %s
video/mpeg; mpeg_play %s

View file

@ -1,274 +0,0 @@
import copy
import os
import sys
import test.support
import unittest
from test.support import os_helper
from test.support import warnings_helper
mailcap = warnings_helper.import_deprecated('mailcap')
# Location of mailcap file
MAILCAPFILE = test.support.findfile("mailcap.txt")
# Dict to act as mock mailcap entry for this test
# The keys and values should match the contents of MAILCAPFILE
MAILCAPDICT = {
'application/x-movie':
[{'compose': 'moviemaker %s',
'x11-bitmap': '"/usr/lib/Zmail/bitmaps/movie.xbm"',
'description': '"Movie"',
'view': 'movieplayer %s',
'lineno': 4}],
'application/*':
[{'copiousoutput': '',
'view': 'echo "This is \\"%t\\" but is 50 \\% Greek to me" \\; cat %s',
'lineno': 5}],
'audio/basic':
[{'edit': 'audiocompose %s',
'compose': 'audiocompose %s',
'description': '"An audio fragment"',
'view': 'showaudio %s',
'lineno': 6}],
'video/mpeg':
[{'view': 'mpeg_play %s', 'lineno': 13}],
'application/postscript':
[{'needsterminal': '', 'view': 'ps-to-terminal %s', 'lineno': 1},
{'compose': 'idraw %s', 'view': 'ps-to-terminal %s', 'lineno': 2}],
'application/x-dvi':
[{'view': 'xdvi %s', 'lineno': 3}],
'message/external-body':
[{'composetyped': 'extcompose %s',
'description': '"A reference to data stored in an external location"',
'needsterminal': '',
'view': 'showexternal %s %{access-type} %{name} %{site} %{directory} %{mode} %{server}',
'lineno': 10}],
'text/richtext':
[{'test': 'test "`echo %{charset} | tr \'[A-Z]\' \'[a-z]\'`" = iso-8859-8',
'copiousoutput': '',
'view': 'shownonascii iso-8859-8 -e richtext -p %s',
'lineno': 11}],
'image/x-xwindowdump':
[{'view': 'display %s', 'lineno': 9}],
'audio/*':
[{'view': '/usr/local/bin/showaudio %t', 'lineno': 7}],
'video/*':
[{'view': 'animate %s', 'lineno': 12}],
'application/frame':
[{'print': '"cat %s | lp"', 'view': 'showframe %s', 'lineno': 0}],
'image/rgb':
[{'view': 'display %s', 'lineno': 8}]
}
# For backwards compatibility, readmailcapfile() and lookup() still support
# the old version of mailcapdict without line numbers.
MAILCAPDICT_DEPRECATED = copy.deepcopy(MAILCAPDICT)
for entry_list in MAILCAPDICT_DEPRECATED.values():
for entry in entry_list:
entry.pop('lineno')
class HelperFunctionTest(unittest.TestCase):
def test_listmailcapfiles(self):
# The return value for listmailcapfiles() will vary by system.
# So verify that listmailcapfiles() returns a list of strings that is of
# non-zero length.
mcfiles = mailcap.listmailcapfiles()
self.assertIsInstance(mcfiles, list)
for m in mcfiles:
self.assertIsInstance(m, str)
with os_helper.EnvironmentVarGuard() as env:
# According to RFC 1524, if MAILCAPS env variable exists, use that
# and only that.
if "MAILCAPS" in env:
env_mailcaps = env["MAILCAPS"].split(os.pathsep)
else:
env_mailcaps = ["/testdir1/.mailcap", "/testdir2/mailcap"]
env["MAILCAPS"] = os.pathsep.join(env_mailcaps)
mcfiles = mailcap.listmailcapfiles()
self.assertEqual(env_mailcaps, mcfiles)
def test_readmailcapfile(self):
# Test readmailcapfile() using test file. It should match MAILCAPDICT.
with open(MAILCAPFILE, 'r') as mcf:
with self.assertWarns(DeprecationWarning):
d = mailcap.readmailcapfile(mcf)
self.assertDictEqual(d, MAILCAPDICT_DEPRECATED)
def test_lookup(self):
# Test without key
expected = [{'view': 'animate %s', 'lineno': 12},
{'view': 'mpeg_play %s', 'lineno': 13}]
actual = mailcap.lookup(MAILCAPDICT, 'video/mpeg')
self.assertListEqual(expected, actual)
# Test with key
key = 'compose'
expected = [{'edit': 'audiocompose %s',
'compose': 'audiocompose %s',
'description': '"An audio fragment"',
'view': 'showaudio %s',
'lineno': 6}]
actual = mailcap.lookup(MAILCAPDICT, 'audio/basic', key)
self.assertListEqual(expected, actual)
# Test on user-defined dicts without line numbers
expected = [{'view': 'mpeg_play %s'}, {'view': 'animate %s'}]
actual = mailcap.lookup(MAILCAPDICT_DEPRECATED, 'video/mpeg')
self.assertListEqual(expected, actual)
def test_subst(self):
plist = ['id=1', 'number=2', 'total=3']
# test case: ([field, MIMEtype, filename, plist=[]], <expected string>)
test_cases = [
(["", "audio/*", "foo.txt"], ""),
(["echo foo", "audio/*", "foo.txt"], "echo foo"),
(["echo %s", "audio/*", "foo.txt"], "echo foo.txt"),
(["echo %t", "audio/wav", "foo.txt"], "echo audio/wav"),
(["echo \\%t", "audio/*", "foo.txt"], "echo %t"),
(["echo foo", "audio/*", "foo.txt", plist], "echo foo"),
(["echo %{total}", "audio/*", "foo.txt", plist], "echo 3")
]
for tc in test_cases:
self.assertEqual(mailcap.subst(*tc[0]), tc[1])
class GetcapsTest(unittest.TestCase):
def test_mock_getcaps(self):
# Test mailcap.getcaps() using mock mailcap file in this dir.
# Temporarily override any existing system mailcap file by pointing the
# MAILCAPS environment variable to our mock file.
with os_helper.EnvironmentVarGuard() as env:
env["MAILCAPS"] = MAILCAPFILE
caps = mailcap.getcaps()
self.assertDictEqual(caps, MAILCAPDICT)
def test_system_mailcap(self):
# Test mailcap.getcaps() with mailcap file(s) on system, if any.
caps = mailcap.getcaps()
self.assertIsInstance(caps, dict)
mailcapfiles = mailcap.listmailcapfiles()
existingmcfiles = [mcf for mcf in mailcapfiles if os.path.exists(mcf)]
if existingmcfiles:
# At least 1 mailcap file exists, so test that.
for (k, v) in caps.items():
self.assertIsInstance(k, str)
self.assertIsInstance(v, list)
for e in v:
self.assertIsInstance(e, dict)
else:
# No mailcap files on system. getcaps() should return empty dict.
self.assertEqual({}, caps)
class FindmatchTest(unittest.TestCase):
def test_findmatch(self):
# default findmatch arguments
c = MAILCAPDICT
fname = "foo.txt"
plist = ["access-type=default", "name=john", "site=python.org",
"directory=/tmp", "mode=foo", "server=bar"]
audio_basic_entry = {
'edit': 'audiocompose %s',
'compose': 'audiocompose %s',
'description': '"An audio fragment"',
'view': 'showaudio %s',
'lineno': 6
}
audio_entry = {"view": "/usr/local/bin/showaudio %t", 'lineno': 7}
video_entry = {'view': 'animate %s', 'lineno': 12}
message_entry = {
'composetyped': 'extcompose %s',
'description': '"A reference to data stored in an external location"', 'needsterminal': '',
'view': 'showexternal %s %{access-type} %{name} %{site} %{directory} %{mode} %{server}',
'lineno': 10,
}
# test case: (findmatch args, findmatch keyword args, expected output)
# positional args: caps, MIMEtype
# keyword args: key="view", filename="/dev/null", plist=[]
# output: (command line, mailcap entry)
cases = [
([{}, "video/mpeg"], {}, (None, None)),
([c, "foo/bar"], {}, (None, None)),
([c, "video/mpeg"], {}, ('animate /dev/null', video_entry)),
([c, "audio/basic", "edit"], {}, ("audiocompose /dev/null", audio_basic_entry)),
([c, "audio/basic", "compose"], {}, ("audiocompose /dev/null", audio_basic_entry)),
([c, "audio/basic", "description"], {}, ('"An audio fragment"', audio_basic_entry)),
([c, "audio/basic", "foobar"], {}, (None, None)),
([c, "video/*"], {"filename": fname}, ("animate %s" % fname, video_entry)),
([c, "audio/basic", "compose"],
{"filename": fname},
("audiocompose %s" % fname, audio_basic_entry)),
([c, "audio/basic"],
{"key": "description", "filename": fname},
('"An audio fragment"', audio_basic_entry)),
([c, "audio/wav"],
{"filename": fname},
("/usr/local/bin/showaudio audio/wav", audio_entry)),
([c, "message/external-body"],
{"plist": plist},
("showexternal /dev/null default john python.org /tmp foo bar", message_entry))
]
self._run_cases(cases)
@unittest.skipUnless(os.name == "posix", "Requires 'test' command on system")
@unittest.skipIf(sys.platform == "vxworks", "'test' command is not supported on VxWorks")
@unittest.skipUnless(
test.support.has_subprocess_support,
"'test' command needs process support."
)
def test_test(self):
# findmatch() will automatically check any "test" conditions and skip
# the entry if the check fails.
caps = {"test/pass": [{"test": "test 1 -eq 1"}],
"test/fail": [{"test": "test 1 -eq 0"}]}
# test case: (findmatch args, findmatch keyword args, expected output)
# positional args: caps, MIMEtype, key ("test")
# keyword args: N/A
# output: (command line, mailcap entry)
cases = [
# findmatch will return the mailcap entry for test/pass because it evaluates to true
([caps, "test/pass", "test"], {}, ("test 1 -eq 1", {"test": "test 1 -eq 1"})),
# findmatch will return None because test/fail evaluates to false
([caps, "test/fail", "test"], {}, (None, None))
]
self._run_cases(cases)
def test_unsafe_mailcap_input(self):
with self.assertWarnsRegex(mailcap.UnsafeMailcapInput,
'Refusing to substitute parameter.*'
'into a shell command'):
unsafe_param = mailcap.subst("echo %{total}",
"audio/wav",
"foo.txt",
["total=*"])
self.assertEqual(unsafe_param, None)
with self.assertWarnsRegex(mailcap.UnsafeMailcapInput,
'Refusing to substitute MIME type'
'.*into a shell'):
unsafe_mimetype = mailcap.subst("echo %t", "audio/*", "foo.txt")
self.assertEqual(unsafe_mimetype, None)
with self.assertWarnsRegex(mailcap.UnsafeMailcapInput,
'Refusing to use mailcap with filename.*'
'Use a safe temporary filename.'):
unsafe_filename = mailcap.findmatch(MAILCAPDICT,
"audio/wav",
filename="foo*.txt")
self.assertEqual(unsafe_filename, (None, None))
def _run_cases(self, cases):
for c in cases:
self.assertEqual(mailcap.findmatch(*c[0], **c[1]), c[2])
if __name__ == '__main__':
unittest.main()

View file

@ -796,7 +796,7 @@ Patch by Kabir Kwatra.
.. nonce: roapI2
.. section: Library
The :mod:`mailcap` module is now deprecated and will be removed in Python
The :mod:`!mailcap` module is now deprecated and will be removed in Python
3.13. See :pep:`594` for the rationale and the :mod:`mimetypes` module for
an alternative. Patch by Victor Stinner.

View file

@ -0,0 +1,2 @@
:pep:`594`: Remove the :mod:`!mailcap` module, deprecated in Python 3.11.
Patch by Victor Stinner.

View file

@ -175,7 +175,6 @@ static const char* _Py_stdlib_module_names[] = {
"logging",
"lzma",
"mailbox",
"mailcap",
"marshal",
"math",
"mimetypes",

View file

@ -69,7 +69,6 @@ OMIT_NETWORKING_FILES = (
"http/",
"imaplib.py",
"mailbox.py",
"mailcap.py",
"nntplib.py",
"poplib.py",
"smtplib.py",