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