You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mailcap.py 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. """Mailcap file handling. See RFC 1524."""
  2. import os
  3. __all__ = ["getcaps","findmatch"]
  4. # Part 1: top-level interface.
  5. def getcaps():
  6. """Return a dictionary containing the mailcap database.
  7. The dictionary maps a MIME type (in all lowercase, e.g. 'text/plain')
  8. to a list of dictionaries corresponding to mailcap entries. The list
  9. collects all the entries for that MIME type from all available mailcap
  10. files. Each dictionary contains key-value pairs for that MIME type,
  11. where the viewing command is stored with the key "view".
  12. """
  13. caps = {}
  14. for mailcap in listmailcapfiles():
  15. try:
  16. fp = open(mailcap, 'r')
  17. except:
  18. continue
  19. morecaps = readmailcapfile(fp)
  20. fp.close()
  21. for key in morecaps.keys():
  22. if not caps.has_key(key):
  23. caps[key] = morecaps[key]
  24. else:
  25. caps[key] = caps[key] + morecaps[key]
  26. return caps
  27. def listmailcapfiles():
  28. """Return a list of all mailcap files found on the system."""
  29. # XXX Actually, this is Unix-specific
  30. if os.environ.has_key('MAILCAPS'):
  31. str = os.environ['MAILCAPS']
  32. mailcaps = str.split(':')
  33. else:
  34. if os.environ.has_key('HOME'):
  35. home = os.environ['HOME']
  36. else:
  37. # Don't bother with getpwuid()
  38. home = '.' # Last resort
  39. mailcaps = [home + '/.mailcap', '/etc/mailcap',
  40. '/usr/etc/mailcap', '/usr/local/etc/mailcap']
  41. return mailcaps
  42. # Part 2: the parser.
  43. def readmailcapfile(fp):
  44. """Read a mailcap file and return a dictionary keyed by MIME type.
  45. Each MIME type is mapped to an entry consisting of a list of
  46. dictionaries; the list will contain more than one such dictionary
  47. if a given MIME type appears more than once in the mailcap file.
  48. Each dictionary contains key-value pairs for that MIME type, where
  49. the viewing command is stored with the key "view".
  50. """
  51. caps = {}
  52. while 1:
  53. line = fp.readline()
  54. if not line: break
  55. # Ignore comments and blank lines
  56. if line[0] == '#' or line.strip() == '':
  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 = key.split('/')
  70. for j in range(len(types)):
  71. types[j] = types[j].strip()
  72. key = '/'.join(types).lower()
  73. # Update the database
  74. if caps.has_key(key):
  75. caps[key].append(fields)
  76. else:
  77. caps[key] = [fields]
  78. return caps
  79. def parseline(line):
  80. """Parse one entry in a mailcap file and return a dictionary.
  81. The viewing command is stored as the value with the key "view",
  82. and the rest of the fields produce key-value pairs in the dict.
  83. """
  84. fields = []
  85. i, n = 0, len(line)
  86. while i < n:
  87. field, i = parsefield(line, i, n)
  88. fields.append(field)
  89. i = i+1 # Skip semicolon
  90. if len(fields) < 2:
  91. return None, None
  92. key, view, rest = fields[0], fields[1], fields[2:]
  93. fields = {'view': view}
  94. for field in rest:
  95. i = field.find('=')
  96. if i < 0:
  97. fkey = field
  98. fvalue = ""
  99. else:
  100. fkey = field[:i].strip()
  101. fvalue = field[i+1:].strip()
  102. if fields.has_key(fkey):
  103. # Ignore it
  104. pass
  105. else:
  106. fields[fkey] = fvalue
  107. return key, fields
  108. def parsefield(line, i, n):
  109. """Separate one key-value pair in a mailcap entry."""
  110. start = i
  111. while i < n:
  112. c = line[i]
  113. if c == ';':
  114. break
  115. elif c == '\\':
  116. i = i+2
  117. else:
  118. i = i+1
  119. return line[start:i].strip(), i
  120. # Part 3: using the database.
  121. def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]):
  122. """Find a match for a mailcap entry.
  123. Return a tuple containing the command line, and the mailcap entry
  124. used; (None, None) if no match is found. This may invoke the
  125. 'test' command of several matching entries before deciding which
  126. entry to use.
  127. """
  128. entries = lookup(caps, MIMEtype, key)
  129. # XXX This code should somehow check for the needsterminal flag.
  130. for e in entries:
  131. if e.has_key('test'):
  132. test = subst(e['test'], filename, plist)
  133. if test and os.system(test) != 0:
  134. continue
  135. command = subst(e[key], MIMEtype, filename, plist)
  136. return command, e
  137. return None, None
  138. def lookup(caps, MIMEtype, key=None):
  139. entries = []
  140. if caps.has_key(MIMEtype):
  141. entries = entries + caps[MIMEtype]
  142. MIMEtypes = MIMEtype.split('/')
  143. MIMEtype = MIMEtypes[0] + '/*'
  144. if caps.has_key(MIMEtype):
  145. entries = entries + caps[MIMEtype]
  146. if key is not None:
  147. entries = filter(lambda e, key=key: e.has_key(key), entries)
  148. return entries
  149. def subst(field, MIMEtype, filename, plist=[]):
  150. # XXX Actually, this is Unix-specific
  151. res = ''
  152. i, n = 0, len(field)
  153. while i < n:
  154. c = field[i]; i = i+1
  155. if c != '%':
  156. if c == '\\':
  157. c = field[i:i+1]; i = i+1
  158. res = res + c
  159. else:
  160. c = field[i]; i = i+1
  161. if c == '%':
  162. res = res + c
  163. elif c == 's':
  164. res = res + filename
  165. elif c == 't':
  166. res = res + MIMEtype
  167. elif c == '{':
  168. start = i
  169. while i < n and field[i] != '}':
  170. i = i+1
  171. name = field[start:i]
  172. i = i+1
  173. res = res + findparam(name, plist)
  174. # XXX To do:
  175. # %n == number of parts if type is multipart/*
  176. # %F == list of alternating type and filename for parts
  177. else:
  178. res = res + '%' + c
  179. return res
  180. def findparam(name, plist):
  181. name = name.lower() + '='
  182. n = len(name)
  183. for p in plist:
  184. if p[:n].lower() == name:
  185. return p[n:]
  186. return ''
  187. # Part 4: test program.
  188. def test():
  189. import sys
  190. caps = getcaps()
  191. if not sys.argv[1:]:
  192. show(caps)
  193. return
  194. for i in range(1, len(sys.argv), 2):
  195. args = sys.argv[i:i+2]
  196. if len(args) < 2:
  197. print "usage: mailcap [MIMEtype file] ..."
  198. return
  199. MIMEtype = args[0]
  200. file = args[1]
  201. command, e = findmatch(caps, MIMEtype, 'view', file)
  202. if not command:
  203. print "No viewer found for", type
  204. else:
  205. print "Executing:", command
  206. sts = os.system(command)
  207. if sts:
  208. print "Exit status:", sts
  209. def show(caps):
  210. print "Mailcap files:"
  211. for fn in listmailcapfiles(): print "\t" + fn
  212. print
  213. if not caps: caps = getcaps()
  214. print "Mailcap entries:"
  215. print
  216. ckeys = caps.keys()
  217. ckeys.sort()
  218. for type in ckeys:
  219. print type
  220. entries = caps[type]
  221. for e in entries:
  222. keys = e.keys()
  223. keys.sort()
  224. for k in keys:
  225. print " %-15s" % k, e[k]
  226. print
  227. if __name__ == '__main__':
  228. test()