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