Importing Gnus v5.8.3.
[elisp/gnus.git-] / lisp / mm-decode.el
1 ;;; mm-decode.el --- Functions for decoding MIME things
2 ;; Copyright (C) 1998,99 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
5 ;;      MORIOKA Tomohiko <morioka@jaist.ac.jp>
6 ;; This file is part of GNU Emacs.
7
8 ;; GNU Emacs is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation; either version 2, or (at your option)
11 ;; any later version.
12
13 ;; GNU Emacs is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 ;; GNU General Public License for more details.
17
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
20 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 ;; Boston, MA 02111-1307, USA.
22
23 ;;; Commentary:
24
25 ;;; Code:
26
27 (require 'mail-parse)
28 (require 'mailcap)
29 (require 'mm-bodies)
30
31 (defgroup mime-display ()
32   "Display of MIME in mail and news articles."
33   :link '(custom-manual "(emacs-mime)Customization")
34   :group 'mail
35   :group 'news)
36
37 ;;; Convenience macros.
38
39 (defmacro mm-handle-buffer (handle)
40   `(nth 0 ,handle))
41 (defmacro mm-handle-type (handle)
42   `(nth 1 ,handle))
43 (defsubst mm-handle-media-type (handle)
44   (if (stringp (car handle))
45       (car handle)
46     (car (mm-handle-type handle))))
47 (defsubst mm-handle-media-supertype (handle)
48   (car (split-string (mm-handle-media-type handle) "/")))
49 (defsubst mm-handle-media-subtype (handle)
50   (cadr (split-string (mm-handle-media-type handle) "/")))
51 (defmacro mm-handle-encoding (handle)
52   `(nth 2 ,handle))
53 (defmacro mm-handle-undisplayer (handle)
54   `(nth 3 ,handle))
55 (defmacro mm-handle-set-undisplayer (handle function)
56   `(setcar (nthcdr 3 ,handle) ,function))
57 (defmacro mm-handle-disposition (handle)
58   `(nth 4 ,handle))
59 (defmacro mm-handle-description (handle)
60   `(nth 5 ,handle))
61 (defmacro mm-handle-cache (handle)
62   `(nth 6 ,handle))
63 (defmacro mm-handle-set-cache (handle contents)
64   `(setcar (nthcdr 6 ,handle) ,contents))
65 (defmacro mm-handle-id (handle)
66   `(nth 7 ,handle))
67 (defmacro mm-make-handle (&optional buffer type encoding undisplayer
68                                     disposition description cache
69                                     id)
70   `(list ,buffer ,type ,encoding ,undisplayer
71          ,disposition ,description ,cache ,id))
72
73 (defcustom mm-inline-media-tests
74   '(("image/jpeg"
75      mm-inline-image
76      (lambda (handle)
77        (mm-valid-and-fit-image-p 'jpeg handle)))
78     ("image/png"
79      mm-inline-image
80      (lambda (handle)
81        (mm-valid-and-fit-image-p 'png handle)))
82     ("image/gif"
83      mm-inline-image
84      (lambda (handle)
85        (mm-valid-and-fit-image-p 'gif handle)))
86     ("image/tiff"
87      mm-inline-image
88      (lambda (handle)
89        (mm-valid-and-fit-image-p 'tiff handle)) )
90     ("image/xbm"
91      mm-inline-image
92      (lambda (handle)
93        (mm-valid-and-fit-image-p 'xbm handle)))
94     ("image/x-xbitmap"
95      mm-inline-image
96      (lambda (handle)
97        (mm-valid-and-fit-image-p 'xbm handle)))
98     ("image/xpm"
99      mm-inline-image
100      (lambda (handle)
101        (mm-valid-and-fit-image-p 'xpm handle)))
102     ("image/x-pixmap"
103      mm-inline-image
104      (lambda (handle)
105        (mm-valid-and-fit-image-p 'xpm handle)))
106     ("image/bmp"
107      mm-inline-image
108      (lambda (handle)
109        (mm-valid-and-fit-image-p 'bmp handle)))
110     ("text/plain" mm-inline-text identity)
111     ("text/enriched" mm-inline-text identity)
112     ("text/richtext" mm-inline-text identity)
113     ("text/x-patch" mm-display-patch-inline
114      (lambda (handle)
115        (locate-library "diff-mode")))
116     ("text/html"
117      mm-inline-text
118      (lambda (handle)
119        (locate-library "w3")))
120     ("text/x-vcard"
121      mm-inline-text
122      (lambda (handle)
123        (or (featurep 'vcard)
124            (locate-library "vcard"))))
125     ("message/delivery-status" mm-inline-text identity)
126     ("message/rfc822" mm-inline-message identity)
127     ("text/.*" mm-inline-text identity)
128     ("audio/wav" mm-inline-audio
129      (lambda (handle)
130        (and (or (featurep 'nas-sound) (featurep 'native-sound))
131             (device-sound-enabled-p))))
132     ("audio/au"
133      mm-inline-audio
134      (lambda (handle)
135        (and (or (featurep 'nas-sound) (featurep 'native-sound))
136             (device-sound-enabled-p))))
137     ("application/pgp-signature" ignore identity)
138     ("multipart/alternative" ignore identity)
139     ("multipart/mixed" ignore identity)
140     ("multipart/related" ignore identity))
141   "Alist of media types/tests saying whether types can be displayed inline."
142   :type '(repeat (list (string :tag "MIME type")
143                        (function :tag "Display function")
144                        (function :tag "Display test")))
145   :group 'mime-display)
146
147 (defcustom mm-inlined-types
148   '("image/.*" "text/.*" "message/delivery-status" "message/rfc822"
149     "application/pgp-signature")
150   "List of media types that are to be displayed inline."
151   :type '(repeat string)
152   :group 'mime-display)
153   
154 (defcustom mm-automatic-display
155   '("text/plain" "text/enriched" "text/richtext" "text/html"
156     "text/x-vcard" "image/.*" "message/delivery-status" "multipart/.*"
157     "message/rfc822" "text/x-patch" "application/pgp-signature")
158   "A list of MIME types to be displayed automatically."
159   :type '(repeat string)
160   :group 'mime-display)
161
162 (defcustom mm-attachment-override-types '("text/x-vcard")
163   "Types to have \"attachment\" ignored if they can be displayed inline."
164   :type '(repeat string)
165   :group 'mime-display)
166
167 (defcustom mm-inline-override-types nil
168   "Types to be treated as attachments even if they can be displayed inline."
169   :type '(repeat string)
170   :group 'mime-display)
171
172 (defcustom mm-automatic-external-display nil
173   "List of MIME type regexps that will be displayed externally automatically."
174   :type '(repeat string)
175   :group 'mime-display)
176
177 (defcustom mm-discouraged-alternatives nil
178   "List of MIME types that are discouraged when viewing multipart/alternative.
179 Viewing agents are supposed to view the last possible part of a message,
180 as that is supposed to be the richest.  However, users may prefer other
181 types instead, and this list says what types are most unwanted.  If,
182 for instance, text/html parts are very unwanted, and text/richtech are
183 somewhat unwanted, then the value of this variable should be set
184 to:
185
186  (\"text/html\" \"text/richtext\")"
187   :type '(repeat string)
188   :group 'mime-display)
189
190 (defvar mm-tmp-directory
191   (cond ((fboundp 'temp-directory) (temp-directory))
192         ((boundp 'temporary-file-directory) temporary-file-directory)
193         ("/tmp/"))
194   "Where mm will store its temporary files.")
195
196 (defcustom mm-inline-large-images nil
197   "If non-nil, then all images fit in the buffer."
198   :type 'boolean
199   :group 'mime-display)
200
201 ;;; Internal variables.
202
203 (defvar mm-dissection-list nil)
204 (defvar mm-last-shell-command "")
205 (defvar mm-content-id-alist nil)
206
207 ;;; The functions.
208
209 (defun mm-dissect-buffer (&optional no-strict-mime)
210   "Dissect the current buffer and return a list of MIME handles."
211   (save-excursion
212     (let (ct ctl type subtype cte cd description id result)
213       (save-restriction
214         (mail-narrow-to-head)
215         (when (or no-strict-mime
216                   (mail-fetch-field "mime-version"))
217           (setq ct (mail-fetch-field "content-type")
218                 ctl (ignore-errors (mail-header-parse-content-type ct))
219                 cte (mail-fetch-field "content-transfer-encoding")
220                 cd (mail-fetch-field "content-disposition")
221                 description (mail-fetch-field "content-description")
222                 id (mail-fetch-field "content-id"))))
223       (if (or (not ctl)
224               (not (string-match "/" (car ctl))))
225           (mm-dissect-singlepart
226            '("text/plain") 
227            (and cte (intern (downcase (mail-header-remove-whitespace
228                                        (mail-header-remove-comments
229                                         cte)))))
230            no-strict-mime
231            (and cd (ignore-errors (mail-header-parse-content-disposition cd)))
232            description)
233         (setq type (split-string (car ctl) "/"))
234         (setq subtype (cadr type)
235               type (pop type))
236         (setq
237          result
238          (cond
239           ((equal type "multipart")
240            (cons (car ctl) (mm-dissect-multipart ctl)))
241           (t
242            (mm-dissect-singlepart
243             ctl
244             (and cte (intern (downcase (mail-header-remove-whitespace
245                                         (mail-header-remove-comments
246                                          cte)))))
247             no-strict-mime
248             (and cd (ignore-errors (mail-header-parse-content-disposition cd)))
249             description id))))
250         (when id
251           (when (string-match " *<\\(.*\\)> *" id)
252             (setq id (match-string 1 id)))
253           (push (cons id result) mm-content-id-alist))
254         result))))
255
256 (defun mm-dissect-singlepart (ctl cte &optional force cdl description id)
257   (when (or force
258             (not (equal "text/plain" (car ctl))))
259     (let ((res (mm-make-handle
260                 (mm-copy-to-buffer) ctl cte nil cdl description nil id)))
261       (push (car res) mm-dissection-list)
262       res)))
263
264 (defun mm-remove-all-parts ()
265   "Remove all MIME handles."
266   (interactive)
267   (mapcar 'mm-remove-part mm-dissection-list)
268   (setq mm-dissection-list nil))
269
270 (defun mm-dissect-multipart (ctl)
271   (goto-char (point-min))
272   (let* ((boundary (concat "\n--" (mail-content-type-get ctl 'boundary)))
273          (close-delimiter (concat (regexp-quote boundary) "--[ \t]*$"))
274          start parts
275          (end (save-excursion
276                 (goto-char (point-max))
277                 (if (re-search-backward close-delimiter nil t)
278                     (match-beginning 0)
279                   (point-max)))))
280     (while (search-forward boundary end t)
281       (goto-char (match-beginning 0))
282       (when start
283         (save-excursion
284           (save-restriction
285             (narrow-to-region start (point))
286             (setq parts (nconc (list (mm-dissect-buffer t)) parts)))))
287       (forward-line 2)
288       (setq start (point)))
289     (when start
290       (save-excursion
291         (save-restriction
292           (narrow-to-region start end)
293           (setq parts (nconc (list (mm-dissect-buffer t)) parts)))))
294     (nreverse parts)))
295
296 (defun mm-copy-to-buffer ()
297   "Copy the contents of the current buffer to a fresh buffer."
298   (save-excursion
299     (let ((obuf (current-buffer))
300           beg)
301       (goto-char (point-min))
302       (search-forward-regexp "^\n" nil t)
303       (setq beg (point))
304       (set-buffer (generate-new-buffer " *mm*"))
305       (insert-buffer-substring obuf beg)
306       (current-buffer))))
307
308 (defun mm-display-part (handle &optional no-default)
309   "Display the MIME part represented by HANDLE.
310 Returns nil if the part is removed; inline if displayed inline;
311 external if displayed external."
312   (save-excursion
313     (mailcap-parse-mailcaps)
314     (if (mm-handle-displayed-p handle)
315         (mm-remove-part handle)
316       (let* ((type (mm-handle-media-type handle))
317              (method (mailcap-mime-info type)))
318         (if (mm-inlined-p handle)
319             (progn
320               (forward-line 1)
321               (mm-display-inline handle)
322               'inline)
323           (when (or method
324                     (not no-default))
325             (if (and (not method)
326                      (equal "text" (car (split-string type))))
327                 (progn
328                   (forward-line 1)
329                   (mm-insert-inline handle (mm-get-part handle))
330                   'inline)
331               (mm-display-external
332                handle (or method 'mailcap-save-binary-file)))))))))
333
334 (defun mm-display-external (handle method)
335   "Display HANDLE using METHOD."
336   (let ((outbuf (current-buffer)))
337     (mm-with-unibyte-buffer
338       (if (functionp method)
339           (let ((cur (current-buffer)))
340             (if (eq method 'mailcap-save-binary-file)
341                 (progn
342                   (set-buffer (generate-new-buffer "*mm*"))
343                   (setq method nil))
344               (mm-insert-part handle)
345               (let ((win (get-buffer-window cur t)))
346                 (when win
347                   (select-window win)))
348               (switch-to-buffer (generate-new-buffer "*mm*")))
349             (buffer-disable-undo)
350             (mm-set-buffer-file-coding-system mm-binary-coding-system)
351             (insert-buffer-substring cur)
352             (message "Viewing with %s" method)
353             (let ((mm (current-buffer))
354                   (non-viewer (assq 'non-viewer
355                                     (mailcap-mime-info
356                                      (mm-handle-media-type handle) t))))
357               (unwind-protect
358                   (if method
359                       (funcall method)
360                     (mm-save-part handle))
361                 (when (and (not non-viewer)
362                            method)
363                   (mm-handle-set-undisplayer handle mm)))))
364         ;; The function is a string to be executed.
365         (mm-insert-part handle)
366         (let* ((dir (make-temp-name (expand-file-name "emm." mm-tmp-directory)))
367                (filename (mail-content-type-get
368                           (mm-handle-disposition handle) 'filename))
369                (mime-info (mailcap-mime-info
370                            (mm-handle-media-type handle) t))
371                (needsterm (or (assoc "needsterm" mime-info)
372                               (assoc "needsterminal" mime-info)))
373                (copiousoutput (assoc "copiousoutput" mime-info))
374                file buffer)
375           ;; We create a private sub-directory where we store our files.
376           (make-directory dir)
377           (set-file-modes dir 448)
378           (if filename
379               (setq file (expand-file-name (file-name-nondirectory filename)
380                                            dir))
381             (setq file (make-temp-name (expand-file-name "mm." dir))))
382           (let ((coding-system-for-write mm-binary-coding-system))
383             (write-region (point-min) (point-max) file nil 'nomesg))
384           (message "Viewing with %s" method)
385           (cond (needsterm
386                  (unwind-protect
387                      (start-process "*display*" nil
388                                     "xterm"
389                                     "-e" shell-file-name 
390                                     shell-command-switch
391                                     (mm-mailcap-command
392                                      method file (mm-handle-type handle)))
393                    (mm-handle-set-undisplayer handle (cons file buffer)))
394                  (message "Displaying %s..." (format method file))
395                  'external)
396                 (copiousoutput
397                  (with-current-buffer outbuf
398                    (forward-line 1)
399                    (mm-insert-inline
400                     handle
401                     (unwind-protect
402                         (progn
403                           (call-process shell-file-name nil
404                                         (setq buffer 
405                                               (generate-new-buffer "*mm*"))
406                                         nil
407                                         shell-command-switch
408                                         (mm-mailcap-command
409                                          method file (mm-handle-type handle)))
410                           (if (buffer-live-p buffer)
411                               (save-excursion
412                                 (set-buffer buffer)
413                                 (buffer-string))))
414                       (progn
415                         (ignore-errors (delete-file file))
416                         (ignore-errors (delete-directory
417                                         (file-name-directory file)))
418                         (ignore-errors (kill-buffer buffer))))))
419                  'inline)
420                 (t
421                  (unwind-protect
422                      (start-process "*display*"
423                                     (setq buffer
424                                           (generate-new-buffer "*mm*"))
425                                     shell-file-name
426                                     shell-command-switch
427                                     (mm-mailcap-command
428                                      method file (mm-handle-type handle)))
429                    (mm-handle-set-undisplayer handle (cons file buffer)))
430                  (message "Displaying %s..." (format method file))
431                  'external)))))))
432   
433 (defun mm-mailcap-command (method file type-list)
434   (let ((ctl (cdr type-list))
435         (beg 0)
436         out sub total)
437     (while (string-match "%{\\([^}]+\\)}\\|%s\\|%t" method beg)
438       (push (substring method beg (match-beginning 0)) out)
439       (setq beg (match-end 0)
440             total (match-string 0 method)
441             sub (match-string 1 method))
442       (cond
443        ((string= total "%s")
444         (push (mm-quote-arg file) out))
445        ((string= total "%t")
446         (push (mm-quote-arg (car type-list)) out))
447        (t
448         (push (mm-quote-arg (or (cdr (assq (intern sub) ctl)) "")) out))))
449     (push (substring method beg (length method)) out)
450     (mapconcat 'identity (nreverse out) "")))
451     
452 (defun mm-remove-parts (handles)
453   "Remove the displayed MIME parts represented by HANDLE."
454   (if (and (listp handles)
455            (bufferp (car handles)))
456       (mm-remove-part handles)
457     (let (handle)
458       (while (setq handle (pop handles))
459         (cond
460          ((stringp handle)
461           ;; Do nothing.
462           )
463          ((and (listp handle)
464                (stringp (car handle)))
465           (mm-remove-parts (cdr handle)))
466          (t
467           (mm-remove-part handle)))))))
468
469 (defun mm-destroy-parts (handles)
470   "Remove the displayed MIME parts represented by HANDLE."
471   (if (and (listp handles)
472            (bufferp (car handles)))
473       (mm-destroy-part handles)
474     (let (handle)
475       (while (setq handle (pop handles))
476         (cond
477          ((stringp handle)
478           ;; Do nothing.
479           )
480          ((and (listp handle)
481                (stringp (car handle)))
482           (mm-destroy-parts (cdr handle)))
483          (t
484           (mm-destroy-part handle)))))))
485
486 (defun mm-remove-part (handle)
487   "Remove the displayed MIME part represented by HANDLE."
488   (when (listp handle)
489     (let ((object (mm-handle-undisplayer handle)))
490       (ignore-errors
491         (cond
492          ;; Internally displayed part.
493          ((mm-annotationp object)
494           (delete-annotation object))
495          ((or (functionp object)
496               (and (listp object)
497                    (eq (car object) 'lambda)))
498           (funcall object))
499          ;; Externally displayed part.
500          ((consp object)
501           (ignore-errors (delete-file (car object)))
502           (ignore-errors (delete-directory (file-name-directory (car object))))
503           (ignore-errors (kill-buffer (cdr object))))
504          ((bufferp object)
505           (when (buffer-live-p object)
506             (kill-buffer object)))))
507       (mm-handle-set-undisplayer handle nil))))
508
509 (defun mm-display-inline (handle)
510   (let* ((type (mm-handle-media-type handle))
511          (function (cadr (mm-assoc-string-match mm-inline-media-tests type))))
512     (funcall function handle)
513     (goto-char (point-min))))
514
515 (defun mm-assoc-string-match (alist type)
516   (dolist (elem alist)
517     (when (string-match (car elem) type)
518       (return elem))))
519
520 (defun mm-inlinable-p (handle)
521   "Say whether HANDLE can be displayed inline."
522   (let ((alist mm-inline-media-tests)
523         (type (mm-handle-media-type handle))
524         test)
525     (while alist
526       (when (string-match (caar alist) type)
527         (setq test (caddar alist)
528               alist nil)
529         (setq test (funcall test handle)))
530       (pop alist))
531     test))
532
533 (defun mm-automatic-display-p (handle)
534   "Say whether the user wants HANDLE to be displayed automatically."
535   (let ((methods mm-automatic-display)
536         (type (mm-handle-media-type handle))
537         method result)
538     (while (setq method (pop methods))
539       (when (and (not (mm-inline-override-p handle))
540                  (string-match method type)
541                  (mm-inlinable-p handle))
542         (setq result t
543               methods nil)))
544     result))
545
546 (defun mm-inlined-p (handle)
547   "Say whether the user wants HANDLE to be displayed automatically."
548   (let ((methods mm-inlined-types)
549         (type (mm-handle-media-type handle))
550         method result)
551     (while (setq method (pop methods))
552       (when (and (not (mm-inline-override-p handle))
553                  (string-match method type)
554                  (mm-inlinable-p handle))
555         (setq result t
556               methods nil)))
557     result))
558
559 (defun mm-attachment-override-p (handle)
560   "Say whether HANDLE should have attachment behavior overridden."
561   (let ((types mm-attachment-override-types)
562         (type (mm-handle-media-type handle))
563         ty)
564     (catch 'found
565       (while (setq ty (pop types))
566         (when (and (string-match ty type)
567                    (mm-inlinable-p handle))
568           (throw 'found t))))))
569
570 (defun mm-inline-override-p (handle)
571   "Say whether HANDLE should have inline behavior overridden."
572   (let ((types mm-inline-override-types)
573         (type (mm-handle-media-type handle))
574         ty)
575     (catch 'found
576       (while (setq ty (pop types))
577         (when (string-match ty type)
578           (throw 'found t))))))
579
580 (defun mm-automatic-external-display-p (type)
581   "Return the user-defined method for TYPE."
582   (let ((methods mm-automatic-external-display)
583         method result)
584     (while (setq method (pop methods))
585       (when (string-match method type)
586         (setq result t
587               methods nil)))
588     result))
589
590 (defun mm-destroy-part (handle)
591   "Destroy the data structures connected to HANDLE."
592   (when (listp handle)
593     (mm-remove-part handle)
594     (when (buffer-live-p (mm-handle-buffer handle))
595       (kill-buffer (mm-handle-buffer handle)))))
596
597 (defun mm-handle-displayed-p (handle)
598   "Say whether HANDLE is displayed or not."
599   (mm-handle-undisplayer handle))
600
601 ;;;
602 ;;; Functions for outputting parts
603 ;;;
604
605 (defun mm-get-part (handle)
606   "Return the contents of HANDLE as a string."
607   (mm-with-unibyte-buffer
608     (mm-insert-part handle)
609     (buffer-string)))
610
611 (defun mm-insert-part (handle)
612   "Insert the contents of HANDLE in the current buffer."
613   (let ((cur (current-buffer)))
614     (save-excursion
615       (if (member (mm-handle-media-supertype handle) '("text" "message"))
616           (with-temp-buffer
617             (insert-buffer-substring (mm-handle-buffer handle))
618             (mm-decode-content-transfer-encoding
619              (mm-handle-encoding handle)
620              (mm-handle-media-type handle))
621             (let ((temp (current-buffer)))
622               (set-buffer cur)
623               (insert-buffer-substring temp)))
624         (mm-with-unibyte-buffer
625           (insert-buffer-substring (mm-handle-buffer handle))
626           (mm-decode-content-transfer-encoding
627            (mm-handle-encoding handle)
628            (mm-handle-media-type handle))
629           (let ((temp (current-buffer)))
630             (set-buffer cur)
631             (insert-buffer-substring temp)))))))
632
633 (defvar mm-default-directory nil)
634
635 (defun mm-save-part (handle)
636   "Write HANDLE to a file."
637   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
638          (filename (mail-content-type-get
639                     (mm-handle-disposition handle) 'filename))
640          file)
641     (when filename
642       (setq filename (file-name-nondirectory filename)))
643     (setq file
644           (read-file-name "Save MIME part to: "
645                           (expand-file-name
646                            (or filename name "")
647                            (or mm-default-directory default-directory))))
648     (setq mm-default-directory (file-name-directory file))
649     (when (or (not (file-exists-p file))
650               (yes-or-no-p (format "File %s already exists; overwrite? "
651                                    file)))
652       (mm-save-part-to-file handle file))))
653
654 (defun mm-save-part-to-file (handle file)
655   (mm-with-unibyte-buffer
656     (mm-insert-part handle)
657     (let ((coding-system-for-write 'binary)
658           ;; Don't re-compress .gz & al.  Arguably we should make
659           ;; `file-name-handler-alist' nil, but that would chop
660           ;; ange-ftp, which is reasonable to use here.
661           (inhibit-file-name-operation 'write-region)
662           (inhibit-file-name-handlers
663            (cons 'jka-compr-handler inhibit-file-name-handlers)))
664       (write-region (point-min) (point-max) file))))
665
666 (defun mm-pipe-part (handle)
667   "Pipe HANDLE to a process."
668   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
669          (command
670           (read-string "Shell command on MIME part: " mm-last-shell-command)))
671     (mm-with-unibyte-buffer
672       (mm-insert-part handle)
673       (shell-command-on-region (point-min) (point-max) command nil))))
674
675 (defun mm-interactively-view-part (handle)
676   "Display HANDLE using METHOD."
677   (let* ((type (mm-handle-media-type handle))
678          (methods
679           (mapcar (lambda (i) (list (cdr (assoc 'viewer i))))
680                   (mailcap-mime-info type 'all)))
681          (method (completing-read "Viewer: " methods)))
682     (when (string= method "")
683       (error "No method given"))
684     (mm-display-external (copy-sequence handle) method)))
685
686 (defun mm-preferred-alternative (handles &optional preferred)
687   "Say which of HANDLES are preferred."
688   (let ((prec (if preferred (list preferred)
689                 (mm-preferred-alternative-precedence handles)))
690         p h result type handle)
691     (while (setq p (pop prec))
692       (setq h handles)
693       (while h
694         (setq handle (car h))
695         (setq type (mm-handle-media-type handle))
696         (when (and (equal p type)
697                    (mm-automatic-display-p handle)
698                    (or (stringp (car handle))
699                        (not (mm-handle-disposition handle))
700                        (equal (car (mm-handle-disposition handle))
701                               "inline")))
702           (setq result handle
703                 h nil
704                 prec nil))
705         (pop h)))
706     result))
707
708 (defun mm-preferred-alternative-precedence (handles)
709   "Return the precedence based on HANDLES and mm-discouraged-alternatives."
710   (let ((seq (nreverse (mapcar (lambda (h)
711                                  (mm-handle-media-type h))
712                                handles))))
713     (dolist (disc (reverse mm-discouraged-alternatives))
714       (dolist (elem (copy-sequence seq))
715         (when (string-match disc elem)
716           (setq seq (nconc (delete elem seq) (list elem))))))
717     seq))
718
719 (defun mm-get-content-id (id)
720   "Return the handle(s) referred to by ID."
721   (cdr (assoc id mm-content-id-alist)))
722
723 (defun mm-get-image (handle)
724   "Return an image instance based on HANDLE."
725   (let ((type (mm-handle-media-subtype handle))
726         spec)
727     ;; Allow some common translations.
728     (setq type
729           (cond
730            ((equal type "x-pixmap")
731             "xpm")
732            ((equal type "x-xbitmap")
733             "xbm")
734            (t type)))
735     (or (mm-handle-cache handle)
736         (mm-with-unibyte-buffer
737           (mm-insert-part handle)
738           (prog1
739               (setq spec
740                     (ignore-errors
741                       (cond
742                        ((equal type "xbm")
743                         ;; xbm images require special handling, since
744                         ;; the only way to create glyphs from these
745                         ;; (without a ton of work) is to write them
746                         ;; out to a file, and then create a file
747                         ;; specifier.
748                         (let ((file (make-temp-name
749                                      (expand-file-name "emm.xbm"
750                                                        mm-tmp-directory))))
751                           (unwind-protect
752                               (progn
753                                 (write-region (point-min) (point-max) file)
754                                 (make-glyph (list (cons 'x file))))
755                             (ignore-errors
756                               (delete-file file)))))
757                        (t
758                         (make-glyph
759                          (vector (intern type) :data (buffer-string)))))))
760             (mm-handle-set-cache handle spec))))))
761
762 (defun mm-image-fit-p (handle)
763   "Say whether the image in HANDLE will fit the current window."
764   (let ((image (mm-get-image handle)))
765     (or mm-inline-large-images
766         (and (< (glyph-width image) (window-pixel-width))
767              (< (glyph-height image) (window-pixel-height))))))
768
769 (defun mm-valid-image-format-p (format)
770   "Say whether FORMAT can be displayed natively by Emacs."
771   (and (fboundp 'valid-image-instantiator-format-p)
772        (valid-image-instantiator-format-p format)))
773
774 (defun mm-valid-and-fit-image-p (format handle)
775   "Say whether FORMAT can be displayed natively and HANDLE fits the window."
776   (and window-system
777        (mm-valid-image-format-p format)
778        (mm-image-fit-p handle)))
779
780 (provide 'mm-decode)
781
782 ;; mm-decode.el ends here