b2a0cadb8048111230ac363168e1a47325c9432e
[gnus] / lisp / mm-decode.el
1 ;;; mm-decode.el --- Functions for decoding MIME things
2
3 ;; Copyright (C) 1998-2015 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-replace-in-string "gnus-util")
32 (autoload 'gnus-read-shell-command "gnus-util")
33 (autoload 'gnus-overlays-at "gnus-util")
34 (autoload 'gnus-overlay-put "gnus-util")
35
36 (autoload 'mm-inline-partial "mm-partial")
37 (autoload 'mm-inline-external-body "mm-extern")
38 (autoload 'mm-extern-cache-contents "mm-extern")
39 (autoload 'mm-insert-inline "mm-view")
40
41 (autoload 'mm-archive-decoders "mm-archive")
42 (autoload 'mm-archive-dissect-and-inline "mm-archive")
43 (autoload 'mm-dissect-archive "mm-archive")
44
45 (defvar gnus-current-window-configuration)
46
47 (add-hook 'gnus-exit-gnus-hook 'mm-destroy-postponed-undisplay-list)
48 (add-hook 'gnus-exit-gnus-hook 'mm-temp-files-delete)
49
50 (defgroup mime-display ()
51   "Display of MIME in mail and news articles."
52   :link '(custom-manual "(emacs-mime)Display Customization")
53   :version "21.1"
54   :group 'mail
55   :group 'news
56   :group 'multimedia)
57
58 (defgroup mime-security ()
59   "MIME security in mail and news articles."
60   :link '(custom-manual "(emacs-mime)Display Customization")
61   :group 'mail
62   :group 'news
63   :group 'multimedia)
64
65 (defface mm-command-output
66   '((((class color)
67       (background dark))
68      (:foreground "ForestGreen"))
69     (((class color)
70       (background light))
71      (:foreground "red3"))
72     (t
73      (:italic t)))
74   "Face used for displaying output from commands."
75   :group 'mime-display)
76
77 ;;; Convenience macros.
78
79 (defmacro mm-handle-buffer (handle)
80   `(nth 0 ,handle))
81 (defmacro mm-handle-type (handle)
82   `(nth 1 ,handle))
83 (defsubst mm-handle-media-type (handle)
84   (if (stringp (car handle))
85       (car handle)
86     (car (mm-handle-type handle))))
87 (defsubst mm-handle-media-supertype (handle)
88   (car (split-string (mm-handle-media-type handle) "/")))
89 (defsubst mm-handle-media-subtype (handle)
90   (cadr (split-string (mm-handle-media-type handle) "/")))
91 (defmacro mm-handle-encoding (handle)
92   `(nth 2 ,handle))
93 (defmacro mm-handle-undisplayer (handle)
94   `(nth 3 ,handle))
95 (defmacro mm-handle-set-undisplayer (handle function)
96   `(setcar (nthcdr 3 ,handle) ,function))
97 (defmacro mm-handle-disposition (handle)
98   `(nth 4 ,handle))
99 (defmacro mm-handle-description (handle)
100   `(nth 5 ,handle))
101 (defmacro mm-handle-cache (handle)
102   `(nth 6 ,handle))
103 (defmacro mm-handle-set-cache (handle contents)
104   `(setcar (nthcdr 6 ,handle) ,contents))
105 (defmacro mm-handle-id (handle)
106   `(nth 7 ,handle))
107 (defmacro mm-handle-multipart-original-buffer (handle)
108   `(get-text-property 0 'buffer (car ,handle)))
109 (defmacro mm-handle-multipart-from (handle)
110   `(get-text-property 0 'from (car ,handle)))
111 (defmacro mm-handle-multipart-ctl-parameter (handle parameter)
112   `(get-text-property 0 ,parameter (car ,handle)))
113
114 (defmacro mm-make-handle (&optional buffer type encoding undisplayer
115                                     disposition description cache
116                                     id)
117   `(list ,buffer ,type ,encoding ,undisplayer
118          ,disposition ,description ,cache ,id))
119
120 (defcustom mm-text-html-renderer
121   (cond ((fboundp 'libxml-parse-html-region) 'shr)
122         ((executable-find "w3m") 'gnus-w3m)
123         ((executable-find "links") 'links)
124         ((executable-find "lynx") 'lynx)
125         ((locate-library "html2text") 'html2text)
126         (t nil))
127   "Render of HTML contents.
128 It is one of defined renderer types, or a rendering function.
129 The defined renderer types are:
130 `shr': use the built-in Gnus HTML renderer;
131 `gnus-w3m': use Gnus renderer based on w3m;
132 `w3m': use emacs-w3m;
133 `w3m-standalone': use plain w3m;
134 `links': use links;
135 `lynx': use lynx;
136 `html2text': use html2text;
137 nil    : use external viewer (default web browser)."
138   :version "24.1"
139   :type '(choice (const shr)
140                  (const gnus-w3m)
141                  (const w3m :tag "emacs-w3m")
142                  (const w3m-standalone :tag "standalone w3m" )
143                  (const links)
144                  (const lynx)
145                  (const html2text)
146                  (const nil :tag "External viewer")
147                  (function))
148   :group 'mime-display)
149
150 (defcustom mm-inline-text-html-with-images nil
151   "If non-nil, Gnus will allow retrieving images in HTML that has <img> tags.
152 See also the documentation for the `mm-w3m-safe-url-regexp'
153 variable."
154   :version "22.1"
155   :type 'boolean
156   :group 'mime-display)
157
158 (defcustom mm-w3m-safe-url-regexp "\\`cid:"
159   "Regexp matching URLs which are considered to be safe.
160 Some HTML mails might contain a nasty trick used by spammers, using
161 the <img> tag which is far more evil than the [Click Here!] button.
162 It is most likely intended to check whether the ominous spam mail has
163 reached your eyes or not, in which case the spammer knows for sure
164 that your email address is valid.  It is done by embedding an
165 identifier string into a URL that you might automatically retrieve
166 when displaying the image.  The default value is \"\\\\`cid:\" which only
167 matches parts embedded to the Multipart/Related type MIME contents and
168 Gnus will never connect to the spammer's site arbitrarily.  You may
169 set this variable to nil if you consider all urls to be safe."
170   :version "22.1"
171   :type '(choice (regexp :tag "Regexp")
172                  (const :tag "All URLs are safe" nil))
173   :group 'mime-display)
174
175 (defcustom mm-inline-text-html-with-w3m-keymap t
176   "If non-nil, use emacs-w3m command keys in the article buffer."
177   :version "22.1"
178   :type 'boolean
179   :group 'mime-display)
180
181 (defcustom mm-enable-external t
182   "Indicate whether external MIME handlers should be used.
183
184 If t, all defined external MIME handlers are used.  If nil, files are saved by
185 `mailcap-save-binary-file'.  If it is the symbol `ask', you are prompted
186 before the external MIME handler is invoked."
187   :version "22.1"
188   :type '(choice (const :tag "Always" t)
189                  (const :tag "Never" nil)
190                  (const :tag "Ask" ask))
191   :group 'mime-display)
192
193 (defcustom mm-inline-media-tests
194   '(("image/p?jpeg"
195      mm-inline-image
196      (lambda (handle)
197        (mm-valid-and-fit-image-p 'jpeg handle)))
198     ("image/png"
199      mm-inline-image
200      (lambda (handle)
201        (mm-valid-and-fit-image-p 'png handle)))
202     ("image/gif"
203      mm-inline-image
204      (lambda (handle)
205        (mm-valid-and-fit-image-p 'gif handle)))
206     ("image/tiff"
207      mm-inline-image
208      (lambda (handle)
209        (mm-valid-and-fit-image-p 'tiff handle)))
210     ("image/xbm"
211      mm-inline-image
212      (lambda (handle)
213        (mm-valid-and-fit-image-p 'xbm handle)))
214     ("image/x-xbitmap"
215      mm-inline-image
216      (lambda (handle)
217        (mm-valid-and-fit-image-p 'xbm handle)))
218     ("image/xpm"
219      mm-inline-image
220      (lambda (handle)
221        (mm-valid-and-fit-image-p 'xpm handle)))
222     ("image/x-xpixmap"
223      mm-inline-image
224      (lambda (handle)
225        (mm-valid-and-fit-image-p 'xpm handle)))
226     ("image/bmp"
227      mm-inline-image
228      (lambda (handle)
229        (mm-valid-and-fit-image-p 'bmp handle)))
230     ("image/x-portable-bitmap"
231      mm-inline-image
232      (lambda (handle)
233        (mm-valid-and-fit-image-p 'pbm handle)))
234     ("text/plain" mm-inline-text identity)
235     ("text/enriched" mm-inline-text identity)
236     ("text/richtext" mm-inline-text identity)
237     ("text/x-patch" mm-display-patch-inline identity)
238     ;; In case mime.types uses x-diff (as does Debian's mime-support-3.40).
239     ("text/x-diff" mm-display-patch-inline identity)
240     ("application/emacs-lisp" mm-display-elisp-inline identity)
241     ("application/x-emacs-lisp" mm-display-elisp-inline identity)
242     ("application/x-shellscript" mm-display-shell-script-inline identity)
243     ("application/x-sh" mm-display-shell-script-inline identity)
244     ("text/x-sh" mm-display-shell-script-inline identity)
245     ("application/javascript" mm-display-javascript-inline identity)
246     ("text/dns" mm-display-dns-inline identity)
247     ("text/x-org" mm-display-org-inline identity)
248     ("text/html"
249      mm-inline-text-html
250      (lambda (handle)
251        mm-text-html-renderer))
252     ("text/x-vcard"
253      mm-inline-text-vcard
254      (lambda (handle)
255        (or (featurep 'vcard)
256            (locate-library "vcard"))))
257     ("message/delivery-status" mm-inline-text identity)
258     ("message/rfc822" mm-inline-message identity)
259     ("message/partial" mm-inline-partial identity)
260     ("message/external-body" mm-inline-external-body identity)
261     ("text/.*" mm-inline-text identity)
262     ("application/x-.?tar\\(-.*\\)?" mm-archive-dissect-and-inline identity)
263     ("application/zip" mm-archive-dissect-and-inline identity)
264     ("audio/wav" mm-inline-audio
265      (lambda (handle)
266        (and (or (featurep 'nas-sound) (featurep 'native-sound))
267             (device-sound-enabled-p))))
268     ("audio/au"
269      mm-inline-audio
270      (lambda (handle)
271        (and (or (featurep 'nas-sound) (featurep 'native-sound))
272             (device-sound-enabled-p))))
273     ("application/pgp-signature" ignore identity)
274     ("application/x-pkcs7-signature" ignore identity)
275     ("application/pkcs7-signature" ignore identity)
276     ("application/x-pkcs7-mime" ignore identity)
277     ("application/pkcs7-mime" ignore identity)
278     ("multipart/alternative" ignore identity)
279     ("multipart/mixed" ignore identity)
280     ("multipart/related" ignore identity)
281     ("image/.*"
282      mm-inline-image
283      (lambda (handle)
284        (and (mm-valid-image-format-p 'imagemagick)
285             (mm-with-unibyte-buffer
286               (mm-insert-part handle)
287               (let ((image
288                      (ignore-errors
289                        (if (fboundp 'create-image)
290                            (create-image (buffer-string) 'imagemagick 'data-p)
291                          (mm-create-image-xemacs
292                           (mm-handle-media-subtype handle))))))
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.  See also `destructive-alist-to-plist'."
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       (mm-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-display-part (handle &optional no-default force)
794   "Display the MIME part represented by HANDLE.
795 Returns nil if the part is removed; inline if displayed inline;
796 external if displayed external."
797   (save-excursion
798     (mailcap-parse-mailcaps)
799     (if (and (not force)
800              (mm-handle-displayed-p handle))
801         (mm-remove-part handle)
802       (let* ((ehandle (if (equal (mm-handle-media-type handle)
803                                  "message/external-body")
804                           (progn
805                             (unless (mm-handle-cache handle)
806                               (mm-extern-cache-contents handle))
807                             (mm-handle-cache handle))
808                         handle))
809              (type (mm-handle-media-type ehandle))
810              (method (mailcap-mime-info type))
811              (filename (or (mail-content-type-get
812                             (mm-handle-disposition handle) 'filename)
813                            (mail-content-type-get
814                             (mm-handle-type handle) 'name)
815                            "<file>"))
816              (external mm-enable-external)
817              (decoder (assoc (car (mm-handle-type handle))
818                              (mm-archive-decoders))))
819         (cond
820          ((and decoder
821                (executable-find (caddr decoder)))
822           (mm-archive-dissect-and-inline handle)
823           'inline)
824          ((and (mm-inlinable-p ehandle)
825                (mm-inlined-p ehandle))
826           (mm-display-inline handle)
827           'inline)
828          ((or method
829               (not no-default))
830           (if (and (not method)
831                    (equal "text" (car (split-string type "/"))))
832               (progn
833                 (forward-line 1)
834                 (mm-insert-inline handle (mm-get-part handle))
835                 'inline)
836             (setq external
837                   (and method         ;; If nil, we always use "save".
838                        (or (eq mm-enable-external t)
839                            (and (eq mm-enable-external 'ask)
840                                 (y-or-n-p
841                                  (concat
842                                   "Display part (" type
843                                   ") "
844                                   (if (stringp method)
845                                       (concat
846                                        "using external program \""
847                                        (format method filename) "\"")
848                                     (format
849                                      "by calling `%s' on the contents)" method))
850                                   "? "))))))
851             (if external
852                 (mm-display-external
853                  handle (or method 'mailcap-save-binary-file))
854               (mm-display-external
855                handle 'mailcap-save-binary-file)))))))))
856
857 (declare-function gnus-configure-windows "gnus-win" (setting &optional force))
858 (defvar mailcap-mime-extensions)        ; mailcap-mime-info autoloads
859 (declare-function term-mode "term" ())
860 (declare-function term-char-mode "term" ())
861
862 (defun mm-display-external (handle method)
863   "Display HANDLE using METHOD."
864   (let ((outbuf (current-buffer)))
865     (mm-with-unibyte-buffer
866       (if (functionp method)
867           (let ((cur (current-buffer)))
868             (if (eq method 'mailcap-save-binary-file)
869                 (progn
870                   (set-buffer (generate-new-buffer " *mm*"))
871                   (setq method nil))
872               (mm-insert-part handle)
873               (mm-add-meta-html-tag handle)
874               (let ((win (get-buffer-window cur t)))
875                 (when win
876                   (select-window win)))
877               (switch-to-buffer (generate-new-buffer " *mm*")))
878             (buffer-disable-undo)
879             (mm-set-buffer-file-coding-system mm-binary-coding-system)
880             (insert-buffer-substring cur)
881             (goto-char (point-min))
882             (when method
883               (message "Viewing with %s" method))
884             (let ((mm (current-buffer))
885                   (non-viewer (assq 'non-viewer
886                                     (mailcap-mime-info
887                                      (mm-handle-media-type handle) t))))
888               (unwind-protect
889                   (if method
890                       (progn
891                         (when (and (boundp 'gnus-summary-buffer)
892                                    (bufferp gnus-summary-buffer)
893                                    (buffer-name gnus-summary-buffer))
894                           ;; So that we pop back to the right place, sort of.
895                           (switch-to-buffer gnus-summary-buffer)
896                           (switch-to-buffer mm))
897                         (delete-other-windows)
898                         (funcall method))
899                     (mm-save-part handle))
900                 (when (and (not non-viewer)
901                            method)
902                   (mm-handle-set-undisplayer handle mm)))))
903         ;; The function is a string to be executed.
904         (mm-insert-part handle)
905         (mm-add-meta-html-tag handle)
906         (let* ((dir (mm-make-temp-file
907                      (expand-file-name "emm." mm-tmp-directory) 'dir))
908                (filename (or
909                           (mail-content-type-get
910                            (mm-handle-disposition handle) 'filename)
911                           (mail-content-type-get
912                            (mm-handle-type handle) 'name)))
913                (mime-info (mailcap-mime-info
914                            (mm-handle-media-type handle) t))
915                (needsterm (or (assoc "needsterm" mime-info)
916                               (assoc "needsterminal" mime-info)))
917                (copiousoutput (assoc "copiousoutput" mime-info))
918                file buffer)
919           ;; We create a private sub-directory where we store our files.
920           (set-file-modes dir #o700)
921           (if filename
922               (setq file (expand-file-name
923                           (gnus-map-function mm-file-name-rewrite-functions
924                                              (file-name-nondirectory filename))
925                           dir))
926             ;; Use nametemplate (defined in RFC1524) if it is specified
927             ;; in mailcap.
928             (let ((suffix (cdr (assoc "nametemplate" mime-info))))
929               (if (and suffix
930                        (string-match "\\`%s\\(\\..+\\)\\'" suffix))
931                   (setq suffix (match-string 1 suffix))
932                 ;; Otherwise, use a suffix according to
933                 ;; `mailcap-mime-extensions'.
934                 (setq suffix (car (rassoc (mm-handle-media-type handle)
935                                           mailcap-mime-extensions))))
936               (setq file (mm-make-temp-file (expand-file-name "mm." dir)
937                                             nil suffix))))
938           (let ((coding-system-for-write mm-binary-coding-system))
939             (write-region (point-min) (point-max) file nil 'nomesg))
940           ;; The file is deleted after the viewer exists.  If the users edits
941           ;; the file, changes will be lost.  Set file to read-only to make it
942           ;; clear.
943           (set-file-modes file #o400)
944           (message "Viewing with %s" method)
945           (cond
946            (needsterm
947             (let ((command (mm-mailcap-command
948                             method file (mm-handle-type handle))))
949               (unwind-protect
950                   (if window-system
951                       (set-process-sentinel
952                        (start-process "*display*" nil
953                                       mm-external-terminal-program
954                                       "-e" shell-file-name
955                                       shell-command-switch command)
956                        `(lambda (process state)
957                           (if (eq 'exit (process-status process))
958                               (run-at-time
959                                60.0 nil
960                                (lambda ()
961                                  (ignore-errors (delete-file ,file))
962                                  (ignore-errors (delete-directory
963                                                  ,(file-name-directory
964                                                    file))))))))
965                     (require 'term)
966                     (require 'gnus-win)
967                     (set-buffer
968                      (setq buffer
969                            (make-term "display"
970                                       shell-file-name
971                                       nil
972                                       shell-command-switch command)))
973                     (term-mode)
974                     (term-char-mode)
975                     (set-process-sentinel
976                      (get-buffer-process buffer)
977                      `(lambda (process state)
978                         (when (eq 'exit (process-status process))
979                           (ignore-errors (delete-file ,file))
980                           (ignore-errors
981                             (delete-directory ,(file-name-directory file)))
982                           (gnus-configure-windows
983                            ',gnus-current-window-configuration))))
984                     (gnus-configure-windows 'display-term))
985                 (mm-handle-set-external-undisplayer handle (cons file buffer))
986                 (add-to-list 'mm-temp-files-to-be-deleted file t))
987               (message "Displaying %s..." command))
988             'external)
989            (copiousoutput
990             (with-current-buffer outbuf
991               (forward-line 1)
992               (mm-insert-inline
993                handle
994                (unwind-protect
995                    (progn
996                      (call-process shell-file-name nil
997                                    (setq buffer
998                                          (generate-new-buffer " *mm*"))
999                                    nil
1000                                    shell-command-switch
1001                                    (mm-mailcap-command
1002                                     method file (mm-handle-type handle)))
1003                      (if (buffer-live-p buffer)
1004                          (with-current-buffer buffer
1005                            (buffer-string))))
1006                  (progn
1007                    (ignore-errors (delete-file file))
1008                    (ignore-errors (delete-directory
1009                                    (file-name-directory file)))
1010                    (ignore-errors (kill-buffer buffer))))))
1011             'inline)
1012            (t
1013             ;; Deleting the temp file should be postponed for some wrappers,
1014             ;; shell scripts, and so on, which might exit right after having
1015             ;; started a viewer command as a background job.
1016             (let ((command (mm-mailcap-command
1017                             method file (mm-handle-type handle))))
1018               (unwind-protect
1019                   (let ((process-connection-type nil))
1020                     (start-process "*display*"
1021                                    (setq buffer
1022                                          (generate-new-buffer " *mm*"))
1023                                    shell-file-name
1024                                    shell-command-switch command)
1025                     (set-process-sentinel
1026                      (get-buffer-process buffer)
1027                      (lexical-let ((outbuf outbuf)
1028                                    (file file)
1029                                    (buffer buffer)
1030                                    (command command)
1031                                    (handle handle))
1032                        (lambda (process state)
1033                          (when (eq (process-status process) 'exit)
1034                            (run-at-time
1035                             60.0 nil
1036                             (lambda ()
1037                               (ignore-errors (delete-file file))
1038                               (ignore-errors (delete-directory
1039                                               (file-name-directory file)))))
1040                            (when (buffer-live-p outbuf)
1041                              (with-current-buffer outbuf
1042                                (let ((buffer-read-only nil)
1043                                      (point (point)))
1044                                  (forward-line 2)
1045                                  (let ((start (point)))
1046                                    (mm-insert-inline
1047                                     handle (with-current-buffer buffer
1048                                              (buffer-string)))
1049                                    (put-text-property start (point)
1050                                                       'face 'mm-command-output))
1051                                  (goto-char point))))
1052                            (when (buffer-live-p buffer)
1053                              (kill-buffer buffer)))
1054                          (message "Displaying %s...done" command)))))
1055                 (mm-handle-set-external-undisplayer
1056                  handle (cons file buffer))
1057                 (add-to-list 'mm-temp-files-to-be-deleted file t))
1058               (message "Displaying %s..." command))
1059             'external)))))))
1060
1061 (defun mm-mailcap-command (method file type-list)
1062   (let ((ctl (cdr type-list))
1063         (beg 0)
1064         (uses-stdin t)
1065         out sub total)
1066     (while (string-match "%{\\([^}]+\\)}\\|'%s'\\|\"%s\"\\|%s\\|%t\\|%%"
1067                          method beg)
1068       (push (substring method beg (match-beginning 0)) out)
1069       (setq beg (match-end 0)
1070             total (match-string 0 method)
1071             sub (match-string 1 method))
1072       (cond
1073        ((string= total "%%")
1074         (push "%" out))
1075        ((or (string= total "%s")
1076             ;; We do our own quoting.
1077             (string= total "'%s'")
1078             (string= total "\"%s\""))
1079         (setq uses-stdin nil)
1080         (push (shell-quote-argument
1081                (gnus-map-function mm-path-name-rewrite-functions file)) out))
1082        ((string= total "%t")
1083         (push (shell-quote-argument (car type-list)) out))
1084        (t
1085         (push (shell-quote-argument (or (cdr (assq (intern sub) ctl)) "")) out))))
1086     (push (substring method beg (length method)) out)
1087     (when uses-stdin
1088       (push "<" out)
1089       (push (shell-quote-argument
1090              (gnus-map-function mm-path-name-rewrite-functions file))
1091             out))
1092     (mapconcat 'identity (nreverse out) "")))
1093
1094 (defun mm-remove-parts (handles)
1095   "Remove the displayed MIME parts represented by HANDLES."
1096   (if (and (listp handles)
1097            (bufferp (car handles)))
1098       (mm-remove-part handles)
1099     (let (handle)
1100       (while (setq handle (pop handles))
1101         (cond
1102          ((stringp handle)
1103           (when (buffer-live-p (get-text-property 0 'buffer handle))
1104             (kill-buffer (get-text-property 0 'buffer handle))))
1105          ((and (listp handle)
1106                (stringp (car handle)))
1107           (mm-remove-parts (cdr handle)))
1108          (t
1109           (mm-remove-part handle)))))))
1110
1111 (defun mm-destroy-parts (handles)
1112   "Remove the displayed MIME parts represented by HANDLES."
1113   (if (and (listp handles)
1114            (bufferp (car handles)))
1115       (mm-destroy-part handles)
1116     (let (handle)
1117       (while (setq handle (pop handles))
1118         (cond
1119          ((stringp handle)
1120           (when (buffer-live-p (get-text-property 0 'buffer handle))
1121             (kill-buffer (get-text-property 0 'buffer handle))))
1122          ((and (listp handle)
1123                (stringp (car handle)))
1124           (mm-destroy-parts handle))
1125          (t
1126           (mm-destroy-part handle)))))))
1127
1128 (defun mm-remove-part (handle)
1129   "Remove the displayed MIME part represented by HANDLE."
1130   (when (listp handle)
1131     (let ((object (mm-handle-undisplayer handle)))
1132       (ignore-errors
1133         (cond
1134          ;; Internally displayed part.
1135          ((mm-annotationp object)
1136           (if (featurep 'xemacs)
1137               (delete-annotation object)))
1138          ((or (functionp object)
1139               (and (listp object)
1140                    (eq (car object) 'lambda)))
1141           (funcall object))
1142          ;; Externally displayed part.
1143          ((consp object)
1144           (condition-case ()
1145               (while (get-buffer-process (cdr object))
1146                 (interrupt-process (get-buffer-process (cdr object)))
1147                 (message "Waiting for external displayer to die...")
1148                 (sit-for 1))
1149             (quit)
1150             (error))
1151           (ignore-errors (and (cdr object) (kill-buffer (cdr object))))
1152           (message "Waiting for external displayer to die...done")
1153           (ignore-errors (delete-file (car object)))
1154           (ignore-errors (delete-directory (file-name-directory
1155                                             (car object)))))
1156          ((bufferp object)
1157           (when (buffer-live-p object)
1158             (kill-buffer object)))))
1159       (mm-handle-set-undisplayer handle nil))))
1160
1161 (defun mm-display-inline (handle)
1162   (let* ((type (mm-handle-media-type handle))
1163          (function (cadr (mm-assoc-string-match mm-inline-media-tests type))))
1164     (funcall function handle)
1165     (goto-char (point-min))))
1166
1167 (defun mm-assoc-string-match (alist type)
1168   (dolist (elem alist)
1169     (when (string-match (car elem) type)
1170       (return elem))))
1171
1172 (defun mm-automatic-display-p (handle)
1173   "Say whether the user wants HANDLE to be displayed automatically."
1174   (let ((methods mm-automatic-display)
1175         (type (mm-handle-media-type handle))
1176         method result)
1177     (while (setq method (pop methods))
1178       (when (and (not (mm-inline-override-p handle))
1179                  (string-match method type))
1180         (setq result t
1181               methods nil)))
1182     result))
1183
1184 (defun mm-inlinable-p (handle &optional type)
1185   "Say whether HANDLE can be displayed inline.
1186 TYPE is the mime-type of the object; it defaults to the one given
1187 in HANDLE."
1188   (unless type (setq type (mm-handle-media-type handle)))
1189   (let ((alist mm-inline-media-tests)
1190         test)
1191     (while alist
1192       (when (string-match (caar alist) type)
1193         (setq test (caddar alist)
1194               alist nil)
1195         (setq test (funcall test handle)))
1196       (pop alist))
1197     test))
1198
1199 (defun mm-inlined-p (handle)
1200   "Say whether the user wants HANDLE to be displayed inline."
1201   (let ((methods mm-inlined-types)
1202         (type (mm-handle-media-type handle))
1203         method result)
1204     (while (setq method (pop methods))
1205       (when (and (not (mm-inline-override-p handle))
1206                  (string-match method type))
1207         (setq result t
1208               methods nil)))
1209     result))
1210
1211 (defun mm-attachment-override-p (handle)
1212   "Say whether HANDLE should have attachment behavior overridden."
1213   (let ((types mm-attachment-override-types)
1214         (type (mm-handle-media-type handle))
1215         ty)
1216     (catch 'found
1217       (while (setq ty (pop types))
1218         (when (and (string-match ty type)
1219                    (mm-inlinable-p handle))
1220           (throw 'found t))))))
1221
1222 (defun mm-inline-override-p (handle)
1223   "Say whether HANDLE should have inline behavior overridden."
1224   (let ((types mm-inline-override-types)
1225         (type (mm-handle-media-type handle))
1226         ty)
1227     (catch 'found
1228       (while (setq ty (pop types))
1229         (when (string-match ty type)
1230           (throw 'found t))))))
1231
1232 (defun mm-automatic-external-display-p (type)
1233   "Return the user-defined method for TYPE."
1234   (let ((methods mm-automatic-external-display)
1235         method result)
1236     (while (setq method (pop methods))
1237       (when (string-match method type)
1238         (setq result t
1239               methods nil)))
1240     result))
1241
1242 (defun mm-destroy-part (handle)
1243   "Destroy the data structures connected to HANDLE."
1244   (when (listp handle)
1245     (mm-remove-part handle)
1246     (when (buffer-live-p (mm-handle-buffer handle))
1247       (kill-buffer (mm-handle-buffer handle)))))
1248
1249 (defun mm-handle-displayed-p (handle)
1250   "Say whether HANDLE is displayed or not."
1251   (mm-handle-undisplayer handle))
1252
1253 ;;;
1254 ;;; Functions for outputting parts
1255 ;;;
1256
1257 (defmacro mm-with-part (handle &rest forms)
1258   "Run FORMS in the temp buffer containing the contents of HANDLE."
1259   ;; The handle-buffer's content is a sequence of bytes, not a sequence of
1260   ;; chars, so the buffer should be unibyte.  It may happen that the
1261   ;; handle-buffer is multibyte for some reason, in which case now is a good
1262   ;; time to adjust it, since we know at this point that it should
1263   ;; be unibyte.
1264   `(let* ((handle ,handle))
1265      (when (and (mm-handle-buffer handle)
1266                 (buffer-name (mm-handle-buffer handle)))
1267        (with-temp-buffer
1268          (mm-disable-multibyte)
1269          (insert-buffer-substring (mm-handle-buffer handle))
1270          (mm-decode-content-transfer-encoding
1271           (mm-handle-encoding handle)
1272           (mm-handle-media-type handle))
1273          ,@forms))))
1274 (put 'mm-with-part 'lisp-indent-function 1)
1275 (put 'mm-with-part 'edebug-form-spec '(body))
1276
1277 (defun mm-get-part (handle &optional no-cache)
1278   "Return the contents of HANDLE as a string.
1279 If NO-CACHE is non-nil, cached contents of a message/external-body part
1280 are ignored."
1281   (if (and (not no-cache)
1282            (equal (mm-handle-media-type handle) "message/external-body"))
1283       (progn
1284         (unless (mm-handle-cache handle)
1285           (mm-extern-cache-contents handle))
1286         (with-current-buffer (mm-handle-buffer (mm-handle-cache handle))
1287           (buffer-string)))
1288     (mm-with-part handle
1289       (buffer-string))))
1290
1291 (defun mm-insert-part (handle &optional no-cache)
1292   "Insert the contents of HANDLE in the current buffer.
1293 If NO-CACHE is non-nil, cached contents of a message/external-body part
1294 are ignored."
1295   (let ((text (cond ((eq (mail-content-type-get (mm-handle-type handle)
1296                                                 'charset)
1297                          'gnus-decoded)
1298                      (with-current-buffer (mm-handle-buffer handle)
1299                        (buffer-string)))
1300                     ((mm-multibyte-p)
1301                      (mm-string-to-multibyte (mm-get-part handle no-cache)))
1302                     (t
1303                      (mm-get-part handle no-cache)))))
1304     (save-restriction
1305       (widen)
1306       (goto-char
1307        (prog1
1308            (point)
1309          (if (and (eq (get-char-property (max (point-min) (1- (point))) 'face)
1310                       'mm-uu-extract)
1311                   (eq (get-char-property 0 'face text) 'mm-uu-extract))
1312              ;; Separate the extracted parts that have the same faces.
1313              (insert "\n" text)
1314            (insert text)))))))
1315
1316 (defun mm-file-name-delete-whitespace (file-name)
1317   "Remove all whitespace characters from FILE-NAME."
1318   (while (string-match "\\s-+" file-name)
1319     (setq file-name (replace-match "" t t file-name)))
1320   file-name)
1321
1322 (defun mm-file-name-trim-whitespace (file-name)
1323   "Remove leading and trailing whitespace characters from FILE-NAME."
1324   (when (string-match "\\`\\s-+" file-name)
1325     (setq file-name (substring file-name (match-end 0))))
1326   (when (string-match "\\s-+\\'" file-name)
1327     (setq file-name (substring file-name 0 (match-beginning 0))))
1328   file-name)
1329
1330 (defun mm-file-name-collapse-whitespace (file-name)
1331   "Collapse multiple whitespace characters in FILE-NAME."
1332   (while (string-match "\\s-\\s-+" file-name)
1333     (setq file-name (replace-match " " t t file-name)))
1334   file-name)
1335
1336 (defun mm-file-name-replace-whitespace (file-name)
1337   "Replace whitespace characters in FILE-NAME with underscores.
1338 Set the option `mm-file-name-replace-whitespace' to any other
1339 string if you do not like underscores."
1340   (let ((s (or mm-file-name-replace-whitespace "_")))
1341     (while (string-match "\\s-" file-name)
1342       (setq file-name (replace-match s t t file-name))))
1343   file-name)
1344
1345 (defun mm-file-name-delete-control (filename)
1346   "Delete control characters from FILENAME."
1347   (gnus-replace-in-string filename "[\x00-\x1f\x7f]" ""))
1348
1349 (defun mm-file-name-delete-gotchas (filename)
1350   "Delete shell gotchas from FILENAME."
1351   (setq filename (gnus-replace-in-string filename "[<>|]" ""))
1352   (gnus-replace-in-string filename "^[.-]+" ""))
1353
1354 (defun mm-save-part (handle &optional prompt)
1355   "Write HANDLE to a file.
1356 PROMPT overrides the default one used to ask user for a file name."
1357   (let ((filename (or (mail-content-type-get
1358                        (mm-handle-disposition handle) 'filename)
1359                       (mail-content-type-get
1360                        (mm-handle-type handle) 'name)))
1361         file)
1362     (when filename
1363       (setq filename (gnus-map-function mm-file-name-rewrite-functions
1364                                         (file-name-nondirectory filename))))
1365     (while
1366         (progn
1367           (setq file
1368                 (read-file-name
1369                  (or prompt
1370                      (format "Save MIME part to (default %s): "
1371                              (or filename "")))
1372                  (or mm-default-directory default-directory)
1373                  (expand-file-name (or filename "")
1374                                    (or mm-default-directory default-directory))))
1375           (cond ((or (not file) (equal file ""))
1376                  (message "Please enter a file name")
1377                  t)
1378                 ((and (file-directory-p file)
1379                       (not filename))
1380                  (message "Please enter a non-directory file name")
1381                  t)
1382                 (t nil)))
1383       (sit-for 2)
1384       (discard-input))
1385     (if (file-directory-p file)
1386         (setq file (expand-file-name filename file))
1387       (setq file (expand-file-name
1388                   file (or mm-default-directory default-directory))))
1389     (setq mm-default-directory (file-name-directory file))
1390     (and (or (not (file-exists-p file))
1391              (yes-or-no-p (format "File %s already exists; overwrite? "
1392                                   file)))
1393          (progn
1394            (mm-save-part-to-file handle file)
1395            file))))
1396
1397 (defun mm-add-meta-html-tag (handle &optional charset force-charset)
1398   "Add meta html tag to specify CHARSET of HANDLE in the current buffer.
1399 CHARSET defaults to the one HANDLE specifies.  Existing meta tag that
1400 specifies charset will not be modified unless FORCE-CHARSET is non-nil.
1401 Return t if meta tag is added or replaced."
1402   (when (equal (mm-handle-media-type handle) "text/html")
1403     (when (or charset
1404               (setq charset (mail-content-type-get (mm-handle-type handle)
1405                                                    'charset)))
1406       (setq charset (format "\
1407 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\">" charset))
1408       (let ((case-fold-search t))
1409         (goto-char (point-min))
1410         (if (re-search-forward "\
1411 <meta\\s-+http-equiv=[\"']?content-type[\"']?\\s-+content=[\"']\
1412 text/\\(\\sw+\\)\\(?:\;\\s-*charset=\\([^\"'>]+\\)\\)?[^>]*>" nil t)
1413             (if (and (not force-charset)
1414                      (match-beginning 2)
1415                      (string-match "\\`html\\'" (match-string 1)))
1416                 ;; Don't modify existing meta tag.
1417                 nil
1418               ;; Replace it with the one specifying charset.
1419               (replace-match charset)
1420               t)
1421           (if (re-search-forward "<head>\\s-*" nil t)
1422               (insert charset "\n")
1423             (re-search-forward "<html\\(?:\\s-+[^>]+\\|\\s-*\\)>\\s-*" nil t)
1424             (insert "<head>\n" charset "\n</head>\n"))
1425           t)))))
1426
1427 (defun mm-save-part-to-file (handle file)
1428   (mm-with-unibyte-buffer
1429     (mm-insert-part handle)
1430     (mm-add-meta-html-tag handle)
1431     (let ((current-file-modes (default-file-modes)))
1432       (set-default-file-modes mm-attachment-file-modes)
1433       (unwind-protect
1434           ;; Don't re-compress .gz & al.  Arguably we should make
1435           ;; `file-name-handler-alist' nil, but that would chop
1436           ;; ange-ftp, which is reasonable to use here.
1437           (mm-write-region (point-min) (point-max) file nil nil nil 'binary t)
1438         (set-default-file-modes current-file-modes)))))
1439
1440 (defun mm-pipe-part (handle &optional cmd)
1441   "Pipe HANDLE to a process.
1442 Use CMD as the process."
1443   (let ((name (mail-content-type-get (mm-handle-type handle) 'name))
1444         (command (or cmd
1445                      (gnus-read-shell-command
1446                       "Shell command on MIME part: " mm-last-shell-command))))
1447     (mm-with-unibyte-buffer
1448       (mm-insert-part handle)
1449       (mm-add-meta-html-tag handle)
1450       (let ((coding-system-for-write 'binary))
1451         (shell-command-on-region (point-min) (point-max) command nil)))))
1452
1453 (autoload 'gnus-completing-read "gnus-util")
1454
1455 (defun mm-interactively-view-part (handle)
1456   "Display HANDLE using METHOD."
1457   (let* ((type (mm-handle-media-type handle))
1458          (methods
1459           (mapcar (lambda (i) (cdr (assoc 'viewer i)))
1460                   (mailcap-mime-info type 'all)))
1461          (method (let ((minibuffer-local-completion-map
1462                         mm-viewer-completion-map))
1463                    (completing-read "Viewer: " methods))))
1464     (when (string= method "")
1465       (error "No method given"))
1466     (if (string-match "^[^% \t]+$" method)
1467         (setq method (concat method " %s")))
1468     (mm-display-external handle method)))
1469
1470 (defun mm-preferred-alternative (handles &optional preferred)
1471   "Say which of HANDLES are preferred."
1472   (let ((prec (if preferred (list preferred)
1473                 (mm-preferred-alternative-precedence handles)))
1474         p h result type handle)
1475     (while (setq p (pop prec))
1476       (setq h handles)
1477       (while h
1478         (setq handle (car h))
1479         (setq type (mm-handle-media-type handle))
1480         (when (and (equal p type)
1481                    (mm-automatic-display-p handle)
1482                    (or (stringp (car handle))
1483                        (not (mm-handle-disposition handle))
1484                        (equal (car (mm-handle-disposition handle))
1485                               "inline")))
1486           (setq result handle
1487                 h nil
1488                 prec nil))
1489         (pop h)))
1490     result))
1491
1492 (defun mm-preferred-alternative-precedence (handles)
1493   "Return the precedence based on HANDLES and `mm-discouraged-alternatives'."
1494   (setq handles (reverse handles))
1495   (dolist (disc (reverse mm-discouraged-alternatives))
1496     (dolist (handle (copy-sequence handles))
1497       (when (string-match disc (mm-handle-media-type handle))
1498         (setq handles (nconc (delete handle handles) (list handle))))))
1499   ;; Remove empty parts.
1500   (dolist (handle (copy-sequence handles))
1501     (when (and (bufferp (mm-handle-buffer handle))
1502                (not (with-current-buffer (mm-handle-buffer handle)
1503                       (goto-char (point-min))
1504                       (re-search-forward "[^ \t\n]" nil t))))
1505       (setq handles (nconc (delete handle handles) (list handle)))))
1506   (mapcar #'mm-handle-media-type handles))
1507
1508 (defun mm-get-content-id (id)
1509   "Return the handle(s) referred to by ID."
1510   (cdr (assoc id mm-content-id-alist)))
1511
1512 (defconst mm-image-type-regexps
1513   '(("/\\*.*XPM.\\*/" . xpm)
1514     ("P[1-6]" . pbm)
1515     ("GIF8" . gif)
1516     ("\377\330" . jpeg)
1517     ("\211PNG\r\n" . png)
1518     ("#define" . xbm)
1519     ("\\(MM\0\\*\\)\\|\\(II\\*\0\\)" . tiff)
1520     ("%!PS" . postscript))
1521   "Alist of (REGEXP . IMAGE-TYPE) pairs used to auto-detect image types.
1522 When the first bytes of an image file match REGEXP, it is assumed to
1523 be of image type IMAGE-TYPE.")
1524
1525 ;; Steal from image.el. image-type-from-data suffers multi-line matching bug.
1526 (defun mm-image-type-from-buffer ()
1527   "Determine the image type from data in the current buffer.
1528 Value is a symbol specifying the image type or nil if type cannot
1529 be determined."
1530   (let ((types mm-image-type-regexps)
1531         type)
1532     (goto-char (point-min))
1533     (while (and types (null type))
1534       (let ((regexp (car (car types)))
1535             (image-type (cdr (car types))))
1536         (when (looking-at regexp)
1537           (setq type image-type))
1538         (setq types (cdr types))))
1539     type))
1540
1541 (defun mm-get-image (handle)
1542   "Return an image instance based on HANDLE."
1543   (let ((type (mm-handle-media-subtype handle))
1544         spec)
1545     ;; Allow some common translations.
1546     (setq type
1547           (cond
1548            ((equal type "x-pixmap")
1549             "xpm")
1550            ((equal type "x-xbitmap")
1551             "xbm")
1552            ((equal type "x-portable-bitmap")
1553             "pbm")
1554            (t type)))
1555     (or (mm-handle-cache handle)
1556         (mm-with-unibyte-buffer
1557           (mm-insert-part handle)
1558           (prog1
1559               (setq spec
1560                     (ignore-errors
1561                       ;; Avoid testing `make-glyph' since W3 may define
1562                       ;; a bogus version of it.
1563                       (if (fboundp 'create-image)
1564                           (create-image (buffer-string)
1565                                         (or (mm-image-type-from-buffer)
1566                                             (intern type))
1567                                         'data-p)
1568                         (mm-create-image-xemacs type))))
1569             (mm-handle-set-cache handle spec))))))
1570
1571 (defun mm-create-image-xemacs (type)
1572   (when (featurep 'xemacs)
1573     (cond
1574      ((equal type "xbm")
1575       ;; xbm images require special handling, since
1576       ;; the only way to create glyphs from these
1577       ;; (without a ton of work) is to write them
1578       ;; out to a file, and then create a file
1579       ;; specifier.
1580       (let ((file (mm-make-temp-file
1581                    (expand-file-name "emm" mm-tmp-directory)
1582                    nil ".xbm")))
1583         (unwind-protect
1584             (progn
1585               (write-region (point-min) (point-max) file)
1586               (make-glyph (list (cons 'x file))))
1587           (ignore-errors
1588             (delete-file file)))))
1589      (t
1590       (make-glyph
1591        (vector
1592         (or (mm-image-type-from-buffer)
1593             (intern type))
1594         :data (buffer-string)))))))
1595
1596 (declare-function image-size "image.c" (spec &optional pixels frame))
1597
1598 (defun mm-image-fit-p (handle)
1599   "Say whether the image in HANDLE will fit the current window."
1600   (let ((image (mm-get-image handle)))
1601     (or (not image)
1602         (if (featurep 'xemacs)
1603             ;; XEmacs's glyphs can actually tell us about their width, so
1604             ;; let's be nice and smart about them.
1605             (or mm-inline-large-images
1606                 (and (<= (glyph-width image) (window-pixel-width))
1607                      (<= (glyph-height image) (window-pixel-height))))
1608           (let* ((size (image-size image))
1609                  (w (car size))
1610                  (h (cdr size)))
1611             (or mm-inline-large-images
1612                 (and (<= h (1- (window-height))) ; Don't include mode line.
1613                      (<= w (window-width)))))))))
1614
1615 (defun mm-valid-image-format-p (format)
1616   "Say whether FORMAT can be displayed natively by Emacs."
1617   (cond
1618    ;; Handle XEmacs
1619    ((fboundp 'valid-image-instantiator-format-p)
1620     (valid-image-instantiator-format-p format))
1621    ;; Handle Emacs
1622    ((fboundp 'image-type-available-p)
1623     (and (display-graphic-p)
1624          (image-type-available-p format)))
1625    ;; Nobody else can do images yet.
1626    (t
1627     nil)))
1628
1629 (defun mm-valid-and-fit-image-p (format handle)
1630   "Say whether FORMAT can be displayed natively and HANDLE fits the window."
1631   (and (mm-valid-image-format-p format)
1632        (mm-image-fit-p handle)))
1633
1634 (defun mm-find-part-by-type (handles type &optional notp recursive)
1635   "Search in HANDLES for part with TYPE.
1636 If NOTP, returns first non-matching part.
1637 If RECURSIVE, search recursively."
1638   (let (handle)
1639     (while handles
1640       (if (and recursive (stringp (caar handles)))
1641           (if (setq handle (mm-find-part-by-type (cdar handles) type
1642                                                  notp recursive))
1643               (setq handles nil))
1644         (if (if notp
1645                 (not (equal (mm-handle-media-type (car handles)) type))
1646               (equal (mm-handle-media-type (car handles)) type))
1647             (setq handle (car handles)
1648                   handles nil)))
1649       (setq handles (cdr handles)))
1650     handle))
1651
1652 (defun mm-find-raw-part-by-type (ctl type &optional notp)
1653   (goto-char (point-min))
1654   (let* ((boundary (concat "--" (mm-handle-multipart-ctl-parameter ctl
1655                                                                    'boundary)))
1656          (close-delimiter (concat "^" (regexp-quote boundary) "--[ \t]*$"))
1657          start
1658          (end (save-excursion
1659                 (goto-char (point-max))
1660                 (if (re-search-backward close-delimiter nil t)
1661                     (match-beginning 0)
1662                   (point-max))))
1663          result)
1664     (setq boundary (concat "^" (regexp-quote boundary) "[ \t]*$"))
1665     (while (and (not result)
1666                 (re-search-forward boundary end t))
1667       (goto-char (match-beginning 0))
1668       (when start
1669         (save-excursion
1670           (save-restriction
1671             (narrow-to-region start (1- (point)))
1672             (when (let* ((ct (mail-fetch-field "content-type"))
1673                          (ctl (and ct (mail-header-parse-content-type ct))))
1674                     (if notp
1675                         (not (equal (car ctl) type))
1676                       (equal (car ctl) type)))
1677               (setq result (buffer-string))))))
1678       (forward-line 1)
1679       (setq start (point)))
1680     (when (and (not result) start)
1681       (save-excursion
1682         (save-restriction
1683           (narrow-to-region start end)
1684           (when (let* ((ct (mail-fetch-field "content-type"))
1685                        (ctl (and ct (mail-header-parse-content-type ct))))
1686                   (if notp
1687                       (not (equal (car ctl) type))
1688                     (equal (car ctl) type)))
1689             (setq result (buffer-string))))))
1690     result))
1691
1692 (defvar mm-security-handle nil)
1693
1694 (defsubst mm-set-handle-multipart-parameter (handle parameter value)
1695   ;; HANDLE could be a CTL.
1696   (when handle
1697     (put-text-property 0 (length (car handle)) parameter value
1698                        (car handle))))
1699
1700 (autoload 'mm-view-pkcs7 "mm-view")
1701
1702 (defun mm-possibly-verify-or-decrypt (parts ctl &optional from)
1703   (let ((type (car ctl))
1704         (subtype (cadr (split-string (car ctl) "/")))
1705         (mm-security-handle ctl) ;; (car CTL) is the type.
1706         protocol func functest)
1707     (cond
1708      ((or (equal type "application/x-pkcs7-mime")
1709           (equal type "application/pkcs7-mime"))
1710       (with-temp-buffer
1711         (when (and (cond
1712                     ((eq mm-decrypt-option 'never) nil)
1713                     ((eq mm-decrypt-option 'always) t)
1714                     ((eq mm-decrypt-option 'known) t)
1715                     (t (y-or-n-p
1716                         (format "Decrypt (S/MIME) part? "))))
1717                    (mm-view-pkcs7 parts from))
1718           (setq parts (mm-dissect-buffer t)))))
1719      ((equal subtype "signed")
1720       (unless (and (setq protocol
1721                          (mm-handle-multipart-ctl-parameter ctl 'protocol))
1722                    (not (equal protocol "multipart/mixed")))
1723         ;; The message is broken or draft-ietf-openpgp-multsig-01.
1724         (let ((protocols mm-verify-function-alist))
1725           (while protocols
1726             (if (and (or (not (setq functest (nth 3 (car protocols))))
1727                          (funcall functest parts ctl))
1728                      (mm-find-part-by-type parts (caar protocols) nil t))
1729                 (setq protocol (caar protocols)
1730                       protocols nil)
1731               (setq protocols (cdr protocols))))))
1732       (setq func (nth 1 (assoc protocol mm-verify-function-alist)))
1733       (when (cond
1734              ((eq mm-verify-option 'never) nil)
1735              ((eq mm-verify-option 'always) t)
1736              ((eq mm-verify-option 'known)
1737               (and func
1738                    (or (not (setq functest
1739                                   (nth 3 (assoc protocol
1740                                                 mm-verify-function-alist))))
1741                        (funcall functest parts ctl))))
1742              (t
1743               (y-or-n-p
1744                (format "Verify signed (%s) part? "
1745                        (or (nth 2 (assoc protocol mm-verify-function-alist))
1746                            (format "protocol=%s" protocol))))))
1747         (save-excursion
1748           (if func
1749               (setq parts (funcall func parts ctl))
1750             (mm-set-handle-multipart-parameter
1751              mm-security-handle 'gnus-details
1752              (format "Unknown sign protocol (%s)" protocol))))))
1753      ((equal subtype "encrypted")
1754       (unless (setq protocol
1755                     (mm-handle-multipart-ctl-parameter ctl 'protocol))
1756         ;; The message is broken.
1757         (let ((parts parts))
1758           (while parts
1759             (if (assoc (mm-handle-media-type (car parts))
1760                        mm-decrypt-function-alist)
1761                 (setq protocol (mm-handle-media-type (car parts))
1762                       parts nil)
1763               (setq parts (cdr parts))))))
1764       (setq func (nth 1 (assoc protocol mm-decrypt-function-alist)))
1765       (when (cond
1766              ((eq mm-decrypt-option 'never) nil)
1767              ((eq mm-decrypt-option 'always) t)
1768              ((eq mm-decrypt-option 'known)
1769               (and func
1770                    (or (not (setq functest
1771                                   (nth 3 (assoc protocol
1772                                                 mm-decrypt-function-alist))))
1773                        (funcall functest parts ctl))))
1774              (t
1775               (y-or-n-p
1776                (format "Decrypt (%s) part? "
1777                        (or (nth 2 (assoc protocol mm-decrypt-function-alist))
1778                            (format "protocol=%s" protocol))))))
1779         (save-excursion
1780           (if func
1781               (setq parts (funcall func parts ctl))
1782             (mm-set-handle-multipart-parameter
1783              mm-security-handle 'gnus-details
1784              (format "Unknown encrypt protocol (%s)" protocol))))))
1785      (t nil))
1786     parts))
1787
1788 (defun mm-multiple-handles (handles)
1789   (and (listp handles)
1790        (> (length handles) 1)
1791        (or (listp (car handles))
1792            (stringp (car handles)))))
1793
1794 (defun mm-complicated-handles (handles)
1795   (and (listp (car handles))
1796        (> (length handles) 1)))
1797
1798 (defun mm-merge-handles (handles1 handles2)
1799   (append
1800    (if (listp (car handles1))
1801        handles1
1802      (list handles1))
1803    (if (listp (car handles2))
1804        handles2
1805      (list handles2))))
1806
1807 (defun mm-readable-p (handle)
1808   "Say whether the content of HANDLE is readable."
1809   (and (< (with-current-buffer (mm-handle-buffer handle)
1810             (buffer-size)) 10000)
1811        (mm-with-unibyte-buffer
1812          (mm-insert-part handle)
1813          (and (eq (mm-body-7-or-8) '7bit)
1814               (not (mm-long-lines-p 76))))))
1815
1816 (declare-function libxml-parse-html-region "xml.c"
1817                   (start end &optional base-url))
1818 (declare-function shr-insert-document "shr" (dom))
1819 (defvar shr-blocked-images)
1820 (defvar shr-use-fonts)
1821 (defvar gnus-inhibit-images)
1822 (autoload 'gnus-blocked-images "gnus-art")
1823
1824 (defun mm-shr (handle)
1825   ;; Require since we bind its variables.
1826   (require 'shr)
1827   (let ((article-buffer (current-buffer))
1828         (shr-width (if (and (boundp 'shr-use-fonts)
1829                             shr-use-fonts)
1830                        nil
1831                      fill-column))
1832         (shr-content-function (lambda (id)
1833                                 (let ((handle (mm-get-content-id id)))
1834                                   (when handle
1835                                     (mm-with-part handle
1836                                       (buffer-string))))))
1837         shr-inhibit-images shr-blocked-images charset char)
1838     (if (and (boundp 'gnus-summary-buffer)
1839              (bufferp gnus-summary-buffer)
1840              (buffer-name gnus-summary-buffer))
1841         (with-current-buffer gnus-summary-buffer
1842           (setq shr-inhibit-images gnus-inhibit-images
1843                 shr-blocked-images (gnus-blocked-images)))
1844       (setq shr-inhibit-images gnus-inhibit-images
1845             shr-blocked-images (gnus-blocked-images)))
1846     (unless handle
1847       (setq handle (mm-dissect-buffer t)))
1848     (setq charset (mail-content-type-get (mm-handle-type handle) 'charset))
1849     (save-restriction
1850       (narrow-to-region (point) (point))
1851       (shr-insert-document
1852        (mm-with-part handle
1853          (insert (prog1
1854                      (if (and charset
1855                               (setq charset
1856                                     (mm-charset-to-coding-system charset
1857                                                                  nil t))
1858                               (not (eq charset 'ascii)))
1859                          (mm-decode-coding-string (buffer-string) charset)
1860                        (mm-string-as-multibyte (buffer-string)))
1861                    (erase-buffer)
1862                    (mm-enable-multibyte)))
1863          (goto-char (point-min))
1864          (setq case-fold-search t)
1865          (while (re-search-forward
1866                  "&#\\(?:x\\([89][0-9a-f]\\)\\|\\(1[2-5][0-9]\\)\\);" nil t)
1867            (when (setq char
1868                        (cdr (assq (if (match-beginning 1)
1869                                       (string-to-number (match-string 1) 16)
1870                                     (string-to-number (match-string 2)))
1871                                   mm-extra-numeric-entities)))
1872              (replace-match (char-to-string char))))
1873          ;; Remove "soft hyphens".
1874          (goto-char (point-min))
1875          (while (search-forward "­" nil t)
1876            (replace-match "" t t))
1877          (libxml-parse-html-region (point-min) (point-max))))
1878       (unless (bobp)
1879         (insert "\n"))
1880       (mm-convert-shr-links)
1881       (mm-handle-set-undisplayer
1882        handle
1883        `(lambda ()
1884           (let ((inhibit-read-only t))
1885             (delete-region ,(copy-marker (point-min) t)
1886                            ,(point-max-marker))))))))
1887
1888 (defvar shr-map)
1889
1890 (autoload 'widget-convert-button "wid-edit")
1891
1892 (defun mm-convert-shr-links ()
1893   (let ((start (point-min))
1894         end)
1895     (while (and start
1896                 (< start (point-max)))
1897       (when (setq start (text-property-not-all start (point-max) 'shr-url nil))
1898         (setq end (next-single-property-change start 'shr-url nil (point-max)))
1899         (widget-convert-button
1900          'url-link start end
1901          :help-echo (get-text-property start 'help-echo)
1902          :keymap shr-map
1903          (get-text-property start 'shr-url))
1904         (put-text-property start end 'local-map nil)
1905         (dolist (overlay (gnus-overlays-at start))
1906           (gnus-overlay-put overlay 'face nil))
1907         (setq start end)))))
1908
1909 (defun mm-handle-filename (handle)
1910   "Return filename of HANDLE if any."
1911   (or (mail-content-type-get (mm-handle-type handle)
1912                              'name)
1913       (mail-content-type-get (mm-handle-disposition handle)
1914                              'filename)))
1915
1916 (provide 'mm-decode)
1917
1918 ;; Local Variables:
1919 ;; coding: utf-8
1920 ;; End:
1921
1922 ;;; mm-decode.el ends here