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