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