This patch against Emacs 22 tidies up mailcap and defines a command
`mailcap-view-external' which applies the mailcap rules to view a
file.  Most of the viewer associations have been removed from
mailcap.el.  These should be taken from the system's mailcap file,
which makes them consistent with other programs and more likely to use
programs which are actually present on the system (especially on a
Debian system).  The viewer list defined in Lisp should basically
comprise Lisp functions and user customizations which override the
system definitions for some reason.  The list is now customizable.
The extensions list is treated similarly.

dired-view-file then uses mailcap-view-external.

2005-09-18  Dave Love  <fx@gnu.org>

	* dired.el (dired-mouse-find-file-other-window, dired-view-file):
	Use mailcap-view-external.

	* gnus/mailcap.el (mailcap-custom-mime-data): New.  Value mainly copied
	from the old `mailcap-mime-data' with non-Emacs-specific entries
	removed.  Make it risky-local.
	(mailcap-mime-data): Initialize to nil.
	(mailcap-parse-mailcaps, mailcap-parse-mailcap-extras)
	(mailcap-add-mailcap-entry, mailcap-viewer-lessp)
	(mailcap-parse-mimetypes, mailcap-parse-mimetype-file)
	(mailcap-mime-types): Simplify.
	(mailcap-mailcap-entry-passes-test): Change needs-terminal test.
	Actually run the test in sh (per metamail).
	(mailcap-unescape-mime-test): Use mailcap-fill-escapes.
	(mailcap-mime-info): Use mailcap-custom-mime-data.  Use
	executable-find on command.
	(mailcap-mime-extensions): Customize and remove most entries.
	(mailcap-mime-type, mailcap-viewer, mailcap-fill-escapes)
	(mailcap-view-external): New.

Index: dired.el
===================================================================
RCS file: /cvsroot/emacs/emacs/lisp/dired.el,v
retrieving revision 1.328
diff -u -p -r1.328 dired.el
--- dired.el	24 Sep 2005 23:26:28 -0000	1.328
+++ dired.el	8 Oct 2005 22:01:49 -0000
@@ -1693,20 +1693,26 @@ Creates a buffer if necessary."
 	    (progn
 	      (select-window window)
 	      (dired-other-window file)))
-      (select-window window)
-      (find-file-other-window (file-name-sans-versions file t)))))
+      ;; Look for some other way to view a certain file.
+      (unless (mailcap-view-external file)
+	(select-window window)
+	(find-file-other-window (file-name-sans-versions file t))))))
 
 (defun dired-view-file ()
   "In Dired, examine a file in view mode, returning to Dired when done.
 When file is a directory, show it in this buffer if it is inserted.
-Otherwise, display it in another buffer."
+The file may be displayed using an external viewer defined by the MIME
+mailcap mechanism on the basis of its extension.  Otherwise, display it
+in another buffer."
   (interactive)
   (let ((file (dired-get-file-for-visit)))
     (if (file-directory-p file)
 	(or (and (cdr dired-subdir-alist)
 		 (dired-goto-subdir file))
 	    (dired file))
-      (view-file file))))
+      ;; Look for some other way to view a certain file.
+      (unless (mailcap-view-external file)
+	(view-file file)))))
 
 (defun dired-find-file-other-window ()
   "In Dired, visit this file or directory in another window."
Index: gnus/mailcap.el
===================================================================
RCS file: /cvsroot/emacs/emacs/lisp/gnus/mailcap.el,v
retrieving revision 1.13
diff -u -p -r1.13 mailcap.el
--- gnus/mailcap.el	6 Aug 2005 19:51:42 -0000	1.13
+++ gnus/mailcap.el	8 Oct 2005 22:01:49 -0000
@@ -48,7 +48,7 @@
     (modify-syntax-entry ?{ "(" table)
     (modify-syntax-entry ?} ")" table)
     table)
