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