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