*** empty log message ***
[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       (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         (uses-stdin t)
437         out sub total)
438     (while (string-match "%{\\([^}]+\\)}\\|%s\\|%t\\|%%" method beg)
439       (push (substring method beg (match-beginning 0)) out)
440       (setq beg (match-end 0)
441             total (match-string 0 method)
442             sub (match-string 1 method))
443       (cond
444        ((string= total "%%")
445         (push "%" out))
446        ((string= total "%s")
447         (setq uses-stdin nil)
448         (push (mm-quote-arg file) out))
449        ((string= total "%t")
450         (push (mm-quote-arg (car type-list)) out))
451        (t
452         (push (mm-quote-arg (or (cdr (assq (intern sub) ctl)) "")) out))))
453     (push (substring method beg (length method)) out)
454     (if uses-stdin
455         (progn
456           (push "<" out)
457           (push (mm-quote-arg file) out)))
458     (mapconcat 'identity (nreverse out) "")))
459     
460 (defun mm-remove-parts (handles)
461   "Remove the displayed MIME parts represented by HANDLE."
462   (if (and (listp handles)
463            (bufferp (car handles)))
464       (mm-remove-part handles)
465     (let (handle)
466       (while (setq handle (pop handles))
467         (cond
468          ((stringp handle)
469           ;; Do nothing.
470           )
471          ((and (listp handle)
472                (stringp (car handle)))
473           (mm-remove-parts (cdr handle)))
474          (t
475           (mm-remove-part handle)))))))
476
477 (defun mm-destroy-parts (handles)
478   "Remove the displayed MIME parts represented by HANDLE."
479   (if (and (listp handles)
480            (bufferp (car handles)))
481       (mm-destroy-part handles)
482     (let (handle)
483       (while (setq handle (pop handles))
484         (cond
485          ((stringp handle)
486           ;; Do nothing.
487           )
488          ((and (listp handle)
489                (stringp (car handle)))
490           (mm-destroy-parts (cdr handle)))
491          (t
492           (mm-destroy-part handle)))))))
493
494 (defun mm-remove-part (handle)
495   "Remove the displayed MIME part represented by HANDLE."
496   (when (listp handle)
497     (let ((object (mm-handle-undisplayer handle)))
498       (ignore-errors
499         (cond
500          ;; Internally displayed part.
501          ((mm-annotationp object)
502           (delete-annotation object))
503          ((or (functionp object)
504               (and (listp object)
505                    (eq (car object) 'lambda)))
506           (funcall object))
507          ;; Externally displayed part.
508          ((consp object)
509           (ignore-errors (delete-file (car object)))
510           (ignore-errors (delete-directory (file-name-directory (car object))))
511           (ignore-errors (kill-buffer (cdr object))))
512          ((bufferp object)
513           (when (buffer-live-p object)
514             (kill-buffer object)))))
515       (mm-handle-set-undisplayer handle nil))))
516
517 (defun mm-display-inline (handle)
518   (let* ((type (mm-handle-media-type handle))
519          (function (cadr (mm-assoc-string-match mm-inline-media-tests type))))
520     (funcall function handle)
521     (goto-char (point-min))))
522
523 (defun mm-assoc-string-match (alist type)
524   (dolist (elem alist)
525     (when (string-match (car elem) type)
526       (return elem))))
527
528 (defun mm-inlinable-p (handle)
529   "Say whether HANDLE can be displayed inline."
530   (let ((alist mm-inline-media-tests)
531         (type (mm-handle-media-type handle))
532         test)
533     (while alist
534       (when (string-match (caar alist) type)
535         (setq test (caddar alist)
536               alist nil)
537         (setq test (funcall test handle)))
538       (pop alist))
539     test))
540
541 (defun mm-automatic-display-p (handle)
542   "Say whether the user wants HANDLE to be displayed automatically."
543   (let ((methods mm-automatic-display)
544         (type (mm-handle-media-type handle))
545         method result)
546     (while (setq method (pop methods))
547       (when (and (not (mm-inline-override-p handle))
548                  (string-match method type)
549                  (mm-inlinable-p handle))
550         (setq result t
551               methods nil)))
552     result))
553
554 (defun mm-inlined-p (handle)
555   "Say whether the user wants HANDLE to be displayed automatically."
556   (let ((methods mm-inlined-types)
557         (type (mm-handle-media-type handle))
558         method result)
559     (while (setq method (pop methods))
560       (when (and (not (mm-inline-override-p handle))
561                  (string-match method type)
562                  (mm-inlinable-p handle))
563         (setq result t
564               methods nil)))
565     result))
566
567 (defun mm-attachment-override-p (handle)
568   "Say whether HANDLE should have attachment behavior overridden."
569   (let ((types mm-attachment-override-types)
570         (type (mm-handle-media-type handle))
571         ty)
572     (catch 'found
573       (while (setq ty (pop types))
574         (when (and (string-match ty type)
575                    (mm-inlinable-p handle))
576           (throw 'found t))))))
577
578 (defun mm-inline-override-p (handle)
579   "Say whether HANDLE should have inline behavior overridden."
580   (let ((types mm-inline-override-types)
581         (type (mm-handle-media-type handle))
582         ty)
583     (catch 'found
584       (while (setq ty (pop types))
585         (when (string-match ty type)
586           (throw 'found t))))))
587
588 (defun mm-automatic-external-display-p (type)
589   "Return the user-defined method for TYPE."
590   (let ((methods mm-automatic-external-display)
591         method result)
592     (while (setq method (pop methods))
593       (when (string-match method type)
594         (setq result t
595               methods nil)))
596     result))
597
598 (defun mm-destroy-part (handle)
599   "Destroy the data structures connected to HANDLE."
600   (when (listp handle)
601     (mm-remove-part handle)
602     (when (buffer-live-p (mm-handle-buffer handle))
603       (kill-buffer (mm-handle-buffer handle)))))
604
605 (defun mm-handle-displayed-p (handle)
606   "Say whether HANDLE is displayed or not."
607   (mm-handle-undisplayer handle))
608
609 ;;;
610 ;;; Functions for outputting parts
611 ;;;
612
613 (defun mm-get-part (handle)
614   "Return the contents of HANDLE as a string."
615   (mm-with-unibyte-buffer
616     (mm-insert-part handle)
617     (buffer-string)))
618
619 (defun mm-insert-part (handle)
620   "Insert the contents of HANDLE in the current buffer."
621   (let ((cur (current-buffer)))
622     (save-excursion
623       (if (member (mm-handle-media-supertype handle) '("text" "message"))
624           (with-temp-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         (mm-with-unibyte-buffer
633           (insert-buffer-substring (mm-handle-buffer handle))
634           (mm-decode-content-transfer-encoding
635            (mm-handle-encoding handle)
636            (mm-handle-media-type handle))
637           (let ((temp (current-buffer)))
638             (set-buffer cur)
639             (insert-buffer-substring temp)))))))
640
641 (defvar mm-default-directory nil)
642
643 (defun mm-save-part (handle)
644   "Write HANDLE to a file."
645   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
646          (filename (mail-content-type-get
647                     (mm-handle-disposition handle) 'filename))
648          file)
649     (when filename
650       (setq filename (file-name-nondirectory filename)))
651     (setq file
652           (read-file-name "Save MIME part to: "
653                           (expand-file-name
654                            (or filename name "")
655                            (or mm-default-directory default-directory))))
656     (setq mm-default-directory (file-name-directory file))
657     (when (or (not (file-exists-p file))
658               (yes-or-no-p (format "File %s already exists; overwrite? "
659                                    file)))
660       (mm-save-part-to-file handle file))))
661
662 (defun mm-save-part-to-file (handle file)
663   (mm-with-unibyte-buffer
664     (mm-insert-part handle)
665     (let ((coding-system-for-write 'binary)
666           ;; Don't re-compress .gz & al.  Arguably we should make
667           ;; `file-name-handler-alist' nil, but that would chop
668           ;; ange-ftp, which is reasonable to use here.
669           (inhibit-file-name-operation 'write-region)
670           (inhibit-file-name-handlers
671            (cons 'jka-compr-handler inhibit-file-name-handlers)))
672       (write-region (point-min) (point-max) file))))
673
674 (defun mm-pipe-part (handle)
675   "Pipe HANDLE to a process."
676   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
677          (command
678           (read-string "Shell command on MIME part: " mm-last-shell-command)))
679     (mm-with-unibyte-buffer
680       (mm-insert-part handle)
681       (shell-command-on-region (point-min) (point-max) command nil))))
682
683 (defun mm-interactively-view-part (handle)
684   "Display HANDLE using METHOD."
685   (let* ((type (mm-handle-media-type handle))
686          (methods
687           (mapcar (lambda (i) (list (cdr (assoc 'viewer i))))
688                   (mailcap-mime-info type 'all)))
689          (method (completing-read "Viewer: " methods)))
690     (when (string= method "")
691       (error "No method given"))
692     (mm-display-external (copy-sequence handle) method)))
693
694 (defun mm-preferred-alternative (handles &optional preferred)
695   "Say which of HANDLES are preferred."
696   (let ((prec (if preferred (list preferred)
697                 (mm-preferred-alternative-precedence handles)))
698         p h result type handle)
699     (while (setq p (pop prec))
700       (setq h handles)
701       (while h
702         (setq handle (car h))
703         (setq type (mm-handle-media-type handle))
704         (when (and (equal p type)
705                    (mm-automatic-display-p handle)
706                    (or (stringp (car handle))
707                        (not (mm-handle-disposition handle))
708                        (equal (car (mm-handle-disposition handle))
709                               "inline")))
710           (setq result handle
711                 h nil
712                 prec nil))
713         (pop h)))
714     result))
715
716 (defun mm-preferred-alternative-precedence (handles)
717   "Return the precedence based on HANDLES and mm-discouraged-alternatives."
718   (let ((seq (nreverse (mapcar (lambda (h)
719                                  (mm-handle-media-type h))
720                                handles))))
721     (dolist (disc (reverse mm-discouraged-alternatives))
722       (dolist (elem (copy-sequence seq))
723         (when (string-match disc elem)
724           (setq seq (nconc (delete elem seq) (list elem))))))
725     seq))
726
727 (defun mm-get-content-id (id)
728   "Return the handle(s) referred to by ID."
729   (cdr (assoc id mm-content-id-alist)))
730
731 (defun mm-get-image (handle)
732   "Return an image instance based on HANDLE."
733   (let ((type (mm-handle-media-subtype handle))
734         spec)
735     ;; Allow some common translations.
736     (setq type
737           (cond
738            ((equal type "x-pixmap")
739             "xpm")
740            ((equal type "x-xbitmap")
741             "xbm")
742            (t type)))
743     (or (mm-handle-cache handle)
744         (mm-with-unibyte-buffer
745           (mm-insert-part handle)
746           (prog1
747               (setq spec
748                     (ignore-errors
749                       (cond
750                        ((equal type "xbm")
751                         ;; xbm images require special handling, since
752                         ;; the only way to create glyphs from these
753                         ;; (without a ton of work) is to write them
754                         ;; out to a file, and then create a file
755                         ;; specifier.
756                         (let ((file (make-temp-name
757                                      (expand-file-name "emm.xbm"
758                                                        mm-tmp-directory))))
759                           (unwind-protect
760                               (progn
761                                 (write-region (point-min) (point-max) file)
762                                 (make-glyph (list (cons 'x file))))
763                             (ignore-errors
764                               (delete-file file)))))
765                        (t
766                         (make-glyph
767                          (vector (intern type) :data (buffer-string)))))))
768             (mm-handle-set-cache handle spec))))))
769
770 (defun mm-image-fit-p (handle)
771   "Say whether the image in HANDLE will fit the current window."
772   (let ((image (mm-get-image handle)))
773     (or mm-inline-large-images
774         (and (< (glyph-width image) (window-pixel-width))
775              (< (glyph-height image) (window-pixel-height))))))
776
777 (defun mm-valid-image-format-p (format)
778   "Say whether FORMAT can be displayed natively by Emacs."
779   (and (fboundp 'valid-image-instantiator-format-p)
780        (valid-image-instantiator-format-p format)))
781
782 (defun mm-valid-and-fit-image-p (format handle)
783   "Say whether FORMAT can be displayed natively and HANDLE fits the window."
784   (and window-system
785        (mm-valid-image-format-p format)
786        (mm-image-fit-p handle)))
787
788 (provide 'mm-decode)
789
790 ;; mm-decode.el ends here