X-Git-Url: http://git.chise.org/gitweb/?a=blobdiff_plain;f=lisp%2Fmm-decode.el;h=717e01709e1ce4af44817b4d9d2d1c5d72548c1a;hb=a2d6af2c24264119c5aff0ef0063733674eef102;hp=c6e2129bd40a365942028eab583e2041d06201cd;hpb=82300762c3419b73fc2e994b14e3d520fe88b0a9;p=elisp%2Fgnus.git- diff --git a/lisp/mm-decode.el b/lisp/mm-decode.el index c6e2129..717e017 100644 --- a/lisp/mm-decode.el +++ b/lisp/mm-decode.el @@ -1,5 +1,5 @@ ;;; mm-decode.el --- Functions for decoding MIME things -;; Copyright (C) 1998,99 Free Software Foundation, Inc. +;; Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; MORIOKA Tomohiko @@ -28,12 +28,28 @@ (require 'mailcap) (require 'mm-bodies) +(defvar mm-xemacs-p (string-match "XEmacs" (emacs-version))) + +(defgroup mime-display () + "Display of MIME in mail and news articles." + :link '(custom-manual "(emacs-mime)Customization") + :group 'mail + :group 'news) + ;;; Convenience macros. (defmacro mm-handle-buffer (handle) `(nth 0 ,handle)) (defmacro mm-handle-type (handle) `(nth 1 ,handle)) +(defsubst mm-handle-media-type (handle) + (if (stringp (car handle)) + (car handle) + (car (mm-handle-type handle)))) +(defsubst mm-handle-media-supertype (handle) + (car (split-string (mm-handle-media-type handle) "/"))) +(defsubst mm-handle-media-subtype (handle) + (cadr (split-string (mm-handle-media-type handle) "/"))) (defmacro mm-handle-encoding (handle) `(nth 2 ,handle)) (defmacro mm-handle-undisplayer (handle) @@ -56,61 +72,122 @@ `(list ,buffer ,type ,encoding ,undisplayer ,disposition ,description ,cache ,id)) -(defvar mm-inline-media-tests - '(("image/jpeg" mm-inline-image - (and window-system (featurep 'jpeg) (mm-image-fit-p handle))) - ("image/png" mm-inline-image - (and window-system (featurep 'png) (mm-image-fit-p handle))) - ("image/gif" mm-inline-image - (and window-system (featurep 'gif) (mm-image-fit-p handle))) - ("image/tiff" mm-inline-image - (and window-system (featurep 'tiff) (mm-image-fit-p handle))) - ("image/xbm" mm-inline-image - (and window-system (fboundp 'device-type) - (eq (device-type) 'x))) - ("image/x-xbitmap" mm-inline-image - (and window-system (fboundp 'device-type) - (eq (device-type) 'x))) - ("image/xpm" mm-inline-image - (and window-system (featurep 'xpm))) - ("image/x-pixmap" mm-inline-image - (and window-system (featurep 'xpm))) - ("image/bmp" mm-inline-image - (and window-system (featurep 'bmp))) - ("text/plain" mm-inline-text t) - ("text/enriched" mm-inline-text t) - ("text/richtext" mm-inline-text t) - ("text/html" mm-inline-text (locate-library "w3")) - ("message/delivery-status" mm-inline-text t) - ("text/.*" mm-inline-text t) +(defcustom mm-inline-media-tests + '(("image/jpeg" + mm-inline-image + (lambda (handle) + (mm-valid-and-fit-image-p 'jpeg handle))) + ("image/png" + mm-inline-image + (lambda (handle) + (mm-valid-and-fit-image-p 'png handle))) + ("image/gif" + mm-inline-image + (lambda (handle) + (mm-valid-and-fit-image-p 'gif handle))) + ("image/tiff" + mm-inline-image + (lambda (handle) + (mm-valid-and-fit-image-p 'tiff handle)) ) + ("image/xbm" + mm-inline-image + (lambda (handle) + (mm-valid-and-fit-image-p 'xbm handle))) + ("image/x-xbitmap" + mm-inline-image + (lambda (handle) + (mm-valid-and-fit-image-p 'xbm handle))) + ("image/xpm" + mm-inline-image + (lambda (handle) + (mm-valid-and-fit-image-p 'xpm handle))) + ("image/x-pixmap" + mm-inline-image + (lambda (handle) + (mm-valid-and-fit-image-p 'xpm handle))) + ("image/bmp" + mm-inline-image + (lambda (handle) + (mm-valid-and-fit-image-p 'bmp handle))) + ("text/plain" mm-inline-text identity) + ("text/enriched" mm-inline-text identity) + ("text/richtext" mm-inline-text identity) + ("text/x-patch" mm-display-patch-inline + (lambda (handle) + (locate-library "diff-mode"))) + ("text/html" + mm-inline-text + (lambda (handle) + (locate-library "w3"))) + ("text/x-vcard" + mm-inline-text + (lambda (handle) + (or (featurep 'vcard) + (locate-library "vcard")))) + ("message/delivery-status" mm-inline-text identity) + ("message/rfc822" mm-inline-message identity) + ("text/.*" mm-inline-text identity) ("audio/wav" mm-inline-audio - (and (or (featurep 'nas-sound) (featurep 'native-sound)) - (device-sound-enabled-p))) - ("audio/au" mm-inline-audio - (and (or (featurep 'nas-sound) (featurep 'native-sound)) - (device-sound-enabled-p))) - ("multipart/alternative" ignore t) - ("multipart/mixed" ignore t) - ("multipart/related" ignore t)) - "Alist of media types/test that say whether the media types can be displayed inline.") - -(defvar mm-user-display-methods - '(("image/.*" . inline) - ("text/.*" . inline) - ("message/delivery-status" . inline))) - -(defvar mm-user-automatic-display - '("text/plain" "text/enriched" "text/richtext" "text/html" - "image/.*" "message/delivery-status" "multipart/.*")) - -(defvar mm-user-automatic-external-display nil - "List of MIME type regexps that will be displayed externally automatically.") - -(defvar mm-alternative-precedence - '("multipart/related" "multipart/mixed" "multipart/alternative" - "image/jpeg" "image/gif" "text/html" "text/enriched" - "text/richtext" "text/plain") - "List that describes the precedence of alternative parts.") + (lambda (handle) + (and (or (featurep 'nas-sound) (featurep 'native-sound)) + (device-sound-enabled-p)))) + ("audio/au" + mm-inline-audio + (lambda (handle) + (and (or (featurep 'nas-sound) (featurep 'native-sound)) + (device-sound-enabled-p)))) + ("application/pgp-signature" ignore identity) + ("multipart/alternative" ignore identity) + ("multipart/mixed" ignore identity) + ("multipart/related" ignore identity)) + "Alist of media types/tests saying whether types can be displayed inline." + :type '(repeat (list (string :tag "MIME type") + (function :tag "Display function") + (function :tag "Display test"))) + :group 'mime-display) + +(defcustom mm-inlined-types + '("image/.*" "text/.*" "message/delivery-status" "message/rfc822" + "application/pgp-signature") + "List of media types that are to be displayed inline." + :type '(repeat string) + :group 'mime-display) + +(defcustom mm-automatic-display + '("text/plain" "text/enriched" "text/richtext" "text/html" + "text/x-vcard" "image/.*" "message/delivery-status" "multipart/.*" + "message/rfc822" "text/x-patch" "application/pgp-signature") + "A list of MIME types to be displayed automatically." + :type '(repeat string) + :group 'mime-display) + +(defcustom mm-attachment-override-types '("text/x-vcard") + "Types to have \"attachment\" ignored if they can be displayed inline." + :type '(repeat string) + :group 'mime-display) + +(defcustom mm-inline-override-types nil + "Types to be treated as attachments even if they can be displayed inline." + :type '(repeat string) + :group 'mime-display) + +(defcustom mm-automatic-external-display nil + "List of MIME type regexps that will be displayed externally automatically." + :type '(repeat string) + :group 'mime-display) + +(defcustom mm-discouraged-alternatives nil + "List of MIME types that are discouraged when viewing multipart/alternative. +Viewing agents are supposed to view the last possible part of a message, +as that is supposed to be the richest. However, users may prefer other +types instead, and this list says what types are most unwanted. If, +for instance, text/html parts are very unwanted, and text/richtech are +somewhat unwanted, then the value of this variable should be set +to: + + (\"text/html\" \"text/richtext\")" + :type '(repeat string) + :group 'mime-display) (defvar mm-tmp-directory (cond ((fboundp 'temp-directory) (temp-directory)) @@ -118,8 +195,10 @@ ("/tmp/")) "Where mm will store its temporary files.") -(defvar mm-all-images-fit nil - "If non-nil, then all images fit in the buffer.") +(defcustom mm-inline-large-images nil + "If non-nil, then all images fit in the buffer." + :type 'boolean + :group 'mime-display) ;;; Internal variables. @@ -138,19 +217,22 @@ (when (or no-strict-mime (mail-fetch-field "mime-version")) (setq ct (mail-fetch-field "content-type") - ctl (condition-case () (mail-header-parse-content-type ct) - (error nil)) + ctl (ignore-errors (mail-header-parse-content-type ct)) cte (mail-fetch-field "content-transfer-encoding") cd (mail-fetch-field "content-disposition") description (mail-fetch-field "content-description") id (mail-fetch-field "content-id")))) + (when cte + (setq cte (mail-header-strip cte))) (if (or (not ctl) (not (string-match "/" (car ctl)))) (mm-dissect-singlepart - '("text/plain") nil no-strict-mime - (and cd (condition-case () - (mail-header-parse-content-disposition cd) - (error nil))) + '("text/plain") + (and cte (intern (downcase (mail-header-remove-whitespace + (mail-header-remove-comments + cte))))) + no-strict-mime + (and cd (ignore-errors (mail-header-parse-content-disposition cd))) description) (setq type (split-string (car ctl) "/")) (setq subtype (cadr type) @@ -167,9 +249,7 @@ (mail-header-remove-comments cte))))) no-strict-mime - (and cd (condition-case () - (mail-header-parse-content-disposition cd) - (error nil))) + (and cd (ignore-errors (mail-header-parse-content-disposition cd))) description id)))) (when id (when (string-match " *<\\(.*\\)> *" id) @@ -179,7 +259,9 @@ (defun mm-dissect-singlepart (ctl cte &optional force cdl description id) (when (or force - (not (equal "text/plain" (car ctl)))) + (if (equal "text/plain" (car ctl)) + (assoc 'format ctl) + t)) (let ((res (mm-make-handle (mm-copy-to-buffer) ctl cte nil cdl description nil id))) (push (car res) mm-dissection-list) @@ -194,13 +276,13 @@ (defun mm-dissect-multipart (ctl) (goto-char (point-min)) (let* ((boundary (concat "\n--" (mail-content-type-get ctl 'boundary))) - (close-delimiter (concat (regexp-quote boundary) "--[ \t]*$")) - start parts - (end (save-excursion - (goto-char (point-max)) - (if (re-search-backward close-delimiter nil t) - (match-beginning 0) - (point-max))))) + (close-delimiter (concat (regexp-quote boundary) "--[ \t]*$")) + start parts + (end (save-excursion + (goto-char (point-max)) + (if (re-search-backward close-delimiter nil t) + (match-beginning 0) + (point-max))))) (while (search-forward boundary end t) (goto-char (match-beginning 0)) (when start @@ -229,10 +311,6 @@ (insert-buffer-substring obuf beg) (current-buffer)))) -(defun mm-inlinable-part-p (type) - "Say whether TYPE can be displayed inline." - (eq (mm-user-method type) 'inline)) - (defun mm-display-part (handle &optional no-default) "Display the MIME part represented by HANDLE. Returns nil if the part is removed; inline if displayed inline; @@ -241,91 +319,150 @@ external if displayed external." (mailcap-parse-mailcaps) (if (mm-handle-displayed-p handle) (mm-remove-part handle) - (let* ((type (car (mm-handle-type handle))) - (method (mailcap-mime-info type)) - (user-method (mm-user-method type))) - (if (eq user-method 'inline) + (let* ((type (mm-handle-media-type handle)) + (method (mailcap-mime-info type))) + (if (mm-inlined-p handle) (progn (forward-line 1) (mm-display-inline handle) 'inline) - (when (or user-method - method + (when (or method (not no-default)) - (if (and (not user-method) - (not method) + (if (and (not method) (equal "text" (car (split-string type)))) (progn (forward-line 1) (mm-insert-inline handle (mm-get-part handle)) 'inline) (mm-display-external - handle (or user-method method - 'mailcap-save-binary-file)) - 'external))))))) + handle (or method 'mailcap-save-binary-file))))))))) (defun mm-display-external (handle method) "Display HANDLE using METHOD." - (mm-with-unibyte-buffer - (if (functionp method) - (let ((cur (current-buffer))) - (if (eq method 'mailcap-save-binary-file) - (progn - (set-buffer (generate-new-buffer "*mm*")) - (setq method nil)) - (mm-insert-part handle) - (let ((win (get-buffer-window cur t))) - (when win - (select-window win))) - (switch-to-buffer (generate-new-buffer "*mm*"))) - (buffer-disable-undo) - (mm-set-buffer-file-coding-system mm-binary-coding-system) - (insert-buffer-substring cur) + (let ((outbuf (current-buffer))) + (mm-with-unibyte-buffer + (if (functionp method) + (let ((cur (current-buffer))) + (if (eq method 'mailcap-save-binary-file) + (progn + (set-buffer (generate-new-buffer "*mm*")) + (setq method nil)) + (mm-insert-part handle) + (let ((win (get-buffer-window cur t))) + (when win + (select-window win))) + (switch-to-buffer (generate-new-buffer "*mm*"))) + (buffer-disable-undo) + (mm-set-buffer-file-coding-system mm-binary-coding-system) + (insert-buffer-substring cur) + (message "Viewing with %s" method) + (let ((mm (current-buffer)) + (non-viewer (assq 'non-viewer + (mailcap-mime-info + (mm-handle-media-type handle) t)))) + (unwind-protect + (if method + (funcall method) + (mm-save-part handle)) + (when (and (not non-viewer) + method) + (mm-handle-set-undisplayer handle mm))))) + ;; The function is a string to be executed. + (mm-insert-part handle) + (let* ((dir (make-temp-name (expand-file-name "emm." mm-tmp-directory))) + (filename (mail-content-type-get + (mm-handle-disposition handle) 'filename)) + (mime-info (mailcap-mime-info + (mm-handle-media-type handle) t)) + (needsterm (or (assoc "needsterm" mime-info) + (assoc "needsterminal" mime-info))) + (copiousoutput (assoc "copiousoutput" mime-info)) + file buffer) + ;; We create a private sub-directory where we store our files. + (make-directory dir) + (set-file-modes dir 448) + (if filename + (setq file (expand-file-name (file-name-nondirectory filename) + dir)) + (setq file (make-temp-name (expand-file-name "mm." dir)))) + (let ((coding-system-for-write mm-binary-coding-system)) + (write-region (point-min) (point-max) file nil 'nomesg)) (message "Viewing with %s" method) - (let ((mm (current-buffer)) - (non-viewer (assoc "non-viewer" - (mailcap-mime-info - (car (mm-handle-type handle)) t)))) - (unwind-protect - (if method - (funcall method) - (mm-save-part handle)) - (unless non-viewer - (mm-handle-set-undisplayer handle mm))))) - ;; The function is a string to be executed. - (mm-insert-part handle) - (let* ((dir (make-temp-name (expand-file-name "emm." mm-tmp-directory))) - (filename (mail-content-type-get - (mm-handle-disposition handle) 'filename)) - (needsterm (assoc "needsterm" - (mailcap-mime-info - (car (mm-handle-type handle)) t))) - process file buffer) - ;; We create a private sub-directory where we store our files. - (make-directory dir) - (set-file-modes dir 448) - (if filename - (setq file (expand-file-name (file-name-nondirectory filename) - dir)) - (setq file (make-temp-name (expand-file-name "mm." dir)))) - (write-region (point-min) (point-max) file nil 'nomesg) - (message "Viewing with %s" method) - (unwind-protect - (setq process - (if needsterm - (start-process "*display*" nil - "xterm" - "-e" shell-file-name "-c" - (format method - (mm-quote-arg file))) - (start-process "*display*" - (setq buffer (generate-new-buffer "*mm*")) - shell-file-name - "-c" (format method - (mm-quote-arg file))))) - (mm-handle-set-undisplayer handle (cons file buffer))) - (message "Displaying %s..." (format method file)))))) - + (cond (needsterm + (unwind-protect + (start-process "*display*" nil + "xterm" + "-e" shell-file-name + shell-command-switch + (mm-mailcap-command + method file (mm-handle-type handle))) + (mm-handle-set-undisplayer handle (cons file buffer))) + (message "Displaying %s..." (format method file)) + 'external) + (copiousoutput + (with-current-buffer outbuf + (forward-line 1) + (mm-insert-inline + handle + (unwind-protect + (progn + (call-process shell-file-name nil + (setq buffer + (generate-new-buffer "*mm*")) + nil + shell-command-switch + (mm-mailcap-command + method file (mm-handle-type handle))) + (if (buffer-live-p buffer) + (save-excursion + (set-buffer buffer) + (buffer-string)))) + (progn + (ignore-errors (delete-file file)) + (ignore-errors (delete-directory + (file-name-directory file))) + (ignore-errors (kill-buffer buffer)))))) + 'inline) + (t + (unwind-protect + (start-process "*display*" + (setq buffer + (generate-new-buffer "*mm*")) + shell-file-name + shell-command-switch + (mm-mailcap-command + method file (mm-handle-type handle))) + (mm-handle-set-undisplayer handle (cons file buffer))) + (message "Displaying %s..." (format method file)) + 'external))))))) + +(defun mm-mailcap-command (method file type-list) + (let ((ctl (cdr type-list)) + (beg 0) + (uses-stdin t) + out sub total) + (while (string-match "%{\\([^}]+\\)}\\|%s\\|%t\\|%%" method beg) + (push (substring method beg (match-beginning 0)) out) + (setq beg (match-end 0) + total (match-string 0 method) + sub (match-string 1 method)) + (cond + ((string= total "%%") + (push "%" out)) + ((string= total "%s") + (setq uses-stdin nil) + (push (mm-quote-arg file) out)) + ((string= total "%t") + (push (mm-quote-arg (car type-list)) out)) + (t + (push (mm-quote-arg (or (cdr (assq (intern sub) ctl)) "")) out)))) + (push (substring method beg (length method)) out) + (if uses-stdin + (progn + (push "<" out) + (push (mm-quote-arg file) out))) + (mapconcat 'identity (nreverse out) ""))) + (defun mm-remove-parts (handles) "Remove the displayed MIME parts represented by HANDLE." (if (and (listp handles) @@ -335,6 +472,7 @@ external if displayed external." (while (setq handle (pop handles)) (cond ((stringp handle) + ;; Do nothing. ) ((and (listp handle) (stringp (car handle))) @@ -351,6 +489,7 @@ external if displayed external." (while (setq handle (pop handles)) (cond ((stringp handle) + ;; Do nothing. ) ((and (listp handle) (stringp (car handle))) @@ -362,76 +501,99 @@ external if displayed external." "Remove the displayed MIME part represented by HANDLE." (when (listp handle) (let ((object (mm-handle-undisplayer handle))) - (condition-case () - (cond - ;; Internally displayed part. - ((mm-annotationp object) - (delete-annotation object)) - ((or (functionp object) - (and (listp object) - (eq (car object) 'lambda))) - (funcall object)) - ;; Externally displayed part. - ((consp object) - (condition-case () - (delete-file (car object)) - (error nil)) - (condition-case () - (delete-directory (file-name-directory (car object))) - (error nil)) - (condition-case () - (kill-buffer (cdr object)) - (error nil))) - ((bufferp object) - (when (buffer-live-p object) - (kill-buffer object)))) - (error nil)) + (ignore-errors + (cond + ;; Internally displayed part. + ((mm-annotationp object) + (delete-annotation object)) + ((or (functionp object) + (and (listp object) + (eq (car object) 'lambda))) + (funcall object)) + ;; Externally displayed part. + ((consp object) + (ignore-errors (delete-file (car object))) + (ignore-errors (delete-directory (file-name-directory (car object)))) + (ignore-errors (kill-buffer (cdr object)))) + ((bufferp object) + (when (buffer-live-p object) + (kill-buffer object))))) (mm-handle-set-undisplayer handle nil)))) (defun mm-display-inline (handle) - (let* ((type (car (mm-handle-type handle))) - (function (cadr (assoc type mm-inline-media-tests)))) + (let* ((type (mm-handle-media-type handle)) + (function (cadr (mm-assoc-string-match mm-inline-media-tests type)))) (funcall function handle) (goto-char (point-min)))) -(defun mm-inlinable-p (type) - "Say whether TYPE can be displayed inline." +(defun mm-assoc-string-match (alist type) + (dolist (elem alist) + (when (string-match (car elem) type) + (return elem)))) + +(defun mm-inlinable-p (handle) + "Say whether HANDLE can be displayed inline." (let ((alist mm-inline-media-tests) + (type (mm-handle-media-type handle)) test) (while alist - (when (equal type (caar alist)) + (when (string-match (caar alist) type) (setq test (caddar alist) alist nil) - (setq test (eval test))) + (setq test (funcall test handle))) (pop alist)) test)) -(defun mm-user-method (type) - "Return the user-defined method for TYPE." - (let ((methods mm-user-display-methods) +(defun mm-automatic-display-p (handle) + "Say whether the user wants HANDLE to be displayed automatically." + (let ((methods mm-automatic-display) + (type (mm-handle-media-type handle)) method result) (while (setq method (pop methods)) - (when (string-match (car method) type) - (when (or (not (eq (cdr method) 'inline)) - (mm-inlinable-p type)) - (setq result (cdr method) - methods nil)))) + (when (and (not (mm-inline-override-p handle)) + (string-match method type) + (mm-inlinable-p handle)) + (setq result t + methods nil))) result)) -(defun mm-automatic-display-p (type) - "Return the user-defined method for TYPE." - (let ((methods mm-user-automatic-display) +(defun mm-inlined-p (handle) + "Say whether the user wants HANDLE to be displayed automatically." + (let ((methods mm-inlined-types) + (type (mm-handle-media-type handle)) method result) (while (setq method (pop methods)) - (when (and (string-match method type) - (mm-inlinable-p type)) + (when (and (not (mm-inline-override-p handle)) + (string-match method type) + (mm-inlinable-p handle)) (setq result t methods nil))) result)) +(defun mm-attachment-override-p (handle) + "Say whether HANDLE should have attachment behavior overridden." + (let ((types mm-attachment-override-types) + (type (mm-handle-media-type handle)) + ty) + (catch 'found + (while (setq ty (pop types)) + (when (and (string-match ty type) + (mm-inlinable-p handle)) + (throw 'found t)))))) + +(defun mm-inline-override-p (handle) + "Say whether HANDLE should have inline behavior overridden." + (let ((types mm-inline-override-types) + (type (mm-handle-media-type handle)) + ty) + (catch 'found + (while (setq ty (pop types)) + (when (string-match ty type) + (throw 'found t)))))) + (defun mm-automatic-external-display-p (type) "Return the user-defined method for TYPE." - (let ((methods mm-user-automatic-external-display) + (let ((methods mm-automatic-external-display) method result) (while (setq method (pop methods)) (when (string-match method type) @@ -439,11 +601,6 @@ external if displayed external." methods nil))) result)) -(defun add-mime-display-method (type method) - "Make parts of TYPE be displayed with METHOD. -This overrides entries in the mailcap file." - (push (cons type method) mm-user-display-methods)) - (defun mm-destroy-part (handle) "Destroy the data structures connected to HANDLE." (when (listp handle) @@ -454,19 +611,6 @@ This overrides entries in the mailcap file." (defun mm-handle-displayed-p (handle) "Say whether HANDLE is displayed or not." (mm-handle-undisplayer handle)) - -(defun mm-quote-arg (arg) - "Return a version of ARG that is safe to evaluate in a shell." - (let ((pos 0) new-pos accum) - ;; *** bug: we don't handle newline characters properly - (while (setq new-pos (string-match "[;!`\"$\\& \t{} |()<>]" arg pos)) - (push (substring arg pos new-pos) accum) - (push "\\" accum) - (push (list (aref arg new-pos)) accum) - (setq pos (1+ new-pos))) - (if (= pos 0) - arg - (apply 'concat (nconc (nreverse accum) (list (substring arg pos))))))) ;;; ;;; Functions for outputting parts @@ -482,14 +626,23 @@ This overrides entries in the mailcap file." "Insert the contents of HANDLE in the current buffer." (let ((cur (current-buffer))) (save-excursion - (mm-with-unibyte-buffer - (insert-buffer-substring (mm-handle-buffer handle)) - (mm-decode-content-transfer-encoding - (mm-handle-encoding handle) - (car (mm-handle-type handle))) - (let ((temp (current-buffer))) - (set-buffer cur) - (insert-buffer temp)))))) + (if (member (mm-handle-media-supertype handle) '("text" "message")) + (with-temp-buffer + (insert-buffer-substring (mm-handle-buffer handle)) + (mm-decode-content-transfer-encoding + (mm-handle-encoding handle) + (mm-handle-media-type handle)) + (let ((temp (current-buffer))) + (set-buffer cur) + (insert-buffer-substring temp))) + (mm-with-unibyte-buffer + (insert-buffer-substring (mm-handle-buffer handle)) + (mm-decode-content-transfer-encoding + (mm-handle-encoding handle) + (mm-handle-media-type handle)) + (let ((temp (current-buffer))) + (set-buffer cur) + (insert-buffer-substring temp))))))) (defvar mm-default-directory nil) @@ -507,28 +660,22 @@ This overrides entries in the mailcap file." (or filename name "") (or mm-default-directory default-directory)))) (setq mm-default-directory (file-name-directory file)) - (mm-with-unibyte-buffer - (mm-insert-part handle) - (when (or (not (file-exists-p file)) - (yes-or-no-p (format "File %s already exists; overwrite? " - file))) - ;; Now every coding system is 100% binary within mm-with-unibyte-buffer - ;; Is text still special? - (let ((coding-system-for-write - (if (equal "text" (car (split-string - (car (mm-handle-type handle)) "/"))) - buffer-file-coding-system - 'binary)) - ;; Don't re-compress .gz & al. Arguably we should make - ;; `file-name-handler-alist' nil, but that would chop - ;; ange-ftp which it's reasonable to use here. - (inhibit-file-name-operation 'write-region) - (inhibit-file-name-handlers - (if (equal (car (mm-handle-type handle)) - "application/octet-stream") - (cons 'jka-compr-handler inhibit-file-name-handlers) - inhibit-file-name-handlers))) - (write-region (point-min) (point-max) file)))))) + (when (or (not (file-exists-p file)) + (yes-or-no-p (format "File %s already exists; overwrite? " + file))) + (mm-save-part-to-file handle file)))) + +(defun mm-save-part-to-file (handle file) + (mm-with-unibyte-buffer + (mm-insert-part handle) + (let ((coding-system-for-write 'binary) + ;; Don't re-compress .gz & al. Arguably we should make + ;; `file-name-handler-alist' nil, but that would chop + ;; ange-ftp, which is reasonable to use here. + (inhibit-file-name-operation 'write-region) + (inhibit-file-name-handlers + (cons 'jka-compr-handler inhibit-file-name-handlers))) + (write-region (point-min) (point-max) file)))) (defun mm-pipe-part (handle) "Pipe HANDLE to a process." @@ -541,44 +688,55 @@ This overrides entries in the mailcap file." (defun mm-interactively-view-part (handle) "Display HANDLE using METHOD." - (let* ((type (car (mm-handle-type handle))) + (let* ((type (mm-handle-media-type handle)) (methods (mapcar (lambda (i) (list (cdr (assoc 'viewer i)))) (mailcap-mime-info type 'all))) (method (completing-read "Viewer: " methods))) + (when (string= method "") + (error "No method given")) (mm-display-external (copy-sequence handle) method))) (defun mm-preferred-alternative (handles &optional preferred) "Say which of HANDLES are preferred." - (let ((prec (if preferred (list preferred) mm-alternative-precedence)) + (let ((prec (if preferred (list preferred) + (mm-preferred-alternative-precedence handles))) p h result type handle) (while (setq p (pop prec)) (setq h handles) (while h - (setq type - (if (stringp (caar h)) - (caar h) - (car (mm-handle-type (car h))))) (setq handle (car h)) + (setq type (mm-handle-media-type handle)) (when (and (equal p type) - (mm-automatic-display-p type) - (or (stringp (caar h)) - (not (mm-handle-disposition (car h))) - (equal (car (mm-handle-disposition (car h))) + (mm-automatic-display-p handle) + (or (stringp (car handle)) + (not (mm-handle-disposition handle)) + (equal (car (mm-handle-disposition handle)) "inline"))) - (setq result (car h) + (setq result handle h nil prec nil)) (pop h))) result)) +(defun mm-preferred-alternative-precedence (handles) + "Return the precedence based on HANDLES and mm-discouraged-alternatives." + (let ((seq (nreverse (mapcar (lambda (h) + (mm-handle-media-type h)) + handles)))) + (dolist (disc (reverse mm-discouraged-alternatives)) + (dolist (elem (copy-sequence seq)) + (when (string-match disc elem) + (setq seq (nconc (delete elem seq) (list elem)))))) + seq)) + (defun mm-get-content-id (id) "Return the handle(s) referred to by ID." (cdr (assoc id mm-content-id-alist))) -(defun mm-get-image (handle) +(defun mm-get-image-emacs (handle) "Return an image instance based on HANDLE." - (let ((type (cadr (split-string (car (mm-handle-type handle)) "/"))) + (let ((type (mm-handle-media-subtype handle)) spec) ;; Allow some common translations. (setq type @@ -593,15 +751,95 @@ This overrides entries in the mailcap file." (mm-insert-part handle) (prog1 (setq spec - (make-glyph `[,(intern type) :data ,(buffer-string)])) + (ignore-errors + (cond + ((equal type "xbm") + ;; xbm images require special handling, since + ;; the only way to create glyphs from these + ;; (without a ton of work) is to write them + ;; out to a file, and then create a file + ;; specifier. + (error "Don't know what to do for XBMs right now.")) + (t + (list 'image :type (intern type) :data (buffer-string)))))) (mm-handle-set-cache handle spec)))))) +(defun mm-get-image-xemacs (handle) + "Return an image instance based on HANDLE." + (let ((type (mm-handle-media-subtype handle)) + spec) + ;; Allow some common translations. + (setq type + (cond + ((equal type "x-pixmap") + "xpm") + ((equal type "x-xbitmap") + "xbm") + (t type))) + (or (mm-handle-cache handle) + (mm-with-unibyte-buffer + (mm-insert-part handle) + (prog1 + (setq spec + (ignore-errors + (cond + ((equal type "xbm") + ;; xbm images require special handling, since + ;; the only way to create glyphs from these + ;; (without a ton of work) is to write them + ;; out to a file, and then create a file + ;; specifier. + (let ((file (make-temp-name + (expand-file-name "emm.xbm" + mm-tmp-directory)))) + (unwind-protect + (progn + (write-region (point-min) (point-max) file) + (make-glyph (list (cons 'x file)))) + (ignore-errors + (delete-file file))))) + (t + (make-glyph + (vector (intern type) :data (buffer-string))))))) + (mm-handle-set-cache handle spec)))))) + +(defun mm-get-image (handle) + (if mm-xemacs-p + (mm-get-image-xemacs handle) + (mm-get-image-emacs handle))) + (defun mm-image-fit-p (handle) "Say whether the image in HANDLE will fit the current window." (let ((image (mm-get-image handle))) - (or mm-all-images-fit - (and (< (glyph-width image) (window-pixel-width)) - (< (glyph-height image) (window-pixel-height)))))) + (if (fboundp 'glyph-width) + ;; XEmacs' glyphs can actually tell us about their width, so + ;; lets be nice and smart about them. + (or mm-inline-large-images + (and (< (glyph-width image) (window-pixel-width)) + (< (glyph-height image) (window-pixel-height)))) + ;; Let's just inline everything under Emacs 21, since the image + ;; specification there doesn't actually get the width/height + ;; until you render the image. + t))) + +(defun mm-valid-image-format-p (format) + "Say whether FORMAT can be displayed natively by Emacs." + (cond + ;; Handle XEmacs + ((fboundp 'valid-image-instantiator-format-p) + (valid-image-instantiator-format-p format)) + ;; Handle Emacs 21 + ((fboundp 'image-type-available-p) + (image-type-available-p format)) + ;; Nobody else can do images yet. + (t + nil))) + +(defun mm-valid-and-fit-image-p (format handle) + "Say whether FORMAT can be displayed natively and HANDLE fits the window." + (and window-system + (mm-valid-image-format-p format) + (mm-image-fit-p handle))) (provide 'mm-decode)