Initial revision
diff --git a/Lib/mailcap.py b/Lib/mailcap.py
new file mode 100644
index 0000000..9da57d7
--- /dev/null
+++ b/Lib/mailcap.py
@@ -0,0 +1,223 @@
+# Mailcap file handling.  See RFC 1524.
+
+import os
+import string
+import tempfile
+
+
+# Part 1: top-level interface.
+
+def getcaps():
+    caps = {}
+    for mailcap in listmailcapfiles():
+	try:
+	    fp = open(mailcap, 'r')
+	except:
+	    continue
+	morecaps = readmailcapfile(fp)
+	fp.close()
+	for key in morecaps.keys():
+	    if not caps.has_key(key):
+		caps[key] = morecaps[key]
+	    else:
+		caps[key] = caps[key] + morecaps[key]
+    return caps
+
+def listmailcapfiles():
+    # XXX Actually, this is Unix-specific
+    if os.environ.has_key('MAILCAPS'):
+	str = os.environ['MAILCAPS']
+	mailcaps = string.splitfields(str, ':')
+    else:
+	if os.environ.has_key('HOME'):
+	    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):
+    caps = {}
+    while 1:
+	line = fp.readline()
+	if not line: break
+	# Ignore comments and blank lines
+	if line[0] == '#' or string.strip(line) == '':
+	    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):
+	    cotinue
+	# Normalize the key
+	types = string.splitfields(key, '/')
+	for j in range(len(types)):
+	    types[j] = string.strip(types[j])
+	key = string.lower(string.joinfields(types, '/'))
+	# Update the database
+	if caps.has_key(key):
+	    caps[key].append(fields)
+	else:
+	    caps[key] = [fields]
+    return caps
+
+def parseline(line):
+    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 = string.find(field, '=')
+	if i < 0:
+	    fkey = field
+	    fvalue = ""
+	else:
+	    fkey = string.strip(field[:i])
+	    fvalue = string.strip(field[i+1:])
+	if fields.has_key(fkey):
+	    # Ignore it
+	    pass
+	else:
+	    fields[fkey] = fvalue
+    return key, fields
+
+def parsefield(line, i, n):
+    start = i
+    while i < n:
+	c = line[i]
+	if c == ';':
+	    break
+	elif c == '\\':
+	    i = i+2
+	else:
+	    i = i+1
+    return string.strip(line[start:i]), i
+
+
+# Part 3: using the database.
+
+def findmatch(caps, type, key='view', filename="/dev/null", plist=[]):
+    entries = lookup(caps, type, key)
+    for e in entries:
+	if e.has_key('test'):
+	    test = subst(e['test'], filename, plist)
+	    if test and os.system(test) != 0:
+		continue
+	command = subst(e[key], type, filename, plist)
+	return command, e
+    return None, None
+
+def lookup(caps, type, key=None):
+    entries = []
+    if caps.has_key(type):
+	entries = entries + caps[type]
+    types = string.splitfields(type, '/')
+    type = types[0] + '/*'
+    if caps.has_key(type):
+	entries = entries + caps[type]
+    if key is not None:
+	entries = filter(lambda e, key=key: e.has_key(key), entries)
+    return entries
+
+def subst(field, type, 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':
+		res = res + type
+	    elif c == '{':
+		start = i
+		while i < n and field[i] <> '}':
+		    i = i+1
+		name = field[start:i]
+		i = i+1
+		res = res + findparam(name, plist)
+	    # 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 = string.lower(name) + '='
+    n = len(name)
+    for p in plist:
+	if string.lower(p[:n]) == 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 [type file] ..."
+	    return
+	type = args[0]
+	file = args[1]
+	command, e = findmatch(caps, type, 'view', file)
+	if not command:
+	    print "No viewer found for", type
+	else:
+	    print "Executing:", command
+	    sts = os.system(command)
+	    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 = caps.keys()
+    ckeys.sort()
+    for type in ckeys:
+	print type
+	entries = caps[type]
+	for e in entries:
+	    keys = e.keys()
+	    keys.sort()
+	    for k in keys:
+		print "  %-15s" % k, e[k]
+	    print
+
+if __name__ == '__main__':
+    test()