(mm-file-name-delete-control)
[gnus] / 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 '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 ((obuf (current-buffer))
598           beg)
599       (goto-char (point-min))
600       (search-forward-regexp "^\n" nil t)
601       (setq beg (point))
602       (set-buffer (generate-new-buffer " *mm*"))
603       (insert-buffer-substring obuf beg)
604       (current-buffer))))
605
606 (defun mm-display-parts (handle &optional no-default)
607   (if (stringp (car handle))
608       (mapcar 'mm-display-parts (cdr handle))
609     (if (bufferp (car handle))
610         (save-restriction
611           (narrow-to-region (point) (point))
612           (mm-display-part handle)
613           (goto-char (point-max)))
614       (mapcar 'mm-display-parts handle))))
615
616 (defun mm-display-part (handle &optional no-default)
617   "Display the MIME part represented by HANDLE.
618 Returns nil if the part is removed; inline if displayed inline;
619 external if displayed external."
620   (save-excursion
621     (mailcap-parse-mailcaps)
622     (if (mm-handle-displayed-p handle)
623         (mm-remove-part handle)
624       (let* ((type (mm-handle-media-type handle))
625              (method (mailcap-mime-info type)))
626         (if (and (mm-inlinable-p handle)
627                  (mm-inlined-p handle))
628             (progn
629               (forward-line 1)
630               (mm-display-inline handle)
631               'inline)
632           (when (or method
633                     (not no-default))
634             (if (and (not method)
635                      (equal "text" (car (split-string type))))
636                 (progn
637                   (forward-line 1)
638                   (mm-insert-inline handle (mm-get-part handle))
639                   'inline)
640               (mm-display-external
641                handle (or method 'mailcap-save-binary-file)))))))))
642
643 (defun mm-display-external (handle method)
644   "Display HANDLE using METHOD."
645   (let ((outbuf (current-buffer)))
646     (mm-with-unibyte-buffer
647       (if (functionp method)
648           (let ((cur (current-buffer)))
649             (if (eq method 'mailcap-save-binary-file)
650                 (progn
651                   (set-buffer (generate-new-buffer " *mm*"))
652                   (setq method nil))
653               (mm-insert-part handle)
654               (let ((win (get-buffer-window cur t)))
655                 (when win
656                   (select-window win)))
657               (switch-to-buffer (generate-new-buffer " *mm*")))
658             (buffer-disable-undo)
659             (mm-set-buffer-file-coding-system mm-binary-coding-system)
660             (insert-buffer-substring cur)
661             (goto-char (point-min))
662             (message "Viewing with %s" method)
663             (let ((mm (current-buffer))
664                   (non-viewer (assq 'non-viewer
665                                     (mailcap-mime-info
666                                      (mm-handle-media-type handle) t))))
667               (unwind-protect
668                   (if method
669                       (funcall method)
670                     (mm-save-part handle))
671                 (when (and (not non-viewer)
672                            method)
673                   (mm-handle-set-undisplayer handle mm)))))
674         ;; The function is a string to be executed.
675         (mm-insert-part handle)
676         (let* ((dir (mm-make-temp-file
677                      (expand-file-name "emm." mm-tmp-directory) 'dir))
678                (filename (or
679                           (mail-content-type-get
680                            (mm-handle-disposition handle) 'filename)
681                           (mail-content-type-get
682                            (mm-handle-type handle) 'name)))
683                (mime-info (mailcap-mime-info
684                            (mm-handle-media-type handle) t))
685                (needsterm (or (assoc "needsterm" mime-info)
686                               (assoc "needsterminal" mime-info)))
687                (copiousoutput (assoc "copiousoutput" mime-info))
688                file buffer)
689           ;; We create a private sub-directory where we store our files.
690           (set-file-modes dir 448)
691           (if filename
692               (setq file (expand-file-name
693                           (gnus-map-function mm-file-name-rewrite-functions
694                                              (file-name-nondirectory filename))
695                           dir))
696             (setq file (mm-make-temp-file (expand-file-name "mm." dir))))
697           (let ((coding-system-for-write mm-binary-coding-system))
698             (write-region (point-min) (point-max) file nil 'nomesg))
699           (message "Viewing with %s" method)
700           (cond
701            (needsterm
702             (let ((command (mm-mailcap-command
703                             method file (mm-handle-type handle))))
704               (unwind-protect
705                   (if window-system
706                       (start-process "*display*" nil
707                                      mm-external-terminal-program
708                                      "-e" shell-file-name
709                                      shell-command-switch command)
710                     (require 'term)
711                     (require 'gnus-win)
712                     (set-buffer
713                      (setq buffer
714                            (make-term "display"
715                                       shell-file-name
716                                       nil
717                                       shell-command-switch command)))
718                     (term-mode)
719                     (term-char-mode)
720                     (set-process-sentinel
721                      (get-buffer-process buffer)
722                      `(lambda (process state)
723                         (if (eq 'exit (process-status process))
724                             (gnus-configure-windows
725                              ',gnus-current-window-configuration))))
726                     (gnus-configure-windows 'display-term))
727                 (mm-handle-set-external-undisplayer handle (cons file buffer)))
728               (message "Displaying %s..." command))
729             'external)
730            (copiousoutput
731             (with-current-buffer outbuf
732               (forward-line 1)
733               (mm-insert-inline
734                handle
735                (unwind-protect
736                    (progn
737                      (call-process shell-file-name nil
738                                    (setq buffer
739                                          (generate-new-buffer " *mm*"))
740                                    nil
741                                    shell-command-switch
742                                    (mm-mailcap-command
743                                     method file (mm-handle-type handle)))
744                      (if (buffer-live-p buffer)
745                          (save-excursion
746                            (set-buffer buffer)
747                            (buffer-string))))
748                  (progn
749                    (ignore-errors (delete-file file))
750                    (ignore-errors (delete-directory
751                                    (file-name-directory file)))
752                    (ignore-errors (kill-buffer buffer))))))
753             'inline)
754            (t
755             (let ((command (mm-mailcap-command
756                             method file (mm-handle-type handle))))
757               (unwind-protect
758                   (start-process "*display*"
759                                  (setq buffer
760                                        (generate-new-buffer " *mm*"))
761                                  shell-file-name
762                                  shell-command-switch command)
763                 (mm-handle-set-external-undisplayer
764                  handle (cons file buffer)))
765               (message "Displaying %s..." command))
766             'external)))))))
767
768 (defun mm-mailcap-command (method file type-list)
769   (let ((ctl (cdr type-list))
770         (beg 0)
771         (uses-stdin t)
772         out sub total)
773     (while (string-match "%{\\([^}]+\\)}\\|'%s'\\|\"%s\"\\|%s\\|%t\\|%%"
774                          method beg)
775       (push (substring method beg (match-beginning 0)) out)
776       (setq beg (match-end 0)
777             total (match-string 0 method)
778             sub (match-string 1 method))
779       (cond
780        ((string= total "%%")
781         (push "%" out))
782        ((or (string= total "%s")
783             ;; We do our own quoting.
784             (string= total "'%s'")
785             (string= total "\"%s\""))
786         (setq uses-stdin nil)
787         (push (mm-quote-arg
788                (gnus-map-function mm-path-name-rewrite-functions file)) out))
789        ((string= total "%t")
790         (push (mm-quote-arg (car type-list)) out))
791        (t
792         (push (mm-quote-arg (or (cdr (assq (intern sub) ctl)) "")) out))))
793     (push (substring method beg (length method)) out)
794     (when uses-stdin
795       (push "<" out)
796       (push (mm-quote-arg
797              (gnus-map-function mm-path-name-rewrite-functions file))
798             out))
799     (mapconcat 'identity (nreverse out) "")))
800
801 (defun mm-remove-parts (handles)
802   "Remove the displayed MIME parts represented by HANDLES."
803   (if (and (listp handles)
804            (bufferp (car handles)))
805       (mm-remove-part handles)
806     (let (handle)
807       (while (setq handle (pop handles))
808         (cond
809          ((stringp handle)
810           (when (buffer-live-p (get-text-property 0 'buffer handle))
811             (kill-buffer (get-text-property 0 'buffer handle))))
812          ((and (listp handle)
813                (stringp (car handle)))
814           (mm-remove-parts (cdr handle)))
815          (t
816           (mm-remove-part handle)))))))
817
818 (defun mm-destroy-parts (handles)
819   "Remove the displayed MIME parts represented by HANDLES."
820   (if (and (listp handles)
821            (bufferp (car handles)))
822       (mm-destroy-part handles)
823     (let (handle)
824       (while (setq handle (pop handles))
825         (cond
826          ((stringp handle)
827           (when (buffer-live-p (get-text-property 0 'buffer handle))
828             (kill-buffer (get-text-property 0 'buffer handle))))
829          ((and (listp handle)
830                (stringp (car handle)))
831           (mm-destroy-parts handle))
832          (t
833           (mm-destroy-part handle)))))))
834
835 (defun mm-remove-part (handle)
836   "Remove the displayed MIME part represented by HANDLE."
837   (when (listp handle)
838     (let ((object (mm-handle-undisplayer handle)))
839       (ignore-errors
840         (cond
841          ;; Internally displayed part.
842          ((mm-annotationp object)
843           (delete-annotation object))
844          ((or (functionp object)
845               (and (listp object)
846                    (eq (car object) 'lambda)))
847           (funcall object))
848          ;; Externally displayed part.
849          ((consp object)
850           (condition-case ()
851               (while (get-buffer-process (cdr object))
852                 (interrupt-process (get-buffer-process (cdr object)))
853                 (message "Waiting for external displayer to die...")
854                 (sit-for 1))
855             (quit)
856             (error))
857           (ignore-errors (and (cdr object) (kill-buffer (cdr object))))
858           (message "Waiting for external displayer to die...done")
859           (ignore-errors (delete-file (car object)))
860           (ignore-errors (delete-directory (file-name-directory
861                                             (car object)))))
862          ((bufferp object)
863           (when (buffer-live-p object)
864             (kill-buffer object)))))
865       (mm-handle-set-undisplayer handle nil))))
866
867 (defun mm-display-inline (handle)
868   (let* ((type (mm-handle-media-type handle))
869          (function (cadr (mm-assoc-string-match mm-inline-media-tests type))))
870     (funcall function handle)
871     (goto-char (point-min))))
872
873 (defun mm-assoc-string-match (alist type)
874   (dolist (elem alist)
875     (when (string-match (car elem) type)
876       (return elem))))
877
878 (defun mm-automatic-display-p (handle)
879   "Say whether the user wants HANDLE to be displayed automatically."
880   (let ((methods mm-automatic-display)
881         (type (mm-handle-media-type handle))
882         method result)
883     (while (setq method (pop methods))
884       (when (and (not (mm-inline-override-p handle))
885                  (string-match method type))
886         (setq result t
887               methods nil)))
888     result))
889
890 (defun mm-inlinable-p (handle)
891   "Say whether HANDLE can be displayed inline."
892   (let ((alist mm-inline-media-tests)
893         (type (mm-handle-media-type handle))
894         test)
895     (while alist
896       (when (string-match (caar alist) type)
897         (setq test (caddar alist)
898               alist nil)
899         (setq test (funcall test handle)))
900       (pop alist))
901     test))
902
903 (defun mm-inlined-p (handle)
904   "Say whether the user wants HANDLE to be displayed inline."
905   (let ((methods mm-inlined-types)
906         (type (mm-handle-media-type handle))
907         method result)
908     (while (setq method (pop methods))
909       (when (and (not (mm-inline-override-p handle))
910                  (string-match method type))
911         (setq result t
912               methods nil)))
913     result))
914
915 (defun mm-attachment-override-p (handle)
916   "Say whether HANDLE should have attachment behavior overridden."
917   (let ((types mm-attachment-override-types)
918         (type (mm-handle-media-type handle))
919         ty)
920     (catch 'found
921       (while (setq ty (pop types))
922         (when (and (string-match ty type)
923                    (mm-inlinable-p handle))
924           (throw 'found t))))))
925
926 (defun mm-inline-override-p (handle)
927   "Say whether HANDLE should have inline behavior overridden."
928   (let ((types mm-inline-override-types)
929         (type (mm-handle-media-type handle))
930         ty)
931     (catch 'found
932       (while (setq ty (pop types))
933         (when (string-match ty type)
934           (throw 'found t))))))
935
936 (defun mm-automatic-external-display-p (type)
937   "Return the user-defined method for TYPE."
938   (let ((methods mm-automatic-external-display)
939         method result)
940     (while (setq method (pop methods))
941       (when (string-match method type)
942         (setq result t
943               methods nil)))
944     result))
945
946 (defun mm-destroy-part (handle)
947   "Destroy the data structures connected to HANDLE."
948   (when (listp handle)
949     (mm-remove-part handle)
950     (when (buffer-live-p (mm-handle-buffer handle))
951       (kill-buffer (mm-handle-buffer handle)))))
952
953 (defun mm-handle-displayed-p (handle)
954   "Say whether HANDLE is displayed or not."
955   (mm-handle-undisplayer handle))
956
957 ;;;
958 ;;; Functions for outputting parts
959 ;;;
960
961 (defun mm-get-part (handle)
962   "Return the contents of HANDLE as a string."
963   (mm-with-unibyte-buffer
964     (insert (with-current-buffer (mm-handle-buffer handle)
965               (mm-with-unibyte-current-buffer
966                 (buffer-string))))
967     (mm-decode-content-transfer-encoding
968      (mm-handle-encoding handle)
969      (mm-handle-media-type handle))
970     (buffer-string)))
971
972 (defun mm-insert-part (handle)
973   "Insert the contents of HANDLE in the current buffer."
974   (let ((cur (current-buffer)))
975     (save-excursion
976       (if (member (mm-handle-media-supertype handle) '("text" "message"))
977           (with-temp-buffer
978             (insert-buffer-substring (mm-handle-buffer handle))
979             (prog1
980                 (mm-decode-content-transfer-encoding
981                  (mm-handle-encoding handle)
982                  (mm-handle-media-type handle))
983               (let ((temp (current-buffer)))
984                 (set-buffer cur)
985                 (insert-buffer-substring temp))))
986         (mm-with-unibyte-buffer
987           (insert-buffer-substring (mm-handle-buffer handle))
988           (prog1
989               (mm-decode-content-transfer-encoding
990                (mm-handle-encoding handle)
991                (mm-handle-media-type handle))
992             (let ((temp (current-buffer)))
993               (set-buffer cur)
994               (insert-buffer-substring temp))))))))
995
996 (defun mm-file-name-delete-whitespace (file-name)
997   "Remove all whitespace characters from FILE-NAME."
998   (while (string-match "\\s-+" file-name)
999     (setq file-name (replace-match "" t t file-name)))
1000   file-name)
1001
1002 (defun mm-file-name-trim-whitespace (file-name)
1003   "Remove leading and trailing whitespace characters from FILE-NAME."
1004   (when (string-match "\\`\\s-+" file-name)
1005     (setq file-name (substring file-name (match-end 0))))
1006   (when (string-match "\\s-+\\'" file-name)
1007     (setq file-name (substring file-name 0 (match-beginning 0))))
1008   file-name)
1009
1010 (defun mm-file-name-collapse-whitespace (file-name)
1011   "Collapse multiple whitespace characters in FILE-NAME."
1012   (while (string-match "\\s-\\s-+" file-name)
1013     (setq file-name (replace-match " " t t file-name)))
1014   file-name)
1015
1016 (defun mm-file-name-replace-whitespace (file-name)
1017   "Replace whitespace characters in FILE-NAME with underscores.
1018 Set the option `mm-file-name-replace-whitespace' to any other
1019 string if you do not like underscores."
1020   (let ((s (or mm-file-name-replace-whitespace "_")))
1021     (while (string-match "\\s-" file-name)
1022       (setq file-name (replace-match s t t file-name))))
1023   file-name)
1024
1025 (defun mm-file-name-delete-control (filename)
1026   "Delete control characters from FILENAME."
1027   (gnus-replace-in-string filename "[\x00-\x1f\x7f]" ""))
1028
1029 (defun mm-file-name-delete-gotchas (filename)
1030   "Delete shell gotchas from FILENAME."
1031   (setq filename (gnus-replace-in-string filename "[<>|]" ""))
1032   (gnus-replace-in-string filename "^[.-]*" ""))
1033
1034 (defun mm-save-part (handle)
1035   "Write HANDLE to a file."
1036   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
1037          (filename (mail-content-type-get
1038                     (mm-handle-disposition handle) 'filename))
1039          file)
1040     (when filename
1041       (setq filename (gnus-map-function mm-file-name-rewrite-functions
1042                                         (file-name-nondirectory filename))))
1043     (setq file
1044           (read-file-name "Save MIME part to: "
1045                           (or mm-default-directory default-directory)
1046                           nil nil (or filename name "")))
1047     (setq mm-default-directory (file-name-directory file))
1048     (and (or (not (file-exists-p file))
1049              (yes-or-no-p (format "File %s already exists; overwrite? "
1050                                   file)))
1051          (progn
1052            (mm-save-part-to-file handle file)
1053            file))))
1054
1055 (defun mm-save-part-to-file (handle file)
1056   (mm-with-unibyte-buffer
1057     (mm-insert-part handle)
1058     (let ((coding-system-for-write 'binary)
1059           ;; Don't re-compress .gz & al.  Arguably we should make
1060           ;; `file-name-handler-alist' nil, but that would chop
1061           ;; ange-ftp, which is reasonable to use here.
1062           (inhibit-file-name-operation 'write-region)
1063           (inhibit-file-name-handlers
1064            (cons 'jka-compr-handler inhibit-file-name-handlers)))
1065       (write-region (point-min) (point-max) file))))
1066
1067 (defun mm-pipe-part (handle)
1068   "Pipe HANDLE to a process."
1069   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
1070          (command
1071           (read-string "Shell command on MIME part: " mm-last-shell-command)))
1072     (mm-with-unibyte-buffer
1073       (mm-insert-part handle)
1074       (let ((coding-system-for-write 'binary))
1075         (shell-command-on-region (point-min) (point-max) command nil)))))
1076
1077 (defun mm-interactively-view-part (handle)
1078   "Display HANDLE using METHOD."
1079   (let* ((type (mm-handle-media-type handle))
1080          (methods
1081           (mapcar (lambda (i) (list (cdr (assoc 'viewer i))))
1082                   (mailcap-mime-info type 'all)))
1083          (method (let ((minibuffer-local-completion-map
1084                         mm-viewer-completion-map))
1085                    (completing-read "Viewer: " methods))))
1086     (when (string= method "")
1087       (error "No method given"))
1088     (if (string-match "^[^% \t]+$" method)
1089         (setq method (concat method " %s")))
1090     (mm-display-external handle method)))
1091
1092 (defun mm-preferred-alternative (handles &optional preferred)
1093   "Say which of HANDLES are preferred."
1094   (let ((prec (if preferred (list preferred)
1095                 (mm-preferred-alternative-precedence handles)))
1096         p h result type handle)
1097     (while (setq p (pop prec))
1098       (setq h handles)
1099       (while h
1100         (setq handle (car h))
1101         (setq type (mm-handle-media-type handle))
1102         (when (and (equal p type)
1103                    (mm-automatic-display-p handle)
1104                    (or (stringp (car handle))
1105                        (not (mm-handle-disposition handle))
1106                        (equal (car (mm-handle-disposition handle))
1107                               "inline")))
1108           (setq result handle
1109                 h nil
1110                 prec nil))
1111         (pop h)))
1112     result))
1113
1114 (defun mm-preferred-alternative-precedence (handles)
1115   "Return the precedence based on HANDLES and `mm-discouraged-alternatives'."
1116   (let ((seq (nreverse (mapcar #'mm-handle-media-type
1117                                handles))))
1118     (dolist (disc (reverse mm-discouraged-alternatives))
1119       (dolist (elem (copy-sequence seq))
1120         (when (string-match disc elem)
1121           (setq seq (nconc (delete elem seq) (list elem))))))
1122     seq))
1123
1124 (defun mm-get-content-id (id)
1125   "Return the handle(s) referred to by ID."
1126   (cdr (assoc id mm-content-id-alist)))
1127
1128 (defconst mm-image-type-regexps
1129   '(("/\\*.*XPM.\\*/" . xpm)
1130     ("P[1-6]" . pbm)
1131     ("GIF8" . gif)
1132     ("\377\330" . jpeg)
1133     ("\211PNG\r\n" . png)
1134     ("#define" . xbm)
1135     ("\\(MM\0\\*\\)\\|\\(II\\*\0\\)" . tiff)
1136     ("%!PS" . postscript))
1137   "Alist of (REGEXP . IMAGE-TYPE) pairs used to auto-detect image types.
1138 When the first bytes of an image file match REGEXP, it is assumed to
1139 be of image type IMAGE-TYPE.")
1140
1141 ;; Steal from image.el. image-type-from-data suffers multi-line matching bug.
1142 (defun mm-image-type-from-buffer ()
1143   "Determine the image type from data in the current buffer.
1144 Value is a symbol specifying the image type or nil if type cannot
1145 be determined."
1146   (let ((types mm-image-type-regexps)
1147         type)
1148     (goto-char (point-min))
1149     (while (and types (null type))
1150       (let ((regexp (car (car types)))
1151             (image-type (cdr (car types))))
1152         (when (looking-at regexp)
1153           (setq type image-type))
1154         (setq types (cdr types))))
1155     type))
1156
1157 (defun mm-get-image (handle)
1158   "Return an image instance based on HANDLE."
1159   (let ((type (mm-handle-media-subtype handle))
1160         spec)
1161     ;; Allow some common translations.
1162     (setq type
1163           (cond
1164            ((equal type "x-pixmap")
1165             "xpm")
1166            ((equal type "x-xbitmap")
1167             "xbm")
1168            ((equal type "x-portable-bitmap")
1169             "pbm")
1170            (t type)))
1171     (or (mm-handle-cache handle)
1172         (mm-with-unibyte-buffer
1173           (mm-insert-part handle)
1174           (prog1
1175               (setq spec
1176                     (ignore-errors
1177                       ;; Avoid testing `make-glyph' since W3 may define
1178                       ;; a bogus version of it.
1179                       (if (fboundp 'create-image)
1180                           (create-image (buffer-string)
1181                                         (or (mm-image-type-from-buffer)
1182                                             (intern type))
1183                                         'data-p)
1184                         (mm-create-image-xemacs type))))
1185             (mm-handle-set-cache handle spec))))))
1186
1187 (defun mm-create-image-xemacs (type)
1188   (cond
1189    ((equal type "xbm")
1190     ;; xbm images require special handling, since
1191     ;; the only way to create glyphs from these
1192     ;; (without a ton of work) is to write them
1193     ;; out to a file, and then create a file
1194     ;; specifier.
1195     (let ((file (mm-make-temp-file
1196                  (expand-file-name "emm.xbm"
1197                                    mm-tmp-directory))))
1198       (unwind-protect
1199           (progn
1200             (write-region (point-min) (point-max) file)
1201             (make-glyph (list (cons 'x file))))
1202         (ignore-errors
1203           (delete-file file)))))
1204    (t
1205     (make-glyph
1206      (vector
1207       (or (mm-image-type-from-buffer)
1208           (intern type))
1209       :data (buffer-string))))))
1210
1211 (defun mm-image-fit-p (handle)
1212   "Say whether the image in HANDLE will fit the current window."
1213   (let ((image (mm-get-image handle)))
1214     (if (fboundp 'glyph-width)
1215         ;; XEmacs' glyphs can actually tell us about their width, so
1216         ;; lets be nice and smart about them.
1217         (or mm-inline-large-images
1218             (and (< (glyph-width image) (window-pixel-width))
1219                  (< (glyph-height image) (window-pixel-height))))
1220       (let* ((size (image-size image))
1221              (w (car size))
1222              (h (cdr size)))
1223         (or mm-inline-large-images
1224             (and (< h (1- (window-height))) ; Don't include mode line.
1225                  (< w (window-width))))))))
1226
1227 (defun mm-valid-image-format-p (format)
1228   "Say whether FORMAT can be displayed natively by Emacs."
1229   (cond
1230    ;; Handle XEmacs
1231    ((fboundp 'valid-image-instantiator-format-p)
1232     (valid-image-instantiator-format-p format))
1233    ;; Handle Emacs 21
1234    ((fboundp 'image-type-available-p)
1235     (and (display-graphic-p)
1236          (image-type-available-p format)))
1237    ;; Nobody else can do images yet.
1238    (t
1239     nil)))
1240
1241 (defun mm-valid-and-fit-image-p (format handle)
1242   "Say whether FORMAT can be displayed natively and HANDLE fits the window."
1243   (and (mm-valid-image-format-p format)
1244        (mm-image-fit-p handle)))
1245
1246 (defun mm-find-part-by-type (handles type &optional notp recursive)
1247   "Search in HANDLES for part with TYPE.
1248 If NOTP, returns first non-matching part.
1249 If RECURSIVE, search recursively."
1250   (let (handle)
1251     (while handles
1252       (if (and recursive (stringp (caar handles)))
1253           (if (setq handle (mm-find-part-by-type (cdar handles) type
1254                                                  notp recursive))
1255               (setq handles nil))
1256         (if (if notp
1257                 (not (equal (mm-handle-media-type (car handles)) type))
1258               (equal (mm-handle-media-type (car handles)) type))
1259             (setq handle (car handles)
1260                   handles nil)))
1261       (setq handles (cdr handles)))
1262     handle))
1263
1264 (defun mm-find-raw-part-by-type (ctl type &optional notp)
1265   (goto-char (point-min))
1266   (let* ((boundary (concat "--" (mm-handle-multipart-ctl-parameter ctl
1267                                                                    'boundary)))
1268          (close-delimiter (concat "^" (regexp-quote boundary) "--[ \t]*$"))
1269          start
1270          (end (save-excursion
1271                 (goto-char (point-max))
1272                 (if (re-search-backward close-delimiter nil t)
1273                     (match-beginning 0)
1274                   (point-max))))
1275          result)
1276     (setq boundary (concat "^" (regexp-quote boundary) "[ \t]*$"))
1277     (while (and (not result)
1278                 (re-search-forward boundary end t))
1279       (goto-char (match-beginning 0))
1280       (when start
1281         (save-excursion
1282           (save-restriction
1283             (narrow-to-region start (1- (point)))
1284             (when (let ((ctl (ignore-errors
1285                                (mail-header-parse-content-type
1286                                 (mail-fetch-field "content-type")))))
1287                     (if notp
1288                         (not (equal (car ctl) type))
1289                       (equal (car ctl) type)))
1290               (setq result (buffer-string))))))
1291       (forward-line 1)
1292       (setq start (point)))
1293     (when (and (not result) start)
1294       (save-excursion
1295         (save-restriction
1296           (narrow-to-region start end)
1297           (when (let ((ctl (ignore-errors
1298                              (mail-header-parse-content-type
1299                               (mail-fetch-field "content-type")))))
1300                   (if notp
1301                       (not (equal (car ctl) type))
1302                     (equal (car ctl) type)))
1303             (setq result (buffer-string))))))
1304     result))
1305
1306 (defvar mm-security-handle nil)
1307
1308 (defsubst mm-set-handle-multipart-parameter (handle parameter value)
1309   ;; HANDLE could be a CTL.
1310   (when handle
1311     (put-text-property 0 (length (car handle)) parameter value
1312                        (car handle))))
1313
1314 (defun mm-possibly-verify-or-decrypt (parts ctl)
1315   (let ((type (car ctl))
1316         (subtype (cadr (split-string (car ctl) "/")))
1317         (mm-security-handle ctl) ;; (car CTL) is the type.
1318         protocol func functest)
1319     (cond
1320      ((or (equal type "application/x-pkcs7-mime")
1321           (equal type "application/pkcs7-mime"))
1322       (with-temp-buffer
1323         (when (and (cond
1324                     ((eq mm-decrypt-option 'never) nil)
1325                     ((eq mm-decrypt-option 'always) t)
1326                     ((eq mm-decrypt-option 'known) t)
1327                     (t (y-or-n-p
1328                         (format "Decrypt (S/MIME) part? "))))
1329                    (mm-view-pkcs7 parts))
1330           (setq parts (mm-dissect-buffer t)))))
1331      ((equal subtype "signed")
1332       (unless (and (setq protocol
1333                          (mm-handle-multipart-ctl-parameter ctl 'protocol))
1334                    (not (equal protocol "multipart/mixed")))
1335         ;; The message is broken or draft-ietf-openpgp-multsig-01.
1336         (let ((protocols mm-verify-function-alist))
1337           (while protocols
1338             (if (and (or (not (setq functest (nth 3 (car protocols))))
1339                          (funcall functest parts ctl))
1340                      (mm-find-part-by-type parts (caar protocols) nil t))
1341                 (setq protocol (caar protocols)
1342                       protocols nil)
1343               (setq protocols (cdr protocols))))))
1344       (setq func (nth 1 (assoc protocol mm-verify-function-alist)))
1345       (when (cond
1346              ((eq mm-verify-option 'never) nil)
1347              ((eq mm-verify-option 'always) t)
1348              ((eq mm-verify-option 'known)
1349               (and func
1350                    (or (not (setq functest
1351                                   (nth 3 (assoc protocol
1352                                                 mm-verify-function-alist))))
1353                        (funcall functest parts ctl))))
1354              (t
1355               (y-or-n-p
1356                (format "Verify signed (%s) part? "
1357                        (or (nth 2 (assoc protocol mm-verify-function-alist))
1358                            (format "protocol=%s" protocol))))))
1359         (save-excursion
1360           (if func
1361               (funcall func parts ctl)
1362             (mm-set-handle-multipart-parameter
1363              mm-security-handle 'gnus-details
1364              (format "Unknown sign protocol (%s)" protocol))))))
1365      ((equal subtype "encrypted")
1366       (unless (setq protocol
1367                     (mm-handle-multipart-ctl-parameter ctl 'protocol))
1368         ;; The message is broken.
1369         (let ((parts parts))
1370           (while parts
1371             (if (assoc (mm-handle-media-type (car parts))
1372                        mm-decrypt-function-alist)
1373                 (setq protocol (mm-handle-media-type (car parts))
1374                       parts nil)
1375               (setq parts (cdr parts))))))
1376       (setq func (nth 1 (assoc protocol mm-decrypt-function-alist)))
1377       (when (cond
1378              ((eq mm-decrypt-option 'never) nil)
1379              ((eq mm-decrypt-option 'always) t)
1380              ((eq mm-decrypt-option 'known)
1381               (and func
1382                    (or (not (setq functest
1383                                   (nth 3 (assoc protocol
1384                                                 mm-decrypt-function-alist))))
1385                        (funcall functest parts ctl))))
1386              (t
1387               (y-or-n-p
1388                (format "Decrypt (%s) part? "
1389                        (or (nth 2 (assoc protocol mm-decrypt-function-alist))
1390                            (format "protocol=%s" protocol))))))
1391         (save-excursion
1392           (if func
1393               (setq parts (funcall func parts ctl))
1394             (mm-set-handle-multipart-parameter
1395              mm-security-handle 'gnus-details
1396              (format "Unknown encrypt protocol (%s)" protocol))))))
1397      (t nil))
1398     parts))
1399
1400 (defun mm-multiple-handles (handles)
1401   (and (listp (car handles))
1402        (> (length handles) 1)))
1403
1404 (defun mm-merge-handles (handles1 handles2)
1405   (append
1406    (if (listp (car handles1))
1407        handles1
1408      (list handles1))
1409    (if (listp (car handles2))
1410        handles2
1411      (list handles2))))
1412
1413 (defun mm-readable-p (handle)
1414   "Say whether the content of HANDLE is readable."
1415   (and (< (with-current-buffer (mm-handle-buffer handle)
1416             (buffer-size)) 10000)
1417        (mm-with-unibyte-buffer
1418          (mm-insert-part handle)
1419          (and (eq (mm-body-7-or-8) '7bit)
1420               (not (mm-long-lines-p 76))))))
1421
1422 (provide 'mm-decode)
1423
1424 ;;; mm-decode.el ends here