blob: b2ddacd046db4555b65ebd84455dc38a70e9b486 [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
Guido van Rossum8988fb21995-09-30 16:52:24 +00004
Skip Montanaro17ab1232001-01-24 06:27:27 +00005__all__ = ["getcaps","findmatch"]
Guido van Rossum8988fb21995-09-30 16:52:24 +00006
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.
Tim Peters07e99cb2001-01-14 23:47:14 +000011
Guido van Rossum54f22ed2000-02-04 15:10:34 +000012 The dictionary maps a MIME type (in all lowercase, e.g. 'text/plain')
13 to a list of dictionaries corresponding to mailcap entries. The list
14 collects all the entries for that MIME type from all available mailcap
15 files. Each dictionary contains key-value pairs for that MIME type,
16 where the viewing command is stored with the key "view".
Guido van Rossumbfc39441997-03-25 21:58:08 +000017
18 """
Guido van Rossum8988fb21995-09-30 16:52:24 +000019 caps = {}
20 for mailcap in listmailcapfiles():
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000021 try:
22 fp = open(mailcap, 'r')
Fred Drake776d39e2001-05-11 18:47:54 +000023 except IOError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000024 continue
25 morecaps = readmailcapfile(fp)
26 fp.close()
Raymond Hettingere0d49722002-06-02 18:55:56 +000027 for key, value in morecaps.iteritems():
Raymond Hettinger54f02222002-06-01 14:18:47 +000028 if not key in caps:
Raymond Hettingere0d49722002-06-02 18:55:56 +000029 caps[key] = value
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000030 else:
Raymond Hettingere0d49722002-06-02 18:55:56 +000031 caps[key] = caps[key] + value
Guido van Rossum8988fb21995-09-30 16:52:24 +000032 return caps
33
34def listmailcapfiles():
Guido van Rossumbfc39441997-03-25 21:58:08 +000035 """Return a list of all mailcap files found on the system."""
Guido van Rossum8988fb21995-09-30 16:52:24 +000036 # XXX Actually, this is Unix-specific
Raymond Hettinger54f02222002-06-01 14:18:47 +000037 if 'MAILCAPS' in os.environ:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000038 str = os.environ['MAILCAPS']
Eric S. Raymond0c03cc22001-02-09 10:23:55 +000039 mailcaps = str.split(':')
Guido van Rossum8988fb21995-09-30 16:52:24 +000040 else:
Raymond Hettinger54f02222002-06-01 14:18:47 +000041 if 'HOME' in os.environ:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000042 home = os.environ['HOME']
43 else:
44 # Don't bother with getpwuid()
45 home = '.' # Last resort
46 mailcaps = [home + '/.mailcap', '/etc/mailcap',
47 '/usr/etc/mailcap', '/usr/local/etc/mailcap']
Guido van Rossum8988fb21995-09-30 16:52:24 +000048 return mailcaps
49
50
51# Part 2: the parser.
52
53def readmailcapfile(fp):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000054 """Read a mailcap file and return a dictionary keyed by MIME type.
55
56 Each MIME type is mapped to an entry consisting of a list of
57 dictionaries; the list will contain more than one such dictionary
58 if a given MIME type appears more than once in the mailcap file.
59 Each dictionary contains key-value pairs for that MIME type, where
60 the viewing command is stored with the key "view".
61 """
Guido van Rossum8988fb21995-09-30 16:52:24 +000062 caps = {}
63 while 1:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000064 line = fp.readline()
65 if not line: break
66 # Ignore comments and blank lines
Eric S. Raymond0c03cc22001-02-09 10:23:55 +000067 if line[0] == '#' or line.strip() == '':
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000068 continue
69 nextline = line
70 # Join continuation lines
71 while nextline[-2:] == '\\\n':
72 nextline = fp.readline()
73 if not nextline: nextline = '\n'
74 line = line[:-2] + nextline
75 # Parse the line
76 key, fields = parseline(line)
77 if not (key and fields):
78 continue
79 # Normalize the key
Eric S. Raymond0c03cc22001-02-09 10:23:55 +000080 types = key.split('/')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000081 for j in range(len(types)):
Eric S. Raymond0c03cc22001-02-09 10:23:55 +000082 types[j] = types[j].strip()
83 key = '/'.join(types).lower()
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000084 # Update the database
Raymond Hettinger54f02222002-06-01 14:18:47 +000085 if key in caps:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000086 caps[key].append(fields)
87 else:
88 caps[key] = [fields]
Guido van Rossum8988fb21995-09-30 16:52:24 +000089 return caps
90
91def parseline(line):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000092 """Parse one entry in a mailcap file and return a dictionary.
93
94 The viewing command is stored as the value with the key "view",
95 and the rest of the fields produce key-value pairs in the dict.
96 """
Guido van Rossum8988fb21995-09-30 16:52:24 +000097 fields = []
98 i, n = 0, len(line)
99 while i < n:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000100 field, i = parsefield(line, i, n)
101 fields.append(field)
102 i = i+1 # Skip semicolon
Guido van Rossum8988fb21995-09-30 16:52:24 +0000103 if len(fields) < 2:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000104 return None, None
Guido van Rossum8988fb21995-09-30 16:52:24 +0000105 key, view, rest = fields[0], fields[1], fields[2:]
106 fields = {'view': view}
107 for field in rest:
Eric S. Raymond0c03cc22001-02-09 10:23:55 +0000108 i = field.find('=')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000109 if i < 0:
110 fkey = field
111 fvalue = ""
112 else:
Eric S. Raymond0c03cc22001-02-09 10:23:55 +0000113 fkey = field[:i].strip()
114 fvalue = field[i+1:].strip()
Raymond Hettinger54f02222002-06-01 14:18:47 +0000115 if fkey in fields:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000116 # Ignore it
117 pass
118 else:
119 fields[fkey] = fvalue
Guido van Rossum8988fb21995-09-30 16:52:24 +0000120 return key, fields
121
122def parsefield(line, i, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000123 """Separate one key-value pair in a mailcap entry."""
Guido van Rossum8988fb21995-09-30 16:52:24 +0000124 start = i
125 while i < n:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000126 c = line[i]
127 if c == ';':
128 break
129 elif c == '\\':
130 i = i+2
131 else:
132 i = i+1
Eric S. Raymond0c03cc22001-02-09 10:23:55 +0000133 return line[start:i].strip(), i
Guido van Rossum8988fb21995-09-30 16:52:24 +0000134
135
136# Part 3: using the database.
137
Guido van Rossumbfc39441997-03-25 21:58:08 +0000138def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]):
139 """Find a match for a mailcap entry.
Tim Peters07e99cb2001-01-14 23:47:14 +0000140
Guido van Rossumbfc39441997-03-25 21:58:08 +0000141 Return a tuple containing the command line, and the mailcap entry
142 used; (None, None) if no match is found. This may invoke the
143 'test' command of several matching entries before deciding which
144 entry to use.
145
146 """
147 entries = lookup(caps, MIMEtype, key)
Tim Peters07e99cb2001-01-14 23:47:14 +0000148 # XXX This code should somehow check for the needsterminal flag.
Guido van Rossum8988fb21995-09-30 16:52:24 +0000149 for e in entries:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000150 if 'test' in e:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000151 test = subst(e['test'], filename, plist)
152 if test and os.system(test) != 0:
153 continue
154 command = subst(e[key], MIMEtype, filename, plist)
155 return command, e
Guido van Rossum8988fb21995-09-30 16:52:24 +0000156 return None, None
157
Guido van Rossumbfc39441997-03-25 21:58:08 +0000158def lookup(caps, MIMEtype, key=None):
Guido van Rossum8988fb21995-09-30 16:52:24 +0000159 entries = []
Raymond Hettinger54f02222002-06-01 14:18:47 +0000160 if MIMEtype in caps:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000161 entries = entries + caps[MIMEtype]
Eric S. Raymond0c03cc22001-02-09 10:23:55 +0000162 MIMEtypes = MIMEtype.split('/')
Guido van Rossumbfc39441997-03-25 21:58:08 +0000163 MIMEtype = MIMEtypes[0] + '/*'
Raymond Hettinger54f02222002-06-01 14:18:47 +0000164 if MIMEtype in caps:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000165 entries = entries + caps[MIMEtype]
Guido van Rossum8988fb21995-09-30 16:52:24 +0000166 if key is not None:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000167 entries = filter(lambda e, key=key: key in e, entries)
Guido van Rossum8988fb21995-09-30 16:52:24 +0000168 return entries
169
Guido van Rossumbfc39441997-03-25 21:58:08 +0000170def subst(field, MIMEtype, filename, plist=[]):
Guido van Rossum8988fb21995-09-30 16:52:24 +0000171 # XXX Actually, this is Unix-specific
172 res = ''
173 i, n = 0, len(field)
174 while i < n:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000175 c = field[i]; i = i+1
Fred Drake8152d322000-12-12 23:20:45 +0000176 if c != '%':
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000177 if c == '\\':
178 c = field[i:i+1]; i = i+1
179 res = res + c
180 else:
181 c = field[i]; i = i+1
182 if c == '%':
183 res = res + c
184 elif c == 's':
185 res = res + filename
186 elif c == 't':
187 res = res + MIMEtype
188 elif c == '{':
189 start = i
Fred Drake8152d322000-12-12 23:20:45 +0000190 while i < n and field[i] != '}':
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000191 i = i+1
192 name = field[start:i]
193 i = i+1
194 res = res + findparam(name, plist)
195 # XXX To do:
196 # %n == number of parts if type is multipart/*
197 # %F == list of alternating type and filename for parts
198 else:
199 res = res + '%' + c
Guido van Rossum8988fb21995-09-30 16:52:24 +0000200 return res
201
202def findparam(name, plist):
Eric S. Raymond0c03cc22001-02-09 10:23:55 +0000203 name = name.lower() + '='
Guido van Rossum8988fb21995-09-30 16:52:24 +0000204 n = len(name)
205 for p in plist:
Eric S. Raymond0c03cc22001-02-09 10:23:55 +0000206 if p[:n].lower() == name:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000207 return p[n:]
Guido van Rossum8988fb21995-09-30 16:52:24 +0000208 return ''
209
210
211# Part 4: test program.
212
213def test():
214 import sys
215 caps = getcaps()
216 if not sys.argv[1:]:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000217 show(caps)
218 return
Guido van Rossum8988fb21995-09-30 16:52:24 +0000219 for i in range(1, len(sys.argv), 2):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000220 args = sys.argv[i:i+2]
221 if len(args) < 2:
222 print "usage: mailcap [MIMEtype file] ..."
223 return
224 MIMEtype = args[0]
225 file = args[1]
226 command, e = findmatch(caps, MIMEtype, 'view', file)
227 if not command:
228 print "No viewer found for", type
229 else:
230 print "Executing:", command
231 sts = os.system(command)
232 if sts:
233 print "Exit status:", sts
Guido van Rossum8988fb21995-09-30 16:52:24 +0000234
235def show(caps):
236 print "Mailcap files:"
237 for fn in listmailcapfiles(): print "\t" + fn
238 print
239 if not caps: caps = getcaps()
240 print "Mailcap entries:"
241 print
242 ckeys = caps.keys()
243 ckeys.sort()
244 for type in ckeys:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000245 print type
246 entries = caps[type]
247 for e in entries:
248 keys = e.keys()
249 keys.sort()
250 for k in keys:
251 print " %-15s" % k, e[k]
252 print
Guido van Rossum8988fb21995-09-30 16:52:24 +0000253
254if __name__ == '__main__':
255 test()