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