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
01e80360 108 (cond ((executable-find "w3m") 'gnus-article-html)
23f87bed
MB
109 ((executable-find "links") 'links)
110 ((executable-find "lynx") 'lynx)
8f7abae3
MB
111 ((locate-library "w3") 'w3)
112 ((locate-library "html2text") 'html2text)
113 (t nil))
23f87bed
MB
114 "Render of HTML contents.
115It is one of defined renderer types, or a rendering function.
116The defined renderer types are:
20a673b2 117`gnus-article-html' : use Gnus renderer based on w3m;
23f87bed
MB
118`w3m' : use emacs-w3m;
119`w3m-standalone': use w3m;
120`links': use links;
121`lynx' : use lynx;
8f7abae3 122`w3' : use Emacs/W3;
23f87bed 123`html2text' : use html2text;
8f7abae3 124nil : use external viewer (default web browser)."
1a10d421 125 :version "24.1"
20a673b2
KY
126 :type '(choice (const gnus-article-html)
127 (const w3)
128 (const w3m :tag "emacs-w3m")
8f7abae3 129 (const w3m-standalone :tag "standalone w3m" )
23f87bed
MB
130 (const links)
131 (const lynx)
132 (const html2text)
8f7abae3 133 (const nil :tag "External viewer")
23f87bed 134 (function))
23f87bed
MB
135 :group 'mime-display)
136
137(defvar mm-inline-text-html-renderer nil
138 "Function used for rendering inline HTML contents.
139It is suggested to customize `mm-text-html-renderer' instead.")
140
141(defcustom mm-inline-text-html-with-images nil
142 "If non-nil, Gnus will allow retrieving images in HTML contents with
143the <img> tags. It has no effect on Emacs/w3. See also the
144documentation for the `mm-w3m-safe-url-regexp' variable."
bf247b6e 145 :version "22.1"
23f87bed
MB
146 :type 'boolean
147 :group 'mime-display)
148
149(defcustom mm-w3m-safe-url-regexp "\\`cid:"
150 "Regexp matching URLs which are considered to be safe.
151Some HTML mails might contain a nasty trick used by spammers, using
152the <img> tag which is far more evil than the [Click Here!] button.
153It is most likely intended to check whether the ominous spam mail has
154reached your eyes or not, in which case the spammer knows for sure
155that your email address is valid. It is done by embedding an
156identifier string into a URL that you might automatically retrieve
157when displaying the image. The default value is \"\\\\`cid:\" which only
158matches parts embedded to the Multipart/Related type MIME contents and
159Gnus will never connect to the spammer's site arbitrarily. You may
160set this variable to nil if you consider all urls to be safe."
bf247b6e 161 :version "22.1"
23f87bed
MB
162 :type '(choice (regexp :tag "Regexp")
163 (const :tag "All URLs are safe" nil))
164 :group 'mime-display)
165
166(defcustom mm-inline-text-html-with-w3m-keymap t
167 "If non-nil, use emacs-w3m command keys in the article buffer."
bf247b6e 168 :version "22.1"
23f87bed
MB
169 :type 'boolean
170 :group 'mime-display)
171
172(defcustom mm-enable-external t
173 "Indicate whether external MIME handlers should be used.
174
175If t, all defined external MIME handlers are used. If nil, files are saved by
176`mailcap-save-binary-file'. If it is the symbol `ask', you are prompted
177before the external MIME handler is invoked."
bf247b6e 178 :version "22.1"
23f87bed
MB
179 :type '(choice (const :tag "Always" t)
180 (const :tag "Never" nil)
181 (const :tag "Ask" ask))
182 :group 'mime-display)
183
c113de23 184(defcustom mm-inline-media-tests
23f87bed 185 '(("image/p?jpeg"
c113de23
GM
186 mm-inline-image
187 (lambda (handle)
188 (mm-valid-and-fit-image-p 'jpeg handle)))
189 ("image/png"
190 mm-inline-image
191 (lambda (handle)
192 (mm-valid-and-fit-image-p 'png handle)))
193 ("image/gif"
194 mm-inline-image
195 (lambda (handle)
196 (mm-valid-and-fit-image-p 'gif handle)))
197 ("image/tiff"
198 mm-inline-image
199 (lambda (handle)
200 (mm-valid-and-fit-image-p 'tiff handle)) )
201 ("image/xbm"
202 mm-inline-image
203 (lambda (handle)
204 (mm-valid-and-fit-image-p 'xbm handle)))
205 ("image/x-xbitmap"
206 mm-inline-image
207 (lambda (handle)
208 (mm-valid-and-fit-image-p 'xbm handle)))
209 ("image/xpm"
210 mm-inline-image
211 (lambda (handle)
212 (mm-valid-and-fit-image-p 'xpm handle)))
23f87bed 213 ("image/x-xpixmap"
c113de23
GM
214 mm-inline-image
215 (lambda (handle)
216 (mm-valid-and-fit-image-p 'xpm handle)))
217 ("image/bmp"
218 mm-inline-image
219 (lambda (handle)
220 (mm-valid-and-fit-image-p 'bmp handle)))
5bb548d2
DL
221 ("image/x-portable-bitmap"
222 mm-inline-image
223 (lambda (handle)
224 (mm-valid-and-fit-image-p 'pbm handle)))
c113de23
GM
225 ("text/plain" mm-inline-text identity)
226 ("text/enriched" mm-inline-text identity)
227 ("text/richtext" mm-inline-text identity)
228 ("text/x-patch" mm-display-patch-inline
229 (lambda (handle)
c615a00c
SM
230 ;; If the diff-mode.el package is installed, the function is
231 ;; autoloaded. Checking (locate-library "diff-mode") would be trying
232 ;; to cater to broken installations. OTOH checking the function
233 ;; makes it possible to install another package which provides an
234 ;; alternative implementation of diff-mode. --Stef
235 (fboundp 'diff-mode)))
7ab0253d
SM
236 ;; In case mime.types uses x-diff (as does Debian's mime-support-3.40).
237 ("text/x-diff" mm-display-patch-inline
238 (lambda (handle) (fboundp 'diff-mode)))
ce9401f3 239 ("application/emacs-lisp" mm-display-elisp-inline identity)
23f87bed 240 ("application/x-emacs-lisp" mm-display-elisp-inline identity)
01c52d31 241 ("text/dns" mm-display-dns-inline identity)
c113de23 242 ("text/html"
23f87bed 243 mm-inline-text-html
c113de23 244 (lambda (handle)
23f87bed
MB
245 (or mm-inline-text-html-renderer
246 mm-text-html-renderer)))
c113de23 247 ("text/x-vcard"
23f87bed 248 mm-inline-text-vcard
c113de23
GM
249 (lambda (handle)
250 (or (featurep 'vcard)
251 (locate-library "vcard"))))
252 ("message/delivery-status" mm-inline-text identity)
253 ("message/rfc822" mm-inline-message identity)
254 ("message/partial" mm-inline-partial identity)
23f87bed 255 ("message/external-body" mm-inline-external-body identity)
c113de23
GM
256 ("text/.*" mm-inline-text identity)
257 ("audio/wav" mm-inline-audio
258 (lambda (handle)
259 (and (or (featurep 'nas-sound) (featurep 'native-sound))
260 (device-sound-enabled-p))))
261 ("audio/au"
262 mm-inline-audio
263 (lambda (handle)
264 (and (or (featurep 'nas-sound) (featurep 'native-sound))
265 (device-sound-enabled-p))))
266 ("application/pgp-signature" ignore identity)
23f87bed
MB
267 ("application/x-pkcs7-signature" ignore identity)
268 ("application/pkcs7-signature" ignore identity)
269 ("application/x-pkcs7-mime" ignore identity)
270 ("application/pkcs7-mime" ignore identity)
c113de23
GM
271 ("multipart/alternative" ignore identity)
272 ("multipart/mixed" ignore identity)
23f87bed
MB
273 ("multipart/related" ignore identity)
274 ;; Disable audio and image
275 ("audio/.*" ignore ignore)
276 ("image/.*" ignore ignore)
277 ;; Default to displaying as text
278 (".*" mm-inline-text mm-readable-p))
c113de23 279 "Alist of media types/tests saying whether types can be displayed inline."
23f87bed 280 :type '(repeat (list (regexp :tag "MIME type")
c113de23
GM
281 (function :tag "Display function")
282 (function :tag "Display test")))
283 :group 'mime-display)
284
285(defcustom mm-inlined-types
286 '("image/.*" "text/.*" "message/delivery-status" "message/rfc822"
23f87bed
MB
287 "message/partial" "message/external-body" "application/emacs-lisp"
288 "application/x-emacs-lisp"
289 "application/pgp-signature" "application/x-pkcs7-signature"
290 "application/pkcs7-signature" "application/x-pkcs7-mime"
e499bc94
MB
291 "application/pkcs7-mime"
292 ;; Mutt still uses this even though it has already been withdrawn.
293 "application/pgp")
23f87bed
MB
294 "List of media types that are to be displayed inline.
295See also `mm-inline-media-tests', which says how to display a media
296type inline."
3031d8b0 297 :type '(repeat regexp)
23f87bed
MB
298 :group 'mime-display)
299
300(defcustom mm-keep-viewer-alive-types
301 '("application/postscript" "application/msword" "application/vnd.ms-excel"
302 "application/pdf" "application/x-dvi")
303 "List of media types for which the external viewer will not be killed
304when selecting a different article."
bf247b6e 305 :version "22.1"
3031d8b0 306 :type '(repeat regexp)
c113de23 307 :group 'mime-display)
a1506d29 308
c113de23 309(defcustom mm-automatic-display
01c52d31 310 '("text/plain" "text/enriched" "text/richtext" "text/html" "text/x-verbatim"
c113de23 311 "text/x-vcard" "image/.*" "message/delivery-status" "multipart/.*"
01c52d31 312 "message/rfc822" "text/x-patch" "text/dns" "application/pgp-signature"
23f87bed
MB
313 "application/emacs-lisp" "application/x-emacs-lisp"
314 "application/x-pkcs7-signature"
315 "application/pkcs7-signature" "application/x-pkcs7-mime"
e499bc94
MB
316 "application/pkcs7-mime"
317 ;; Mutt still uses this even though it has already been withdrawn.
0565caeb 318 "application/pgp\\'")
c113de23 319 "A list of MIME types to be displayed automatically."
3031d8b0 320 :type '(repeat regexp)
c113de23
GM
321 :group 'mime-display)
322
23f87bed
MB
323(defcustom mm-attachment-override-types '("text/x-vcard"
324 "application/pkcs7-mime"
325 "application/x-pkcs7-mime"
326 "application/pkcs7-signature"
327 "application/x-pkcs7-signature")
c113de23 328 "Types to have \"attachment\" ignored if they can be displayed inline."
3031d8b0 329 :type '(repeat regexp)
c113de23
GM
330 :group 'mime-display)
331
332(defcustom mm-inline-override-types nil
333 "Types to be treated as attachments even if they can be displayed inline."
3031d8b0 334 :type '(repeat regexp)
c113de23
GM
335 :group 'mime-display)
336
337(defcustom mm-automatic-external-display nil
338 "List of MIME type regexps that will be displayed externally automatically."
3031d8b0 339 :type '(repeat regexp)
c113de23
GM
340 :group 'mime-display)
341
342(defcustom mm-discouraged-alternatives nil
343 "List of MIME types that are discouraged when viewing multipart/alternative.
344Viewing agents are supposed to view the last possible part of a message,
345as that is supposed to be the richest. However, users may prefer other
346types instead, and this list says what types are most unwanted. If,
347for instance, text/html parts are very unwanted, and text/richtext are
348somewhat unwanted, then the value of this variable should be set
349to:
350
58090a8d
MB
351 (\"text/html\" \"text/richtext\")
352
353Adding \"image/.*\" might also be useful. Spammers use it as the
3031d8b0
MB
354prefered part of multipart/alternative messages. See also
355`gnus-buttonized-mime-types', to which adding \"multipart/alternative\"
356enables you to choose manually one of two types those mails include."
58090a8d 357 :type '(repeat regexp) ;; See `mm-preferred-alternative-precedence'.
c113de23
GM
358 :group 'mime-display)
359
23f87bed
MB
360(defcustom mm-tmp-directory
361 (if (fboundp 'temp-directory)
362 (temp-directory)
363 (if (boundp 'temporary-file-directory)
364 temporary-file-directory
365 "/tmp/"))
366 "Where mm will store its temporary files."
367 :type 'directory
368 :group 'mime-display)
c113de23
GM
369
370(defcustom mm-inline-large-images nil
371 "If non-nil, then all images fit in the buffer."
372 :type 'boolean
373 :group 'mime-display)
374
01c52d31 375(defcustom mm-file-name-rewrite-functions
23f87bed 376 '(mm-file-name-delete-control mm-file-name-delete-gotchas)
01c52d31 377 "List of functions used for rewriting file names of MIME parts.
23f87bed
MB
378Each function takes a file name as input and returns a file name.
379
01c52d31
MB
380Ready-made functions include `mm-file-name-delete-control',
381`mm-file-name-delete-gotchas' (you should not remove these two
382functions), `mm-file-name-delete-whitespace',
383`mm-file-name-trim-whitespace', `mm-file-name-collapse-whitespace',
384`mm-file-name-replace-whitespace', `capitalize', `downcase',
385`upcase', and `upcase-initials'."
386 :type '(list (set :inline t
387 (const mm-file-name-delete-control)
388 (const mm-file-name-delete-gotchas)
389 (const mm-file-name-delete-whitespace)
390 (const mm-file-name-trim-whitespace)
391 (const mm-file-name-collapse-whitespace)
392 (const mm-file-name-replace-whitespace)
393 (const capitalize)
394 (const downcase)
395 (const upcase)
396 (const upcase-initials)
397 (repeat :inline t
398 :tag "Function"
399 function)))
330f707b 400 :version "23.1" ;; No Gnus
01c52d31
MB
401 :group 'mime-display)
402
23f87bed
MB
403
404(defvar mm-path-name-rewrite-functions nil
405 "*List of functions for rewriting the full file names of MIME parts.
406This is used when viewing parts externally, and is meant for
407transforming the absolute name so that non-compliant programs can find
408the file where it's saved.
409
410Each function takes a file name as input and returns a file name.")
411
412(defvar mm-file-name-replace-whitespace nil
413 "String used for replacing whitespace characters; default is `\"_\"'.")
414
415(defcustom mm-default-directory nil
416 "The default directory where mm will save files.
417If not set, `default-directory' will be used."
418 :type '(choice directory (const :tag "Default" nil))
419 :group 'mime-display)
420
421(defcustom mm-attachment-file-modes 384
422 "Set the mode bits of saved attachments to this integer."
bf247b6e 423 :version "22.1"
23f87bed
MB
424 :type 'integer
425 :group 'mime-display)
426
427(defcustom mm-external-terminal-program "xterm"
428 "The program to start an external terminal."
bf247b6e 429 :version "22.1"
23f87bed
MB
430 :type 'string
431 :group 'mime-display)
432
c113de23
GM
433;;; Internal variables.
434
c113de23
GM
435(defvar mm-last-shell-command "")
436(defvar mm-content-id-alist nil)
23f87bed 437(defvar mm-postponed-undisplay-list nil)
c113de23
GM
438
439;; According to RFC2046, in particular, in a digest, the default
440;; Content-Type value for a body part is changed from "text/plain" to
441;; "message/rfc822".
442(defvar mm-dissect-default-type "text/plain")
443
23f87bed
MB
444(autoload 'mml2015-verify "mml2015")
445(autoload 'mml2015-verify-test "mml2015")
446(autoload 'mml-smime-verify "mml-smime")
447(autoload 'mml-smime-verify-test "mml-smime")
448
449(defvar mm-verify-function-alist
450 '(("application/pgp-signature" mml2015-verify "PGP" mml2015-verify-test)
451 ("application/x-gnus-pgp-signature" mm-uu-pgp-signed-extract-1 "PGP"
452 mm-uu-pgp-signed-test)
453 ("application/pkcs7-signature" mml-smime-verify "S/MIME"
454 mml-smime-verify-test)
455 ("application/x-pkcs7-signature" mml-smime-verify "S/MIME"
456 mml-smime-verify-test)))
457
458(defcustom mm-verify-option 'never
459 "Option of verifying signed parts.
460`never', not verify; `always', always verify;
01c52d31
MB
461`known', only verify known protocols. Otherwise, ask user.
462
463When set to `always' or `known', you should add
464\"multipart/signed\" to `gnus-buttonized-mime-types' to see
465result of the verification."
bf247b6e 466 :version "22.1"
23f87bed
MB
467 :type '(choice (item always)
468 (item never)
469 (item :tag "only known protocols" known)
470 (item :tag "ask" nil))
471 :group 'mime-security)
472
473(autoload 'mml2015-decrypt "mml2015")
474(autoload 'mml2015-decrypt-test "mml2015")
475
476(defvar mm-decrypt-function-alist
477 '(("application/pgp-encrypted" mml2015-decrypt "PGP" mml2015-decrypt-test)
478 ("application/x-gnus-pgp-encrypted" mm-uu-pgp-encrypted-extract-1 "PGP"
479 mm-uu-pgp-encrypted-test)))
480
481(defcustom mm-decrypt-option nil
482 "Option of decrypting encrypted parts.
483`never', not decrypt; `always', always decrypt;
484`known', only decrypt known protocols. Otherwise, ask user."
bf247b6e 485 :version "22.1"
23f87bed
MB
486 :type '(choice (item always)
487 (item never)
488 (item :tag "only known protocols" known)
489 (item :tag "ask" nil))
490 :group 'mime-security)
491
492(defvar mm-viewer-completion-map
493 (let ((map (make-sparse-keymap 'mm-viewer-completion-map)))
494 (set-keymap-parent map minibuffer-local-completion-map)
c615a00c
SM
495 ;; Should we bind other key to minibuffer-complete-word?
496 (define-key map " " 'self-insert-command)
23f87bed
MB
497 map)
498 "Keymap for input viewer with completion.")
499
62a27ccf
DL
500(defvar mm-viewer-completion-map
501 (let ((map (make-sparse-keymap 'mm-viewer-completion-map)))
502 (set-keymap-parent map minibuffer-local-completion-map)
c615a00c
SM
503 ;; Should we bind other key to minibuffer-complete-word?
504 (define-key map " " 'self-insert-command)
62a27ccf
DL
505 map)
506 "Keymap for input viewer with completion.")
507
c113de23
GM
508;;; The functions.
509
23f87bed
MB
510(defun mm-alist-to-plist (alist)
511 "Convert association list ALIST into the equivalent property-list form.
512The plist is returned. This converts from
513
514\((a . 1) (b . 2) (c . 3))
515
516into
517
518\(a 1 b 2 c 3)
519
520The original alist is not modified. See also `destructive-alist-to-plist'."
521 (let (plist)
522 (while alist
523 (let ((el (car alist)))
524 (setq plist (cons (cdr el) (cons (car el) plist))))
525 (setq alist (cdr alist)))
526 (nreverse plist)))
527
528(defun mm-keep-viewer-alive-p (handle)
529 "Say whether external viewer for HANDLE should stay alive."
530 (let ((types mm-keep-viewer-alive-types)
531 (type (mm-handle-media-type handle))
532 ty)
533 (catch 'found
534 (while (setq ty (pop types))
535 (when (string-match ty type)
536 (throw 'found t))))))
537
538(defun mm-handle-set-external-undisplayer (handle function)
539 "Set the undisplayer for HANDLE to FUNCTION.
540Postpone undisplaying of viewers for types in
541`mm-keep-viewer-alive-types'."
542 (if (mm-keep-viewer-alive-p handle)
543 (let ((new-handle (copy-sequence handle)))
544 (mm-handle-set-undisplayer new-handle function)
545 (mm-handle-set-undisplayer handle nil)
546 (push new-handle mm-postponed-undisplay-list))
547 (mm-handle-set-undisplayer handle function)))
548
549(defun mm-destroy-postponed-undisplay-list ()
550 (when mm-postponed-undisplay-list
551 (message "Destroying external MIME viewers")
552 (mm-destroy-parts mm-postponed-undisplay-list)))
553
aa8f8277
GM
554(autoload 'message-fetch-field "message")
555
ee7d3cc0 556(defun mm-dissect-buffer (&optional no-strict-mime loose-mime from)
c113de23
GM
557 "Dissect the current buffer and return a list of MIME handles."
558 (save-excursion
ee7d3cc0 559 (let (ct ctl type subtype cte cd description id result)
c113de23
GM
560 (save-restriction
561 (mail-narrow-to-head)
562 (when (or no-strict-mime
23f87bed 563 loose-mime
c113de23
GM
564 (mail-fetch-field "mime-version"))
565 (setq ct (mail-fetch-field "content-type")
c96ec15a 566 ctl (and ct (mail-header-parse-content-type ct))
c113de23
GM
567 cte (mail-fetch-field "content-transfer-encoding")
568 cd (mail-fetch-field "content-disposition")
aa227ba5
KY
569 ;; Newlines in description should be stripped so as
570 ;; not to break the MIME tag into two or more lines.
571 description (message-fetch-field "content-description")
23f87bed 572 id (mail-fetch-field "content-id"))
ee7d3cc0 573 (unless from
c96ec15a 574 (setq from (mail-fetch-field "from")))
23f87bed
MB
575 ;; FIXME: In some circumstances, this code is running within
576 ;; an unibyte macro. mail-extract-address-components
577 ;; creates unibyte buffers. This `if', though not a perfect
578 ;; solution, avoids most of them.
579 (if from
4599d0ec
MB
580 (setq from (cadr (mail-extract-address-components from))))
581 (if description
582 (setq description (mail-decode-encoded-word-string
583 description)))))
c113de23
GM
584 (if (or (not ctl)
585 (not (string-match "/" (car ctl))))
586 (mm-dissect-singlepart
587 (list mm-dissect-default-type)
01c52d31 588 (and cte (intern (downcase (mail-header-strip cte))))
c113de23 589 no-strict-mime
c96ec15a 590 (and cd (mail-header-parse-content-disposition cd))
c113de23
GM
591 description)
592 (setq type (split-string (car ctl) "/"))
593 (setq subtype (cadr type)
54e573e6 594 type (car type))
c113de23
GM
595 (setq
596 result
597 (cond
598 ((equal type "multipart")
599 (let ((mm-dissect-default-type (if (equal subtype "digest")
600 "message/rfc822"
23f87bed
MB
601 "text/plain"))
602 (start (cdr (assq 'start (cdr ctl)))))
603 (add-text-properties 0 (length (car ctl))
604 (mm-alist-to-plist (cdr ctl)) (car ctl))
605
606 ;; what really needs to be done here is a way to link a
607 ;; MIME handle back to it's parent MIME handle (in a multilevel
608 ;; MIME article). That would probably require changing
c615a00c 609 ;; the mm-handle API so we simply store the multipart buffer
23f87bed
MB
610 ;; name as a text property of the "multipart/whatever" string.
611 (add-text-properties 0 (length (car ctl))
612 (list 'buffer (mm-copy-to-buffer)
613 'from from
614 'start start)
615 (car ctl))
ee7d3cc0 616 (cons (car ctl) (mm-dissect-multipart ctl from))))
c113de23 617 (t
23f87bed
MB
618 (mm-possibly-verify-or-decrypt
619 (mm-dissect-singlepart
620 ctl
01c52d31 621 (and cte (intern (downcase (mail-header-strip cte))))
23f87bed 622 no-strict-mime
c96ec15a 623 (and cd (mail-header-parse-content-disposition cd))
23f87bed
MB
624 description id)
625 ctl))))
c113de23
GM
626 (when id
627 (when (string-match " *<\\(.*\\)> *" id)
628 (setq id (match-string 1 id)))
629 (push (cons id result) mm-content-id-alist))
630 result))))
631
632(defun mm-dissect-singlepart (ctl cte &optional force cdl description id)
633 (when (or force
634 (if (equal "text/plain" (car ctl))
635 (assoc 'format ctl)
636 t))
23f87bed
MB
637 (mm-make-handle
638 (mm-copy-to-buffer) ctl cte nil cdl description nil id)))
c113de23 639
ee7d3cc0 640(defun mm-dissect-multipart (ctl from)
c113de23
GM
641 (goto-char (point-min))
642 (let* ((boundary (concat "\n--" (mail-content-type-get ctl 'boundary)))
643 (close-delimiter (concat (regexp-quote boundary) "--[ \t]*$"))
644 start parts
645 (end (save-excursion
646 (goto-char (point-max))
647 (if (re-search-backward close-delimiter nil t)
648 (match-beginning 0)
649 (point-max)))))
650 (setq boundary (concat (regexp-quote boundary) "[ \t]*$"))
4481aa98 651 (while (and (< (point) end) (re-search-forward boundary end t))
c113de23
GM
652 (goto-char (match-beginning 0))
653 (when start
654 (save-excursion
655 (save-restriction
656 (narrow-to-region start (point))
ee7d3cc0 657 (setq parts (nconc (list (mm-dissect-buffer t nil from)) parts)))))
23f87bed
MB
658 (end-of-line 2)
659 (or (looking-at boundary)
660 (forward-line 1))
c113de23 661 (setq start (point)))
4481aa98 662 (when (and start (< start end))
c113de23
GM
663 (save-excursion
664 (save-restriction
665 (narrow-to-region start end)
ee7d3cc0 666 (setq parts (nconc (list (mm-dissect-buffer t nil from)) parts)))))
23f87bed 667 (mm-possibly-verify-or-decrypt (nreverse parts) ctl)))
c113de23
GM
668
669(defun mm-copy-to-buffer ()
670 "Copy the contents of the current buffer to a fresh buffer."
d4eb2b7e 671 (let ((obuf (current-buffer))
61dc89b0 672 (mb (mm-multibyte-p))
d4eb2b7e
SM
673 beg)
674 (goto-char (point-min))
675 (search-forward-regexp "^\n" nil t)
676 (setq beg (point))
54e573e6 677 (with-current-buffer
61dc89b0
SM
678 (generate-new-buffer " *mm*")
679 ;; Preserve the data's unibyteness (for url-insert-file-contents).
680 (mm-set-buffer-multibyte mb)
c113de23
GM
681 (insert-buffer-substring obuf beg)
682 (current-buffer))))
683
23f87bed
MB
684(defun mm-display-parts (handle &optional no-default)
685 (if (stringp (car handle))
686 (mapcar 'mm-display-parts (cdr handle))
687 (if (bufferp (car handle))
688 (save-restriction
689 (narrow-to-region (point) (point))
690 (mm-display-part handle)
691 (goto-char (point-max)))
692 (mapcar 'mm-display-parts handle))))
693
aa8f8277
GM
694(autoload 'mailcap-parse-mailcaps "mailcap")
695(autoload 'mailcap-mime-info "mailcap")
696
c113de23
GM
697(defun mm-display-part (handle &optional no-default)
698 "Display the MIME part represented by HANDLE.
699Returns nil if the part is removed; inline if displayed inline;
700external if displayed external."
701 (save-excursion
702 (mailcap-parse-mailcaps)
703 (if (mm-handle-displayed-p handle)
704 (mm-remove-part handle)
163cb72d
MB
705 (let* ((ehandle (if (equal (mm-handle-media-type handle)
706 "message/external-body")
707 (progn
708 (unless (mm-handle-cache handle)
709 (mm-extern-cache-contents handle))
710 (mm-handle-cache handle))
711 handle))
712 (type (mm-handle-media-type ehandle))
23f87bed
MB
713 (method (mailcap-mime-info type))
714 (filename (or (mail-content-type-get
715 (mm-handle-disposition handle) 'filename)
716 (mail-content-type-get
717 (mm-handle-type handle) 'name)
718 "<file>"))
719 (external mm-enable-external))
163cb72d
MB
720 (if (and (mm-inlinable-p ehandle)
721 (mm-inlined-p ehandle))
c113de23
GM
722 (progn
723 (forward-line 1)
724 (mm-display-inline handle)
725 'inline)
726 (when (or method
727 (not no-default))
728 (if (and (not method)
163cb72d 729 (equal "text" (car (split-string type "/"))))
c113de23
GM
730 (progn
731 (forward-line 1)
732 (mm-insert-inline handle (mm-get-part handle))
733 'inline)
54e573e6
MB
734 (setq external
735 (and method ;; If nil, we always use "save".
23f87bed
MB
736 (stringp method) ;; 'mailcap-save-binary-file
737 (or (eq mm-enable-external t)
738 (and (eq mm-enable-external 'ask)
739 (y-or-n-p
740 (concat
741 "Display part (" type
742 ") using external program"
743 ;; Can non-string method ever happen?
744 (if (stringp method)
745 (concat
746 " \"" (format method filename) "\"")
747 "")
54e573e6 748 "? "))))))
23f87bed
MB
749 (if external
750 (mm-display-external
751 handle (or method 'mailcap-save-binary-file))
752 (mm-display-external
753 handle 'mailcap-save-binary-file)))))))))
c113de23 754
60ef4154 755(declare-function gnus-configure-windows "gnus-win" (setting &optional force))
aa8f8277 756(defvar mailcap-mime-extensions) ; mailcap-mime-info autoloads
60ef4154 757
c113de23
GM
758(defun mm-display-external (handle method)
759 "Display HANDLE using METHOD."
760 (let ((outbuf (current-buffer)))
761 (mm-with-unibyte-buffer
762 (if (functionp method)
763 (let ((cur (current-buffer)))
764 (if (eq method 'mailcap-save-binary-file)
765 (progn
6eb681a3 766 (set-buffer (generate-new-buffer " *mm*"))
c113de23
GM
767 (setq method nil))
768 (mm-insert-part handle)
87035689 769 (mm-add-meta-html-tag handle)
c113de23
GM
770 (let ((win (get-buffer-window cur t)))
771 (when win
772 (select-window win)))
6eb681a3 773 (switch-to-buffer (generate-new-buffer " *mm*")))
23f87bed 774 (buffer-disable-undo)
c113de23
GM
775 (mm-set-buffer-file-coding-system mm-binary-coding-system)
776 (insert-buffer-substring cur)
777 (goto-char (point-min))
23f87bed
MB
778 (when method
779 (message "Viewing with %s" method))
c113de23
GM
780 (let ((mm (current-buffer))
781 (non-viewer (assq 'non-viewer
782 (mailcap-mime-info
783 (mm-handle-media-type handle) t))))
784 (unwind-protect
785 (if method
786 (funcall method)
787 (mm-save-part handle))
788 (when (and (not non-viewer)
789 method)
790 (mm-handle-set-undisplayer handle mm)))))
791 ;; The function is a string to be executed.
792 (mm-insert-part handle)
87035689 793 (mm-add-meta-html-tag handle)
23f87bed 794 (let* ((dir (mm-make-temp-file
3efe5554 795 (expand-file-name "emm." mm-tmp-directory) 'dir))
23f87bed
MB
796 (filename (or
797 (mail-content-type-get
798 (mm-handle-disposition handle) 'filename)
799 (mail-content-type-get
800 (mm-handle-type handle) 'name)))
c113de23
GM
801 (mime-info (mailcap-mime-info
802 (mm-handle-media-type handle) t))
803 (needsterm (or (assoc "needsterm" mime-info)
804 (assoc "needsterminal" mime-info)))
805 (copiousoutput (assoc "copiousoutput" mime-info))
806 file buffer)
807 ;; We create a private sub-directory where we store our files.
d55fe5bb 808 (set-file-modes dir #o700)
c113de23 809 (if filename
23f87bed
MB
810 (setq file (expand-file-name
811 (gnus-map-function mm-file-name-rewrite-functions
812 (file-name-nondirectory filename))
813 dir))
cf5a5c38
MB
814 ;; Use nametemplate (defined in RFC1524) if it is specified
815 ;; in mailcap.
816 (let ((suffix (cdr (assoc "nametemplate" mime-info))))
817 (if (and suffix
818 (string-match "\\`%s\\(\\..+\\)\\'" suffix))
819 (setq suffix (match-string 1 suffix))
820 ;; Otherwise, use a suffix according to
821 ;; `mailcap-mime-extensions'.
822 (setq suffix (car (rassoc (mm-handle-media-type handle)
823 mailcap-mime-extensions))))
824 (setq file (mm-make-temp-file (expand-file-name "mm." dir)
825 nil suffix))))
c113de23
GM
826 (let ((coding-system-for-write mm-binary-coding-system))
827 (write-region (point-min) (point-max) file nil 'nomesg))
d55fe5bb
MB
828 ;; The file is deleted after the viewer exists. If the users edits
829 ;; the file, changes will be lost. Set file to read-only to make it
830 ;; clear.
831 (set-file-modes file #o400)
c113de23 832 (message "Viewing with %s" method)
23f87bed
MB
833 (cond
834 (needsterm
835 (let ((command (mm-mailcap-command
836 method file (mm-handle-type handle))))
837 (unwind-protect
838 (if window-system
839 (start-process "*display*" nil
840 mm-external-terminal-program
841 "-e" shell-file-name
842 shell-command-switch command)
843 (require 'term)
844 (require 'gnus-win)
845 (set-buffer
846 (setq buffer
847 (make-term "display"
848 shell-file-name
849 nil
850 shell-command-switch command)))
851 (term-mode)
852 (term-char-mode)
853 (set-process-sentinel
854 (get-buffer-process buffer)
855 `(lambda (process state)
856 (if (eq 'exit (process-status process))
857 (gnus-configure-windows
858 ',gnus-current-window-configuration))))
859 (gnus-configure-windows 'display-term))
860 (mm-handle-set-external-undisplayer handle (cons file buffer)))
861 (message "Displaying %s..." command))
862 'external)
863 (copiousoutput
864 (with-current-buffer outbuf
865 (forward-line 1)
866 (mm-insert-inline
867 handle
868 (unwind-protect
869 (progn
870 (call-process shell-file-name nil
871 (setq buffer
872 (generate-new-buffer " *mm*"))
873 nil
874 shell-command-switch
875 (mm-mailcap-command
876 method file (mm-handle-type handle)))
877 (if (buffer-live-p buffer)
c615a00c 878 (with-current-buffer buffer
23f87bed
MB
879 (buffer-string))))
880 (progn
881 (ignore-errors (delete-file file))
882 (ignore-errors (delete-directory
883 (file-name-directory file)))
884 (ignore-errors (kill-buffer buffer))))))
885 'inline)
886 (t
58090a8d
MB
887 ;; Deleting the temp file should be postponed for some wrappers,
888 ;; shell scripts, and so on, which might exit right after having
889 ;; started a viewer command as a background job.
23f87bed
MB
890 (let ((command (mm-mailcap-command
891 method file (mm-handle-type handle))))
892 (unwind-protect
4b91459a
MB
893 (progn
894 (start-process "*display*"
895 (setq buffer
896 (generate-new-buffer " *mm*"))
897 shell-file-name
898 shell-command-switch command)
899 (set-process-sentinel
900 (get-buffer-process buffer)
58090a8d
MB
901 (lexical-let ;; Don't use `let'.
902 ;; Function used to remove temp file and directory.
903 ((fn `(lambda nil
904 ;; Don't use `ignore-errors'.
905 (condition-case nil
906 (delete-file ,file)
907 (error))
908 (condition-case nil
909 (delete-directory
910 ,(file-name-directory file))
911 (error))))
912 ;; Form uses to kill the process buffer and
913 ;; remove the undisplayer.
914 (fm `(progn
915 (kill-buffer ,buffer)
916 ,(macroexpand
917 (list 'mm-handle-set-undisplayer
918 (list 'quote handle)
919 nil))))
920 ;; Message to be issued when the process exits.
921 (done (format "Displaying %s...done" command))
922 ;; In particular, the timer object (which is
923 ;; a vector in Emacs but is a list in XEmacs)
924 ;; requires that it is lexically scoped.
925 (timer (run-at-time 2.0 nil 'ignore)))
9efa445f 926 (if (featurep 'xemacs)
ba361211
MB
927 (lambda (process state)
928 (when (eq 'exit (process-status process))
929 (if (memq timer itimer-list)
930 (set-itimer-function timer fn)
931 (funcall fn))
932 (ignore-errors (eval fm))
933 (message "%s" done)))
934 (lambda (process state)
935 (when (eq 'exit (process-status process))
936 (if (memq timer timer-list)
937 (timer-set-function timer fn)
938 (funcall fn))
939 (ignore-errors (eval fm))
940 (message "%s" done)))))))
23f87bed
MB
941 (mm-handle-set-external-undisplayer
942 handle (cons file buffer)))
943 (message "Displaying %s..." command))
944 'external)))))))
a1506d29 945
c113de23
GM
946(defun mm-mailcap-command (method file type-list)
947 (let ((ctl (cdr type-list))
948 (beg 0)
949 (uses-stdin t)
950 out sub total)
23f87bed
MB
951 (while (string-match "%{\\([^}]+\\)}\\|'%s'\\|\"%s\"\\|%s\\|%t\\|%%"
952 method beg)
c113de23
GM
953 (push (substring method beg (match-beginning 0)) out)
954 (setq beg (match-end 0)
955 total (match-string 0 method)
956 sub (match-string 1 method))
957 (cond
958 ((string= total "%%")
959 (push "%" out))
23f87bed
MB
960 ((or (string= total "%s")
961 ;; We do our own quoting.
962 (string= total "'%s'")
963 (string= total "\"%s\""))
c113de23 964 (setq uses-stdin nil)
01c52d31 965 (push (shell-quote-argument
23f87bed 966 (gnus-map-function mm-path-name-rewrite-functions file)) out))
c113de23 967 ((string= total "%t")
01c52d31 968 (push (shell-quote-argument (car type-list)) out))
c113de23 969 (t
01c52d31 970 (push (shell-quote-argument (or (cdr (assq (intern sub) ctl)) "")) out))))
c113de23 971 (push (substring method beg (length method)) out)
23f87bed
MB
972 (when uses-stdin
973 (push "<" out)
01c52d31 974 (push (shell-quote-argument
23f87bed
MB
975 (gnus-map-function mm-path-name-rewrite-functions file))
976 out))
c113de23 977 (mapconcat 'identity (nreverse out) "")))
a1506d29 978
c113de23
GM
979(defun mm-remove-parts (handles)
980 "Remove the displayed MIME parts represented by HANDLES."
981 (if (and (listp handles)
982 (bufferp (car handles)))
983 (mm-remove-part handles)
984 (let (handle)
985 (while (setq handle (pop handles))
986 (cond
987 ((stringp handle)
23f87bed
MB
988 (when (buffer-live-p (get-text-property 0 'buffer handle))
989 (kill-buffer (get-text-property 0 'buffer handle))))
c113de23
GM
990 ((and (listp handle)
991 (stringp (car handle)))
992 (mm-remove-parts (cdr handle)))
993 (t
994 (mm-remove-part handle)))))))
995
996(defun mm-destroy-parts (handles)
997 "Remove the displayed MIME parts represented by HANDLES."
998 (if (and (listp handles)
999 (bufferp (car handles)))
1000 (mm-destroy-part handles)
1001 (let (handle)
1002 (while (setq handle (pop handles))
1003 (cond
1004 ((stringp handle)
23f87bed
MB
1005 (when (buffer-live-p (get-text-property 0 'buffer handle))
1006 (kill-buffer (get-text-property 0 'buffer handle))))
c113de23
GM
1007 ((and (listp handle)
1008 (stringp (car handle)))
23f87bed 1009 (mm-destroy-parts handle))
c113de23
GM
1010 (t
1011 (mm-destroy-part handle)))))))
1012
1013(defun mm-remove-part (handle)
1014 "Remove the displayed MIME part represented by HANDLE."
1015 (when (listp handle)
1016 (let ((object (mm-handle-undisplayer handle)))
1017 (ignore-errors
1018 (cond
1019 ;; Internally displayed part.
1020 ((mm-annotationp object)
60ef4154
GM
1021 (if (featurep 'xemacs)
1022 (delete-annotation object)))
c113de23
GM
1023 ((or (functionp object)
1024 (and (listp object)
1025 (eq (car object) 'lambda)))
1026 (funcall object))
1027 ;; Externally displayed part.
1028 ((consp object)
23f87bed
MB
1029 (condition-case ()
1030 (while (get-buffer-process (cdr object))
1031 (interrupt-process (get-buffer-process (cdr object)))
1032 (message "Waiting for external displayer to die...")
1033 (sit-for 1))
1034 (quit)
1035 (error))
1036 (ignore-errors (and (cdr object) (kill-buffer (cdr object))))
1037 (message "Waiting for external displayer to die...done")
c113de23 1038 (ignore-errors (delete-file (car object)))
23f87bed
MB
1039 (ignore-errors (delete-directory (file-name-directory
1040 (car object)))))
c113de23
GM
1041 ((bufferp object)
1042 (when (buffer-live-p object)
1043 (kill-buffer object)))))
1044 (mm-handle-set-undisplayer handle nil))))
1045
1046(defun mm-display-inline (handle)
1047 (let* ((type (mm-handle-media-type handle))
1048 (function (cadr (mm-assoc-string-match mm-inline-media-tests type))))
1049 (funcall function handle)
1050 (goto-char (point-min))))
1051
1052(defun mm-assoc-string-match (alist type)
1053 (dolist (elem alist)
1054 (when (string-match (car elem) type)
1055 (return elem))))
1056
23f87bed
MB
1057(defun mm-automatic-display-p (handle)
1058 "Say whether the user wants HANDLE to be displayed automatically."
1059 (let ((methods mm-automatic-display)
1060 (type (mm-handle-media-type handle))
1061 method result)
1062 (while (setq method (pop methods))
1063 (when (and (not (mm-inline-override-p handle))
1064 (string-match method type))
1065 (setq result t
1066 methods nil)))
1067 result))
1068
54e573e6
MB
1069(defun mm-inlinable-p (handle &optional type)
1070 "Say whether HANDLE can be displayed inline.
1071TYPE is the mime-type of the object; it defaults to the one given
1072in HANDLE."
1073 (unless type (setq type (mm-handle-media-type handle)))
c113de23 1074 (let ((alist mm-inline-media-tests)
c113de23
GM
1075 test)
1076 (while alist
1077 (when (string-match (caar alist) type)
1078 (setq test (caddar alist)
1079 alist nil)
1080 (setq test (funcall test handle)))
1081 (pop alist))
1082 test))
1083
c113de23 1084(defun mm-inlined-p (handle)
23f87bed 1085 "Say whether the user wants HANDLE to be displayed inline."
c113de23
GM
1086 (let ((methods mm-inlined-types)
1087 (type (mm-handle-media-type handle))
1088 method result)
1089 (while (setq method (pop methods))
1090 (when (and (not (mm-inline-override-p handle))
23f87bed 1091 (string-match method type))
c113de23
GM
1092 (setq result t
1093 methods nil)))
1094 result))
1095
1096(defun mm-attachment-override-p (handle)
1097 "Say whether HANDLE should have attachment behavior overridden."
1098 (let ((types mm-attachment-override-types)
1099 (type (mm-handle-media-type handle))
1100 ty)
1101 (catch 'found
1102 (while (setq ty (pop types))
1103 (when (and (string-match ty type)
1104 (mm-inlinable-p handle))
1105 (throw 'found t))))))
1106
1107(defun mm-inline-override-p (handle)
1108 "Say whether HANDLE should have inline behavior overridden."
1109 (let ((types mm-inline-override-types)
1110 (type (mm-handle-media-type handle))
1111 ty)
1112 (catch 'found
1113 (while (setq ty (pop types))
1114 (when (string-match ty type)
1115 (throw 'found t))))))
1116
1117(defun mm-automatic-external-display-p (type)
1118 "Return the user-defined method for TYPE."
1119 (let ((methods mm-automatic-external-display)
1120 method result)
1121 (while (setq method (pop methods))
1122 (when (string-match method type)
1123 (setq result t
1124 methods nil)))
1125 result))
1126
1127(defun mm-destroy-part (handle)
1128 "Destroy the data structures connected to HANDLE."
1129 (when (listp handle)
1130 (mm-remove-part handle)
1131 (when (buffer-live-p (mm-handle-buffer handle))
1132 (kill-buffer (mm-handle-buffer handle)))))
1133
1134(defun mm-handle-displayed-p (handle)
1135 "Say whether HANDLE is displayed or not."
1136 (mm-handle-undisplayer handle))
1137
1138;;;
1139;;; Functions for outputting parts
1140;;;
1141
531bedc3
MB
1142(defmacro mm-with-part (handle &rest forms)
1143 "Run FORMS in the temp buffer containing the contents of HANDLE."
d4eb2b7e
SM
1144 ;; The handle-buffer's content is a sequence of bytes, not a sequence of
1145 ;; chars, so the buffer should be unibyte. It may happen that the
1146 ;; handle-buffer is multibyte for some reason, in which case now is a good
1147 ;; time to adjust it, since we know at this point that it should
1148 ;; be unibyte.
1149 `(let* ((handle ,handle))
531bedc3 1150 (with-temp-buffer
531bedc3 1151 (mm-disable-multibyte)
d4eb2b7e 1152 (insert-buffer-substring (mm-handle-buffer handle))
531bedc3
MB
1153 (mm-decode-content-transfer-encoding
1154 (mm-handle-encoding handle)
1155 (mm-handle-media-type handle))
1156 ,@forms)))
1157(put 'mm-with-part 'lisp-indent-function 1)
1158(put 'mm-with-part 'edebug-form-spec '(body))
1159
77218834
MB
1160(defun mm-get-part (handle &optional no-cache)
1161 "Return the contents of HANDLE as a string.
1162If NO-CACHE is non-nil, cached contents of a message/external-body part
1163are ignored."
1164 (if (and (not no-cache)
1165 (equal (mm-handle-media-type handle) "message/external-body"))
531bedc3
MB
1166 (progn
1167 (unless (mm-handle-cache handle)
1168 (mm-extern-cache-contents handle))
1169 (with-current-buffer (mm-handle-buffer (mm-handle-cache handle))
1170 (buffer-string)))
1171 (mm-with-part handle
719120ef 1172 (buffer-string))))
c113de23 1173
77218834
MB
1174(defun mm-insert-part (handle &optional no-cache)
1175 "Insert the contents of HANDLE in the current buffer.
1176If NO-CACHE is non-nil, cached contents of a message/external-body part
1177are ignored."
01c52d31
MB
1178 (let ((text (cond ((eq (mail-content-type-get (mm-handle-type handle)
1179 'charset)
1180 'gnus-decoded)
1181 (with-current-buffer (mm-handle-buffer handle)
1182 (buffer-string)))
1183 ((mm-multibyte-p)
1184 (mm-string-to-multibyte (mm-get-part handle no-cache)))
1185 (t
1186 (mm-get-part handle no-cache)))))
1187 (save-restriction
1188 (widen)
1189 (goto-char
1190 (prog1
1191 (point)
1192 (if (and (eq (get-char-property (max (point-min) (1- (point))) 'face)
1193 'mm-uu-extract)
1194 (eq (get-char-property 0 'face text) 'mm-uu-extract))
1195 ;; Separate the extracted parts that have the same faces.
1196 (insert "\n" text)
1197 (insert text)))))))
23f87bed
MB
1198
1199(defun mm-file-name-delete-whitespace (file-name)
1200 "Remove all whitespace characters from FILE-NAME."
1201 (while (string-match "\\s-+" file-name)
1202 (setq file-name (replace-match "" t t file-name)))
1203 file-name)
1204
1205(defun mm-file-name-trim-whitespace (file-name)
1206 "Remove leading and trailing whitespace characters from FILE-NAME."
1207 (when (string-match "\\`\\s-+" file-name)
1208 (setq file-name (substring file-name (match-end 0))))
1209 (when (string-match "\\s-+\\'" file-name)
1210 (setq file-name (substring file-name 0 (match-beginning 0))))
1211 file-name)
1212
1213(defun mm-file-name-collapse-whitespace (file-name)
1214 "Collapse multiple whitespace characters in FILE-NAME."
1215 (while (string-match "\\s-\\s-+" file-name)
1216 (setq file-name (replace-match " " t t file-name)))
1217 file-name)
1218
1219(defun mm-file-name-replace-whitespace (file-name)
1220 "Replace whitespace characters in FILE-NAME with underscores.
1221Set the option `mm-file-name-replace-whitespace' to any other
1222string if you do not like underscores."
1223 (let ((s (or mm-file-name-replace-whitespace "_")))
1224 (while (string-match "\\s-" file-name)
1225 (setq file-name (replace-match s t t file-name))))
1226 file-name)
1227
1228(defun mm-file-name-delete-control (filename)
1229 "Delete control characters from FILENAME."
1230 (gnus-replace-in-string filename "[\x00-\x1f\x7f]" ""))
1231
1232(defun mm-file-name-delete-gotchas (filename)
1233 "Delete shell gotchas from FILENAME."
1234 (setq filename (gnus-replace-in-string filename "[<>|]" ""))
1235 (gnus-replace-in-string filename "^[.-]+" ""))
c113de23 1236
01c52d31
MB
1237(defun mm-save-part (handle &optional prompt)
1238 "Write HANDLE to a file.
1239PROMPT overrides the default one used to ask user for a file name."
531bedc3
MB
1240 (let ((filename (or (mail-content-type-get
1241 (mm-handle-disposition handle) 'filename)
1242 (mail-content-type-get
1243 (mm-handle-type handle) 'name)))
1244 file)
c113de23 1245 (when filename
23f87bed
MB
1246 (setq filename (gnus-map-function mm-file-name-rewrite-functions
1247 (file-name-nondirectory filename))))
c113de23 1248 (setq file
7ab0253d
SM
1249 (read-file-name (or prompt "Save MIME part to: ")
1250 (or mm-default-directory default-directory)
1251 nil nil (or filename "")))
c113de23 1252 (setq mm-default-directory (file-name-directory file))
23f87bed
MB
1253 (and (or (not (file-exists-p file))
1254 (yes-or-no-p (format "File %s already exists; overwrite? "
1255 file)))
1256 (progn
1257 (mm-save-part-to-file handle file)
1258 file))))
c113de23 1259
82fc7980 1260(defun mm-add-meta-html-tag (handle &optional charset force-charset)
bbbe940b
MB
1261 "Add meta html tag to specify CHARSET of HANDLE in the current buffer.
1262CHARSET defaults to the one HANDLE specifies. Existing meta tag that
82fc7980
KY
1263specifies charset will not be modified unless FORCE-CHARSET is non-nil.
1264Return t if meta tag is added or replaced."
bbbe940b
MB
1265 (when (equal (mm-handle-media-type handle) "text/html")
1266 (when (or charset
1267 (setq charset (mail-content-type-get (mm-handle-type handle)
1268 'charset)))
1269 (setq charset (format "\
1270<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\">" charset))
1271 (let ((case-fold-search t))
1272 (goto-char (point-min))
1273 (if (re-search-forward "\
1274<meta\\s-+http-equiv=[\"']?content-type[\"']?\\s-+content=[\"']\
9d9ef8e8 1275text/\\(\\sw+\\)\\(?:\;\\s-*charset=\\(.+\\)\\)?[\"'][^>]*>" nil t)
82fc7980
KY
1276 (if (and (not force-charset)
1277 (match-beginning 2)
bbbe940b
MB
1278 (string-match "\\`html\\'" (match-string 1)))
1279 ;; Don't modify existing meta tag.
1280 nil
1281 ;; Replace it with the one specifying charset.
1282 (replace-match charset)
1283 t)
1284 (if (re-search-forward "<head>\\s-*" nil t)
1285 (insert charset "\n")
1286 (re-search-forward "<html\\(?:\\s-+[^>]+\\|\\s-*\\)>\\s-*" nil t)
1287 (insert "<head>\n" charset "\n</head>\n"))
1288 t)))))
1289
c113de23
GM
1290(defun mm-save-part-to-file (handle file)
1291 (mm-with-unibyte-buffer
1292 (mm-insert-part handle)
bbbe940b 1293 (mm-add-meta-html-tag handle)
01c52d31
MB
1294 (let ((current-file-modes (default-file-modes)))
1295 (set-default-file-modes mm-attachment-file-modes)
1296 (unwind-protect
c113de23
GM
1297 ;; Don't re-compress .gz & al. Arguably we should make
1298 ;; `file-name-handler-alist' nil, but that would chop
1299 ;; ange-ftp, which is reasonable to use here.
01c52d31 1300 (mm-write-region (point-min) (point-max) file nil nil nil 'binary t)
23f87bed 1301 (set-default-file-modes current-file-modes)))))
c113de23 1302
9581ba4d
KY
1303(defun mm-pipe-part (handle &optional cmd)
1304 "Pipe HANDLE to a process.
1305Use CMD as the process."
1306 (let ((name (mail-content-type-get (mm-handle-type handle) 'name))
1307 (command (or cmd
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
1673;;; mm-decode.el ends here