diff lisp/vm/vm-mime.el @ 20:859a2309aef8 r19-15b93

Import from CVS: tag r19-15b93
author cvs
date Mon, 13 Aug 2007 08:50:05 +0200
parents
children 4103f0995bd7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/vm/vm-mime.el	Mon Aug 13 08:50:05 2007 +0200
@@ -0,0 +1,2495 @@
+;;; MIME support functions
+;;; Copyright (C) 1997 Kyle E. Jones
+;;;
+;;; This program is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 1, or (at your option)
+;;; any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+(provide 'vm-mime)
+
+(defun vm-mime-error (&rest args)
+  (signal 'vm-mime-error (list (apply 'format args)))
+  (error "can't return from vm-mime-error"))
+
+(if (fboundp 'define-error)
+    (define-error 'vm-mime-error "MIME error")
+  (put 'vm-mime-error 'error-conditions '(vm-mime-error error))
+  (put 'vm-mime-error 'error-message "MIME error"))
+
+(defun vm-mm-layout-type (e) (aref e 0))
+(defun vm-mm-layout-encoding (e) (aref e 1))
+(defun vm-mm-layout-id (e) (aref e 2))
+(defun vm-mm-layout-description (e) (aref e 3))
+(defun vm-mm-layout-disposition (e) (aref e 4))
+(defun vm-mm-layout-header-start (e) (aref e 5))
+(defun vm-mm-layout-body-start (e) (aref e 6))
+(defun vm-mm-layout-body-end (e) (aref e 7))
+(defun vm-mm-layout-parts (e) (aref e 8))
+(defun vm-mm-layout-cache (e) (aref e 9))
+
+(defun vm-set-mm-layout-cache (e c) (aset e 8 c))
+
+(defun vm-mm-layout (m)
+  (or (vm-mime-layout-of m)
+      (progn (vm-set-mime-layout-of
+	      m
+	      (condition-case data
+		  (vm-mime-parse-entity m)
+		(vm-mime-error (apply 'message (cdr data)))))
+	     (vm-mime-layout-of m))))
+
+(defun vm-mm-encoded-header (m)
+  (or (vm-mime-encoded-header-flag-of m)
+      (progn (setq m (vm-real-message-of m))
+	     (vm-set-mime-encoded-header-flag-of
+	      m
+	      (save-excursion
+		(set-buffer (vm-buffer-of m))
+		(save-excursion
+		  (save-restriction
+		    (widen)
+		    (goto-char (vm-headers-of m))
+		    (or (re-search-forward vm-mime-encoded-word-regexp
+					   (vm-text-of m) t)
+			'none)))))
+	     (vm-mime-encoded-header-flag-of m))))
+
+(defun vm-mime-Q-decode-region (start end)
+  (let ((buffer-read-only nil))
+    (subst-char-in-region start end ?_ (string-to-char " ") t)
+    (vm-mime-qp-decode-region start end)))
+
+(fset 'vm-mime-B-decode-region 'vm-mime-base64-decode-region)
+
+(defun vm-mime-Q-encode-region (start end)
+  (let ((buffer-read-only nil))
+    (subst-char-in-region start end (string-to-char " ") ?_ t)
+    (vm-mime-qp-encode-region start end)))
+
+(fset 'vm-mime-B-encode-region 'vm-mime-base64-encode-region)
+
+(defun vm-mime-Q-decode-string (string)
+  (vm-with-string-as-region string 'vm-mime-Q-decode-region))
+
+(defun vm-mime-B-decode-string (string)
+  (vm-with-string-as-region string 'vm-mime-B-decode-region))
+
+(defun vm-mime-Q-encode-string (string)
+  (vm-with-string-as-region string 'vm-mime-Q-encode-region))
+
+(defun vm-mime-B-encode-string (string)
+  (vm-with-string-as-region string 'vm-mime-B-encode-region))
+
+(defun vm-mime-crlf-to-lf-region (start end)
+  (let ((buffer-read-only nil))
+    (save-excursion
+      (save-restriction
+	(narrow-to-region start end)
+	(goto-char start)
+	(while (search-forward "\r\n" nil t)
+	  (delete-char -2)
+	  (insert "\n"))))))
+      
+(defun vm-mime-lf-to-crlf-region (start end)
+  (let ((buffer-read-only nil))
+    (save-excursion
+      (save-restriction
+	(narrow-to-region start end)
+	(goto-char start)
+	(while (search-forward "\n" nil t)
+	  (delete-char -1)
+	  (insert "\r\n"))))))
+      
+(defun vm-mime-charset-decode-region (charset start end)
+  (let ((buffer-read-only nil)
+	(cell (vm-mime-charset-internally-displayable-p charset))
+	(opoint (point)))
+    (cond ((and cell (vm-xemacs-mule-p) (eq (device-type) 'x))
+	   (decode-coding-region start end (car cell))))
+    ;; In XEmacs 20.0 beta93 decode-coding-region moves point.
+    (goto-char opoint)))
+
+(defun vm-mime-transfer-decode-region (layout start end)
+  (let ((case-fold-search t) (crlf nil))
+    (cond ((string-match "^base64$" (vm-mm-layout-encoding layout))
+	   (cond ((vm-mime-types-match "text"
+				       (car (vm-mm-layout-type layout)))
+		  (setq crlf t))
+		 ((vm-mime-types-match "message"
+				       (car (vm-mm-layout-type layout)))
+		  (setq crlf t)))
+	   (vm-mime-base64-decode-region start end crlf))
+	  ((string-match "^quoted-printable$"
+			 (vm-mm-layout-encoding layout))
+	   (vm-mime-qp-decode-region start end)))))
+
+(defun vm-mime-base64-decode-region (start end &optional crlf)
+  (vm-unsaved-message "Decoding base64...")
+  (let ((work-buffer nil)
+	(done nil)
+	(counter 0)
+	(bits 0)
+	(lim 0) inputpos
+	(non-data-chars (concat "^=" vm-mime-base64-alphabet)))
+    (unwind-protect
+	(save-excursion
+	  (setq work-buffer (generate-new-buffer " *vm-work*"))
+	  (buffer-disable-undo work-buffer)
+	  (if vm-mime-base64-decoder-program
+	      (let* ((binary-process-output t) ; any text already has CRLFs
+		     (status (apply 'vm-run-command-on-region
+				   start end work-buffer
+				   vm-mime-base64-decoder-program
+				   vm-mime-base64-decoder-switches)))
+		(if (not (eq status t))
+		    (vm-mime-error "%s" (cdr status))))
+	    (goto-char start)
+	    (skip-chars-forward non-data-chars end)
+	    (while (not done)
+	      (setq inputpos (point))
+	      (cond
+	       ((> (skip-chars-forward vm-mime-base64-alphabet end) 0)
+		(setq lim (point))
+		(while (< inputpos lim)
+		  (setq bits (+ bits 
+				(aref vm-mime-base64-alphabet-decoding-vector
+				      (char-after inputpos))))
+		  (vm-increment counter)
+		  (vm-increment inputpos)
+		  (cond ((= counter 4)
+			 (vm-insert-char (lsh bits -16) 1 nil work-buffer)
+			 (vm-insert-char (logand (lsh bits -8) 255) 1 nil
+					 work-buffer)
+			 (vm-insert-char (logand bits 255) 1 nil work-buffer)
+			 (setq bits 0 counter 0))
+			(t (setq bits (lsh bits 6)))))))
+	      (cond
+	       ((= (point) end)
+		(if (not (zerop counter))
+		    (vm-mime-error "at least %d bits missing at end of base64 encoding"
+				   (* (- 4 counter) 6)))
+		(setq done t))
+	       ((= (char-after (point)) 61) ; 61 is ASCII equals
+		(setq done t)
+		(cond ((= counter 1)
+		       (vm-mime-error "at least 2 bits missing at end of base64 encoding"))
+		      ((= counter 2)
+		       (vm-insert-char (lsh bits -10) 1 nil work-buffer))
+		      ((= counter 3)
+		       (vm-insert-char (lsh bits -16) 1 nil work-buffer)
+		       (vm-insert-char (logand (lsh bits -8) 255)
+				       1 nil work-buffer))
+		      ((= counter 0) t)))
+	       (t (skip-chars-forward non-data-chars end)))))
+	  (and crlf
+	       (save-excursion
+		 (set-buffer work-buffer)
+		 (vm-mime-crlf-to-lf-region (point-min) (point-max))))
+	  (or (markerp end) (setq end (vm-marker end)))
+	  (goto-char start)
+	  (insert-buffer-substring work-buffer)
+	  (delete-region (point) end))
+      (and work-buffer (kill-buffer work-buffer))))
+  (vm-unsaved-message "Decoding base64... done"))
+
+(defun vm-mime-base64-encode-region (start end &optional crlf)
+  (vm-unsaved-message "Encoding base64...")
+  (let ((work-buffer nil)
+	(counter 0)
+	(cols 0)
+	(bits 0)
+	(alphabet vm-mime-base64-alphabet)
+	inputpos)
+    (unwind-protect
+	(save-excursion
+	  (setq work-buffer (generate-new-buffer " *vm-work*"))
+	  (buffer-disable-undo work-buffer)
+	  (if crlf
+	      (progn
+		(or (markerp end) (setq end (vm-marker end)))
+		(vm-mime-lf-to-crlf-region start end)))
+	  (if vm-mime-base64-encoder-program
+	      (let ((status (apply 'vm-run-command-on-region
+				   start end work-buffer
+				   vm-mime-base64-encoder-program
+				   vm-mime-base64-encoder-switches)))
+		(if (not (eq status t))
+		    (vm-mime-error "%s" (cdr status))))
+	    (setq inputpos start)
+	    (while (< inputpos end)
+	      (setq bits (+ bits (char-after inputpos)))
+	      (vm-increment counter)
+	      (cond ((= counter 3)
+		     (vm-insert-char (aref alphabet (lsh bits -18)) 1 nil
+				     work-buffer)
+		     (vm-insert-char (aref alphabet (logand (lsh bits -12) 63))
+				     1 nil work-buffer)
+		     (vm-insert-char (aref alphabet (logand (lsh bits -6) 63))
+				     1 nil work-buffer)
+		     (vm-insert-char (aref alphabet (logand bits 63)) 1 nil
+				     work-buffer)
+		     (setq cols (+ cols 4))
+		     (cond ((= cols 72)
+			    (vm-insert-char ?\n 1 nil work-buffer)
+			    (setq cols 0)))
+		     (setq bits 0 counter 0))
+		    (t (setq bits (lsh bits 8))))
+	      (vm-increment inputpos))
+	    ;; write out any remaining bits with appropriate padding
+	    (if (= counter 0)
+		nil
+	      (setq bits (lsh bits (- 16 (* 8 counter))))
+	      (vm-insert-char (aref alphabet (lsh bits -18)) 1 nil
+			      work-buffer)
+	      (vm-insert-char (aref alphabet (logand (lsh bits -12) 63))
+			      1 nil work-buffer)
+	      (if (= counter 1)
+		  (vm-insert-char ?= 2 nil work-buffer)
+		(vm-insert-char (aref alphabet (logand (lsh bits -6) 63))
+				1 nil work-buffer)
+		(vm-insert-char ?= 1 nil work-buffer)))
+	    (if (> cols 0)
+		(vm-insert-char ?\n 1 nil work-buffer)))
+	  (or (markerp end) (setq end (vm-marker end)))
+	  (goto-char start)
+	  (insert-buffer-substring work-buffer)
+	  (delete-region (point) end))
+      (and work-buffer (kill-buffer work-buffer))))
+  (vm-unsaved-message "Encoding base64... done"))
+
+(defun vm-mime-qp-decode-region (start end)
+  (vm-unsaved-message "Decoding quoted-printable...")
+  (let ((work-buffer nil)
+	(buf (current-buffer))
+	(case-fold-search nil)
+	(hex-digit-alist '((?0 .  0)  (?1 .  1)  (?2 .  2)  (?3 .  3)
+			   (?4 .  4)  (?5 .  5)  (?6 .  6)  (?7 .  7)
+			   (?8 .  8)  (?9 .  9)  (?A . 10)  (?B . 11)
+			   (?C . 12)  (?D . 13)  (?E . 14)  (?F . 15)))
+	inputpos stop-point copy-point)
+    (unwind-protect
+	(save-excursion
+	  (setq work-buffer (generate-new-buffer " *vm-work*"))
+	  (buffer-disable-undo work-buffer)
+	  (goto-char start)
+	  (setq inputpos start)
+	  (while (< inputpos end)
+	    (skip-chars-forward "^=\n" end)
+	    (setq stop-point (point))
+	    (cond ((looking-at "\n")
+		   ;; spaces or tabs before a hard line break must be ignored
+		   (skip-chars-backward " \t")
+		   (setq copy-point (point))
+		   (goto-char stop-point))
+		  (t (setq copy-point stop-point)))
+	    (save-excursion
+	      (set-buffer work-buffer)
+	      (insert-buffer-substring buf inputpos copy-point))
+	    (cond ((= (point) end) t)
+		  ((looking-at "\n")
+		   (vm-insert-char ?\n 1 nil work-buffer)
+		   (forward-char))
+		  (t ;; looking at =
+		   (forward-char)
+		   (cond ((looking-at "[0-9A-F][0-9A-F]")
+			  (vm-insert-char (+ (* (cdr (assq (char-after (point))
+							   hex-digit-alist))
+						16)
+					     (cdr (assq (char-after
+							 (1+ (point)))
+							hex-digit-alist)))
+					  1 nil work-buffer)
+			  (forward-char 2))
+			 ((looking-at "\n") ; soft line break
+			  (forward-char))
+			 ((looking-at "\r")
+			  ;; assume the user's goatfucking
+			  ;; delivery software didn't convert
+			  ;; from Internet's CRLF newline
+			  ;; convention to the local LF
+			  ;; convention.
+			  (forward-char))
+			 ((looking-at "[ \t]")
+			  ;; garbage added in transit
+			  (skip-chars-forward " \t" end))
+			 (t (vm-mime-error "something other than line break or hex digits after = in quoted-printable encoding")))))
+	    (setq inputpos (point)))
+	  (or (markerp end) (setq end (vm-marker end)))
+	  (goto-char start)
+	  (insert-buffer-substring work-buffer)
+	  (delete-region (point) end))
+      (and work-buffer (kill-buffer work-buffer))))
+  (vm-unsaved-message "Decoding quoted-printable... done"))
+
+(defun vm-mime-qp-encode-region (start end)
+  (vm-unsaved-message "Encoding quoted-printable...")
+  (let ((work-buffer nil)
+	(buf (current-buffer))
+	(cols 0)
+	(hex-digit-alist '((?0 .  0)  (?1 .  1)  (?2 .  2)  (?3 .  3)
+			   (?4 .  4)  (?5 .  5)  (?6 .  6)  (?7 .  7)
+			   (?8 .  8)  (?9 .  9)  (?A . 10)  (?B . 11)
+			   (?C . 12)  (?D . 13)  (?E . 14)  (?F . 15)))
+	char inputpos)
+    (unwind-protect
+	(save-excursion
+	  (setq work-buffer (generate-new-buffer " *vm-work*"))
+	  (buffer-disable-undo work-buffer)
+	  (setq inputpos start)
+	  (while (< inputpos end)
+	    (setq char (char-after inputpos))
+	    (cond ((= char ?\n)
+		   (vm-insert-char char 1 nil work-buffer)
+		   (setq cols 0))
+		  ((and (= char 32) (not (= ?\n (char-after (1+ inputpos)))))
+		   (vm-insert-char char 1 nil work-buffer)
+		   (vm-increment cols))
+		  ((or (< char 33) (> char 126) (= char 61))
+		   (vm-insert-char ?= 1 nil work-buffer)
+		   (vm-insert-char (car (rassq (lsh char -4) hex-digit-alist))
+				   1 nil work-buffer)
+		   (vm-insert-char (car (rassq (logand char 15)
+					       hex-digit-alist))
+				   1 nil work-buffer)
+		   (setq cols (+ cols 3)))
+		  (t (vm-insert-char char 1 nil work-buffer)
+		     (vm-increment cols)))
+	    (cond ((> cols 70)
+		   (vm-insert-char ?= 1 nil work-buffer)
+		   (vm-insert-char ?\n 1 nil work-buffer)
+		   (setq cols 0)))
+	    (vm-increment inputpos))
+	  (or (markerp end) (setq end (vm-marker end)))
+	  (goto-char start)
+	  (insert-buffer-substring work-buffer)
+	  (delete-region (point) end))
+      (and work-buffer (kill-buffer work-buffer))))
+  (vm-unsaved-message "Encoding quoted-printable... done"))
+
+(defun vm-decode-mime-message-headers (m)
+  (let ((case-fold-search t)
+	(buffer-read-only nil)
+	charset encoding match-start match-end start end)
+    (save-excursion
+      (goto-char (vm-headers-of m))
+      (while (re-search-forward vm-mime-encoded-word-regexp (vm-text-of m) t)
+	(setq match-start (match-beginning 0)
+	      match-end (match-end 0)
+	      charset (match-string 1)
+	      encoding (match-string 2)
+	      start (match-beginning 3)
+	      end (vm-marker (match-end 3)))
+	;; don't change anything if we can't display the
+	;; character set properly.
+	(if (not (vm-mime-charset-internally-displayable-p charset))
+	    nil
+	  (delete-region end match-end)
+	  (cond ((string-match "B" encoding)
+		 (vm-mime-B-decode-region start end))
+		((string-match "Q" encoding)
+		 (vm-mime-Q-decode-region start end))
+		(t (vm-mime-error "unknown encoded word encoding, %s"
+				  encoding)))
+	  (vm-mime-charset-decode-region charset start end)
+	  (delete-region match-start start))))))
+
+(defun vm-decode-mime-encoded-words ()
+  (let ((case-fold-search t)
+	(buffer-read-only nil)
+	charset encoding match-start match-end start end)
+    (save-excursion
+      (goto-char (point-min))
+      (while (re-search-forward vm-mime-encoded-word-regexp nil t)
+	(setq match-start (match-beginning 0)
+	      match-end (match-end 0)
+	      charset (match-string 1)
+	      encoding (match-string 2)
+	      start (match-beginning 3)
+	      end (vm-marker (match-end 3)))
+	;; don't change anything if we can't display the
+	;; character set properly.
+	(if (not (vm-mime-charset-internally-displayable-p charset))
+	    nil
+	  (delete-region end match-end)
+	  (cond ((string-match "B" encoding)
+		 (vm-mime-B-decode-region start end))
+		((string-match "Q" encoding)
+		 (vm-mime-Q-decode-region start end))
+		(t (vm-mime-error "unknown encoded word encoding, %s"
+				  encoding)))
+	  (vm-mime-charset-decode-region charset start end)
+	  (delete-region match-start start))))))
+
+(defun vm-decode-mime-encoded-words-maybe (string)
+  (if (and vm-display-using-mime
+	   (string-match vm-mime-encoded-word-regexp string))
+      (vm-with-string-as-temp-buffer string 'vm-decode-mime-encoded-words)
+    string ))
+
+(defun vm-mime-parse-content-header (string &optional sepchar)
+  (if (null string)
+      ()
+    (let ((work-buffer nil))
+      (save-excursion
+       (unwind-protect
+	   (let ((list nil)
+		 (nonspecials "^\"\\( \t\n\r\f")
+		 start s char sp+sepchar)
+	     (if sepchar
+		 (setq nonspecials (concat nonspecials (list sepchar))
+		       sp+sepchar (concat "\t\f\n\r " (list sepchar))))
+	     (setq work-buffer (generate-new-buffer "*vm-work*"))
+	     (buffer-disable-undo work-buffer)
+	     (set-buffer work-buffer)
+	     (insert string)
+	     (goto-char (point-min))
+	     (skip-chars-forward "\t\f\n\r ")
+	     (setq start (point))
+	     (while (not (eobp))
+	       (skip-chars-forward nonspecials)
+	       (setq char (following-char))
+	       (cond ((looking-at "[ \t\n\r\f]")
+		      (delete-char 1))
+		     ((= char ?\\)
+		      (forward-char 1)
+		      (if (not (eobp))
+			  (forward-char 1)))
+		     ((and sepchar (= char sepchar))
+		      (setq s (buffer-substring start (point)))
+		      (if (or (null (string-match "^[\t\f\n\r ]+$" s))
+			      (not (string= s "")))
+			  (setq list (cons s list)))
+		      (skip-chars-forward sp+sepchar)
+		      (setq start (point)))
+		     ((looking-at " \t\n\r\f")
+		      (skip-chars-forward " \t\n\r\f"))
+		     ((= char ?\")
+		      (delete-char 1)
+		      (cond ((= (char-after (point)) ?\")
+			     (delete-char 1))
+			    ((re-search-forward "[^\\]\"" nil 0)
+			     (delete-char -1))))
+		     ((= char ?\()
+		      (let ((parens 1)
+			    (pos (point)))
+			(forward-char 1)
+			(while (and (not (eobp)) (not (zerop parens)))
+			  (re-search-forward "[()]" nil 0)
+			  (cond ((or (eobp)
+				     (= (char-after (- (point) 2)) ?\\)))
+				((= (preceding-char) ?\()
+				 (setq parens (1+ parens)))
+				(t
+				 (setq parens (1- parens)))))
+			(delete-region pos (point))))))
+	     (setq s (buffer-substring start (point)))
+	     (if (and (null (string-match "^[\t\f\n\r ]+$" s))
+		      (not (string= s "")))
+		 (setq list (cons s list)))
+	     (nreverse list))
+	(and work-buffer (kill-buffer work-buffer)))))))
+
+(defun vm-mime-get-header-contents (header-name-regexp)
+  (let ((contents nil)
+	regexp)
+    (setq regexp (concat "^\\(" header-name-regexp "\\)\\|\\(^$\\)"))
+    (save-excursion
+      (let ((case-fold-search t))
+	(if (and (re-search-forward regexp nil t)
+		 (match-beginning 1)
+		 (progn (goto-char (match-beginning 0))
+			(vm-match-header)))
+	    (vm-matched-header-contents)
+	  nil )))))
+
+(defun vm-mime-parse-entity (&optional m default-type default-encoding)
+  (let ((case-fold-search t) version type encoding id description
+	disposition boundary boundary-regexp start
+	multipart-list c-t c-t-e done p returnval)
+    (and m (vm-unsaved-message "Parsing MIME message..."))
+    (prog1
+    (catch 'return-value
+      (save-excursion
+	(if m
+	    (progn
+	      (setq m (vm-real-message-of m))
+	      (set-buffer (vm-buffer-of m))))
+	(save-excursion
+	  (save-restriction
+	    (if m
+		(progn
+		  (setq version (vm-get-header-contents m "MIME-Version:")
+			version (car (vm-mime-parse-content-header version))
+			type (vm-get-header-contents m "Content-Type:")
+			type (vm-mime-parse-content-header type ?\;)
+			encoding (or (vm-get-header-contents
+				      m "Content-Transfer-Encoding:")
+				     "7bit")
+			encoding (car (vm-mime-parse-content-header encoding))
+			id (vm-get-header-contents m "Content-ID:")
+			id (car (vm-mime-parse-content-header id))
+			description (vm-get-header-contents
+				     m "Content-Description:")
+			description (and description
+					 (if (string-match "^[ \t\n]$"
+							   description)
+					     nil
+					   description))
+			disposition (vm-get-header-contents
+				     m "Content-Disposition:")
+			disposition (and disposition
+					 (vm-mime-parse-content-header
+					  disposition ?\;)))
+		  (widen)
+		  (narrow-to-region (vm-headers-of m) (vm-text-end-of m)))
+	      (goto-char (point-min))
+	      (setq type (vm-mime-get-header-contents "Content-Type:")
+		    type (or (vm-mime-parse-content-header type ?\;)
+			     default-type)
+		    encoding (or (vm-mime-get-header-contents
+				  "Content-Transfer-Encoding:")
+				 default-encoding)
+		    encoding (car (vm-mime-parse-content-header encoding))
+		    id (vm-mime-get-header-contents "Content-ID:")
+		    id (car (vm-mime-parse-content-header id))
+		    description (vm-mime-get-header-contents
+				 "Content-Description:")
+		    description (and description (if (string-match "^[ \t\n]+$"
+								   description)
+						     nil
+						   description))
+		    disposition (vm-mime-get-header-contents
+				 "Content-Disposition:")
+		    disposition (and disposition
+				     (vm-mime-parse-content-header
+				      disposition ?\;))))
+	    (cond ((null m) t)
+		  ((null version)
+		   (throw 'return-value 'none))
+		  ((string= version "1.0") t)
+		  (t (vm-mime-error "Unsupported MIME version: %s" version)))
+	    (cond ((and m (null type))
+		   (throw 'return-value
+			  (vector '("text/plain" "charset=us-ascii")
+				  encoding id description disposition
+				  (vm-headers-of m)
+				  (vm-text-of m)
+				  (vm-text-end-of m)
+				  nil nil nil )))
+		  ((null type)
+		   (goto-char (point-min))
+		   (or (re-search-forward "^\n\\|\n\\'" nil t)
+		       (vm-mime-error "MIME part missing header/body separator line"))
+		   (vector default-type encoding id description disposition
+			   (vm-marker (point-min))
+			   (vm-marker (point))
+			   (vm-marker (point-max))
+			   nil nil nil ))
+		  ((null (string-match "[^/ ]+/[^/ ]+" (car type)))
+		   (vm-mime-error "Malformed MIME content type: %s" (car type)))
+		  ((and (string-match "^multipart/\\|^message/" (car type))
+			(null (string-match "^\\(7bit\\|8bit\\|binary\\)$"
+					    encoding)))
+		   (vm-mime-error "Opaque transfer encoding used with multipart or message type: %s, %s" (car type) encoding))
+		  ((and (string-match "^message/partial$" (car type))
+			(null (string-match "^7bit$" encoding)))
+		   (vm-mime-error "Non-7BIT transfer encoding used with message/partial message: %s" encoding))
+		  ((string-match "^multipart/digest" (car type))
+		   (setq c-t '("message/rfc822")
+			 c-t-e "7bit"))
+		  ((string-match "^multipart/" (car type))
+		   (setq c-t '("text/plain" "charset=us-ascii")
+			 c-t-e "7bit")) ; below
+		  ((string-match "^message/rfc822" (car type))
+		   (setq c-t '("text/plain" "charset=us-ascii")
+			 c-t-e "7bit")
+		   (goto-char (point-min))
+		   (or (re-search-forward "^\n\\|\n\\'" nil t)
+		       (vm-mime-error "MIME part missing header/body separator line"))
+		   (throw 'return-value
+			  (vector type encoding id description disposition
+				  (vm-marker (point-min))
+				  (vm-marker (point))
+				  (vm-marker (point-max))
+				  (list
+				   (save-restriction
+				     (narrow-to-region (point) (point-max))
+				     (vm-mime-parse-entity nil c-t c-t-e)))
+				  nil )))
+		  (t
+		   (goto-char (point-min))
+		   (or (re-search-forward "^\n\\|\n\\'" nil t)
+		       (vm-mime-error "MIME part missing header/body separator line"))
+		   (throw 'return-value
+			  (vector type encoding id description disposition
+				  (vm-marker (point-min))
+				  (vm-marker (point))
+				  (vm-marker (point-max))
+				  nil nil ))))
+	    (setq p (cdr type)
+		  boundary nil)
+	    (while p
+	      (if (string-match "^boundary=" (car p))
+		  (setq boundary (car (vm-parse (car p) "=\\(.+\\)"))
+			p nil)
+		(setq p (cdr p))))
+	    (or boundary
+		(vm-mime-error
+		 "Boundary parameter missing in %s type specification"
+		 (car type)))
+	    (setq boundary-regexp (regexp-quote boundary)
+		  boundary-regexp (concat "^--" boundary-regexp "\\(--\\)?\n"))
+	    (goto-char (point-min))
+	    (setq start nil
+		  multipart-list nil
+		  done nil)
+	    (while (and (not done) (re-search-forward boundary-regexp nil t))
+	      (cond ((null start)
+		     (setq start (match-end 0)))
+		    (t
+		     (and (match-beginning 1)
+			  (setq done t))
+		     (save-excursion
+		       (save-restriction
+			 (narrow-to-region start (1- (match-beginning 0)))
+			 (setq start (match-end 0))
+			 (setq multipart-list
+			       (cons (vm-mime-parse-entity-safe nil c-t c-t-e)
+				     multipart-list)))))))
+	    (if (not done)
+		(vm-mime-error "final %s boundary missing" boundary))
+	    (goto-char (point-min))
+	    (or (re-search-forward "^\n\\|\n\\'" nil t)
+		(vm-mime-error "MIME part missing header/body separator line"))
+	    (vector type encoding id description disposition
+		    (vm-marker (point-min))
+		    (vm-marker (point))
+		    (vm-marker (point-max))
+		    (nreverse multipart-list)
+		    nil )))))
+    (and m (vm-unsaved-message "Parsing MIME message... done"))
+    )))
+
+(defun vm-mime-parse-entity-safe (&optional m c-t c-t-e)
+  (or c-t (setq c-t '("text/plain" "charset=us-ascii")))
+  ;; don't let subpart parse errors make the whole parse fail.  use default
+  ;; type if the parse fails.
+  (condition-case error-data
+      (vm-mime-parse-entity nil c-t c-t-e)
+    (vm-mime-error
+     (let ((header (if m
+		       (vm-headers-of m)
+		     (vm-marker (point-min))))
+	   (text (if m
+		     (vm-text-of m)
+		   (save-excursion
+		     (re-search-forward "^\n\\|\n\\'"
+					nil 0)
+		     (vm-marker (point)))))
+	   (text-end (if m
+			 (vm-text-end-of m)
+		       (vm-marker (point-max)))))
+     (vector c-t
+	     (vm-determine-proper-content-transfer-encoding text text-end)
+	     nil
+	     ;; cram the error message into the description slot
+	     (car error-data)
+	     ;; mark as an attachment to improve the chance that the user
+	     ;; will see the description.
+	     '("attachment")
+	     header
+	     text
+	     text-end)))))
+
+(defun vm-mime-get-xxx-parameter (layout name param-list)
+  (let ((match-end (1+ (length name)))
+	(name-regexp (concat (regexp-quote name) "="))
+	(case-fold-search t)
+	(done nil))
+    (while (and param-list (not done))
+      (if (and (string-match name-regexp (car param-list))
+	       (= (match-end 0) match-end))
+	  (setq done t)
+	(setq param-list (cdr param-list))))
+    (and (car param-list) (car (vm-parse (car param-list) "=\\(.*\\)")))))
+
+(defun vm-mime-get-parameter (layout name)
+  (vm-mime-get-xxx-parameter layout name (cdr (vm-mm-layout-type layout))))
+
+(defun vm-mime-get-disposition-parameter (layout name)
+  (vm-mime-get-xxx-parameter layout name
+			     (cdr (vm-mm-layout-disposition layout))))
+
+(defun vm-mime-insert-mime-body (layout)
+  (vm-insert-region-from-buffer (marker-buffer (vm-mm-layout-body-start layout))
+				(vm-mm-layout-body-start layout)
+				(vm-mm-layout-body-end layout)))
+
+(defun vm-mime-insert-mime-headers (layout)
+  (vm-insert-region-from-buffer (marker-buffer (vm-mm-layout-body-start layout))
+				(vm-mm-layout-header-start layout)
+				(vm-mm-layout-body-start layout))
+  (if (and (not (bobp)) (char-equal (char-after (1- (point))) ?\n))
+      (delete-char -1)))
+
+(defun vm-make-presentation-copy (m)
+  (let ((mail-buffer (current-buffer))
+	b mm
+	(real-m (vm-real-message-of m))
+	(modified (buffer-modified-p)))
+    (cond ((or (null vm-presentation-buffer-handle)
+	       (null (buffer-name vm-presentation-buffer-handle)))
+	   (setq b (generate-new-buffer (concat (buffer-name)
+						" Presentation")))
+	   (save-excursion
+	     (set-buffer b)
+	     (if (fboundp 'buffer-disable-undo)
+		 (buffer-disable-undo (current-buffer))
+	       ;; obfuscation to make the v19 compiler not whine
+	       ;; about obsolete functions.
+	       (let ((x 'buffer-flush-undo))
+		 (funcall x (current-buffer))))
+	     (setq mode-name "VM Presentation"
+		   major-mode 'vm-presentation-mode
+		   vm-message-pointer (list nil)
+		   vm-mail-buffer mail-buffer
+		   mode-popup-menu (and vm-use-menus vm-popup-menu-on-mouse-3
+					(vm-menu-support-possible-p)
+					(vm-menu-mode-menu))
+		   buffer-read-only t
+		   mode-line-format vm-mode-line-format)
+	     (cond ((vm-fsfemacs-19-p)
+		    ;; need to do this outside the let because
+		    ;; loading disp-table initializes
+		    ;; standard-display-table.
+		    (require 'disp-table)
+		    (let* ((standard-display-table
+			    (copy-sequence standard-display-table)))
+		      (standard-display-european t)
+		      (setq buffer-display-table standard-display-table))))
+	     (if vm-frame-per-folder
+		 (vm-set-hooks-for-frame-deletion))
+	     (use-local-map vm-mode-map)
+	     (and (vm-toolbar-support-possible-p) vm-use-toolbar
+		  (vm-toolbar-install-toolbar))
+	     (and (vm-menu-support-possible-p)
+		  (vm-menu-install-menus)))
+	   (setq vm-presentation-buffer-handle b)))
+    ;; do this (widen) outside save-restricton intentionally.  since
+    ;; we're using the presentation buffer, make the folder
+    ;; buffer unpretty so maybe the user gets the idea.
+    ;;(widen)
+    ;; widening isn't enough.  users just complain that "I'm
+    ;; looking at the wrong message."  Curse their miserable hides.
+    ;; bury the buffer so they'll have a tough time finding it.
+    (bury-buffer (current-buffer))
+    (setq b vm-presentation-buffer-handle
+	  vm-presentation-buffer vm-presentation-buffer-handle
+	  vm-mime-decoded nil)
+    (save-excursion
+      (set-buffer (vm-buffer-of real-m))
+      (save-restriction
+	(widen)
+	;; must reference this now so that headers will be in
+	;; their final position before the message is copied.
+	;; otherwise the vheader offset computed below will be
+	;; wrong.
+	(vm-vheaders-of real-m)
+	(set-buffer b)
+	(widen)
+	(let ((buffer-read-only nil)
+	      (modified (buffer-modified-p)))
+	  (unwind-protect
+	      (progn
+		(erase-buffer)
+		(insert-buffer-substring (vm-buffer-of real-m)
+					 (vm-start-of real-m)
+					 (vm-end-of real-m)))
+	    (set-buffer-modified-p modified)))
+	(setq mm (copy-sequence m))
+	(vm-set-location-data-of mm (vm-copy (vm-location-data-of m)))
+	(set-marker (vm-start-of mm) (point-min))
+	(set-marker (vm-headers-of mm) (+ (vm-start-of mm)
+					  (- (vm-headers-of real-m)
+					     (vm-start-of real-m))))
+	(set-marker (vm-vheaders-of mm) (+ (vm-start-of mm)
+					   (- (vm-vheaders-of real-m)
+					      (vm-start-of real-m))))
+	(set-marker (vm-text-of mm) (+ (vm-start-of mm)
+				       (- (vm-text-of real-m)
+					  (vm-start-of real-m))))
+	(set-marker (vm-text-end-of mm) (+ (vm-start-of mm)
+					   (- (vm-text-end-of real-m)
+					      (vm-start-of real-m))))
+	(set-marker (vm-end-of mm) (+ (vm-start-of mm)
+				      (- (vm-end-of real-m)
+					 (vm-start-of real-m))))
+	(setcar vm-message-pointer mm)))))
+
+(fset 'vm-presentation-mode 'vm-mode)
+(put 'vm-presentation-mode 'mode-class 'special)
+
+(defun vm-determine-proper-charset (beg end)
+  (save-excursion
+    (save-restriction
+      (narrow-to-region beg end)
+      (catch 'done
+	(goto-char (point-min))
+	(and (re-search-forward "[^\000-\177]" nil t)
+	     (throw 'done (or vm-mime-8bit-composition-charset "iso-8859-1")))
+	(throw 'done "us-ascii")))))
+
+(defun vm-determine-proper-content-transfer-encoding (beg end)
+  (save-excursion
+    (save-restriction
+      (narrow-to-region beg end)
+      (catch 'done
+	(goto-char (point-min))
+	(and (re-search-forward "[\000\015]" nil t)
+	     (throw 'done "binary"))
+
+	(let ((toolong nil) bol)
+	  (goto-char (point-min))
+	  (setq bol (point))
+	  (while (and (not (eobp)) (not toolong))
+	    (forward-line)
+	    (setq toolong (> (- (point) bol) 998)
+		  bol (point)))
+	  (and toolong (throw 'done "binary")))
+	 
+	(goto-char (point-min))
+	(and (re-search-forward "[\200-\377]" nil t)
+	     (throw 'done "8bit"))
+
+	"7bit"))))
+
+(defun vm-mime-types-match (type type/subtype)
+  (let ((case-fold-search t))
+    (cond ((string-match "/" type)
+	   (if (and (string-match (regexp-quote type) type/subtype)
+		    (equal 0 (match-beginning 0))
+		    (equal (length type/subtype) (match-end 0)))
+	       t
+	     nil ))
+	  ((and (string-match (regexp-quote type) type/subtype)
+		(equal 0 (match-beginning 0))
+		(equal (save-match-data
+			 (string-match "/" type/subtype (match-end 0)))
+		       (match-end 0)))))))
+
+(defvar native-sound-only-on-console)
+
+(defun vm-mime-can-display-internal (layout)
+  (let ((type (car (vm-mm-layout-type layout))))
+    (cond ((vm-mime-types-match "image/jpeg" type)
+	   (and (vm-xemacs-p)
+		(featurep 'jpeg)
+		(eq (device-type) 'x)))
+	  ((vm-mime-types-match "image/gif" type)
+	   (and (vm-xemacs-p)
+		(featurep 'gif)
+		(eq (device-type) 'x)))
+	  ((vm-mime-types-match "image/png" type)
+	   (and (vm-xemacs-p)
+		(featurep 'png)
+		(eq (device-type) 'x)))
+	  ((vm-mime-types-match "image/tiff" type)
+	   (and (vm-xemacs-p)
+		(featurep 'tiff)
+		(eq (device-type) 'x)))
+	  ((vm-mime-types-match "audio/basic" type)
+	   (and (vm-xemacs-p)
+		(or (featurep 'native-sound)
+		    (featurep 'nas-sound))
+		(or (device-sound-enabled-p)
+		    (and (featurep 'native-sound)
+			 (not native-sound-only-on-console)
+			 (eq (device-type) 'x)))))
+	  ((vm-mime-types-match "multipart" type) t)
+	  ((vm-mime-types-match "message/external-body" type) nil)
+	  ((vm-mime-types-match "message" type) t)
+	  ((or (vm-mime-types-match "text/plain" type)
+	       (vm-mime-types-match "text/enriched" type))
+	   (let ((charset (or (vm-mime-get-parameter layout "charset")
+			      "us-ascii")))
+	     (vm-mime-charset-internally-displayable-p charset)))
+	  ((vm-mime-types-match "text/html" type)
+	   (condition-case ()
+	       (progn (require 'w3)
+		      (fboundp 'w3-region))
+	     (error nil)))
+	  (t nil))))
+
+(defun vm-mime-can-convert (type)
+  (let ((alist vm-mime-type-converter-alist)
+	;; fake layout. make it the wrong length so an error will
+	;; be signaled if vm-mime-can-display-internal ever asks
+	;; for one of the other fields
+	(fake-layout (make-vector 1 (list nil)))
+	(done nil))
+    (while (and alist (not done))
+      (cond ((and (vm-mime-types-match (car (car alist)) type)
+		  (or (progn
+			(setcar (aref fake-layout 0) (nth 1 (car alist)))
+			(vm-mime-can-display-internal fake-layout))
+		      (vm-mime-find-external-viewer (nth 1 (car alist)))))
+	     (setq done t))
+	    (t (setq alist (cdr alist)))))
+    (and alist (car alist))))
+
+(defun vm-mime-convert-undisplayable-layout (layout)
+  (let ((ooo (vm-mime-can-convert (car (vm-mm-layout-type layout)))))
+    (vm-unsaved-message "Converting %s to %s..."
+			(car (vm-mm-layout-type layout))
+			(nth 1 ooo))
+    (save-excursion
+      (set-buffer (generate-new-buffer " *mime object*"))
+      (setq vm-message-garbage-alist
+	    (cons (cons (current-buffer) 'kill-buffer)
+		  vm-message-garbage-alist))
+      (vm-mime-insert-mime-body layout)
+      (vm-mime-transfer-decode-region layout (point-min) (point-max))
+      (call-process-region (point-min) (point-max) shell-file-name
+			   t t nil shell-command-switch (nth 2 ooo))
+      (goto-char (point-min))
+      (insert "Content-Type: " (nth 1 ooo) "\n")
+      (insert "Content-Transfer-Encoding: binary\n\n")
+      (set-buffer-modified-p nil)
+      (vm-unsaved-message "Converting %s to %s... done"
+			(car (vm-mm-layout-type layout))
+			(nth 1 ooo))
+      (vector (list (nth 1 ooo))
+	      "binary"
+	      (vm-mm-layout-id layout)
+	      (vm-mm-layout-description layout)
+	      (vm-mm-layout-disposition layout)
+	      (vm-marker (point-min))
+	      (vm-marker (point))
+	      (vm-marker (point-max))
+	      nil
+	      nil ))))
+
+(defun vm-mime-should-display-button (layout dont-honor-content-disposition)
+  (if (and vm-honor-mime-content-disposition
+	   (not dont-honor-content-disposition)
+	   (vm-mm-layout-disposition layout))
+      (let ((case-fold-search t))
+	(string-match "^attachment$" (car (vm-mm-layout-disposition layout))))
+    (let ((i-list vm-auto-displayed-mime-content-types)
+	  (type (car (vm-mm-layout-type layout)))
+	  (matched nil))
+      (if (eq i-list t)
+	  nil
+	(while (and i-list (not matched))
+	  (if (vm-mime-types-match (car i-list) type)
+	      (setq matched t)
+	    (setq i-list (cdr i-list))))
+	(not matched) ))))
+
+(defun vm-mime-should-display-internal (layout dont-honor-content-disposition)
+  (if (and vm-honor-mime-content-disposition
+	   (not dont-honor-content-disposition)
+	   (vm-mm-layout-disposition layout))
+      (let ((case-fold-search t))
+	(string-match "^inline$" (car (vm-mm-layout-disposition layout))))
+    (let ((i-list vm-mime-internal-content-types)
+	  (type (car (vm-mm-layout-type layout)))
+	  (matched nil))
+      (if (eq i-list t)
+	  t
+	(while (and i-list (not matched))
+	  (if (vm-mime-types-match (car i-list) type)
+	      (setq matched t)
+	    (setq i-list (cdr i-list))))
+	matched ))))
+
+(defun vm-mime-find-external-viewer (type)
+  (let ((e-alist vm-mime-external-content-types-alist)
+	(matched nil))
+    (while (and e-alist (not matched))
+      (if (and (vm-mime-types-match (car (car e-alist)) type)
+	       (cdr (car e-alist)))
+	  (setq matched (cdr (car e-alist)))
+	(setq e-alist (cdr e-alist))))
+    matched ))
+(fset 'vm-mime-should-display-external 'vm-mime-find-external-viewer)
+
+(defun vm-mime-delete-button-maybe (extent)
+  (let ((buffer-read-only))
+    ;; if displayed MIME object should replace the button
+    ;; remove the button now.
+    (cond ((vm-extent-property extent 'vm-mime-disposable)
+	   (delete-region (vm-extent-start-position extent)
+			  (vm-extent-end-position extent))
+	   (vm-detach-extent extent)))))
+
+(defun vm-decode-mime-message ()
+  "Decode the MIME objects in the current message.
+
+The first time this command is run on a message, decoding is done.
+The second time, buttons for all the objects are displayed instead.
+The third time, the raw, undecoded data is displayed.
+
+If decoding, the decoded objects might be displayed immediately, or
+buttons might be displayed that you need to activate to view the
+object.  See the documentation for the variables
+
+    vm-auto-displayed-mime-content-types
+    vm-mime-internal-content-types
+    vm-mime-external-content-types-alist
+
+to see how to control whether you see buttons or objects.
+
+If the variable vm-mime-display-function is set, then its value
+is called as a function with no arguments, and none of the
+actions mentioned in the preceding paragraphs are done.  At the
+time of the call, the current buffer will be the presentation
+buffer for the folder and a copy of the current message will be
+in the buffer.  The function is expected to make the message
+`MIME presentable' to the user in whatever manner it sees fit."
+  (interactive)
+  (vm-follow-summary-cursor)
+  (vm-select-folder-buffer)
+  (vm-check-for-killed-summary)
+  (vm-check-for-killed-presentation)
+  (vm-error-if-folder-empty)
+  (if (and (not vm-display-using-mime)
+	   (null vm-mime-display-function))
+      (error "MIME display disabled, set vm-display-using-mime non-nil to enable."))
+  (if vm-mime-display-function
+      (progn
+	(vm-make-presentation-copy (car vm-message-pointer))
+	(set-buffer vm-presentation-buffer)
+	(funcall vm-mime-display-function))
+    (if vm-mime-decoded
+	(if (eq vm-mime-decoded 'decoded)
+	    (let ((vm-preview-read-messages nil)
+		  (vm-auto-decode-mime-messages t)
+		  (vm-honor-mime-content-disposition nil)
+		  (vm-auto-displayed-mime-content-types '("multipart")))
+	      (setq vm-mime-decoded nil)
+	      (intern (buffer-name) vm-buffers-needing-display-update)
+	      (save-excursion
+		(vm-preview-current-message))
+	      (setq vm-mime-decoded 'buttons))
+	  (let ((vm-preview-read-messages nil)
+		(vm-auto-decode-mime-messages nil))
+	    (intern (buffer-name) vm-buffers-needing-display-update)
+	    (vm-preview-current-message)))
+      (let ((layout (vm-mm-layout (car vm-message-pointer)))
+	    (m (car vm-message-pointer)))
+	(vm-unsaved-message "Decoding MIME message...")
+	(cond ((stringp layout)
+	       (error "Invalid MIME message: %s" layout)))
+	(if (vm-mime-plain-message-p m)
+	    (error "Message needs no decoding."))
+	(or vm-presentation-buffer
+	    ;; maybe user killed it
+	    (error "No presentation buffer."))
+	(set-buffer vm-presentation-buffer)
+	(setq m (car vm-message-pointer))
+	(vm-save-restriction
+	 (widen)
+	 (goto-char (vm-text-of m))
+	 (let ((buffer-read-only nil)
+	       (modified (buffer-modified-p)))
+	   (unwind-protect
+	       (save-excursion
+		 (and (not (eq (vm-mm-encoded-header m) 'none))
+		      (vm-decode-mime-message-headers m))
+		 (if (vectorp layout)
+		     (progn
+		       (vm-decode-mime-layout layout)
+		       (delete-region (point) (point-max)))))
+	     (set-buffer-modified-p modified))))
+	(save-excursion (set-buffer vm-mail-buffer)
+			(setq vm-mime-decoded 'decoded))
+	(intern (buffer-name vm-mail-buffer) vm-buffers-needing-display-update)
+	(vm-update-summary-and-mode-line)
+	(vm-unsaved-message "Decoding MIME message... done"))))
+  (vm-display nil nil '(vm-decode-mime-message)
+	      '(vm-decode-mime-message reading-message)))
+
+(defun vm-decode-mime-layout (layout &optional dont-honor-c-d)
+  (let ((modified (buffer-modified-p)) type type-no-subtype (extent nil))
+    (unwind-protect
+	(progn
+	  (if (not (vectorp layout))
+	      (progn
+		(setq extent layout
+		      layout (vm-extent-property extent 'vm-mime-layout))
+		(goto-char (vm-extent-start-position extent))))
+	  (setq type (downcase (car (vm-mm-layout-type layout)))
+		type-no-subtype (car (vm-parse type "\\([^/]+\\)")))
+	  (cond ((and (vm-mime-should-display-button layout dont-honor-c-d)
+		      (or (condition-case nil
+			      (funcall (intern
+					(concat "vm-mime-display-button-"
+						type))
+				       layout)
+			    (void-function nil))
+			  (condition-case nil
+			      (funcall (intern
+					(concat "vm-mime-display-button-"
+						type-no-subtype))
+				       layout)
+			    (void-function nil)))))
+		((and (vm-mime-should-display-internal layout dont-honor-c-d)
+		      (condition-case nil
+			      (funcall (intern
+					(concat "vm-mime-display-internal-"
+						type))
+				       layout)
+			    (void-function nil))))
+		((vm-mime-types-match "multipart" type)
+		 (or (condition-case nil
+			 (funcall (intern
+				   (concat "vm-mime-display-internal-"
+					   type))
+				  layout)
+		       (void-function nil))
+		     (vm-mime-display-internal-multipart/mixed layout)))
+		((and (vm-mime-should-display-external type)
+		      (vm-mime-display-external-generic layout))
+		 (and extent (vm-set-extent-property
+			      extent 'vm-mime-disposable nil)))
+		((vm-mime-can-convert type)
+		 (vm-decode-mime-layout
+		  (vm-mime-convert-undisplayable-layout layout)))
+		((and (or (vm-mime-types-match "message" type)
+			  (vm-mime-types-match "text" type))
+		      ;; display unmatched message and text types as
+		      ;; text/plain.
+		      (vm-mime-display-internal-text/plain layout)))
+		(t (vm-mime-display-internal-application/octet-stream
+		    (or extent layout))))
+	  (and extent (vm-mime-delete-button-maybe extent)))
+      (set-buffer-modified-p modified)))
+  t )
+
+(defun vm-mime-display-button-text (layout)
+  (vm-mime-display-button-xxxx layout t))
+
+(defun vm-mime-display-internal-text/html (layout)
+  (let ((buffer-read-only nil)
+	(work-buffer nil))
+    (vm-unsaved-message "Inlining text/html, be patient...")
+    ;; w3-region is not as tame as we would like.
+    ;; make sure the yoke is firmly attached.
+    (unwind-protect
+	(progn
+	  (save-excursion
+	    (set-buffer (setq work-buffer
+			      (generate-new-buffer " *workbuf*")))
+	    (vm-mime-insert-mime-body layout)
+	    (vm-mime-transfer-decode-region layout (point-min) (point-max))
+	    (save-excursion
+	      (save-window-excursion
+		(w3-region (point-min) (point-max)))))
+	  (insert-buffer-substring work-buffer))
+      (and work-buffer (kill-buffer work-buffer)))
+    (vm-unsaved-message "Inlining text/html... done")
+    t ))
+
+(defun vm-mime-display-internal-text/plain (layout &optional ignore-urls)
+  (let ((start (point)) end
+	(buffer-read-only nil)
+	(charset (or (vm-mime-get-parameter layout "charset") "us-ascii")))
+    (if (not (vm-mime-charset-internally-displayable-p charset))
+	nil
+      (vm-mime-insert-mime-body layout)
+      (setq end (point-marker))
+      (vm-mime-transfer-decode-region layout start end)
+      (vm-mime-charset-decode-region charset start end)
+      (or ignore-urls (vm-energize-urls-in-message-region start end))
+      t )))
+
+(defun vm-mime-display-internal-text/enriched (layout)
+  (require 'enriched)
+  (let ((start (point)) end
+	(buffer-read-only nil)
+	(enriched-verbose t))
+    (vm-unsaved-message "Decoding text/enriched, be patient...")
+    (vm-mime-insert-mime-body layout)
+    (setq end (point-marker))
+    (vm-mime-transfer-decode-region layout start end)
+    ;; enriched-decode expects a couple of headers at the top of
+    ;; the region and will remove anything that looks like a
+    ;; header.  Put a header section here for it to eat so it
+    ;; won't eat message text instead.
+    (goto-char start)
+    (insert "Comment: You should not see this header\n\n")
+    (enriched-decode start end)
+    (vm-energize-urls-in-message-region start end)
+    (goto-char end)
+    (vm-unsaved-message "Decoding text/enriched... done")
+    t ))
+
+(defun vm-mime-display-external-generic (layout)
+  (let ((program-list (vm-mime-find-external-viewer
+		       (car (vm-mm-layout-type layout))))
+	(process (nth 0 (vm-mm-layout-cache layout)))
+	(tempfile (nth 1 (vm-mm-layout-cache layout)))
+	(buffer-read-only nil)
+	(start (point))
+	end)
+    (if (and (processp process) (eq (process-status process) 'run))
+	nil
+      (cond ((or (null tempfile) (null (file-exists-p tempfile)))
+	     (vm-mime-insert-mime-body layout)
+	     (setq end (point-marker))
+	     (vm-mime-transfer-decode-region layout start end)
+	     (setq tempfile (vm-make-tempfile-name))
+	     ;; Tell DOS/Windows NT whether the file is binary
+	     (setq buffer-file-type (not (vm-mime-text-type-p layout)))
+	     (write-region start end tempfile nil 0)
+	     (delete-region start end)
+	     (save-excursion
+	       (vm-select-folder-buffer)
+	       (setq vm-folder-garbage-alist
+		     (cons (cons tempfile 'delete-file)
+			   vm-folder-garbage-alist)))))
+      (vm-unsaved-message "Launching %s..." (mapconcat 'identity
+						       program-list
+						       " "))
+      (setq process
+	    (apply 'start-process
+		   (format "view %25s" (vm-mime-layout-description layout))
+		   nil (append program-list (list tempfile))))
+      (process-kill-without-query process t)
+      (vm-unsaved-message "Launching %s... done" (mapconcat 'identity
+							    program-list
+							    " "))
+      (save-excursion
+	(vm-select-folder-buffer)
+	(setq vm-message-garbage-alist
+	      (cons (cons process 'delete-process)
+		    vm-message-garbage-alist)))
+      (vm-set-mm-layout-cache layout (list process tempfile))))
+  t )
+
+(defun vm-mime-display-internal-application/octet-stream (layout)
+  (if (vectorp layout)
+      (let ((buffer-read-only nil)
+	    (description (vm-mm-layout-description layout)))
+	(vm-mime-insert-button
+	 (format "%-35s [%s to save to a file]"
+		 (vm-mime-layout-description layout)
+		 (if (vm-mouse-support-possible-p)
+		     "Click mouse-2"
+		   "Press RETURN"))
+	 (function
+	  (lambda (layout)
+	    (save-excursion
+	      (vm-mime-display-internal-application/octet-stream layout))))
+	 layout nil))
+    (goto-char (vm-extent-start-position layout))
+    (setq layout (vm-extent-property layout 'vm-mime-layout))
+    ;; support old "name" paramater for application/octet-stream
+    ;; but don't override the "filename" parameter extracted from
+    ;; Content-Disposition, if any.
+    (let ((default-filename
+	    (if (vm-mime-get-disposition-parameter layout "filename")
+		nil
+	      (vm-mime-get-parameter layout "name"))))
+      (vm-mime-send-body-to-file layout default-filename)))
+  t )
+(fset 'vm-mime-display-button-application
+      'vm-mime-display-internal-application/octet-stream)
+
+(defun vm-mime-display-button-image (layout)
+  (vm-mime-display-button-xxxx layout t))
+
+(defun vm-mime-display-button-audio (layout)
+  (vm-mime-display-button-xxxx layout nil))
+
+(defun vm-mime-display-button-video (layout)
+  (vm-mime-display-button-xxxx layout t))
+
+(defun vm-mime-display-button-message (layout)
+  (vm-mime-display-button-xxxx layout t))
+
+(defun vm-mime-display-button-multipart (layout)
+  (vm-mime-display-button-xxxx layout t))
+
+(defun vm-mime-display-internal-multipart/mixed (layout)
+  (let ((part-list (vm-mm-layout-parts layout)))
+    (while part-list
+      (vm-decode-mime-layout (car part-list))
+      (setq part-list (cdr part-list)))
+    t ))
+
+(defun vm-mime-display-internal-multipart/alternative (layout)
+  (let (best-layout)
+    (cond ((eq vm-mime-alternative-select-method 'best)
+	   (let ((done nil)
+		 (best nil)
+		 part-list type)
+	     (setq part-list (vm-mm-layout-parts layout)
+		   part-list (nreverse (copy-sequence part-list)))
+	     (while (and part-list (not done))
+	       (setq type (car (vm-mm-layout-type (car part-list))))
+	       (if (or (vm-mime-can-display-internal (car part-list))
+		       (vm-mime-find-external-viewer type))
+		   (setq best (car part-list)
+			 done t)
+		 (setq part-list (cdr part-list))))
+	     (setq best-layout (or best (car (vm-mm-layout-parts layout))))))
+	  ((eq vm-mime-alternative-select-method 'best-internal)
+	   (let ((done nil)
+		 (best nil)
+		 (second-best nil)
+		 part-list type)
+	     (setq part-list (vm-mm-layout-parts layout)
+		   part-list (nreverse (copy-sequence part-list)))
+	     (while (and part-list (not done))
+	       (setq type (car (vm-mm-layout-type (car part-list))))
+	       (cond ((vm-mime-can-display-internal (car part-list))
+		      (setq best (car part-list)
+			    done t))
+		     ((and (null second-best)
+			   (vm-mime-find-external-viewer type))
+		      (setq second-best (car part-list))))
+	       (setq part-list (cdr part-list)))
+	     (setq best-layout (or best second-best
+				   (car (vm-mm-layout-parts layout)))))))
+  (vm-decode-mime-layout best-layout)))
+
+(defun vm-mime-display-button-multipart/parallel (layout)
+  (vm-mime-insert-button
+   (format "%-35s [%s to display in parallel]"
+	   (vm-mime-layout-description layout)
+	   (if (vm-mouse-support-possible-p)
+	       "Click mouse-2"
+	     "Press RETURN"))
+   (function
+    (lambda (layout)
+      (save-excursion
+	(let ((vm-auto-displayed-mime-content-types t))
+	  (vm-decode-mime-layout layout t)))))
+   layout t))
+
+(fset 'vm-mime-display-internal-multipart/parallel
+      'vm-mime-display-internal-multipart/mixed)
+
+(defun vm-mime-display-internal-multipart/digest (layout)
+  (if (vectorp layout)
+      (let ((buffer-read-only nil))
+	(vm-mime-insert-button
+	 (format "%-35s [%s to display]"
+		 (vm-mime-layout-description layout)
+		 (if (vm-mouse-support-possible-p)
+		     "Click mouse-2"
+		   "Press RETURN"))
+	 (function
+	  (lambda (layout)
+	    (save-excursion
+	      (vm-mime-display-internal-multipart/digest layout))))
+	 layout nil))
+    (goto-char (vm-extent-start-position layout))
+    (setq layout (vm-extent-property layout 'vm-mime-layout))
+    (set-buffer (generate-new-buffer (format "digest from %s/%s"
+					     (buffer-name vm-mail-buffer)
+					     (vm-number-of
+					      (car vm-message-pointer)))))
+    (setq vm-folder-type vm-default-folder-type)
+    (vm-mime-burst-layout layout nil)
+    (vm-save-buffer-excursion
+     (vm-goto-new-folder-frame-maybe 'folder)
+     (vm-mode))
+    ;; temp buffer, don't offer to save it.
+    (setq buffer-offer-save nil)
+    (vm-display nil nil (list this-command) '(vm-mode startup)))
+  t )
+(fset 'vm-mime-display-button-multipart/digest
+      'vm-mime-display-internal-multipart/digest)
+
+(defun vm-mime-display-internal-message/rfc822 (layout)
+  (if (vectorp layout)
+      (let ((buffer-read-only nil))
+	(vm-mime-insert-button
+	 (format "%-35s [%s to display]"
+		 (vm-mime-layout-description layout)
+		 (if (vm-mouse-support-possible-p)
+		     "Click mouse-2"
+		   "Press RETURN"))
+	 (function
+	  (lambda (layout)
+	    (save-excursion
+	      (vm-mime-display-internal-message/rfc822 layout))))
+	 layout nil))
+    (goto-char (vm-extent-start-position layout))
+    (setq layout (vm-extent-property layout 'vm-mime-layout))
+    (set-buffer (generate-new-buffer
+		 (format "message from %s/%s"
+			 (buffer-name vm-mail-buffer)
+			 (vm-number-of
+			  (car vm-message-pointer)))))
+    (setq vm-folder-type vm-default-folder-type)
+    (vm-mime-burst-layout layout nil)
+    (set-buffer-modified-p nil)
+    (vm-save-buffer-excursion
+     (vm-goto-new-folder-frame-maybe 'folder)
+     (vm-mode))
+    ;; temp buffer, don't offer to save it.
+    (setq buffer-offer-save nil)
+    (vm-display (or vm-presentation-buffer (current-buffer)) t
+		(list this-command) '(vm-mode startup)))
+  t )
+(fset 'vm-mime-display-button-message/rfc822
+      'vm-mime-display-internal-message/rfc822)
+
+(defun vm-mime-display-internal-message/partial (layout)
+  (if (vectorp layout)
+      (let ((buffer-read-only nil)
+	    (number (vm-mime-get-parameter layout "number"))
+	    (total (vm-mime-get-parameter layout "total")))
+	(vm-mime-insert-button
+	 (format "%-35s [%s to attempt assembly]"
+		 (concat (vm-mime-layout-description layout)
+			 (and number (concat ", part " number))
+			 (and number total (concat " of " total)))
+		 (if (vm-mouse-support-possible-p)
+		     "Click mouse-2"
+		   "Press RETURN"))
+	 (function
+	  (lambda (layout)
+	    (save-excursion
+	      (vm-mime-display-internal-message/partial layout))))
+	 layout nil))
+    (vm-unsaved-message "Assembling message...")
+    (let ((parts nil)
+	  (missing nil)
+	  (work-buffer nil)
+	  extent id o number total m i prev part-header-pos
+	  p-id p-number p-total p-list)
+      (setq extent layout
+	    layout (vm-extent-property extent 'vm-mime-layout)
+	    id (vm-mime-get-parameter layout "id"))
+      (if (null id)
+	  (vm-mime-error
+	   "message/partial message missing id parameter"))
+      (save-excursion
+	(set-buffer (marker-buffer (vm-mm-layout-body-start layout)))
+	(save-excursion
+	  (save-restriction
+	    (widen)
+	    (goto-char (point-min))
+	    (while (and (search-forward id nil t)
+			(setq m (vm-message-at-point)))
+	      (setq o (vm-mm-layout m))
+	      (if (not (vectorp o))
+		  nil
+		(setq p-list (vm-mime-find-message/partials o id))
+		(while p-list
+		  (setq p-id (vm-mime-get-parameter (car p-list) "id"))
+		  (setq p-total (vm-mime-get-parameter (car p-list) "total"))
+		  (if (null p-total)
+		      nil
+		    (setq p-total (string-to-int p-total))
+		    (if (< p-total 1)
+			(vm-mime-error "message/partial specified part total < 0, %d" p-total))
+		    (if total
+			(if (not (= total p-total))
+			    (vm-mime-error "message/partial speificed total differs between parts, (%d != %d)" p-total total))
+		      (setq total p-total)))
+		  (setq p-number (vm-mime-get-parameter (car p-list) "number"))
+		  (if (null p-number)
+		      (vm-mime-error
+		       "message/partial message missing number parameter"))
+		  (setq p-number (string-to-int p-number))
+		  (if (< p-number 1)
+		      (vm-mime-error "message/partial part number < 0, %d"
+				     p-number))
+		  (if (and total (> p-number total))
+		      (vm-mime-error "message/partial part number greater than expected number of parts, (%d > %d)" p-number total))
+		  (setq parts (cons (list p-number (car p-list)) parts)
+			p-list (cdr p-list))))
+	      (goto-char (vm-mm-layout-body-end o))))))
+      (if (null total)
+	  (vm-mime-error "total number of parts not specified in any message/partial part"))
+      (setq parts (sort parts
+			(function
+			 (lambda (p q)
+			   (< (car p)
+			      (car q))))))
+      (setq i 0
+	    p-list parts)
+      (while p-list
+	(cond ((< i (car (car p-list)))
+	       (vm-increment i)
+	       (cond ((not (= i (car (car p-list))))
+		      (setq missing (cons i missing)))
+		     (t (setq prev p-list
+			      p-list (cdr p-list)))))
+	      (t
+	       ;; remove duplicate part
+	       (setcdr prev (cdr p-list))
+	       (setq p-list (cdr p-list)))))
+      (while (< i total)
+	(vm-increment i)
+	(setq missing (cons i missing)))
+      (if missing
+	  (vm-mime-error "part%s %s%s missing"
+			 (if (cdr missing) "s" "")
+			 (mapconcat
+			  (function identity)
+			  (nreverse (mapcar 'int-to-string
+					    (or (cdr missing) missing)))
+			  ", ")
+			 (if (cdr missing)
+			     (concat " and " (car missing))
+			   "")))
+      (set-buffer (generate-new-buffer "assembled message"))
+      (setq vm-folder-type vm-default-folder-type)
+      (vm-mime-insert-mime-headers (car (cdr (car parts))))
+      (goto-char (point-min))
+      (vm-reorder-message-headers
+       nil nil
+"\\(Encrypted\\|Content-\\|MIME-Version\\|Message-ID\\|Subject\\|X-VM-\\|Status\\)")
+      (goto-char (point-max))
+      (setq part-header-pos (point))
+      (while parts
+	(vm-mime-insert-mime-body (car (cdr (car parts))))
+	(setq parts (cdr parts)))
+      (goto-char part-header-pos)
+      (vm-reorder-message-headers
+       nil '("Subject" "MIME-Version" "Content-" "Message-ID" "Encrypted") nil)
+      (vm-munge-message-separators vm-folder-type (point-min) (point-max))
+      (goto-char (point-min))
+      (insert (vm-leading-message-separator))
+      (goto-char (point-max))
+      (insert (vm-trailing-message-separator))
+      (set-buffer-modified-p nil)
+      (vm-unsaved-message "Assembling message... done")
+      (vm-save-buffer-excursion
+       (vm-goto-new-folder-frame-maybe 'folder)
+       (vm-mode))
+      ;; temp buffer, don't offer to save it.
+      (setq buffer-offer-save nil)
+      (vm-display (or vm-presentation-buffer (current-buffer)) t
+		  (list this-command) '(vm-mode startup)))
+    t ))
+(fset 'vm-mime-display-button-message/partial
+      'vm-mime-display-internal-message/partial)
+
+(defun vm-mime-display-internal-image-xxxx (layout feature name)
+  (if (and (vm-xemacs-p)
+	   (featurep feature)
+	   (eq (device-type) 'x))
+      (let ((start (point)) end tempfile g e
+	    (buffer-read-only nil))
+	(if (vm-mm-layout-cache layout)
+	    (setq g (vm-mm-layout-cache layout))
+	  (vm-mime-insert-mime-body layout)
+	  (setq end (point-marker))
+	  (vm-mime-transfer-decode-region layout start end)
+	  (setq tempfile (vm-make-tempfile-name))
+	  (write-region start end tempfile nil 0)
+	  (vm-unsaved-message "Creating %s glyph..." name)
+	  (setq g (make-glyph
+		   (list (vector feature ':file tempfile)
+			 (vector 'string
+				 ':data
+				 (format "[Unknown %s image encoding]\n"
+					 name)))))
+	  (vm-unsaved-message "")
+	  (vm-set-mm-layout-cache layout g)
+	  (save-excursion
+	    (vm-select-folder-buffer)
+	    (setq vm-folder-garbage-alist
+		  (cons (cons tempfile 'delete-file)
+			vm-folder-garbage-alist)))
+	  (delete-region start end))
+	(if (not (bolp))
+	    (insert-char ?\n 2)
+	  (insert-char ?\n 1))
+	(setq e (vm-make-extent (1- (point)) (point)))
+	(vm-set-extent-property e 'begin-glyph g)
+	t )))
+
+(defun vm-mime-display-internal-image/gif (layout)
+  (vm-mime-display-internal-image-xxxx layout 'gif "GIF"))
+
+(defun vm-mime-display-internal-image/jpeg (layout)
+  (vm-mime-display-internal-image-xxxx layout 'jpeg "JPEG"))
+
+(defun vm-mime-display-internal-image/png (layout)
+  (vm-mime-display-internal-image-xxxx layout 'png "PNG"))
+
+(defun vm-mime-display-internal-image/tiff (layout)
+  (vm-mime-display-internal-image-xxxx layout 'tiff "TIFF"))
+
+(defun vm-mime-display-internal-audio/basic (layout)
+  (if (and (vm-xemacs-p)
+	   (or (featurep 'native-sound)
+	       (featurep 'nas-sound))
+	   (or (device-sound-enabled-p)
+	       (and (featurep 'native-sound)
+		    (not native-sound-only-on-console)
+		    (eq (device-type) 'x))))
+      (let ((start (point)) end tempfile
+	    (buffer-read-only nil))
+	(if (vm-mm-layout-cache layout)
+	    (setq tempfile (vm-mm-layout-cache layout))
+	  (vm-mime-insert-mime-body layout)
+	  (setq end (point-marker))
+	  (vm-mime-transfer-decode-region layout start end)
+	  (setq tempfile (vm-make-tempfile-name))
+	  (write-region start end tempfile nil 0)
+	  (vm-set-mm-layout-cache layout tempfile)
+	  (save-excursion
+	    (vm-select-folder-buffer)
+	    (setq vm-folder-garbage-alist
+		  (cons (cons tempfile 'delete-file)
+			vm-folder-garbage-alist)))
+	  (delete-region start end))
+	(start-itimer "audioplayer"
+		      (list 'lambda nil (list 'play-sound-file tempfile))
+		      1)
+	t )
+    nil ))
+
+(defun vm-mime-display-button-xxxx (layout disposable)
+  (let ((description (vm-mime-layout-description layout)))
+    (vm-mime-insert-button
+     (format "%-35s [%s to display]"
+	     description
+	     (if (vm-mouse-support-possible-p) "Click mouse-2" "Press RETURN"))
+     (function
+      (lambda (layout)
+	(save-excursion
+	  (let ((vm-auto-displayed-mime-content-types t))
+	    (vm-decode-mime-layout layout t)))))
+     layout disposable)
+    t ))
+
+(defun vm-mime-run-display-function-at-point (&optional function)
+  (interactive)
+  ;; save excursion to keep point from moving.  its motion would
+  ;; drag window point along, to a place arbitrarily far from
+  ;; where it was when the user triggered the button.
+  (save-excursion
+    (cond ((vm-fsfemacs-19-p)
+	   (let (o-list o (found nil))
+	     (setq o-list (overlays-at (point)))
+	     (while (and o-list (not found))
+	       (cond ((overlay-get (car o-list) 'vm-mime-layout)
+		      (setq found t)
+		      (funcall (or function (overlay-get (car o-list)
+							 'vm-mime-function))
+			       (car o-list))))
+	       (setq o-list (cdr o-list)))))
+	  ((vm-xemacs-p)
+	   (let ((e (extent-at (point) nil 'vm-mime-layout)))
+	     (funcall (or function (extent-property e 'vm-mime-function))
+		      e))))))
+
+;; for the karking compiler
+(defvar vm-menu-mime-dispose-menu)
+
+(defun vm-mime-insert-button (caption action layout disposable)
+  (let ((start (point))	e
+	(keymap (make-sparse-keymap))
+	(buffer-read-only nil))
+    (if (fboundp 'set-keymap-parents)
+	(set-keymap-parents keymap (list (current-local-map)))
+      (setq keymap (nconc keymap (current-local-map))))
+    (define-key keymap "\r" 'vm-mime-run-display-function-at-point)
+    (if (and (vm-mouse-xemacs-mouse-p) vm-popup-menu-on-mouse-3)
+	(define-key keymap 'button3 'vm-menu-popup-mime-dispose-menu))
+    (if (not (bolp))
+	(insert "\n"))
+    (insert caption "\n")
+    ;; we MUST have the five arg make-overlay.  overlays must
+    ;; advance when text is inserted at their start position or
+    ;; inline text and graphics will seep into the button
+    ;; overlay and then be removed when the button is removed.
+    (if (fboundp 'make-overlay)
+	(setq e (make-overlay start (point) nil t nil))
+      (setq e (make-extent start (point)))
+      (set-extent-property e 'start-open t)
+      (set-extent-property e 'end-open t))
+    ;; for emacs
+    (vm-set-extent-property e 'mouse-face 'highlight)
+    (vm-set-extent-property e 'local-map keymap)
+    ;; for xemacs
+    (vm-set-extent-property e 'highlight t)
+    (vm-set-extent-property e 'keymap keymap)
+    (vm-set-extent-property e 'balloon-help 'vm-mouse-3-help)
+    ;; for all
+    (vm-set-extent-property e 'vm-mime-disposable disposable)
+    (vm-set-extent-property e 'face vm-mime-button-face)
+    (vm-set-extent-property e 'vm-mime-layout layout)
+    (vm-set-extent-property e 'vm-mime-function action)))
+
+(defun vm-mime-send-body-to-file (layout &optional default-filename)
+  (if (not (vectorp layout))
+      (setq layout (vm-extent-property layout 'vm-mime-layout)))
+  (or default-filename
+      (setq default-filename
+	    (vm-mime-get-disposition-parameter layout "filename")))
+  (and default-filename
+       (setq default-filename (file-name-nondirectory default-filename)))
+  (let ((work-buffer nil)
+	;; evade the XEmacs dialox box, yeccch.
+	(should-use-dialog-box nil)
+	file)
+    (setq file
+	  (read-file-name
+	   (if default-filename
+	       (format "Write MIME body to file (default %s): "
+		       default-filename)
+	     "Write MIME body to file: ")
+	   vm-mime-attachment-save-directory default-filename)
+	  file (expand-file-name file vm-mime-attachment-save-directory))
+    (save-excursion
+      (unwind-protect
+	  (progn
+	    (setq work-buffer (generate-new-buffer " *vm-work*"))
+	    (buffer-disable-undo work-buffer)
+	    (set-buffer work-buffer)
+	    ;; Tell DOS/Windows NT whether the file is binary
+	    (setq buffer-file-type (not (vm-mime-text-type-p layout)))
+	    (vm-mime-insert-mime-body layout)
+	    (vm-mime-transfer-decode-region layout (point-min) (point-max))
+	    (or (not (file-exists-p file))
+		(y-or-n-p "File exists, overwrite? ")
+		(error "Aborted"))
+	    (write-region (point-min) (point-max) file nil nil))
+	(and work-buffer (kill-buffer work-buffer))))))
+
+(defun vm-mime-pipe-body-to-command (layout &optional discard-output)
+  (if (not (vectorp layout))
+      (setq layout (vm-extent-property layout 'vm-mime-layout)))
+  (let ((command-line (read-string "Pipe to command: "))
+	(output-buffer (if discard-output
+			   0
+			 (get-buffer-create "*Shell Command Output*")))
+	(work-buffer nil))
+    (save-excursion
+      (if (bufferp output-buffer)
+	  (progn
+	    (set-buffer output-buffer)
+	    (erase-buffer)))
+      (unwind-protect
+	  (progn
+	    (setq work-buffer (generate-new-buffer " *vm-work*"))
+	    (buffer-disable-undo work-buffer)
+	    (set-buffer work-buffer)
+	    (vm-mime-insert-mime-body layout)
+	    (vm-mime-transfer-decode-region layout (point-min) (point-max))
+	    (let ((pop-up-windows (and pop-up-windows
+				       (eq vm-mutable-windows t)))
+		  ;; Tell DOS/Windows NT whether the input is binary
+		  (binary-process-input (not (vm-mime-text-type-p layout))))
+	      (call-process-region (point-min) (point-max)
+				   (or shell-file-name "sh")
+				   nil output-buffer nil
+				   shell-command-switch command-line)))
+	(and work-buffer (kill-buffer work-buffer)))
+      (if (bufferp output-buffer)
+	  (progn
+	    (set-buffer output-buffer)
+	    (if (not (zerop (buffer-size)))
+		(vm-display output-buffer t (list this-command)
+			    '(vm-pipe-message-to-command))
+	      (vm-display nil nil (list this-command)
+			  '(vm-pipe-message-to-command)))))))
+  t )
+
+(defun vm-mime-pipe-body-to-command-discard-output (layout)
+  (vm-mime-pipe-body-to-command layout t))
+
+(defun vm-mime-scrub-description (string)
+  (let ((work-buffer nil))
+      (save-excursion
+       (unwind-protect
+	   (progn
+	     (setq work-buffer (generate-new-buffer " *vm-work*"))
+	     (buffer-disable-undo work-buffer)
+	     (set-buffer work-buffer)
+	     (insert string)
+	     (while (re-search-forward "[ \t\n]+" nil t)
+	       (replace-match " "))
+	     (buffer-string))
+	 (and work-buffer (kill-buffer work-buffer))))))
+
+(defun vm-mime-layout-description (layout)
+  (if (vm-mm-layout-description layout)
+      (vm-mime-scrub-description (vm-mm-layout-description layout))
+    (let ((type (car (vm-mm-layout-type layout)))
+	  name)
+      (cond ((vm-mime-types-match "multipart/digest" type)
+	     (let ((n (length (vm-mm-layout-parts layout))))
+	       (format "digest (%d message%s)" n (if (= n 1) "" "s"))))
+	    ((vm-mime-types-match "multipart/alternative" type)
+	     "multipart alternative")
+	    ((vm-mime-types-match "multipart" type)
+	     (let ((n (length (vm-mm-layout-parts layout))))
+	       (format "multipart message (%d part%s)" n (if (= n 1) "" "s"))))
+	    ((vm-mime-types-match "text/plain" type)
+	     (format "plain text%s"
+		     (let ((charset (vm-mime-get-parameter layout "charset")))
+		       (if charset
+			   (concat ", " charset)
+			 ""))))
+	    ((vm-mime-types-match "text/enriched" type)
+	     "enriched text")
+	    ((vm-mime-types-match "text/html" type)
+	     "HTML")
+	    ((vm-mime-types-match "image/gif" type)
+	     "GIF image")
+	    ((vm-mime-types-match "image/jpeg" type)
+	     "JPEG image")
+	    ((and (vm-mime-types-match "application/octet-stream" type)
+		  (setq name (vm-mime-get-parameter layout "name"))
+		  (save-match-data (not (string-match "^[ \t]*$" name))))
+	     name)
+	    (t type)))))
+
+(defun vm-mime-layout-contains-type (layout type)
+  (if (vm-mime-types-match type (car (vm-mm-layout-type layout)))
+      layout
+    (let ((p (vm-mm-layout-parts layout))
+	  (result nil)
+	  (done nil))
+      (while (and p (not done))
+	(if (setq result (vm-mime-layout-contains-type (car p) type))
+	    (setq done t)
+	  (setq p (cdr p))))
+      result )))
+  
+(defun vm-mime-plain-message-p (m)
+  (save-match-data
+    (let ((o (vm-mm-layout m))
+	  (case-fold-search t))
+      (and (eq (vm-mm-encoded-header m) 'none)
+	   (or (not (vectorp o))
+	       (and (vm-mime-types-match "text/plain"
+					 (car (vm-mm-layout-type o)))
+		    (string-match "^\\(us-ascii\\|iso-8859-1\\)$"
+				  (or (vm-mime-get-parameter o "charset")
+				      "us-ascii"))
+		    (string-match "^\\(7bit\\|8bit\\|binary\\)$"
+				  (vm-mm-layout-encoding o))))))))
+
+(defun vm-mime-text-type-p (layout)
+  (or (vm-mime-types-match "text" (car (vm-mm-layout-type layout)))
+      (vm-mime-types-match "message" (car (vm-mm-layout-type layout)))))
+
+(defun vm-mime-charset-internally-displayable-p (name)
+  (cond ((and (vm-xemacs-mule-p) (eq (device-type) 'x))
+	 (cdr (assoc (downcase name) vm-mime-xemacs-mule-charset-alist)))
+	((vm-xemacs-p)
+	 (vm-member (downcase name) '("us-ascii" "iso-8859-1")))
+	((vm-fsfemacs-19-p)
+	 (vm-member (downcase name) '("us-ascii" "iso-8859-1")))))
+
+(defun vm-mime-find-message/partials (layout id)
+  (let ((list nil)
+	(type (vm-mm-layout-type layout)))
+    (cond ((vm-mime-types-match "multipart" (car type))
+	   (let ((parts (vm-mm-layout-parts layout)) o)
+	     (while parts
+	       (setq o (vm-mime-find-message/partials (car parts) id))
+	       (if o
+		   (setq list (nconc o list)))
+	       (setq parts (cdr parts)))))
+	  ((vm-mime-types-match "message/partial" (car type))
+	   (if (equal (vm-mime-get-parameter layout "id") id)
+	       (setq list (cons layout list)))))
+    list ))
+
+(defun vm-message-at-point ()
+  (let ((mp vm-message-list)
+	(point (point))
+	(done nil))
+    (while (and mp (not done))
+      (if (and (>= point (vm-start-of (car mp)))
+	       (<= point (vm-end-of (car mp))))
+	  (setq done t)
+	(setq mp (cdr mp))))
+    (car mp)))
+
+(defun vm-mime-make-multipart-boundary ()
+  (let ((boundary (make-string 40 ?a))
+	(i 0))
+    (random t)
+    (while (< i (length boundary))
+      (aset boundary i (aref vm-mime-base64-alphabet
+			     (% (vm-abs (lsh (random) -8))
+				(length vm-mime-base64-alphabet))))
+      (vm-increment i))
+    boundary ))
+
+(defun vm-mime-attach-file (file type &optional charset)
+  "Attach a file to a VM composition buffer to be sent along with the message.
+The file is not inserted into the buffer and MIME encoded until
+you execute vm-mail-send or vm-mail-send-and-exit.  A visible tag
+indicating the existence of the attachment is placed in the
+composition buffer.  You can move the attachment around or remove
+it entirely with normal text editing commands.  If you remove the
+attachment tag, the attachment will not be sent.
+
+First argument, FILE, is the name of the file to attach.  Second
+argument, TYPE, is the MIME Content-Type of the file.  Optional
+third argument CHARSET is the character set of the attached
+document.  This argument is only used for text types, and it
+is ignored for other types.
+
+When called interactively all arguments are read from the
+minibuffer.
+
+This command is for attaching files that do not have a MIME
+header section at the top.  For files with MIME headers, you
+should use vm-mime-attach-mime-file to attach such a file.  VM
+will extract the content type information from the headers in
+this case and not prompt you for it in the minibuffer."
+  (interactive
+   ;; protect value of last-command and this-command
+   (let ((last-command last-command)
+	 (this-command this-command)
+	 (charset nil)
+	 file default-type type)
+     (if (null vm-send-using-mime)
+	 (error "MIME attachments disabled, set vm-send-using-mime non-nil to enable."))
+     (setq file (vm-read-file-name "Attach file: " nil nil t)
+	   default-type (or (vm-mime-default-type-from-filename file)
+			    "application/octet-stream")
+	   type (completing-read
+		 (format "Content type (default %s): "
+			 default-type)
+		 vm-mime-type-completion-alist)
+	   type (if (> (length type) 0) type default-type))
+     (if (vm-mime-types-match "text" type)
+	 (setq charset (completing-read "Character set (default US-ASCII): "
+					vm-mime-charset-completion-alist)
+	       charset (if (> (length charset) 0) charset)))
+     (list file type charset)))
+  (if (null vm-send-using-mime)
+      (error "MIME attachments disabled, set vm-send-using-mime non-nil to enable."))
+  (if (file-directory-p file)
+      (error "%s is a directory, cannot attach" file))
+  (if (not (file-exists-p file))
+      (error "No such file: %s" file))
+  (if (not (file-readable-p file))
+      (error "You don't have permission to read %s" file))
+  (and charset (setq charset (list (concat "charset=" charset))))
+  (vm-mime-attach-object file type charset nil))
+
+(defun vm-mime-attach-mime-file (file)
+  "Attach a MIME encoded file to a VM composition buffer to be sent
+along with the message.
+
+The file is not inserted into the buffer until you execute
+vm-mail-send or vm-mail-send-and-exit.  A visible tag indicating
+the existence of the attachment is placed in the composition
+buffer.  You can move the attachment around or remove it entirely
+with normal text editing commands.  If you remove the attachment
+tag, the attachment will not be sent.
+
+The sole argument, FILE, is the name of the file to attach.
+When called interactively the FILE argument is read from the
+minibuffer.
+
+This command is for attaching files that have a MIME
+header section at the top.  For files without MIME headers, you
+should use vm-mime-attach-file to attach such a file.  VM
+will interactively query you for the file type information."
+  (interactive
+   ;; protect value of last-command and this-command
+   (let ((last-command last-command)
+	 (this-command this-command)
+	 file)
+     (if (null vm-send-using-mime)
+	 (error "MIME attachments disabled, set vm-send-using-mime non-nil to enable."))
+     (setq file (vm-read-file-name "Attach file: " nil nil t))
+     (list file)))
+  (if (null vm-send-using-mime)
+      (error "MIME attachments disabled, set vm-send-using-mime non-nil to enable."))
+  (if (file-directory-p file)
+      (error "%s is a directory, cannot attach" file))
+  (if (not (file-exists-p file))
+      (error "No such file: %s" file))
+  (if (not (file-readable-p file))
+      (error "You don't have permission to read %s" file))
+  (vm-mime-attach-object file "MIME file" nil t))
+
+(defun vm-mime-attach-object (object type params mimed)
+  (if (not (eq major-mode 'mail-mode))
+      (error "Command must be used in a VM Mail mode buffer."))
+  (let ((start (point))
+	e tag-string)
+    (setq tag-string (format "[ATTACHMENT %s, %s]" object type))
+    (insert tag-string "\n")
+    (cond ((fboundp 'make-overlay)
+	   (setq e (make-overlay start (point) nil t nil))
+	   (overlay-put e 'face vm-mime-button-face))
+	  ((fboundp 'make-extent)
+	   (setq e (make-extent start (1- (point))))
+	   (set-extent-property e 'start-open t)
+	   (set-extent-property e 'face vm-mime-button-face)))
+    (vm-set-extent-property e 'duplicable t)
+;; crashes XEmacs
+;;    (vm-set-extent-property e 'replicating t)
+    (vm-set-extent-property e 'vm-mime-type type)
+    (vm-set-extent-property e 'vm-mime-object object)
+    (vm-set-extent-property e 'vm-mime-params params)
+    (vm-set-extent-property e 'vm-mime-encoded mimed)))
+
+(defun vm-mime-default-type-from-filename (file)
+  (let ((alist vm-mime-attachment-auto-type-alist)
+	(case-fold-search t)
+	(done nil))
+    (while (and alist (not done))
+      (if (string-match (car (car alist)) file)
+	  (setq done t)
+	(setq alist (cdr alist))))
+    (and alist (cdr (car alist)))))
+
+(defun vm-remove-mail-mode-header-separator ()
+  (save-excursion
+    (goto-char (point-min))
+    (if (re-search-forward (concat "^" mail-header-separator "$") nil t)
+	(progn
+	  (delete-region (match-beginning 0) (match-end 0))
+	   t )
+      nil )))
+
+(defun vm-add-mail-mode-header-separator ()
+  (save-excursion
+    (goto-char (point-min))
+    (if (re-search-forward "^$" nil t)
+	(replace-match mail-header-separator t t))))
+
+(defun vm-mime-transfer-encode-region (encoding beg end crlf)
+  (let ((case-fold-search t))
+    (cond ((string-match "^binary$" encoding)
+	   (vm-mime-base64-encode-region beg end crlf)
+	   (setq encoding "base64"))
+	  ((string-match "^7bit$" encoding) t)
+	  ((string-match "^base64$" encoding) t)
+	  ((string-match "^quoted-printable$" encoding) t)
+	  ;; must be 8bit
+	  ((eq vm-mime-8bit-text-transfer-encoding 'quoted-printable)
+	   (vm-mime-qp-encode-region beg end)
+	   (setq encoding "quoted-printable"))
+	  ((eq vm-mime-8bit-text-transfer-encoding 'base64)
+	   (vm-mime-base64-encode-region beg end crlf)
+	   (setq encoding "base64"))
+	  ((eq vm-mime-8bit-text-transfer-encoding 'send) t))
+    encoding ))
+
+(defun vm-mime-transfer-encode-layout (layout)
+  (if (vm-mime-text-type-p layout)
+      (vm-mime-transfer-encode-region (vm-mm-layout-encoding layout)
+				      (vm-mm-layout-body-start layout)
+				      (vm-mm-layout-body-end layout)
+				      t)
+    (vm-mime-transfer-encode-region (vm-mm-layout-encoding layout)
+				    (vm-mm-layout-body-start layout)
+				    (vm-mm-layout-body-end layout)
+				    nil)))
+(defun vm-mime-encode-composition ()
+ "MIME encode the current buffer.
+Attachment tags added to the buffer with vm-mime-attach-file are expanded
+and the approriate content-type and boundary markup information is added."
+  (interactive)
+  (save-restriction
+    (widen)
+    (if (not (eq major-mode 'mail-mode))
+	(error "Command must be used in a VM Mail mode buffer."))
+    (or (null (vm-mail-mode-get-header-contents "MIME-Version:"))
+	(error "Message is already MIME encoded."))
+    (let ((8bit nil)
+	  (just-one nil)
+	  (boundary-positions nil)
+	  already-mimed layout e e-list boundary
+	  type encoding charset params object opoint-min)
+      (mail-text)
+      (setq e-list (if (fboundp 'extent-list)
+		       (extent-list nil (point) (point-max))
+		     (overlays-in (point) (point-max)))
+	    e-list (vm-delete (function
+			       (lambda (e)
+				 (vm-extent-property e 'vm-mime-object)))
+			      e-list t)
+	    e-list (sort e-list (function
+				 (lambda (e1 e2)
+				   (< (vm-extent-end-position e1)
+				      (vm-extent-end-position e2))))))
+      ;; If there's just one attachment and no other readable
+      ;; text in the buffer then make the message type just be
+      ;; the attachment type rather than sending a multipart
+      ;; message with one attachment
+      (setq just-one (and (= (length e-list) 1)
+			  (looking-at "[ \t\n]*")
+			  (= (match-end 0)
+			     (vm-extent-start-position (car e-list)))
+			  (save-excursion
+			    (goto-char (vm-extent-end-position (car e-list)))
+			    (looking-at "[ \t\n]*\\'"))))
+      (if (null e-list)
+	  (progn
+	    (narrow-to-region (point) (point-max))
+	    (setq charset (vm-determine-proper-charset (point-min)
+						       (point-max)))
+	    (setq encoding (vm-determine-proper-content-transfer-encoding
+			    (point-min)
+			    (point-max))
+		  encoding (vm-mime-transfer-encode-region encoding
+							   (point-min)
+							   (point-max)
+							   t))
+	    (widen)
+	    (vm-remove-mail-mode-header-separator)
+	    (goto-char (point-min))
+	    (vm-reorder-message-headers
+	     nil nil "\\(Content-Type:\\|Content-Transfer-Encoding\\|MIME-Version:\\)")
+	    (insert "MIME-Version: 1.0\n")
+	    (insert "Content-Type: text/plain; charset=" charset "\n")
+	    (insert "Content-Transfer-Encoding: " encoding "\n")
+	    (vm-add-mail-mode-header-separator))
+	(while e-list
+	  (setq e (car e-list))
+	  (if (or just-one (= (point) (vm-extent-start-position e)))
+	      nil
+	    (narrow-to-region (point) (vm-extent-start-position e))
+	    (setq charset (vm-determine-proper-charset (point-min)
+						       (point-max)))
+	    (setq encoding (vm-determine-proper-content-transfer-encoding
+			    (point-min)
+			    (point-max))
+		  encoding (vm-mime-transfer-encode-region encoding
+							   (point-min)
+							   (point-max)
+							   t))
+	    (setq boundary-positions (cons (point-marker) boundary-positions))
+	    (insert "Content-Type: text/plain; charset=" charset "\n")
+	    (insert "Content-Transfer-Encoding: " encoding "\n\n")
+	    (widen))
+	  (goto-char (vm-extent-end-position e))
+	  (narrow-to-region (point) (point))
+	  (setq object (vm-extent-property e 'vm-mime-object))
+	  (cond ((bufferp object)
+		 (insert-buffer-substring object))
+		((stringp object)
+		 (insert-file-contents-literally object)))
+	  (if (setq already-mimed (vm-extent-property e 'vm-mime-encoded))
+	      (setq layout (vm-mime-parse-entity
+			    nil (list "text/plain" "charset=us-ascii")
+			    "7bit")
+		    type (car (vm-mm-layout-type layout))
+		    params (cdr (vm-mm-layout-type layout)))
+	    (setq type (vm-extent-property e 'vm-mime-type)
+		  params (vm-extent-property e 'vm-mime-parameters)))
+	  (cond ((vm-mime-types-match "text" type)
+		 (setq encoding
+		       (vm-determine-proper-content-transfer-encoding
+			(if already-mimed
+			    (vm-mm-layout-body-start layout)
+			  (point-min))
+			(point-max))
+		       encoding (vm-mime-transfer-encode-region
+				 encoding
+				 (if already-mimed
+				     (vm-mm-layout-body-start layout)
+				   (point-min))
+				 (point-max)
+				 t))
+		 (setq 8bit (or 8bit (equal encoding "8bit"))))
+		((or (vm-mime-types-match "message/rfc822" type)
+		     (vm-mime-types-match "multipart" type))
+		 (setq opoint-min (point-min))
+		 (if (not already-mimed)
+		     (setq layout (vm-mime-parse-entity
+				   nil (list "text/plain" "charset=us-ascii")
+				   "7bit")))
+		 ;; MIME messages of type "message" and
+		 ;; "multipart" are required to have a non-opaque
+		 ;; content transfer encoding.  This means that
+		 ;; if the user only wants to send out 7bit data,
+		 ;; then any subpart that contains 8bit data must
+		 ;; have an opaque (qp or base64) 8->7bit
+		 ;; conversion performed on it so that the
+		 ;; enclosing entity can use an non-opqaue
+		 ;; encoding.
+		 ;;
+		 ;; message/partial requires a "7bit" encoding so
+		 ;; force 8->7 conversion in that case.
+		 (let ((vm-mime-8bit-text-transfer-encoding
+			(if (vm-mime-types-match "message/partial" type)
+			    'quoted-printable
+			  vm-mime-8bit-text-transfer-encoding)))
+		   (vm-mime-map-atomic-layouts 'vm-mime-transfer-encode-layout
+					       (vm-mm-layout-parts layout)))
+		 ;; now figure out a proper content trasnfer
+		 ;; encoding value for the enclosing entity.
+		 (re-search-forward "^\n" nil t)
+		 (save-restriction
+		   (narrow-to-region (point) (point-max))
+		   (setq encoding
+			 (vm-determine-proper-content-transfer-encoding
+			  (point-min)
+			  (point-max))))
+		 (setq 8bit (or 8bit (equal encoding "8bit")))
+		 (goto-char (point-max))
+		 (widen)
+		 (narrow-to-region opoint-min (point)))
+		(t
+		 (vm-mime-base64-encode-region
+		  (if already-mimed
+		      (vm-mm-layout-body-start layout)
+		    (point-min))
+		  (point-max))
+		 (setq encoding "base64")))
+	  (if just-one
+	      nil
+	    (goto-char (point-min))
+	    (setq boundary-positions (cons (point-marker) boundary-positions))
+	    (if (not already-mimed)
+		nil
+	      ;; trim headers
+	      (vm-reorder-message-headers
+	       nil '("Content-Description:" "Content-ID:") nil)
+	      ;; remove header/text separator
+	      (goto-char (1- (vm-mm-layout-body-start layout)))
+	      (if (looking-at "\n")
+		  (delete-char 1)))
+	    (insert "Content-Type: " type)
+	    (if params
+		(if vm-mime-avoid-folding-content-type
+		    (insert "; " (mapconcat 'identity params "; ") "\n")
+		  (insert ";\n\t" (mapconcat 'identity params ";\n\t") "\n"))
+	      (insert "\n"))
+	    (insert "Content-Transfer-Encoding: " encoding "\n\n"))
+	  (goto-char (point-max))
+	  (widen)
+	  (delete-region (vm-extent-start-position e)
+			 (vm-extent-end-position e))
+	  (vm-detach-extent e)
+	  (setq e-list (cdr e-list)))
+	;; handle the remaining chunk of text after the last
+	;; extent, if any.
+	(if (or just-one (= (point) (point-max)))
+	    nil
+	  (setq charset (vm-determine-proper-charset (point)
+						     (point-max)))
+	  (setq encoding (vm-determine-proper-content-transfer-encoding
+			  (point)
+			  (point-max))
+		encoding (vm-mime-transfer-encode-region encoding
+							 (point)
+							 (point-max)
+							 t))
+	  (setq 8bit (or 8bit (equal encoding "8bit")))
+	  (setq boundary-positions (cons (point-marker) boundary-positions))
+	  (insert "Content-Type: text/plain; charset=" charset "\n")
+	  (insert "Content-Transfer-Encoding: " encoding "\n\n")
+	  (goto-char (point-max)))
+	(setq boundary (vm-mime-make-multipart-boundary))
+	(mail-text)
+	(while (re-search-forward (concat "^--"
+					  (regexp-quote boundary)
+					  "\\(--\\)?$")
+				  nil t)
+	  (setq boundary (vm-mime-make-multipart-boundary))
+	  (mail-text))
+	(goto-char (point-max))
+	(or just-one (insert "\n--" boundary "--\n"))
+	(while boundary-positions
+	  (goto-char (car boundary-positions))
+	  (insert "\n--" boundary "\n")
+	  (setq boundary-positions (cdr boundary-positions)))
+	(if (and just-one already-mimed)
+	    (progn
+	      (goto-char (vm-mm-layout-header-start layout))
+	      ;; trim headers
+	      (vm-reorder-message-headers
+	       nil '("Content-Description:" "Content-ID:") nil)
+	      ;; remove header/text separator
+	      (goto-char (1- (vm-mm-layout-body-start layout)))
+	      (if (looking-at "\n")
+		  (delete-char 1))
+	      ;; copy remainder to enclosing entity's header section
+	      (insert-buffer-substring (current-buffer)
+				       (vm-mm-layout-header-start layout)
+				       (vm-mm-layout-body-start layout))
+	      (delete-region (vm-mm-layout-header-start layout)
+			     (vm-mm-layout-body-start layout))))
+	(goto-char (point-min))
+	(vm-remove-mail-mode-header-separator)
+	(vm-reorder-message-headers
+	 nil nil "\\(Content-Type:\\|MIME-Version:\\|Content-Transfer-Encoding\\)")
+	(vm-add-mail-mode-header-separator)
+	(insert "MIME-Version: 1.0\n")
+	(if (not just-one)
+	    (insert (if vm-mime-avoid-folding-content-type
+			"Content-Type: multipart/mixed; boundary=\""
+		      "Content-Type: multipart/mixed;\n\tboundary=\"")
+		    boundary "\"\n")
+	  (insert "Content-Type: " type)
+	  (if params
+	      (if vm-mime-avoid-folding-content-type
+		  (insert "; " (mapconcat 'identity params "; ") "\n")
+		(insert ";\n\t" (mapconcat 'identity params ";\n\t"))))
+	  (insert "\n"))
+	(if just-one
+	    (insert "Content-Transfer-Encoding: " encoding "\n")
+	  (if 8bit
+	      (insert "Content-Transfer-Encoding: 8bit\n")
+	    (insert "Content-Transfer-Encoding: 7bit\n")))))))
+
+(defun vm-mime-fragment-composition (size)
+  (save-restriction
+    (widen)
+    (vm-unsaved-message "Fragmenting message...")
+    (let ((buffers nil)
+	  (id (vm-mime-make-multipart-boundary))
+	  (n 1)
+	  (the-end nil)
+	  b header-start header-end master-buffer start end)
+      (vm-remove-mail-mode-header-separator)
+      ;; message/partial must have "7bit" content transfer
+      ;; encoding, so verify that everything has been encoded for
+      ;; 7bit transmission.
+      (let ((vm-mime-8bit-text-transfer-encoding
+	     (if (eq vm-mime-8bit-text-transfer-encoding 'send)
+		 'quoted-printable
+	       vm-mime-8bit-text-transfer-encoding)))
+	(vm-mime-map-atomic-layouts
+	 'vm-mime-transfer-encode-layout
+	 (list (vm-mime-parse-entity nil (list "text/plain" "charset=us-ascii")
+				     "7bit"))))
+      (goto-char (point-min))
+      (setq header-start (point))
+      (search-forward "\n\n")
+      (setq header-end (1- (point)))
+      (setq master-buffer (current-buffer))
+      (goto-char (point-min))
+      (setq start (point))
+      (while (not (eobp))
+	(condition-case nil
+	    (progn
+	      (forward-char (max (- size 150) 2000))
+	      (beginning-of-line))
+	  (end-of-buffer (setq the-end t)))
+	(setq end (point))
+	(setq b (generate-new-buffer (concat (buffer-name) " part "
+					     (int-to-string n))))
+	(setq buffers (cons b buffers))
+	(set-buffer b)
+	(make-local-variable 'vm-send-using-mime)
+	(setq vm-send-using-mime nil)
+	(insert-buffer-substring master-buffer header-start header-end)
+	(goto-char (point-min))
+	(vm-reorder-message-headers nil nil
+         "\\(Content-Type:\\|MIME-Version:\\|Content-Transfer-Encoding\\)")
+	(insert "MIME-Version: 1.0\n")
+	(insert (format
+		 (if vm-mime-avoid-folding-content-type
+		     "Content-Type: message/partial; id=%s; number=%d"
+		   "Content-Type: message/partial;\n\tid=%s;\n\tnumber=%d")
+		 id n))
+	(if the-end
+	    (if vm-mime-avoid-folding-content-type
+		(insert (format "; total=%d\n" n))
+	      (insert (format ";\n\ttotal=%d\n" n)))
+	  (insert "\n"))
+	(insert "Content-Transfer-Encoding: 7bit\n")
+	(goto-char (point-max))
+	(insert mail-header-separator "\n")
+	(insert-buffer-substring master-buffer start end)
+	(vm-increment n)
+	(set-buffer master-buffer)
+	(setq start (point)))
+      (vm-unsaved-message "Fragmenting message... done")
+      (nreverse buffers))))
+
+(defun vm-mime-preview-composition ()
+  "Show how the current composition buffer might be displayed
+in a MIME-aware mail reader.  VM copies and encodes the current
+mail composition buffer and displays it as a mail folder.
+Type `q' to quit this temp folder and return to composing your
+message."
+  (interactive)
+  (if (not (eq major-mode 'mail-mode))
+      (error "Command must be used in a VM Mail mode buffer."))
+  (let ((temp-buffer nil)
+	(mail-buffer (current-buffer))
+	e-list)
+    (unwind-protect
+	(progn
+	  (mail-text)
+	  (setq e-list (if (fboundp 'extent-list)
+			   (extent-list nil (point) (point-max))
+			 (overlays-in (point) (point-max)))
+		e-list (vm-delete (function
+				   (lambda (e)
+				     (vm-extent-property e 'vm-mime-object)))
+				  e-list t)
+		e-list (sort e-list (function
+				     (lambda (e1 e2)
+				       (< (vm-extent-end-position e1)
+					  (vm-extent-end-position e2))))))
+	  (setq temp-buffer (generate-new-buffer "composition preview"))
+	  (set-buffer temp-buffer)
+	  ;; so vm-mime-encode-composition won't complain
+	  (setq major-mode 'mail-mode)
+	  (vm-insert-region-from-buffer mail-buffer)
+	  (mapcar 'vm-copy-extent e-list)
+	  (goto-char (point-min))
+	  (or (vm-mail-mode-get-header-contents "From")
+	      (insert "From: " (or user-mail-address (user-login-name)) "\n"))
+	  (or (vm-mail-mode-get-header-contents "Message-ID")
+	      (insert "Message-ID: <fake@fake.com>\n"))
+	  (or (vm-mail-mode-get-header-contents "Date")
+	      (insert "Date: "
+		      (format-time-string "%a, %d %b %Y %H%M%S %Z"
+					  (current-time))
+		      "\n"))
+	  (and vm-send-using-mime
+	       (null (vm-mail-mode-get-header-contents "MIME-Version:"))
+	       (vm-mime-encode-composition))
+	  (goto-char (point-min))
+	  (insert (vm-leading-message-separator 'From_))
+	  (goto-char (point-max))
+	  (insert (vm-trailing-message-separator 'From_))
+	  (set-buffer-modified-p nil)
+	  ;; point of no return, don't kill it if the user quits
+	  (setq temp-buffer nil)
+	  (let ((vm-auto-decode-mime-messages t)
+		(vm-auto-displayed-mime-content-types t))
+	    (vm-save-buffer-excursion
+	     (vm-goto-new-folder-frame-maybe 'folder)
+	     (vm-mode)))
+	  (message
+	   (substitute-command-keys
+	    "Type \\[vm-quit] to continue composing your message"))
+	  ;; temp buffer, don't offer to save it.
+	  (setq buffer-offer-save nil)
+	  (vm-display (or vm-presentation-buffer (current-buffer)) t
+		      (list this-command) '(vm-mode startup)))
+      (and temp-buffer (kill-buffer temp-buffer)))))
+
+(defun vm-mime-composite-type-p (type)
+  (or (vm-mime-types-match "message" type)
+      (vm-mime-types-match "multipart" type)))
+
+(defun vm-mime-map-atomic-layouts (function list)
+  (while list
+    (if (vm-mime-composite-type-p (car (vm-mm-layout-type (car list))))
+	(vm-mime-map-atomic-layouts function (vm-mm-layout-parts (car list)))
+      (funcall function (car list)))
+    (setq list (cdr list))))