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