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