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