-  "A syntax table for parsing SGML attributes.")
+  "A syntax table for parsing mailcap attributes.")
 
 (eval-and-compile
   (when (featurep 'xemacs)
@@ -68,16 +68,9 @@
 	     " ")
   "Shell command (including switches) used to print Postscript files.")
 
-;; Postpone using defcustom for this as it's so big and we essentially
-;; have to have two copies of the data around then.  Perhaps just
-;; customize the Lisp viewers and rely on the normal configuration
-;; files for the rest?  -- fx
-(defvar mailcap-mime-data
+(defcustom mailcap-custom-mime-data
   `(("application"
-     ("vnd.ms-excel"
-      (viewer . "gnumeric %s")
-      (test   . (getenv "DISPLAY"))
-      (type . "application/vnd.ms-excel"))
+     ;; Fixme: These ssl functions not defined, even in old ssl.el.
      ("x-x509-ca-cert"
       (viewer . ssl-view-site-cert)
       (test . (fboundp 'ssl-view-site-cert))
@@ -90,24 +83,13 @@
       (viewer . mailcap-save-binary-file)
       (non-viewer . t)
       (type . "application/octet-stream"))
-     ("dvi"
-      (viewer . "xdvi -safer %s")
-      (test   . (eq window-system 'x))
-      ("needsx11")
-      (type   . "application/dvi")
-      ("print" . "dvips -qRP %s"))
-     ("dvi"
-      (viewer . "dvitty %s")
-      (test   . (not (getenv "DISPLAY")))
-      (type   . "application/dvi")
-      ("print" . "dvips -qRP %s"))
      ("emacs-lisp"
       (viewer . mailcap-maybe-eval)
       (type   . "application/emacs-lisp"))
      ("x-emacs-lisp"
       (viewer . mailcap-maybe-eval)
       (type   . "application/x-emacs-lisp"))
-     ("x-tar"
+     ("x-tar"				; Fixme: why not tar-mode?
       (viewer . mailcap-save-binary-file)
       (non-viewer . t)
       (type   . "application/x-tar"))
@@ -132,63 +114,16 @@
       (test   . (fboundp 'texinfo-mode))
       (type   . "application/tex"))
      ("zip"
-      (viewer . mailcap-save-binary-file)
+      (viewer . mailcap-save-binary-file) ; Fixme: why not archive-mode?
       (non-viewer . t)
       (type   . "application/zip")
       ("copiousoutput"))
-     ("pdf"
-      (viewer . "gv -safer %s")
-      (type . "application/pdf")
-      (test . window-system)
-      ("print" . ,(concat "pdf2ps %s - | " mailcap-print-command)))
-     ("pdf"
-      (viewer . "gpdf %s")
-      (type . "application/pdf")
-      ("print" . ,(concat "pdftops %s - | " mailcap-print-command))
-      (test . (eq window-system 'x)))
-     ("pdf"
-      (viewer . "xpdf %s")
-      (type . "application/pdf")
-      ("print" . ,(concat "pdftops %s - | " mailcap-print-command))
-      (test . (eq window-system 'x)))
-     ("pdf"
-      (viewer . ,(concat "pdftotext %s -"))
-      (type   . "application/pdf")
-      ("print" . ,(concat "pdftops %s - | " mailcap-print-command))
-      ("copiousoutput"))
-     ("postscript"
-      (viewer . "gv -safer %s")
-      (type . "application/postscript")
-      (test   . window-system)
-      ("print" . ,(concat mailcap-print-command " %s"))
-      ("needsx11"))
-     ("postscript"
-      (viewer . "ghostview -dSAFER %s")
-      (type . "application/postscript")
-      (test   . (eq window-system 'x))
-      ("print" . ,(concat mailcap-print-command " %s"))
-      ("needsx11"))
-     ("postscript"
-      (viewer . "ps2ascii %s")
-      (type . "application/postscript")
-      (test . (not (getenv "DISPLAY")))
-      ("print" . ,(concat mailcap-print-command " %s"))
-      ("copiousoutput"))
      ("sieve"
       (viewer . sieve-mode)
       (test   . (fboundp 'sieve-mode))
-      (type   . "application/sieve"))
-     ("pgp-keys"
-      (viewer . "gpg --import --interactive --verbose")
-      (type   . "application/pgp-keys")
-      ("needsterminal")))
-    ("audio"
-     ("x-mpeg"
-      (viewer . "maplay %s")
-      (type   . "audio/x-mpeg"))
-     (".*"
-      (viewer . "showaudio")
-      (type   . "audio/*")))
+      (type   . "application/sieve")))
+    ("audio")
+    ;; Fixme: define internal audio `viewers' for Emacsen which can do sound.
     ("message"
      ("rfc-*822"
       (viewer . mm-view-message)
@@ -207,34 +142,9 @@
       (viewer . view-mode)
       (type   . "message/rfc822")))
     ("image"
-     ("x-xwd"
-      (viewer  . "xwud -in %s")
-      (type    . "image/x-xwd")
-      ("compose" . "xwd -frame > %s")
-      (test    . (eq window-system 'x))
-      ("needsx11"))
-     ("x11-dump"
-      (viewer . "xwud -in %s")
-      (type . "image/x-xwd")
-      ("compose" . "xwd -frame > %s")
-      (test   . (eq window-system 'x))
-      ("needsx11"))
-     ("windowdump"
-      (viewer . "xwud -in %s")
-      (type . "image/x-xwd")
-      ("compose" . "xwd -frame > %s")
-      (test   . (eq window-system 'x))
-      ("needsx11"))
-     (".*"
-      (viewer . "display %s")
-      (type . "image/*")
-      (test   . (eq window-system 'x))
-      ("needsx11"))
-     (".*"
-      (viewer . "ee %s")
-      (type . "image/*")
-      (test   . (eq window-system 'x))
-      ("needsx11")))
+     ;; Fixme: define internal graphics viewer(s) for when we can
+     ;; display images.
+     )
     ("text"
      ("plain"
       (viewer  . w3-mode)
@@ -242,39 +152,31 @@
       (type    . "text/plain"))
      ("plain"
       (viewer  . view-mode)
-      (test    . (fboundp 'view-mode))
-      (type    . "text/plain"))
-     ("plain"
-      (viewer  . fundamental-mode)
       (type    . "text/plain"))
      ("enriched"
       (viewer . enriched-decode)
       (test   . (fboundp 'enriched-decode))
       (type   . "text/enriched"))
+     ;; Fixme: use browse-url-of-file?
      ("html"
       (viewer . mm-w3-prepare-buffer)
       (test   . (fboundp 'w3-prepare-buffer))
       (type   . "text/html")))
-    ("video"
-     ("mpeg"
-      (viewer . "mpeg_play %s")
-      (type   . "video/mpeg")
-      (test   . (eq window-system 'x))
-      ("needsx11")))
-    ("x-world"
-     ("x-vrml"
-      (viewer  . "webspace -remote %s -URL %u")
-      (type    . "x-world/x-vrml")
-      ("description"
-       "VRML document")))
     ("archive"
      ("tar"
       (viewer . tar-mode)
       (type . "archive/tar")
       (test . (fboundp 'tar-mode)))))
-  "The mailcap structure is an assoc list of assoc lists.
-1st assoc list is keyed on the major content-type
-2nd assoc list is keyed on the minor content-type (which can be a regexp)
+  "Customization of mailcap entries.
+Overrides RFC 1524-style configuration in `mailcap-mime-info'.
+
+Normally just contains entries for internal (Lisp-implemented) viewers.
+Can also specify external viewers, but they are probably best put in
+your ~/.mailcap file; see mailcap(5).
+
+The mailcap structure is an alist of alists.
+1st alist is keyed on the major content-type
+2nd alist is keyed on the minor content-type (which can be a regexp)
 
 Which looks like:
 -----------------
@@ -283,13 +185,14 @@ Which looks like:
   (\"text\"
    (\"plain\" . <info>)))
 
-Where <info> is another assoc list of the various information
+Where <info> is another alist of the various information
 related to the mailcap RFC 1524.  This is keyed on the lowercase
 attribute name (viewer, test, etc).  This looks like:
  ((viewer . VIEWERINFO)
   (test   . TESTINFO)
   (xxxx   . \"STRING\")
-  FLAG)
+  (non-viewer . t)
+  (FLAG))
 
 Where VIEWERINFO specifies how the content-type is viewed.  Can be
 a string, in which case it is run through a shell, with
@@ -303,7 +206,20 @@ Content-Type header as argument to retur
 validity.  Otherwise, if it is a non-function Lisp symbol or list
 whose car is a symbol, it is `eval'led to yield the validity.  If it
 is a string or list of strings, it represents a shell command to run
-to return a true or false shell value for the validity.")
+to return a true or false shell value for the validity.
+
+A `non-viewer' element indicates that VIEWERINFO is not actually a
+shell command viewer, but rather used to save the file, for instance."
+  :version "22.1"
+  :group 'mailcap
+  :type '(alist :key-type (string :tag "Major type")
+		:value-type (alist :key-type (string :tag "Minor type")
+				   :value-type alist)))
+(put 'mailcap-custom-mime-data 'risky-local-variable t)
+
+(defvar mailcap-mime-data nil
+  "Compilation of mailcap entries extracted from RFC 1524-style files.
+See `mailcap-custom-mime-data' for the format.")
 (put 'mailcap-mime-data 'risky-local-variable t)
 
 (defcustom mailcap-download-directory nil
@@ -356,11 +272,11 @@ Gnus might fail to display all of it.")
 					    "*Warning*"))))
 	    (unwind-protect
 		(with-current-buffer buffer
-		  (insert (substitute-command-keys
-			   mailcap-maybe-eval-warning))
+		  (insert (substitute-command-keys mailcap-maybe-eval-warning))
 		  (goto-char (point-min))
 		  (display-buffer buffer)
-		  (yes-or-no-p "This is potentially dangerous emacs-lisp code, evaluate it? "))
+		  (yes-or-no-p "\
+This is potentially dangerous emacs-lisp code.  Evaluate it? "))
 	      (kill-buffer buffer))))
       (eval-buffer (current-buffer)))
     (when (buffer-live-p lisp-buffer)
@@ -400,17 +316,13 @@ MAILCAPS if set; otherwise (on Unix) use
 	      ;; with /usr before /usr/local.
 	      '("~/.mailcap" "/etc/mailcap" "/usr/etc/mailcap"
 		"/usr/local/etc/mailcap"))))
-    (let ((fnames (reverse
-		   (if (stringp path)
-		       (delete "" (split-string path path-separator))
-		     path)))
-	  fname)
-      (while fnames
-	(setq fname (car fnames))
+    (let ((fnames (reverse (if (stringp path)
+			       (delete "" (split-string path path-separator))
+			     path))))
+      (dolist (fname fnames)
 	(if (and (file-readable-p fname)
 		 (file-regular-p fname))
-	    (mailcap-parse-mailcap fname))
-	(setq fnames (cdr fnames))))
+	    (mailcap-parse-mailcap fname))))
       (setq mailcap-parsed-p t)))
 
 (defun mailcap-parse-mailcap (fname)
@@ -492,7 +404,7 @@ MAILCAPS if set; otherwise (on Unix) use
   (let (
 	name				; From name=
 	value				; its value
-	results				; Assoc list of results
+	results				; Alist of results
 	name-pos			; Start of XXXX= position
 	val-pos				; Start of value position
 	done				; Found end of \'d ;s?
@@ -528,35 +440,29 @@ MAILCAPS if set; otherwise (on Unix) use
 		    (skip-chars-forward ";"))
 		(setq done t))))
 	  (setq	value (buffer-substring val-pos (point))))
-	(setq results (cons (cons name value) results))
+	(push (cons name value) results)
 	(skip-chars-forward " \";\n\t"))
       results)))
 
 (defun mailcap-mailcap-entry-passes-test (info)
   "Return non-nil iff mailcap entry INFO passes its test clause.
-Also return non-nil if no test clause is present."
-  (let ((test (assq 'test info))	; The test clause
-	status)
-    (setq status (and test (split-string (cdr test) " ")))
-    (if (and (or (assoc "needsterm" info)
-		 (assoc "needsterminal" info)
-		 (assoc "needsx11" info))
-	     (not (getenv "DISPLAY")))
-	(setq status nil)
-      (cond
-       ((and (equal (nth 0 status) "test")
-	     (equal (nth 1 status) "-n")
-	     (or (equal (nth 2 status) "$DISPLAY")
-		 (equal (nth 2 status) "\"$DISPLAY\"")))
-	(setq status (if (getenv "DISPLAY") t nil)))
-       ((and (equal (nth 0 status) "test")
-	     (equal (nth 1 status) "-z")
-	     (or (equal (nth 2 status) "$DISPLAY")
-		 (equal (nth 2 status) "\"$DISPLAY\"")))
-	(setq status (if (getenv "DISPLAY") nil t)))
-       (test nil)
-       (t nil)))
-    (and test (listp test) (setcdr test status))))
+Takes into account `needsterm', `needsterminal' and `needsx11' too.
+Also return non-nil if no relevant clauses are present.
+Note that this can run shell code from mailcap, so the mailcap contents
+must be sane"
+  (let ((test (assq 'test info)))
+    ;; Don't accept needsterminal, e.g. text/html entries which run
+    ;; `more' in xterm.
+    (unless (or (assoc "needsterm" info)
+		(assoc "needsterminal" info)
+		(and (assoc "needsx11" info)
+		     (or (not (eq window-system 'x))
+			 (not (getenv "DISPLAY")))))
+      (if (null test)
+	  t
+	(let ((shell-file-name "/bin/sh"))
+	  (with-temp-buffer
+	    (eq 0 (shell-command test t t))))))))
 
 ;;;
 ;;; The action routines.
@@ -576,52 +482,21 @@ Also return non-nil if no test clause is
     (nconc exact wildcard)))
 
 (defun mailcap-unescape-mime-test (test type-info)
-  (let (save-pos save-chr subst)
-    (cond
-     ((symbolp test) test)
-     ((and (listp test) (symbolp (car test))) test)
-     ((or (stringp test)
-	  (and (listp test) (stringp (car test))
-	       (setq test (mapconcat 'identity test " "))))
-      (with-temp-buffer
-	(insert test)
-	(goto-char (point-min))
-	(while (not (eobp))
-	  (skip-chars-forward "^%")
-	  (if (/= (- (point)
-		     (progn (skip-chars-backward "\\\\")
-			    (point)))
-		  0)			; It is an escaped %
-	      (progn
-		(delete-char 1)
-		(skip-chars-forward "%."))
-	    (setq save-pos (point))
-	    (skip-chars-forward "%")
-	    (setq save-chr (char-after (point)))
-	    ;; Escapes:
-	    ;; %s: name of a file for the body data
-	    ;; %t: content-type
-	    ;; %{<parameter name}: value of parameter in mailcap entry
-	    ;; %n: number of sub-parts for multipart content-type
-	    ;; %F: a set of content-type/filename pairs for multiparts
-	    (cond
-	     ((null save-chr) nil)
-	     ((= save-chr ?t)
-	      (delete-region save-pos (progn (forward-char 1) (point)))
-	      (insert (or (cdr (assq 'type type-info)) "\"\"")))
-	     ((memq save-chr '(?M ?n ?F))
-	      (delete-region save-pos (progn (forward-char 1) (point)))
-	      (insert "\"\""))
-	     ((= save-chr ?{)
-	      (forward-char 1)
-	      (skip-chars-forward "^}")
-	      (downcase-region (+ 2 save-pos) (point))
-	      (setq subst (buffer-substring (+ 2 save-pos) (point)))
-	      (delete-region save-pos (1+ (point)))
-	      (insert (or (cdr (assoc subst type-info)) "\"\"")))
-	     (t nil))))
-	(buffer-string)))
-     (t (error "Bad value to mailcap-unescape-mime-test: %s" test)))))
+  (cond
+   ((symbolp test) test)
+   ((and (listp test) (symbolp (car test))) test)
+   ((or (stringp test)
+	(and (listp test) (stringp (car test))
+	     (setq test (mapconcat 'identity test " "))))
+    ;; Escapes:
+    ;; %s: name of a file for the body data
+    ;; %t: content-type
+    ;; %{<parameter name}: value of parameter in mailcap entry
+    ;; %n: number of sub-parts for multipart content-type (ignored)
+    ;; %F: a set of content-type/filename pairs for multiparts (ignored)
+    (mailcap-fill-escapes test nil (or (cdr (assq 'type type-info)) "")
+			  type-info))
+   (t (error "Bad value to mailcap-unescape-mime-test: %s" test))))
 
 (defvar mailcap-viewer-test-cache nil)
 
@@ -663,22 +538,20 @@ to supply to the test."
 (defun mailcap-add-mailcap-entry (major minor info)
   (let ((old-major (assoc major mailcap-mime-data)))
     (if (null old-major)		; New major area
-	(setq mailcap-mime-data
-	      (cons (cons major (list (cons minor info)))
-		    mailcap-mime-data))
-      (let ((cur-minor (assoc minor old-major)))
-	(cond
-	 ((or (null cur-minor)		; New minor area, or
-	      (assq 'test info))	; Has a test, insert at beginning
-	  (setcdr old-major (cons (cons minor info) (cdr old-major))))
-	 ((and (not (assq 'test info))	; No test info, replace completely
-	       (not (assq 'test cur-minor))
+	(push (cons major (list (cons minor info)))
+	      mailcap-mime-data)
+       (let ((cur-minor (assoc minor old-major)))
+ 	(cond
+ 	 ((or (null cur-minor)		; New minor area, or
+ 	      (assq 'test info))	; Has a test, insert at beginning
+ 	  (setcdr old-major (cons (cons minor info) (cdr old-major))))
+ 	 ((and (not (assq 'test info))	; No test info, replace completely
+ 	       (not (assq 'test cur-minor))
 	       (equal (assq 'viewer info)  ; Keep alternative viewer
 		      (assq 'viewer cur-minor)))
-	  (setcdr cur-minor info))
-	 (t
-	  (setcdr old-major (cons (cons minor info) (cdr old-major))))))
-      )))
+ 	  (setcdr cur-minor info))
+ 	 (t
+ 	  (setcdr old-major (cons (cons minor info) (cdr old-major)))))))))
 
 (defun mailcap-add (type viewer &optional test)
   "Add VIEWER as a handler for TYPE.
@@ -709,8 +582,7 @@ If TEST is not given, it defaults to t."
      ((and (not x-wild) y-wild)
       t)
      ((and (not y-lisp) x-lisp)
-      t)
-     (t nil))))
+      t))))
 
 (defun mailcap-mime-info (string &optional request)
   "Get the MIME viewer command for STRING, return nil if none found.
@@ -722,7 +594,7 @@ entry) will be returned.  If it is a str
 corresponding to that string will be returned (print, description,
 whatever).  If a number, then all the information for this specific
 viewer is returned.  If `all', then all possible viewers for
-this type is returned."
+this type are returned."
   (let (
 	major				; Major encoding (text, etc)
 	minor				; Minor encoding (html, etc)
@@ -740,14 +612,23 @@ this type is returned."
       (setq major (split-string (car ctl) "/"))
       (setq minor (cadr major)
 	    major (car major))
-      (when (setq major-info (cdr (assoc major mailcap-mime-data)))
+      (when (setq major-info
+		  (append (cdr (assoc major mailcap-custom-mime-data))
+			  (cdr (assoc major mailcap-mime-data))))
 	(when (setq viewers (mailcap-possible-viewers major-info minor))
 	  (setq info (mapcar (lambda (a) (cons (symbol-name (car a))
 					       (cdr a)))
 			     (cdr ctl)))
 	  (while viewers
 	    (if (mailcap-viewer-passes-test (car viewers) info)
-		(setq passed (cons (car viewers) passed)))
+		(let ((command (cdr (assq 'viewer viewer))))
+		  ;; Don't use viewers referencing programs we can't
+		  ;; find.  (Do this here rather than when parsing
+		  ;; mailcap so that we can install a viewer and have
+		  ;; it work later.)
+		  (if (or (not (stringp command))
+			  (executable-find command))
+		      (setq passed (cons (car viewers) passed)))))
 	    (setq viewers (cdr viewers)))
 	  (setq passed (sort passed 'mailcap-viewer-lessp))
 	  (setq viewer (car passed))))
@@ -760,8 +641,7 @@ this type is returned."
        ((or (null request) (equal request ""))
 	(mailcap-unescape-mime-test (cdr (assq 'viewer viewer)) info))
        ((stringp request)
-	(mailcap-unescape-mime-test
-	 (cdr-safe (assoc request viewer)) info))
+	(mailcap-unescape-mime-test (cdr-safe (assoc request viewer)) info))
        ((eq request 'all)
 	passed)
        (t
@@ -777,103 +657,15 @@ this type is returned."
 ;;; Experimental MIME-types parsing
 ;;;
 
-(defvar mailcap-mime-extensions
-  '((""        . "text/plain")
-    (".abs"   . "audio/x-mpeg")
-    (".aif"   . "audio/aiff")
-    (".aifc"  . "audio/aiff")
-    (".aiff"  . "audio/aiff")
-    (".ano"   . "application/x-annotator")
-    (".au"    . "audio/ulaw")
-    (".avi"   . "video/x-msvideo")
-    (".bcpio" . "application/x-bcpio")
-    (".bin"   . "application/octet-stream")
-    (".cdf"   . "application/x-netcdr")
-    (".cpio"  . "application/x-cpio")
-    (".csh"   . "application/x-csh")
-    (".css"   . "text/css")
-    (".dvi"   . "application/x-dvi")
-    (".diff"  . "text/x-patch")
-    (".el"    . "application/emacs-lisp")
-    (".eps"   . "application/postscript")
-    (".etx"   . "text/x-setext")
-    (".exe"   . "application/octet-stream")
-    (".fax"   . "image/x-fax")
-    (".gif"   . "image/gif")
-    (".hdf"   . "application/x-hdf")
-    (".hqx"   . "application/mac-binhex40")
-    (".htm"   . "text/html")
-    (".html"  . "text/html")
-    (".icon"  . "image/x-icon")
-    (".ief"   . "image/ief")
-    (".jpg"   . "image/jpeg")
-    (".macp"  . "image/x-macpaint")
-    (".man"   . "application/x-troff-man")
-    (".me"    . "application/x-troff-me")
-    (".mif"   . "application/mif")
-    (".mov"   . "video/quicktime")
-    (".movie" . "video/x-sgi-movie")
-    (".mp2"   . "audio/x-mpeg")
-    (".mp3"   . "audio/x-mpeg")
-    (".mp2a"  . "audio/x-mpeg2")
-    (".mpa"   . "audio/x-mpeg")
-    (".mpa2"  . "audio/x-mpeg2")
-    (".mpe"   . "video/mpeg")
-    (".mpeg"  . "video/mpeg")
-    (".mpega" . "audio/x-mpeg")
-    (".mpegv" . "video/mpeg")
-    (".mpg"   . "video/mpeg")
-    (".mpv"   . "video/mpeg")
-    (".ms"    . "application/x-troff-ms")
-    (".nc"    . "application/x-netcdf")
-    (".nc"    . "application/x-netcdf")
-    (".oda"   . "application/oda")
-    (".patch" . "text/x-patch")
-    (".pbm"   . "image/x-portable-bitmap")
-    (".pdf"   . "application/pdf")
-    (".pgm"   . "image/portable-graymap")
-    (".pict"  . "image/pict")
-    (".png"   . "image/png")
-    (".pnm"   . "image/x-portable-anymap")
-    (".ppm"   . "image/portable-pixmap")
-    (".ps"    . "application/postscript")
-    (".qt"    . "video/quicktime")
-    (".ras"   . "image/x-raster")
-    (".rgb"   . "image/x-rgb")
-    (".rtf"   . "application/rtf")
-    (".rtx"   . "text/richtext")
-    (".sh"    . "application/x-sh")
-    (".sit"   . "application/x-stuffit")
-    (".siv"   . "application/sieve")
-    (".snd"   . "audio/basic")
-    (".src"   . "application/x-wais-source")
-    (".tar"   . "archive/tar")
-    (".tcl"   . "application/x-tcl")
-    (".tex"   . "application/x-tex")
-    (".texi"  . "application/texinfo")
-    (".tga"   . "image/x-targa")
-    (".tif"   . "image/tiff")
-    (".tiff"  . "image/tiff")
-    (".tr"    . "application/x-troff")
-    (".troff" . "application/x-troff")
-    (".tsv"   . "text/tab-separated-values")
-    (".txt"   . "text/plain")
-    (".vbs"   . "video/mpeg")
-    (".vox"   . "audio/basic")
-    (".vrml"  . "x-world/x-vrml")
-    (".wav"   . "audio/x-wav")
-    (".xls"   . "application/vnd.ms-excel")
-    (".wrl"   . "x-world/x-vrml")
-    (".xbm"   . "image/xbm")
-    (".xpm"   . "image/xpm")
-    (".xwd"   . "image/windowdump")
-    (".zip"   . "application/zip")
-    (".ai"    . "application/postscript")
-    (".jpe"   . "image/jpeg")
-    (".jpeg"  . "image/jpeg"))
+(defcustom mailcap-mime-extensions
+  '((""        . "text/plain"))
   "An alist of file extensions and corresponding MIME content-types.
 This exists for you to customize the information in Lisp.  It is
-merged with values from mailcap files by `mailcap-parse-mimetypes'.")
+merged with values from mailcap files by `mailcap-parse-mimetypes'."
+  :group 'mailcap
+  :version "22.1"
+  :type '(alist :key-type (string :tag "Extension")
+		:value-type (string :tag "MIME type")))
 
 (defvar mailcap-mimetypes-parsed-p nil)
 
@@ -907,13 +699,10 @@ If FORCE, re-parse even if already parse
 		"/usr/local/www/conf/mime-types"))))
     (let ((fnames (reverse (if (stringp path)
 			       (delete "" (split-string path path-separator))
-			     path)))
-	  fname)
-      (while fnames
-	(setq fname (car fnames))
+			     path))))
+      (dolist (fname fnames)
 	(if (and (file-readable-p fname))
-	    (mailcap-parse-mimetype-file fname))
-	(setq fnames (cdr fnames))))
+	    (mailcap-parse-mimetype-file fname))))
     (setq mailcap-mimetypes-parsed-p t)))
 
 (defun mailcap-parse-mimetype-file (fname)
@@ -943,16 +732,16 @@ If FORCE, re-parse even if already parse
 	  (skip-chars-forward "^ \t\n")
 	  (setq extns (cons (buffer-substring save-pos (point)) extns)))
 	(while extns
-	  (setq mailcap-mime-extensions
-		(cons
-		 (cons (if (= (string-to-char (car extns)) ?.)
-			   (car extns)
-			 (concat "." (car extns))) type)
-		 mailcap-mime-extensions)
-		extns (cdr extns)))))))
+	  (push
+	   (cons (if (= (string-to-char (car extns)) ?.)
+		     (car extns)
+		   (concat "." (car extns)))
+		 type)
+	   mailcap-mime-extensions)
+	  (setq extns (cdr extns)))))))
 
 (defun mailcap-extension-to-mime (extn)
-  "Return the MIME content type of the file extensions EXTN."
+  "Return the MIME content type of the file extension EXTN."
   (mailcap-parse-mimetypes)
   (if (and (stringp extn)
 	   (not (eq (string-to-char extn) ?.)))
@@ -966,22 +755,129 @@ If FORCE, re-parse even if already parse
   "Return a list of MIME media types."
   (mailcap-parse-mimetypes)
   (mm-delete-duplicates
-   (nconc
-    (mapcar 'cdr mailcap-mime-extensions)
-    (apply
-     'nconc
-     (mapcar
-      (lambda (l)
-	(delq nil
-	      (mapcar
-	       (lambda (m)
-		 (let ((type (cdr (assq 'type (cdr m)))))
-		   (if (equal (cadr (split-string type "/"))
-			      "*")
-		       nil
-		     type)))
-	       (cdr l))))
-      mailcap-mime-data)))))
+   (nconc (mapcar 'cdr mailcap-mime-extensions)
+	  (apply 'nconc
+		 (mapcar
+		  (lambda (l)
+		    (mapcar
+		     (lambda (m)
+		       (let ((type (cdr (assq 'type (cdr m)))))
+			 (unless (equal (cadr (split-string type "/"))
+					"*")
+			   type)))
+		     (cdr l)))
+		  mailcap-mime-data)))))
+
+(defun mailcap-mime-type (file)
+  "Return the MIME type corresponding to the extension of FILE."
+  (let ((ext (file-name-extension file))) ; copes with backup names
+    (if ext (mailcap-extension-to-mime ext))))
+
+;;;###autoload
+(defun mailcap-viewer (file)
+  "Return an external viewer command for the given FILE.
+This is derived from the MIME mailcap and mime.types
+configuration files.  It will have a `%s' placeholder for the
+file name in the command.  Return nil if there is no viewer
+defined, or if it is a Lisp function rather than a command string.
+
+If the file is regarded as `encoded', the return value is a list
+of the command to view the decoded file and it MIME type.  Here,
+`encoded' currently means compressed in a way which
+Auto-compression mode understands and that Auto-compression mode
+is on.  In this case, viewing code is expected to use `copy-file'
+to make an uncompressed temporary file to view."
+  (mailcap-parse-mailcaps)
+  (let* ((encoded (when (eq 'jka-compr-handler
+			    (find-file-name-handler file 'copy-file))
+		    (setq file (file-name-sans-extension file))))
+	 (type (mailcap-mime-type file))
+	 (viewer (if (stringp type)
+		     (mailcap-mime-info type)))
+	 %s case-fold-search)
+    (if (and viewer encoded)
+	(list viewer type)
+      viewer)))
+
+(defun mailcap-fill-escapes (viewer file type params)
+  "Substitute escape sequences in the MIME VIEWER string for displaying FILE.
+TYPE is the MIME type, like `text/plain', e.g. returned by
+`mailcap-mime-type'.  PARAMS is an alist of MIME parameters and
+values, indexed by interned symbols naming the parameters;
+normally it will be nil.
+
+TYPE may differ from the result of applying `mailcap-mime-type'
+to FILE is the original file was encoded (compressed) and was
+unencoded to a temporary file.
+
+VIEWER may also be a mailcap test string, for instance, not
+necessarily actually a viewer.  In that case, FILE is nil, and
+`%s' is treated literally."
+  (let* (%s case-fold-search
+	 (result
+	  (replace-regexp-in-string
+	   (if file
+	       "%s\\|'%s'\\|\"%s\"\\|%{\\([^}]+\\)}\\|%t\\|%%\\|\\\\.\\|%."
+	     "%{\\([^}]+\\)}\\|%t\\|%%\\|\\\\.\\|%.")
+	   (lambda (match)
+	     (cond
+	      ((string= match "%%") "\\%")
+	      ((or (string= match "%s")
+		   ;; Some mailcaps, e.g. Debian, erroneously quote fields.
+		   (string= match "'%s'")
+		   (string= match "\"%s\""))
+	       (if file
+		   (shell-quote-argument (setq %s file))
+		 "%s"))
+	      ((string= match "%t") (shell-quote-argument type))
+	      ((eq ?\\ (aref match 0))	; any escaped char
+	       (shell-quote-argument (substring match 1)))
+	      ((match-beginning 1)	; parameter %{...}
+	       (shell-quote-argument (or (cdr (assq (match-string 1) params))
+					 "")))
+	      (t			; else invalid escape
+	       "")))
+	   viewer t t)))
+    (if (or %s (= 0 (length file)))
+	result
+      (concat result " <" (shell-quote-argument file)))))
+
+;;;###autoload
+(defun mailcap-view-external (file)
+  "Run any appropriate external MIME viewer defined for FILE.
+Return nil if no such viewer is defined, otherwise return t."
+  (let* ((cmd (mailcap-viewer file))
+	 (type (if (listp cmd)
+		   (cadr cmd)
+		 (mailcap-mime-type file)))
+	 (term (or (mailcap-mime-info type "needsterm")
+		   (mailcap-mime-info type "needsterminal")))
+	 (encoded (listp cmd)))
+    (if encoded
+	(setq cmd (car cmd)))
+    ;; Ignore Lisp functions and shell commands requiring a terminal.
+    ;; (For the latter, assume we can display in a buffer if
+    ;; necessary, and it's silly to spawn an xterm.  In princple, we
+    ;; should filter all the possible viewers, but that probably
+    ;; doesn't win over testing the top-priority one.)
+    (when (and (stringp cmd) (not term))
+      (let (temp-file)
+	(if encoded
+	    ;; File needs decoding (uncompressing).  `temp-file' can't
+	    ;; get a magic name, so we can rely on the (jka-compr)
+	    ;; handler to uncompress `file' by copying to it.
+	    ;; `temp-file' can then be used for display.
+	    (progn
+	      (setq temp-file (make-temp-file "dired"))
+	      (copy-file file temp-file t)
+	      (setq cmd (mailcap-fill-escapes (car cmd) temp-file type nil)))
+	  (setq cmd (mailcap-fill-escapes cmd file type nil)))
+	(let ((proc (start-process-shell-command "*dired*" nil cmd nil)))
+	  ;; Delete any temporary file when process exits.
+	  (if temp-file
+	      (set-process-sentinel proc `(lambda (proc string)
+					    (delete-file ,temp-file))))))
+      t)))
 
 (provide 'mailcap)
 
