1"""Mailcap file handling. See RFC 1524.""" 2 3import os 4 5__all__ = ["getcaps","findmatch"] 6 7# Part 1: top-level interface. 8 9def getcaps(): 10 """Return a dictionary containing the mailcap database. 11 12 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". 17 18 """ 19 caps = {} 20 for mailcap in listmailcapfiles(): 21 try: 22 fp = open(mailcap, 'r') 23 except IOError: 24 continue 25 with fp: 26 morecaps = readmailcapfile(fp) 27 for key, value in morecaps.iteritems(): 28 if not key in caps: 29 caps[key] = value 30 else: 31 caps[key] = caps[key] + value 32 return caps 33 34def listmailcapfiles(): 35 """Return a list of all mailcap files found on the system.""" 36 # XXX Actually, this is Unix-specific 37 if 'MAILCAPS' in os.environ: 38 str = os.environ['MAILCAPS'] 39 mailcaps = str.split(':') 40 else: 41 if 'HOME' in os.environ: 42 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'] 48 return mailcaps 49 50 51# Part 2: the parser. 52 53def readmailcapfile(fp): 54 """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 """ 62 caps = {} 63 while 1: 64 line = fp.readline() 65 if not line: break 66 # Ignore comments and blank lines 67 if line[0] == '#' or line.strip() == '': 68 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 80 types = key.split('/') 81 for j in range(len(types)): 82 types[j] = types[j].strip() 83 key = '/'.join(types).lower() 84 # Update the database 85 if key in caps: 86 caps[key].append(fields) 87 else: 88 caps[key] = [fields] 89 return caps 90 91def parseline(line): 92 """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 """ 97 fields = [] 98 i, n = 0, len(line) 99 while i < n: 100 field, i = parsefield(line, i, n) 101 fields.append(field) 102 i = i+1 # Skip semicolon 103 if len(fields) < 2: 104 return None, None 105 key, view, rest = fields[0], fields[1], fields[2:] 106 fields = {'view': view} 107 for field in rest: 108 i = field.find('=') 109 if i < 0: 110 fkey = field 111 fvalue = "" 112 else: 113 fkey = field[:i].strip() 114 fvalue = field[i+1:].strip() 115 if fkey in fields: 116 # Ignore it 117 pass 118 else: 119 fields[fkey] = fvalue 120 return key, fields 121 122def parsefield(line, i, n): 123 """Separate one key-value pair in a mailcap entry.""" 124 start = i 125 while i < n: 126 c = line[i] 127 if c == ';': 128 break 129 elif c == '\\': 130 i = i+2 131 else: 132 i = i+1 133 return line[start:i].strip(), i 134 135 136# Part 3: using the database. 137 138def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]): 139 """Find a match for a mailcap entry. 140 141 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) 148 # XXX This code should somehow check for the needsterminal flag. 149 for e in entries: 150 if 'test' in e: 151 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 156 return None, None 157 158def lookup(caps, MIMEtype, key=None): 159 entries = [] 160 if MIMEtype in caps: 161 entries = entries + caps[MIMEtype] 162 MIMEtypes = MIMEtype.split('/') 163 MIMEtype = MIMEtypes[0] + '/*' 164 if MIMEtype in caps: 165 entries = entries + caps[MIMEtype] 166 if key is not None: 167 entries = filter(lambda e, key=key: key in e, entries) 168 return entries 169 170def subst(field, MIMEtype, filename, plist=[]): 171 # XXX Actually, this is Unix-specific 172 res = '' 173 i, n = 0, len(field) 174 while i < n: 175 c = field[i]; i = i+1 176 if c != '%': 177 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 190 while i < n and field[i] != '}': 191 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 200 return res 201 202def findparam(name, plist): 203 name = name.lower() + '=' 204 n = len(name) 205 for p in plist: 206 if p[:n].lower() == name: 207 return p[n:] 208 return '' 209 210 211# Part 4: test program. 212 213def test(): 214 import sys 215 caps = getcaps() 216 if not sys.argv[1:]: 217 show(caps) 218 return 219 for i in range(1, len(sys.argv), 2): 220 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 234 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: 245 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 253 254if __name__ == '__main__': 255 test() 256