未翻訳のページ
このページはまだ日本語に翻訳されていないため、英語の原文を表示しています。 翻訳に協力する
mailcap Module Complexity¶
The mailcap module reads mailcap files (RFC 1524), which map MIME types to the shell commands
that view, edit, compose or print them. getcaps() parses every mailcap file it can find into
one dictionary, and findmatch() picks an entry from that dictionary and fills in its command
line. Nothing is cached: each getcaps() call opens and parses the files again.
Removed in Python 3.13
Deprecated in Python 3.11 and removed in Python 3.13 by PEP 594. The examples need Python 3.11 or 3.12.
L is the characters across the mailcap files read, and e is the entries for the requested
MIME type plus those for its type/* wildcard, in a dictionary getcaps() built. The bounds
assume what mailcap files and calls look like in practice: a handful of files, entries a line or
two long, a short filename and a short parameter list, so substituting into one command is O(1).
A non-empty test command is run through os.system(), one shell process per entry tested,
and that process is priced on top of the bound.
Complexity Reference¶
Functions¶
| Operation | Time | Space | Notes |
|---|---|---|---|
mailcap.getcaps() |
O(L) | O(L) | Reads the files named by MAILCAPS, or ~/.mailcap, /etc/mailcap, /usr/etc/mailcap and /usr/local/etc/mailcap, skipping any that cannot be opened. Returns a new dictionary on every call |
mailcap.findmatch(caps, MIMEtype, key='view', filename='/dev/null', plist=[]) |
O(e) plus one shell process per test run |
O(e) | Returns (command, entry), or (None, None). Takes the first entry, in file order, that has key, whose test command is empty or exits 0, and whose command substitutes safely. MIMEtype is matched as given, against the lowercased keys getcaps() stores |
Constants and exceptions¶
| Operation | Time | Space | Notes |
|---|---|---|---|
mailcap.UnsafeMailcapInput |
O(1) | O(1) | Warning subclass, issued when findmatch() refuses a filename, MIME type or parameter holding shell metacharacters |
Reading the Database¶
getcaps() parses every line of every file it reads, and keeps a dictionary per entry.
Each call does it all again, so call it once and pass the result to every findmatch().
import mailcap
import os
import tempfile
with tempfile.TemporaryDirectory() as directory:
first = os.path.join(directory, 'first')
second = os.path.join(directory, 'second')
with open(first, 'w') as f:
f.write('text/plain; cat %s; copiousoutput\n')
with open(second, 'w') as f:
f.write('Text/Plain; less %s\nimage/*; display %s\n')
missing = os.path.join(directory, 'missing')
os.environ['MAILCAPS'] = os.pathsep.join([first, missing, second])
caps = mailcap.getcaps() # O(L) - every file, every line
# Keys are lowercased, and entries from later files follow earlier ones
assert [e['view'] for e in caps['text/plain']] == ['cat %s', 'less %s']
assert caps['text/plain'][0]['copiousoutput'] == ''
assert mailcap.getcaps() is not caps # O(L) again - nothing is cached
Finding a Handler¶
findmatch() looks up the exact type and its type/* wildcard, and returns the first entry in
file order that has the requested key and passes its test. It returns the filled-in command and the entry dictionary,
not a flag.
import mailcap
import os
import tempfile
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, 'mailcap')
with open(path, 'w') as f:
f.write('image/*; display %s\n'
'image/png; pngview %s\n'
'text/plain; cat %s; edit=vi %s\n')
os.environ['MAILCAPS'] = path
caps = mailcap.getcaps() # O(L)
command, entry = mailcap.findmatch(caps, 'image/png', filename='a.png') # O(e)
assert command == 'display a.png' # the wildcard comes first in the file
assert entry['lineno'] == 0
command, entry = mailcap.findmatch(caps, 'text/plain', key='edit', filename='notes.txt')
assert command == 'vi notes.txt'
# Keys were lowercased when read; the type you pass is not
assert mailcap.findmatch(caps, 'Text/Plain') == (None, None)
assert mailcap.findmatch(caps, 'video/mp4') == (None, None)
Test Commands Run a Shell¶
An entry with a non-empty test field is accepted only if that command exits 0, and
findmatch() runs it with os.system() as soon as it reaches the entry. A type with several tested entries can start
several shells for one lookup. Use %t in a test field for the filename: %s there raises
TypeError.
import mailcap
caps = {'text/html': [
{'view': 'remote-browser %s', 'test': 'exit 1', 'lineno': 0},
{'view': 'lynx %s', 'test': 'exit 0', 'lineno': 1},
]}
# Two shell processes: the first test fails, the second passes
command, entry = mailcap.findmatch(caps, 'text/html', filename='index.html')
assert command == 'lynx index.html'
Unsafe Input¶
A filename holding anything but word characters, @+=:,./- and characters from U+00A1 up - a
space or a ;, say - is refused before any test runs. So is such a MIME type substituted by %t, or
such a parameter substituted by %{name}.
import mailcap
import warnings
caps = {'text/plain': [{'view': 'cat %s', 'lineno': 0}]}
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter('always')
result = mailcap.findmatch(caps, 'text/plain', filename='x; rm -rf ~')
assert result == (None, None)
assert caught[0].category is mailcap.UnsafeMailcapInput
Performance Best Practices¶
✅ Do:
- Call
getcaps()once and reuse the dictionary; every call reparses every file - Lowercase the MIME type before
findmatch(), since that is how the keys are stored - Pass a plain temporary filename, not one built from untrusted input
❌ Avoid:
- Calling
findmatch()in a loop over types whose entries carrytestfields - each test is a shell process %sin atestfield - it raisesTypeErrorinstead of naming the file
Version Notes¶
- Python 3.10.8+:
findmatch()refuses unsafe filenames, MIME types and parameters withUnsafeMailcapInput - Python 3.11+: Importing the module emits a
DeprecationWarning - Python 3.13+: Removed by PEP 594;
import mailcapraisesModuleNotFoundError
Related Modules¶
- mimetypes - maps filenames to MIME types, the replacement the deprecation points to
- subprocess - runs a command list without a shell, unlike
os.system()