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