* fill-flowed.el: New file.
[gnus] / 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       (when cte
224         (setq cte (mail-header-strip cte)))
225       (if (or (not ctl)
226               (not (string-match "/" (car ctl))))
227           (mm-dissect-singlepart
228            '("text/plain") 
229            (and cte (intern (downcase (mail-header-remove-whitespace
230                                        (mail-header-remove-comments
231                                         cte)))))
232            no-strict-mime
233            (and cd (ignore-errors (mail-header-parse-content-disposition cd)))
234            description)
235         (setq type (split-string (car ctl) "/"))
236         (setq subtype (cadr type)
237               type (pop type))
238         (setq
239          result
240          (cond
241           ((equal type "multipart")
242            (cons (car ctl) (mm-dissect-multipart ctl)))
243           (t
244            (mm-dissect-singlepart
245             ctl
246             (and cte (intern (downcase (mail-header-remove-whitespace
247                                         (mail-header-remove-comments
248                                          cte)))))
249             no-strict-mime
250             (and cd (ignore-errors (mail-header-parse-content-disposition cd)))
251             description id))))
252         (when id
253           (when (string-match " *<\\(.*\\)> *" id)
254             (setq id (match-string 1 id)))
255           (push (cons id result) mm-content-id-alist))
256         result))))
257
258 (defun mm-dissect-singlepart (ctl cte &optional force cdl description id)
259   (when (or force
260             (if (equal "text/plain" (car ctl))
261                 (assoc 'format ctl)
262               t))
263     (let ((res (mm-make-handle
264                 (mm-copy-to-buffer) ctl cte nil cdl description nil id)))
265       (push (car res) mm-dissection-list)
266       res)))
267
268 (defun mm-remove-all-parts ()
269   "Remove all MIME handles."
270   (interactive)
271   (mapcar 'mm-remove-part mm-dissection-list)
272   (setq mm-dissection-list nil))
273
274 (defun mm-dissect-multipart (ctl)
275   (goto-char (point-min))
276   (let* ((boundary (concat "\n--" (mail-content-type-get ctl 'boundary)))
277          (close-delimiter (concat (regexp-quote boundary) "--[ \t]*$"))
278          start parts
279          (end (save-excursion
280                 (goto-char (point-max))
281                 (if (re-search-backward close-delimiter nil t)
282                     (match-beginning 0)
283                   (point-max)))))
284     (while (search-forward boundary end t)
285       (goto-char (match-beginning 0))
286       (when start
287         (save-excursion
288           (save-restriction
289             (narrow-to-region start (point))
290             (setq parts (nconc (list (mm-dissect-buffer t)) parts)))))
291       (forward-line 2)
292       (setq start (point)))
293     (when start
294       (save-excursion
295         (save-restriction
296           (narrow-to-region start end)
297           (setq parts (nconc (list (mm-dissect-buffer t)) parts)))))
298     (nreverse parts)))
299
300 (defun mm-copy-to-buffer ()
301   "Copy the contents of the current buffer to a fresh buffer."
302   (save-excursion
303     (let ((obuf (current-buffer))
304           beg)
305       (goto-char (point-min))
306       (search-forward-regexp "^\n" nil t)
307       (setq beg (point))
308       (set-buffer (generate-new-buffer " *mm*"))
309       (insert-buffer-substring obuf beg)
310       (current-buffer))))
311
312 (defun mm-display-part (handle &optional no-default)
313   "Display the MIME part represented by HANDLE.
314 Returns nil if the part is removed; inline if displayed inline;
315 external if displayed external."
316   (save-excursion
317     (mailcap-parse-mailcaps)
318     (if (mm-handle-displayed-p handle)
319         (mm-remove-part handle)
320       (let* ((type (mm-handle-media-type handle))
321              (method (mailcap-mime-info type)))
322         (if (mm-inlined-p handle)
323             (progn
324               (forward-line 1)
325               (mm-display-inline handle)
326               'inline)
327           (when (or method
328                     (not no-default))
329             (if (and (not method)
330                      (equal "text" (car (split-string type))))
331                 (progn
332                   (forward-line 1)
333                   (mm-insert-inline handle (mm-get-part handle))
334                   'inline)
335               (mm-display-external
336                handle (or method 'mailcap-save-binary-file)))))))))
337
338 (defun mm-display-external (handle method)
339   "Display HANDLE using METHOD."
340   (let ((outbuf (current-buffer)))
341     (mm-with-unibyte-buffer
342       (if (functionp method)
343           (let ((cur (current-buffer)))
344             (if (eq method 'mailcap-save-binary-file)
345                 (progn
346                   (set-buffer (generate-new-buffer "*mm*"))
347                   (setq method nil))
348               (mm-insert-part handle)
349               (let ((win (get-buffer-window cur t)))
350                 (when win
351                   (select-window win)))
352               (switch-to-buffer (generate-new-buffer "*mm*")))
353             (buffer-disable-undo)
354             (mm-set-buffer-file-coding-system mm-binary-coding-system)
355             (insert-buffer-substring cur)
356             (message "Viewing with %s" method)
357             (let ((mm (current-buffer))
358                   (non-viewer (assq 'non-viewer
359                                     (mailcap-mime-info
360                                      (mm-handle-media-type handle) t))))
361               (unwind-protect
362                   (if method
363                       (funcall method)
364                     (mm-save-part handle))
365                 (when (and (not non-viewer)
366                            method)
367                   (mm-handle-set-undisplayer handle mm)))))
368         ;; The function is a string to be executed.
369         (mm-insert-part handle)
370         (let* ((dir (make-temp-name (expand-file-name "emm." mm-tmp-directory)))
371                (filename (mail-content-type-get
372                           (mm-handle-disposition handle) 'filename))
373                (mime-info (mailcap-mime-info
374                            (mm-handle-media-type handle) t))
375                (needsterm (or (assoc "needsterm" mime-info)
376                               (assoc "needsterminal" mime-info)))
377                (copiousoutput (assoc "copiousoutput" mime-info))
378                file buffer)
379           ;; We create a private sub-directory where we store our files.
380           (make-directory dir)
381           (set-file-modes dir 448)
382           (if filename
383               (setq file (expand-file-name (file-name-nondirectory filename)
384                                            dir))
385             (setq file (make-temp-name (expand-file-name "mm." dir))))
386           (let ((coding-system-for-write mm-binary-coding-system))
387             (write-region (point-min) (point-max) file nil 'nomesg))
388           (message "Viewing with %s" method)
389           (cond (needsterm
390                  (unwind-protect
391                      (start-process "*display*" nil
392                                     "xterm"
393                                     "-e" shell-file-name 
394                                     shell-command-switch
395                                     (mm-mailcap-command
396                                      method file (mm-handle-type handle)))
397                    (mm-handle-set-undisplayer handle (cons file buffer)))
398                  (message "Displaying %s..." (format method file))
399                  'external)
400                 (copiousoutput
401                  (with-current-buffer outbuf
402                    (forward-line 1)
403                    (mm-insert-inline
404                     handle
405                     (unwind-protect
406                         (progn
407                           (call-process shell-file-name nil
408                                         (setq buffer 
409                                               (generate-new-buffer "*mm*"))
410                                         nil
411                                         shell-command-switch
412                                         (mm-mailcap-command
413                                          method file (mm-handle-type handle)))
414                           (if (buffer-live-p buffer)
415                               (save-excursion
416                                 (set-buffer buffer)
417                                 (buffer-string))))
418                       (progn
419                         (ignore-errors (delete-file file))
420                         (ignore-errors (delete-directory
421                                         (file-name-directory file)))
422                         (ignore-errors (kill-buffer buffer))))))
423                  'inline)
424                 (t
425                  (unwind-protect
426                      (start-process "*display*"
427                                     (setq buffer
428                                           (generate-new-buffer "*mm*"))
429                                     shell-file-name
430                                     shell-command-switch
431                                     (mm-mailcap-command
432                                      method file (mm-handle-type handle)))
433                    (mm-handle-set-undisplayer handle (cons file buffer)))
434                  (message "Displaying %s..." (format method file))
435                  'external)))))))
436   
437 (defun mm-mailcap-command (method file type-list)
438   (let ((ctl (cdr type-list))
439         (beg 0)
440         (uses-stdin t)
441         out sub total)
442     (while (string-match "%{\\([^}]+\\)}\\|%s\\|%t\\|%%" method beg)
443       (push (substring method beg (match-beginning 0)) out)
444       (setq beg (match-end 0)
445             total (match-string 0 method)
446             sub (match-string 1 method))
447       (cond
448        ((string= total "%%")
449         (push "%" out))
450        ((string= total "%s")
451         (setq uses-stdin nil)
452         (push (mm-quote-arg file) out))
453        ((string= total "%t")
454         (push (mm-quote-arg (car type-list)) out))
455        (t
456         (push (mm-quote-arg (or (cdr (assq (intern sub) ctl)) "")) out))))
457     (push (substring method beg (length method)) out)
458     (if uses-stdin
459         (progn
460           (push "<" out)
461           (push (mm-quote-arg file) out)))
462     (mapconcat 'identity (nreverse out) "")))
463     
464 (defun mm-remove-parts (handles)
465   "Remove the displayed MIME parts represented by HANDLE."
466   (if (and (listp handles)
467            (bufferp (car handles)))
468       (mm-remove-part handles)
469     (let (handle)
470       (while (setq handle (pop handles))
471         (cond
472          ((stringp handle)
473           ;; Do nothing.
474           )
475          ((and (listp handle)
476                (stringp (car handle)))
477           (mm-remove-parts (cdr handle)))
478          (t
479           (mm-remove-part handle)))))))
480
481 (defun mm-destroy-parts (handles)
482   "Remove the displayed MIME parts represented by HANDLE."
483   (if (and (listp handles)
484            (bufferp (car handles)))
485       (mm-destroy-part handles)
486     (let (handle)
487       (while (setq handle (pop handles))
488         (cond
489          ((stringp handle)
490           ;; Do nothing.
491           )
492          ((and (listp handle)
493                (stringp (car handle)))
494           (mm-destroy-parts (cdr handle)))
495          (t
496           (mm-destroy-part handle)))))))
497
498 (defun mm-remove-part (handle)
499   "Remove the displayed MIME part represented by HANDLE."
500   (when (listp handle)
501     (let ((object (mm-handle-undisplayer handle)))
502       (ignore-errors
503         (cond
504          ;; Internally displayed part.
505          ((mm-annotationp object)
506           (delete-annotation object))
507          ((or (functionp object)
508               (and (listp object)
509                    (eq (car object) 'lambda)))
510           (funcall object))
511          ;; Externally displayed part.
512          ((consp object)
513           (ignore-errors (delete-file (car object)))
514           (ignore-errors (delete-directory (file-name-directory (car object))))
515           (ignore-errors (kill-buffer (cdr object))))
516          ((bufferp object)
517           (when (buffer-live-p object)
518             (kill-buffer object)))))
519       (mm-handle-set-undisplayer handle nil))))
520
521 (defun mm-display-inline (handle)
522   (let* ((type (mm-handle-media-type handle))
523          (function (cadr (mm-assoc-string-match mm-inline-media-tests type))))
524     (funcall function handle)
525     (goto-char (point-min))))
526
527 (defun mm-assoc-string-match (alist type)
528   (dolist (elem alist)
529     (when (string-match (car elem) type)
530       (return elem))))
531
532 (defun mm-inlinable-p (handle)
533   "Say whether HANDLE can be displayed inline."
534   (let ((alist mm-inline-media-tests)
535         (type (mm-handle-media-type handle))
536         test)
537     (while alist
538       (when (string-match (caar alist) type)
539         (setq test (caddar alist)
540               alist nil)
541         (setq test (funcall test handle)))
542       (pop alist))
543     test))
544
545 (defun mm-automatic-display-p (handle)
546   "Say whether the user wants HANDLE to be displayed automatically."
547   (let ((methods mm-automatic-display)
548         (type (mm-handle-media-type handle))
549         method result)
550     (while (setq method (pop methods))
551       (when (and (not (mm-inline-override-p handle))
552                  (string-match method type)
553                  (mm-inlinable-p handle))
554         (setq result t
555               methods nil)))
556     result))
557
558 (defun mm-inlined-p (handle)
559   "Say whether the user wants HANDLE to be displayed automatically."
560   (let ((methods mm-inlined-types)
561         (type (mm-handle-media-type handle))
562         method result)
563     (while (setq method (pop methods))
564       (when (and (not (mm-inline-override-p handle))
565                  (string-match method type)
566                  (mm-inlinable-p handle))
567         (setq result t
568               methods nil)))
569     result))
570
571 (defun mm-attachment-override-p (handle)
572   "Say whether HANDLE should have attachment behavior overridden."
573   (let ((types mm-attachment-override-types)
574         (type (mm-handle-media-type handle))
575         ty)
576     (catch 'found
577       (while (setq ty (pop types))
578         (when (and (string-match ty type)
579                    (mm-inlinable-p handle))
580           (throw 'found t))))))
581
582 (defun mm-inline-override-p (handle)
583   "Say whether HANDLE should have inline behavior overridden."
584   (let ((types mm-inline-override-types)
585         (type (mm-handle-media-type handle))
586         ty)
587     (catch 'found
588       (while (setq ty (pop types))
589         (when (string-match ty type)
590           (throw 'found t))))))
591
592 (defun mm-automatic-external-display-p (type)
593   "Return the user-defined method for TYPE."
594   (let ((methods mm-automatic-external-display)
595         method result)
596     (while (setq method (pop methods))
597       (when (string-match method type)
598         (setq result t
599               methods nil)))
600     result))
601
602 (defun mm-destroy-part (handle)
603   "Destroy the data structures connected to HANDLE."
604   (when (listp handle)
605     (mm-remove-part handle)
606     (when (buffer-live-p (mm-handle-buffer handle))
607       (kill-buffer (mm-handle-buffer handle)))))
608
609 (defun mm-handle-displayed-p (handle)
610   "Say whether HANDLE is displayed or not."
611   (mm-handle-undisplayer handle))
612
613 ;;;
614 ;;; Functions for outputting parts
615 ;;;
616
617 (defun mm-get-part (handle)
618   "Return the contents of HANDLE as a string."
619   (mm-with-unibyte-buffer
620     (mm-insert-part handle)
621     (buffer-string)))
622
623 (defun mm-insert-part (handle)
624   "Insert the contents of HANDLE in the current buffer."
625   (let ((cur (current-buffer)))
626     (save-excursion
627       (if (member (mm-handle-media-supertype handle) '("text" "message"))
628           (with-temp-buffer
629             (insert-buffer-substring (mm-handle-buffer handle))
630             (mm-decode-content-transfer-encoding
631              (mm-handle-encoding handle)
632              (mm-handle-media-type handle))
633             (let ((temp (current-buffer)))
634               (set-buffer cur)
635               (insert-buffer-substring temp)))
636         (mm-with-unibyte-buffer
637           (insert-buffer-substring (mm-handle-buffer handle))
638           (mm-decode-content-transfer-encoding
639            (mm-handle-encoding handle)
640            (mm-handle-media-type handle))
641           (let ((temp (current-buffer)))
642             (set-buffer cur)
643             (insert-buffer-substring temp)))))))
644
645 (defvar mm-default-directory nil)
646
647 (defun mm-save-part (handle)
648   "Write HANDLE to a file."
649   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
650          (filename (mail-content-type-get
651                     (mm-handle-disposition handle) 'filename))
652          file)
653     (when filename
654       (setq filename (file-name-nondirectory filename)))
655     (setq file
656           (read-file-name "Save MIME part to: "
657                           (expand-file-name
658                            (or filename name "")
659                            (or mm-default-directory default-directory))))
660     (setq mm-default-directory (file-name-directory file))
661     (when (or (not (file-exists-p file))
662               (yes-or-no-p (format "File %s already exists; overwrite? "
663                                    file)))
664       (mm-save-part-to-file handle file))))
665
666 (defun mm-save-part-to-file (handle file)
667   (mm-with-unibyte-buffer
668     (mm-insert-part handle)
669     (let ((coding-system-for-write 'binary)
670           ;; Don't re-compress .gz & al.  Arguably we should make
671           ;; `file-name-handler-alist' nil, but that would chop
672           ;; ange-ftp, which is reasonable to use here.
673           (inhibit-file-name-operation 'write-region)
674           (inhibit-file-name-handlers
675            (cons 'jka-compr-handler inhibit-file-name-handlers)))
676       (write-region (point-min) (point-max) file))))
677
678 (defun mm-pipe-part (handle)
679   "Pipe HANDLE to a process."
680   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
681          (command
682           (read-string "Shell command on MIME part: " mm-last-shell-command)))
683     (mm-with-unibyte-buffer
684       (mm-insert-part handle)
685       (shell-command-on-region (point-min) (point-max) command nil))))
686
687 (defun mm-interactively-view-part (handle)
688   "Display HANDLE using METHOD."
689   (let* ((type (mm-handle-media-type handle))
690          (methods
691           (mapcar (lambda (i) (list (cdr (assoc 'viewer i))))
692                   (mailcap-mime-info type 'all)))
693          (method (completing-read "Viewer: " methods)))
694     (when (string= method "")
695       (error "No method given"))
696     (mm-display-external (copy-sequence handle) method)))
697
698 (defun mm-preferred-alternative (handles &optional preferred)
699   "Say which of HANDLES are preferred."
700   (let ((prec (if preferred (list preferred)
701                 (mm-preferred-alternative-precedence handles)))
702         p h result type handle)
703     (while (setq p (pop prec))
704       (setq h handles)
705       (while h
706         (setq handle (car h))
707         (setq type (mm-handle-media-type handle))
708         (when (and (equal p type)
709                    (mm-automatic-display-p handle)
710                    (or (stringp (car handle))
711                        (not (mm-handle-disposition handle))
712                        (equal (car (mm-handle-disposition handle))
713                               "inline")))
714           (setq result handle
715                 h nil
716                 prec nil))
717         (pop h)))
718     result))
719
720 (defun mm-preferred-alternative-precedence (handles)
721   "Return the precedence based on HANDLES and mm-discouraged-alternatives."
722   (let ((seq (nreverse (mapcar (lambda (h)
723                                  (mm-handle-media-type h))
724                                handles))))
725     (dolist (disc (reverse mm-discouraged-alternatives))
726       (dolist (elem (copy-sequence seq))
727         (when (string-match disc elem)
728           (setq seq (nconc (delete elem seq) (list elem))))))
729     seq))
730
731 (defun mm-get-content-id (id)
732   "Return the handle(s) referred to by ID."
733   (cdr (assoc id mm-content-id-alist)))
734
735 (defun mm-get-image (handle)
736   "Return an image instance based on HANDLE."
737   (let ((type (mm-handle-media-subtype handle))
738         spec)
739     ;; Allow some common translations.
740     (setq type
741           (cond
742            ((equal type "x-pixmap")
743             "xpm")
744            ((equal type "x-xbitmap")
745             "xbm")
746            (t type)))
747     (or (mm-handle-cache handle)
748         (mm-with-unibyte-buffer
749           (mm-insert-part handle)
750           (prog1
751               (setq spec
752                     (ignore-errors
753                       (cond
754                        ((equal type "xbm")
755                         ;; xbm images require special handling, since
756                         ;; the only way to create glyphs from these
757                         ;; (without a ton of work) is to write them
758                         ;; out to a file, and then create a file
759                         ;; specifier.
760                         (let ((file (make-temp-name
761                                      (expand-file-name "emm.xbm"
762                                                        mm-tmp-directory))))
763                           (unwind-protect
764                               (progn
765                                 (write-region (point-min) (point-max) file)
766                                 (make-glyph (list (cons 'x file))))
767                             (ignore-errors
768                               (delete-file file)))))
769                        (t
770                         (make-glyph
771                          (vector (intern type) :data (buffer-string)))))))
772             (mm-handle-set-cache handle spec))))))
773
774 (defun mm-image-fit-p (handle)
775   "Say whether the image in HANDLE will fit the current window."
776   (let ((image (mm-get-image handle)))
777     (or mm-inline-large-images
778         (and (< (glyph-width image) (window-pixel-width))
779              (< (glyph-height image) (window-pixel-height))))))
780
781 (defun mm-valid-image-format-p (format)
782   "Say whether FORMAT can be displayed natively by Emacs."
783   (and (fboundp 'valid-image-instantiator-format-p)
784        (valid-image-instantiator-format-p format)))
785
786 (defun mm-valid-and-fit-image-p (format handle)
787   "Say whether FORMAT can be displayed natively and HANDLE fits the window."
788   (and window-system
789        (mm-valid-image-format-p format)
790        (mm-image-fit-p handle)))
791
792 (provide 'mm-decode)
793
794 ;; mm-decode.el ends here