summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/mailcap.rst94
-rw-r--r--Doc/library/superseded.rst1
-rw-r--r--Doc/whatsnew/3.11.rst2
-rw-r--r--Doc/whatsnew/3.12.rst2
-rw-r--r--Doc/whatsnew/3.13.rst4
-rw-r--r--Lib/mailcap.py302
-rw-r--r--Lib/test/mailcap.txt39
-rw-r--r--Lib/test/test_mailcap.py274
-rw-r--r--Misc/NEWS.d/3.11.0b1.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2023-05-24-15-17-05.gh-issue-104773.EmFIQ5.rst2
-rw-r--r--Python/stdlib_module_names.h1
-rwxr-xr-xTools/wasm/wasm_assets.py1
12 files changed, 9 insertions, 715 deletions
diff --git a/Doc/library/mailcap.rst b/Doc/library/mailcap.rst
deleted file mode 100644
index bfaedb4..0000000
--- a/Doc/library/mailcap.rst
+++ /dev/null
@@ -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'})
-
diff --git a/Doc/library/superseded.rst b/Doc/library/superseded.rst
index 1ab7b08..3c67ae4 100644
--- a/Doc/library/superseded.rst
+++ b/Doc/library/superseded.rst
@@ -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
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index 9734d43..fd4a75c 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -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
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index 316296a..7bf0df1 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -893,7 +893,7 @@ Modules (see :pep:`594`):
* :mod:`chunk`
* :mod:`crypt`
* :mod:`imghdr`
-* :mod:`mailcap`
+* :mod:`!mailcap`
* :mod:`msilib`
* :mod:`nis`
* :mod:`nntplib`
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index 83d539b6..1102225 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -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
======================
diff --git a/Lib/mailcap.py b/Lib/mailcap.py
deleted file mode 100644
index 2f4656e..0000000
--- a/Lib/mailcap.py
+++ /dev/null
@@ -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()
diff --git a/Lib/test/mailcap.txt b/Lib/test/mailcap.txt
deleted file mode 100644
index 08a76e6..0000000
--- a/Lib/test/mailcap.txt
+++ /dev/null
@@ -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 \ No newline at end of file
diff --git a/Lib/test/test_mailcap.py b/Lib/test/test_mailcap.py
deleted file mode 100644
index 8a94b0c..0000000
--- a/Lib/test/test_mailcap.py
+++ /dev/null
@@ -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()
diff --git a/Misc/NEWS.d/3.11.0b1.rst b/Misc/NEWS.d/3.11.0b1.rst
index d8c2ec0..1338819 100644
--- a/Misc/NEWS.d/3.11.0b1.rst
+++ b/Misc/NEWS.d/3.11.0b1.rst
@@ -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.
diff --git a/Misc/NEWS.d/next/Library/2023-05-24-15-17-05.gh-issue-104773.EmFIQ5.rst b/Misc/NEWS.d/next/Library/2023-05-24-15-17-05.gh-issue-104773.EmFIQ5.rst
new file mode 100644
index 0000000..95a99a2
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-05-24-15-17-05.gh-issue-104773.EmFIQ5.rst
@@ -0,0 +1,2 @@
+:pep:`594`: Remove the :mod:`!mailcap` module, deprecated in Python 3.11.
+Patch by Victor Stinner.
diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h
index 37d5c05..7aaa4f4 100644
--- a/Python/stdlib_module_names.h
+++ b/Python/stdlib_module_names.h
@@ -175,7 +175,6 @@ static const char* _Py_stdlib_module_names[] = {
"logging",
"lzma",
"mailbox",
-"mailcap",
"marshal",
"math",
"mimetypes",
diff --git a/Tools/wasm/wasm_assets.py b/Tools/wasm/wasm_assets.py
index 47bc238..34340ad 100755
--- a/Tools/wasm/wasm_assets.py
+++ b/Tools/wasm/wasm_assets.py
@@ -69,7 +69,6 @@ OMIT_NETWORKING_FILES = (
"http/",
"imaplib.py",
"mailbox.py",
- "mailcap.py",
"nntplib.py",
"poplib.py",
"smtplib.py",