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