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