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