Merge from emacs-23
[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
8f7abae3
MB
108 (cond ((executable-find "w3m")
109 (if (locate-library "w3m")
110 'w3m
111 'w3m-standalone))
23f87bed
MB
112 ((executable-find "links") 'links)
113 ((executable-find "lynx") 'lynx)
8f7abae3
MB
114 ((locate-library "w3") 'w3)
115 ((locate-library "html2text") 'html2text)
116 (t nil))
23f87bed
MB
117 "Render of HTML contents.
118It is one of defined renderer types, or a rendering function.
119The defined renderer types are:
23f87bed
MB
120`w3m' : use emacs-w3m;
121`w3m-standalone': use w3m;
122`links': use links;
123`lynx' : use lynx;
8f7abae3 124`w3' : use Emacs/W3;
23f87bed 125`html2text' : use html2text;
8f7abae3
MB
126nil : use external viewer (default web browser)."
127 :version "23.0" ;; No Gnus
23f87bed 128 :type '(choice (const w3)
8f7abae3
MB
129 (const w3m :tag "emacs-w3m")
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))
531bedc3 1151 (with-temp-buffer
531bedc3 1152 (mm-disable-multibyte)
d4eb2b7e 1153 (insert-buffer-substring (mm-handle-buffer handle))
531bedc3
MB
1154 (mm-decode-content-transfer-encoding
1155 (mm-handle-encoding handle)
1156 (mm-handle-media-type handle))
1157 ,@forms)))
1158(put 'mm-with-part 'lisp-indent-function 1)
1159(put 'mm-with-part 'edebug-form-spec '(body))
1160
77218834
MB
1161(defun mm-get-part (handle &optional no-cache)
1162 "Return the contents of HANDLE as a string.
1163If NO-CACHE is non-nil, cached contents of a message/external-body part
1164are ignored."
1165 (if (and (not no-cache)
1166 (equal (mm-handle-media-type handle) "message/external-body"))
531bedc3
MB
1167 (progn
1168 (unless (mm-handle-cache handle)
1169 (mm-extern-cache-contents handle))
1170 (with-current-buffer (mm-handle-buffer (mm-handle-cache handle))
1171 (buffer-string)))
1172 (mm-with-part handle
719120ef 1173 (buffer-string))))
c113de23 1174
77218834
MB
1175(defun mm-insert-part (handle &optional no-cache)
1176 "Insert the contents of HANDLE in the current buffer.
1177If NO-CACHE is non-nil, cached contents of a message/external-body part
1178are ignored."
01c52d31
MB
1179 (let ((text (cond ((eq (mail-content-type-get (mm-handle-type handle)
1180 'charset)
1181 'gnus-decoded)
1182 (with-current-buffer (mm-handle-buffer handle)
1183 (buffer-string)))
1184 ((mm-multibyte-p)
1185 (mm-string-to-multibyte (mm-get-part handle no-cache)))
1186 (t
1187 (mm-get-part handle no-cache)))))
1188 (save-restriction
1189 (widen)
1190 (goto-char
1191 (prog1
1192 (point)
1193 (if (and (eq (get-char-property (max (point-min) (1- (point))) 'face)
1194 'mm-uu-extract)
1195 (eq (get-char-property 0 'face text) 'mm-uu-extract))
1196 ;; Separate the extracted parts that have the same faces.
1197 (insert "\n" text)
1198 (insert text)))))))
23f87bed
MB
1199
1200(defun mm-file-name-delete-whitespace (file-name)
1201 "Remove all whitespace characters from FILE-NAME."
1202 (while (string-match "\\s-+" file-name)
1203 (setq file-name (replace-match "" t t file-name)))
1204 file-name)
1205
1206(defun mm-file-name-trim-whitespace (file-name)
1207 "Remove leading and trailing whitespace characters from FILE-NAME."
1208 (when (string-match "\\`\\s-+" file-name)
1209 (setq file-name (substring file-name (match-end 0))))
1210 (when (string-match "\\s-+\\'" file-name)
1211 (setq file-name (substring file-name 0 (match-beginning 0))))
1212 file-name)
1213
1214(defun mm-file-name-collapse-whitespace (file-name)
1215 "Collapse multiple whitespace characters in FILE-NAME."
1216 (while (string-match "\\s-\\s-+" file-name)
1217 (setq file-name (replace-match " " t t file-name)))
1218 file-name)
1219
1220(defun mm-file-name-replace-whitespace (file-name)
1221 "Replace whitespace characters in FILE-NAME with underscores.
1222Set the option `mm-file-name-replace-whitespace' to any other
1223string if you do not like underscores."
1224 (let ((s (or mm-file-name-replace-whitespace "_")))
1225 (while (string-match "\\s-" file-name)
1226 (setq file-name (replace-match s t t file-name))))
1227 file-name)
1228
1229(defun mm-file-name-delete-control (filename)
1230 "Delete control characters from FILENAME."
1231 (gnus-replace-in-string filename "[\x00-\x1f\x7f]" ""))
1232
1233(defun mm-file-name-delete-gotchas (filename)
1234 "Delete shell gotchas from FILENAME."
1235 (setq filename (gnus-replace-in-string filename "[<>|]" ""))
1236 (gnus-replace-in-string filename "^[.-]+" ""))
c113de23 1237
01c52d31
MB
1238(defun mm-save-part (handle &optional prompt)
1239 "Write HANDLE to a file.
1240PROMPT overrides the default one used to ask user for a file name."
531bedc3
MB
1241 (let ((filename (or (mail-content-type-get
1242 (mm-handle-disposition handle) 'filename)
1243 (mail-content-type-get
1244 (mm-handle-type handle) 'name)))
1245 file)
c113de23 1246 (when filename
23f87bed
MB
1247 (setq filename (gnus-map-function mm-file-name-rewrite-functions
1248 (file-name-nondirectory filename))))
c113de23 1249 (setq file
7ab0253d
SM
1250 (read-file-name (or prompt "Save MIME part to: ")
1251 (or mm-default-directory default-directory)
1252 nil nil (or filename "")))
c113de23 1253 (setq mm-default-directory (file-name-directory file))
23f87bed
MB
1254 (and (or (not (file-exists-p file))
1255 (yes-or-no-p (format "File %s already exists; overwrite? "
1256 file)))
1257 (progn
1258 (mm-save-part-to-file handle file)
1259 file))))
c113de23 1260
82fc7980 1261(defun mm-add-meta-html-tag (handle &optional charset force-charset)
bbbe940b
MB
1262 "Add meta html tag to specify CHARSET of HANDLE in the current buffer.
1263CHARSET defaults to the one HANDLE specifies. Existing meta tag that
82fc7980
KY
1264specifies charset will not be modified unless FORCE-CHARSET is non-nil.
1265Return t if meta tag is added or replaced."
bbbe940b
MB
1266 (when (equal (mm-handle-media-type handle) "text/html")
1267 (when (or charset
1268 (setq charset (mail-content-type-get (mm-handle-type handle)
1269 'charset)))
1270 (setq charset (format "\
1271<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\">" charset))
1272 (let ((case-fold-search t))
1273 (goto-char (point-min))
1274 (if (re-search-forward "\
1275<meta\\s-+http-equiv=[\"']?content-type[\"']?\\s-+content=[\"']\
9d9ef8e8 1276text/\\(\\sw+\\)\\(?:\;\\s-*charset=\\(.+\\)\\)?[\"'][^>]*>" nil t)
82fc7980
KY
1277 (if (and (not force-charset)
1278 (match-beginning 2)
bbbe940b
MB
1279 (string-match "\\`html\\'" (match-string 1)))
1280 ;; Don't modify existing meta tag.
1281 nil
1282 ;; Replace it with the one specifying charset.
1283 (replace-match charset)
1284 t)
1285 (if (re-search-forward "<head>\\s-*" nil t)
1286 (insert charset "\n")
1287 (re-search-forward "<html\\(?:\\s-+[^>]+\\|\\s-*\\)>\\s-*" nil t)
1288 (insert "<head>\n" charset "\n</head>\n"))
1289 t)))))
1290
c113de23
GM
1291(defun mm-save-part-to-file (handle file)
1292 (mm-with-unibyte-buffer
1293 (mm-insert-part handle)
bbbe940b 1294 (mm-add-meta-html-tag handle)
01c52d31
MB
1295 (let ((current-file-modes (default-file-modes)))
1296 (set-default-file-modes mm-attachment-file-modes)
1297 (unwind-protect
c113de23
GM
1298 ;; Don't re-compress .gz & al. Arguably we should make
1299 ;; `file-name-handler-alist' nil, but that would chop
1300 ;; ange-ftp, which is reasonable to use here.
01c52d31 1301 (mm-write-region (point-min) (point-max) file nil nil nil 'binary t)
23f87bed 1302 (set-default-file-modes current-file-modes)))))
c113de23
GM
1303
1304(defun mm-pipe-part (handle)
1305 "Pipe HANDLE to a process."
1306 (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
1307 (command
d346bf7e
SM
1308 (gnus-read-shell-command
1309 "Shell command on MIME part: " mm-last-shell-command)))
c113de23
GM
1310 (mm-with-unibyte-buffer
1311 (mm-insert-part handle)
bbbe940b 1312 (mm-add-meta-html-tag handle)
23f87bed
MB
1313 (let ((coding-system-for-write 'binary))
1314 (shell-command-on-region (point-min) (point-max) command nil)))))
c113de23
GM
1315
1316(defun mm-interactively-view-part (handle)
1317 "Display HANDLE using METHOD."
1318 (let* ((type (mm-handle-media-type handle))
1319 (methods
1320 (mapcar (lambda (i) (list (cdr (assoc 'viewer i))))
1321 (mailcap-mime-info type 'all)))
62a27ccf
DL
1322 (method (let ((minibuffer-local-completion-map
1323 mm-viewer-completion-map))
1324 (completing-read "Viewer: " methods))))
c113de23
GM
1325 (when (string= method "")
1326 (error "No method given"))
5bb548d2 1327 (if (string-match "^[^% \t]+$" method)
c113de23 1328 (setq method (concat method " %s")))
619ac84f 1329 (mm-display-external handle method)))
c113de23
GM
1330
1331(defun mm-preferred-alternative (handles &optional preferred)
1332 "Say which of HANDLES are preferred."
1333 (let ((prec (if preferred (list preferred)
1334 (mm-preferred-alternative-precedence handles)))
1335 p h result type handle)
1336 (while (setq p (pop prec))
1337 (setq h handles)
1338 (while h
1339 (setq handle (car h))
1340 (setq type (mm-handle-media-type handle))
1341 (when (and (equal p type)
1342 (mm-automatic-display-p handle)
1343 (or (stringp (car handle))
1344 (not (mm-handle-disposition handle))
1345 (equal (car (mm-handle-disposition handle))
1346 "inline")))
1347 (setq result handle
1348 h nil
1349 prec nil))
1350 (pop h)))
1351 result))
1352
1353(defun mm-preferred-alternative-precedence (handles)
1354 "Return the precedence based on HANDLES and `mm-discouraged-alternatives'."
1355 (let ((seq (nreverse (mapcar #'mm-handle-media-type
1356 handles))))
1357 (dolist (disc (reverse mm-discouraged-alternatives))
1358 (dolist (elem (copy-sequence seq))
1359 (when (string-match disc elem)
1360 (setq seq (nconc (delete elem seq) (list elem))))))
1361 seq))
1362
1363(defun mm-get-content-id (id)
1364 "Return the handle(s) referred to by ID."
1365 (cdr (assoc id mm-content-id-alist)))
1366
23f87bed
MB
1367(defconst mm-image-type-regexps
1368 '(("/\\*.*XPM.\\*/" . xpm)
1369 ("P[1-6]" . pbm)
1370 ("GIF8" . gif)
1371 ("\377\330" . jpeg)
1372 ("\211PNG\r\n" . png)
1373 ("#define" . xbm)
1374 ("\\(MM\0\\*\\)\\|\\(II\\*\0\\)" . tiff)
1375 ("%!PS" . postscript))
1376 "Alist of (REGEXP . IMAGE-TYPE) pairs used to auto-detect image types.
1377When the first bytes of an image file match REGEXP, it is assumed to
1378be of image type IMAGE-TYPE.")
1379
1380;; Steal from image.el. image-type-from-data suffers multi-line matching bug.
1381(defun mm-image-type-from-buffer ()
1382 "Determine the image type from data in the current buffer.
1383Value is a symbol specifying the image type or nil if type cannot
1384be determined."
1385 (let ((types mm-image-type-regexps)
1386 type)
1387 (goto-char (point-min))
1388 (while (and types (null type))
1389 (let ((regexp (car (car types)))
1390 (image-type (cdr (car types))))
1391 (when (looking-at regexp)
1392 (setq type image-type))
1393 (setq types (cdr types))))
1394 type))
1395
c113de23
GM
1396(defun mm-get-image (handle)
1397 "Return an image instance based on HANDLE."
1398 (let ((type (mm-handle-media-subtype handle))
1399 spec)
1400 ;; Allow some common translations.
1401 (setq type
1402 (cond
1403 ((equal type "x-pixmap")
1404 "xpm")
1405 ((equal type "x-xbitmap")
1406 "xbm")
5bb548d2
DL
1407 ((equal type "x-portable-bitmap")
1408 "pbm")
c113de23
GM
1409 (t type)))
1410 (or (mm-handle-cache handle)
1411 (mm-with-unibyte-buffer
1412 (mm-insert-part handle)
1413 (prog1
1414 (setq spec
1415 (ignore-errors
23f87bed
MB
1416 ;; Avoid testing `make-glyph' since W3 may define
1417 ;; a bogus version of it.
c113de23 1418 (if (fboundp 'create-image)
23f87bed
MB
1419 (create-image (buffer-string)
1420 (or (mm-image-type-from-buffer)
1421 (intern type))
1422 'data-p)
1423 (mm-create-image-xemacs type))))
c113de23
GM
1424 (mm-handle-set-cache handle spec))))))
1425
23f87bed 1426(defun mm-create-image-xemacs (type)
9efa445f
DN
1427 (when (featurep 'xemacs)
1428 (cond
1429 ((equal type "xbm")
1430 ;; xbm images require special handling, since
1431 ;; the only way to create glyphs from these
1432 ;; (without a ton of work) is to write them
1433 ;; out to a file, and then create a file
1434 ;; specifier.
1435 (let ((file (mm-make-temp-file
1436 (expand-file-name "emm" mm-tmp-directory)
1437 nil ".xbm")))
1438 (unwind-protect
1439 (progn
1440 (write-region (point-min) (point-max) file)
1441 (make-glyph (list (cons 'x file))))
1442 (ignore-errors
1443 (delete-file file)))))
1444 (t
1445 (make-glyph
1446 (vector
1447 (or (mm-image-type-from-buffer)
1448 (intern type))
1449 :data (buffer-string)))))))
23f87bed 1450
12c9fab6
GM
1451(declare-function image-size "image.c" (spec &optional pixels frame))
1452
c113de23
GM
1453(defun mm-image-fit-p (handle)
1454 "Say whether the image in HANDLE will fit the current window."
1455 (let ((image (mm-get-image handle)))
524705ae 1456 (or (not image)
9efa445f 1457 (if (featurep 'xemacs)
524705ae
MB
1458 ;; XEmacs' glyphs can actually tell us about their width, so
1459 ;; lets be nice and smart about them.
1460 (or mm-inline-large-images
1461 (and (<= (glyph-width image) (window-pixel-width))
1462 (<= (glyph-height image) (window-pixel-height))))
1463 (let* ((size (image-size image))
1464 (w (car size))
1465 (h (cdr size)))
1466 (or mm-inline-large-images
1467 (and (<= h (1- (window-height))) ; Don't include mode line.
1468 (<= w (window-width)))))))))
c113de23
GM
1469
1470(defun mm-valid-image-format-p (format)
1471 "Say whether FORMAT can be displayed natively by Emacs."
1472 (cond
1473 ;; Handle XEmacs
1474 ((fboundp 'valid-image-instantiator-format-p)
1475 (valid-image-instantiator-format-p format))
1476 ;; Handle Emacs 21
1477 ((fboundp 'image-type-available-p)
1478 (and (display-graphic-p)
1479 (image-type-available-p format)))
1480 ;; Nobody else can do images yet.
1481 (t
1482 nil)))
1483
1484(defun mm-valid-and-fit-image-p (format handle)
1485 "Say whether FORMAT can be displayed natively and HANDLE fits the window."
62a27ccf 1486 (and (mm-valid-image-format-p format)
c113de23
GM
1487 (mm-image-fit-p handle)))
1488
23f87bed
MB
1489(defun mm-find-part-by-type (handles type &optional notp recursive)
1490 "Search in HANDLES for part with TYPE.
1491If NOTP, returns first non-matching part.
1492If RECURSIVE, search recursively."
1493 (let (handle)
1494 (while handles
1495 (if (and recursive (stringp (caar handles)))
1496 (if (setq handle (mm-find-part-by-type (cdar handles) type
1497 notp recursive))
1498 (setq handles nil))
1499 (if (if notp
1500 (not (equal (mm-handle-media-type (car handles)) type))
1501 (equal (mm-handle-media-type (car handles)) type))
1502 (setq handle (car handles)
1503 handles nil)))
1504 (setq handles (cdr handles)))
1505 handle))
1506
1507(defun mm-find-raw-part-by-type (ctl type &optional notp)
1508 (goto-char (point-min))
1509 (let* ((boundary (concat "--" (mm-handle-multipart-ctl-parameter ctl
1510 'boundary)))
1511 (close-delimiter (concat "^" (regexp-quote boundary) "--[ \t]*$"))
1512 start
1513 (end (save-excursion
1514 (goto-char (point-max))
1515 (if (re-search-backward close-delimiter nil t)
1516 (match-beginning 0)
1517 (point-max))))
1518 result)
1519 (setq boundary (concat "^" (regexp-quote boundary) "[ \t]*$"))
1520 (while (and (not result)
1521 (re-search-forward boundary end t))
1522 (goto-char (match-beginning 0))
1523 (when start
1524 (save-excursion
1525 (save-restriction
1526 (narrow-to-region start (1- (point)))
c96ec15a
MB
1527 (when (let* ((ct (mail-fetch-field "content-type"))
1528 (ctl (and ct (mail-header-parse-content-type ct))))
23f87bed
MB
1529 (if notp
1530 (not (equal (car ctl) type))
1531 (equal (car ctl) type)))
1532 (setq result (buffer-string))))))
1533 (forward-line 1)
1534 (setq start (point)))
1535 (when (and (not result) start)
1536 (save-excursion
1537 (save-restriction
1538 (narrow-to-region start end)
c96ec15a
MB
1539 (when (let* ((ct (mail-fetch-field "content-type"))
1540 (ctl (and ct (mail-header-parse-content-type ct))))
23f87bed
MB
1541 (if notp
1542 (not (equal (car ctl) type))
1543 (equal (car ctl) type)))
1544 (setq result (buffer-string))))))
1545 result))
1546
1547(defvar mm-security-handle nil)
1548
1549(defsubst mm-set-handle-multipart-parameter (handle parameter value)
1550 ;; HANDLE could be a CTL.
1551 (when handle
1552 (put-text-property 0 (length (car handle)) parameter value
1553 (car handle))))
1554
60ef4154
GM
1555(autoload 'mm-view-pkcs7 "mm-view")
1556
23f87bed
MB
1557(defun mm-possibly-verify-or-decrypt (parts ctl)
1558 (let ((type (car ctl))
1559 (subtype (cadr (split-string (car ctl) "/")))
1560 (mm-security-handle ctl) ;; (car CTL) is the type.
1561 protocol func functest)
1562 (cond
1563 ((or (equal type "application/x-pkcs7-mime")
1564 (equal type "application/pkcs7-mime"))
1565 (with-temp-buffer
1566 (when (and (cond
1567 ((eq mm-decrypt-option 'never) nil)
1568 ((eq mm-decrypt-option 'always) t)
1569 ((eq mm-decrypt-option 'known) t)
1570 (t (y-or-n-p
1571 (format "Decrypt (S/MIME) part? "))))
1572 (mm-view-pkcs7 parts))
1573 (setq parts (mm-dissect-buffer t)))))
1574 ((equal subtype "signed")
1575 (unless (and (setq protocol
1576 (mm-handle-multipart-ctl-parameter ctl 'protocol))
1577 (not (equal protocol "multipart/mixed")))
1578 ;; The message is broken or draft-ietf-openpgp-multsig-01.
1579 (let ((protocols mm-verify-function-alist))
1580 (while protocols
1581 (if (and (or (not (setq functest (nth 3 (car protocols))))
1582 (funcall functest parts ctl))
1583 (mm-find-part-by-type parts (caar protocols) nil t))
1584 (setq protocol (caar protocols)
1585 protocols nil)
1586 (setq protocols (cdr protocols))))))
1587 (setq func (nth 1 (assoc protocol mm-verify-function-alist)))
1588 (when (cond
1589 ((eq mm-verify-option 'never) nil)
1590 ((eq mm-verify-option 'always) t)
1591 ((eq mm-verify-option 'known)
1592 (and func
1593 (or (not (setq functest
1594 (nth 3 (assoc protocol
1595 mm-verify-function-alist))))
1596 (funcall functest parts ctl))))
1597 (t
1598 (y-or-n-p
1599 (format "Verify signed (%s) part? "
1600 (or (nth 2 (assoc protocol mm-verify-function-alist))
1601 (format "protocol=%s" protocol))))))
1602 (save-excursion
1603 (if func
01c52d31 1604 (setq parts (funcall func parts ctl))
23f87bed
MB
1605 (mm-set-handle-multipart-parameter
1606 mm-security-handle 'gnus-details
1607 (format "Unknown sign protocol (%s)" protocol))))))
1608 ((equal subtype "encrypted")
1609 (unless (setq protocol
1610 (mm-handle-multipart-ctl-parameter ctl 'protocol))
1611 ;; The message is broken.
1612 (let ((parts parts))
1613 (while parts
1614 (if (assoc (mm-handle-media-type (car parts))
1615 mm-decrypt-function-alist)
1616 (setq protocol (mm-handle-media-type (car parts))
1617 parts nil)
1618 (setq parts (cdr parts))))))
1619 (setq func (nth 1 (assoc protocol mm-decrypt-function-alist)))
1620 (when (cond
1621 ((eq mm-decrypt-option 'never) nil)
1622 ((eq mm-decrypt-option 'always) t)
1623 ((eq mm-decrypt-option 'known)
1624 (and func
1625 (or (not (setq functest
1626 (nth 3 (assoc protocol
1627 mm-decrypt-function-alist))))
1628 (funcall functest parts ctl))))
1629 (t
1630 (y-or-n-p
1631 (format "Decrypt (%s) part? "
1632 (or (nth 2 (assoc protocol mm-decrypt-function-alist))
1633 (format "protocol=%s" protocol))))))
1634 (save-excursion
1635 (if func
1636 (setq parts (funcall func parts ctl))
1637 (mm-set-handle-multipart-parameter
1638 mm-security-handle 'gnus-details
1639 (format "Unknown encrypt protocol (%s)" protocol))))))
1640 (t nil))
1641 parts))
1642
1643(defun mm-multiple-handles (handles)
1644 (and (listp handles)
1645 (> (length handles) 1)
1646 (or (listp (car handles))
1647 (stringp (car handles)))))
1648
1649(defun mm-complicated-handles (handles)
1650 (and (listp (car handles))
1651 (> (length handles) 1)))
1652
1653(defun mm-merge-handles (handles1 handles2)
1654 (append
1655 (if (listp (car handles1))
1656 handles1
1657 (list handles1))
1658 (if (listp (car handles2))
1659 handles2
1660 (list handles2))))
1661
1662(defun mm-readable-p (handle)
1663 "Say whether the content of HANDLE is readable."
1664 (and (< (with-current-buffer (mm-handle-buffer handle)
1665 (buffer-size)) 10000)
1666 (mm-with-unibyte-buffer
1667 (mm-insert-part handle)
1668 (and (eq (mm-body-7-or-8) '7bit)
1669 (not (mm-long-lines-p 76))))))
1670
c113de23
GM
1671(provide 'mm-decode)
1672
46cdaf24 1673;; arch-tag: 4f35d360-56b8-4030-9388-3ed82d359b9b
c113de23 1674;;; mm-decode.el ends here