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