84eb07b9e35f6b8006d103d3ec6fc3f785dbfca3
[elisp/gnus.git-] / lisp / mm-decode.el
1 ;;; mm-decode.el --- Functions for decoding MIME things
2 ;; Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
5 ;;      MORIOKA Tomohiko <morioka@jaist.ac.jp>
6 ;; This file is part of GNU Emacs.
7
8 ;; GNU Emacs is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation; either version 2, or (at your option)
11 ;; any later version.
12
13 ;; GNU Emacs is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 ;; GNU General Public License for more details.
17
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
20 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 ;; Boston, MA 02111-1307, USA.
22
23 ;;; Commentary:
24
25 ;; Jaap-Henk Hoepman (jhh@xs4all.nl):
26 ;;
27 ;; Added support for delayed destroy of external MIME viewers. All external
28 ;; viewers for mime types in mm-keep-viewer-alive-types will remain active
29 ;; after switching articles or groups, and will only be removed when exiting
30 ;; gnus.
31 ;;
32
33 ;;; Code:
34
35 (require 'mail-parse)
36 (require 'gnus-mailcap)
37 (require 'mm-bodies)
38 (eval-when-compile (require 'cl)
39                    (require 'term))
40
41 (eval-and-compile
42   (autoload 'mm-inline-partial "mm-partial")
43   (autoload 'mm-inline-external-body "mm-extern")
44   (autoload 'mm-insert-inline "mm-view"))
45
46 (add-hook 'gnus-exit-gnus-hook 'mm-destroy-postponed-undisplay-list)
47
48 (defgroup mime-display ()
49   "Display of MIME in mail and news articles."
50   :link '(custom-manual "(emacs-mime)Customization")
51   :version "21.1"
52   :group 'mail
53   :group 'news
54   :group 'multimedia)
55
56 (defgroup mime-security ()
57   "MIME security in mail and news articles."
58   :link '(custom-manual "(emacs-mime)Customization")
59   :group 'mail
60   :group 'news
61   :group 'multimedia)
62
63 ;;; Convenience macros.
64
65 (defmacro mm-handle-buffer (handle)
66   `(nth 0 ,handle))
67 (defmacro mm-handle-type (handle)
68   `(nth 1 ,handle))
69 (defsubst mm-handle-media-type (handle)
70   (if (stringp (car handle))
71       (car handle)
72     (car (mm-handle-type handle))))
73 (defsubst mm-handle-media-supertype (handle)
74   (car (split-string (mm-handle-media-type handle) "/")))
75 (defsubst mm-handle-media-subtype (handle)
76   (cadr (split-string (mm-handle-media-type handle) "/")))
77 (defmacro mm-handle-encoding (handle)
78   `(nth 2 ,handle))
79 (defmacro mm-handle-undisplayer (handle)
80   `(nth 3 ,handle))
81 (defmacro mm-handle-set-undisplayer (handle function)
82   `(setcar (nthcdr 3 ,handle) ,function))
83 (defmacro mm-handle-disposition (handle)
84   `(nth 4 ,handle))
85 (defmacro mm-handle-description (handle)
86   `(nth 5 ,handle))
87 (defmacro mm-handle-cache (handle)
88   `(nth 6 ,handle))
89 (defmacro mm-handle-set-cache (handle contents)
90   `(setcar (nthcdr 6 ,handle) ,contents))
91 (defmacro mm-handle-id (handle)
92   `(nth 7 ,handle))
93 (defmacro mm-handle-multipart-original-buffer (handle)
94   `(get-text-property 0 'buffer (car ,handle)))
95 (defmacro mm-handle-multipart-from (handle)
96   `(get-text-property 0 'from (car ,handle)))
97 (defmacro mm-handle-multipart-ctl-parameter (handle parameter)
98   `(get-text-property 0 ,parameter (car ,handle)))
99
100 (defmacro mm-make-handle (&optional buffer type encoding undisplayer
101                                     disposition description cache
102                                     id)
103   `(list ,buffer ,type ,encoding ,undisplayer
104          ,disposition ,description ,cache ,id))
105
106 (defcustom mm-inline-media-tests
107   '(("image/jpeg"
108      mm-inline-image
109      (lambda (handle)
110        (mm-valid-and-fit-image-p 'jpeg handle)))
111     ("image/png"
112      mm-inline-image
113      (lambda (handle)
114        (mm-valid-and-fit-image-p 'png handle)))
115     ("image/gif"
116      mm-inline-image
117      (lambda (handle)
118        (mm-valid-and-fit-image-p 'gif handle)))
119     ("image/tiff"
120      mm-inline-image
121      (lambda (handle)
122        (mm-valid-and-fit-image-p 'tiff handle)) )
123     ("image/xbm"
124      mm-inline-image
125      (lambda (handle)
126        (mm-valid-and-fit-image-p 'xbm handle)))
127     ("image/x-xbitmap"
128      mm-inline-image
129      (lambda (handle)
130        (mm-valid-and-fit-image-p 'xbm handle)))
131     ("image/xpm"
132      mm-inline-image
133      (lambda (handle)
134        (mm-valid-and-fit-image-p 'xpm handle)))
135     ("image/x-pixmap"
136      mm-inline-image
137      (lambda (handle)
138        (mm-valid-and-fit-image-p 'xpm handle)))
139     ("image/bmp"
140      mm-inline-image
141      (lambda (handle)
142        (mm-valid-and-fit-image-p 'bmp handle)))
143     ("image/x-portable-bitmap"
144      mm-inline-image
145      (lambda (handle)
146        (mm-valid-and-fit-image-p 'pbm handle)))
147     ("text/plain" mm-inline-text identity)
148     ("text/enriched" mm-inline-text identity)
149     ("text/richtext" mm-inline-text identity)
150     ("text/x-patch" mm-display-patch-inline
151      (lambda (handle)
152        (locate-library "diff-mode")))
153     ("application/emacs-lisp" mm-display-elisp-inline identity)
154     ("text/html"
155      mm-inline-text
156      (lambda (handle)
157        (locate-library "w3")))
158     ("text/x-vcard"
159      mm-inline-text
160      (lambda (handle)
161        (or (featurep 'vcard)
162            (locate-library "vcard"))))
163     ("message/delivery-status" mm-inline-text identity)
164     ("message/rfc822" mm-inline-message identity)
165     ("message/partial" mm-inline-partial identity)
166     ("message/external-body" mm-inline-external-body identity)
167     ("text/.*" mm-inline-text identity)
168     ("audio/wav" mm-inline-audio
169      (lambda (handle)
170        (and (or (featurep 'nas-sound) (featurep 'native-sound))
171             (device-sound-enabled-p))))
172     ("audio/au"
173      mm-inline-audio
174      (lambda (handle)
175        (and (or (featurep 'nas-sound) (featurep 'native-sound))
176             (device-sound-enabled-p))))
177     ("application/pgp-signature" ignore identity)
178     ("application/x-pkcs7-signature" ignore identity)
179     ("application/pkcs7-signature" ignore identity)
180     ("multipart/alternative" ignore identity)
181     ("multipart/mixed" ignore identity)
182     ("multipart/related" ignore identity)
183     ;; Default to displaying as text
184     (".*" mm-inline-text identity))
185   "Alist of media types/tests saying whether types can be displayed inline."
186   :type '(repeat (list (string :tag "MIME type")
187                        (function :tag "Display function")
188                        (function :tag "Display test")))
189   :group 'mime-display)
190
191 (defcustom mm-inlined-types
192   '("image/.*" "text/.*" "message/delivery-status" "message/rfc822"
193     "message/partial" "message/external-body" "application/emacs-lisp"
194     "application/pgp-signature" "application/x-pkcs7-signature"
195     "application/pkcs7-signature")
196   "List of media types that are to be displayed inline.
197 See also `mm-inline-media-tests', which says how to display a media
198 type inline."
199   :type '(repeat string)
200   :group 'mime-display)
201
202 (defcustom mm-keep-viewer-alive-types
203   '("application/postscript" "application/msword" "application/vnd.ms-excel"
204     "application/pdf" "application/x-dvi")
205   "List of media types for which the external viewer will not be killed
206 when selecting a different article."
207   :type '(repeat string)
208   :group 'mime-display)
209
210 (defcustom mm-automatic-display
211   '("text/plain" "text/enriched" "text/richtext" "text/html"
212     "text/x-vcard" "image/.*" "message/delivery-status" "multipart/.*"
213     "message/rfc822" "text/x-patch" "application/pgp-signature"
214     "application/emacs-lisp" "application/x-pkcs7-signature"
215     "application/pkcs7-signature")
216   "A list of MIME types to be displayed automatically."
217   :type '(repeat string)
218   :group 'mime-display)
219
220 (defcustom mm-attachment-override-types '("text/x-vcard")
221   "Types to have \"attachment\" ignored if they can be displayed inline."
222   :type '(repeat string)
223   :group 'mime-display)
224
225 (defcustom mm-inline-override-types nil
226   "Types to be treated as attachments even if they can be displayed inline."
227   :type '(repeat string)
228   :group 'mime-display)
229
230 (defcustom mm-automatic-external-display nil
231   "List of MIME type regexps that will be displayed externally automatically."
232   :type '(repeat string)
233   :group 'mime-display)
234
235 (defcustom mm-discouraged-alternatives nil
236   "List of MIME types that are discouraged when viewing multipart/alternative.
237 Viewing agents are supposed to view the last possible part of a message,
238 as that is supposed to be the richest.  However, users may prefer other
239 types instead, and this list says what types are most unwanted.  If,
240 for instance, text/html parts are very unwanted, and text/richtext are
241 somewhat unwanted, then the value of this variable should be set
242 to:
243
244  (\"text/html\" \"text/richtext\")"
245   :type '(repeat string)
246   :group 'mime-display)
247
248 (defcustom mm-tmp-directory
249   (cond ((fboundp 'temp-directory) (temp-directory))
250         ((boundp 'temporary-file-directory) temporary-file-directory)
251         ("/tmp/"))
252   "Where mm will store its temporary files."
253   :type 'directory
254   :group 'mime-display)
255
256 (defcustom mm-inline-large-images nil
257   "If non-nil, then all images fit in the buffer."
258   :type 'boolean
259   :group 'mime-display)
260
261 (defvar mm-file-name-rewrite-functions nil
262   "*List of functions used for rewriting file names of MIME parts.
263 Each function takes a file name as input and returns a file name.
264
265 Ready-made functions include
266 `mm-file-name-delete-whitespace',
267 `mm-file-name-trim-whitespace',
268 `mm-file-name-collapse-whitespace',
269 `mm-file-name-replace-whitespace',
270 `capitalize', `downcase', `upcase', and
271 `upcase-initials'.")
272
273 (defvar mm-file-name-replace-whitespace nil
274   "String used for replacing whitespace characters; default is `\"_\"'.")
275
276 (defcustom mm-default-directory nil
277   "The default directory where mm will save files.
278 If not set, `default-directory' will be used."
279   :type 'directory
280   :group 'mime-display)
281
282 (defcustom mm-external-terminal-program "xterm"
283   "The program to start an external terminal."
284   :type 'string
285   :group 'mime-display)
286
287 ;;; Internal variables.
288
289 (defvar mm-dissection-list nil)
290 (defvar mm-last-shell-command "")
291 (defvar mm-content-id-alist nil)
292 (defvar mm-postponed-undisplay-list nil)
293
294 ;; According to RFC2046, in particular, in a digest, the default
295 ;; Content-Type value for a body part is changed from "text/plain" to
296 ;; "message/rfc822".
297 (defvar mm-dissect-default-type "text/plain")
298
299 (autoload 'mml2015-verify "mml2015")
300 (autoload 'mml2015-verify-test "mml2015")
301 (autoload 'mml-smime-verify "mml-smime")
302 (autoload 'mml-smime-verify-test "mml-smime")
303
304 (defvar mm-verify-function-alist
305   '(("application/pgp-signature" mml2015-verify "PGP" mml2015-verify-test)
306     ("application/x-gnus-pgp-signature" mm-uu-pgp-signed-extract-1 "PGP"
307      mm-uu-pgp-signed-test)
308     ("application/pkcs7-signature" mml-smime-verify "S/MIME"
309      mml-smime-verify-test)
310     ("application/x-pkcs7-signature" mml-smime-verify "S/MIME"
311      mml-smime-verify-test)))
312
313 (defcustom mm-verify-option 'never
314   "Option of verifying signed parts.
315 `never', not verify; `always', always verify;
316 `known', only verify known protocols. Otherwise, ask user."
317   :type '(choice (item always)
318                  (item never)
319                  (item :tag "only known protocols" known)
320                  (item :tag "ask" nil))
321   :group 'mime-security)
322
323 (autoload 'mml2015-decrypt "mml2015")
324 (autoload 'mml2015-decrypt-test "mml2015")
325
326 (defvar mm-decrypt-function-alist
327   '(("application/pgp-encrypted" mml2015-decrypt "PGP" mml2015-decrypt-test)
328     ("application/x-gnus-pgp-encrypted" mm-uu-pgp-encrypted-extract-1 "PGP"
329      mm-uu-pgp-encrypted-test)))
330
331 (defcustom mm-decrypt-option nil
332   "Option of decrypting encrypted parts.
333 `never', not decrypt; `always', always decrypt;
334 `known', only decrypt known protocols. Otherwise, ask user."
335   :type '(choice (item always)
336                  (item never)
337                  (item :tag "only known protocols" known)
338                  (item :tag "ask" nil))
339   :group 'mime-security)
340
341 (defvar mm-viewer-completion-map
342   (let ((map (make-sparse-keymap 'mm-viewer-completion-map)))
343     (set-keymap-parent map minibuffer-local-completion-map)
344     map)
345   "Keymap for input viewer with completion.")
346
347 ;; Should we bind other key to minibuffer-complete-word?
348 (define-key mm-viewer-completion-map " " 'self-insert-command)
349
350 (defvar mm-viewer-completion-map
351   (let ((map (make-sparse-keymap 'mm-viewer-completion-map)))
352     (set-keymap-parent map minibuffer-local-completion-map)
353     map)
354   "Keymap for input viewer with completion.")
355
356 ;; Should we bind other key to minibuffer-complete-word?
357 (define-key mm-viewer-completion-map " " 'self-insert-command)
358
359 ;;; The functions.
360
361 (defun mm-alist-to-plist (alist)
362   "Convert association list ALIST into the equivalent property-list form.
363 The plist is returned.  This converts from
364
365 \((a . 1) (b . 2) (c . 3))
366
367 into
368
369 \(a 1 b 2 c 3)
370
371 The original alist is not modified.  See also `destructive-alist-to-plist'."
372   (let (plist)
373     (while alist
374       (let ((el (car alist)))
375         (setq plist (cons (cdr el) (cons (car el) plist))))
376       (setq alist (cdr alist)))
377     (nreverse plist)))
378
379 (defun mm-keep-viewer-alive-p (handle)
380   "Say whether external viewer for HANDLE should stay alive."
381   (let ((types mm-keep-viewer-alive-types)
382         (type (mm-handle-media-type handle))
383         ty)
384     (catch 'found
385       (while (setq ty (pop types))
386         (when (string-match ty type)
387           (throw 'found t))))))
388
389 (defun mm-handle-set-external-undisplayer (handle function)
390   "Set the undisplayer for this handle; postpone undisplaying of viewers
391 for types in mm-keep-viewer-alive-types."
392   (if (mm-keep-viewer-alive-p handle)
393       (let ((new-handle (copy-sequence handle)))
394         (mm-handle-set-undisplayer new-handle function)
395         (mm-handle-set-undisplayer handle nil)
396         (push new-handle mm-postponed-undisplay-list))
397     (mm-handle-set-undisplayer handle function)))
398
399 (defun mm-destroy-postponed-undisplay-list ()
400   (message "Destroying external MIME viewers")
401   (mm-destroy-parts mm-postponed-undisplay-list))
402
403 (defun mm-dissect-buffer (&optional no-strict-mime)
404   "Dissect the current buffer and return a list of MIME handles."
405   (save-excursion
406     (let (ct ctl type subtype cte cd description id result from)
407       (save-restriction
408         (mail-narrow-to-head)
409         (when (or no-strict-mime
410                   (mail-fetch-field "mime-version"))
411           (setq ct (mail-fetch-field "content-type")
412                 ctl (ignore-errors (mail-header-parse-content-type ct))
413                 cte (mail-fetch-field "content-transfer-encoding")
414                 cd (mail-fetch-field "content-disposition")
415                 description (mail-fetch-field "content-description")
416                 from (mail-fetch-field "from")
417                 id (mail-fetch-field "content-id"))
418           ;; FIXME: In some circumstances, this code is running within
419           ;; an unibyte macro.  mail-extract-address-components
420           ;; creates unibyte buffers. This `if', though not a perfect
421           ;; solution, avoids most of them.
422           (if from
423               (setq from (cadr (mail-extract-address-components from))))))
424       (when cte
425         (setq cte (mail-header-strip cte)))
426       (if (or (not ctl)
427               (not (string-match "/" (car ctl))))
428           (mm-dissect-singlepart
429            (list mm-dissect-default-type)
430            (and cte (intern (downcase (mail-header-remove-whitespace
431                                        (mail-header-remove-comments
432                                         cte)))))
433            no-strict-mime
434            (and cd (ignore-errors (mail-header-parse-content-disposition cd)))
435            description)
436         (setq type (split-string (car ctl) "/"))
437         (setq subtype (cadr type)
438               type (pop type))
439         (setq
440          result
441          (cond
442           ((equal type "multipart")
443            (let ((mm-dissect-default-type (if (equal subtype "digest")
444                                               "message/rfc822"
445                                             "text/plain")))
446              (add-text-properties 0 (length (car ctl))
447                                   (mm-alist-to-plist (cdr ctl)) (car ctl))
448
449              ;; what really needs to be done here is a way to link a
450              ;; MIME handle back to it's parent MIME handle (in a multilevel
451              ;; MIME article).  That would probably require changing
452              ;; the mm-handle API so we simply store the multipart buffert
453              ;; name as a text property of the "multipart/whatever" string.
454              (add-text-properties 0 (length (car ctl))
455                                   (list 'buffer (mm-copy-to-buffer))
456                                   (car ctl))
457              (add-text-properties 0 (length (car ctl))
458                                   (list 'from from)
459                                   (car ctl))
460              (cons (car ctl) (mm-dissect-multipart ctl))))
461           (t
462            (mm-dissect-singlepart
463             ctl
464             (and cte (intern (downcase (mail-header-remove-whitespace
465                                         (mail-header-remove-comments
466                                          cte)))))
467             no-strict-mime
468             (and cd (ignore-errors (mail-header-parse-content-disposition cd)))
469             description id))))
470         (when id
471           (when (string-match " *<\\(.*\\)> *" id)
472             (setq id (match-string 1 id)))
473           (push (cons id result) mm-content-id-alist))
474         result))))
475
476 (defun mm-dissect-singlepart (ctl cte &optional force cdl description id)
477   (when (or force
478             (if (equal "text/plain" (car ctl))
479                 (assoc 'format ctl)
480               t))
481     (let ((res (mm-make-handle
482                 (mm-copy-to-buffer) ctl cte nil cdl description nil id)))
483       (push (car res) mm-dissection-list)
484       res)))
485
486 (defun mm-remove-all-parts ()
487   "Remove all MIME handles."
488   (interactive)
489   (mapcar 'mm-remove-part mm-dissection-list)
490   (setq mm-dissection-list nil))
491
492 (defun mm-dissect-multipart (ctl)
493   (goto-char (point-min))
494   (let* ((boundary (concat "\n--" (mail-content-type-get ctl 'boundary)))
495          (close-delimiter (concat (regexp-quote boundary) "--[ \t]*$"))
496          start parts
497          (end (save-excursion
498                 (goto-char (point-max))
499                 (if (re-search-backward close-delimiter nil t)
500                     (match-beginning 0)
501                   (point-max)))))
502     (setq boundary (concat (regexp-quote boundary) "[ \t]*$"))
503     (while (and (< (point) end) (re-search-forward boundary end t))
504       (goto-char (match-beginning 0))
505       (when start
506         (save-excursion
507           (save-restriction
508             (narrow-to-region start (point))
509             (setq parts (nconc (list (mm-dissect-buffer t)) parts)))))
510       (forward-line 2)
511       (setq start (point)))
512     (when (and start (< start end))
513       (save-excursion
514         (save-restriction
515           (narrow-to-region start end)
516           (setq parts (nconc (list (mm-dissect-buffer t)) parts)))))
517     (mm-possibly-verify-or-decrypt (nreverse parts) ctl)))
518
519 (defun mm-copy-to-buffer ()
520   "Copy the contents of the current buffer to a fresh buffer."
521   (save-excursion
522     (let ((flag enable-multibyte-characters)
523           (new-buffer (generate-new-buffer " *mm*")))
524       (goto-char (point-min))
525       (search-forward-regexp "^\n" nil t)
526       (save-restriction
527         (narrow-to-region (point) (point-max))
528         (when flag
529           (set-buffer-multibyte nil))
530         (copy-to-buffer new-buffer (point-min) (point-max))
531         (when flag
532           (set-buffer-multibyte t)))
533       new-buffer)))
534
535 (defun mm-display-parts (handle &optional no-default)
536   (if (stringp (car handle))
537       (mapcar 'mm-display-parts (cdr handle))
538     (if (bufferp (car handle))
539         (save-restriction
540           (narrow-to-region (point) (point))
541           (mm-display-part handle)
542           (goto-char (point-max)))
543       (mapcar 'mm-display-parts handle))))
544
545 (defun mm-display-part (handle &optional no-default)
546   "Display the MIME part represented by HANDLE.
547 Returns nil if the part is removed; inline if displayed inline;
548 external if displayed external."
549   (save-excursion
550     (mailcap-parse-mailcaps)
551     (if (mm-handle-displayed-p handle)
552         (mm-remove-part handle)
553       (let* ((type (mm-handle-media-type handle))
554              (method (mailcap-mime-info type)))
555         (if (and (mm-inlinable-p handle)
556                  (mm-inlined-p handle))
557             (progn
558               (forward-line 1)
559               (mm-display-inline handle)
560               'inline)
561           (when (or method
562                     (not no-default))
563             (if (and (not method)
564                      (equal "text" (car (split-string type))))
565                 (progn
566                   (forward-line 1)
567                   (mm-insert-inline handle (mm-get-part handle))
568                   'inline)
569               (mm-display-external
570                handle (or method 'mailcap-save-binary-file)))))))))
571
572 (defun mm-display-external (handle method)
573   "Display HANDLE using METHOD."
574   (let ((outbuf (current-buffer)))
575     (mm-with-unibyte-buffer
576       (if (functionp method)
577           (let ((cur (current-buffer)))
578             (if (eq method 'mailcap-save-binary-file)
579                 (progn
580                   (set-buffer (generate-new-buffer " *mm*"))
581                   (setq method nil))
582               (mm-insert-part handle)
583               (let ((win (get-buffer-window cur t)))
584                 (when win
585                   (select-window win)))
586               (switch-to-buffer (generate-new-buffer " *mm*")))
587             (buffer-disable-undo)
588             (mm-set-buffer-file-coding-system mm-binary-coding-system)
589             (insert-buffer-substring cur)
590             (goto-char (point-min))
591             (message "Viewing with %s" method)
592             (let ((mm (current-buffer))
593                   (non-viewer (assq 'non-viewer
594                                     (mailcap-mime-info
595                                      (mm-handle-media-type handle) t))))
596               (unwind-protect
597                   (if method
598                       (funcall method)
599                     (mm-save-part handle))
600                 (when (and (not non-viewer)
601                            method)
602                   (mm-handle-set-undisplayer handle mm)))))
603         ;; The function is a string to be executed.
604         (mm-insert-part handle)
605         (let* ((dir (make-temp-name (expand-file-name "emm." mm-tmp-directory)))
606                (filename (mail-content-type-get
607                           (mm-handle-disposition handle) 'filename))
608                (mime-info (mailcap-mime-info
609                            (mm-handle-media-type handle) t))
610                (needsterm (or (assoc "needsterm" mime-info)
611                               (assoc "needsterminal" mime-info)))
612                (copiousoutput (assoc "copiousoutput" mime-info))
613                file buffer)
614           ;; We create a private sub-directory where we store our files.
615           (make-directory dir)
616           (set-file-modes dir 448)
617           (if filename
618               (setq file (expand-file-name (file-name-nondirectory filename)
619                                            dir))
620             (setq file (make-temp-name (expand-file-name "mm." dir))))
621           (let ((coding-system-for-write mm-binary-coding-system))
622             (write-region (point-min) (point-max) file nil 'nomesg))
623           (message "Viewing with %s" method)
624           (cond (needsterm
625                  (unwind-protect
626                      (if window-system
627                          (start-process "*display*" nil
628                                         mm-external-terminal-program
629                                         "-e" shell-file-name
630                                         shell-command-switch
631                                         (mm-mailcap-command
632                                          method file (mm-handle-type handle)))
633                        (require 'term)
634                        (require 'gnus-win)
635                        (set-buffer
636                         (setq buffer
637                               (make-term "display"
638                                          shell-file-name
639                                          nil
640                                          shell-command-switch
641                                          (mm-mailcap-command
642                                           method file
643                                           (mm-handle-type handle)))))
644                        (term-mode)
645                        (term-char-mode)
646                        (set-process-sentinel
647                         (get-buffer-process buffer)
648                         `(lambda (process state)
649                            (if (eq 'exit (process-status process))
650                                (gnus-configure-windows
651                                 ',gnus-current-window-configuration))))
652                        (gnus-configure-windows 'display-term))
653                    (mm-handle-set-external-undisplayer handle (cons file buffer)))
654                  (message "Displaying %s..." (format method file))
655                  'external)
656                 (copiousoutput
657                  (with-current-buffer outbuf
658                    (forward-line 1)
659                    (mm-insert-inline
660                     handle
661                     (unwind-protect
662                         (progn
663                           (call-process shell-file-name nil
664                                         (setq buffer
665                                               (generate-new-buffer " *mm*"))
666                                         nil
667                                         shell-command-switch
668                                         (mm-mailcap-command
669                                          method file (mm-handle-type handle)))
670                           (if (buffer-live-p buffer)
671                               (save-excursion
672                                 (set-buffer buffer)
673                                 (buffer-string))))
674                       (progn
675                         (ignore-errors (delete-file file))
676                         (ignore-errors (delete-directory
677                                         (file-name-directory file)))
678                         (ignore-errors (kill-buffer buffer))))))
679                  'inline)
680                 (t
681                  (unwind-protect
682                      (start-process "*display*"
683                                     (setq buffer
684                                           (generate-new-buffer " *mm*"))
685                                     shell-file-name
686                                     shell-command-switch
687                                     (mm-mailcap-command
688                                      method file (mm-handle-type handle)))
689                    (mm-handle-set-external-undisplayer handle (cons file buffer)))
690                  (message "Displaying %s..." (format method file))
691                  'external)))))))
692
693 (defun mm-mailcap-command (method file type-list)
694   (let ((ctl (cdr type-list))
695         (beg 0)
696         (uses-stdin t)
697         out sub total)
698     (while (string-match "%{\\([^}]+\\)}\\|%s\\|%t\\|%%" method beg)
699       (push (substring method beg (match-beginning 0)) out)
700       (setq beg (match-end 0)
701             total (match-string 0 method)
702             sub (match-string 1 method))
703       (cond
704        ((string= total "%%")
705         (push "%" out))
706        ((string= total "%s")
707         (setq uses-stdin nil)
708         (push (mm-quote-arg file) out))
709        ((string= total "%t")
710         (push (mm-quote-arg (car type-list)) out))
711        (t
712         (push (mm-quote-arg (or (cdr (assq (intern sub) ctl)) "")) out))))
713     (push (substring method beg (length method)) out)
714     (if uses-stdin
715         (progn
716           (push "<" out)
717           (push (mm-quote-arg file) out)))
718     (mapconcat 'identity (nreverse out) "")))
719
720 (defun mm-remove-parts (handles)
721   "Remove the displayed MIME parts represented by HANDLES."
722   (if (and (listp handles)
723            (bufferp (car handles)))
724       (mm-remove-part handles)
725     (let (handle)
726       (while (setq handle (pop handles))
727         (cond
728          ((stringp handle)
729           (when (buffer-live-p (get-text-property 0 'buffer handle))
730             (kill-buffer (get-text-property 0 'buffer handle))))
731          ((and (listp handle)
732                (stringp (car handle)))
733           (mm-remove-parts (cdr handle)))
734          (t
735           (mm-remove-part handle)))))))
736
737 (defun mm-destroy-parts (handles)
738   "Remove the displayed MIME parts represented by HANDLES."
739   (if (and (listp handles)
740            (bufferp (car handles)))
741       (mm-destroy-part handles)
742     (let (handle)
743       (while (setq handle (pop handles))
744         (cond
745          ((stringp handle)
746           (when (buffer-live-p (get-text-property 0 'buffer handle))
747             (kill-buffer (get-text-property 0 'buffer handle))))
748          ((and (listp handle)
749                (stringp (car handle)))
750           (mm-destroy-parts handle))
751          (t
752           (mm-destroy-part handle)))))))
753
754 (defun mm-remove-part (handle)
755   "Remove the displayed MIME part represented by HANDLE."
756   (when (listp handle)
757     (let ((object (mm-handle-undisplayer handle)))
758       (ignore-errors
759         (cond
760          ;; Internally displayed part.
761          ((mm-annotationp object)
762           (delete-annotation object))
763          ((or (functionp object)
764               (and (listp object)
765                    (eq (car object) 'lambda)))
766           (funcall object))
767          ;; Externally displayed part.
768          ((consp object)
769           (ignore-errors (delete-file (car object)))
770           (ignore-errors (delete-directory (file-name-directory (car object))))
771           (ignore-errors (and (cdr object) (kill-buffer (cdr object)))))
772          ((bufferp object)
773           (when (buffer-live-p object)
774             (kill-buffer object)))))
775       (mm-handle-set-undisplayer handle nil))))
776
777 (defun mm-display-inline (handle)
778   (let* ((type (mm-handle-media-type handle))
779          (function (cadr (mm-assoc-string-match mm-inline-media-tests type))))
780     (funcall function handle)
781     (goto-char (point-min))))
782
783 (defun mm-assoc-string-match (alist type)
784   (dolist (elem alist)
785     (when (string-match (car elem) type)
786       (return elem))))
787
788 (defun mm-automatic-display-p (handle)
789   "Say whether the user wants HANDLE to be displayed automatically."
790   (let ((methods mm-automatic-display)
791         (type (mm-handle-media-type handle))
792         method result)
793     (while (setq method (pop methods))
794       (when (and (not (mm-inline-override-p handle))
795                  (string-match method type))
796         (setq result t
797               methods nil)))
798     result))
799
800 (defun mm-inlinable-p (handle)
801   "Say whether HANDLE can be displayed inline."
802   (let ((alist mm-inline-media-tests)
803         (type (mm-handle-media-type handle))
804         test)
805     (while alist
806       (when (string-match (caar alist) type)
807         (setq test (caddar alist)
808               alist nil)
809         (setq test (funcall test handle)))
810       (pop alist))
811     test))
812
813 (defun mm-inlined-p (handle)
814   "Say whether the user wants HANDLE to be displayed inline."
815   (let ((methods mm-inlined-types)
816         (type (mm-handle-media-type handle))
817         method result)
818     (while (setq method (pop methods))
819       (when (and (not (mm-inline-override-p handle))
820                  (string-match method type))
821         (setq result t
822               methods nil)))
823     result))
824
825 (defun mm-attachment-override-p (handle)
826   "Say whether HANDLE should have attachment behavior overridden."
827   (let ((types mm-attachment-override-types)
828         (type (mm-handle-media-type handle))
829         ty)
830     (catch 'found
831       (while (setq ty (pop types))
832         (when (and (string-match ty type)
833                    (mm-inlinable-p handle))
834           (throw 'found t))))))
835
836 (defun mm-inline-override-p (handle)
837   "Say whether HANDLE should have inline behavior overridden."
838   (let ((types mm-inline-override-types)
839         (type (mm-handle-media-type handle))
840         ty)
841     (catch 'found
842       (while (setq ty (pop types))
843         (when (string-match ty type)
844           (throw 'found t))))))
845
846 (defun mm-automatic-external-display-p (type)
847   "Return the user-defined method for TYPE."
848   (let ((methods mm-automatic-external-display)
849         method result)
850     (while (setq method (pop methods))
851       (when (string-match method type)
852         (setq result t
853               methods nil)))
854     result))
855
856 (defun mm-destroy-part (handle)
857   "Destroy the data structures connected to HANDLE."
858   (when (listp handle)
859     (mm-remove-part handle)
860     (when (buffer-live-p (mm-handle-buffer handle))
861       (kill-buffer (mm-handle-buffer handle)))))
862
863 (defun mm-handle-displayed-p (handle)
864   "Say whether HANDLE is displayed or not."
865   (mm-handle-undisplayer handle))
866
867 ;;;
868 ;;; Functions for outputting parts
869 ;;;
870
871 (defun mm-get-part (handle)
872   "Return the contents of HANDLE as a string."
873   (mm-with-unibyte-buffer
874     (insert (with-current-buffer (mm-handle-buffer handle)
875               (mm-with-unibyte-current-buffer-mule4
876                 (buffer-string))))
877     (mm-decode-content-transfer-encoding
878      (mm-handle-encoding handle)
879      (mm-handle-media-type handle))
880     (buffer-string)))
881
882 (defun mm-insert-part (handle)
883   "Insert the contents of HANDLE in the current buffer."
884   (let ((cur (current-buffer)))
885     (save-excursion
886       (if (member (mm-handle-media-supertype handle) '("text" "message"))
887           (with-temp-buffer
888             (insert-buffer-substring (mm-handle-buffer handle))
889             (mm-decode-content-transfer-encoding
890              (mm-handle-encoding handle)
891              (mm-handle-media-type handle))
892             (let ((temp (current-buffer)))
893               (set-buffer cur)
894               (insert-buffer-substring temp)))
895         (mm-with-unibyte-buffer
896           (insert-buffer-substring (mm-handle-buffer handle))
897           (mm-decode-content-transfer-encoding
898            (mm-handle-encoding handle)
899            (mm-handle-media-type handle))
900           (let ((temp (current-buffer)))
901             (set-buffer cur)
902             (insert-buffer-substring temp)))))))
903
904 (defun mm-file-name-delete-whitespace (file-name)
905   "Remove all whitespace characters from FILE-NAME."
906   (while (string-match "\\s-+" file-name)
907     (setq file-name (replace-match "" t t file-name)))
908   file-name)
909
910 (defun mm-file-name-trim-whitespace (file-name)
911   "Remove leading and trailing whitespace characters from FILE-NAME."
912   (when (string-match "\\`\\s-+" file-name)
913     (setq file-name (substring file-name (match-end 0))))
914   (when (string-match "\\s-+\\'" file-name)
915     (setq file-name (substring file-name 0 (match-beginning 0))))
916   file-name)
917
918 (defun mm-file-name-collapse-whitespace (file-name)
919   "Collapse multiple whitespace characters in FILE-NAME."
920   (while (string-match "\\s-\\s-+" file-name)
921     (setq file-name (replace-match " " t t file-name)))
922   file-name)
923
924 (defun mm-file-name-replace-whitespace (file-name)
925   "Replace whitespace characters in FILE-NAME with underscores.
926 Set `mm-file-name-replace-whitespace' to any other string if you do not
927 like underscores."
928   (let ((s (or mm-file-name-replace-whitespace "_")))
929     (while (string-match "\\s-" file-name)
930       (setq file-name (replace-match s t t file-name))))
931   file-name)
932
933 (defun mm-save-part (handle)
934   "Write HANDLE to a file."
935   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
936          (filename (mail-content-type-get
937                     (mm-handle-disposition handle) 'filename))
938          file)
939     (when filename
940       (setq filename (gnus-map-function mm-file-name-rewrite-functions
941                                         (file-name-nondirectory filename))))
942     (setq file
943           (read-file-name "Save MIME part to: "
944                           (expand-file-name
945                            (or filename name "")
946                            (or mm-default-directory default-directory))))
947     (setq mm-default-directory (file-name-directory file))
948     (and (or (not (file-exists-p file))
949              (yes-or-no-p (format "File %s already exists; overwrite? "
950                                   file)))
951          (progn
952            (mm-save-part-to-file handle file)
953            file))))
954
955 (defun mm-save-part-to-file (handle file)
956   (mm-with-unibyte-buffer
957     (mm-insert-part handle)
958     (let ((coding-system-for-write 'binary)
959           ;; Don't re-compress .gz & al.  Arguably we should make
960           ;; `file-name-handler-alist' nil, but that would chop
961           ;; ange-ftp, which is reasonable to use here.
962           (inhibit-file-name-operation 'write-region)
963           (inhibit-file-name-handlers
964            (cons 'jka-compr-handler inhibit-file-name-handlers)))
965       (write-region (point-min) (point-max) file))))
966
967 (defun mm-pipe-part (handle)
968   "Pipe HANDLE to a process."
969   (let* ((name (mail-content-type-get (mm-handle-type handle) 'name))
970          (command
971           (read-string "Shell command on MIME part: " mm-last-shell-command)))
972     (mm-with-unibyte-buffer
973       (mm-insert-part handle)
974       (let ((coding-system-for-write 'binary))
975         (shell-command-on-region (point-min) (point-max) command nil)))))
976
977 (defun mm-interactively-view-part (handle)
978   "Display HANDLE using METHOD."
979   (let* ((type (mm-handle-media-type handle))
980          (methods
981           (mapcar (lambda (i) (list (cdr (assoc 'viewer i))))
982                   (mailcap-mime-info type 'all)))
983          (method (let ((minibuffer-local-completion-map
984                         mm-viewer-completion-map))
985                    (completing-read "Viewer: " methods))))
986     (when (string= method "")
987       (error "No method given"))
988     (if (string-match "^[^% \t]+$" method)
989         (setq method (concat method " %s")))
990     (mm-display-external handle method)))
991
992 (defun mm-preferred-alternative (handles &optional preferred)
993   "Say which of HANDLES are preferred."
994   (let ((prec (if preferred (list preferred)
995                 (mm-preferred-alternative-precedence handles)))
996         p h result type handle)
997     (while (setq p (pop prec))
998       (setq h handles)
999       (while h
1000         (setq handle (car h))
1001         (setq type (mm-handle-media-type handle))
1002         (when (and (equal p type)
1003                    (mm-automatic-display-p handle)
1004                    (or (stringp (car handle))
1005                        (not (mm-handle-disposition handle))
1006                        (equal (car (mm-handle-disposition handle))
1007                               "inline")))
1008           (setq result handle
1009                 h nil
1010                 prec nil))
1011         (pop h)))
1012     result))
1013
1014 (defun mm-preferred-alternative-precedence (handles)
1015   "Return the precedence based on HANDLES and `mm-discouraged-alternatives'."
1016   (let ((seq (nreverse (mapcar #'mm-handle-media-type
1017                                handles))))
1018     (dolist (disc (reverse mm-discouraged-alternatives))
1019       (dolist (elem (copy-sequence seq))
1020         (when (string-match disc elem)
1021           (setq seq (nconc (delete elem seq) (list elem))))))
1022     seq))
1023
1024 (defun mm-get-content-id (id)
1025   "Return the handle(s) referred to by ID."
1026   (cdr (assoc id mm-content-id-alist)))
1027
1028 (defun mm-get-image (handle)
1029   "Return an image instance based on HANDLE."
1030   (let ((type (mm-handle-media-subtype handle))
1031         spec)
1032     ;; Allow some common translations.
1033     (setq type
1034           (cond
1035            ((equal type "x-pixmap")
1036             "xpm")
1037            ((equal type "x-xbitmap")
1038             "xbm")
1039            ((equal type "x-portable-bitmap")
1040             "pbm")
1041            (t type)))
1042     (or (mm-handle-cache handle)
1043         (mm-with-unibyte-buffer
1044           (mm-insert-part handle)
1045           (prog1
1046               (setq spec
1047                     (ignore-errors
1048                       ;; Avoid testing `make-glyph' since W3 may define
1049                       ;; a bogus version of it.
1050                       (if (fboundp 'create-image)
1051                           (create-image (buffer-string) (intern type) 'data-p)
1052                         (cond
1053                          ((equal type "xbm")
1054                           ;; xbm images require special handling, since
1055                           ;; the only way to create glyphs from these
1056                           ;; (without a ton of work) is to write them
1057                           ;; out to a file, and then create a file
1058                           ;; specifier.
1059                           (let ((file (make-temp-name
1060                                        (expand-file-name "emm.xbm"
1061                                                          mm-tmp-directory))))
1062                             (unwind-protect
1063                                 (progn
1064                                   (write-region (point-min) (point-max) file)
1065                                   (make-glyph (list (cons 'x file))))
1066                               (ignore-errors
1067                                 (delete-file file)))))
1068                          (t
1069                           (make-glyph
1070                            (vector (intern type) :data (buffer-string))))))))
1071             (mm-handle-set-cache handle spec))))))
1072
1073 (defun mm-image-fit-p (handle)
1074   "Say whether the image in HANDLE will fit the current window."
1075   (let ((image (mm-get-image handle)))
1076     (if (fboundp 'glyph-width)
1077         ;; XEmacs' glyphs can actually tell us about their width, so
1078         ;; lets be nice and smart about them.
1079         (or mm-inline-large-images
1080             (and (< (glyph-width image) (window-pixel-width))
1081                  (< (glyph-height image) (window-pixel-height))))
1082       (let* ((size (image-size image))
1083              (w (car size))
1084              (h (cdr size)))
1085         (or mm-inline-large-images
1086             (and (< h (1- (window-height))) ; Don't include mode line.
1087                  (< w (window-width))))))))
1088
1089 (defun mm-valid-image-format-p (format)
1090   "Say whether FORMAT can be displayed natively by Emacs."
1091   (cond
1092    ;; Handle XEmacs
1093    ((fboundp 'valid-image-instantiator-format-p)
1094     (valid-image-instantiator-format-p format))
1095    ;; Handle Emacs 21
1096    ((fboundp 'image-type-available-p)
1097     (and (display-graphic-p)
1098          (image-type-available-p format)))
1099    ;; Nobody else can do images yet.
1100    (t
1101     nil)))
1102
1103 (defun mm-valid-and-fit-image-p (format handle)
1104   "Say whether FORMAT can be displayed natively and HANDLE fits the window."
1105   (and (mm-valid-image-format-p format)
1106        (mm-image-fit-p handle)))
1107
1108 (defun mm-find-part-by-type (handles type &optional notp recursive)
1109   "Search in HANDLES for part with TYPE.
1110 If NOTP, returns first non-matching part.
1111 If RECURSIVE, search recursively."
1112   (let (handle)
1113     (while handles
1114       (if (and recursive (stringp (caar handles)))
1115           (if (setq handle (mm-find-part-by-type (cdar handles) type
1116                                                  notp recursive))
1117               (setq handles nil))
1118         (if (if notp
1119                 (not (equal (mm-handle-media-type (car handles)) type))
1120               (equal (mm-handle-media-type (car handles)) type))
1121             (setq handle (car handles)
1122                   handles nil)))
1123       (setq handles (cdr handles)))
1124     handle))
1125
1126 (defun mm-find-raw-part-by-type (ctl type &optional notp)
1127   (goto-char (point-min))
1128   (let* ((boundary (concat "--" (mm-handle-multipart-ctl-parameter ctl
1129                                                                    'boundary)))
1130          (close-delimiter (concat "^" (regexp-quote boundary) "--[ \t]*$"))
1131          start
1132          (end (save-excursion
1133                 (goto-char (point-max))
1134                 (if (re-search-backward close-delimiter nil t)
1135                     (match-beginning 0)
1136                   (point-max))))
1137          result)
1138     (setq boundary (concat "^" (regexp-quote boundary) "[ \t]*$"))
1139     (while (and (not result)
1140                 (re-search-forward boundary end t))
1141       (goto-char (match-beginning 0))
1142       (when start
1143         (save-excursion
1144           (save-restriction
1145             (narrow-to-region start (1- (point)))
1146             (when (let ((ctl (ignore-errors
1147                                (mail-header-parse-content-type
1148                                 (mail-fetch-field "content-type")))))
1149                     (if notp
1150                         (not (equal (car ctl) type))
1151                       (equal (car ctl) type)))
1152               (setq result (buffer-substring (point-min) (point-max)))))))
1153       (forward-line 1)
1154       (setq start (point)))
1155     (when (and (not result) start)
1156       (save-excursion
1157         (save-restriction
1158           (narrow-to-region start end)
1159           (when (let ((ctl (ignore-errors
1160                              (mail-header-parse-content-type
1161                               (mail-fetch-field "content-type")))))
1162                   (if notp
1163                       (not (equal (car ctl) type))
1164                     (equal (car ctl) type)))
1165             (setq result (buffer-substring (point-min) (point-max)))))))
1166     result))
1167
1168 (defvar mm-security-handle nil)
1169
1170 (defsubst mm-set-handle-multipart-parameter (handle parameter value)
1171   ;; HANDLE could be a CTL.
1172   (if handle
1173       (put-text-property 0 (length (car handle)) parameter value
1174                          (car handle))))
1175
1176 (defun mm-possibly-verify-or-decrypt (parts ctl)
1177   (let ((subtype (cadr (split-string (car ctl) "/")))
1178         (mm-security-handle ctl) ;; (car CTL) is the type.
1179         protocol func functest)
1180     (cond
1181      ((equal subtype "signed")
1182       (unless (and (setq protocol
1183                          (mm-handle-multipart-ctl-parameter ctl 'protocol))
1184                    (not (equal protocol "multipart/mixed")))
1185         ;; The message is broken or draft-ietf-openpgp-multsig-01.
1186         (let ((protocols mm-verify-function-alist))
1187           (while protocols
1188             (if (and (or (not (setq functest (nth 3 (car protocols))))
1189                          (funcall functest parts ctl))
1190                      (mm-find-part-by-type parts (caar protocols) nil t))
1191                 (setq protocol (caar protocols)
1192                       protocols nil)
1193               (setq protocols (cdr protocols))))))
1194       (setq func (nth 1 (assoc protocol mm-verify-function-alist)))
1195       (if (cond
1196            ((eq mm-verify-option 'never) nil)
1197            ((eq mm-verify-option 'always) t)
1198            ((eq mm-verify-option 'known)
1199             (and func
1200                  (or (not (setq functest
1201                                 (nth 3 (assoc protocol
1202                                               mm-verify-function-alist))))
1203                      (funcall functest parts ctl))))
1204            (t (y-or-n-p
1205                (format "Verify signed (%s) part? "
1206                        (or (nth 2 (assoc protocol mm-verify-function-alist))
1207                            (format "protocol=%s" protocol))))))
1208           (save-excursion
1209             (if func
1210                 (funcall func parts ctl)
1211               (mm-set-handle-multipart-parameter
1212                mm-security-handle 'gnus-details
1213                (format "Unknown sign protocol (%s)" protocol))))))
1214      ((equal subtype "encrypted")
1215       (unless (setq protocol
1216                     (mm-handle-multipart-ctl-parameter ctl 'protocol))
1217         ;; The message is broken.
1218         (let ((parts parts))
1219           (while parts
1220             (if (assoc (mm-handle-media-type (car parts))
1221                        mm-decrypt-function-alist)
1222                 (setq protocol (mm-handle-media-type (car parts))
1223                       parts nil)
1224               (setq parts (cdr parts))))))
1225       (setq func (nth 1 (assoc protocol mm-decrypt-function-alist)))
1226       (if (cond
1227            ((eq mm-decrypt-option 'never) nil)
1228            ((eq mm-decrypt-option 'always) t)
1229            ((eq mm-decrypt-option 'known)
1230             (and func
1231                  (or (not (setq functest
1232                                 (nth 3 (assoc protocol
1233                                               mm-decrypt-function-alist))))
1234                      (funcall functest parts ctl))))
1235            (t (y-or-n-p
1236                (format "Decrypt (%s) part? "
1237                        (or (nth 2 (assoc protocol mm-decrypt-function-alist))
1238                            (format "protocol=%s" protocol))))))
1239           (save-excursion
1240             (if func
1241                 (setq parts (funcall func parts ctl))
1242               (mm-set-handle-multipart-parameter
1243                mm-security-handle 'gnus-details
1244                (format "Unknown encrypt protocol (%s)" protocol))))))
1245      (t nil))
1246     parts))
1247
1248 (defun mm-multiple-handles (handles)
1249   (and (listp (car handles))
1250        (> (length handles) 1)))
1251
1252 (defun mm-merge-handles (handles1 handles2)
1253   (append
1254    (if (listp (car handles1))
1255        handles1
1256      (list handles1))
1257    (if (listp (car handles2))
1258        handles2
1259      (list handles2))))
1260
1261 (provide 'mm-decode)
1262
1263 ;;; mm-decode.el ends here