Synch to Gnus 200310022323.
[elisp/gnus.git-] / lisp / mm-decode.el
1 ;;; mm-decode.el --- Functions for decoding MIME things
2 ;; Copyright (C) 1998, 1999, 2000, 2001, 2002,
3 ;;        2003 Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;;      MORIOKA Tomohiko <morioka@jaist.ac.jp>
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24 ;;; Commentary:
25
26 ;;; Code:
27
28 (require 'mail-parse)
29 (require 'gnus-mailcap)
30 (require 'mm-bodies)
31 (eval-when-compile (require 'cl)
32                    (require 'term))
33
34 (eval-and-compile
35   (autoload 'executable-find "executable")
36   (autoload 'mm-inline-partial "mm-partial")
37   (autoload 'mm-inline-external-body "mm-extern")
38   (autoload 'mm-insert-inline "mm-view"))
39
40 (add-hook 'gnus-exit-gnus-hook 'mm-destroy-postponed-undisplay-list)
41
42 (defgroup mime-display ()
43   "Display of MIME in mail and news articles."
44   :link '(custom-manual "(emacs-mime)Customization")
45   :version "21.1"
46   :group 'mail
47   :group 'news
48   :group 'multimedia)
49
50 (defgroup mime-security ()
51   "MIME security in mail and news articles."
52   :link '(custom-manual "(emacs-mime)Customization")
53   :group 'mail
54   :group 'news
55   :group 'multimedia)
56
57 ;;; Convenience macros.
58
59 (defmacro mm-handle-buffer (handle)
60   `(nth 0 ,handle))
61 (defmacro mm-handle-type (handle)
62   `(nth 1 ,handle))
63 (defsubst mm-handle-media-type (handle)
64   (if (stringp (car handle))
65       (car handle)
66     (car (mm-handle-type handle))))
67 (defsubst mm-handle-media-supertype (handle)
68   (car (split-string (mm-handle-media-type handle) "/")))
69 (defsubst mm-handle-media-subtype (handle)
70   (cadr (split-string (mm-handle-media-type handle) "/")))
71 (defmacro mm-handle-encoding (handle)
72   `(nth 2 ,handle))
73 (defmacro mm-handle-undisplayer (handle)
74   `(nth 3 ,handle))
75 (defmacro mm-handle-set-undisplayer (handle function)
76   `(setcar (nthcdr 3 ,handle) ,function))
77 (defmacro mm-handle-disposition (handle)
78   `(nth 4 ,handle))
79 (defmacro mm-handle-description (handle)
80   `(nth 5 ,handle))
81 (defmacro mm-handle-cache (handle)
82   `(nth 6 ,handle))
83 (defmacro mm-handle-set-cache (handle contents)
84   `(setcar (nthcdr 6 ,handle) ,contents))
85 (defmacro mm-handle-id (handle)
86   `(nth 7 ,handle))
87 (defmacro mm-handle-multipart-original-buffer (handle)
88   `(get-text-property 0 'buffer (car ,handle)))
89 (defmacro mm-handle-multipart-from (handle)
90   `(get-text-property 0 'from (car ,handle)))
91 (defmacro mm-handle-multipart-ctl-parameter (handle parameter)
92   `(get-text-property 0 ,parameter (car ,handle)))
93
94 (defmacro mm-make-handle (&optional buffer type encoding undisplayer
95                                     disposition description cache
96                                     id)
97   `(list ,buffer ,type ,encoding ,undisplayer
98          ,disposition ,description ,cache ,id))
99
100 (defcustom mm-text-html-renderer
101   (cond ((locate-library "w3") 'w3)
102         ((locate-library "w3m") 'w3m)
103         ((executable-find "links") 'links)
104         ((executable-find "lynx") 'lynx)
105         (t 'html2text))
106   "Render of HTML contents.
107 It is one of defined renderer types, or a rendering function.
108 The defined renderer types are:
109 `w3'   : using Emacs/W3;
110 `w3m'  : using emacs-w3m;
111 `links': using links;
112 `lynx' : using lynx;
113 `html2text' : using html2text;
114 nil    : using external viewer."
115   :type '(choice (const w3)
116                  (const w3m)
117                  (const links)
118                  (const lynx)
119                  (const html2text)
120                  (const nil)
121                  (function))
122   :version "21.3"
123   :group 'mime-display)
124
125 (defvar mm-inline-text-html-renderer nil
126   "Function used for rendering inline HTML contents.
127 It is suggested to customize `mm-text-html-renderer' instead.")
128
129 (defcustom mm-inline-text-html-with-images nil
130   "If non-nil, Gnus will allow retrieving images in the HTML contents
131 with <img> tags.  It has no effect on Emacs/w3.  See also
132 the documentation for the option `mm-w3m-safe-url-regexp'."
133   :type 'boolean
134   :group 'mime-display)
135
136 (defcustom mm-w3m-safe-url-regexp "\\`cid:"
137   "Regexp that matches safe url names.
138 Some HTML mails might have a trick of spammers using <img> tags.
139 It is likely to be intended to verify whether you have read the
140 mail.  You can prevent your personal informations from leaking by
141 setting this to the regexp which matches the safe url names.  The
142 value of the variable `w3m-safe-url-regexp' will be bound with
143 this value.  You may set this value to nil if you consider all
144 urls to be safe."
145   :type '(choice (regexp :tag "Regexp")
146                  (const :tag "All URLs are safe" nil))
147   :group 'mime-display)
148
149 (defcustom mm-inline-text-html-with-w3m-keymap t
150   "If non-nil, use emacs-w3m command keys in the article buffer."
151   :type 'boolean
152   :group 'mime-display)
153
154 (defcustom mm-inline-media-tests
155   '(("image/jpeg"
156      mm-inline-image
157      (lambda (handle)
158        (mm-valid-and-fit-image-p 'jpeg handle)))
159     ("image/png"
160      mm-inline-image
161      (lambda (handle)
162        (mm-valid-and-fit-image-p 'png handle)))
163     ("image/gif"
164      mm-inline-image
165      (lambda (handle)
166        (mm-valid-and-fit-image-p 'gif handle)))
167     ("image/tiff"
168      mm-inline-image
169      (lambda (handle)
170        (mm-valid-and-fit-image-p 'tiff handle)) )
171     ("image/xbm"
172      mm-inline-image
173      (lambda (handle)
174        (mm-valid-and-fit-image-p 'xbm handle)))
175     ("image/x-xbitmap"
176      mm-inline-image
177      (lambda (handle)
178        (mm-valid-and-fit-image-p 'xbm handle)))
179     ("image/xpm"
180      mm-inline-image
181      (lambda (handle)
182        (mm-valid-and-fit-image-p 'xpm handle)))
183     ("image/x-xpixmap"
184      mm-inline-image
185      (lambda (handle)
186        (mm-valid-and-fit-image-p 'xpm handle)))
187     ("image/bmp"
188      mm-inline-image
189      (lambda (handle)
190        (mm-valid-and-fit-image-p 'bmp handle)))
191     ("image/x-portable-bitmap"
192      mm-inline-image
193      (lambda (handle)
194        (mm-valid-and-fit-image-p 'pbm handle)))
195     ("text/plain" mm-inline-text identity)
196     ("text/enriched" mm-inline-text identity)
197     ("text/richtext" mm-inline-text identity)
198     ("text/x-patch" mm-display-patch-inline
199      (lambda (handle)
200        (locate-library "diff-mode")))
201     ("application/emacs-lisp" mm-display-elisp-inline identity)
202     ("application/x-emacs-lisp" mm-display-elisp-inline identity)
203     ("text/html"
204      mm-inline-text-html
205      (lambda (handle)
206        (or mm-inline-text-html-renderer
207            mm-text-html-renderer)))
208     ("text/x-vcard"
209      mm-inline-text-vcard
210      (lambda (handle)
211        (or (featurep 'vcard)
212            (locate-library "vcard"))))
213     ("message/delivery-status" mm-inline-text identity)
214     ("message/rfc822" mm-inline-message identity)
215     ("message/partial" mm-inline-partial identity)
216     ("message/external-body" mm-inline-external-body identity)
217     ("text/.*" mm-inline-text identity)
218     ("audio/wav" mm-inline-audio
219      (lambda (handle)
220        (and (or (featurep 'nas-sound) (featurep 'native-sound))
221             (device-sound-enabled-p))))
222     ("audio/au"
223      mm-inline-audio
224      (lambda (handle)
225        (and (or (featurep 'nas-sound) (featurep 'native-sound))
226             (device-sound-enabled-p))))
227     ("application/pgp-signature" ignore identity)
228     ("application/x-pkcs7-signature" ignore identity)
229     ("application/pkcs7-signature" ignore identity)
230     ("application/x-pkcs7-mime" ignore identity)
231     ("application/pkcs7-mime" ignore identity)
232     ("multipart/alternative" ignore identity)
233     ("multipart/mixed" ignore identity)
234     ("multipart/related" ignore identity)
235     ;; Disable audio and image
236     ("audio/.*" ignore ignore)
237     ("image/.*" ignore ignore)
238     ;; Default to displaying as text
239     (".*" mm-inline-text mm-readable-p))
240   "Alist of media types/tests saying whether types can be displayed inline."
241   :type '(repeat (list (string :tag "MIME type")
242                        (function :tag "Display function")
243                        (function :tag "Display test")))
244   :group 'mime-display)
245
246 (defcustom mm-inlined-types
247   '("image/.*" "text/.*" "message/delivery-status" "message/rfc822"
248     "message/partial" "message/external-body" "application/emacs-lisp"
249     "application/x-emacs-lisp"
250     "application/pgp-signature" "application/x-pkcs7-signature"
251     "application/pkcs7-signature" "application/x-pkcs7-mime"
252     "application/pkcs7-mime")
253   "List of media types that are to be displayed inline.
254 See also `mm-inline-media-tests', which says how to display a media
255 type inline."
256   :type '(repeat string)
257   :group 'mime-display)
258
259 (defcustom mm-keep-viewer-alive-types
260   '("application/postscript" "application/msword" "application/vnd.ms-excel"
261     "application/pdf" "application/x-dvi")
262   "List of media types for which the external viewer will not be killed
263 when selecting a different article."
264   :type '(repeat string)
265   :group 'mime-display)
266
267 (defcustom mm-automatic-display
268   '("text/plain" "text/enriched" "text/richtext" "text/html"
269     "text/x-vcard" "image/.*" "message/delivery-status" "multipart/.*"
270     "message/rfc822" "text/x-patch" "application/pgp-signature"
271     "application/emacs-lisp" "application/x-emacs-lisp"
272     "application/x-pkcs7-signature"
273     "application/pkcs7-signature" "application/x-pkcs7-mime"
274     "application/pkcs7-mime")
275   "A list of MIME types to be displayed automatically."
276   :type '(repeat string)
277   :group 'mime-display)
278
279 (defcustom mm-attachment-override-types '("text/x-vcard"
280                                           "application/pkcs7-mime"
281                                           "application/x-pkcs7-mime"
282                                           "application/pkcs7-signature"
283                                           "application/x-pkcs7-signature")
284   "Types to have \"attachment\" ignored if they can be displayed inline."
285   :type '(repeat string)
286   :group 'mime-display)
287
288 (defcustom mm-inline-override-types nil
289   "Types to be treated as attachments even if they can be displayed inline."
290   :type '(repeat string)
291   :group 'mime-display)
292
293 (defcustom mm-automatic-external-display nil
294   "List of MIME type regexps that will be displayed externally automatically."
295   :type '(repeat string)
296   :group 'mime-display)
297
298 (defcustom mm-discouraged-alternatives nil
299   "List of MIME types that are discouraged when viewing multipart/alternative.
300 Viewing agents are supposed to view the last possible part of a message,
301 as that is supposed to be the richest.  However, users may prefer other
302 types instead, and this list says what types are most unwanted.  If,
303 for instance, text/html parts are very unwanted, and text/richtext are
304 somewhat unwanted, then the value of this variable should be set
305 to:
306
307  (\"text/html\" \"text/richtext\")"
308   :type '(repeat string)
309   :group 'mime-display)
310
311 (defcustom mm-tmp-directory
312   (if (fboundp 'temp-directory)
313       (temp-directory)
314     (if (boundp 'temporary-file-directory)
315         temporary-file-directory
316       "/tmp/"))
317   "Where mm will store its temporary files."
318   :type 'directory
319   :group 'mime-display)
320
321 (defcustom mm-inline-large-images nil
322   "If non-nil, then all images fit in the buffer."
323   :type 'boolean
324   :group 'mime-display)
325
326 (defvar mm-file-name-rewrite-functions
327   '(mm-file-name-delete-control mm-file-name-delete-gotchas)
328   "*List of functions used for rewriting file names of MIME parts.
329 Each function takes a file name as input and returns a file name.
330
331 Ready-made functions include
332 `mm-file-name-delete-control'
333 `mm-file-name-delete-gotchas'
334 `mm-file-name-delete-whitespace',
335 `mm-file-name-trim-whitespace',
336 `mm-file-name-collapse-whitespace',
337 `mm-file-name-replace-whitespace',
338 `capitalize', `downcase', `upcase', and
339 `upcase-initials'.")
340
341 (defvar mm-path-name-rewrite-functions nil
342   "*List of functions for rewriting the full file names of MIME parts.
343 This is used when viewing parts externally, and is meant for
344 transforming the absolute name so that non-compliant programs can find
345 the file where it's saved.
346
347 Each function takes a file name as input and returns a file name.")
348
349 (defvar mm-file-name-replace-whitespace nil
350   "String used for replacing whitespace characters; default is `\"_\"'.")
351
352 (defcustom mm-default-directory nil
353   "The default directory where mm will save files.
354 If not set, `default-directory' will be used."
355   :type '(choice directory (const :tag "Default" nil))
356   :group 'mime-display)
357
358 (defcustom mm-external-terminal-program "xterm"
359   "The program to start an external terminal."
360   :type 'string
361   :group 'mime-display)
362
363 ;;; Internal variables.
364
365 (defvar mm-last-shell-command "")
366 (defvar mm-content-id-alist nil)
367 (defvar mm-postponed-undisplay-list nil)
368
369 ;; According to RFC2046, in particular, in a digest, the default
370 ;; Content-Type value for a body part is changed from "text/plain" to
371 ;; "message/rfc822".
372 (defvar mm-dissect-default-type "text/plain")
373
374 (autoload 'mml2015-verify "mml2015")
375 (autoload 'mml2015-verify-test "mml2015")
376 (autoload 'mml-smime-verify "mml-smime")
377 (autoload 'mml-smime-verify-test "mml-smime")
378
379 (defvar mm-verify-function-alist
380   '(("application/pgp-signature" mml2015-verify "PGP" mml2015-verify-test)
381     ("application/x-gnus-pgp-signature" mm-uu-pgp-signed-extract-1 "PGP"
382      mm-uu-pgp-signed-test)
383     ("application/pkcs7-signature" mml-smime-verify "S/MIME"
384      mml-smime-verify-test)
385     ("application/x-pkcs7-signature" mml-smime-verify "S/MIME"
386      mml-smime-verify-test)))
387
388 (defcustom mm-verify-option 'never
389   "Option of verifying signed parts.
390 `never', not verify; `always', always verify;
391 `known', only verify known protocols.  Otherwise, ask user."
392   :type '(choice (item always)
393                  (item never)
394                  (item :tag "only known protocols" known)
395                  (item :tag "ask" nil))
396   :group 'mime-security)
397
398 (autoload 'mml2015-decrypt "mml2015")
399 (autoload 'mml2015-decrypt-test "mml2015")
400
401 (defvar mm-decrypt-function-alist
402   '(("application/pgp-encrypted" mml2015-decrypt "PGP" mml2015-decrypt-test)
403     ("application/x-gnus-pgp-encrypted" mm-uu-pgp-encrypted-extract-1 "PGP"
404      mm-uu-pgp-encrypted-test)))
405
406 (defcustom mm-decrypt-option nil
407   "Option of decrypting encrypted parts.
408 `never', not decrypt; `always', always decrypt;
409 `known', only decrypt known protocols.  Otherwise, ask user."
410   :type '(choice (item always)
411                  (item never)
412                  (item :tag "only known protocols" known)
413                  (item :tag "ask" nil))
414   :group 'mime-security)
415
416 (defvar mm-viewer-completion-map
417   (let ((map (make-sparse-keymap 'mm-viewer-completion-map)))
418     (set-keymap-parent map minibuffer-local-completion-map)
419     map)
420   "Keymap for input viewer with completion.")
421
422 ;; Should we bind other key to minibuffer-complete-word?
423 (define-key mm-viewer-completion-map " " 'self-insert-command)
424
425 (defvar mm-viewer-completion-map
426   (let ((map (make-sparse-keymap 'mm-viewer-completion-map)))
427     (set-keymap-parent map minibuffer-local-completion-map)
428     map)
429   "Keymap for input viewer with completion.")
430
431 ;; Should we bind other key to minibuffer-complete-word?
432 (define-key mm-viewer-completion-map " " 'self-insert-command)
433
434 ;;; The functions.
435
436 (defun mm-alist-to-plist (alist)
437   "Convert association list ALIST into the equivalent property-list form.
438 The plist is returned.  This converts from
439
440 \((a . 1) (b . 2) (c . 3))
441
442 into
443
444 \(a 1 b 2 c 3)
445
446 The original alist is not modified.  See also `destructive-alist-to-plist'."
447   (let (plist)
448     (while alist
449       (let ((el (car alist)))
450         (setq plist (cons (cdr el) (cons (car el) plist))))
451       (setq alist (cdr alist)))
452     (nreverse plist)))
453
454 (defun mm-keep-viewer-alive-p (handle)
455   "Say whether external viewer for HANDLE should stay alive."
456   (let ((types mm-keep-viewer-alive-types)
457         (type (mm-handle-media-type handle))
458         ty)
459     (catch 'found
460       (while (setq ty (pop types))
461         (when (string-match ty type)
462           (throw 'found t))))))
463
464 (defun mm-handle-set-external-undisplayer (handle function)
465   "Set the undisplayer for HANDLE to FUNCTION.
466 Postpone undisplaying of viewers for types in
467 `mm-keep-viewer-alive-types'."
468   (if (mm-keep-viewer-alive-p handle)
469       (let ((new-handle (copy-sequence handle)))
470         (mm-handle-set-undisplayer new-handle function)
471         (mm-handle-set-undisplayer handle nil)
472         (push new-handle mm-postponed-undisplay-list))
473     (mm-handle-set-undisplayer handle function)))
474
475 (defun mm-destroy-postponed-undisplay-list ()
476   (when mm-postponed-undisplay-list
477     (message "Destroying external MIME viewers")
478     (mm-destroy-parts mm-postponed-undisplay-list)))
479
480 (defun mm-dissect-buffer (&optional no-strict-mime loose-mime)
481   "Dissect the current buffer and return a list of MIME handles."
482   (save-excursion
483     (let (ct ctl type subtype cte cd description id result from)
484       (save-restriction
485         (mail-narrow-to-head)
486         (when (or no-strict-mime
487                   loose-mime
488                   (mail-fetch-field "mime-version"))
489           (setq ct (mail-fetch-field "content-type")
490                 ctl (ignore-errors (mail-header-parse-content-type ct))
491                 cte (mail-fetch-field "content-transfer-encoding")
492                 cd (mail-fetch-field "content-disposition")
493                 description (mail-fetch-field "content-description")
494                 from (mail-fetch-field "from")
495                 id (mail-fetch-field "content-id"))
496           ;; FIXME: In some circumstances, this code is running within
497           ;; an unibyte macro.  mail-extract-address-components
498           ;; creates unibyte buffers. This `if', though not a perfect
499           ;; solution, avoids most of them.
500           (if from
501               (setq from (cadr (mail-extract-address-components from))))))
502       (when cte
503         (setq cte (mail-header-strip cte)))
504       (if (or (not ctl)
505               (not (string-match "/" (car ctl))))
506           (mm-dissect-singlepart
507            (list mm-dissect-default-type)
508            (and cte (intern (downcase (mail-header-remove-whitespace
509                                        (mail-header-remove-comments
510                                         cte)))))
511            no-strict-mime
512            (and cd (ignore-errors (mail-header-parse-content-disposition cd)))
513            description)
514         (setq type (split-string (car ctl) "/"))
515         (setq subtype (cadr type)
516               type (pop type))
517         (setq
518          result
519          (cond
520           ((equal type "multipart")
521            (let ((mm-dissect-default-type (if (equal subtype "digest")
522                                               "message/rfc822"
523                                             "text/plain")))
524              (add-text-properties 0 (length (car ctl))
525                                   (mm-alist-to-plist (cdr ctl)) (car ctl))
526
527              ;; what really needs to be done here is a way to link a
528              ;; MIME handle back to it's parent MIME handle (in a multilevel
529              ;; MIME article).  That would probably require changing
530              ;; the mm-handle API so we simply store the multipart buffert
531              ;; name as a text property of the "multipart/whatever" string.
532              (add-text-properties 0 (length (car ctl))
533                                   (list 'buffer (mm-copy-to-buffer))
534                                   (car ctl))
535              (add-text-properties 0 (length (car ctl))
536                                   (list 'from from)
537                                   (car ctl))
538              (cons (car ctl) (mm-dissect-multipart ctl))))
539           (t
540            (mm-possibly-verify-or-decrypt
541             (mm-dissect-singlepart
542              ctl
543              (and cte (intern (downcase (mail-header-remove-whitespace
544                                          (mail-header-remove-comments
545                                           cte)))))
546              no-strict-mime
547              (and cd (ignore-errors
548                        (mail-header-parse-content-disposition cd)))
549              description id)
550             ctl))))
551         (when id
552           (when (string-match " *<\\(.*\\)> *" id)
553             (setq id (match-string 1 id)))
554           (push (cons id result) mm-content-id-alist))
555         result))))
556
557 (defun mm-dissect-singlepart (ctl cte &optional force cdl description id)
558   (when (or force
559             (if (equal "text/plain" (car ctl))
560                 (assoc 'format ctl)
561               t))
562     (mm-make-handle
563      (mm-copy-to-buffer) ctl cte nil cdl description nil id)))
564
565 (defun mm-dissect-multipart (ctl)
566   (goto-char (point-min))
567   (let* ((boundary (concat "\n--" (mail-content-type-get ctl 'boundary)))
568          (close-delimiter (concat (regexp-quote boundary) "--[ \t]*$"))
569          start parts
570          (end (save-excursion
571                 (goto-char (point-max))
572                 (if (re-search-backward close-delimiter nil t)
573                     (match-beginning 0)
574                   (point-max)))))
575     (setq boundary (concat (regexp-quote boundary) "[ \t]*$"))
576     (while (and (< (point) end) (re-search-forward boundary end t))
577       (goto-char (match-beginning 0))
578       (when start
579         (save-excursion
580           (save-restriction
581             (narrow-to-region start (point))
582             (setq parts (nconc (list (mm-dissect-buffer t)) parts)))))
583       (end-of-line 2)
584       (or (looking-at boundary)
585           (forward-line 1))
586       (setq start (point)))
587     (when (and start (< start end))
588       (save-excursion
589         (save-restriction
590           (narrow-to-region start end)
591           (setq parts (nconc (list (mm-dissect-buffer t)) parts)))))
592     (mm-possibly-verify-or-decrypt (nreverse parts) ctl)))
593
594 (defun mm-copy-to-buffer ()
595   "Copy the contents of the current buffer to a fresh buffer."
596   (save-excursion
597     (let ((flag enable-multibyte-characters)
598           (new-buffer (generate-new-buffer " *mm*")))
599       (goto-char (point-min))
600       (search-forward-regexp "^\n" nil t)
601       (save-restriction
602         (narrow-to-region (point) (point-max))
603         (when flag
604           (set-buffer-multibyte nil))
605         (copy-to-buffer new-buffer (point-min) (point-max))
606         (when flag
607           (set-buffer-multibyte t)))
608       new-buffer)))
609
610 (defun mm-display-parts (handle &optional no-default)
611   (if (stringp (car handle))
612       (mapcar 'mm-display-parts (cdr handle))
613     (if (bufferp (car handle))
614         (save-restriction
615           (narrow-to-region (point) (point))
616           (mm-display-part handle)
617           (goto-char (point-max)))
618       (mapcar 'mm-display-parts handle))))
619
620 (defun mm-display-part (handle &optional no-default)
621   "Display the MIME part represented by HANDLE.
622 Returns nil if the part is removed; inline if displayed inline;
623 external if displayed external."
624   (save-excursion
625     (mailcap-parse-mailcaps)
626     (if (mm-handle-displayed-p handle)
627         (mm-remove-part handle)
628       (let* ((type (mm-handle-media-type handle))
629              (method (mailcap-mime-info type)))
630         (if (and (mm-inlinable-p handle)
631                  (mm-inlined-p handle))
632             (progn
633               (forward-line 1)
634               (mm-display-inline handle)
635               'inline)
636           (when (or method
637                     (not no-default))
638             (if (and (not method)
639                      (equal "text" (car (split-string type))))
640                 (progn
641                   (forward-line 1)
642                   (mm-insert-inline handle (mm-get-part handle))
643                   'inline)
644               (mm-display-external
645                handle (or method 'mailcap-save-binary-file)))))))))
646
647 (defun mm-display-external (handle method)
648   "Display HANDLE using METHOD."
649   (let ((outbuf (current-buffer)))
650     (mm-with-unibyte-buffer
651       (if (functionp method)
652           (let ((cur (current-buffer)))
653             (if (eq method 'mailcap-save-binary-file)
654                 (progn
655                   (set-buffer (generate-new-buffer " *mm*"))
656                   (setq method nil))
657               (mm-insert-part handle)
658               (let ((win (get-buffer-window cur t)))
659                 (when win
660                   (select-window win)))
661               (switch-to-buffer (generate-new-buffer " *mm*")))
662             (buffer-disable-undo)
663             (mm-set-buffer-file-coding-system mm-binary-coding-system)
664             (insert-buffer-substring cur)
665             (goto-char (point-min))
666             (message "Viewing with %s" method)
667             (let ((mm (current-buffer))
668                   (non-viewer (assq 'non-viewer
669                                     (mailcap-mime-info
670                                      (mm-handle-media-type handle) t))))
671               (unwind-protect
672                   (if method
673                       (funcall method)
674                     (mm-save-part handle))
675                 (when (and (not non-viewer)
676                            method)
677                   (mm-handle-set-undisplayer handle mm)))))
678         ;; The function is a string to be executed.
679         (mm-insert-part handle)
680         (let* ((dir (mm-make-temp-file
681                      (expand-file-name "emm." mm-tmp-directory) 'dir))
682                (filename (or
683                           (mail-content-type-get
684                            (mm-handle-disposition handle) 'filename)
685                           (mail-content-type-get
686                            (mm-handle-type handle) 'name)))
687                (mime-info (mailcap-mime-info
688                            (mm-handle-media-type handle) t))
689                (needsterm (or (assoc "needsterm" mime-info)
690                               (assoc "needsterminal" mime-info)))
691                (copiousoutput (assoc "copiousoutput" mime-info))
692                file buffer)
693           ;; We create a private sub-directory where we store our files.
694           (set-file-modes dir 448)
695           (if filename
696               (setq file (expand-file-name
697                           (gnus-map-function mm-file-name-rewrite-functions
698                                              (file-name-nondirectory filename))
699                           dir))
700             (setq file (mm-make-temp-file (expand-file-name "mm." dir))))
701           (let ((coding-system-for-write mm-binary-coding-system))
702             (write-region (point-min) (point-max) file nil 'nomesg))
703           (message "Viewing with %s" method)
704           (cond
705            (needsterm
706             (let ((command (mm-mailcap-command
707                             method file (mm-handle-type handle))))
708               (unwind-protect
709                   (if window-system
710                       (start-process "*display*" nil
711                                      mm-external-terminal-program
712                                      "-e" shell-file-name
713                                      shell-command-switch command)
714                     (require 'term)
715                     (require 'gnus-win)
716                     (set-buffer
717                      (setq buffer
718                            (make-term "display"
719                                       shell-file-name
720                                       nil
721                                       shell-command-switch command)))
722                     (term-mode)
723                     (term-char-mode)
724                     (set-process-sentinel
725                      (get-buffer-process buffer)
726                      `(lambda (process state)
727                         (if (eq 'exit (process-status process))
728                             (gnus-configure-windows
729                              ',gnus-current-window-configuration))))
730                     (gnus-configure-windows 'display-term))
731                 (mm-handle-set-external-undisplayer handle (cons file buffer)))
732               (message "Displaying %s..." command))
733             'external)
734            (copiousoutput
735             (with-current-buffer outbuf
736               (forward-line 1)
737               (mm-insert-inline
738                handle
739                (unwind-protect
740                    (progn
741                      (call-process shell-file-name nil
742                                    (setq buffer
743                                          (generate-new-buffer " *mm*"))
744                                    nil
745                                    shell-command-switch
746                                    (mm-mailcap-command
747                                     method file (mm-handle-type handle)))
748                      (if (buffer-live-p buffer)
749                          (save-excursion
750                            (set-buffer buffer)
751                            (buffer-string))))
752                  (progn
753                    (ignore-errors (delete-file file))
754                    (ignore-errors (delete-directory
755                                    (file-name-directory file)))
756                    (ignore-errors (kill-buffer buffer))))))
757             'inline)
758            (t
759             (let ((command (mm-mailcap-command
760                             method file (mm-handle-type handle))))
761               (unwind-protect
762                   (start-process "*display*"
763                                  (setq buffer
764                                        (generate-new-buffer " *mm*"))
765                                  shell-file-name
766                                  shell-command-switch command)
767                 (mm-handle-set-external-undisplayer
768                  handle (cons file buffer)))
769               (message "Displaying %s..." command))
770             'external)))))))
771
772 (defun mm-mailcap-command (method file type-list)
773   (let ((ctl (cdr type-list))
774         (beg 0)
775         (uses-stdin t)
776         out sub total)
777     (while (string-match "%{\\([^}]+\\)}\\|'%s'\\|\"%s\"\\|%s\\|%t\\|%%"
778                          method beg)
779       (push (substring method beg (match-beginning 0)) out)
780       (setq beg (match-end 0)
781             total (match-string 0 method)
782             sub (match-string 1 method))
783       (cond
784        ((string= total "%%")
785         (push "%" out))
786        ((or (string= total "%s")
787             ;; We do our own quoting.
788             (string= total "'%s'")
789             (string= total "\"%s\""))
790         (setq uses-stdin nil)
791         (push (mm-quote-arg
792                (gnus-map-function mm-path-name-rewrite-functions file)) out))
793        ((string= total "%t")
794         (push (mm-quote-arg (car type-list)) out))
795        (t
796         (push (mm-quote-arg (or (cdr (assq (intern sub) ctl)) "")) out))))
797     (push (substring method beg (length method)) out)
798     (when uses-stdin
799       (push "<" out)
800       (push (mm-quote-arg
801              (gnus-map-function mm-path-name-rewrite-functions file))
802             out))
803     (mapconcat 'identity (nreverse out) "")))
804
805 (defun mm-remove-parts (handles)
806   "Remove the displayed MIME parts represented by HANDLES."
807   (if (and (listp handles)
808            (bufferp (car handles)))
809       (mm-remove-part handles)
810     (let (handle)
811       (while (setq handle (pop handles))
812         (cond
813          ((stringp handle)
814           (when (buffer-live-p (get-text-property 0 'buffer handle))
815             (kill-buffer (get-text-property 0 'buffer handle))))
816          ((and (listp handle)
817                (stringp (car handle)))
818           (mm-remove-parts (cdr handle)))
819          (t
820           (mm-remove-part handle)))))))
821
822 (defun mm-destroy-parts (handles)
823   "Remove the displayed MIME parts represented by HANDLES."
824   (if (and (listp handles)
825            (bufferp (car handles)))
826       (mm-destroy-part handles)
827     (let (handle)
828       (while (setq handle (pop handles))
829         (cond
830          ((stringp handle)
831           (when (buffer-live-p (get-text-property 0 'buffer handle))
832             (kill-buffer (get-text-property 0 'buffer handle))))
833          ((and (listp handle)
834                (stringp (car handle)))
835           (mm-destroy-parts handle))
836          (t
837           (mm-destroy-part handle)))))))
838
839 (defun mm-remove-part (handle)
840   "Remove the displayed MIME part represented by HANDLE."
841   (when (listp handle)
842     (let ((object (mm-handle-undisplayer handle)))
843       (ignore-errors
844         (cond
845          ;; Internally displayed part.
846          ((mm-annotationp object)
847           (delete-annotation object))
848          ((or (functionp object)
849               (and (listp object)
850                    (eq (car object) 'lambda)))
851           (funcall object))
852          ;; Externally displayed part.
853          ((consp object)
854           (condition-case ()
855               (while (get-buffer-process (cdr object))
856                 (interrupt-process (get-buffer-process (cdr object)))
857                 (message "Waiting for external displayer to die...")
858                 (sit-for 1))
859             (quit)
860             (error))
861           (ignore-errors (and (cdr object) (kill-buffer (cdr object))))
862           (message "Waiting for external displayer to die...done")
863           (ignore-errors (delete-file (car object)))
864           (ignore-errors (delete-directory (file-name-directory
865                                             (car object)))))
866          ((bufferp object)
867           (when (buffer-live-p object)
868             (kill-buffer object)))))
869       (mm-handle-set-undisplayer handle nil))))
870
871 (defun mm-display-inline (handle)
872   (let* ((type (mm-handle-media-type handle))
873          (function (cadr (mm-assoc-string-match mm-inline-media-tests type))))
874     (funcall function handle)
875     (goto-char (point-min))))
876
877 (defun mm-assoc-string-match (alist type)
878   (dolist (elem alist)
879     (when (string-match (car elem) type)
880       (return elem))))
881
882 (defun mm-automatic-display-p (handle)
883   "Say whether the user wants HANDLE to be displayed automatically."
884   (let ((methods mm-automatic-display)
885         (type (mm-handle-media-type handle))
886         method result)
887     (while (setq method (pop methods))
888       (when (and (not (mm-inline-override-p handle))
889                  (string-match method type))
890         (setq result t
891               methods nil)))
892     result))
893
894 (defun mm-inlinable-p (handle)
895   "Say whether HANDLE can be displayed inline."
896   (let ((alist mm-inline-media-tests)
897         (type (mm-handle-media-type handle))
898         test)
899     (while alist
900       (when (string-match (caar alist) type)
901         (setq test (caddar alist)
902               alist nil)
903         (setq test (funcall test handle)))
904       (pop alist))
905     test))
906
907 (defun mm-inlined-p (handle)
908   "Say whether the user wants HANDLE to be displayed inline."
909   (let ((methods mm-inlined-types)
910         (type (mm-handle-media-type handle))
911         method result)
912     (while (setq method (pop methods))
913       (when (and (not (mm-inline-override-p handle))
914                  (string-match method type))
915         (setq result t
916               methods nil)))
917     result))
918
919 (defun mm-attachment-override-p (handle)
920   "Say whether HANDLE should have attachment behavior overridden."
921   (let ((types mm-attachment-override-types)
922         (type (mm-handle-media-type handle))
923         ty)
924     (catch 'found
925       (while (setq ty (pop types))
926         (when (and (string-match ty type)
927                    (mm-inlinable-p handle))
928           (throw 'found t))))))
929
930 (defun mm-inline-override-p (handle)
931   "Say whether HANDLE should have inline behavior overridden."
932   (let ((types mm-inline-override-types)
933         (type (mm-handle-media-type handle))
934         ty)
935     (catch 'found
936       (while (setq ty (pop types))
937         (when (string-match ty type)
938           (throw 'found t))))))
939
940 (defun mm-automatic-external-display-p (type)
941   "Return the user-defined method for TYPE."
942   (let ((methods mm-automatic-external-display)
943         method result)
944     (while (setq method (pop methods))
945       (when (string-match method type)
946         (setq result t
947               methods nil)))
948     result))
949
950 (defun mm-destroy-part (handle)
951   "Destroy the data structures connected to HANDLE."
952   (when (listp handle)
953     (mm-remove-part handle)
954     (when (buffer-live-p (mm-handle-buffer handle))
955       (kill-buffer (mm-handle-buffer handle)))))
956
957 (defun mm-handle-displayed-p (handle)
958   "Say whether HANDLE is displayed or not."
959   (mm-handle-undisplayer handle))
960
961 ;;;
962 ;;; Functions for outputting parts
963 ;;;
964
965 (defun mm-get-part (handle)
966   "Return the contents of HANDLE as a string."
967   (mm-with-unibyte-buffer
968     (insert (with-current-buffer (mm-handle-buffer handle)
969               (mm-with-unibyte-current-buffer
970                 (buffer-string))))
971     (mm-decode-content-transfer-encoding
972      (mm-handle-encoding handle)
973      (mm-handle-media-type handle))
974     (buffer-string)))
975
976 (defun mm-insert-part (handle)
977   "Insert the contents of HANDLE in the current buffer."
978   (let ((cur (current-buffer)))
979     (save-excursion
980       (if (member (mm-handle-media-supertype handle) '("text" "message"))
981           (with-temp-buffer
982             (insert-buffer-substring (mm-handle-buffer handle))
983             (prog1
984                 (mm-decode-content-transfer-encoding
985                  (mm-handle-encoding handle)
986                  (mm-handle-media-type handle))
987               (let ((temp (current-buffer)))
988                 (set-buffer cur)
989                 (insert-buffer-substring temp))))
990         (mm-with-unibyte-buffer
991           (insert-buffer-substring (mm-handle-buffer handle))
992           (prog1
993               (mm-decode-content-transfer-encoding
994                (mm-handle-encoding handle)
995                (mm-handle-media-type handle))
996             (let ((temp (current-buffer)))
997               (set-buffer cur)
998               (insert-buffer-substring temp))))))))
999
1000 (defun mm-file-name-delete-whitespace (file-name)
1001   "Remove all whitespace characters from FILE-NAME."
1002   (while (string-match "\\s-+" file-name)
1003     (setq file-name (replace-match "" t t file-name)))
1004   file-name)
1005
1006 (defun mm-file-name-trim-whitespace (file-name)
1007   "Remove leading and trailing whitespace characters from FILE-NAME."
1008   (when (string-match "\\`\\s-+" file-name)
1009     (setq file-name (substring file-name (match-end 0))))
1010   (when (string-match "\\s-+\\'" file-name)
1011     (setq file-name (substring file-name 0 (match-beginning 0))))
1012   file-name)
1013
1014 (defun mm-file-name-collapse-whitespace (file-name)
1015   "Collapse multiple whitespace characters in FILE-NAME."
1016   (while (string-match "\\s-\\s-+" file-name)
1017     (setq file-name (replace-match " " t t file-name)))
1018   file-name)
1019
1020 (defun mm-file-name-replace-whitespace (file-name)
1021   "Replace whitespace characters in FILE-NAME with underscores.
1022 Set the option `mm-file-name-replace-whitespace' to any other
1023 string if you do not like underscores."
1024   (let ((s (or mm-file-name-replace-whitespace "_")))
1025     (while (string-match "\\s-" file-name)
1026       (setq file-name (replace-match s t t file-name))))
1027   file-name)
1028
1029 (defun mm-file-name-delete-control (filename)
1030   "Delete control characters from FILENAME."
1031   (gnus-replace-in-string filename "[\x00-\x1f\x7f]" ""))
1032
1033 (defun mm-file-name-delete-gotchas (filename)
1034   "Delete shell gotchas from FILENAME."
1035   (setq filename (gnus-replace-in-string filename "[<>|]" ""))
1036   (gnus-replace-in-string filename "^[.-]*" ""))
1037
1038 (defun mm-save-part (handle)
1039   "Write HANDLE to a file."
1040   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
1041          (filename (mail-content-type-get
1042                     (mm-handle-disposition handle) 'filename))
1043          file)
1044     (when filename
1045       (setq filename (gnus-map-function mm-file-name-rewrite-functions
1046                                         (file-name-nondirectory filename))))
1047     (setq file
1048           (read-file-name "Save MIME part to: "
1049                           (or mm-default-directory default-directory)
1050                           nil nil (or filename name "")))
1051     (setq mm-default-directory (file-name-directory file))
1052     (and (or (not (file-exists-p file))
1053              (yes-or-no-p (format "File %s already exists; overwrite? "
1054                                   file)))
1055          (progn
1056            (mm-save-part-to-file handle file)
1057            file))))
1058
1059 (defun mm-save-part-to-file (handle file)
1060   (mm-with-unibyte-buffer
1061     (mm-insert-part handle)
1062     (let ((coding-system-for-write 'binary)
1063           ;; Don't re-compress .gz & al.  Arguably we should make
1064           ;; `file-name-handler-alist' nil, but that would chop
1065           ;; ange-ftp, which is reasonable to use here.
1066           (inhibit-file-name-operation 'write-region)
1067           (inhibit-file-name-handlers
1068            (cons 'jka-compr-handler inhibit-file-name-handlers)))
1069       (write-region (point-min) (point-max) file))))
1070
1071 (defun mm-pipe-part (handle)
1072   "Pipe HANDLE to a process."
1073   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
1074          (command
1075           (read-string "Shell command on MIME part: " mm-last-shell-command)))
1076     (mm-with-unibyte-buffer
1077       (mm-insert-part handle)
1078       (let ((coding-system-for-write 'binary))
1079         (shell-command-on-region (point-min) (point-max) command nil)))))
1080
1081 (defun mm-interactively-view-part (handle)
1082   "Display HANDLE using METHOD."
1083   (let* ((type (mm-handle-media-type handle))
1084          (methods
1085           (mapcar (lambda (i) (list (cdr (assoc 'viewer i))))
1086                   (mailcap-mime-info type 'all)))
1087          (method (let ((minibuffer-local-completion-map
1088                         mm-viewer-completion-map))
1089                    (completing-read "Viewer: " methods))))
1090     (when (string= method "")
1091       (error "No method given"))
1092     (if (string-match "^[^% \t]+$" method)
1093         (setq method (concat method " %s")))
1094     (mm-display-external handle method)))
1095
1096 (defun mm-preferred-alternative (handles &optional preferred)
1097   "Say which of HANDLES are preferred."
1098   (let ((prec (if preferred (list preferred)
1099                 (mm-preferred-alternative-precedence handles)))
1100         p h result type handle)
1101     (while (setq p (pop prec))
1102       (setq h handles)
1103       (while h
1104         (setq handle (car h))
1105         (setq type (mm-handle-media-type handle))
1106         (when (and (equal p type)
1107                    (mm-automatic-display-p handle)
1108                    (or (stringp (car handle))
1109                        (not (mm-handle-disposition handle))
1110                        (equal (car (mm-handle-disposition handle))
1111                               "inline")))
1112           (setq result handle
1113                 h nil
1114                 prec nil))
1115         (pop h)))
1116     result))
1117
1118 (defun mm-preferred-alternative-precedence (handles)
1119   "Return the precedence based on HANDLES and `mm-discouraged-alternatives'."
1120   (let ((seq (nreverse (mapcar #'mm-handle-media-type
1121                                handles))))
1122     (dolist (disc (reverse mm-discouraged-alternatives))
1123       (dolist (elem (copy-sequence seq))
1124         (when (string-match disc elem)
1125           (setq seq (nconc (delete elem seq) (list elem))))))
1126     seq))
1127
1128 (defun mm-get-content-id (id)
1129   "Return the handle(s) referred to by ID."
1130   (cdr (assoc id mm-content-id-alist)))
1131
1132 (defconst mm-image-type-regexps
1133   '(("/\\*.*XPM.\\*/" . xpm)
1134     ("P[1-6]" . pbm)
1135     ("GIF8" . gif)
1136     ("\377\330" . jpeg)
1137     ("\211PNG\r\n" . png)
1138     ("#define" . xbm)
1139     ("\\(MM\0\\*\\)\\|\\(II\\*\0\\)" . tiff)
1140     ("%!PS" . postscript))
1141   "Alist of (REGEXP . IMAGE-TYPE) pairs used to auto-detect image types.
1142 When the first bytes of an image file match REGEXP, it is assumed to
1143 be of image type IMAGE-TYPE.")
1144
1145 ;; Steal from image.el. image-type-from-data suffers multi-line matching bug.
1146 (defun mm-image-type-from-buffer ()
1147   "Determine the image type from data in the current buffer.
1148 Value is a symbol specifying the image type or nil if type cannot
1149 be determined."
1150   (let ((types mm-image-type-regexps)
1151         type)
1152     (goto-char (point-min))
1153     (while (and types (null type))
1154       (let ((regexp (car (car types)))
1155             (image-type (cdr (car types))))
1156         (when (looking-at regexp)
1157           (setq type image-type))
1158         (setq types (cdr types))))
1159     type))
1160
1161 (defun mm-get-image (handle)
1162   "Return an image instance based on HANDLE."
1163   (let ((type (mm-handle-media-subtype handle))
1164         spec)
1165     ;; Allow some common translations.
1166     (setq type
1167           (cond
1168            ((equal type "x-pixmap")
1169             "xpm")
1170            ((equal type "x-xbitmap")
1171             "xbm")
1172            ((equal type "x-portable-bitmap")
1173             "pbm")
1174            (t type)))
1175     (or (mm-handle-cache handle)
1176         (mm-with-unibyte-buffer
1177           (mm-insert-part handle)
1178           (prog1
1179               (setq spec
1180                     (ignore-errors
1181                       ;; Avoid testing `make-glyph' since W3 may define
1182                       ;; a bogus version of it.
1183                       (if (fboundp 'create-image)
1184                           (create-image (buffer-string)
1185                                         (or (mm-image-type-from-buffer)
1186                                             (intern type))
1187                                         'data-p)
1188                         (mm-create-image-xemacs type))))
1189             (mm-handle-set-cache handle spec))))))
1190
1191 (defun mm-create-image-xemacs (type)
1192   (cond
1193    ((equal type "xbm")
1194     ;; xbm images require special handling, since
1195     ;; the only way to create glyphs from these
1196     ;; (without a ton of work) is to write them
1197     ;; out to a file, and then create a file
1198     ;; specifier.
1199     (let ((file (mm-make-temp-file
1200                  (expand-file-name "emm.xbm"
1201                                    mm-tmp-directory))))
1202       (unwind-protect
1203           (progn
1204             (write-region (point-min) (point-max) file)
1205             (make-glyph (list (cons 'x file))))
1206         (ignore-errors
1207           (delete-file file)))))
1208    (t
1209     (make-glyph
1210      (vector
1211       (or (mm-image-type-from-buffer)
1212           (intern type))
1213       :data (buffer-string))))))
1214
1215 (defun mm-image-fit-p (handle)
1216   "Say whether the image in HANDLE will fit the current window."
1217   (let ((image (mm-get-image handle)))
1218     (if (fboundp 'glyph-width)
1219         ;; XEmacs' glyphs can actually tell us about their width, so
1220         ;; lets be nice and smart about them.
1221         (or mm-inline-large-images
1222             (and (< (glyph-width image) (window-pixel-width))
1223                  (< (glyph-height image) (window-pixel-height))))
1224       (let* ((size (image-size image))
1225              (w (car size))
1226              (h (cdr size)))
1227         (or mm-inline-large-images
1228             (and (< h (1- (window-height))) ; Don't include mode line.
1229                  (< w (window-width))))))))
1230
1231 (defun mm-valid-image-format-p (format)
1232   "Say whether FORMAT can be displayed natively by Emacs."
1233   (cond
1234    ;; Handle XEmacs
1235    ((fboundp 'valid-image-instantiator-format-p)
1236     (valid-image-instantiator-format-p format))
1237    ;; Handle Emacs 21
1238    ((fboundp 'image-type-available-p)
1239     (and (display-graphic-p)
1240          (image-type-available-p format)))
1241    ;; Nobody else can do images yet.
1242    (t
1243     nil)))
1244
1245 (defun mm-valid-and-fit-image-p (format handle)
1246   "Say whether FORMAT can be displayed natively and HANDLE fits the window."
1247   (and (mm-valid-image-format-p format)
1248        (mm-image-fit-p handle)))
1249
1250 (defun mm-find-part-by-type (handles type &optional notp recursive)
1251   "Search in HANDLES for part with TYPE.
1252 If NOTP, returns first non-matching part.
1253 If RECURSIVE, search recursively."
1254   (let (handle)
1255     (while handles
1256       (if (and recursive (stringp (caar handles)))
1257           (if (setq handle (mm-find-part-by-type (cdar handles) type
1258                                                  notp recursive))
1259               (setq handles nil))
1260         (if (if notp
1261                 (not (equal (mm-handle-media-type (car handles)) type))
1262               (equal (mm-handle-media-type (car handles)) type))
1263             (setq handle (car handles)
1264                   handles nil)))
1265       (setq handles (cdr handles)))
1266     handle))
1267
1268 (defun mm-find-raw-part-by-type (ctl type &optional notp)
1269   (goto-char (point-min))
1270   (let* ((boundary (concat "--" (mm-handle-multipart-ctl-parameter ctl
1271                                                                    'boundary)))
1272          (close-delimiter (concat "^" (regexp-quote boundary) "--[ \t]*$"))
1273          start
1274          (end (save-excursion
1275                 (goto-char (point-max))
1276                 (if (re-search-backward close-delimiter nil t)
1277                     (match-beginning 0)
1278                   (point-max))))
1279          result)
1280     (setq boundary (concat "^" (regexp-quote boundary) "[ \t]*$"))
1281     (while (and (not result)
1282                 (re-search-forward boundary end t))
1283       (goto-char (match-beginning 0))
1284       (when start
1285         (save-excursion
1286           (save-restriction
1287             (narrow-to-region start (1- (point)))
1288             (when (let ((ctl (ignore-errors
1289                                (mail-header-parse-content-type
1290                                 (mail-fetch-field "content-type")))))
1291                     (if notp
1292                         (not (equal (car ctl) type))
1293                       (equal (car ctl) type)))
1294               (setq result (buffer-string))))))
1295       (forward-line 1)
1296       (setq start (point)))
1297     (when (and (not result) start)
1298       (save-excursion
1299         (save-restriction
1300           (narrow-to-region start end)
1301           (when (let ((ctl (ignore-errors
1302                              (mail-header-parse-content-type
1303                               (mail-fetch-field "content-type")))))
1304                   (if notp
1305                       (not (equal (car ctl) type))
1306                     (equal (car ctl) type)))
1307             (setq result (buffer-string))))))
1308     result))
1309
1310 (defvar mm-security-handle nil)
1311
1312 (defsubst mm-set-handle-multipart-parameter (handle parameter value)
1313   ;; HANDLE could be a CTL.
1314   (when handle
1315     (put-text-property 0 (length (car handle)) parameter value
1316                        (car handle))))
1317
1318 (defun mm-possibly-verify-or-decrypt (parts ctl)
1319   (let ((type (car ctl))
1320         (subtype (cadr (split-string (car ctl) "/")))
1321         (mm-security-handle ctl) ;; (car CTL) is the type.
1322         protocol func functest)
1323     (cond
1324      ((or (equal type "application/x-pkcs7-mime")
1325           (equal type "application/pkcs7-mime"))
1326       (with-temp-buffer
1327         (when (and (cond
1328                     ((eq mm-decrypt-option 'never) nil)
1329                     ((eq mm-decrypt-option 'always) t)
1330                     ((eq mm-decrypt-option 'known) t)
1331                     (t (y-or-n-p
1332                         (format "Decrypt (S/MIME) part? "))))
1333                    (mm-view-pkcs7 parts))
1334           (setq parts (mm-dissect-buffer t)))))
1335      ((equal subtype "signed")
1336       (unless (and (setq protocol
1337                          (mm-handle-multipart-ctl-parameter ctl 'protocol))
1338                    (not (equal protocol "multipart/mixed")))
1339         ;; The message is broken or draft-ietf-openpgp-multsig-01.
1340         (let ((protocols mm-verify-function-alist))
1341           (while protocols
1342             (if (and (or (not (setq functest (nth 3 (car protocols))))
1343                          (funcall functest parts ctl))
1344                      (mm-find-part-by-type parts (caar protocols) nil t))
1345                 (setq protocol (caar protocols)
1346                       protocols nil)
1347               (setq protocols (cdr protocols))))))
1348       (setq func (nth 1 (assoc protocol mm-verify-function-alist)))
1349       (when (cond
1350              ((eq mm-verify-option 'never) nil)
1351              ((eq mm-verify-option 'always) t)
1352              ((eq mm-verify-option 'known)
1353               (and func
1354                    (or (not (setq functest
1355                                   (nth 3 (assoc protocol
1356                                                 mm-verify-function-alist))))
1357                        (funcall functest parts ctl))))
1358              (t
1359               (y-or-n-p
1360                (format "Verify signed (%s) part? "
1361                        (or (nth 2 (assoc protocol mm-verify-function-alist))
1362                            (format "protocol=%s" protocol))))))
1363         (save-excursion
1364           (if func
1365               (funcall func parts ctl)
1366             (mm-set-handle-multipart-parameter
1367              mm-security-handle 'gnus-details
1368              (format "Unknown sign protocol (%s)" protocol))))))
1369      ((equal subtype "encrypted")
1370       (unless (setq protocol
1371                     (mm-handle-multipart-ctl-parameter ctl 'protocol))
1372         ;; The message is broken.
1373         (let ((parts parts))
1374           (while parts
1375             (if (assoc (mm-handle-media-type (car parts))
1376                        mm-decrypt-function-alist)
1377                 (setq protocol (mm-handle-media-type (car parts))
1378                       parts nil)
1379               (setq parts (cdr parts))))))
1380       (setq func (nth 1 (assoc protocol mm-decrypt-function-alist)))
1381       (when (cond
1382              ((eq mm-decrypt-option 'never) nil)
1383              ((eq mm-decrypt-option 'always) t)
1384              ((eq mm-decrypt-option 'known)
1385               (and func
1386                    (or (not (setq functest
1387                                   (nth 3 (assoc protocol
1388                                                 mm-decrypt-function-alist))))
1389                        (funcall functest parts ctl))))
1390              (t
1391               (y-or-n-p
1392                (format "Decrypt (%s) part? "
1393                        (or (nth 2 (assoc protocol mm-decrypt-function-alist))
1394                            (format "protocol=%s" protocol))))))
1395         (save-excursion
1396           (if func
1397               (setq parts (funcall func parts ctl))
1398             (mm-set-handle-multipart-parameter
1399              mm-security-handle 'gnus-details
1400              (format "Unknown encrypt protocol (%s)" protocol))))))
1401      (t nil))
1402     parts))
1403
1404 (defun mm-multiple-handles (handles)
1405   (and (listp (car handles))
1406        (> (length handles) 1)))
1407
1408 (defun mm-merge-handles (handles1 handles2)
1409   (append
1410    (if (listp (car handles1))
1411        handles1
1412      (list handles1))
1413    (if (listp (car handles2))
1414        handles2
1415      (list handles2))))
1416
1417 (defun mm-readable-p (handle)
1418   "Say whether the content of HANDLE is readable."
1419   (and (< (with-current-buffer (mm-handle-buffer handle)
1420             (buffer-size)) 10000)
1421        (mm-with-unibyte-buffer
1422          (mm-insert-part handle)
1423          (and (eq (mm-body-7-or-8) '7bit)
1424               (not (mm-long-lines-p 76))))))
1425
1426 (provide 'mm-decode)
1427
1428 ;;; mm-decode.el ends here