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