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     (progn
369      (setq new-handle (copy-sequence handle))
370      (mm-handle-set-undisplayer new-handle function)
371      (mm-handle-set-undisplayer handle nil)
372      (push new-handle mm-postponed-undisplay-list)
373     )
374   (mm-handle-set-undisplayer handle function)
375   )
376 )
377
378 (defun mm-destroy-postponed-undisplay-list ()
379   (message "Destroying external MIME viewers")
380   (mm-destroy-parts mm-postponed-undisplay-list)
381 )
382
383 (defun mm-dissect-buffer (&optional no-strict-mime)
384   "Dissect the current buffer and return a list of MIME handles."
385   (save-excursion
386     (let (ct ctl type subtype cte cd description id result from)
387       (save-restriction
388         (mail-narrow-to-head)
389         (when (or no-strict-mime
390                   (mail-fetch-field "mime-version"))
391           (setq ct (mail-fetch-field "content-type")
392                 ctl (ignore-errors (mail-header-parse-content-type ct))
393                 cte (mail-fetch-field "content-transfer-encoding")
394                 cd (mail-fetch-field "content-disposition")
395                 description (mail-fetch-field "content-description")
396                 from (mail-fetch-field "from")
397                 id (mail-fetch-field "content-id"))
398           ;; FIXME: In some circumstances, this code is running within
399           ;; an unibyte macro.  mail-extract-address-components
400           ;; creates unibyte buffers. This `if', though not a perfect
401           ;; solution, avoids most of them.
402           (if from
403               (setq from (cadr (mail-extract-address-components from))))))
404       (when cte
405         (setq cte (mail-header-strip cte)))
406       (if (or (not ctl)
407               (not (string-match "/" (car ctl))))
408           (mm-dissect-singlepart
409            (list mm-dissect-default-type)
410            (and cte (intern (downcase (mail-header-remove-whitespace
411                                        (mail-header-remove-comments
412                                         cte)))))
413            no-strict-mime
414            (and cd (ignore-errors (mail-header-parse-content-disposition cd)))
415            description)
416         (setq type (split-string (car ctl) "/"))
417         (setq subtype (cadr type)
418               type (pop type))
419         (setq
420          result
421          (cond
422           ((equal type "multipart")
423            (let ((mm-dissect-default-type (if (equal subtype "digest")
424                                               "message/rfc822"
425                                             "text/plain")))
426              (add-text-properties 0 (length (car ctl))
427                                   (mm-alist-to-plist (cdr ctl)) (car ctl))
428
429              ;; what really needs to be done here is a way to link a
430              ;; MIME handle back to it's parent MIME handle (in a multilevel
431              ;; MIME article).  That would probably require changing
432              ;; the mm-handle API so we simply store the multipart buffert
433              ;; name as a text property of the "multipart/whatever" string.
434              (add-text-properties 0 (length (car ctl))
435                                   (list 'buffer (mm-copy-to-buffer))
436                                   (car ctl))
437              (add-text-properties 0 (length (car ctl))
438                                   (list 'from from)
439                                   (car ctl))
440              (cons (car ctl) (mm-dissect-multipart ctl))))
441           (t
442            (mm-dissect-singlepart
443             ctl
444             (and cte (intern (downcase (mail-header-remove-whitespace
445                                         (mail-header-remove-comments
446                                          cte)))))
447             no-strict-mime
448             (and cd (ignore-errors (mail-header-parse-content-disposition cd)))
449             description id))))
450         (when id
451           (when (string-match " *<\\(.*\\)> *" id)
452             (setq id (match-string 1 id)))
453           (push (cons id result) mm-content-id-alist))
454         result))))
455
456 (defun mm-dissect-singlepart (ctl cte &optional force cdl description id)
457   (when (or force
458             (if (equal "text/plain" (car ctl))
459                 (assoc 'format ctl)
460               t))
461     (let ((res (mm-make-handle
462                 (mm-copy-to-buffer) ctl cte nil cdl description nil id)))
463       (push (car res) mm-dissection-list)
464       res)))
465
466 (defun mm-remove-all-parts ()
467   "Remove all MIME handles."
468   (interactive)
469   (mapcar 'mm-remove-part mm-dissection-list)
470   (setq mm-dissection-list nil))
471
472 (defun mm-dissect-multipart (ctl)
473   (goto-char (point-min))
474   (let* ((boundary (concat "\n--" (mail-content-type-get ctl 'boundary)))
475          (close-delimiter (concat (regexp-quote boundary) "--[ \t]*$"))
476          start parts
477          (end (save-excursion
478                 (goto-char (point-max))
479                 (if (re-search-backward close-delimiter nil t)
480                     (match-beginning 0)
481                   (point-max)))))
482     (setq boundary (concat (regexp-quote boundary) "[ \t]*$"))
483     (while (and (< (point) end) (re-search-forward boundary end t))
484       (goto-char (match-beginning 0))
485       (when start
486         (save-excursion
487           (save-restriction
488             (narrow-to-region start (point))
489             (setq parts (nconc (list (mm-dissect-buffer t)) parts)))))
490       (forward-line 2)
491       (setq start (point)))
492     (when (and start (< start end))
493       (save-excursion
494         (save-restriction
495           (narrow-to-region start end)
496           (setq parts (nconc (list (mm-dissect-buffer t)) parts)))))
497     (mm-possibly-verify-or-decrypt (nreverse parts) ctl)))
498
499 (defun mm-copy-to-buffer ()
500   "Copy the contents of the current buffer to a fresh buffer."
501   (save-excursion
502     (let ((flag enable-multibyte-characters)
503           (new-buffer (generate-new-buffer " *mm*")))
504       (goto-char (point-min))
505       (search-forward-regexp "^\n" nil t)
506       (save-restriction
507         (narrow-to-region (point) (point-max))
508         (when flag
509           (set-buffer-multibyte nil))
510         (copy-to-buffer new-buffer (point-min) (point-max))
511         (when flag
512           (set-buffer-multibyte t)))
513       new-buffer)))
514
515 (defun mm-display-parts (handle &optional no-default)
516   (if (stringp (car handle))
517       (mapcar 'mm-display-parts (cdr handle))
518     (if (bufferp (car handle))
519         (save-restriction
520           (narrow-to-region (point) (point))
521           (mm-display-part handle)
522           (goto-char (point-max)))
523       (mapcar 'mm-display-parts handle))))
524
525 (defun mm-display-part (handle &optional no-default)
526   "Display the MIME part represented by HANDLE.
527 Returns nil if the part is removed; inline if displayed inline;
528 external if displayed external."
529   (save-excursion
530     (mailcap-parse-mailcaps)
531     (if (mm-handle-displayed-p handle)
532         (mm-remove-part handle)
533       (let* ((type (mm-handle-media-type handle))
534              (method (mailcap-mime-info type)))
535         (if (mm-inlined-p handle)
536             (progn
537               (forward-line 1)
538               (mm-display-inline handle)
539               'inline)
540           (when (or method
541                     (not no-default))
542             (if (and (not method)
543                      (equal "text" (car (split-string type))))
544                 (progn
545                   (forward-line 1)
546                   (mm-insert-inline handle (mm-get-part handle))
547                   'inline)
548               (mm-display-external
549                handle (or method 'mailcap-save-binary-file)))))))))
550
551 (defun mm-display-external (handle method)
552   "Display HANDLE using METHOD."
553   (let ((outbuf (current-buffer)))
554     (mm-with-unibyte-buffer
555       (if (functionp method)
556           (let ((cur (current-buffer)))
557             (if (eq method 'mailcap-save-binary-file)
558                 (progn
559                   (set-buffer (generate-new-buffer " *mm*"))
560                   (setq method nil))
561               (mm-insert-part handle)
562               (let ((win (get-buffer-window cur t)))
563                 (when win
564                   (select-window win)))
565               (switch-to-buffer (generate-new-buffer " *mm*")))
566             (buffer-disable-undo)
567             (mm-set-buffer-file-coding-system mm-binary-coding-system)
568             (insert-buffer-substring cur)
569             (goto-char (point-min))
570             (message "Viewing with %s" method)
571             (let ((mm (current-buffer))
572                   (non-viewer (assq 'non-viewer
573                                     (mailcap-mime-info
574                                      (mm-handle-media-type handle) t))))
575               (unwind-protect
576                   (if method
577                       (funcall method)
578                     (mm-save-part handle))
579                 (when (and (not non-viewer)
580                            method)
581                   (mm-handle-set-undisplayer handle mm)))))
582         ;; The function is a string to be executed.
583         (mm-insert-part handle)
584         (let* ((dir (make-temp-name (expand-file-name "emm." mm-tmp-directory)))
585                (filename (mail-content-type-get
586                           (mm-handle-disposition handle) 'filename))
587                (mime-info (mailcap-mime-info
588                            (mm-handle-media-type handle) t))
589                (needsterm (or (assoc "needsterm" mime-info)
590                               (assoc "needsterminal" mime-info)))
591                (copiousoutput (assoc "copiousoutput" mime-info))
592                file buffer)
593           ;; We create a private sub-directory where we store our files.
594           (make-directory dir)
595           (set-file-modes dir 448)
596           (if filename
597               (setq file (expand-file-name (file-name-nondirectory filename)
598                                            dir))
599             (setq file (make-temp-name (expand-file-name "mm." dir))))
600           (let ((coding-system-for-write mm-binary-coding-system))
601             (write-region (point-min) (point-max) file nil 'nomesg))
602           (message "Viewing with %s" method)
603           (cond (needsterm
604                  (unwind-protect
605                      (start-process "*display*" nil
606                                     "xterm"
607                                     "-e" shell-file-name
608                                     shell-command-switch
609                                     (mm-mailcap-command
610                                      method file (mm-handle-type handle)))
611                    (mm-handle-set-external-undisplayer handle (cons file buffer)))
612                  (message "Displaying %s..." (format method file))
613                  'external)
614                 (copiousoutput
615                  (with-current-buffer outbuf
616                    (forward-line 1)
617                    (mm-insert-inline
618                     handle
619                     (unwind-protect
620                         (progn
621                           (call-process shell-file-name nil
622                                         (setq buffer
623                                               (generate-new-buffer " *mm*"))
624                                         nil
625                                         shell-command-switch
626                                         (mm-mailcap-command
627                                          method file (mm-handle-type handle)))
628                           (if (buffer-live-p buffer)
629                               (save-excursion
630                                 (set-buffer buffer)
631                                 (buffer-string))))
632                       (progn
633                         (ignore-errors (delete-file file))
634                         (ignore-errors (delete-directory
635                                         (file-name-directory file)))
636                         (ignore-errors (kill-buffer buffer))))))
637                  'inline)
638                 (t
639                  (unwind-protect
640                      (start-process "*display*"
641                                     (setq buffer
642                                           (generate-new-buffer " *mm*"))
643                                     shell-file-name
644                                     shell-command-switch
645                                     (mm-mailcap-command
646                                      method file (mm-handle-type handle)))
647                    (mm-handle-set-external-undisplayer handle (cons file buffer)))
648                  (message "Displaying %s..." (format method file))
649                  'external)))))))
650
651 (defun mm-mailcap-command (method file type-list)
652   (let ((ctl (cdr type-list))
653         (beg 0)
654         (uses-stdin t)
655         out sub total)
656     (while (string-match "%{\\([^}]+\\)}\\|%s\\|%t\\|%%" method beg)
657       (push (substring method beg (match-beginning 0)) out)
658       (setq beg (match-end 0)
659             total (match-string 0 method)
660             sub (match-string 1 method))
661       (cond
662        ((string= total "%%")
663         (push "%" out))
664        ((string= total "%s")
665         (setq uses-stdin nil)
666         (push (mm-quote-arg file) out))
667        ((string= total "%t")
668         (push (mm-quote-arg (car type-list)) out))
669        (t
670         (push (mm-quote-arg (or (cdr (assq (intern sub) ctl)) "")) out))))
671     (push (substring method beg (length method)) out)
672     (if uses-stdin
673         (progn
674           (push "<" out)
675           (push (mm-quote-arg file) out)))
676     (mapconcat 'identity (nreverse out) "")))
677
678 (defun mm-remove-parts (handles)
679   "Remove the displayed MIME parts represented by HANDLES."
680   (if (and (listp handles)
681            (bufferp (car handles)))
682       (mm-remove-part handles)
683     (let (handle)
684       (while (setq handle (pop handles))
685         (cond
686          ((stringp handle)
687           (when (buffer-live-p (get-text-property 0 'buffer handle))
688             (kill-buffer (get-text-property 0 'buffer handle))))
689          ((and (listp handle)
690                (stringp (car handle)))
691           (mm-remove-parts (cdr handle)))
692          (t
693           (mm-remove-part handle)))))))
694
695 (defun mm-destroy-parts (handles)
696   "Remove the displayed MIME parts represented by HANDLES."
697   (if (and (listp handles)
698            (bufferp (car handles)))
699       (mm-destroy-part handles)
700     (let (handle)
701       (while (setq handle (pop handles))
702         (cond
703          ((stringp handle)
704           (when (buffer-live-p (get-text-property 0 'buffer handle))
705             (kill-buffer (get-text-property 0 'buffer handle))))
706          ((and (listp handle)
707                (stringp (car handle)))
708           (mm-destroy-parts handle))
709          (t
710           (mm-destroy-part handle)))))))
711
712 (defun mm-remove-part (handle)
713   "Remove the displayed MIME part represented by HANDLE."
714   (when (listp handle)
715     (let ((object (mm-handle-undisplayer handle)))
716       (ignore-errors
717         (cond
718          ;; Internally displayed part.
719          ((mm-annotationp object)
720           (delete-annotation object))
721          ((or (functionp object)
722               (and (listp object)
723                    (eq (car object) 'lambda)))
724           (funcall object))
725          ;; Externally displayed part.
726          ((consp object)
727           (ignore-errors (delete-file (car object)))
728           (ignore-errors (delete-directory (file-name-directory (car object))))
729           (ignore-errors (kill-buffer (cdr object))))
730          ((bufferp object)
731           (when (buffer-live-p object)
732             (kill-buffer object)))))
733       (mm-handle-set-undisplayer handle nil))))
734
735 (defun mm-display-inline (handle)
736   (let* ((type (mm-handle-media-type handle))
737          (function (cadr (mm-assoc-string-match mm-inline-media-tests type))))
738     (funcall function handle)
739     (goto-char (point-min))))
740
741 (defun mm-assoc-string-match (alist type)
742   (dolist (elem alist)
743     (when (string-match (car elem) type)
744       (return elem))))
745
746 (defun mm-inlinable-p (handle)
747   "Say whether HANDLE can be displayed inline."
748   (let ((alist mm-inline-media-tests)
749         (type (mm-handle-media-type handle))
750         test)
751     (while alist
752       (when (string-match (caar alist) type)
753         (setq test (caddar alist)
754               alist nil)
755         (setq test (funcall test handle)))
756       (pop alist))
757     test))
758
759 (defun mm-automatic-display-p (handle)
760   "Say whether the user wants HANDLE to be displayed automatically."
761   (let ((methods mm-automatic-display)
762         (type (mm-handle-media-type handle))
763         method result)
764     (while (setq method (pop methods))
765       (when (and (not (mm-inline-override-p handle))
766                  (string-match method type)
767                  (mm-inlinable-p handle))
768         (setq result t
769               methods nil)))
770     result))
771
772 (defun mm-inlined-p (handle)
773   "Say whether the user wants HANDLE to be displayed automatically."
774   (let ((methods mm-inlined-types)
775         (type (mm-handle-media-type handle))
776         method result)
777     (while (setq method (pop methods))
778       (when (and (not (mm-inline-override-p handle))
779                  (string-match method type)
780                  (mm-inlinable-p handle))
781         (setq result t
782               methods nil)))
783     result))
784
785 (defun mm-attachment-override-p (handle)
786   "Say whether HANDLE should have attachment behavior overridden."
787   (let ((types mm-attachment-override-types)
788         (type (mm-handle-media-type handle))
789         ty)
790     (catch 'found
791       (while (setq ty (pop types))
792         (when (and (string-match ty type)
793                    (mm-inlinable-p handle))
794           (throw 'found t))))))
795
796 (defun mm-inline-override-p (handle)
797   "Say whether HANDLE should have inline behavior overridden."
798   (let ((types mm-inline-override-types)
799         (type (mm-handle-media-type handle))
800         ty)
801     (catch 'found
802       (while (setq ty (pop types))
803         (when (string-match ty type)
804           (throw 'found t))))))
805
806 (defun mm-automatic-external-display-p (type)
807   "Return the user-defined method for TYPE."
808   (let ((methods mm-automatic-external-display)
809         method result)
810     (while (setq method (pop methods))
811       (when (string-match method type)
812         (setq result t
813               methods nil)))
814     result))
815
816 (defun mm-destroy-part (handle)
817   "Destroy the data structures connected to HANDLE."
818   (when (listp handle)
819     (mm-remove-part handle)
820     (when (buffer-live-p (mm-handle-buffer handle))
821       (kill-buffer (mm-handle-buffer handle)))))
822
823 (defun mm-handle-displayed-p (handle)
824   "Say whether HANDLE is displayed or not."
825   (mm-handle-undisplayer handle))
826
827 ;;;
828 ;;; Functions for outputting parts
829 ;;;
830
831 (defun mm-get-part (handle)
832   "Return the contents of HANDLE as a string."
833   (mm-with-unibyte-buffer
834     (insert (with-current-buffer (mm-handle-buffer handle)
835               (mm-with-unibyte-current-buffer-mule4
836                 (buffer-string))))
837     (mm-decode-content-transfer-encoding
838      (mm-handle-encoding handle)
839      (mm-handle-media-type handle))
840     (buffer-string)))
841
842 (defun mm-insert-part (handle)
843   "Insert the contents of HANDLE in the current buffer."
844   (let ((cur (current-buffer)))
845     (save-excursion
846       (if (member (mm-handle-media-supertype handle) '("text" "message"))
847           (with-temp-buffer
848             (insert-buffer-substring (mm-handle-buffer handle))
849             (mm-decode-content-transfer-encoding
850              (mm-handle-encoding handle)
851              (mm-handle-media-type handle))
852             (let ((temp (current-buffer)))
853               (set-buffer cur)
854               (insert-buffer-substring temp)))
855         (mm-with-unibyte-buffer
856           (insert-buffer-substring (mm-handle-buffer handle))
857           (mm-decode-content-transfer-encoding
858            (mm-handle-encoding handle)
859            (mm-handle-media-type handle))
860           (let ((temp (current-buffer)))
861             (set-buffer cur)
862             (insert-buffer-substring temp)))))))
863
864 (defun mm-save-part (handle)
865   "Write HANDLE to a file."
866   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
867          (filename (mail-content-type-get
868                     (mm-handle-disposition handle) 'filename))
869          file)
870     (when filename
871       (setq filename (file-name-nondirectory filename)))
872     (setq file
873           (read-file-name "Save MIME part to: "
874                           (expand-file-name
875                            (or filename name "")
876                            (or mm-default-directory default-directory))))
877     (setq mm-default-directory (file-name-directory file))
878     (and (or (not (file-exists-p file))
879              (yes-or-no-p (format "File %s already exists; overwrite? "
880                                   file)))
881          (progn
882            (mm-save-part-to-file handle file)
883            file))))
884
885 (defun mm-save-part-to-file (handle file)
886   (mm-with-unibyte-buffer
887     (mm-insert-part handle)
888     (let ((coding-system-for-write 'binary)
889           ;; Don't re-compress .gz & al.  Arguably we should make
890           ;; `file-name-handler-alist' nil, but that would chop
891           ;; ange-ftp, which is reasonable to use here.
892           (inhibit-file-name-operation 'write-region)
893           (inhibit-file-name-handlers
894            (cons 'jka-compr-handler inhibit-file-name-handlers)))
895       (write-region (point-min) (point-max) file))))
896
897 (defun mm-pipe-part (handle)
898   "Pipe HANDLE to a process."
899   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
900          (command
901           (read-string "Shell command on MIME part: " mm-last-shell-command)))
902     (mm-with-unibyte-buffer
903       (mm-insert-part handle)
904       (shell-command-on-region (point-min) (point-max) command nil))))
905
906 (defun mm-interactively-view-part (handle)
907   "Display HANDLE using METHOD."
908   (let* ((type (mm-handle-media-type handle))
909          (methods
910           (mapcar (lambda (i) (list (cdr (assoc 'viewer i))))
911                   (mailcap-mime-info type 'all)))
912          (method (let ((minibuffer-local-completion-map
913                         mm-viewer-completion-map))
914                    (completing-read "Viewer: " methods))))
915     (when (string= method "")
916       (error "No method given"))
917     (if (string-match "^[^% \t]+$" method)
918         (setq method (concat method " %s")))
919     (mm-display-external handle method)))
920
921 (defun mm-preferred-alternative (handles &optional preferred)
922   "Say which of HANDLES are preferred."
923   (let ((prec (if preferred (list preferred)
924                 (mm-preferred-alternative-precedence handles)))
925         p h result type handle)
926     (while (setq p (pop prec))
927       (setq h handles)
928       (while h
929         (setq handle (car h))
930         (setq type (mm-handle-media-type handle))
931         (when (and (equal p type)
932                    (mm-automatic-display-p handle)
933                    (or (stringp (car handle))
934                        (not (mm-handle-disposition handle))
935                        (equal (car (mm-handle-disposition handle))
936                               "inline")))
937           (setq result handle
938                 h nil
939                 prec nil))
940         (pop h)))
941     result))
942
943 (defun mm-preferred-alternative-precedence (handles)
944   "Return the precedence based on HANDLES and `mm-discouraged-alternatives'."
945   (let ((seq (nreverse (mapcar #'mm-handle-media-type
946                                handles))))
947     (dolist (disc (reverse mm-discouraged-alternatives))
948       (dolist (elem (copy-sequence seq))
949         (when (string-match disc elem)
950           (setq seq (nconc (delete elem seq) (list elem))))))
951     seq))
952
953 (defun mm-get-content-id (id)
954   "Return the handle(s) referred to by ID."
955   (cdr (assoc id mm-content-id-alist)))
956
957 (defun mm-get-image (handle)
958   "Return an image instance based on HANDLE."
959   (let ((type (mm-handle-media-subtype handle))
960         spec)
961     ;; Allow some common translations.
962     (setq type
963           (cond
964            ((equal type "x-pixmap")
965             "xpm")
966            ((equal type "x-xbitmap")
967             "xbm")
968            ((equal type "x-portable-bitmap")
969             "pbm")
970            (t type)))
971     (or (mm-handle-cache handle)
972         (mm-with-unibyte-buffer
973           (mm-insert-part handle)
974           (prog1
975               (setq spec
976                     (ignore-errors
977                      ;; Avoid testing `make-glyph' since W3 may define
978                      ;; a bogus version of it.
979                       (if (fboundp 'create-image)
980                           (create-image (buffer-string) (intern type) 'data-p)
981                         (cond
982                          ((equal type "xbm")
983                           ;; xbm images require special handling, since
984                           ;; the only way to create glyphs from these
985                           ;; (without a ton of work) is to write them
986                           ;; out to a file, and then create a file
987                           ;; specifier.
988                           (let ((file (make-temp-name
989                                        (expand-file-name "emm.xbm"
990                                                          mm-tmp-directory))))
991                             (unwind-protect
992                                 (progn
993                                   (write-region (point-min) (point-max) file)
994                                   (make-glyph (list (cons 'x file))))
995                               (ignore-errors
996                                (delete-file file)))))
997                          (t
998                           (make-glyph
999                            (vector (intern type) :data (buffer-string))))))))
1000             (mm-handle-set-cache handle spec))))))
1001
1002 (defun mm-image-fit-p (handle)
1003   "Say whether the image in HANDLE will fit the current window."
1004   (let ((image (mm-get-image handle)))
1005     (if (fboundp 'glyph-width)
1006         ;; XEmacs' glyphs can actually tell us about their width, so
1007         ;; lets be nice and smart about them.
1008         (or mm-inline-large-images
1009             (and (< (glyph-width image) (window-pixel-width))
1010                  (< (glyph-height image) (window-pixel-height))))
1011       (let* ((size (image-size image))
1012              (w (car size))
1013              (h (cdr size)))
1014         (or mm-inline-large-images
1015             (and (< h (1- (window-height))) ; Don't include mode line.
1016                  (< w (window-width))))))))
1017
1018 (defun mm-valid-image-format-p (format)
1019   "Say whether FORMAT can be displayed natively by Emacs."
1020   (cond
1021    ;; Handle XEmacs
1022    ((fboundp 'valid-image-instantiator-format-p)
1023     (valid-image-instantiator-format-p format))
1024    ;; Handle Emacs 21
1025    ((fboundp 'image-type-available-p)
1026     (and (display-graphic-p)
1027          (image-type-available-p format)))
1028    ;; Nobody else can do images yet.
1029    (t
1030     nil)))
1031
1032 (defun mm-valid-and-fit-image-p (format handle)
1033   "Say whether FORMAT can be displayed natively and HANDLE fits the window."
1034   (and (mm-valid-image-format-p format)
1035        (mm-image-fit-p handle)))
1036
1037 (defun mm-find-part-by-type (handles type &optional notp recursive)
1038   "Search in HANDLES for part with TYPE.
1039 If NOTP, returns first non-matching part.
1040 If RECURSIVE, search recursively."
1041   (let (handle)
1042     (while handles
1043       (if (and recursive (stringp (caar handles)))
1044           (if (setq handle (mm-find-part-by-type (cdar handles) type
1045                                                  notp recursive))
1046               (setq handles nil))
1047         (if (if notp
1048                 (not (equal (mm-handle-media-type (car handles)) type))
1049               (equal (mm-handle-media-type (car handles)) type))
1050             (setq handle (car handles)
1051                   handles nil)))
1052       (setq handles (cdr handles)))
1053     handle))
1054
1055 (defun mm-find-raw-part-by-type (ctl type &optional notp)
1056   (goto-char (point-min))
1057   (let* ((boundary (concat "--" (mm-handle-multipart-ctl-parameter ctl
1058                                                                    'boundary)))
1059          (close-delimiter (concat "^" (regexp-quote boundary) "--[ \t]*$"))
1060          start
1061          (end (save-excursion
1062                 (goto-char (point-max))
1063                 (if (re-search-backward close-delimiter nil t)
1064                     (match-beginning 0)
1065                   (point-max))))
1066          result)
1067     (setq boundary (concat "^" (regexp-quote boundary) "[ \t]*$"))
1068     (while (and (not result)
1069                 (re-search-forward boundary end t))
1070       (goto-char (match-beginning 0))
1071       (when start
1072         (save-excursion
1073           (save-restriction
1074             (narrow-to-region start (1- (point)))
1075             (when (let ((ctl (ignore-errors
1076                                (mail-header-parse-content-type
1077                                 (mail-fetch-field "content-type")))))
1078                     (if notp
1079                         (not (equal (car ctl) type))
1080                       (equal (car ctl) type)))
1081               (setq result (buffer-substring (point-min) (point-max)))))))
1082       (forward-line 1)
1083       (setq start (point)))
1084     (when (and (not result) start)
1085       (save-excursion
1086         (save-restriction
1087           (narrow-to-region start end)
1088           (when (let ((ctl (ignore-errors
1089                              (mail-header-parse-content-type
1090                               (mail-fetch-field "content-type")))))
1091                   (if notp
1092                       (not (equal (car ctl) type))
1093                     (equal (car ctl) type)))
1094             (setq result (buffer-substring (point-min) (point-max)))))))
1095     result))
1096
1097 (defvar mm-security-handle nil)
1098
1099 (defsubst mm-set-handle-multipart-parameter (handle parameter value)
1100   ;; HANDLE could be a CTL.
1101   (if handle
1102       (put-text-property 0 (length (car handle)) parameter value
1103                          (car handle))))
1104
1105 (defun mm-possibly-verify-or-decrypt (parts ctl)
1106   (let ((subtype (cadr (split-string (car ctl) "/")))
1107         (mm-security-handle ctl) ;; (car CTL) is the type.
1108         protocol func functest)
1109     (cond
1110      ((equal subtype "signed")
1111       (unless (and (setq protocol
1112                          (mm-handle-multipart-ctl-parameter ctl 'protocol))
1113                    (not (equal protocol "multipart/mixed")))
1114         ;; The message is broken or draft-ietf-openpgp-multsig-01.
1115         (let ((protocols mm-verify-function-alist))
1116           (while protocols
1117             (if (and (or (not (setq functest (nth 3 (car protocols))))
1118                          (funcall functest parts ctl))
1119                      (mm-find-part-by-type parts (caar protocols) nil t))
1120                 (setq protocol (caar protocols)
1121                       protocols nil)
1122               (setq protocols (cdr protocols))))))
1123       (setq func (nth 1 (assoc protocol mm-verify-function-alist)))
1124       (if (cond
1125            ((eq mm-verify-option 'never) nil)
1126            ((eq mm-verify-option 'always) t)
1127            ((eq mm-verify-option 'known)
1128             (and func
1129                  (or (not (setq functest
1130                                 (nth 3 (assoc protocol
1131                                               mm-verify-function-alist))))
1132                      (funcall functest parts ctl))))
1133            (t (y-or-n-p
1134                (format "Verify signed (%s) part? "
1135                        (or (nth 2 (assoc protocol mm-verify-function-alist))
1136                            (format "protocol=%s" protocol))))))
1137           (save-excursion
1138             (if func
1139                 (funcall func parts ctl)
1140               (mm-set-handle-multipart-parameter
1141                mm-security-handle 'gnus-details
1142                (format "Unknown sign protocol (%s)" protocol))))))
1143      ((equal subtype "encrypted")
1144       (unless (setq protocol
1145                     (mm-handle-multipart-ctl-parameter ctl 'protocol))
1146         ;; The message is broken.
1147         (let ((parts parts))
1148           (while parts
1149             (if (assoc (mm-handle-media-type (car parts))
1150                        mm-decrypt-function-alist)
1151                 (setq protocol (mm-handle-media-type (car parts))
1152                       parts nil)
1153               (setq parts (cdr parts))))))
1154       (setq func (nth 1 (assoc protocol mm-decrypt-function-alist)))
1155       (if (cond
1156            ((eq mm-decrypt-option 'never) nil)
1157            ((eq mm-decrypt-option 'always) t)
1158            ((eq mm-decrypt-option 'known)
1159             (and func
1160                  (or (not (setq functest
1161                                 (nth 3 (assoc protocol
1162                                               mm-decrypt-function-alist))))
1163                      (funcall functest parts ctl))))
1164            (t (y-or-n-p
1165                (format "Decrypt (%s) part? "
1166                        (or (nth 2 (assoc protocol mm-decrypt-function-alist))
1167                            (format "protocol=%s" protocol))))))
1168           (save-excursion
1169             (if func
1170                 (setq parts (funcall func parts ctl))
1171               (mm-set-handle-multipart-parameter
1172                mm-security-handle 'gnus-details
1173                (format "Unknown encrypt protocol (%s)" protocol))))))
1174      (t nil))
1175     parts))
1176
1177 (defun mm-multiple-handles (handles)
1178    (and (listp (car handles)) 
1179         (> (length handles) 1)))
1180
1181 (defun mm-merge-handles (handles1 handles2) 
1182   (append
1183    (if (listp (car handles1)) 
1184        handles1
1185      (list handles1))
1186    (if (listp (car handles2))
1187        handles2
1188      (list handles2))))
1189
1190 (provide 'mm-decode)
1191
1192 ;;; mm-decode.el ends here