Synch with Oort Gnus.
[elisp/gnus.git-] / 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 'gnus-mailcap)
29 (require 'mm-bodies)
30 (eval-when-compile (require 'cl))
31
32 (eval-and-compile
33   (autoload 'mm-inline-partial "mm-partial")
34   (autoload 'mm-inline-external-body "mm-extern")
35   (autoload 'mm-insert-inline "mm-view"))
36
37 (defgroup mime-display ()
38   "Display of MIME in mail and news articles."
39   :link '(custom-manual "(emacs-mime)Customization")
40   :version "21.1"
41   :group 'mail
42   :group 'news
43   :group 'multimedia)
44
45 (defgroup mime-security ()
46   "MIME security in mail and news articles."
47   :link '(custom-manual "(emacs-mime)Customization")
48   :group 'mail
49   :group 'news
50   :group 'multimedia)
51
52 ;;; Convenience macros.
53
54 (defmacro mm-handle-buffer (handle)
55   `(nth 0 ,handle))
56 (defmacro mm-handle-type (handle)
57   `(nth 1 ,handle))
58 (defsubst mm-handle-media-type (handle)
59   (if (stringp (car handle))
60       (car handle)
61     (car (mm-handle-type handle))))
62 (defsubst mm-handle-media-supertype (handle)
63   (car (split-string (mm-handle-media-type handle) "/")))
64 (defsubst mm-handle-media-subtype (handle)
65   (cadr (split-string (mm-handle-media-type handle) "/")))
66 (defmacro mm-handle-encoding (handle)
67   `(nth 2 ,handle))
68 (defmacro mm-handle-undisplayer (handle)
69   `(nth 3 ,handle))
70 (defmacro mm-handle-set-undisplayer (handle function)
71   `(setcar (nthcdr 3 ,handle) ,function))
72 (defmacro mm-handle-disposition (handle)
73   `(nth 4 ,handle))
74 (defmacro mm-handle-description (handle)
75   `(nth 5 ,handle))
76 (defmacro mm-handle-cache (handle)
77   `(nth 6 ,handle))
78 (defmacro mm-handle-set-cache (handle contents)
79   `(setcar (nthcdr 6 ,handle) ,contents))
80 (defmacro mm-handle-id (handle)
81   `(nth 7 ,handle))
82 (defmacro mm-handle-multipart-original-buffer (handle)
83   `(get-text-property 0 'buffer (car ,handle)))
84 (defmacro mm-handle-multipart-ctl-parameter (handle parameter)
85   `(get-text-property 0 ,parameter (car ,handle)))
86
87 (defmacro mm-make-handle (&optional buffer type encoding undisplayer
88                                     disposition description cache
89                                     id)
90   `(list ,buffer ,type ,encoding ,undisplayer
91          ,disposition ,description ,cache ,id))
92
93 (defcustom mm-inline-media-tests
94   '(("image/jpeg"
95      mm-inline-image
96      (lambda (handle)
97        (mm-valid-and-fit-image-p 'jpeg handle)))
98     ("image/png"
99      mm-inline-image
100      (lambda (handle)
101        (mm-valid-and-fit-image-p 'png handle)))
102     ("image/gif"
103      mm-inline-image
104      (lambda (handle)
105        (mm-valid-and-fit-image-p 'gif handle)))
106     ("image/tiff"
107      mm-inline-image
108      (lambda (handle)
109        (mm-valid-and-fit-image-p 'tiff handle)) )
110     ("image/xbm"
111      mm-inline-image
112      (lambda (handle)
113        (mm-valid-and-fit-image-p 'xbm handle)))
114     ("image/x-xbitmap"
115      mm-inline-image
116      (lambda (handle)
117        (mm-valid-and-fit-image-p 'xbm handle)))
118     ("image/xpm"
119      mm-inline-image
120      (lambda (handle)
121        (mm-valid-and-fit-image-p 'xpm handle)))
122     ("image/x-pixmap"
123      mm-inline-image
124      (lambda (handle)
125        (mm-valid-and-fit-image-p 'xpm handle)))
126     ("image/bmp"
127      mm-inline-image
128      (lambda (handle)
129        (mm-valid-and-fit-image-p 'bmp handle)))
130     ("image/x-portable-bitmap"
131      mm-inline-image
132      (lambda (handle)
133        (mm-valid-and-fit-image-p 'pbm handle)))
134     ("text/plain" mm-inline-text identity)
135     ("text/enriched" mm-inline-text identity)
136     ("text/richtext" mm-inline-text identity)
137     ("text/x-patch" mm-display-patch-inline
138      (lambda (handle)
139        (locate-library "diff-mode")))
140     ("application/emacs-lisp" mm-display-elisp-inline identity)
141     ("text/html"
142      mm-inline-text
143      (lambda (handle)
144        (locate-library "w3")))
145     ("text/x-vcard"
146      mm-inline-text
147      (lambda (handle)
148        (or (featurep 'vcard)
149            (locate-library "vcard"))))
150     ("message/delivery-status" mm-inline-text identity)
151     ("message/rfc822" mm-inline-message identity)
152     ("message/partial" mm-inline-partial identity)
153     ("message/external-body" mm-inline-external-body identity)
154     ("text/.*" mm-inline-text identity)
155     ("audio/wav" mm-inline-audio
156      (lambda (handle)
157        (and (or (featurep 'nas-sound) (featurep 'native-sound))
158             (device-sound-enabled-p))))
159     ("audio/au"
160      mm-inline-audio
161      (lambda (handle)
162        (and (or (featurep 'nas-sound) (featurep 'native-sound))
163             (device-sound-enabled-p))))
164     ("application/pgp-signature" ignore identity)
165     ("application/x-pkcs7-signature" ignore identity)
166     ("application/pkcs7-signature" ignore identity)
167     ("multipart/alternative" ignore identity)
168     ("multipart/mixed" ignore identity)
169     ("multipart/related" ignore identity))
170   "Alist of media types/tests saying whether types can be displayed inline."
171   :type '(repeat (list (string :tag "MIME type")
172                        (function :tag "Display function")
173                        (function :tag "Display test")))
174   :group 'mime-display)
175
176 (defcustom mm-inlined-types
177   '("image/.*" "text/.*" "message/delivery-status" "message/rfc822"
178     "message/partial" "message/external-body" "application/emacs-lisp"
179     "application/pgp-signature" "application/x-pkcs7-signature"
180     "application/pkcs7-signature")
181   "List of media types that are to be displayed inline."
182   :type '(repeat string)
183   :group 'mime-display)
184   
185 (defcustom mm-automatic-display
186   '("text/plain" "text/enriched" "text/richtext" "text/html"
187     "text/x-vcard" "image/.*" "message/delivery-status" "multipart/.*"
188     "message/rfc822" "text/x-patch" "application/pgp-signature"
189     "application/emacs-lisp" "application/x-pkcs7-signature"
190     "application/pkcs7-signature")
191   "A list of MIME types to be displayed automatically."
192   :type '(repeat string)
193   :group 'mime-display)
194
195 (defcustom mm-attachment-override-types '("text/x-vcard")
196   "Types to have \"attachment\" ignored if they can be displayed inline."
197   :type '(repeat string)
198   :group 'mime-display)
199
200 (defcustom mm-inline-override-types nil
201   "Types to be treated as attachments even if they can be displayed inline."
202   :type '(repeat string)
203   :group 'mime-display)
204
205 (defcustom mm-automatic-external-display nil
206   "List of MIME type regexps that will be displayed externally automatically."
207   :type '(repeat string)
208   :group 'mime-display)
209
210 (defcustom mm-discouraged-alternatives nil
211   "List of MIME types that are discouraged when viewing multipart/alternative.
212 Viewing agents are supposed to view the last possible part of a message,
213 as that is supposed to be the richest.  However, users may prefer other
214 types instead, and this list says what types are most unwanted.  If,
215 for instance, text/html parts are very unwanted, and text/richtext are
216 somewhat unwanted, then the value of this variable should be set
217 to:
218
219  (\"text/html\" \"text/richtext\")"
220   :type '(repeat string)
221   :group 'mime-display)
222
223 (defvar mm-tmp-directory
224   (cond ((fboundp 'temp-directory) (temp-directory))
225         ((boundp 'temporary-file-directory) temporary-file-directory)
226         ("/tmp/"))
227   "Where mm will store its temporary files.")
228
229 (defcustom mm-inline-large-images nil
230   "If non-nil, then all images fit in the buffer."
231   :type 'boolean
232   :group 'mime-display)
233
234 ;;; Internal variables.
235
236 (defvar mm-dissection-list nil)
237 (defvar mm-last-shell-command "")
238 (defvar mm-content-id-alist nil)
239
240 ;; According to RFC2046, in particular, in a digest, the default
241 ;; Content-Type value for a body part is changed from "text/plain" to
242 ;; "message/rfc822".
243 (defvar mm-dissect-default-type "text/plain")
244
245 (autoload 'mml2015-verify "mml2015")
246 (autoload 'mml2015-verify-test "mml2015")
247 (autoload 'mml-smime-verify "mml-smime")
248 (autoload 'mml-smime-verify-test "mml-smime")
249
250 (defvar mm-verify-function-alist
251   '(("application/pgp-signature" mml2015-verify "PGP" mml2015-verify-test)
252     ("application/x-gnus-pgp-signature" mm-uu-pgp-signed-extract-1 "PGP" 
253      mm-uu-pgp-signed-test)
254     ("application/pkcs7-signature" mml-smime-verify "S/MIME" 
255      mml-smime-verify-test)
256     ("application/x-pkcs7-signature" mml-smime-verify "S/MIME" 
257      mml-smime-verify-test)))
258
259 (defcustom mm-verify-option 'never
260   "Option of verifying signed parts.
261 `never', not verify; `always', always verify; 
262 `known', only verify known protocols. Otherwise, ask user."
263   :type '(choice (item always)
264                  (item never)
265                  (item :tag "only known protocols" known)
266                  (item :tag "ask" nil))
267   :group 'mime-security)
268
269 (autoload 'mml2015-decrypt "mml2015")
270 (autoload 'mml2015-decrypt-test "mml2015")
271
272 (defvar mm-decrypt-function-alist
273   '(("application/pgp-encrypted" mml2015-decrypt "PGP" mml2015-decrypt-test)
274     ("application/x-gnus-pgp-encrypted" mm-uu-pgp-encrypted-extract-1 "PGP" 
275      mm-uu-pgp-encrypted-test)))
276
277 (defcustom mm-decrypt-option nil
278   "Option of decrypting encrypted parts.
279 `never', not decrypt; `always', always decrypt; 
280 `known', only decrypt known protocols. Otherwise, ask user."
281   :type '(choice (item always)
282                  (item never)
283                  (item :tag "only known protocols" known)
284                  (item :tag "ask" nil))
285   :group 'mime-security)
286
287 (defvar mm-viewer-completion-map
288   (let ((map (make-sparse-keymap 'mm-viewer-completion-map)))
289     (set-keymap-parent map minibuffer-local-completion-map)
290     map)
291   "Keymap for input viewer with completion.")
292
293 ;; Should we bind other key to minibuffer-complete-word?
294 (define-key mm-viewer-completion-map " " 'self-insert-command) 
295
296 (defvar mm-viewer-completion-map
297   (let ((map (make-sparse-keymap 'mm-viewer-completion-map)))
298     (set-keymap-parent map minibuffer-local-completion-map)
299     map)
300   "Keymap for input viewer with completion.")
301
302 ;; Should we bind other key to minibuffer-complete-word?
303 (define-key mm-viewer-completion-map " " 'self-insert-command)
304
305 ;;; The functions.
306
307 (defun mm-alist-to-plist (alist)
308   "Convert association list ALIST into the equivalent property-list form.
309 The plist is returned.  This converts from
310
311 \((a . 1) (b . 2) (c . 3))
312
313 into
314
315 \(a 1 b 2 c 3)
316
317 The original alist is not modified.  See also `destructive-alist-to-plist'."
318   (let (plist)
319     (while alist
320       (let ((el (car alist)))
321         (setq plist (cons (cdr el) (cons (car el) plist))))
322       (setq alist (cdr alist)))
323     (nreverse plist)))
324
325 (defun mm-dissect-buffer (&optional no-strict-mime)
326   "Dissect the current buffer and return a list of MIME handles."
327   (save-excursion
328     (let (ct ctl type subtype cte cd description id result)
329       (save-restriction
330         (mail-narrow-to-head)
331         (when (or no-strict-mime
332                   (mail-fetch-field "mime-version"))
333           (setq ct (mail-fetch-field "content-type")
334                 ctl (ignore-errors (mail-header-parse-content-type ct))
335                 cte (mail-fetch-field "content-transfer-encoding")
336                 cd (mail-fetch-field "content-disposition")
337                 description (mail-fetch-field "content-description")
338                 id (mail-fetch-field "content-id"))))
339       (when cte
340         (setq cte (mail-header-strip cte)))
341       (if (or (not ctl)
342               (not (string-match "/" (car ctl))))
343           (mm-dissect-singlepart
344            (list mm-dissect-default-type)
345            (and cte (intern (downcase (mail-header-remove-whitespace
346                                        (mail-header-remove-comments
347                                         cte)))))
348            no-strict-mime
349            (and cd (ignore-errors (mail-header-parse-content-disposition cd)))
350            description)
351         (setq type (split-string (car ctl) "/"))
352         (setq subtype (cadr type)
353               type (pop type))
354         (setq
355          result
356          (cond
357           ((equal type "multipart")
358            (let ((mm-dissect-default-type (if (equal subtype "digest")
359                                               "message/rfc822"
360                                             "text/plain")))
361              (add-text-properties 0 (length (car ctl))
362                                   (mm-alist-to-plist (cdr ctl)) (car ctl))
363
364              ;; what really needs to be done here is a way to link a
365              ;; MIME handle back to it's parent MIME handle (in a multilevel
366              ;; MIME article).  That would probably require changing
367              ;; the mm-handle API so we simply store the multipart buffert
368              ;; name as a text property of the "multipart/whatever" string.
369              (add-text-properties 0 (length (car ctl))
370                                   (list 'buffer (mm-copy-to-buffer))
371                                   (car ctl))
372              (cons (car ctl) (mm-dissect-multipart ctl))))
373           (t
374            (mm-dissect-singlepart
375             ctl
376             (and cte (intern (downcase (mail-header-remove-whitespace
377                                         (mail-header-remove-comments
378                                          cte)))))
379             no-strict-mime
380             (and cd (ignore-errors (mail-header-parse-content-disposition cd)))
381             description id))))
382         (when id
383           (when (string-match " *<\\(.*\\)> *" id)
384             (setq id (match-string 1 id)))
385           (push (cons id result) mm-content-id-alist))
386         result))))
387
388 (defun mm-dissect-singlepart (ctl cte &optional force cdl description id)
389   (when (or force
390             (if (equal "text/plain" (car ctl))
391                 (assoc 'format ctl)
392               t))
393     (let ((res (mm-make-handle
394                 (mm-copy-to-buffer) ctl cte nil cdl description nil id)))
395       (push (car res) mm-dissection-list)
396       res)))
397
398 (defun mm-remove-all-parts ()
399   "Remove all MIME handles."
400   (interactive)
401   (mapcar 'mm-remove-part mm-dissection-list)
402   (setq mm-dissection-list nil))
403
404 (defun mm-dissect-multipart (ctl)
405   (goto-char (point-min))
406   (let* ((boundary (concat "\n--" (mail-content-type-get ctl 'boundary)))
407          (close-delimiter (concat (regexp-quote boundary) "--[ \t]*$"))
408          start parts
409          (end (save-excursion
410                 (goto-char (point-max))
411                 (if (re-search-backward close-delimiter nil t)
412                     (match-beginning 0)
413                   (point-max)))))
414     (setq boundary (concat (regexp-quote boundary) "[ \t]*$"))
415     (while (and (< (point) end) (re-search-forward boundary end t))
416       (goto-char (match-beginning 0))
417       (when start
418         (save-excursion
419           (save-restriction
420             (narrow-to-region start (point))
421             (setq parts (nconc (list (mm-dissect-buffer t)) parts)))))
422       (forward-line 2)
423       (setq start (point)))
424     (when (and start (< start end))
425       (save-excursion
426         (save-restriction
427           (narrow-to-region start end)
428           (setq parts (nconc (list (mm-dissect-buffer t)) parts)))))
429     (mm-possibly-verify-or-decrypt (nreverse parts) ctl)))
430
431 (defun mm-copy-to-buffer ()
432   "Copy the contents of the current buffer to a fresh buffer."
433   (save-excursion
434     (let ((obuf (current-buffer))
435           beg)
436       (goto-char (point-min))
437       (search-forward-regexp "^\n" nil t)
438       (setq beg (point))
439       (set-buffer (generate-new-buffer " *mm*"))
440       (insert-buffer-substring obuf beg)
441       (current-buffer))))
442
443 (defun mm-display-parts (handle &optional no-default)
444   (if (stringp (car handle))
445       (mapcar 'mm-display-parts (cdr handle))
446     (if (bufferp (car handle))
447         (save-restriction
448           (narrow-to-region (point) (point))
449           (mm-display-part handle)
450           (goto-char (point-max)))
451       (mapcar 'mm-display-parts handle))))
452
453 (defun mm-display-part (handle &optional no-default)
454   "Display the MIME part represented by HANDLE.
455 Returns nil if the part is removed; inline if displayed inline;
456 external if displayed external."
457   (save-excursion
458     (mailcap-parse-mailcaps)
459     (if (mm-handle-displayed-p handle)
460         (mm-remove-part handle)
461       (let* ((type (mm-handle-media-type handle))
462              (method (mailcap-mime-info type)))
463         (if (mm-inlined-p handle)
464             (progn
465               (forward-line 1)
466               (mm-display-inline handle)
467               'inline)
468           (when (or method
469                     (not no-default))
470             (if (and (not method)
471                      (equal "text" (car (split-string type))))
472                 (progn
473                   (forward-line 1)
474                   (mm-insert-inline handle (mm-get-part handle))
475                   'inline)
476               (mm-display-external
477                handle (or method 'mailcap-save-binary-file)))))))))
478
479 (defun mm-display-external (handle method)
480   "Display HANDLE using METHOD."
481   (let ((outbuf (current-buffer)))
482     (mm-with-unibyte-buffer
483       (if (functionp method)
484           (let ((cur (current-buffer)))
485             (if (eq method 'mailcap-save-binary-file)
486                 (progn
487                   (set-buffer (generate-new-buffer " *mm*"))
488                   (setq method nil))
489               (mm-insert-part handle)
490               (let ((win (get-buffer-window cur t)))
491                 (when win
492                   (select-window win)))
493               (switch-to-buffer (generate-new-buffer " *mm*")))
494             (buffer-disable-undo)
495             (mm-set-buffer-file-coding-system mm-binary-coding-system)
496             (insert-buffer-substring cur)
497             (goto-char (point-min))
498             (message "Viewing with %s" method)
499             (let ((mm (current-buffer))
500                   (non-viewer (assq 'non-viewer
501                                     (mailcap-mime-info
502                                      (mm-handle-media-type handle) t))))
503               (unwind-protect
504                   (if method
505                       (funcall method)
506                     (mm-save-part handle))
507                 (when (and (not non-viewer)
508                            method)
509                   (mm-handle-set-undisplayer handle mm)))))
510         ;; The function is a string to be executed.
511         (mm-insert-part handle)
512         (let* ((dir (make-temp-name (expand-file-name "emm." mm-tmp-directory)))
513                (filename (mail-content-type-get
514                           (mm-handle-disposition handle) 'filename))
515                (mime-info (mailcap-mime-info
516                            (mm-handle-media-type handle) t))
517                (needsterm (or (assoc "needsterm" mime-info)
518                               (assoc "needsterminal" mime-info)))
519                (copiousoutput (assoc "copiousoutput" mime-info))
520                file buffer)
521           ;; We create a private sub-directory where we store our files.
522           (make-directory dir)
523           (set-file-modes dir 448)
524           (if filename
525               (setq file (expand-file-name (file-name-nondirectory filename)
526                                            dir))
527             (setq file (make-temp-name (expand-file-name "mm." dir))))
528           (let ((coding-system-for-write mm-binary-coding-system))
529             (write-region (point-min) (point-max) file nil 'nomesg))
530           (message "Viewing with %s" method)
531           (cond (needsterm
532                  (unwind-protect
533                      (start-process "*display*" nil
534                                     "xterm"
535                                     "-e" shell-file-name
536                                     shell-command-switch
537                                     (mm-mailcap-command
538                                      method file (mm-handle-type handle)))
539                    (mm-handle-set-undisplayer handle (cons file buffer)))
540                  (message "Displaying %s..." (format method file))
541                  'external)
542                 (copiousoutput
543                  (with-current-buffer outbuf
544                    (forward-line 1)
545                    (mm-insert-inline
546                     handle
547                     (unwind-protect
548                         (progn
549                           (call-process shell-file-name nil
550                                         (setq buffer
551                                               (generate-new-buffer " *mm*"))
552                                         nil
553                                         shell-command-switch
554                                         (mm-mailcap-command
555                                          method file (mm-handle-type handle)))
556                           (if (buffer-live-p buffer)
557                               (save-excursion
558                                 (set-buffer buffer)
559                                 (buffer-string))))
560                       (progn
561                         (ignore-errors (delete-file file))
562                         (ignore-errors (delete-directory
563                                         (file-name-directory file)))
564                         (ignore-errors (kill-buffer buffer))))))
565                  'inline)
566                 (t
567                  (unwind-protect
568                      (start-process "*display*"
569                                     (setq buffer
570                                           (generate-new-buffer " *mm*"))
571                                     shell-file-name
572                                     shell-command-switch
573                                     (mm-mailcap-command
574                                      method file (mm-handle-type handle)))
575                    (mm-handle-set-undisplayer handle (cons file buffer)))
576                  (message "Displaying %s..." (format method file))
577                  'external)))))))
578   
579 (defun mm-mailcap-command (method file type-list)
580   (let ((ctl (cdr type-list))
581         (beg 0)
582         (uses-stdin t)
583         out sub total)
584     (while (string-match "%{\\([^}]+\\)}\\|%s\\|%t\\|%%" method beg)
585       (push (substring method beg (match-beginning 0)) out)
586       (setq beg (match-end 0)
587             total (match-string 0 method)
588             sub (match-string 1 method))
589       (cond
590        ((string= total "%%")
591         (push "%" out))
592        ((string= total "%s")
593         (setq uses-stdin nil)
594         (push (mm-quote-arg file) out))
595        ((string= total "%t")
596         (push (mm-quote-arg (car type-list)) out))
597        (t
598         (push (mm-quote-arg (or (cdr (assq (intern sub) ctl)) "")) out))))
599     (push (substring method beg (length method)) out)
600     (if uses-stdin
601         (progn
602           (push "<" out)
603           (push (mm-quote-arg file) out)))
604     (mapconcat 'identity (nreverse out) "")))
605
606 (defun mm-remove-parts (handles)
607   "Remove the displayed MIME parts represented by HANDLES."
608   (if (and (listp handles)
609            (bufferp (car handles)))
610       (mm-remove-part handles)
611     (let (handle)
612       (while (setq handle (pop handles))
613         (cond
614          ((stringp handle)
615           (when (buffer-live-p (get-text-property 0 'buffer handle))
616             (kill-buffer (get-text-property 0 'buffer handle))))
617          ((and (listp handle)
618                (stringp (car handle)))
619           (mm-remove-parts (cdr handle)))
620          (t
621           (mm-remove-part handle)))))))
622
623 (defun mm-destroy-parts (handles)
624   "Remove the displayed MIME parts represented by HANDLES."
625   (if (and (listp handles)
626            (bufferp (car handles)))
627       (mm-destroy-part handles)
628     (let (handle)
629       (while (setq handle (pop handles))
630         (cond
631          ((stringp handle)
632           (when (buffer-live-p (get-text-property 0 'buffer handle))
633             (kill-buffer (get-text-property 0 'buffer handle))))
634          ((and (listp handle)
635                (stringp (car handle)))
636           (mm-destroy-parts handle))
637          (t
638           (mm-destroy-part handle)))))))
639
640 (defun mm-remove-part (handle)
641   "Remove the displayed MIME part represented by HANDLE."
642   (when (listp handle)
643     (let ((object (mm-handle-undisplayer handle)))
644       (ignore-errors
645         (cond
646          ;; Internally displayed part.
647          ((mm-annotationp object)
648           (delete-annotation object))
649          ((or (functionp object)
650               (and (listp object)
651                    (eq (car object) 'lambda)))
652           (funcall object))
653          ;; Externally displayed part.
654          ((consp object)
655           (ignore-errors (delete-file (car object)))
656           (ignore-errors (delete-directory (file-name-directory (car object))))
657           (ignore-errors (kill-buffer (cdr object))))
658          ((bufferp object)
659           (when (buffer-live-p object)
660             (kill-buffer object)))))
661       (mm-handle-set-undisplayer handle nil))))
662
663 (defun mm-display-inline (handle)
664   (let* ((type (mm-handle-media-type handle))
665          (function (cadr (mm-assoc-string-match mm-inline-media-tests type))))
666     (funcall function handle)
667     (goto-char (point-min))))
668
669 (defun mm-assoc-string-match (alist type)
670   (dolist (elem alist)
671     (when (string-match (car elem) type)
672       (return elem))))
673
674 (defun mm-inlinable-p (handle)
675   "Say whether HANDLE can be displayed inline."
676   (let ((alist mm-inline-media-tests)
677         (type (mm-handle-media-type handle))
678         test)
679     (while alist
680       (when (string-match (caar alist) type)
681         (setq test (caddar alist)
682               alist nil)
683         (setq test (funcall test handle)))
684       (pop alist))
685     test))
686
687 (defun mm-automatic-display-p (handle)
688   "Say whether the user wants HANDLE to be displayed automatically."
689   (let ((methods mm-automatic-display)
690         (type (mm-handle-media-type handle))
691         method result)
692     (while (setq method (pop methods))
693       (when (and (not (mm-inline-override-p handle))
694                  (string-match method type)
695                  (mm-inlinable-p handle))
696         (setq result t
697               methods nil)))
698     result))
699
700 (defun mm-inlined-p (handle)
701   "Say whether the user wants HANDLE to be displayed automatically."
702   (let ((methods mm-inlined-types)
703         (type (mm-handle-media-type handle))
704         method result)
705     (while (setq method (pop methods))
706       (when (and (not (mm-inline-override-p handle))
707                  (string-match method type)
708                  (mm-inlinable-p handle))
709         (setq result t
710               methods nil)))
711     result))
712
713 (defun mm-attachment-override-p (handle)
714   "Say whether HANDLE should have attachment behavior overridden."
715   (let ((types mm-attachment-override-types)
716         (type (mm-handle-media-type handle))
717         ty)
718     (catch 'found
719       (while (setq ty (pop types))
720         (when (and (string-match ty type)
721                    (mm-inlinable-p handle))
722           (throw 'found t))))))
723
724 (defun mm-inline-override-p (handle)
725   "Say whether HANDLE should have inline behavior overridden."
726   (let ((types mm-inline-override-types)
727         (type (mm-handle-media-type handle))
728         ty)
729     (catch 'found
730       (while (setq ty (pop types))
731         (when (string-match ty type)
732           (throw 'found t))))))
733
734 (defun mm-automatic-external-display-p (type)
735   "Return the user-defined method for TYPE."
736   (let ((methods mm-automatic-external-display)
737         method result)
738     (while (setq method (pop methods))
739       (when (string-match method type)
740         (setq result t
741               methods nil)))
742     result))
743
744 (defun mm-destroy-part (handle)
745   "Destroy the data structures connected to HANDLE."
746   (when (listp handle)
747     (mm-remove-part handle)
748     (when (buffer-live-p (mm-handle-buffer handle))
749       (kill-buffer (mm-handle-buffer handle)))))
750
751 (defun mm-handle-displayed-p (handle)
752   "Say whether HANDLE is displayed or not."
753   (mm-handle-undisplayer handle))
754
755 ;;;
756 ;;; Functions for outputting parts
757 ;;;
758
759 (defun mm-get-part (handle)
760   "Return the contents of HANDLE as a string."
761   (mm-with-unibyte-buffer
762     (insert (with-current-buffer (mm-handle-buffer handle)
763               (mm-with-unibyte-current-buffer-mule4
764                 (buffer-string))))
765     (mm-decode-content-transfer-encoding
766      (mm-handle-encoding handle)
767      (mm-handle-media-type handle))
768     (buffer-string)))
769
770 (defun mm-insert-part (handle)
771   "Insert the contents of HANDLE in the current buffer."
772   (let ((cur (current-buffer)))
773     (save-excursion
774       (if (member (mm-handle-media-supertype handle) '("text" "message"))
775           (with-temp-buffer
776             (insert-buffer-substring (mm-handle-buffer handle))
777             (mm-decode-content-transfer-encoding
778              (mm-handle-encoding handle)
779              (mm-handle-media-type handle))
780             (let ((temp (current-buffer)))
781               (set-buffer cur)
782               (insert-buffer-substring temp)))
783         (mm-with-unibyte-buffer
784           (insert-buffer-substring (mm-handle-buffer handle))
785           (mm-decode-content-transfer-encoding
786            (mm-handle-encoding handle)
787            (mm-handle-media-type handle))
788           (let ((temp (current-buffer)))
789             (set-buffer cur)
790             (insert-buffer-substring temp)))))))
791
792 (defvar mm-default-directory nil)
793
794 (defun mm-save-part (handle)
795   "Write HANDLE to a file."
796   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
797          (filename (mail-content-type-get
798                     (mm-handle-disposition handle) 'filename))
799          file)
800     (when filename
801       (setq filename (file-name-nondirectory filename)))
802     (setq file
803           (read-file-name "Save MIME part to: "
804                           (expand-file-name
805                            (or filename name "")
806                            (or mm-default-directory default-directory))))
807     (setq mm-default-directory (file-name-directory file))
808     (and (or (not (file-exists-p file))
809              (yes-or-no-p (format "File %s already exists; overwrite? "
810                                   file)))
811          (progn
812            (mm-save-part-to-file handle file)
813            file))))
814
815 (defun mm-save-part-to-file (handle file)
816   (mm-with-unibyte-buffer
817     (mm-insert-part handle)
818     (let ((coding-system-for-write 'binary)
819           ;; Don't re-compress .gz & al.  Arguably we should make
820           ;; `file-name-handler-alist' nil, but that would chop
821           ;; ange-ftp, which is reasonable to use here.
822           (inhibit-file-name-operation 'write-region)
823           (inhibit-file-name-handlers
824            (cons 'jka-compr-handler inhibit-file-name-handlers)))
825       (write-region (point-min) (point-max) file))))
826
827 (defun mm-pipe-part (handle)
828   "Pipe HANDLE to a process."
829   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
830          (command
831           (read-string "Shell command on MIME part: " mm-last-shell-command)))
832     (mm-with-unibyte-buffer
833       (mm-insert-part handle)
834       (shell-command-on-region (point-min) (point-max) command nil))))
835
836 (defun mm-interactively-view-part (handle)
837   "Display HANDLE using METHOD."
838   (let* ((type (mm-handle-media-type handle))
839          (methods
840           (mapcar (lambda (i) (list (cdr (assoc 'viewer i))))
841                   (mailcap-mime-info type 'all)))
842          (method (let ((minibuffer-local-completion-map
843                         mm-viewer-completion-map))
844                    (completing-read "Viewer: " methods))))
845     (when (string= method "")
846       (error "No method given"))
847     (if (string-match "^[^% \t]+$" method)
848         (setq method (concat method " %s")))
849     (mm-display-external handle method)))
850
851 (defun mm-preferred-alternative (handles &optional preferred)
852   "Say which of HANDLES are preferred."
853   (let ((prec (if preferred (list preferred)
854                 (mm-preferred-alternative-precedence handles)))
855         p h result type handle)
856     (while (setq p (pop prec))
857       (setq h handles)
858       (while h
859         (setq handle (car h))
860         (setq type (mm-handle-media-type handle))
861         (when (and (equal p type)
862                    (mm-automatic-display-p handle)
863                    (or (stringp (car handle))
864                        (not (mm-handle-disposition handle))
865                        (equal (car (mm-handle-disposition handle))
866                               "inline")))
867           (setq result handle
868                 h nil
869                 prec nil))
870         (pop h)))
871     result))
872
873 (defun mm-preferred-alternative-precedence (handles)
874   "Return the precedence based on HANDLES and `mm-discouraged-alternatives'."
875   (let ((seq (nreverse (mapcar #'mm-handle-media-type
876                                handles))))
877     (dolist (disc (reverse mm-discouraged-alternatives))
878       (dolist (elem (copy-sequence seq))
879         (when (string-match disc elem)
880           (setq seq (nconc (delete elem seq) (list elem))))))
881     seq))
882
883 (defun mm-get-content-id (id)
884   "Return the handle(s) referred to by ID."
885   (cdr (assoc id mm-content-id-alist)))
886
887 (defun mm-get-image (handle)
888   "Return an image instance based on HANDLE."
889   (let ((type (mm-handle-media-subtype handle))
890         spec)
891     ;; Allow some common translations.
892     (setq type
893           (cond
894            ((equal type "x-pixmap")
895             "xpm")
896            ((equal type "x-xbitmap")
897             "xbm")
898            ((equal type "x-portable-bitmap")
899             "pbm")
900            (t type)))
901     (or (mm-handle-cache handle)
902         (mm-with-unibyte-buffer
903           (mm-insert-part handle)
904           (prog1
905               (setq spec
906                     (ignore-errors
907                      ;; Avoid testing `make-glyph' since W3 may define
908                      ;; a bogus version of it.
909                       (if (fboundp 'create-image)
910                           (create-image (buffer-string) (intern type) 'data-p)
911                         (cond
912                          ((equal type "xbm")
913                           ;; xbm images require special handling, since
914                           ;; the only way to create glyphs from these
915                           ;; (without a ton of work) is to write them
916                           ;; out to a file, and then create a file
917                           ;; specifier.
918                           (let ((file (make-temp-name
919                                        (expand-file-name "emm.xbm"
920                                                          mm-tmp-directory))))
921                             (unwind-protect
922                                 (progn
923                                   (write-region (point-min) (point-max) file)
924                                   (make-glyph (list (cons 'x file))))
925                               (ignore-errors
926                                (delete-file file)))))
927                          (t
928                           (make-glyph
929                            (vector (intern type) :data (buffer-string))))))))
930             (mm-handle-set-cache handle spec))))))
931
932 (defun mm-image-fit-p (handle)
933   "Say whether the image in HANDLE will fit the current window."
934   (let ((image (mm-get-image handle)))
935     (if (fboundp 'glyph-width)
936         ;; XEmacs' glyphs can actually tell us about their width, so
937         ;; lets be nice and smart about them.
938         (or mm-inline-large-images
939             (and (< (glyph-width image) (window-pixel-width))
940                  (< (glyph-height image) (window-pixel-height))))
941       (let* ((size (image-size image))
942              (w (car size))
943              (h (cdr size)))
944         (or mm-inline-large-images
945             (and (< h (1- (window-height))) ; Don't include mode line.
946                  (< w (window-width))))))))
947
948 (defun mm-valid-image-format-p (format)
949   "Say whether FORMAT can be displayed natively by Emacs."
950   (cond
951    ;; Handle XEmacs
952    ((fboundp 'valid-image-instantiator-format-p)
953     (valid-image-instantiator-format-p format))
954    ;; Handle Emacs 21
955    ((fboundp 'image-type-available-p)
956     (and (display-graphic-p)
957          (image-type-available-p format)))
958    ;; Nobody else can do images yet.
959    (t
960     nil)))
961
962 (defun mm-valid-and-fit-image-p (format handle)
963   "Say whether FORMAT can be displayed natively and HANDLE fits the window."
964   (and (mm-valid-image-format-p format)
965        (mm-image-fit-p handle)))
966
967 (defun mm-find-part-by-type (handles type &optional notp recursive) 
968   "Search in HANDLES for part with TYPE.
969 If NOTP, returns first non-matching part.
970 If RECURSIVE, search recursively."
971   (let (handle)
972     (while handles
973       (if (and recursive (stringp (caar handles)))
974           (if (setq handle (mm-find-part-by-type (cdar handles) type
975                                                  notp recursive))
976               (setq handles nil))
977         (if (if notp
978                 (not (equal (mm-handle-media-type (car handles)) type))
979               (equal (mm-handle-media-type (car handles)) type))
980             (setq handle (car handles)
981                   handles nil)))
982       (setq handles (cdr handles)))
983     handle))
984
985 (defun mm-find-raw-part-by-type (ctl type &optional notp) 
986   (goto-char (point-min))
987   (let* ((boundary (concat "--" (mm-handle-multipart-ctl-parameter ctl 
988                                                                    'boundary)))
989          (close-delimiter (concat "^" (regexp-quote boundary) "--[ \t]*$"))
990          start
991          (end (save-excursion
992                 (goto-char (point-max))
993                 (if (re-search-backward close-delimiter nil t)
994                     (match-beginning 0)
995                   (point-max))))
996          result)
997     (setq boundary (concat "^" (regexp-quote boundary) "[ \t]*$"))
998     (while (and (not result)
999                 (re-search-forward boundary end t))
1000       (goto-char (match-beginning 0))
1001       (when start
1002         (save-excursion
1003           (save-restriction
1004             (narrow-to-region start (1- (point)))
1005             (when (let ((ctl (ignore-errors 
1006                                (mail-header-parse-content-type 
1007                                 (mail-fetch-field "content-type")))))
1008                     (if notp
1009                         (not (equal (car ctl) type))
1010                       (equal (car ctl) type)))
1011               (setq result (buffer-substring (point-min) (point-max)))))))
1012       (forward-line 1)
1013       (setq start (point)))
1014     (when (and (not result) start)
1015       (save-excursion
1016         (save-restriction
1017           (narrow-to-region start end)
1018           (when (let ((ctl (ignore-errors 
1019                              (mail-header-parse-content-type 
1020                               (mail-fetch-field "content-type")))))
1021                   (if notp
1022                       (not (equal (car ctl) type))
1023                     (equal (car ctl) type)))
1024             (setq result (buffer-substring (point-min) (point-max)))))))
1025     result))
1026
1027 (defvar mm-security-handle nil)
1028 (defvar mm-security-from nil)
1029
1030 (defsubst mm-set-handle-multipart-parameter (handle parameter value)
1031   ;; HANDLE could be a CTL.
1032   (if handle
1033       (put-text-property 0 (length (car handle)) parameter value 
1034                          (car handle))))
1035
1036 (defun mm-possibly-verify-or-decrypt (parts ctl)
1037   (let ((subtype (cadr (split-string (car ctl) "/")))
1038         (mm-security-handle ctl) ;; (car CTL) is the type.
1039         (mm-security-from
1040          (save-restriction
1041            (mail-narrow-to-head)
1042            (cadr (mail-extract-address-components 
1043                   (or (mail-fetch-field "from") "")))))
1044         protocol func functest)
1045     (cond 
1046      ((equal subtype "signed")
1047       (unless (and (setq protocol 
1048                          (mm-handle-multipart-ctl-parameter ctl 'protocol))
1049                    (not (equal protocol "multipart/mixed")))
1050         ;; The message is broken or draft-ietf-openpgp-multsig-01.
1051         (let ((protocols mm-verify-function-alist))
1052           (while protocols
1053             (if (and (or (not (setq functest (nth 3 (car protocols))))
1054                          (funcall functest parts ctl))
1055                      (mm-find-part-by-type parts (caar protocols) nil t))
1056                 (setq protocol (caar protocols)
1057                       protocols nil)
1058               (setq protocols (cdr protocols))))))
1059       (setq func (nth 1 (assoc protocol mm-verify-function-alist)))
1060       (if (cond
1061            ((eq mm-verify-option 'never) nil)
1062            ((eq mm-verify-option 'always) t)
1063            ((eq mm-verify-option 'known) 
1064             (and func 
1065                  (or (not (setq functest 
1066                                 (nth 3 (assoc protocol 
1067                                               mm-verify-function-alist))))
1068                      (funcall functest parts ctl))))
1069            (t (y-or-n-p
1070                (format "Verify signed (%s) part? "
1071                        (or (nth 2 (assoc protocol mm-verify-function-alist))
1072                            (format "protocol=%s" protocol))))))
1073           (save-excursion
1074             (if func
1075                 (funcall func parts ctl)
1076               (mm-set-handle-multipart-parameter 
1077                mm-security-handle 'gnus-details 
1078                (format "Unknown sign protocol (%s)" protocol))))))
1079      ((equal subtype "encrypted")
1080       (unless (setq protocol 
1081                     (mm-handle-multipart-ctl-parameter ctl 'protocol))
1082         ;; The message is broken.
1083         (let ((parts parts))
1084           (while parts
1085             (if (assoc (mm-handle-media-type (car parts)) 
1086                        mm-decrypt-function-alist)
1087                 (setq protocol (mm-handle-media-type (car parts))
1088                       parts nil)
1089               (setq parts (cdr parts))))))
1090       (setq func (nth 1 (assoc protocol mm-decrypt-function-alist)))
1091       (if (cond
1092            ((eq mm-decrypt-option 'never) nil)
1093            ((eq mm-decrypt-option 'always) t)
1094            ((eq mm-decrypt-option 'known)
1095             (and func 
1096                  (or (not (setq functest 
1097                                 (nth 3 (assoc protocol 
1098                                               mm-decrypt-function-alist))))
1099                      (funcall functest parts ctl))))
1100            (t (y-or-n-p 
1101                (format "Decrypt (%s) part? "
1102                        (or (nth 2 (assoc protocol mm-decrypt-function-alist))
1103                            (format "protocol=%s" protocol))))))
1104           (save-excursion
1105             (if func
1106                 (setq parts (funcall func parts ctl))
1107               (mm-set-handle-multipart-parameter 
1108                mm-security-handle 'gnus-details 
1109                (format "Unknown encrypt protocol (%s)" protocol))))))
1110      (t nil))
1111     parts))
1112
1113 (provide 'mm-decode)
1114
1115 ;;; mm-decode.el ends here