Merge from emacs--rel--22
[bpt/emacs.git] / lisp / gnus / mailcap.el
CommitLineData
c0393b5e 1;;; mailcap.el --- MIME media types configuration
e84b4b86
TTN
2
3;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
e3fe4da0 4;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
c113de23
GM
5
6;; Author: William M. Perry <wmperry@aventail.com>
7;; Lars Magne Ingebrigtsen <larsi@gnus.org>
c0393b5e 8;; Keywords: news, mail, multimedia
c113de23
GM
9
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software; you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
5a9dffec 14;; the Free Software Foundation; either version 3, or (at your option)
c113de23
GM
15;; any later version.
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs; see the file COPYING. If not, write to the
3a35cf56
LK
24;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25;; Boston, MA 02110-1301, USA.
c113de23
GM
26
27;;; Commentary:
28
c0393b5e
DL
29;; Provides configuration of MIME media types from directly from Lisp
30;; and via the usual mailcap mechanism (RFC 1524). Deals with
31;; mime.types similarly.
32
c113de23
GM
33;;; Code:
34
35(eval-when-compile (require 'cl))
b890d447
MB
36(autoload 'mail-header-parse-content-type "mail-parse")
37
38;; `mm-delete-duplicates' is an alias for `delete-dups' in Emacs 22.
39(defalias 'mailcap-delete-duplicates
40 (if (fboundp 'delete-dups)
41 'delete-dups
42 (autoload 'mm-delete-duplicates "mm-util")
43 'mm-delete-duplicates))
c113de23 44
c0393b5e
DL
45(defgroup mailcap nil
46 "Definition of viewers for MIME types."
47 :version "21.1"
48 :group 'mime)
49
c113de23
GM
50(defvar mailcap-parse-args-syntax-table
51 (let ((table (copy-syntax-table emacs-lisp-mode-syntax-table)))
52 (modify-syntax-entry ?' "\"" table)
53 (modify-syntax-entry ?` "\"" table)
54 (modify-syntax-entry ?{ "(" table)
55 (modify-syntax-entry ?} ")" table)
56 table)
23f87bed
MB
57 "A syntax table for parsing SGML attributes.")
58
59(eval-and-compile
60 (when (featurep 'xemacs)
61 (condition-case nil
62 (require 'lpr)
63 (error nil))))
64
65(defvar mailcap-print-command
66 (mapconcat 'identity
67 (cons (if (boundp 'lpr-command)
68 lpr-command
69 "lpr")
70 (when (boundp 'lpr-switches)
71 (if (stringp lpr-switches)
72 (list lpr-switches)
73 lpr-switches)))
74 " ")
75 "Shell command (including switches) used to print Postscript files.")
c113de23 76
c0393b5e
DL
77;; Postpone using defcustom for this as it's so big and we essentially
78;; have to have two copies of the data around then. Perhaps just
79;; customize the Lisp viewers and rely on the normal configuration
80;; files for the rest? -- fx
c113de23 81(defvar mailcap-mime-data
23f87bed
MB
82 `(("application"
83 ("vnd.ms-excel"
84 (viewer . "gnumeric %s")
85 (test . (getenv "DISPLAY"))
86 (type . "application/vnd.ms-excel"))
c113de23
GM
87 ("x-x509-ca-cert"
88 (viewer . ssl-view-site-cert)
89 (test . (fboundp 'ssl-view-site-cert))
90 (type . "application/x-x509-ca-cert"))
91 ("x-x509-user-cert"
92 (viewer . ssl-view-user-cert)
93 (test . (fboundp 'ssl-view-user-cert))
94 (type . "application/x-x509-user-cert"))
95 ("octet-stream"
96 (viewer . mailcap-save-binary-file)
97 (non-viewer . t)
98 (type . "application/octet-stream"))
c113de23 99 ("dvi"
23f87bed
MB
100 (viewer . "xdvi -safer %s")
101 (test . (eq window-system 'x))
c113de23 102 ("needsx11")
23f87bed
MB
103 (type . "application/dvi")
104 ("print" . "dvips -qRP %s"))
c113de23
GM
105 ("dvi"
106 (viewer . "dvitty %s")
107 (test . (not (getenv "DISPLAY")))
23f87bed
MB
108 (type . "application/dvi")
109 ("print" . "dvips -qRP %s"))
c113de23
GM
110 ("emacs-lisp"
111 (viewer . mailcap-maybe-eval)
112 (type . "application/emacs-lisp"))
23f87bed
MB
113 ("x-emacs-lisp"
114 (viewer . mailcap-maybe-eval)
115 (type . "application/x-emacs-lisp"))
c113de23
GM
116 ("x-tar"
117 (viewer . mailcap-save-binary-file)
118 (non-viewer . t)
119 (type . "application/x-tar"))
120 ("x-latex"
121 (viewer . tex-mode)
122 (test . (fboundp 'tex-mode))
123 (type . "application/x-latex"))
124 ("x-tex"
125 (viewer . tex-mode)
126 (test . (fboundp 'tex-mode))
127 (type . "application/x-tex"))
128 ("latex"
129 (viewer . tex-mode)
130 (test . (fboundp 'tex-mode))
131 (type . "application/latex"))
132 ("tex"
133 (viewer . tex-mode)
134 (test . (fboundp 'tex-mode))
135 (type . "application/tex"))
136 ("texinfo"
137 (viewer . texinfo-mode)
138 (test . (fboundp 'texinfo-mode))
139 (type . "application/tex"))
140 ("zip"
141 (viewer . mailcap-save-binary-file)
142 (non-viewer . t)
143 (type . "application/zip")
144 ("copiousoutput"))
c0393b5e 145 ("pdf"
23f87bed 146 (viewer . "gv -safer %s")
c0393b5e 147 (type . "application/pdf")
23f87bed
MB
148 (test . window-system)
149 ("print" . ,(concat "pdf2ps %s - | " mailcap-print-command)))
c0393b5e 150 ("pdf"
f0096211 151 (viewer . "gpdf %s")
c0393b5e 152 (type . "application/pdf")
23f87bed
MB
153 ("print" . ,(concat "pdftops %s - | " mailcap-print-command))
154 (test . (eq window-system 'x)))
c113de23 155 ("pdf"
f0096211
MB
156 (viewer . "xpdf %s")
157 (type . "application/pdf")
158 ("print" . ,(concat "pdftops %s - | " mailcap-print-command))
159 (test . (eq window-system 'x)))
23f87bed
MB
160 ("pdf"
161 (viewer . ,(concat "pdftotext %s -"))
162 (type . "application/pdf")
163 ("print" . ,(concat "pdftops %s - | " mailcap-print-command))
164 ("copiousoutput"))
c113de23 165 ("postscript"
c0393b5e
DL
166 (viewer . "gv -safer %s")
167 (type . "application/postscript")
168 (test . window-system)
23f87bed 169 ("print" . ,(concat mailcap-print-command " %s"))
c0393b5e 170 ("needsx11"))
c113de23
GM
171 ("postscript"
172 (viewer . "ghostview -dSAFER %s")
173 (type . "application/postscript")
23f87bed
MB
174 (test . (eq window-system 'x))
175 ("print" . ,(concat mailcap-print-command " %s"))
c113de23
GM
176 ("needsx11"))
177 ("postscript"
178 (viewer . "ps2ascii %s")
179 (type . "application/postscript")
180 (test . (not (getenv "DISPLAY")))
23f87bed
MB
181 ("print" . ,(concat mailcap-print-command " %s"))
182 ("copiousoutput"))
183 ("sieve"
184 (viewer . sieve-mode)
185 (test . (fboundp 'sieve-mode))
186 (type . "application/sieve"))
187 ("pgp-keys"
188 (viewer . "gpg --import --interactive --verbose")
189 (type . "application/pgp-keys")
190 ("needsterminal")))
c113de23
GM
191 ("audio"
192 ("x-mpeg"
193 (viewer . "maplay %s")
194 (type . "audio/x-mpeg"))
195 (".*"
196 (viewer . "showaudio")
197 (type . "audio/*")))
198 ("message"
199 ("rfc-*822"
200 (viewer . mm-view-message)
201 (test . (and (featurep 'gnus)
202 (gnus-alive-p)))
203 (type . "message/rfc822"))
204 ("rfc-*822"
205 (viewer . vm-mode)
206 (test . (fboundp 'vm-mode))
207 (type . "message/rfc822"))
208 ("rfc-*822"
209 (viewer . w3-mode)
210 (test . (fboundp 'w3-mode))
211 (type . "message/rfc822"))
212 ("rfc-*822"
213 (viewer . view-mode)
c113de23
GM
214 (type . "message/rfc822")))
215 ("image"
216 ("x-xwd"
217 (viewer . "xwud -in %s")
218 (type . "image/x-xwd")
219 ("compose" . "xwd -frame > %s")
23f87bed 220 (test . (eq window-system 'x))
c113de23
GM
221 ("needsx11"))
222 ("x11-dump"
223 (viewer . "xwud -in %s")
224 (type . "image/x-xwd")
225 ("compose" . "xwd -frame > %s")
23f87bed 226 (test . (eq window-system 'x))
c113de23
GM
227 ("needsx11"))
228 ("windowdump"
229 (viewer . "xwud -in %s")
230 (type . "image/x-xwd")
231 ("compose" . "xwd -frame > %s")
23f87bed 232 (test . (eq window-system 'x))
c113de23 233 ("needsx11"))
c113de23
GM
234 (".*"
235 (viewer . "display %s")
236 (type . "image/*")
23f87bed 237 (test . (eq window-system 'x))
c113de23
GM
238 ("needsx11"))
239 (".*"
240 (viewer . "ee %s")
241 (type . "image/*")
23f87bed 242 (test . (eq window-system 'x))
c113de23
GM
243 ("needsx11")))
244 ("text"
245 ("plain"
246 (viewer . w3-mode)
247 (test . (fboundp 'w3-mode))
248 (type . "text/plain"))
249 ("plain"
250 (viewer . view-mode)
251 (test . (fboundp 'view-mode))
252 (type . "text/plain"))
253 ("plain"
254 (viewer . fundamental-mode)
255 (type . "text/plain"))
256 ("enriched"
23f87bed 257 (viewer . enriched-decode)
c113de23
GM
258 (test . (fboundp 'enriched-decode))
259 (type . "text/enriched"))
260 ("html"
261 (viewer . mm-w3-prepare-buffer)
262 (test . (fboundp 'w3-prepare-buffer))
01c52d31
MB
263 (type . "text/html"))
264 ("dns"
265 (viewer . dns-mode)
266 (test . (fboundp 'dns-mode))
267 (type . "text/dns")))
c113de23
GM
268 ("video"
269 ("mpeg"
270 (viewer . "mpeg_play %s")
271 (type . "video/mpeg")
23f87bed 272 (test . (eq window-system 'x))
c113de23
GM
273 ("needsx11")))
274 ("x-world"
275 ("x-vrml"
276 (viewer . "webspace -remote %s -URL %u")
277 (type . "x-world/x-vrml")
278 ("description"
279 "VRML document")))
280 ("archive"
281 ("tar"
282 (viewer . tar-mode)
283 (type . "archive/tar")
284 (test . (fboundp 'tar-mode)))))
285 "The mailcap structure is an assoc list of assoc lists.
2861st assoc list is keyed on the major content-type
2872nd assoc list is keyed on the minor content-type (which can be a regexp)
288
289Which looks like:
290-----------------
291 ((\"application\"
292 (\"postscript\" . <info>))
293 (\"text\"
294 (\"plain\" . <info>)))
295
296Where <info> is another assoc list of the various information
c0393b5e 297related to the mailcap RFC 1524. This is keyed on the lowercase
c113de23 298attribute name (viewer, test, etc). This looks like:
c0393b5e
DL
299 ((viewer . VIEWERINFO)
300 (test . TESTINFO)
301 (xxxx . \"STRING\")
302 FLAG)
c113de23 303
c0393b5e 304Where VIEWERINFO specifies how the content-type is viewed. Can be
c113de23
GM
305a string, in which case it is run through a shell, with
306appropriate parameters, or a symbol, in which case the symbol is
c0393b5e
DL
307`funcall'ed, with the buffer as an argument.
308
309TESTINFO is a test for the viewer's applicability, or nil. If nil, it
310means the viewer is always valid. If it is a Lisp function, it is
311called with a list of items from any extra fields from the
312Content-Type header as argument to return a boolean value for the
313validity. Otherwise, if it is a non-function Lisp symbol or list
314whose car is a symbol, it is `eval'led to yield the validity. If it
315is a string or list of strings, it represents a shell command to run
316to return a true or false shell value for the validity.")
f8bbbafb 317(put 'mailcap-mime-data 'risky-local-variable t)
c0393b5e
DL
318
319(defcustom mailcap-download-directory nil
003dabb7 320 "*Directory to which `mailcap-save-binary-file' downloads files by default.
f0529b5b 321nil means your home directory."
003dabb7
DL
322 :type '(choice (const :tag "Home directory" nil)
323 directory)
c0393b5e 324 :group 'mailcap)
c113de23 325
23f87bed
MB
326(defvar mailcap-poor-system-types
327 '(ms-dos ms-windows windows-nt win32 w32 mswindows)
328 "Systems that don't have a Unix-like directory hierarchy.")
329
c113de23
GM
330;;;
331;;; Utility functions
332;;;
333
c113de23
GM
334(defun mailcap-save-binary-file ()
335 (goto-char (point-min))
336 (unwind-protect
337 (let ((file (read-file-name
338 "Filename to save as: "
339 (or mailcap-download-directory "~/")))
340 (require-final-newline nil))
341 (write-region (point-min) (point-max) file))
342 (kill-buffer (current-buffer))))
343
344(defvar mailcap-maybe-eval-warning
345 "*** WARNING ***
346
c0393b5e 347This MIME part contains untrusted and possibly harmful content.
c113de23
GM
348If you evaluate the Emacs Lisp code contained in it, a lot of nasty
349things can happen. Please examine the code very carefully before you
350instruct Emacs to evaluate it. You can browse the buffer containing
351the code using \\[scroll-other-window].
352
353If you are unsure what to do, please answer \"no\"."
354 "Text of warning message displayed by `mailcap-maybe-eval'.
355Make sure that this text consists only of few text lines. Otherwise,
356Gnus might fail to display all of it.")
a1506d29 357
c113de23 358(defun mailcap-maybe-eval ()
c0393b5e 359 "Maybe evaluate a buffer of Emacs Lisp code."
c113de23
GM
360 (let ((lisp-buffer (current-buffer)))
361 (goto-char (point-min))
362 (when
363 (save-window-excursion
364 (delete-other-windows)
365 (let ((buffer (get-buffer-create (generate-new-buffer-name
366 "*Warning*"))))
367 (unwind-protect
368 (with-current-buffer buffer
c0393b5e 369 (insert (substitute-command-keys
c113de23
GM
370 mailcap-maybe-eval-warning))
371 (goto-char (point-min))
372 (display-buffer buffer)
373 (yes-or-no-p "This is potentially dangerous emacs-lisp code, evaluate it? "))
374 (kill-buffer buffer))))
375 (eval-buffer (current-buffer)))
376 (when (buffer-live-p lisp-buffer)
377 (with-current-buffer lisp-buffer
378 (emacs-lisp-mode)))))
379
380
381;;;
382;;; The mailcap parser
383;;;
384
385(defun mailcap-replace-regexp (regexp to-string)
386 ;; Quiet replace-regexp.
387 (goto-char (point-min))
388 (while (re-search-forward regexp nil t)
389 (replace-match to-string t nil)))
390
391(defvar mailcap-parsed-p nil)
392
393(defun mailcap-parse-mailcaps (&optional path force)
394 "Parse out all the mailcaps specified in a path string PATH.
395Components of PATH are separated by the `path-separator' character
396appropriate for this system. If FORCE, re-parse even if already
397parsed. If PATH is omitted, use the value of environment variable
398MAILCAPS if set; otherwise (on Unix) use the path from RFC 1524, plus
399/usr/local/etc/mailcap."
400 (interactive (list nil t))
401 (when (or (not mailcap-parsed-p)
402 force)
403 (cond
404 (path nil)
405 ((getenv "MAILCAPS") (setq path (getenv "MAILCAPS")))
23f87bed 406 ((memq system-type mailcap-poor-system-types)
c113de23
GM
407 (setq path '("~/.mailcap" "~/mail.cap" "~/etc/mail.cap")))
408 (t (setq path
409 ;; This is per RFC 1524, specifically
410 ;; with /usr before /usr/local.
411 '("~/.mailcap" "/etc/mailcap" "/usr/etc/mailcap"
412 "/usr/local/etc/mailcap"))))
413 (let ((fnames (reverse
414 (if (stringp path)
9b198954 415 (delete "" (split-string path path-separator))
c113de23
GM
416 path)))
417 fname)
418 (while fnames
419 (setq fname (car fnames))
420 (if (and (file-readable-p fname)
421 (file-regular-p fname))
422 (mailcap-parse-mailcap fname))
423 (setq fnames (cdr fnames))))
424 (setq mailcap-parsed-p t)))
425
426(defun mailcap-parse-mailcap (fname)
c0393b5e 427 "Parse out the mailcap file specified by FNAME."
c113de23
GM
428 (let (major ; The major mime type (image/audio/etc)
429 minor ; The minor mime type (gif, basic, etc)
430 save-pos ; Misc saved positions used in parsing
431 viewer ; How to view this mime type
432 info ; Misc info about this mime type
433 )
434 (with-temp-buffer
435 (insert-file-contents fname)
436 (set-syntax-table mailcap-parse-args-syntax-table)
437 (mailcap-replace-regexp "#.*" "") ; Remove all comments
438 (mailcap-replace-regexp "\\\\[ \t]*\n" " ") ; And collapse spaces
439 (mailcap-replace-regexp "\n+" "\n") ; And blank lines
440 (goto-char (point-max))
441 (skip-chars-backward " \t\n")
442 (delete-region (point) (point-max))
443 (while (not (bobp))
444 (skip-chars-backward " \t\n")
445 (beginning-of-line)
446 (setq save-pos (point)
447 info nil)
448 (skip-chars-forward "^/; \t\n")
449 (downcase-region save-pos (point))
450 (setq major (buffer-substring save-pos (point)))
451 (skip-chars-forward " \t")
452 (setq minor "")
453 (when (eq (char-after) ?/)
454 (forward-char)
455 (skip-chars-forward " \t")
456 (setq save-pos (point))
457 (skip-chars-forward "^; \t\n")
458 (downcase-region save-pos (point))
459 (setq minor
460 (cond
461 ((eq ?* (or (char-after save-pos) 0)) ".*")
462 ((= (point) save-pos) ".*")
463 (t (regexp-quote (buffer-substring save-pos (point)))))))
464 (skip-chars-forward " \t")
465 ;;; Got the major/minor chunks, now for the viewers/etc
466 ;;; The first item _must_ be a viewer, according to the
c0393b5e 467 ;;; RFC for mailcap files (#1524)
c113de23 468 (setq viewer "")
c0393b5e 469 (when (eq (char-after) ?\;)
c113de23
GM
470 (forward-char)
471 (skip-chars-forward " \t")
472 (setq save-pos (point))
473 (skip-chars-forward "^;\n")
474 ;; skip \;
475 (while (eq (char-before) ?\\)
476 (backward-delete-char 1)
477 (forward-char)
478 (skip-chars-forward "^;\n"))
479 (if (eq (or (char-after save-pos) 0) ?')
480 (setq viewer (progn
481 (narrow-to-region (1+ save-pos) (point))
482 (goto-char (point-min))
483 (prog1
484 (read (current-buffer))
485 (goto-char (point-max))
486 (widen))))
487 (setq viewer (buffer-substring save-pos (point)))))
488 (setq save-pos (point))
489 (end-of-line)
c0393b5e 490 (unless (equal viewer "")
c113de23
GM
491 (setq info (nconc (list (cons 'viewer viewer)
492 (cons 'type (concat major "/"
493 (if (string= minor ".*")
494 "*" minor))))
495 (mailcap-parse-mailcap-extras save-pos (point))))
496 (mailcap-mailcap-entry-passes-test info)
497 (mailcap-add-mailcap-entry major minor info))
498 (beginning-of-line)))))
499
500(defun mailcap-parse-mailcap-extras (st nd)
c0393b5e 501 "Grab all the extra stuff from a mailcap entry."
c113de23
GM
502 (let (
503 name ; From name=
504 value ; its value
505 results ; Assoc list of results
506 name-pos ; Start of XXXX= position
507 val-pos ; Start of value position
508 done ; Found end of \'d ;s?
509 )
510 (save-restriction
511 (narrow-to-region st nd)
512 (goto-char (point-min))
513 (skip-chars-forward " \n\t;")
514 (while (not (eobp))
515 (setq done nil)
516 (setq name-pos (point))
517 (skip-chars-forward "^ \n\t=;")
518 (downcase-region name-pos (point))
519 (setq name (buffer-substring name-pos (point)))
520 (skip-chars-forward " \t\n")
521 (if (not (eq (char-after (point)) ?=)) ; There is no value
522 (setq value t)
523 (skip-chars-forward " \t\n=")
524 (setq val-pos (point))
525 (if (memq (char-after val-pos) '(?\" ?'))
526 (progn
527 (setq val-pos (1+ val-pos))
528 (condition-case nil
529 (progn
530 (forward-sexp 1)
531 (backward-char 1))
532 (error (goto-char (point-max)))))
533 (while (not done)
534 (skip-chars-forward "^;")
535 (if (eq (char-after (1- (point))) ?\\ )
536 (progn
537 (subst-char-in-region (1- (point)) (point) ?\\ ? )
538 (skip-chars-forward ";"))
539 (setq done t))))
540 (setq value (buffer-substring val-pos (point))))
7347faa8
MB
541 ;; `test' as symbol, others like "copiousoutput" and "needsx11" as
542 ;; strings
543 (setq results (cons (cons (if (string-equal name "test")
544 'test
545 name)
546 value) results))
c113de23
GM
547 (skip-chars-forward " \";\n\t"))
548 results)))
549
550(defun mailcap-mailcap-entry-passes-test (info)
e7f767c2 551 "Return non-nil if mailcap entry INFO passes its test clause.
c0393b5e
DL
552Also return non-nil if no test clause is present."
553 (let ((test (assq 'test info)) ; The test clause
554 status)
c113de23
GM
555 (setq status (and test (split-string (cdr test) " ")))
556 (if (and (or (assoc "needsterm" info)
557 (assoc "needsterminal" info)
558 (assoc "needsx11" info))
559 (not (getenv "DISPLAY")))
560 (setq status nil)
561 (cond
562 ((and (equal (nth 0 status) "test")
563 (equal (nth 1 status) "-n")
564 (or (equal (nth 2 status) "$DISPLAY")
565 (equal (nth 2 status) "\"$DISPLAY\"")))
566 (setq status (if (getenv "DISPLAY") t nil)))
567 ((and (equal (nth 0 status) "test")
568 (equal (nth 1 status) "-z")
569 (or (equal (nth 2 status) "$DISPLAY")
570 (equal (nth 2 status) "\"$DISPLAY\"")))
571 (setq status (if (getenv "DISPLAY") nil t)))
572 (test nil)
573 (t nil)))
574 (and test (listp test) (setcdr test status))))
575
576;;;
577;;; The action routines.
578;;;
579
580(defun mailcap-possible-viewers (major minor)
c0393b5e 581 "Return a list of possible viewers from MAJOR for minor type MINOR."
c113de23
GM
582 (let ((exact '())
583 (wildcard '()))
584 (while major
585 (cond
586 ((equal (car (car major)) minor)
587 (setq exact (cons (cdr (car major)) exact)))
23f87bed 588 ((and minor (string-match (concat "^" (car (car major)) "$") minor))
c113de23
GM
589 (setq wildcard (cons (cdr (car major)) wildcard))))
590 (setq major (cdr major)))
591 (nconc exact wildcard)))
592
593(defun mailcap-unescape-mime-test (test type-info)
594 (let (save-pos save-chr subst)
595 (cond
596 ((symbolp test) test)
597 ((and (listp test) (symbolp (car test))) test)
598 ((or (stringp test)
599 (and (listp test) (stringp (car test))
600 (setq test (mapconcat 'identity test " "))))
601 (with-temp-buffer
602 (insert test)
603 (goto-char (point-min))
604 (while (not (eobp))
605 (skip-chars-forward "^%")
606 (if (/= (- (point)
607 (progn (skip-chars-backward "\\\\")
608 (point)))
609 0) ; It is an escaped %
610 (progn
611 (delete-char 1)
612 (skip-chars-forward "%."))
613 (setq save-pos (point))
614 (skip-chars-forward "%")
615 (setq save-chr (char-after (point)))
c0393b5e
DL
616 ;; Escapes:
617 ;; %s: name of a file for the body data
618 ;; %t: content-type
619 ;; %{<parameter name}: value of parameter in mailcap entry
620 ;; %n: number of sub-parts for multipart content-type
621 ;; %F: a set of content-type/filename pairs for multiparts
c113de23
GM
622 (cond
623 ((null save-chr) nil)
624 ((= save-chr ?t)
625 (delete-region save-pos (progn (forward-char 1) (point)))
626 (insert (or (cdr (assq 'type type-info)) "\"\"")))
c0393b5e 627 ((memq save-chr '(?M ?n ?F))
c113de23
GM
628 (delete-region save-pos (progn (forward-char 1) (point)))
629 (insert "\"\""))
630 ((= save-chr ?{)
631 (forward-char 1)
632 (skip-chars-forward "^}")
633 (downcase-region (+ 2 save-pos) (point))
634 (setq subst (buffer-substring (+ 2 save-pos) (point)))
635 (delete-region save-pos (1+ (point)))
636 (insert (or (cdr (assoc subst type-info)) "\"\"")))
637 (t nil))))
638 (buffer-string)))
c0393b5e 639 (t (error "Bad value to mailcap-unescape-mime-test: %s" test)))))
c113de23
GM
640
641(defvar mailcap-viewer-test-cache nil)
642
643(defun mailcap-viewer-passes-test (viewer-info type-info)
e7f767c2 644 "Return non-nil if viewer specified by VIEWER-INFO passes its test clause.
23f87bed 645Also return non-nil if it has no test clause. TYPE-INFO is an argument
c0393b5e 646to supply to the test."
c113de23
GM
647 (let* ((test-info (assq 'test viewer-info))
648 (test (cdr test-info))
649 (otest test)
650 (viewer (cdr (assoc 'viewer viewer-info)))
651 (default-directory (expand-file-name "~/"))
652 status parsed-test cache result)
46e8fe3d
MB
653 (cond ((setq cache (assoc test mailcap-viewer-test-cache))
654 (cadr cache))
655 ((not test-info) t) ; No test clause
656 (t
657 (setq
658 result
659 (cond
660 ((not test) nil) ; Already failed test
661 ((eq test t) t) ; Already passed test
662 ((functionp test) ; Lisp function as test
663 (funcall test type-info))
664 ((and (symbolp test) ; Lisp variable as test
665 (boundp test))
666 (symbol-value test))
667 ((and (listp test) ; List to be eval'd
668 (symbolp (car test)))
669 (eval test))
670 (t
671 (setq test (mailcap-unescape-mime-test test type-info)
672 test (list shell-file-name nil nil nil
673 shell-command-switch test)
674 status (apply 'call-process test))
675 (eq 0 status))))
676 (push (list otest result) mailcap-viewer-test-cache)
677 result))))
c113de23
GM
678
679(defun mailcap-add-mailcap-entry (major minor info)
680 (let ((old-major (assoc major mailcap-mime-data)))
681 (if (null old-major) ; New major area
682 (setq mailcap-mime-data
683 (cons (cons major (list (cons minor info)))
684 mailcap-mime-data))
23f87bed
MB
685 (let ((cur-minor (assoc minor old-major)))
686 (cond
687 ((or (null cur-minor) ; New minor area, or
688 (assq 'test info)) ; Has a test, insert at beginning
689 (setcdr old-major (cons (cons minor info) (cdr old-major))))
690 ((and (not (assq 'test info)) ; No test info, replace completely
691 (not (assq 'test cur-minor))
c113de23
GM
692 (equal (assq 'viewer info) ; Keep alternative viewer
693 (assq 'viewer cur-minor)))
23f87bed
MB
694 (setcdr cur-minor info))
695 (t
696 (setcdr old-major (cons (cons minor info) (cdr old-major))))))
c113de23
GM
697 )))
698
699(defun mailcap-add (type viewer &optional test)
700 "Add VIEWER as a handler for TYPE.
701If TEST is not given, it defaults to t."
702 (let ((tl (split-string type "/")))
703 (when (or (not (car tl))
704 (not (cadr tl)))
705 (error "%s is not a valid MIME type" type))
706 (mailcap-add-mailcap-entry
707 (car tl) (cadr tl)
708 `((viewer . ,viewer)
709 (test . ,(if test test t))
710 (type . ,type)))))
711
712;;;
713;;; The main whabbo
714;;;
715
716(defun mailcap-viewer-lessp (x y)
e7f767c2 717 "Return t if viewer X is more desirable than viewer Y."
c113de23
GM
718 (let ((x-wild (string-match "[*?]" (or (cdr-safe (assq 'type x)) "")))
719 (y-wild (string-match "[*?]" (or (cdr-safe (assq 'type y)) "")))
720 (x-lisp (not (stringp (or (cdr-safe (assq 'viewer x)) ""))))
721 (y-lisp (not (stringp (or (cdr-safe (assq 'viewer y)) "")))))
722 (cond
723 ((and x-wild (not y-wild))
724 nil)
725 ((and (not x-wild) y-wild)
726 t)
727 ((and (not y-lisp) x-lisp)
728 t)
729 (t nil))))
730
b890d447 731(defun mailcap-mime-info (string &optional request no-decode)
c113de23
GM
732 "Get the MIME viewer command for STRING, return nil if none found.
733Expects a complete content-type header line as its argument.
734
735Second argument REQUEST specifies what information to return. If it is
736nil or the empty string, the viewer (second field of the mailcap
737entry) will be returned. If it is a string, then the mailcap field
738corresponding to that string will be returned (print, description,
739whatever). If a number, then all the information for this specific
740viewer is returned. If `all', then all possible viewers for
b890d447
MB
741this type is returned.
742
743If NO-DECODE is non-nil, don't decode STRING."
744 ;; NO-DECODE avoids calling `mail-header-parse-content-type' from
745 ;; `mail-parse.el'
c113de23
GM
746 (let (
747 major ; Major encoding (text, etc)
748 minor ; Minor encoding (html, etc)
749 info ; Other info
750 save-pos ; Misc. position during parse
751 major-info ; (assoc major mailcap-mime-data)
752 minor-info ; (assoc minor major-info)
753 test ; current test proc.
754 viewers ; Possible viewers
755 passed ; Viewers that passed the test
756 viewer ; The one and only viewer
757 ctl)
758 (save-excursion
b890d447
MB
759 (setq ctl
760 (if no-decode
761 (list (or string "text/plain"))
762 (mail-header-parse-content-type (or string "text/plain"))))
c113de23
GM
763 (setq major (split-string (car ctl) "/"))
764 (setq minor (cadr major)
765 major (car major))
766 (when (setq major-info (cdr (assoc major mailcap-mime-data)))
767 (when (setq viewers (mailcap-possible-viewers major-info minor))
768 (setq info (mapcar (lambda (a) (cons (symbol-name (car a))
769 (cdr a)))
770 (cdr ctl)))
771 (while viewers
772 (if (mailcap-viewer-passes-test (car viewers) info)
773 (setq passed (cons (car viewers) passed)))
774 (setq viewers (cdr viewers)))
775 (setq passed (sort passed 'mailcap-viewer-lessp))
776 (setq viewer (car passed))))
777 (when (and (stringp (cdr (assq 'viewer viewer)))
778 passed)
779 (setq viewer (car passed)))
780 (cond
781 ((and (null viewer) (not (equal major "default")) request)
b890d447 782 (mailcap-mime-info "default" request no-decode))
c113de23
GM
783 ((or (null request) (equal request ""))
784 (mailcap-unescape-mime-test (cdr (assq 'viewer viewer)) info))
785 ((stringp request)
23f87bed
MB
786 (mailcap-unescape-mime-test
787 (cdr-safe (assoc request viewer)) info))
c113de23
GM
788 ((eq request 'all)
789 passed)
790 (t
791 ;; MUST make a copy *sigh*, else we modify mailcap-mime-data
792 (setq viewer (copy-sequence viewer))
793 (let ((view (assq 'viewer viewer))
794 (test (assq 'test viewer)))
795 (if view (setcdr view (mailcap-unescape-mime-test (cdr view) info)))
796 (if test (setcdr test (mailcap-unescape-mime-test (cdr test) info))))
797 viewer)))))
798
799;;;
800;;; Experimental MIME-types parsing
801;;;
802
803(defvar mailcap-mime-extensions
c0393b5e
DL
804 '(("" . "text/plain")
805 (".abs" . "audio/x-mpeg")
806 (".aif" . "audio/aiff")
807 (".aifc" . "audio/aiff")
808 (".aiff" . "audio/aiff")
809 (".ano" . "application/x-annotator")
810 (".au" . "audio/ulaw")
811 (".avi" . "video/x-msvideo")
812 (".bcpio" . "application/x-bcpio")
813 (".bin" . "application/octet-stream")
814 (".cdf" . "application/x-netcdr")
815 (".cpio" . "application/x-cpio")
816 (".csh" . "application/x-csh")
817 (".css" . "text/css")
818 (".dvi" . "application/x-dvi")
819 (".diff" . "text/x-patch")
820 (".el" . "application/emacs-lisp")
821 (".eps" . "application/postscript")
822 (".etx" . "text/x-setext")
823 (".exe" . "application/octet-stream")
824 (".fax" . "image/x-fax")
825 (".gif" . "image/gif")
826 (".hdf" . "application/x-hdf")
827 (".hqx" . "application/mac-binhex40")
828 (".htm" . "text/html")
829 (".html" . "text/html")
830 (".icon" . "image/x-icon")
831 (".ief" . "image/ief")
832 (".jpg" . "image/jpeg")
833 (".macp" . "image/x-macpaint")
834 (".man" . "application/x-troff-man")
835 (".me" . "application/x-troff-me")
836 (".mif" . "application/mif")
837 (".mov" . "video/quicktime")
838 (".movie" . "video/x-sgi-movie")
839 (".mp2" . "audio/x-mpeg")
840 (".mp3" . "audio/x-mpeg")
841 (".mp2a" . "audio/x-mpeg2")
842 (".mpa" . "audio/x-mpeg")
843 (".mpa2" . "audio/x-mpeg2")
844 (".mpe" . "video/mpeg")
845 (".mpeg" . "video/mpeg")
846 (".mpega" . "audio/x-mpeg")
847 (".mpegv" . "video/mpeg")
848 (".mpg" . "video/mpeg")
849 (".mpv" . "video/mpeg")
850 (".ms" . "application/x-troff-ms")
851 (".nc" . "application/x-netcdf")
852 (".nc" . "application/x-netcdf")
853 (".oda" . "application/oda")
854 (".patch" . "text/x-patch")
855 (".pbm" . "image/x-portable-bitmap")
856 (".pdf" . "application/pdf")
857 (".pgm" . "image/portable-graymap")
858 (".pict" . "image/pict")
859 (".png" . "image/png")
860 (".pnm" . "image/x-portable-anymap")
861 (".ppm" . "image/portable-pixmap")
862 (".ps" . "application/postscript")
863 (".qt" . "video/quicktime")
864 (".ras" . "image/x-raster")
865 (".rgb" . "image/x-rgb")
866 (".rtf" . "application/rtf")
867 (".rtx" . "text/richtext")
868 (".sh" . "application/x-sh")
869 (".sit" . "application/x-stuffit")
23f87bed 870 (".siv" . "application/sieve")
c0393b5e 871 (".snd" . "audio/basic")
01c52d31 872 (".soa" . "text/dns")
c0393b5e
DL
873 (".src" . "application/x-wais-source")
874 (".tar" . "archive/tar")
875 (".tcl" . "application/x-tcl")
876 (".tex" . "application/x-tex")
877 (".texi" . "application/texinfo")
878 (".tga" . "image/x-targa")
879 (".tif" . "image/tiff")
880 (".tiff" . "image/tiff")
881 (".tr" . "application/x-troff")
882 (".troff" . "application/x-troff")
883 (".tsv" . "text/tab-separated-values")
884 (".txt" . "text/plain")
885 (".vbs" . "video/mpeg")
886 (".vox" . "audio/basic")
887 (".vrml" . "x-world/x-vrml")
888 (".wav" . "audio/x-wav")
23f87bed 889 (".xls" . "application/vnd.ms-excel")
c0393b5e
DL
890 (".wrl" . "x-world/x-vrml")
891 (".xbm" . "image/xbm")
892 (".xpm" . "image/xpm")
893 (".xwd" . "image/windowdump")
894 (".zip" . "application/zip")
895 (".ai" . "application/postscript")
896 (".jpe" . "image/jpeg")
897 (".jpeg" . "image/jpeg"))
898 "An alist of file extensions and corresponding MIME content-types.
899This exists for you to customize the information in Lisp. It is
900merged with values from mailcap files by `mailcap-parse-mimetypes'.")
c113de23
GM
901
902(defvar mailcap-mimetypes-parsed-p nil)
903
904(defun mailcap-parse-mimetypes (&optional path force)
c0393b5e 905 "Parse out all the mimetypes specified in a Unix-style path string PATH.
c113de23
GM
906Components of PATH are separated by the `path-separator' character
907appropriate for this system. If PATH is omitted, use the value of
908environment variable MIMETYPES if set; otherwise use a default path.
909If FORCE, re-parse even if already parsed."
910 (interactive (list nil t))
911 (when (or (not mailcap-mimetypes-parsed-p)
912 force)
913 (cond
914 (path nil)
915 ((getenv "MIMETYPES") (setq path (getenv "MIMETYPES")))
23f87bed 916 ((memq system-type mailcap-poor-system-types)
c113de23
GM
917 (setq path '("~/mime.typ" "~/etc/mime.typ")))
918 (t (setq path
919 ;; mime.types seems to be the normal name, definitely so
920 ;; on current GNUish systems. The search order follows
921 ;; that for mailcap.
922 '("~/.mime.types"
923 "/etc/mime.types"
924 "/usr/etc/mime.types"
925 "/usr/local/etc/mime.types"
926 "/usr/local/www/conf/mime.types"
927 "~/.mime-types"
928 "/etc/mime-types"
929 "/usr/etc/mime-types"
930 "/usr/local/etc/mime-types"
931 "/usr/local/www/conf/mime-types"))))
932 (let ((fnames (reverse (if (stringp path)
9b198954 933 (delete "" (split-string path path-separator))
c113de23
GM
934 path)))
935 fname)
936 (while fnames
937 (setq fname (car fnames))
938 (if (and (file-readable-p fname))
939 (mailcap-parse-mimetype-file fname))
940 (setq fnames (cdr fnames))))
941 (setq mailcap-mimetypes-parsed-p t)))
942
943(defun mailcap-parse-mimetype-file (fname)
c0393b5e 944 "Parse out a mime-types file FNAME."
c113de23
GM
945 (let (type ; The MIME type for this line
946 extns ; The extensions for this line
947 save-pos ; Misc. saved buffer positions
948 )
949 (with-temp-buffer
950 (insert-file-contents fname)
951 (mailcap-replace-regexp "#.*" "")
952 (mailcap-replace-regexp "\n+" "\n")
953 (mailcap-replace-regexp "[ \t]+$" "")
954 (goto-char (point-max))
955 (skip-chars-backward " \t\n")
956 (delete-region (point) (point-max))
957 (goto-char (point-min))
958 (while (not (eobp))
959 (skip-chars-forward " \t\n")
960 (setq save-pos (point))
961 (skip-chars-forward "^ \t\n")
962 (downcase-region save-pos (point))
963 (setq type (buffer-substring save-pos (point)))
964 (while (not (eolp))
965 (skip-chars-forward " \t")
966 (setq save-pos (point))
967 (skip-chars-forward "^ \t\n")
968 (setq extns (cons (buffer-substring save-pos (point)) extns)))
969 (while extns
970 (setq mailcap-mime-extensions
971 (cons
972 (cons (if (= (string-to-char (car extns)) ?.)
973 (car extns)
974 (concat "." (car extns))) type)
975 mailcap-mime-extensions)
976 extns (cdr extns)))))))
977
978(defun mailcap-extension-to-mime (extn)
979 "Return the MIME content type of the file extensions EXTN."
980 (mailcap-parse-mimetypes)
981 (if (and (stringp extn)
982 (not (eq (string-to-char extn) ?.)))
983 (setq extn (concat "." extn)))
984 (cdr (assoc (downcase extn) mailcap-mime-extensions)))
985
c0393b5e
DL
986;; Unused?
987(defalias 'mailcap-command-p 'executable-find)
c113de23
GM
988
989(defun mailcap-mime-types ()
990 "Return a list of MIME media types."
991 (mailcap-parse-mimetypes)
b890d447 992 (mailcap-delete-duplicates
9b198954
DL
993 (nconc
994 (mapcar 'cdr mailcap-mime-extensions)
995 (apply
996 'nconc
997 (mapcar
998 (lambda (l)
999 (delq nil
1000 (mapcar
1001 (lambda (m)
1002 (let ((type (cdr (assq 'type (cdr m)))))
1003 (if (equal (cadr (split-string type "/"))
1004 "*")
1005 nil
1006 type)))
1007 (cdr l))))
1008 mailcap-mime-data)))))
c113de23 1009
a837fe74
JL
1010;;;
1011;;; Useful supplementary functions
1012;;;
1013
1014(defun mailcap-file-default-commands (files)
1015 "Return a list of default commands for FILES."
1016 (mailcap-parse-mailcaps)
1017 (mailcap-parse-mimetypes)
1018 (let* ((all-mime-type
1019 ;; All unique MIME types from file extensions
1020 (delete-dups (mapcar (lambda (file)
1021 (mailcap-extension-to-mime
1022 (file-name-extension file t)))
1023 files)))
1024 (all-mime-info
1025 ;; All MIME info lists
1026 (delete-dups (mapcar (lambda (mime-type)
1027 (mailcap-mime-info mime-type 'all))
1028 all-mime-type)))
1029 (common-mime-info
1030 ;; Intersection of mime-infos from different mime-types;
1031 ;; or just the first MIME info for a single MIME type
1032 (if (cdr all-mime-info)
1033 (delq nil (mapcar (lambda (mi1)
1034 (unless (memq nil (mapcar
1035 (lambda (mi2)
1036 (member mi1 mi2))
1037 (cdr all-mime-info)))
1038 mi1))
1039 (car all-mime-info)))
1040 (car all-mime-info)))
1041 (commands
1042 ;; Command strings from `viewer' field of the MIME info
1043 (delete-dups
1044 (delq nil (mapcar (lambda (mime-info)
1045 (let ((command (cdr (assoc 'viewer mime-info))))
1046 (if (stringp command)
1047 (replace-regexp-in-string
1048 ;; Replace mailcap's `%s' placeholder
1049 ;; with dired's `?' placeholder
1050 "%s" "?"
1051 (replace-regexp-in-string
1052 ;; Remove the final filename placeholder
1053 "\s*\\('\\)?%s\\1?\s*\\'" "" command nil t)
1054 nil t))))
1055 common-mime-info)))))
1056 commands))
1057
c113de23
GM
1058(provide 'mailcap)
1059
cbee283d 1060;; arch-tag: 1fd4f9c9-c305-4d2e-9747-3a4d45baa0bd
c113de23 1061;;; mailcap.el ends here