]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-104773: PEP 594: Remove the mailcap module (#104867)
authorVictor Stinner <vstinner@python.org>
Wed, 24 May 2023 14:16:55 +0000 (16:16 +0200)
committerGitHub <noreply@github.com>
Wed, 24 May 2023 14:16:55 +0000 (14:16 +0000)
Remove Lib/test/mailcap.txt test file.

12 files changed:
Doc/library/mailcap.rst [deleted file]
Doc/library/superseded.rst
Doc/whatsnew/3.11.rst
Doc/whatsnew/3.12.rst
Doc/whatsnew/3.13.rst
Lib/mailcap.py [deleted file]
Lib/test/mailcap.txt [deleted file]
Lib/test/test_mailcap.py [deleted file]
Misc/NEWS.d/3.11.0b1.rst
Misc/NEWS.d/next/Library/2023-05-24-15-17-05.gh-issue-104773.EmFIQ5.rst [new file with mode: 0644]
Python/stdlib_module_names.h
Tools/wasm/wasm_assets.py

diff --git a/Doc/library/mailcap.rst b/Doc/library/mailcap.rst
deleted file mode 100644 (file)
index bfaedb4..0000000
+++ /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'})
-
index 1ab7b08d4d202cedc759031e3056d119c234aaec..3c67ae491636e4bbe2217f56692ebe3ec62c5edd 100644 (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
index 9734d43ef87d2515f4bc3970c59e611b0aa3a089..fd4a75ce47b35040040f039feccd9ffeca1874e7 100644 (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
index 316296ae038e4ea22d2d237c684e8272175d6bfc..7bf0df17fb72d05302486ab6b1b4914ef31909bb 100644 (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`
index 83d539b6fc3d9a84c5470a1d12aa5fe7c9d8cec3..1102225e50b6587a1952ce6daa32f01187faf299 100644 (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
 ======================
diff --git a/Lib/mailcap.py b/Lib/mailcap.py
deleted file mode 100644 (file)
index 2f4656e..0000000
+++ /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 (file)
index 08a76e6..0000000
+++ /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 (file)
index 8a94b0c..0000000
+++ /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()
index d8c2ec0a7997114307ccf24c58f9175ec70b7540..1338819375bc89bf0fb108a49b41a3a1ae2a2a6a 100644 (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.
 
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 (file)
index 0000000..95a99a2
--- /dev/null
@@ -0,0 +1,2 @@
+:pep:`594`: Remove the :mod:`!mailcap` module, deprecated in Python 3.11.
+Patch by Victor Stinner.
index 37d5c05579ebe1560acea1db9dc63d964fbeda4d..7aaa4f4ccdb3c3d407615174e337f65ad85c55da 100644 (file)
@@ -175,7 +175,6 @@ static const char* _Py_stdlib_module_names[] = {
 "logging",
 "lzma",
 "mailbox",
-"mailcap",
 "marshal",
 "math",
 "mimetypes",
index 47bc238c64819f5dce785938b733b002344d8d2a..34340adb3bc314bf046bdbe92803c2f9843284f6 100755 (executable)
@@ -69,7 +69,6 @@ OMIT_NETWORKING_FILES = (
     "http/",
     "imaplib.py",
     "mailbox.py",
-    "mailcap.py",
     "nntplib.py",
     "poplib.py",
     "smtplib.py",