;;; 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>
;;; 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)))
(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")
(type . "application/emacs-lisp"))
("x-tar"
(viewer . mailcap-save-binary-file)
+ (non-viewer . t)
(type . "application/x-tar"))
("x-latex"
(viewer . tex-mode)
(type . "application/tex"))
("zip"
(viewer . mailcap-save-binary-file)
+ (non-viewer . t)
(type . "application/zip")
("copiousoutput"))
("pdf"
(viewer . "open %s")
(type . "application/postscript")
(test . (eq (mm-device-type) 'ns)))
- ("postscript"
- (viewer . "ghostview %s")
+ ("postscript"
+ (viewer . "ghostview -dSAFER %s")
(type . "application/postscript")
(test . (eq (mm-device-type) 'x))
("needsx11"))
("x-mpeg"
(viewer . "maplay %s")
(type . "audio/x-mpeg"))
- (".*"
- (viewer . mm-view-sound-file)
- (test . (or (featurep 'nas-sound)
- (featurep 'native-sound)))
- (type . "audio/*"))
(".*"
(viewer . "showaudio")
(type . "audio/*")))
("message"
+ ("rfc-*822"
+ (viewer . mm-view-message)
+ (test . (and (featurep 'gnus)
+ (gnus-alive-p)))
+ (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"))
- ("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")))
(type . "text/plain"))
("enriched"
(viewer . enriched-decode-region)
- (test . (fboundp 'enriched-decode-region))
+ (test . (fboundp 'enriched-decode))
(type . "text/enriched"))
("html"
(viewer . mm-w3-prepare-buffer)
(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)
(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.")
;;;
;;;
(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 "")
(expand-file-name fname mailcap-temporary-directory))))
(defun mailcap-save-binary-file ()
- (let ((file (read-file-name
- "Filename to save as: "
- (or mailcap-download-directory "~/")))
- (require-final-newline nil))
- (write-region (point-min) (point-max) file)
+ (goto-char (point-min))
+ (unwind-protect
+ (let ((file (read-file-name
+ "Filename to save as: "
+ (or mailcap-download-directory "~/")))
+ (require-final-newline nil))
+ (write-region (point-min) (point-max) file))
(kill-buffer (current-buffer))))
+(defvar mailcap-maybe-eval-warning
+ "*** WARNING ***
+
+This MIME part contains untrusted and possibly harmful content.
+If you evaluate the Emacs Lisp code contained in it, a lot of nasty
+things can happen. Please examine the code very carefully before you
+instruct Emacs to evaluate it. You can browse the buffer containing
+the code using \\[scroll-other-window].
+
+If you are unsure what to do, please answer \"no\"."
+ "Text of warning message displayed by `mailcap-maybe-eval'.
+Make sure that this text consists only of few text lines. Otherwise,
+Gnus might fail to display all of it.")
+
(defun mailcap-maybe-eval ()
- "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)))
+ "Maybe evaluate a buffer of emacs lisp code."
+ (let ((lisp-buffer (current-buffer)))
+ (goto-char (point-min))
+ (when
+ (save-window-excursion
+ (delete-other-windows)
+ (let ((buffer (get-buffer-create (generate-new-buffer-name
+ "*Warning*"))))
+ (unwind-protect
+ (with-current-buffer buffer
+ (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? "))
+ (kill-buffer buffer))))
+ (eval-buffer (current-buffer)))
+ (when (buffer-live-p lisp-buffer)
+ (with-current-buffer lisp-buffer
+ (emacs-lisp-mode)))))
+
;;;
;;; The mailcap parser
(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."
- (interactive)
+ "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)
(cond
(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))
- (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
(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
- ((= ?* (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 (= (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
(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 (/= (or (char-after (point)) 0) ?=) ; There is no value
- (setq value nil)
+ (if (not (eq (char-after (point)) ?=)) ; There is no value
+ (setq value t)
(skip-chars-forward " \t\n=")
(setq val-pos (point))
(if (memq (char-after val-pos) '(?\" ?'))
(error (goto-char (point-max)))))
(while (not done)
(skip-chars-forward "^;")
- (if (= (or (char-after (1- (point))) 0) ?\\ )
+ (if (eq (char-after (1- (point))) ?\\ )
(progn
(subst-char-in-region (1- (point)) (point) ?\\ ? )
(skip-chars-forward ";"))
(setq done t))))
(setq value (buffer-substring val-pos (point))))
- (setq results (cons (cons name value) results)))
- results)))
+ (setq results (cons (cons name value) results))
+ (skip-chars-forward " \";\n\t"))
+ results)))
(defun mailcap-mailcap-entry-passes-test (info)
;; Return t iff a mailcap entry passes its test clause or no test
(test (assq 'test info)) ; The test clause
)
(setq status (and test (split-string (cdr test) " ")))
- (if (and (assoc "needsx11" info) (not (getenv "DISPLAY")))
+ (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")
(cond
((equal (car (car major)) minor)
(setq exact (cons (cdr (car major)) exact)))
- ((string-match (car (car major)) minor)
+ ((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)
(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
(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)
"Get the MIME viewer command for STRING, return nil if none found.
-Expects a complete content-type header line as its argument.
+Expects a complete content-type header line as its argument.
Second argument REQUEST specifies what information to return. If it is
nil or the empty string, the viewer (second field of the mailcap
(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)
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)))
(".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")
(".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")
(".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.")
-
-(defun mailcap-parse-mimetypes (&optional path)
- ;; Parse out all the mimetypes specified in a unix-style path string 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))
- ";" ":"))))
- fname)
- (while fnames
- (setq fname (car fnames))
- (if (and (file-exists-p fname) (file-readable-p fname))
- (mailcap-parse-mimetype-file (car fnames)))
- (setq fnames (cdr fnames)))))
+ "An assoc list of file extensions and corresponding MIME content-types.")
+
+(defvar mailcap-mimetypes-parsed-p nil)
+
+(defun mailcap-parse-mimetypes (&optional path force)
+ "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.
+If FORCE, re-parse even if already parsed."
+ (interactive (list nil t))
+ (when (or (not mailcap-mimetypes-parsed-p)
+ force)
+ (cond
+ (path nil)
+ ((getenv "MIMETYPES") (setq path (getenv "MIMETYPES")))
+ ((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-readable-p fname))
+ (mailcap-parse-mimetype-file fname))
+ (setq fnames (cdr fnames))))
+ (setq mailcap-mimetypes-parsed-p t)))
(defun mailcap-parse-mimetype-file (fname)
;; Parse out a mime-types file
(while (not (eobp))
(skip-chars-forward " \t\n")
(setq save-pos (point))
- (skip-chars-forward "^ \t")
+ (skip-chars-forward "^ \t\n")
(downcase-region save-pos (point))
(setq type (buffer-substring save-pos (point)))
(while (not (eolp))
(defun mailcap-extension-to-mime (extn)
"Return the MIME content type of the file extensions EXTN."
+ (mailcap-parse-mimetypes)
(if (and (stringp extn)
(not (eq (string-to-char extn) ?.)))
(setq extn (concat "." extn)))
(cdr (assoc (downcase extn) mailcap-mime-extensions)))
+(defvar mailcap-binary-suffixes
+ (if (memq system-type '(ms-dos windows-nt))
+ '(".exe" ".com" ".bat" ".cmd" ".btm" "")
+ '("")))
+
+(defun mailcap-command-p (command)
+ "Say whether COMMAND is in the exec path.
+The path of COMMAND will be returned iff COMMAND is a command."
+ (let ((path (if (file-name-absolute-p command) '(nil) exec-path))
+ file dir)
+ (catch 'found
+ (while (setq dir (pop path))
+ (let ((suffixes mailcap-binary-suffixes))
+ (while suffixes
+ (when (and (file-executable-p
+ (setq file (expand-file-name
+ (concat command (pop suffixes))
+ dir)))
+ (not (file-directory-p file)))
+ (throw 'found file))))))))
+
+(defun mailcap-mime-types ()
+ "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)))))
+
(provide 'mailcap)
;;; mailcap.el ends here