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