blob: e19a7468283dc6222d04be675765ee69c6ede219 [file] [log] [blame]
Guido van Rossumbfc39441997-03-25 21:58:08 +00001"""Mailcap file handling. See RFC 1524."""
Guido van Rossum8988fb21995-09-30 16:52:24 +00002
3import os
4import string
Guido van Rossum8988fb21995-09-30 16:52:24 +00005
6
7# Part 1: top-level interface.
8
9def getcaps():
Guido van Rossumbfc39441997-03-25 21:58:08 +000010 """Return a dictionary containing the mailcap database.
11
12 The dictionary maps a MIME type (in all lowercase,
13 e.g. 'text/plain') to a list of corresponding mailcap entries.
14
15 """
Guido van Rossum8988fb21995-09-30 16:52:24 +000016 caps = {}
17 for mailcap in listmailcapfiles():
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000018 try:
19 fp = open(mailcap, 'r')
20 except:
21 continue
22 morecaps = readmailcapfile(fp)
23 fp.close()
24 for key in morecaps.keys():
25 if not caps.has_key(key):
26 caps[key] = morecaps[key]
27 else:
28 caps[key] = caps[key] + morecaps[key]
Guido van Rossum8988fb21995-09-30 16:52:24 +000029 return caps
30
31def listmailcapfiles():
Guido van Rossumbfc39441997-03-25 21:58:08 +000032 """Return a list of all mailcap files found on the system."""
Guido van Rossum8988fb21995-09-30 16:52:24 +000033 # XXX Actually, this is Unix-specific
34 if os.environ.has_key('MAILCAPS'):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000035 str = os.environ['MAILCAPS']
36 mailcaps = string.splitfields(str, ':')
Guido van Rossum8988fb21995-09-30 16:52:24 +000037 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000038 if os.environ.has_key('HOME'):
39 home = os.environ['HOME']
40 else:
41 # Don't bother with getpwuid()
42 home = '.' # Last resort
43 mailcaps = [home + '/.mailcap', '/etc/mailcap',
44 '/usr/etc/mailcap', '/usr/local/etc/mailcap']
Guido van Rossum8988fb21995-09-30 16:52:24 +000045 return mailcaps
46
47
48# Part 2: the parser.
49
50def readmailcapfile(fp):
51 caps = {}
52 while 1:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000053 line = fp.readline()
54 if not line: break
55 # Ignore comments and blank lines
56 if line[0] == '#' or string.strip(line) == '':
57 continue
58 nextline = line
59 # Join continuation lines
60 while nextline[-2:] == '\\\n':
61 nextline = fp.readline()
62 if not nextline: nextline = '\n'
63 line = line[:-2] + nextline
64 # Parse the line
65 key, fields = parseline(line)
66 if not (key and fields):
67 continue
68 # Normalize the key
69 types = string.splitfields(key, '/')
70 for j in range(len(types)):
71 types[j] = string.strip(types[j])
72 key = string.lower(string.joinfields(types, '/'))
73 # Update the database
74 if caps.has_key(key):
75 caps[key].append(fields)
76 else:
77 caps[key] = [fields]
Guido van Rossum8988fb21995-09-30 16:52:24 +000078 return caps
79
80def parseline(line):
81 fields = []
82 i, n = 0, len(line)
83 while i < n:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000084 field, i = parsefield(line, i, n)
85 fields.append(field)
86 i = i+1 # Skip semicolon
Guido van Rossum8988fb21995-09-30 16:52:24 +000087 if len(fields) < 2:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000088 return None, None
Guido van Rossum8988fb21995-09-30 16:52:24 +000089 key, view, rest = fields[0], fields[1], fields[2:]
90 fields = {'view': view}
91 for field in rest:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000092 i = string.find(field, '=')
93 if i < 0:
94 fkey = field
95 fvalue = ""
96 else:
97 fkey = string.strip(field[:i])
98 fvalue = string.strip(field[i+1:])
99 if fields.has_key(fkey):
100 # Ignore it
101 pass
102 else:
103 fields[fkey] = fvalue
Guido van Rossum8988fb21995-09-30 16:52:24 +0000104 return key, fields
105
106def parsefield(line, i, n):
107 start = i
108 while i < n:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000109 c = line[i]
110 if c == ';':
111 break
112 elif c == '\\':
113 i = i+2
114 else:
115 i = i+1
Guido van Rossum8988fb21995-09-30 16:52:24 +0000116 return string.strip(line[start:i]), i
117
118
119# Part 3: using the database.
120
Guido van Rossumbfc39441997-03-25 21:58:08 +0000121def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]):
122 """Find a match for a mailcap entry.
123
124 Return a tuple containing the command line, and the mailcap entry
125 used; (None, None) if no match is found. This may invoke the
126 'test' command of several matching entries before deciding which
127 entry to use.
128
129 """
130 entries = lookup(caps, MIMEtype, key)
131 # XXX This code should somehow check for the needsterminal flag.
Guido van Rossum8988fb21995-09-30 16:52:24 +0000132 for e in entries:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000133 if e.has_key('test'):
134 test = subst(e['test'], filename, plist)
135 if test and os.system(test) != 0:
136 continue
137 command = subst(e[key], MIMEtype, filename, plist)
138 return command, e
Guido van Rossum8988fb21995-09-30 16:52:24 +0000139 return None, None
140
Guido van Rossumbfc39441997-03-25 21:58:08 +0000141def lookup(caps, MIMEtype, key=None):
Guido van Rossum8988fb21995-09-30 16:52:24 +0000142 entries = []
Guido van Rossumbfc39441997-03-25 21:58:08 +0000143 if caps.has_key(MIMEtype):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000144 entries = entries + caps[MIMEtype]
Guido van Rossumbfc39441997-03-25 21:58:08 +0000145 MIMEtypes = string.splitfields(MIMEtype, '/')
146 MIMEtype = MIMEtypes[0] + '/*'
147 if caps.has_key(MIMEtype):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000148 entries = entries + caps[MIMEtype]
Guido van Rossum8988fb21995-09-30 16:52:24 +0000149 if key is not None:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000150 entries = filter(lambda e, key=key: e.has_key(key), entries)
Guido van Rossum8988fb21995-09-30 16:52:24 +0000151 return entries
152
Guido van Rossumbfc39441997-03-25 21:58:08 +0000153def subst(field, MIMEtype, filename, plist=[]):
Guido van Rossum8988fb21995-09-30 16:52:24 +0000154 # XXX Actually, this is Unix-specific
155 res = ''
156 i, n = 0, len(field)
157 while i < n:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000158 c = field[i]; i = i+1
159 if c <> '%':
160 if c == '\\':
161 c = field[i:i+1]; i = i+1
162 res = res + c
163 else:
164 c = field[i]; i = i+1
165 if c == '%':
166 res = res + c
167 elif c == 's':
168 res = res + filename
169 elif c == 't':
170 res = res + MIMEtype
171 elif c == '{':
172 start = i
173 while i < n and field[i] <> '}':
174 i = i+1
175 name = field[start:i]
176 i = i+1
177 res = res + findparam(name, plist)
178 # XXX To do:
179 # %n == number of parts if type is multipart/*
180 # %F == list of alternating type and filename for parts
181 else:
182 res = res + '%' + c
Guido van Rossum8988fb21995-09-30 16:52:24 +0000183 return res
184
185def findparam(name, plist):
186 name = string.lower(name) + '='
187 n = len(name)
188 for p in plist:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000189 if string.lower(p[:n]) == name:
190 return p[n:]
Guido van Rossum8988fb21995-09-30 16:52:24 +0000191 return ''
192
193
194# Part 4: test program.
195
196def test():
197 import sys
198 caps = getcaps()
199 if not sys.argv[1:]:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000200 show(caps)
201 return
Guido van Rossum8988fb21995-09-30 16:52:24 +0000202 for i in range(1, len(sys.argv), 2):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000203 args = sys.argv[i:i+2]
204 if len(args) < 2:
205 print "usage: mailcap [MIMEtype file] ..."
206 return
207 MIMEtype = args[0]
208 file = args[1]
209 command, e = findmatch(caps, MIMEtype, 'view', file)
210 if not command:
211 print "No viewer found for", type
212 else:
213 print "Executing:", command
214 sts = os.system(command)
215 if sts:
216 print "Exit status:", sts
Guido van Rossum8988fb21995-09-30 16:52:24 +0000217
218def show(caps):
219 print "Mailcap files:"
220 for fn in listmailcapfiles(): print "\t" + fn
221 print
222 if not caps: caps = getcaps()
223 print "Mailcap entries:"
224 print
225 ckeys = caps.keys()
226 ckeys.sort()
227 for type in ckeys:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000228 print type
229 entries = caps[type]
230 for e in entries:
231 keys = e.keys()
232 keys.sort()
233 for k in keys:
234 print " %-15s" % k, e[k]
235 print
Guido van Rossum8988fb21995-09-30 16:52:24 +0000236
237if __name__ == '__main__':
238 test()