Importing Gnus v5.8.6.
[elisp/gnus.git-] / lisp / mailcap.el
index 9a8307e..3450905 100644 (file)
@@ -1,5 +1,5 @@
 ;;; mailcap.el --- Functions for displaying MIME parts
-;; Copyright (C) 1998 Free Software Foundation, Inc.
+;; Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
 
 ;; Author: William M. Perry <wmperry@aventail.com>
 ;;     Lars Magne Ingebrigtsen <larsi@gnus.org>
@@ -26,9 +26,9 @@
 
 ;;; Code:
 
-(eval-and-compile
-  (require 'cl))
+(eval-when-compile (require 'cl))
 (require 'mail-parse)
+(require 'mm-util)
 
 (defvar mailcap-parse-args-syntax-table
   (let ((table (copy-syntax-table emacs-lisp-mode-syntax-table)))
@@ -51,7 +51,8 @@
       (type . "application/x-x509-user-cert"))
      ("octet-stream"
       (viewer . mailcap-save-binary-file)
-      (type ."application/octet-stream"))
+      (non-viewer . t)
+      (type . "application/octet-stream"))
      ("dvi"
       (viewer . "open %s")
       (type   . "application/dvi")
@@ -70,6 +71,7 @@
       (type   . "application/emacs-lisp"))
      ("x-tar"
       (viewer . mailcap-save-binary-file)
+      (non-viewer . t)
       (type   . "application/x-tar"))
      ("x-latex"
       (viewer . tex-mode)
@@ -93,6 +95,7 @@
       (type   . "application/tex"))
      ("zip"
       (viewer . mailcap-save-binary-file)
+      (non-viewer . t)
       (type   . "application/zip")
       ("copiousoutput"))
      ("pdf"
       (type   . "application/postscript")
       (test   . (eq (mm-device-type) 'ns)))
      ("postscript"
-      (viewer . "ghostview %s")
+      (viewer . "ghostview -dSAFER %s")
       (type . "application/postscript")
       (test   . (eq (mm-device-type) 'x))
       ("needsx11"))
       (viewer . "maplay %s")
       (type   . "audio/x-mpeg"))
      (".*"
-      (viewer . mailcap-save-binary-file)
-      (test   . (or (featurep 'nas-sound)
-                     (featurep 'native-sound)))
-      (type   . "audio/*"))
-     (".*"
       (viewer . "showaudio")
       (type   . "audio/*")))
     ("message"
      ("rfc-*822"
-      (viewer . gnus-article-prepare-display)
+      (viewer . mm-view-message)
       (test   . (and (featurep 'gnus)
                     (gnus-alive-p)))
-      (type   . "message/rfc-822"))
+      (type   . "message/rfc822"))
      ("rfc-*822"
       (viewer . vm-mode)
       (test   . (fboundp 'vm-mode))
-      (type   . "message/rfc-822"))
+      (type   . "message/rfc822"))
      ("rfc-*822"
       (viewer . w3-mode)
       (test   . (fboundp 'w3-mode))
-      (type   . "message/rfc-822"))
+      (type   . "message/rfc822"))
      ("rfc-*822"
       (viewer . view-mode)
       (test   . (fboundp 'view-mode))
-      (type   . "message/rfc-822"))
+      (type   . "message/rfc822"))
      ("rfc-*822"
       (viewer . fundamental-mode)
-      (type   . "message/rfc-822")))
+      (type   . "message/rfc822")))
     ("image"
      ("x-xwd"
       (viewer  . "xwud -in %s")
       (type   . "image/*")
       (test   . (eq (mm-device-type) 'ns)))
      (".*"
-      (viewer . "xv -perfect %s")
+      (viewer . "display %s")
+      (type . "image/*")
+      (test   . (eq (mm-device-type) 'x))
+      ("needsx11"))
+     (".*"
+      (viewer . "ee %s")
       (type . "image/*")
       (test   . (eq (mm-device-type) 'x))
       ("needsx11")))
       (viewer . tar-mode)
       (type . "archive/tar")
       (test . (fboundp 'tar-mode)))))
-     "*The mailcap structure is an assoc list of assoc lists.
+  "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)
 
@@ -241,7 +244,10 @@ not.")
 (defvar mailcap-download-directory nil
   "*Where downloaded files should go by default.")
 
-(defvar mailcap-temporary-directory (or (getenv "TMPDIR") "/tmp")
+(defvar mailcap-temporary-directory
+  (cond ((fboundp 'temp-directory) (temp-directory))
+       ((boundp 'temporary-file-directory) temporary-file-directory)
+       ("/tmp/"))
   "*Where temporary files go.")
 
 ;;;
@@ -249,7 +255,7 @@ not.")
 ;;;
 
 (defun mailcap-generate-unique-filename (&optional fmt)
-  "Generate a unique filename in mailcap-temporary-directory"
+  "Generate a unique filename in mailcap-temporary-directory."
   (if (not fmt)
       (let ((base (format "mailcap-tmp.%d" (user-real-uid)))
            (fname "")
@@ -281,7 +287,7 @@ not.")
     (kill-buffer (current-buffer))))
 
 (defun mailcap-maybe-eval ()
-  "Maybe evaluate a buffer of emacs lisp code"
+  "Maybe evaluate a buffer of emacs lisp code."
   (if (yes-or-no-p "This is emacs-lisp code, evaluate it? ")
       (eval-buffer (current-buffer))
     (emacs-lisp-mode)))
@@ -299,8 +305,12 @@ not.")
 (defvar mailcap-parsed-p nil)
 
 (defun mailcap-parse-mailcaps (&optional path force)
-  "Parse out all the mailcaps specified in a unix-style path string PATH.
-If FORCE, re-parse even if already parsed."
+  "Parse out all the mailcaps specified in a path string PATH.
+Components of PATH are separated by the `path-separator' character
+appropriate for this system.  If FORCE, re-parse even if already
+parsed.  If PATH is omitted, use the value of environment variable
+MAILCAPS if set; otherwise (on Unix) use the path from RFC 1524, plus
+/usr/local/etc/mailcap."
   (interactive (list nil t))
   (when (or (not mailcap-parsed-p)
            force)
@@ -308,26 +318,24 @@ If FORCE, re-parse even if already parsed."
      (path nil)
      ((getenv "MAILCAPS") (setq path (getenv "MAILCAPS")))
      ((memq system-type '(ms-dos ms-windows windows-nt))
-      (setq path (mapconcat 'expand-file-name '("~/mail.cap" "~/etc/mail.cap")
-                           ";")))
-     (t (setq path (mapconcat 'expand-file-name
-                             '("~/.mailcap"
-                               "/etc/mailcap:/usr/etc/mailcap"
-                               "/usr/local/etc/mailcap") ":"))))
+      (setq path '("~/.mailcap" "~/mail.cap" "~/etc/mail.cap")))
+     (t (setq path
+             ;; This is per RFC 1524, specifically
+             ;; with /usr before /usr/local.
+             '("~/.mailcap" "/etc/mailcap" "/usr/etc/mailcap"
+               "/usr/local/etc/mailcap"))))
     (let ((fnames (reverse
-                  (split-string
-                   path (if (memq system-type
-                                  '(ms-dos ms-windows windows-nt))
-                            ";"
-                          ":"))))
+                  (if (stringp path)
+                      (parse-colon-path path)
+                    path)))
          fname)
       (while fnames
        (setq fname (car fnames))
-       (if (and (file-exists-p fname) (file-readable-p fname)
-                (file-regular-p fname))
-           (mailcap-parse-mailcap (car fnames)))
+       (if (and (file-readable-p fname)
+                (file-regular-p fname))
+           (mailcap-parse-mailcap fname))
        (setq fnames (cdr fnames))))
-    (setq mailcap-parsed-p t)))
+      (setq mailcap-parsed-p t)))
 
 (defun mailcap-parse-mailcap (fname)
   ;; Parse out the mailcap file specified by FNAME
@@ -341,54 +349,67 @@ If FORCE, re-parse even if already parsed."
       (insert-file-contents fname)
       (set-syntax-table mailcap-parse-args-syntax-table)
       (mailcap-replace-regexp "#.*" "")        ; Remove all comments
+      (mailcap-replace-regexp "\\\\[ \t]*\n" " ") ; And collapse spaces
       (mailcap-replace-regexp "\n+" "\n") ; And blank lines
-      (mailcap-replace-regexp "\\\\[ \t\n]+" " ") ; And collapse spaces
-      (mailcap-replace-regexp (concat (regexp-quote "\\") "[ \t]*\n") "")
       (goto-char (point-max))
       (skip-chars-backward " \t\n")
       (delete-region (point) (point-max))
-      (goto-char (point-min))
-      (while (not (eobp))
-       (skip-chars-forward " \t\n")
+      (while (not (bobp))
+       (skip-chars-backward " \t\n")
+       (beginning-of-line)
        (setq save-pos (point)
              info nil)
-       (skip-chars-forward "^/;")
+       (skip-chars-forward "^/; \t\n")
        (downcase-region save-pos (point))
        (setq major (buffer-substring save-pos (point)))
-       (skip-chars-forward "/ \t\n")
-       (setq save-pos (point))
-       (skip-chars-forward "^;")
-       (downcase-region save-pos (point))
-       (setq minor
-             (cond
-              ((eq ?* (or (char-after save-pos) 0)) ".*")
-              ((= (point) save-pos) ".*")
-              (t (buffer-substring save-pos (point)))))
-       (skip-chars-forward "; \t\n")
+       (skip-chars-forward " \t")
+       (setq minor "")
+       (when (eq (char-after) ?/)
+         (forward-char)
+         (skip-chars-forward " \t")
+         (setq save-pos (point))
+         (skip-chars-forward "^; \t\n")
+         (downcase-region save-pos (point))
+         (setq minor
+               (cond
+                ((eq ?* (or (char-after save-pos) 0)) ".*")
+                ((= (point) save-pos) ".*")
+                (t (regexp-quote (buffer-substring save-pos (point)))))))
+       (skip-chars-forward " \t")
        ;;; Got the major/minor chunks, now for the viewers/etc
        ;;; The first item _must_ be a viewer, according to the
        ;;; RFC for mailcap files (#1343)
-       (skip-chars-forward "; \t\n")
-       (setq save-pos (point))
-       (skip-chars-forward "^;\n")
-       (if (eq (or (char-after save-pos) 0) ?')
-           (setq viewer (progn
-                          (narrow-to-region (1+ save-pos) (point))
-                          (goto-char (point-min))
-                          (prog1
-                              (read (current-buffer))
-                            (goto-char (point-max))
-                            (widen))))
-         (setq viewer (buffer-substring save-pos (point))))
+       (setq viewer "")
+       (when (eq (char-after) ?\;) 
+         (forward-char)
+         (skip-chars-forward " \t")
+         (setq save-pos (point))
+         (skip-chars-forward "^;\n")
+         ;; skip \;
+         (while (eq (char-before) ?\\)
+           (backward-delete-char 1)
+           (forward-char)
+           (skip-chars-forward "^;\n"))
+         (if (eq (or (char-after save-pos) 0) ?')
+             (setq viewer (progn
+                            (narrow-to-region (1+ save-pos) (point))
+                            (goto-char (point-min))
+                            (prog1
+                                (read (current-buffer))
+                              (goto-char (point-max))
+                              (widen))))
+           (setq viewer (buffer-substring save-pos (point)))))
        (setq save-pos (point))
        (end-of-line)
-       (setq info (nconc (list (cons 'viewer viewer)
-                               (cons 'type (concat major "/"
-                                                   (if (string= minor ".*")
-                                                       "*" minor))))
-                         (mailcap-parse-mailcap-extras save-pos (point))))
-       (mailcap-mailcap-entry-passes-test info)
-       (mailcap-add-mailcap-entry major minor info)))))
+       (unless (equal viewer "") 
+         (setq info (nconc (list (cons 'viewer viewer)
+                                 (cons 'type (concat major "/"
+                                                     (if (string= minor ".*")
+                                                         "*" minor))))
+                           (mailcap-parse-mailcap-extras save-pos (point))))
+         (mailcap-mailcap-entry-passes-test info)
+         (mailcap-add-mailcap-entry major minor info))
+       (beginning-of-line)))))
 
 (defun mailcap-parse-mailcap-extras (st nd)
   ;; Grab all the extra stuff from a mailcap entry
@@ -406,14 +427,13 @@ If FORCE, re-parse even if already parsed."
       (skip-chars-forward " \n\t;")
       (while (not (eobp))
        (setq done nil)
-       (skip-chars-forward " \";\n\t")
        (setq name-pos (point))
-       (skip-chars-forward "^ \n\t=")
+       (skip-chars-forward "^ \n\t=;")
        (downcase-region name-pos (point))
        (setq name (buffer-substring name-pos (point)))
        (skip-chars-forward " \t\n")
        (if (not (eq (char-after (point)) ?=)) ; There is no value
-           (setq value nil)
+           (setq value t)
          (skip-chars-forward " \t\n=")
          (setq val-pos (point))
          (if (memq (char-after val-pos) '(?\" ?'))
@@ -432,7 +452,8 @@ If FORCE, re-parse even if already parsed."
                    (skip-chars-forward ";"))
                (setq done t))))
          (setq value (buffer-substring val-pos (point))))
-       (setq results (cons (cons name value) results)))
+       (setq results (cons (cons name value) results))
+       (skip-chars-forward " \";\n\t"))
       results)))
 
 (defun mailcap-mailcap-entry-passes-test (info)
@@ -443,6 +464,7 @@ If FORCE, re-parse even if already parsed."
        )
     (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)
@@ -476,7 +498,7 @@ If FORCE, re-parse even if already parsed."
        ((and minor (string-match (car (car major)) minor))
        (setq wildcard (cons (cdr (car major)) wildcard))))
       (setq major (cdr major)))
-    (nconc (nreverse exact) (nreverse wildcard))))
+    (nconc exact wildcard)))
 
 (defun mailcap-unescape-mime-test (test type-info)
   (let (save-pos save-chr subst)
@@ -569,16 +591,32 @@ If FORCE, re-parse even if already parsed."
        (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)))
-         (setcdr cur-minor info))
-        (t
-         (setcdr old-major (cons (cons minor info) (cdr old-major)))))))))
+       (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))))))
+      )))
+
+(defun mailcap-add (type viewer &optional test)
+  "Add VIEWER as a handler for TYPE.
+If TEST is not given, it defaults to t."
+  (let ((tl (split-string type "/")))
+    (when (or (not (car tl))
+             (not (cadr tl)))
+      (error "%s is not a valid MIME type" type))
+    (mailcap-add-mailcap-entry
+     (car tl) (cadr tl)
+     `((viewer . ,viewer)
+       (test . ,(if test test t))
+       (type . ,type)))))
 
 ;;;
 ;;; The main whabbo
@@ -591,12 +629,12 @@ If FORCE, re-parse even if already parsed."
        (x-lisp (not (stringp (or (cdr-safe (assq 'viewer x)) ""))))
        (y-lisp (not (stringp (or (cdr-safe (assq 'viewer y)) "")))))
     (cond
-     ((and x-lisp (not y-lisp))
-      t)
-     ((and (not y-lisp) x-wild (not y-wild))
-      t)
+     ((and x-wild (not y-wild))
+      nil)
      ((and (not x-wild) y-wild)
       t)
+     ((and (not y-lisp) x-lisp)
+      t)
      (t nil))))
 
 (defun mailcap-mime-info (string &optional request)
@@ -636,7 +674,7 @@ this type is returned."
            (if (mailcap-viewer-passes-test (car viewers) info)
                (setq passed (cons (car viewers) passed)))
            (setq viewers (cdr viewers)))
-         (setq passed (sort (nreverse passed) 'mailcap-viewer-lessp))
+         (setq passed (sort passed 'mailcap-viewer-lessp))
          (setq viewer (car passed))))
       (when (and (stringp (cdr (assq 'viewer viewer)))
                 passed)
@@ -654,7 +692,7 @@ this type is returned."
        passed)
        (t
        ;; MUST make a copy *sigh*, else we modify mailcap-mime-data
-       (setq viewer (copy-tree viewer))
+       (setq viewer (copy-sequence viewer))
        (let ((view (assq 'viewer viewer))
              (test (assq 'test viewer)))
          (if view (setcdr view (mailcap-unescape-mime-test (cdr view) info)))
@@ -679,7 +717,9 @@ this type is returned."
     (".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")
@@ -714,6 +754,7 @@ this type is returned."
     (".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")
@@ -749,38 +790,46 @@ this type is returned."
     (".wav"      . "audio/x-wav")
     (".wrl"      . "x-world/x-vrml")
     (".xbm"      . "image/xbm")
-    (".xpm"      . "image/x-pixmap")
+    (".xpm"      . "image/xpm")
     (".xwd"      . "image/windowdump")
     (".zip"      . "application/zip")
     (".ai"       . "application/postscript")
     (".jpe"      . "image/jpeg")
     (".jpeg"     . "image/jpeg"))
-  "*An assoc list of file extensions and the MIME content-types they
-correspond to.")
+  "An assoc list of file extensions and corresponding MIME content-types.")
 
 (defun mailcap-parse-mimetypes (&optional path)
-  ;; Parse out all the mimetypes specified in a unix-style path string PATH
+  "Parse out all the mimetypes specified in a unix-style path string PATH.
+Components of PATH are separated by the `path-separator' character
+appropriate for this system.  If PATH is omitted, use the value of
+environment variable MIMETYPES if set; otherwise use a default path."
   (cond
    (path nil)
    ((getenv "MIMETYPES") (setq path (getenv "MIMETYPES")))
    ((memq system-type '(ms-dos ms-windows windows-nt))
-    (setq path (mapconcat 'expand-file-name
-                         '("~/mime.typ" "~/etc/mime.typ") ";")))
-   (t (setq path (mapconcat 'expand-file-name
-                           '("~/.mime-types"
-                             "/etc/mime-types:/usr/etc/mime-types"
-                             "/usr/local/etc/mime-types"
-                             "/usr/local/www/conf/mime-types") ":"))))
-  (let ((fnames (reverse
-                (split-string path
-                              (if (memq system-type
-                                        '(ms-dos ms-windows windows-nt))
-                                  ";" ":"))))
+    (setq path '("~/mime.typ" "~/etc/mime.typ")))
+   (t (setq path
+           ;; mime.types seems to be the normal name, definitely so
+           ;; on current GNUish systems.  The search order follows
+           ;; that for mailcap.
+           '("~/.mime.types"
+             "/etc/mime.types"
+             "/usr/etc/mime.types"
+             "/usr/local/etc/mime.types"
+             "/usr/local/www/conf/mime.types"
+             "~/.mime-types"
+             "/etc/mime-types"
+             "/usr/etc/mime-types"
+             "/usr/local/etc/mime-types"
+             "/usr/local/www/conf/mime-types"))))
+  (let ((fnames (reverse (if (stringp path)
+                            (parse-colon-path path)
+                          path)))
        fname)
     (while fnames
       (setq fname (car fnames))
-      (if (and (file-exists-p fname) (file-readable-p fname))
-         (mailcap-parse-mimetype-file (car fnames)))
+      (if (and (file-readable-p fname))
+         (mailcap-parse-mimetype-file fname))
       (setq fnames (cdr fnames)))))
 
 (defun mailcap-parse-mimetype-file (fname)
@@ -846,6 +895,10 @@ The path of COMMAND will be returned iff COMMAND is a command."
                       (not (file-directory-p file)))
              (throw 'found file))))))))
 
+(defun mailcap-mime-types ()
+  "Return a list of MIME media types."
+  (mm-delete-duplicates (mapcar 'cdr mailcap-mime-extensions)))
+
 (provide 'mailcap)
 
 ;;; mailcap.el ends here