comparison lisp/gnus/mm-decode.el @ 31717:6b20b7e85e3c

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