New directory
[bpt/emacs.git] / lisp / mh-e / mh-mime.el
CommitLineData
bdcfe844 1;;; mh-mime.el --- MH-E support for composing MIME messages
c26cf6c8 2
924df208 3;; Copyright (C) 1993, 1995, 2001, 02, 2003 Free Software Foundation, Inc.
a1b4049d
BW
4
5;; Author: Bill Wohler <wohler@newt.com>
6;; Maintainer: Bill Wohler <wohler@newt.com>
7;; Keywords: mail
8;; See: mh-e.el
c26cf6c8 9
60370d40 10;; This file is part of GNU Emacs.
c26cf6c8 11
9b7bc076 12;; GNU Emacs is free software; you can redistribute it and/or modify
c26cf6c8
RS
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation; either version 2, or (at your option)
15;; any later version.
16
9b7bc076 17;; GNU Emacs is distributed in the hope that it will be useful,
c26cf6c8
RS
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
b578f267
EN
23;; along with GNU Emacs; see the file COPYING. If not, write to the
24;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25;; Boston, MA 02111-1307, USA.
c26cf6c8
RS
26
27;;; Commentary:
28
bdcfe844 29;; Internal support for MH-E package.
b578f267
EN
30;; Support for generating an mhn composition file.
31;; MIME is supported only by MH 6.8 or later.
c26cf6c8 32
847b8219
KH
33;;; Change Log:
34
c26cf6c8
RS
35;;; Code:
36
bdcfe844 37(require 'cl)
c26cf6c8 38(require 'mh-comp)
bdcfe844 39(require 'mh-utils)
c3d9274a
BW
40(load "mm-decode" t t) ; Non-fatal dependency
41(load "mm-uu" t t) ; Non-fatal dependency
42(load "mailcap" t t) ; Non-fatal dependency
43(load "smiley" t t) ; Non-fatal dependency
bdcfe844
BW
44(require 'gnus-util)
45
46(autoload 'gnus-article-goto-header "gnus-art")
47(autoload 'article-emphasize "gnus-art")
48(autoload 'gnus-get-buffer-create "gnus")
49(autoload 'gnus-eval-format "gnus-spec")
50(autoload 'widget-convert-button "wid-edit")
51(autoload 'message-options-set-recipient "message")
52(autoload 'mml-secure-message-sign-pgpmime "mml-sec")
53(autoload 'mml-secure-message-encrypt-pgpmime "mml-sec")
54(autoload 'mml-minibuffer-read-file "mml")
55(autoload 'mml-minibuffer-read-description "mml")
56(autoload 'mml-insert-empty-tag "mml")
57(autoload 'mml-to-mime "mml")
58(autoload 'mml-attach-file "mml")
924df208 59(autoload 'rfc2047-decode-region "rfc2047")
bdcfe844 60
c3d9274a 61;;;###mh-autoload
bdcfe844
BW
62(defun mh-compose-insertion (&optional inline)
63 "Add a directive to insert a MIME part from a file, using mhn or gnus.
64If the variable `mh-compose-insertion' is set to 'mhn, then that will be used.
65If it is set to 'gnus, then that will be used instead.
66Optional argument INLINE means make it an inline attachment."
67 (interactive "P")
68 (if (equal mh-compose-insertion 'gnus)
69 (if inline
70 (mh-mml-attach-file "inline")
71 (mh-mml-attach-file))
72 (call-interactively 'mh-mhn-compose-insertion)))
73
c3d9274a 74;;;###mh-autoload
bdcfe844
BW
75(defun mh-compose-forward (&optional description folder message)
76 "Add a MIME directive to forward a message, using mhn or gnus.
77If the variable `mh-compose-insertion' is set to 'mhn, then that will be used.
78If it is set to 'gnus, then that will be used instead.
79Optional argument DESCRIPTION is a description of the attachment.
80Optional argument FOLDER is the folder from which the forwarded message should
81come.
82Optional argument MESSAGE is the message to forward.
83If any of the optional arguments are absent, they are prompted for."
84 (interactive (list
c3d9274a
BW
85 (read-string "Forw Content-description: ")
86 (mh-prompt-for-folder "Message from" mh-sent-from-folder nil)
87 (read-string (format "Messages%s: "
88 (if mh-sent-from-msg
bdcfe844 89 (format " [%d]" mh-sent-from-msg)
c3d9274a 90 "")))))
bdcfe844
BW
91 (if (equal mh-compose-insertion 'gnus)
92 (mh-mml-forward-message description folder message)
93 (mh-mhn-compose-forw description folder message)))
c26cf6c8 94
c26cf6c8
RS
95;; To do:
96;; paragraph code should not fill # lines if MIME enabled.
97;; implement mh-auto-edit-mhn (if non-nil, \\[mh-send-letter]
c3d9274a 98;; invokes mh-edit-mhn automatically before sending.)
c26cf6c8
RS
99;; actually, instead of mh-auto-edit-mhn,
100;; should read automhnproc from profile
101;; MIME option to mh-forward
102;; command to move to content-description insertion point
103
847b8219
KH
104(defvar mh-mhn-args nil
105 "Extra arguments to have \\[mh-edit-mhn] pass to the \"mhn\" command.
106The arguments are passed to mhn if \\[mh-edit-mhn] is given a
107prefix argument. Normally default arguments to mhn are specified in the
108MH profile.")
109
bdcfe844
BW
110(defvar mh-media-type-regexp
111 (concat (regexp-opt '("text" "image" "audio" "video" "application"
112 "multipart" "message") t)
113 "/[-.+a-zA-Z0-9]+")
114 "Regexp matching valid media types used in MIME attachment compositions.")
115
116;; Just defvar the variable to avoid compiler warning... This doesn't bind
117;; the variable, so things should work exactly as before.
118(defvar mh-have-file-command)
847b8219 119
a1b4049d
BW
120(defun mh-have-file-command ()
121 "Return t if 'file' command is on the system.
122'file -i' is used to get MIME type of composition insertion."
123 (when (not (boundp 'mh-have-file-command))
c3d9274a 124 (load "executable" t t) ; executable-find not autoloaded in emacs20
a1b4049d
BW
125 (setq mh-have-file-command
126 (and (fboundp 'executable-find)
127 (executable-find "file") ; file command exists
128 ; and accepts -i and -b args.
129 (zerop (call-process "file" nil nil nil "-i" "-b"
130 (expand-file-name "inc" mh-progs))))))
131 mh-have-file-command)
132
bdcfe844
BW
133(defvar mh-file-mime-type-substitutions
134 '(("application/msword" "\.xls" "application/ms-excel")
135 ("application/msword" "\.ppt" "application/ms-powerpoint"))
136 "Substitutions to make for Content-Type returned from file command.
137The first element is the Content-Type returned by the file command.
138The second element is a regexp matching the file name, usually the extension.
139The third element is the Content-Type to replace with.")
140
141(defun mh-file-mime-type-substitute (content-type filename)
142 "Return possibly changed CONTENT-TYPE on the FILENAME.
143Substitutions are made from the `mh-file-mime-type-substitutions' variable."
144 (let ((subst mh-file-mime-type-substitutions)
145 (type) (match) (answer content-type)
146 (case-fold-search t))
147 (while subst
148 (setq type (car (car subst))
149 match (elt (car subst) 1))
150 (if (and (string-equal content-type type)
151 (string-match match filename))
152 (setq answer (elt (car subst) 2)
153 subst nil)
154 (setq subst (cdr subst))))
155 answer))
156
a1b4049d
BW
157(defun mh-file-mime-type (filename)
158 "Return MIME type of FILENAME from file command.
159Returns nil if file command not on system."
160 (cond
161 ((not (mh-have-file-command))
162 nil) ;No file command, exit now.
163 ((not (and (file-exists-p filename)(file-readable-p filename)))
164 nil)
165 (t
166 (save-excursion
167 (let ((tmp-buffer (get-buffer-create mh-temp-buffer)))
168 (set-buffer tmp-buffer)
169 (unwind-protect
170 (progn
171 (call-process "file" nil '(t nil) nil "-b" "-i"
172 (expand-file-name filename))
173 (goto-char (point-min))
174 (if (not (re-search-forward mh-media-type-regexp nil t))
175 nil
bdcfe844 176 (mh-file-mime-type-substitute (match-string 0) filename)))
a1b4049d
BW
177 (kill-buffer tmp-buffer)))))))
178
a1b4049d 179;;; This is needed for Emacs20 which doesn't have mailcap-mime-types.
c26cf6c8 180(defvar mh-mime-content-types
a1b4049d
BW
181 '(("application/mac-binhex40") ("application/msword")
182 ("application/octet-stream") ("application/pdf") ("application/pgp-keys")
183 ("application/pgp-signature") ("application/pkcs7-signature")
184 ("application/postscript") ("application/rtf")
185 ("application/vnd.ms-excel") ("application/vnd.ms-powerpoint")
186 ("application/vnd.ms-project") ("application/vnd.ms-tnef")
187 ("application/wordperfect5.1") ("application/wordperfect6.0")
188 ("application/zip")
189
190 ("audio/basic") ("audio/mpeg")
191
192 ("image/gif") ("image/jpeg") ("image/png")
193
194 ("message/delivery-status")
195 ("message/external-body") ("message/partial") ("message/rfc822")
196
197 ("text/enriched") ("text/html") ("text/plain") ("text/rfc822-headers")
198 ("text/richtext") ("text/xml")
199
200 ("video/mpeg") ("video/quicktime"))
201 "Legal MIME content types.
202See documentation for \\[mh-edit-mhn].")
203
c3d9274a 204;;;###mh-autoload
3fda54a2 205(defun mh-mhn-compose-insertion (filename type description attributes)
42c21202 206 "Add a directive to insert a MIME message part from a file.
bdcfe844
BW
207This is the typical way to insert non-text parts in a message.
208
209Arguments are FILENAME, which tells where to find the file, TYPE, the MIME
210content type, DESCRIPTION, a line of text for the Content-Description field.
211ATTRIBUTES is a comma separated list of name=value pairs that is appended to
212the Content-Type field of the attachment.
213
a1b4049d
BW
214See also \\[mh-edit-mhn]."
215 (interactive (let ((filename (read-file-name "Insert contents of: ")))
c3d9274a
BW
216 (list
217 filename
a1b4049d 218 (or (mh-file-mime-type filename)
c3d9274a
BW
219 (completing-read "Content-Type: "
220 (if (fboundp 'mailcap-mime-types)
221 (mapcar 'list (mailcap-mime-types))
222 mh-mime-content-types)))
223 (read-string "Content-Description: ")
224 (read-string "Content-Attributes: "
225 (concat "name=\""
226 (file-name-nondirectory filename)
227 "\"")))))
3fda54a2 228 (mh-mhn-compose-type filename type description attributes ))
c26cf6c8 229
3fda54a2 230(defun mh-mhn-compose-type (filename type
c3d9274a 231 &optional description attributes comment)
bdcfe844
BW
232 "Insert a mhn directive to insert a file.
233
234The file specified by FILENAME is encoded as TYPE. An optional DESCRIPTION is
235used as the Content-Description field, optional set of ATTRIBUTES and an
236optional COMMENT can also be included."
c26cf6c8
RS
237 (beginning-of-line)
238 (insert "#" type)
239 (and attributes
240 (insert "; " attributes))
241 (and comment
242 (insert " (" comment ")"))
243 (insert " [")
244 (and description
245 (insert description))
3fda54a2 246 (insert "] " (expand-file-name filename))
c26cf6c8
RS
247 (insert "\n"))
248
249
c3d9274a 250;;;###mh-autoload
3fda54a2 251(defun mh-mhn-compose-anon-ftp (host filename type description)
42c21202 252 "Add a directive for a MIME anonymous ftp external body part.
bdcfe844
BW
253This directive tells MH to include a reference to a message/external-body part
254retrievable by anonymous FTP.
255
256Arguments are HOST and FILENAME, which tell where to find the file, TYPE, the
257MIME content type, and DESCRIPTION, a line of text for the Content-description
258header.
259
260See also \\[mh-edit-mhn]."
c26cf6c8 261 (interactive (list
c3d9274a
BW
262 (read-string "Remote host: ")
263 (read-string "Remote filename: ")
264 (completing-read "External Content-Type: "
265 (if (fboundp 'mailcap-mime-types)
266 (mapcar 'list (mailcap-mime-types))
267 mh-mime-content-types))
268 (read-string "External Content-Description: ")))
3fda54a2 269 (mh-mhn-compose-external-type "anon-ftp" host filename
c3d9274a 270 type description))
c26cf6c8 271
c3d9274a 272;;;###mh-autoload
3fda54a2 273(defun mh-mhn-compose-external-compressed-tar (host filename description)
42c21202 274 "Add a directive to include a MIME reference to a compressed tar file.
bdcfe844
BW
275The file should be available via anonymous ftp. This directive tells MH to
276include a reference to a message/external-body part.
277
3fda54a2 278Arguments are HOST and FILENAME, which tell where to find the file, and
847b8219 279DESCRIPTION, a line of text for the Content-description header.
bdcfe844 280
c26cf6c8
RS
281See also \\[mh-edit-mhn]."
282 (interactive (list
c3d9274a
BW
283 (read-string "Remote host: ")
284 (read-string "Remote filename: ")
285 (read-string "Tar file Content-description: ")))
3fda54a2 286 (mh-mhn-compose-external-type "anon-ftp" host filename
c3d9274a
BW
287 "application/octet-stream"
288 description
289 "type=tar; conversions=x-compress"
290 "mode=image"))
c26cf6c8
RS
291
292
3fda54a2 293(defun mh-mhn-compose-external-type (access-type host filename type
c3d9274a
BW
294 &optional description
295 attributes extra-params
296 comment)
bdcfe844
BW
297 "Add a directive to include a MIME reference to a remote file.
298The file should be available via anonymous ftp. This directive tells MH to
299include a reference to a message/external-body part.
300
301Arguments are ACCESS-TYPE, HOST and FILENAME, which tell where to find the
302file and TYPE which is the MIME Content-Type. Optional arguments include
303DESCRIPTION, a line of text for the Content-description header, ATTRIBUTES,
304EXTRA-PARAMS, and COMMENT.
305
306See also \\[mh-edit-mhn]."
c26cf6c8
RS
307 (beginning-of-line)
308 (insert "#@" type)
309 (and attributes
310 (insert "; " attributes))
311 (and comment
312 (insert " (" comment ") "))
313 (insert " [")
314 (and description
315 (insert description))
316 (insert "] ")
317 (insert "access-type=" access-type "; ")
318 (insert "site=" host)
3fda54a2
RS
319 (insert "; name=" (file-name-nondirectory filename))
320 (insert "; directory=\"" (file-name-directory filename) "\"")
c26cf6c8
RS
321 (and extra-params
322 (insert "; " extra-params))
323 (insert "\n"))
324
c3d9274a 325;;;###mh-autoload
847b8219 326(defun mh-mhn-compose-forw (&optional description folder messages)
42c21202 327 "Add a forw directive to this message, to forward a message with MIME.
c26cf6c8 328This directive tells MH to include the named messages in this one.
bdcfe844 329
c26cf6c8 330Arguments are DESCRIPTION, a line of text for the Content-description header,
42c21202 331and FOLDER and MESSAGES, which name the message(s) to be forwarded.
bdcfe844 332
c26cf6c8
RS
333See also \\[mh-edit-mhn]."
334 (interactive (list
c3d9274a
BW
335 (read-string "Forw Content-description: ")
336 (mh-prompt-for-folder "Message from" mh-sent-from-folder nil)
337 (read-string (format "Messages%s: "
338 (if mh-sent-from-msg
339 (format " [%d]" mh-sent-from-msg)
340 "")))))
c26cf6c8
RS
341 (beginning-of-line)
342 (insert "#forw [")
343 (and description
344 (not (string= description ""))
345 (insert description))
346 (insert "]")
347 (and folder
348 (not (string= folder ""))
349 (insert " " folder))
847b8219 350 (if (and messages
c3d9274a 351 (not (string= messages "")))
c26cf6c8 352 (let ((start (point)))
c3d9274a
BW
353 (insert " " messages)
354 (subst-char-in-region start (point) ?, ? ))
c26cf6c8 355 (if mh-sent-from-msg
c3d9274a 356 (insert " " (int-to-string mh-sent-from-msg))))
c26cf6c8
RS
357 (insert "\n"))
358
c3d9274a 359;;;###mh-autoload
847b8219
KH
360(defun mh-edit-mhn (&optional extra-args)
361 "Format the current draft for MIME, expanding any mhn directives.
a1b4049d
BW
362
363Process the current draft with the mhn program, which, using directives
364already inserted in the draft, fills in all the MIME components and header
365fields.
366
924df208
BW
367This step is performed automatically when sending the message, but this
368function may be called manually before sending the draft as well.
a1b4049d
BW
369
370The `\\[mh-revert-mhn-edit]' command undoes this command. The arguments in the
371list `mh-mhn-args' are passed to mhn if this function is passed an optional
372prefix argument EXTRA-ARGS.
373
374For assistance with creating mhn directives to insert various types of
375components in a message, see \\[mh-mhn-compose-insertion] (generic insertion
376from a file), \\[mh-mhn-compose-anon-ftp] (external reference to file via
377anonymous ftp), \\[mh-mhn-compose-external-compressed-tar] \ \(reference to
378compressed tar file via anonymous ftp), and \\[mh-mhn-compose-forw] (forward
924df208 379message).
a1b4049d 380
bdcfe844
BW
381The value of `mh-edit-mhn-hook' is a list of functions to be called, with no
382arguments, after performing the conversion.
383
a1b4049d 384The mhn program is part of MH version 6.8 or later."
847b8219 385 (interactive "*P")
c26cf6c8
RS
386 (save-buffer)
387 (message "mhn editing...")
a1b4049d 388 (cond
bdcfe844 389 (mh-nmh-flag
a1b4049d
BW
390 (mh-exec-cmd-error nil
391 "mhbuild" (if extra-args mh-mhn-args) buffer-file-name))
392 (t
393 (mh-exec-cmd-error (format "mhdraft=%s" buffer-file-name)
394 "mhn" (if extra-args mh-mhn-args) buffer-file-name)))
c26cf6c8 395 (revert-buffer t t)
847b8219
KH
396 (message "mhn editing...done")
397 (run-hooks 'mh-edit-mhn-hook))
c26cf6c8 398
c3d9274a 399;;;###mh-autoload
c26cf6c8 400(defun mh-revert-mhn-edit (noconfirm)
a1b4049d
BW
401 "Undo the effect of \\[mh-edit-mhn] by reverting to the backup file.
402Optional non-nil argument NOCONFIRM means don't ask for confirmation."
c26cf6c8
RS
403 (interactive "*P")
404 (if (null buffer-file-name)
405 (error "Buffer does not seem to be associated with any file"))
406 (let ((backup-strings '("," "#"))
c3d9274a 407 backup-file)
c26cf6c8 408 (while (and backup-strings
c3d9274a
BW
409 (not (file-exists-p
410 (setq backup-file
411 (concat (file-name-directory buffer-file-name)
412 (car backup-strings)
413 (file-name-nondirectory buffer-file-name)
414 ".orig")))))
c26cf6c8
RS
415 (setq backup-strings (cdr backup-strings)))
416 (or backup-strings
c3d9274a 417 (error "Backup file for %s no longer exists!" buffer-file-name))
c26cf6c8 418 (or noconfirm
c3d9274a
BW
419 (yes-or-no-p (format "Revert buffer from file %s? "
420 backup-file))
421 (error "Revert not confirmed"))
c26cf6c8
RS
422 (let ((buffer-read-only nil))
423 (erase-buffer)
424 (insert-file-contents backup-file))
425 (after-find-file nil)))
60370d40 426
924df208
BW
427;;;###mh-autoload
428(defun mh-mhn-directive-present-p ()
429 "Check if the current buffer has text which might be a MHN directive."
430 (save-excursion
431 (block 'search-for-mhn-directive
432 (goto-char (point-min))
433 (while (re-search-forward "^#" nil t)
434 (let ((s (buffer-substring-no-properties (point) (line-end-position))))
435 (cond ((equal s ""))
436 ((string-match "^forw[ \t\n]+" s)
437 (return-from 'search-for-mhn-directive t))
438 (t (let ((first-token (car (split-string s "[ \t;@]"))))
439 (when (string-match mh-media-type-regexp first-token)
440 (return-from 'search-for-mhn-directive t)))))))
441 nil)))
442
bdcfe844
BW
443\f
444
445;;; MIME composition functions
446
c3d9274a 447;;;###mh-autoload
bdcfe844 448(defun mh-mml-to-mime ()
924df208
BW
449 "Compose MIME message from mml directives.
450This step is performed automatically when sending the message, but this
451function may be called manually before sending the draft as well."
bdcfe844
BW
452 (interactive)
453 (when mh-gnus-pgp-support-flag ;; This is only needed for PGP
454 (message-options-set-recipient))
924df208 455 (mml-to-mime))
bdcfe844 456
c3d9274a 457;;;###mh-autoload
bdcfe844
BW
458(defun mh-mml-forward-message (description folder message)
459 "Forward a message as attachment.
460The function will prompt the user for a DESCRIPTION, a FOLDER and MESSAGE
461number."
462 (let ((msg (if (equal message "")
463 mh-sent-from-msg
464 (car (read-from-string message)))))
465 (cond ((integerp msg)
466 (if (string= "" description)
467 ;; Rationale: mml-attach-file constructs a malformed composition
468 ;; if the description string is empty. This fixes SF #625168.
469 (mml-attach-file (format "%s%s/%d"
470 mh-user-path (substring folder 1) msg)
471 "message/rfc822")
472 (mml-attach-file (format "%s%s/%d"
473 mh-user-path (substring folder 1) msg)
474 "message/rfc822"
924df208 475 description)))
bdcfe844
BW
476 (t (error "The message number, %s is not a integer!" msg)))))
477
c3d9274a 478;;;###mh-autoload
bdcfe844
BW
479(defun mh-mml-attach-file (&optional disposition)
480 "Attach a file to the outgoing MIME message.
481The file is not inserted or encoded until you send the message with
482`\\[mh-send-letter]'.
483Message disposition is \"inline\" or \"attachment\" and is prompted for if
484DISPOSITION is nil.
485
486This is basically `mml-attach-file' from gnus, modified such that a prefix
487argument yields an `inline' disposition and Content-Type is determined
488automatically."
489 (let* ((file (mml-minibuffer-read-file "Attach file: "))
490 (type (or (mh-file-mime-type file)
491 (completing-read "Content-Type: "
492 (if (fboundp 'mailcap-mime-types)
493 (mapcar 'list (mailcap-mime-types))
494 mh-mime-content-types))))
495 (description (mml-minibuffer-read-description))
496 (dispos (or disposition
497 (completing-read "Disposition: [attachment] "
498 '(("attachment")("inline"))
499 nil t nil nil
500 "attachment"))))
501 (mml-insert-empty-tag 'part 'type type 'filename file
924df208 502 'disposition dispos 'description description)))
bdcfe844 503
c3d9274a 504;;;###mh-autoload
bdcfe844
BW
505(defun mh-mml-secure-message-sign-pgpmime ()
506 "Add directive to encrypt/sign the entire message."
507 (interactive)
508 (if (not mh-gnus-pgp-support-flag)
509 (error "Sorry. Your version of gnus does not support PGP/GPG")
924df208 510 (mml-secure-message-sign-pgpmime)))
bdcfe844 511
c3d9274a 512;;;###mh-autoload
bdcfe844
BW
513(defun mh-mml-secure-message-encrypt-pgpmime (&optional dontsign)
514 "Add directive to encrypt and sign the entire message.
515If called with a prefix argument DONTSIGN, only encrypt (do NOT sign)."
516 (interactive "P")
517 (if (not mh-gnus-pgp-support-flag)
518 (error "Sorry. Your version of gnus does not support PGP/GPG")
924df208
BW
519 (mml-secure-message-encrypt-pgpmime dontsign)))
520
521;;;###mh-autoload
522(defun mh-mml-directive-present-p ()
523 "Check if the current buffer has text which may be an MML directive."
524 (save-excursion
525 (goto-char (point-min))
526 (re-search-forward
527 "\\(<#part\\(.\\|\n\\)*>[ \n\t]*<#/part>\\|^<#secure.+>$\\)"
528 nil t)))
bdcfe844
BW
529
530\f
531
532;;; MIME decoding
533
bdcfe844
BW
534(defmacro mh-defun-compat (function arg-list &rest body)
535 "This is a macro to define functions which are not defined.
536It is used for Gnus utility functions which were added recently. If FUNCTION
537is not defined then it is defined to have argument list, ARG-LIST and body,
538BODY."
539 (let ((defined-p (fboundp function)))
540 (unless defined-p
541 `(defun ,function ,arg-list ,@body))))
c3d9274a 542(put 'mh-defun-compat 'lisp-indent-function 'defun)
bdcfe844
BW
543
544;; Copy of original function from gnus-util.el
545(mh-defun-compat gnus-local-map-property (map)
546 "Return a list suitable for a text property list specifying keymap MAP."
547 (cond (mh-xemacs-flag (list 'keymap map))
548 ((>= emacs-major-version 21) (list 'keymap map))
549 (t (list 'local-map map))))
550
551;; Copy of original function from mm-decode.el
552(mh-defun-compat mm-merge-handles (handles1 handles2)
553 (append (if (listp (car handles1)) handles1 (list handles1))
554 (if (listp (car handles2)) handles2 (list handles2))))
555
556;; Copy of function from mm-decode.el
557(mh-defun-compat mm-set-handle-multipart-parameter (handle parameter value)
558 ;; HANDLE could be a CTL.
559 (if handle
560 (put-text-property 0 (length (car handle)) parameter value
c3d9274a 561 (car handle))))
bdcfe844
BW
562
563;; Copy of original macro is in mm-decode.el
564(mh-defun-compat mm-handle-multipart-ctl-parameter (handle parameter)
565 (get-text-property 0 parameter (car handle)))
566
924df208
BW
567(mh-do-in-xemacs (defvar default-enable-multibyte-characters))
568
bdcfe844
BW
569;; Copy of original function in mm-decode.el
570(mh-defun-compat mm-readable-p (handle)
571 "Say whether the content of HANDLE is readable."
572 (and (< (with-current-buffer (mm-handle-buffer handle)
c3d9274a 573 (buffer-size)) 10000)
bdcfe844 574 (mm-with-unibyte-buffer
c3d9274a
BW
575 (mm-insert-part handle)
576 (and (eq (mm-body-7-or-8) '7bit)
577 (not (mm-long-lines-p 76))))))
bdcfe844
BW
578
579;; Copy of original function in mm-bodies.el
580(mh-defun-compat mm-long-lines-p (length)
581 "Say whether any of the lines in the buffer is longer than LINES."
582 (save-excursion
583 (goto-char (point-min))
584 (end-of-line)
585 (while (and (not (eobp))
c3d9274a 586 (not (> (current-column) length)))
bdcfe844
BW
587 (forward-line 1)
588 (end-of-line))
589 (and (> (current-column) length)
c3d9274a 590 (current-column))))
bdcfe844
BW
591
592(mh-defun-compat mm-keep-viewer-alive-p (handle)
593 ;; Released Gnus doesn't keep handles associated with externally displayed
594 ;; MIME parts. So this will always return nil.
595 nil)
596
597(mh-defun-compat mm-destroy-parts (list)
598 "Older emacs don't have this function."
599 nil)
600
601;;; This is mm-save-part from gnus 5.10 since that function in emacs21.2 is
602;;; buggy (the args to read-file-name are incorrect). When all supported
603;;; versions of Emacs come with at least Gnus 5.10, we can delete this
604;;; function and rename calls to mh-mm-save-part to mm-save-part.
605(defun mh-mm-save-part (handle)
606 "Write HANDLE to a file."
607 (let ((name (mail-content-type-get (mm-handle-type handle) 'name))
c3d9274a
BW
608 (filename (mail-content-type-get
609 (mm-handle-disposition handle) 'filename))
610 file)
bdcfe844
BW
611 (when filename
612 (setq filename (file-name-nondirectory filename)))
613 (setq file (read-file-name "Save MIME part to: "
c3d9274a
BW
614 (or mm-default-directory
615 default-directory)
616 nil nil (or filename name "")))
bdcfe844
BW
617 (setq mm-default-directory (file-name-directory file))
618 (and (or (not (file-exists-p file))
c3d9274a
BW
619 (yes-or-no-p (format "File %s already exists; overwrite? "
620 file)))
621 (mm-save-part-to-file handle file))))
bdcfe844
BW
622
623\f
624
625;;; MIME cleanup
626
c3d9274a 627;;;###mh-autoload
bdcfe844
BW
628(defun mh-mime-cleanup ()
629 "Free the decoded MIME parts."
630 (let ((mime-data (gethash (current-buffer) mh-globals-hash)))
631 ;; This is for Emacs, what about XEmacs?
924df208 632 (mh-funcall-if-exists remove-images (point-min) (point-max))
bdcfe844
BW
633 (when mime-data
634 (mm-destroy-parts (mh-mime-handles mime-data))
635 (remhash (current-buffer) mh-globals-hash))))
636
c3d9274a 637;;;###mh-autoload
bdcfe844
BW
638(defun mh-destroy-postponed-handles ()
639 "Free MIME data for externally displayed mime parts."
640 (let ((mime-data (mh-buffer-data)))
641 (when mime-data
642 (mm-destroy-parts (mh-mime-handles mime-data)))
643 (remhash (current-buffer) mh-globals-hash)))
644
645(defun mh-handle-set-external-undisplayer (folder handle function)
646 "Replacement for `mm-handle-set-external-undisplayer'.
647This is only called in recent versions of Gnus. The MIME handles are stored
648in data structures corresponding to MH-E folder buffer FOLDER instead of in
649Gnus (as in the original). The MIME part, HANDLE is associated with the
650undisplayer FUNCTION."
651 (if (mm-keep-viewer-alive-p handle)
652 (let ((new-handle (copy-sequence handle)))
c3d9274a
BW
653 (mm-handle-set-undisplayer new-handle function)
654 (mm-handle-set-undisplayer handle nil)
bdcfe844
BW
655 (save-excursion
656 (set-buffer folder)
657 (push new-handle (mh-mime-handles (mh-buffer-data)))))
658 (mm-handle-set-undisplayer handle function)))
659
660\f
661
662;;; MIME transformations
c3d9274a 663(eval-when-compile (require 'font-lock))
bdcfe844 664
c3d9274a 665;;;###mh-autoload
bdcfe844
BW
666(defun mh-add-missing-mime-version-header ()
667 "Some mail programs don't put a MIME-Version header.
668I have seen this only in spam, so maybe we shouldn't fix this ;-)"
669 (save-excursion
670 (goto-char (point-min))
671 (when (and (message-fetch-field "content-type")
672 (not (message-fetch-field "mime-version")))
673 (when (search-forward "\n\n" nil t)
674 (forward-line -1)
675 (insert "MIME-Version: 1.0\n")))))
676
c3d9274a 677;;;###mh-autoload
bdcfe844
BW
678(defun mh-display-smileys ()
679 "Function to display smileys."
c3d9274a
BW
680 (when (and mh-graphical-smileys-flag
681 (fboundp 'smiley-region)
682 (boundp 'font-lock-maximum-size)
924df208 683 font-lock-maximum-size
c3d9274a 684 (>= (/ font-lock-maximum-size 8) (buffer-size)))
bdcfe844
BW
685 (smiley-region (point-min) (point-max))))
686
c3d9274a 687;;;###mh-autoload
bdcfe844
BW
688(defun mh-display-emphasis ()
689 "Function to display graphical emphasis."
c3d9274a 690 (when (and mh-graphical-emphasis-flag
924df208
BW
691 (if font-lock-maximum-size
692 (>= (/ font-lock-maximum-size 8) (buffer-size))))
c3d9274a 693 (flet ((article-goto-body ())) ; shadow this function to do nothing
bdcfe844
BW
694 (save-excursion
695 (goto-char (point-min))
696 (article-emphasize)))))
697
698;; Copied from gnus-art.el (should be checked for other cool things that can
699;; be added to the buttons)
700(defvar mh-mime-button-commands
701 '((mh-press-button "\r" "Toggle Display")))
702(defvar mh-mime-button-map
703 (let ((map (make-sparse-keymap)))
704 (unless (>= (string-to-number emacs-version) 21)
705 ;; XEmacs doesn't care.
706 (set-keymap-parent map mh-show-mode-map))
924df208
BW
707 (mh-do-in-gnu-emacs
708 (define-key map [mouse-2] 'mh-push-button))
709 (mh-do-in-xemacs
710 (define-key map '(button2) 'mh-push-button))
bdcfe844
BW
711 (dolist (c mh-mime-button-commands)
712 (define-key map (cadr c) (car c)))
713 map))
714(defvar mh-mime-button-line-format-alist
715 '((?T long-type ?s)
716 (?d description ?s)
717 (?p index ?s)
718 (?e dots ?s)))
719(defvar mh-mime-button-line-format "%{%([%p. %d%T]%)%}%e\n")
720(defvar mh-mime-security-button-pressed nil)
721(defvar mh-mime-security-button-line-format "%{%([[%t:%i]%D]%)%}\n")
722(defvar mh-mime-security-button-end-line-format "%{%([[End of %t]%D]%)%}\n")
723(defvar mh-mime-security-button-line-format-alist
724 '((?t type ?s)
725 (?i info ?s)
726 (?d details ?s)
727 (?D pressed-details ?s)))
728(defvar mh-mime-security-button-map
729 (let ((map (make-sparse-keymap)))
730 (unless (>= (string-to-number emacs-version) 21)
731 (set-keymap-parent map mh-show-mode-map))
732 (define-key map "\r" 'mh-press-button)
924df208
BW
733 (mh-do-in-gnu-emacs
734 (define-key map [mouse-2] 'mh-push-button))
735 (mh-do-in-xemacs
736 (define-key map '(button2) 'mh-push-button))
bdcfe844
BW
737 map))
738
739(defvar mh-mime-save-parts-directory nil
740 "Default to use for `mh-mime-save-parts-default-directory'.
741Set from last use.")
742
c3d9274a 743;;;###mh-autoload
bdcfe844
BW
744(defun mh-mime-save-parts (arg)
745 "Store the MIME parts of the current message.
746If ARG, prompt for directory, else use that specified by the variable
747`mh-mime-save-parts-default-directory'. These directories may be superseded by
748mh_profile directives, since this function calls on mhstore or mhn to do the
749actual storing."
750 (interactive "P")
751 (let ((msg (if (eq major-mode 'mh-show-mode)
752 (mh-show-buffer-message-number)
753 (mh-get-msg-num t)))
754 (folder (if (eq major-mode 'mh-show-mode)
755 mh-show-folder-buffer
756 mh-current-folder))
757 (command (if mh-nmh-flag "mhstore" "mhn"))
758 (directory
759 (cond
760 ((and (or arg
761 (equal nil mh-mime-save-parts-default-directory)
762 (equal t mh-mime-save-parts-default-directory))
763 (not mh-mime-save-parts-directory))
764 (read-file-name "Store in what directory? " nil nil t nil))
765 ((and (or arg
766 (equal t mh-mime-save-parts-default-directory))
767 mh-mime-save-parts-directory)
768 (read-file-name (format
769 "Store in what directory? [%s] "
770 mh-mime-save-parts-directory)
771 "" mh-mime-save-parts-directory t ""))
772 ((stringp mh-mime-save-parts-default-directory)
773 mh-mime-save-parts-default-directory)
774 (t
775 mh-mime-save-parts-directory))))
776 (if (and (equal directory "") mh-mime-save-parts-directory)
777 (setq directory mh-mime-save-parts-directory))
778 (if (not (file-directory-p directory))
779 (message "No directory specified.")
780 (if (equal nil mh-mime-save-parts-default-directory)
781 (setq mh-mime-save-parts-directory directory))
782 (save-excursion
924df208 783 (set-buffer (get-buffer-create mh-log-buffer))
bdcfe844
BW
784 (cd directory)
785 (setq mh-mime-save-parts-directory directory)
924df208
BW
786 (let ((initial-size (mh-truncate-log-buffer)))
787 (apply 'call-process
788 (expand-file-name command mh-progs) nil t nil
789 (mh-list-to-string (list folder msg "-auto")))
790 (if (> (buffer-size) initial-size)
791 (save-window-excursion
792 (switch-to-buffer-other-window mh-log-buffer)
793 (sit-for 3))))))))
bdcfe844
BW
794
795;; Avoid errors if gnus-sum isn't loaded yet...
796(defvar gnus-newsgroup-charset nil)
797(defvar gnus-newsgroup-name nil)
798
924df208
BW
799(defun mh-decode-message-body ()
800 "Decode message based on charset.
801If message has been encoded for transfer take that into account."
802 (let* ((ct (ignore-errors (mail-header-parse-content-type
803 (message-fetch-field "Content-Type" t))))
804 (charset (mail-content-type-get ct 'charset))
805 (cte (message-fetch-field "Content-Transfer-Encoding")))
806 (when (stringp cte) (setq cte (mail-header-strip cte)))
807 (when (or (not ct) (equal (car ct) "text/plain"))
808 (save-restriction
809 (narrow-to-region (min (1+ (mh-mail-header-end)) (point-max))
810 (point-max))
811 (mm-decode-body charset
812 (and cte (intern (downcase
813 (gnus-strip-whitespace cte))))
814 (car ct))))))
815
816;;;###mh-autoload
817(defun mh-decode-message-header ()
818 "Decode RFC2047 encoded message header fields."
819 (when mh-decode-mime-flag
820 (let ((buffer-read-only nil))
821 (rfc2047-decode-region (point-min) (mh-mail-header-end)))))
822
c3d9274a 823;;;###mh-autoload
bdcfe844
BW
824(defun mh-mime-display (&optional pre-dissected-handles)
825 "Display (and possibly decode) MIME handles.
826Optional argument, PRE-DISSECTED-HANDLES is a list of MIME handles. If
827present they are displayed otherwise the buffer is parsed and then
828displayed."
829 (let ((handles ())
924df208
BW
830 (folder mh-show-folder-buffer)
831 (raw-message-data (buffer-string)))
c3d9274a
BW
832 (flet ((mm-handle-set-external-undisplayer
833 (handle function)
834 (mh-handle-set-external-undisplayer folder handle function)))
924df208
BW
835 (goto-char (point-min))
836 (unless (search-forward "\n\n" nil t)
837 (goto-char (point-max))
838 (insert "\n\n"))
839
840 (condition-case err
841 (progn
842 ;; If needed dissect the current buffer
843 (if pre-dissected-handles
844 (setq handles pre-dissected-handles)
845 (setq handles (or (mm-dissect-buffer nil) (mm-uu-dissect)))
846 (setf (mh-mime-handles (mh-buffer-data))
847 (mm-merge-handles handles
848 (mh-mime-handles (mh-buffer-data))))
849 (unless handles (mh-decode-message-body)))
850
851 (when (and handles
852 (or (not (stringp (car handles))) (cdr handles)))
853 ;; Goto start of message body
854 (goto-char (point-min))
855 (or (search-forward "\n\n" nil t) (goto-char (point-max)))
bdcfe844 856
924df208
BW
857 ;; Delete the body
858 (delete-region (point) (point-max))
bdcfe844 859
924df208
BW
860 ;; Display the MIME handles
861 (mh-mime-display-part handles)))
862 (error
863 (message "Please report this error. The error message is:\n %s"
864 (error-message-string err))
865 (delete-region (point-min) (point-max))
866 (insert raw-message-data))))))
bdcfe844
BW
867
868(defun mh-mime-display-part (handle)
869 "Decides the viewer to call based on the type of HANDLE."
870 (cond ((null handle) nil)
871 ((not (stringp (car handle)))
872 (mh-mime-display-single handle))
873 ((equal (car handle) "multipart/alternative")
874 (mh-mime-display-alternative (cdr handle)))
875 ((and mh-gnus-pgp-support-flag
876 (or (equal (car handle) "multipart/signed")
877 (equal (car handle) "multipart/encrypted")))
878 (mh-mime-display-security handle))
879 (t (mh-mime-display-mixed (cdr handle)))))
880
881(defun mh-mime-display-alternative (handles)
882 "Choose among the alternatives, HANDLES the part that will be displayed.
883If no part is preferred then all the parts are displayed."
884 (let ((preferred (mm-preferred-alternative handles)))
885 (cond ((and preferred (stringp (car preferred)))
886 (mh-mime-display-part preferred))
887 (preferred
888 (save-restriction
889 (narrow-to-region (point) (if (eobp) (point) (1+ (point))))
c3d9274a 890 (mh-mime-display-single preferred)
bdcfe844
BW
891 (goto-char (point-max))))
892 (t (mh-mime-display-mixed handles)))))
893
894(defun mh-mime-display-mixed (handles)
895 "Display the list of MIME parts, HANDLES recursively."
896 (mapcar #'mh-mime-display-part handles))
897
898(defun mh-mime-part-index (handle)
899 "Generate the button number for MIME part, HANDLE.
900Notice that a hash table is used to display the same number when buttons need
901to be displayed multiple times (for instance when nested messages are
902opened)."
903 (or (gethash handle (mh-mime-part-index-hash (mh-buffer-data)))
904 (setf (gethash handle (mh-mime-part-index-hash (mh-buffer-data)))
905 (incf (mh-mime-parts-count (mh-buffer-data))))))
906
907;;; Avoid compiler warnings for XEmacs functions...
908(eval-when (compile)
c3d9274a
BW
909 (loop for function in '(glyph-width window-pixel-width
910 glyph-height window-pixel-height)
911 do (or (fboundp function) (defalias function 'ignore))))
bdcfe844
BW
912
913(defun mh-small-image-p (handle)
914 "Decide whether HANDLE is a \"small\" image that can be displayed inline.
915This is only useful if a Content-Disposition header is not present."
916 (let ((media-test (caddr (assoc (car (mm-handle-type handle))
917 mh-mm-inline-media-tests)))
918 (mm-inline-large-images t))
919 (and media-test
920 (equal (mm-handle-media-supertype handle) "image")
c3d9274a
BW
921 (funcall media-test handle) ; Since mm-inline-large-images is T,
922 ; this only tells us if the image is
923 ; something that emacs can display
bdcfe844
BW
924 (let* ((image (mm-get-image handle)))
925 (cond ((fboundp 'glyph-width)
926 ;; XEmacs -- totally untested, copied from gnus
924df208
BW
927 (and (mh-funcall-if-exists glyphp image)
928 (< (glyph-width image)
bdcfe844
BW
929 (or mh-max-inline-image-width
930 (window-pixel-width)))
931 (< (glyph-height image)
932 (or mh-max-inline-image-height
933 (window-pixel-height)))))
934 ((fboundp 'image-size)
935 ;; Emacs21 -- copied from gnus
924df208
BW
936 (let ((size (mh-funcall-if-exists image-size image)))
937 (and size
938 (< (cdr size)
bdcfe844
BW
939 (or mh-max-inline-image-height
940 (1- (window-height))))
941 (< (car size)
942 (or mh-max-inline-image-width (window-width))))))
943 (t
944 ;; Can't show image inline
945 nil))))))
946
c3d9274a
BW
947(defun mh-inline-vcard-p (handle)
948 "Decide if HANDLE is a vcard that must be displayed inline."
949 (let ((type (mm-handle-type handle)))
924df208
BW
950 (and (or (featurep 'vcard) (fboundp 'vcard-pretty-print))
951 (consp type)
c3d9274a
BW
952 (equal (car type) "text/x-vcard")
953 (save-excursion
954 (save-restriction
955 (widen)
956 (goto-char (point-min))
957 (not (re-search-forward "^-- $" nil t)))))))
958
bdcfe844
BW
959(defun mh-mime-display-single (handle)
960 "Display a leaf node, HANDLE in the MIME tree."
961 (let* ((type (mm-handle-media-type handle))
962 (small-image-flag (mh-small-image-p handle))
963 (attachmentp (equal (car (mm-handle-disposition handle))
964 "attachment"))
965 (inlinep (and (equal (car (mm-handle-disposition handle)) "inline")
966 (mm-inlinable-p handle)
967 (mm-inlined-p handle)))
c3d9274a
BW
968 (displayp (or inlinep ; show if inline OR
969 (mh-inline-vcard-p handle); inline vcard OR
970 (and (not attachmentp) ; if not an attachment
971 (or small-image-flag ; and small image
972 ; and user wants inline
bdcfe844
BW
973 (and (not (equal
974 (mm-handle-media-supertype handle)
975 "image"))
976 (mm-inlinable-p handle)
977 (mm-inlined-p handle)))))))
978 (save-restriction
979 (narrow-to-region (point) (if (eobp) (point) (1+ (point))))
980 (cond ((and mh-gnus-pgp-support-flag
981 (equal type "application/pgp-signature"))
c3d9274a 982 nil) ; skip signatures as they are already handled...
bdcfe844
BW
983 ((not displayp)
984 (insert "\n")
985 (mh-insert-mime-button handle (mh-mime-part-index handle) nil))
986 ((and displayp (not mh-display-buttons-for-inline-parts-flag))
987 (or (mm-display-part handle) (mm-display-part handle)))
988 ((and displayp mh-display-buttons-for-inline-parts-flag)
989 (insert "\n")
990 (mh-insert-mime-button handle (mh-mime-part-index handle) nil)
991 (forward-line -1)
992 (mh-mm-display-part handle)))
993 (goto-char (point-max)))))
994
924df208
BW
995(mh-do-in-xemacs
996 (defvar dots)
997 (defvar type))
998
bdcfe844
BW
999(defun mh-insert-mime-button (handle index displayed)
1000 "Insert MIME button for HANDLE.
1001INDEX is the part number that will be DISPLAYED. It is also used by commands
1002like \"K v\" which operate on individual MIME parts."
1003 ;; The button could be displayed by a previous decode. In that case
1004 ;; undisplay it if we need a hidden button.
1005 (when (and (mm-handle-displayed-p handle) (not displayed))
1006 (mm-display-part handle))
1007 (let ((name (or (mail-content-type-get (mm-handle-type handle) 'name)
1008 (mail-content-type-get (mm-handle-disposition handle)
1009 'filename)
1010 (mail-content-type-get (mm-handle-type handle) 'url)
1011 ""))
1012 (type (mm-handle-media-type handle))
1013 (description (mail-decode-encoded-word-string
1014 (or (mm-handle-description handle) "")))
1015 (dots (if (or displayed (mm-handle-displayed-p handle)) " " "..."))
1016 long-type begin end)
1017 (if (string-match ".*/" name) (setq name (substring name (match-end 0))))
1018 (setq long-type (concat type (and (not (equal name ""))
1019 (concat "; " name))))
1020 (unless (equal description "")
1021 (setq long-type (concat " --- " long-type)))
1022 (unless (bolp) (insert "\n"))
1023 (setq begin (point))
1024 (gnus-eval-format
1025 mh-mime-button-line-format mh-mime-button-line-format-alist
1026 `(,@(gnus-local-map-property mh-mime-button-map)
c3d9274a
BW
1027 mh-callback mh-mm-display-part
1028 mh-part ,index
1029 mh-data ,handle))
bdcfe844
BW
1030 (setq end (point))
1031 (widget-convert-button
1032 'link begin end
1033 :mime-handle handle
1034 :action 'mh-widget-press-button
1035 :button-keymap mh-mime-button-map
1036 :help-echo
1037 "Mouse-2 click or press RET (in show buffer) to toggle display")))
1038
1039;; There is a bug in Gnus inline image display due to which an extra line
1040;; gets inserted every time it is viewed. To work around that problem we are
1041;; using an extra property 'mh-region to remember the region that is added
1042;; when the button is clicked. The region is then deleted to make sure that
1043;; no extra lines get inserted.
1044(defun mh-mm-display-part (handle)
1045 "Toggle display of button for MIME part, HANDLE."
1046 (beginning-of-line)
1047 (let ((id (get-text-property (point) 'mh-part))
1048 (point (point))
1049 (window (selected-window))
1050 (mail-parse-charset 'nil)
1051 (mail-parse-ignored-charsets nil)
1052 region buffer-read-only)
1053 (save-excursion
1054 (unwind-protect
1055 (let ((win (get-buffer-window (current-buffer) t)))
1056 (when win
1057 (select-window win))
1058 (goto-char point)
1059
1060 (if (mm-handle-displayed-p handle)
1061 ;; This will remove the part.
1062 (progn
1063 ;; Delete the button and displayed part (if any)
1064 (let ((region (get-text-property point 'mh-region)))
924df208
BW
1065 (when (and region (fboundp 'remove-images))
1066 (mh-funcall-if-exists
1067 remove-images (car region) (cdr region)))
bdcfe844
BW
1068 (mm-display-part handle)
1069 (when region
1070 (delete-region (car region) (cdr region))))
1071 ;; Delete button (if it still remains). This happens for
1072 ;; externally displayed parts where the previous step does
1073 ;; nothing.
1074 (unless (eolp)
1075 (delete-region (point) (progn (forward-line) (point)))))
1076 (save-restriction
1077 (delete-region (point) (progn (forward-line 1) (point)))
1078 (narrow-to-region (point) (point))
1079 ;; Maybe we need another unwind-protect here.
1080 (when (equal (mm-handle-media-supertype handle) "image")
1081 (insert "\n"))
1082 (when (and (not (eq (ignore-errors (mm-display-part handle))
1083 'inline))
1084 (equal (mm-handle-media-supertype handle)
1085 "image"))
1086 (goto-char (point-min))
1087 (delete-char 1))
1088 (when (equal (mm-handle-media-supertype handle) "text")
1089 (when (eq mh-highlight-citation-p 'gnus)
1090 (mh-gnus-article-highlight-citation))
1091 (mh-display-smileys)
1092 (mh-display-emphasis))
1093 (setq region (cons (progn (goto-char (point-min))
1094 (point-marker))
1095 (progn (goto-char (point-max))
1096 (point-marker)))))))
1097 (when (window-live-p window)
1098 (select-window window))
1099 (goto-char point)
1100 (beginning-of-line)
1101 (mh-insert-mime-button handle id (mm-handle-displayed-p handle))
1102 (goto-char point)
1103 (when region
1104 (add-text-properties (line-beginning-position) (line-end-position)
1105 `(mh-region ,region)))))))
1106
c3d9274a 1107;;;###mh-autoload
bdcfe844
BW
1108(defun mh-press-button ()
1109 "Press MIME button.
1110If the MIME part is visible then it is removed. Otherwise the part is
1111displayed."
1112 (interactive)
1113 (let ((mm-inline-media-tests mh-mm-inline-media-tests)
1114 (data (get-text-property (point) 'mh-data))
1115 (function (get-text-property (point) 'mh-callback))
1116 (buffer-read-only nil)
1117 (folder mh-show-folder-buffer))
c3d9274a
BW
1118 (flet ((mm-handle-set-external-undisplayer
1119 (handle function)
1120 (mh-handle-set-external-undisplayer folder handle function)))
bdcfe844
BW
1121 (when (and function (eolp))
1122 (backward-char))
1123 (unwind-protect (and function (funcall function data))
1124 (set-buffer-modified-p nil)))))
1125
c3d9274a 1126;;;###mh-autoload
bdcfe844
BW
1127(defun mh-push-button (event)
1128 "Click MIME button for EVENT.
1129If the MIME part is visible then it is removed. Otherwise the part is
1130displayed. This function is called when the mouse is used to click the MIME
1131button."
1132 (interactive "e")
924df208
BW
1133 (save-excursion
1134 (let* ((event-window
1135 (or (mh-funcall-if-exists posn-window (event-start event));GNU Emacs
1136 (mh-funcall-if-exists event-window event))) ;XEmacs
1137 (event-position
1138 (or (mh-funcall-if-exists posn-point (event-start event)) ;GNU Emacs
1139 (mh-funcall-if-exists event-closest-point event))) ;XEmacs
1140 (original-window (selected-window))
1141 (original-position (progn
1142 (set-buffer (window-buffer event-window))
1143 (set-marker (make-marker) (point))))
1144 (folder mh-show-folder-buffer)
1145 (mm-inline-media-tests mh-mm-inline-media-tests)
1146 (data (get-text-property event-position 'mh-data))
1147 (function (get-text-property event-position 'mh-callback))
1148 (buffer-read-only nil))
1149 (unwind-protect
1150 (progn
1151 (select-window event-window)
1152 (flet ((mm-handle-set-external-undisplayer (handle func)
1153 (mh-handle-set-external-undisplayer folder handle func)))
1154 (goto-char event-position)
1155 (and function (funcall function data))))
1156 (set-buffer-modified-p nil)
1157 (goto-char original-position)
1158 (set-marker original-position nil)
1159 (select-window original-window)))))
bdcfe844 1160
c3d9274a 1161;;;###mh-autoload
bdcfe844
BW
1162(defun mh-mime-save-part ()
1163 "Save MIME part at point."
1164 (interactive)
1165 (let ((data (get-text-property (point) 'mh-data)))
1166 (when data
1167 (let ((mm-default-directory mh-mime-save-parts-directory))
c3d9274a
BW
1168 (mh-mm-save-part data)
1169 (setq mh-mime-save-parts-directory mm-default-directory)))))
bdcfe844 1170
c3d9274a 1171;;;###mh-autoload
bdcfe844
BW
1172(defun mh-mime-inline-part ()
1173 "Toggle display of the raw MIME part."
1174 (interactive)
1175 (let* ((buffer-read-only nil)
1176 (data (get-text-property (point) 'mh-data))
1177 (inserted-flag (get-text-property (point) 'mh-mime-inserted))
1178 (displayed-flag (mm-handle-displayed-p data))
1179 (point (point))
1180 start end)
1181 (cond ((and data (not inserted-flag) (not displayed-flag))
1182 (let ((contents (mm-get-part data)))
1183 (add-text-properties (line-beginning-position) (line-end-position)
1184 '(mh-mime-inserted t))
1185 (setq start (point-marker))
1186 (forward-line 1)
1187 (mm-insert-inline data contents)
1188 (setq end (point-marker))
1189 (add-text-properties
1190 start (progn (goto-char start) (line-end-position))
1191 `(mh-region (,start . ,end)))))
1192 ((and data (or inserted-flag displayed-flag))
1193 (mh-press-button)
1194 (message "MIME part already inserted")))
1195 (goto-char point)
1196 (set-buffer-modified-p nil)))
1197
1198(defun mh-widget-press-button (widget el)
1199 "Callback for widget, WIDGET.
1200Parameter EL is unused."
1201 (goto-char (widget-get widget :from))
1202 (mh-press-button))
1203
1204(defun mh-mime-display-security (handle)
1205 "Display PGP encrypted/signed message, HANDLE."
1206 (insert "\n")
1207 (save-restriction
1208 (narrow-to-region (point) (point))
1209 (mh-insert-mime-security-button handle)
1210 (mh-mime-display-mixed (cdr handle))
1211 (insert "\n")
1212 (let ((mh-mime-security-button-line-format
c3d9274a 1213 mh-mime-security-button-end-line-format))
bdcfe844
BW
1214 (mh-insert-mime-security-button handle))
1215 (mm-set-handle-multipart-parameter
1216 handle 'mh-region
1217 (cons (set-marker (make-marker) (point-min))
1218 (set-marker (make-marker) (point-max))))))
1219
1220;;; I rewrote the security part because Gnus doesn't seem to ever minimize
1221;;; the button. That is once the mime-security button is pressed there seems
1222;;; to be no way of getting rid of the inserted text.
1223(defun mh-mime-security-show-details (handle)
1224 "Toggle display of detailed security info for HANDLE."
1225 (let ((details (mm-handle-multipart-ctl-parameter handle 'gnus-details)))
1226 (when details
1227 (let ((mh-mime-security-button-pressed
c3d9274a 1228 (not (get-text-property (point) 'mh-button-pressed)))
bdcfe844 1229 (mh-mime-security-button-line-format
c3d9274a 1230 (get-text-property (point) 'mh-line-format)))
bdcfe844
BW
1231 (forward-char -1)
1232 (while (eq (get-text-property (point) 'mh-line-format)
1233 mh-mime-security-button-line-format)
1234 (forward-char -1))
1235 (forward-char)
1236 (save-restriction
1237 (narrow-to-region (point) (point))
1238 (mh-insert-mime-security-button handle))
1239 (delete-region
1240 (point)
1241 (or (text-property-not-all
1242 (point) (point-max)
1243 'mh-line-format mh-mime-security-button-line-format)
1244 (point-max)))
1245 (forward-line -1)))))
1246
1247(defun mh-mime-security-press-button (handle)
1248 "Callback from security button for part HANDLE."
1249 (when (mm-handle-multipart-ctl-parameter handle 'gnus-info)
1250 (mh-mime-security-show-details handle)))
1251
1252;; These variables should already be initialized in mm-decode.el if we have a
1253;; recent enough Gnus. The defvars are here to avoid compiler warnings.
1254(defvar mm-verify-function-alist nil)
1255(defvar mm-decrypt-function-alist nil)
1256
1257(defvar pressed-details)
1258
1259(defun mh-insert-mime-security-button (handle)
1260 "Display buttons for PGP message, HANDLE."
1261 (let* ((protocol (mm-handle-multipart-ctl-parameter handle 'protocol))
1262 (crypto-type (or (nth 2 (assoc protocol mm-verify-function-alist))
1263 (nth 2 (assoc protocol mm-decrypt-function-alist))
1264 "Unknown"))
1265 (type (concat crypto-type
1266 (if (equal (car handle) "multipart/signed")
1267 " Signed" " Encrypted")
1268 " Part"))
1269 (info (or (mm-handle-multipart-ctl-parameter handle 'gnus-info)
1270 "Undecided"))
1271 (details (mm-handle-multipart-ctl-parameter handle 'gnus-details))
1272 pressed-details begin end)
1273 (setq details (if details (concat "\n" details) ""))
1274 (setq pressed-details (if mh-mime-security-button-pressed details ""))
1275 (unless (bolp) (insert "\n"))
1276 (setq begin (point))
1277 (gnus-eval-format
1278 mh-mime-security-button-line-format
1279 mh-mime-security-button-line-format-alist
1280 `(,@(gnus-local-map-property mh-mime-security-button-map)
c3d9274a
BW
1281 mh-button-pressed ,mh-mime-security-button-pressed
1282 mh-callback mh-mime-security-press-button
1283 mh-line-format ,mh-mime-security-button-line-format
1284 mh-data ,handle))
bdcfe844
BW
1285 (setq end (point))
1286 (widget-convert-button 'link begin end
1287 :mime-handle handle
1288 :action 'mh-widget-press-button
1289 :button-keymap mh-mime-security-button-map
1290 :help-echo "Mouse-2 click or press RET (in show buffer) to see security details.")
1291 (when (equal info "Failed")
1292 (let* ((type (if (equal (car handle) "multipart/signed")
1293 "verification" "decryption"))
1294 (warning (if (equal type "decryption")
1295 "(passphrase may be incorrect)" "")))
1296 (message "%s %s failed %s" crypto-type type warning)))))
1297
1298(defun mh-mm-inline-message (handle)
1299 "Display message, HANDLE.
1300The function decodes the message and displays it. It avoids decoding the same
1301message multiple times."
1302 (let ((b (point))
bdcfe844
BW
1303 (clean-message-header mh-clean-message-header-flag)
1304 (invisible-headers mh-invisible-headers)
1305 (visible-headers mh-visible-headers))
bdcfe844
BW
1306 (save-excursion
1307 (save-restriction
1308 (narrow-to-region b b)
1309 (mm-insert-part handle)
1310 (mh-mime-display
1311 (or (gethash handle (mh-mime-handles-cache (mh-buffer-data)))
1312 (setf (gethash handle (mh-mime-handles-cache (mh-buffer-data)))
1313 (let ((handles (or (mm-dissect-buffer nil)
1314 (mm-uu-dissect))))
1315 (setf (mh-mime-handles (mh-buffer-data))
1316 (mm-merge-handles
1317 handles (mh-mime-handles (mh-buffer-data))))
1318 handles))))
1319
1320 (goto-char (point-min))
924df208 1321 (mh-show-xface)
bdcfe844
BW
1322 (cond (clean-message-header
1323 (mh-clean-msg-header (point-min)
1324 invisible-headers
1325 visible-headers)
1326 (goto-char (point-min)))
1327 (t
1328 (mh-start-of-uncleaned-message)))
924df208 1329 (mh-decode-message-header)
bdcfe844
BW
1330 (mh-show-addr)
1331 ;; The other highlighting types don't need anything special
1332 (when (eq mh-highlight-citation-p 'gnus)
1333 (mh-gnus-article-highlight-citation))
1334 (goto-char (point-min))
1335 (insert "\n------- Forwarded Message\n\n")
1336 (mh-display-smileys)
1337 (mh-display-emphasis)
1338 (mm-handle-set-undisplayer
1339 handle
1340 `(lambda ()
1341 (let (buffer-read-only)
1342 (if (fboundp 'remove-specifier)
1343 ;; This is only valid on XEmacs.
1344 (mapcar (lambda (prop)
1345 (remove-specifier
1346 (face-property 'default prop) (current-buffer)))
1347 '(background background-pixmap foreground)))
1348 (delete-region ,(point-min-marker) ,(point-max-marker)))))))))
1349
1350(provide 'mh-mime)
1351
1352;;; Local Variables:
c3d9274a 1353;;; indent-tabs-mode: nil
bdcfe844
BW
1354;;; sentence-end-double-space: nil
1355;;; End:
1356
60370d40 1357;;; mh-mime.el ends here