]> code.delx.au - gnu-emacs/blobdiff - lisp/mail/rmail.el
(smtpmail-queue-counter): New variable.
[gnu-emacs] / lisp / mail / rmail.el
index 70506ef5ad17bb9cf912b60696953d2ddf7b5297..18edb1d6b18c3c51e179048db8adad33fa9b2ce6 100644 (file)
@@ -1,6 +1,6 @@
-;;; rmail.el --- main code of "RMAIL" mail reader for Emacs.
+;;; rmail.el --- main code of "RMAIL" mail reader for Emacs
 
-;; Copyright (C) 1985,86,87,88,93,94,95,96,97,98,2000
+;; Copyright (C) 1985,86,87,88,93,94,95,96,97,98,2000, 2001
 ;;             Free Software Foundation, Inc.
 
 ;; Maintainer: FSF
@@ -23,6 +23,8 @@
 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 ;; Boston, MA 02111-1307, USA.
 
+;;; Commentary:
+
 ;;; Code:
 
 ;; Souped up by shane@mit-ajax based on ideas of rlk@athena.mit.edu
@@ -38,6 +40,7 @@
 ;;
 
 (require 'mail-utils)
+(eval-when-compile (require 'mule-util)) ; for detect-coding-with-priority
 
 ; These variables now declared in paths.el.
 ;(defvar rmail-spool-directory "/usr/spool/mail/"
   :prefix "rmail-"
   :group 'rmail)
 
+(defgroup rmail-edit nil
+  "Rmail editing."
+  :prefix "rmail-edit-"
+  :group 'rmail)
+
 
 (defcustom rmail-movemail-program nil
   "If non-nil, name of program for fetching new mail."
@@ -124,8 +132,8 @@ rather than deleted, after it is retrieved."
 
 ;;;###autoload
 (defcustom rmail-dont-reply-to-names nil "\
-*A regexp specifying names to prune of reply to messages.
-A value of nil means exclude your own login name as an address
+*A regexp specifying addresses to prune from a reply message.
+A value of nil means exclude your own email address as an address
 plus whatever is specified by `rmail-default-dont-reply-to-names'."
   :type '(choice regexp (const :tag "Your Name" nil))
   :group 'rmail-reply)
@@ -135,11 +143,25 @@ plus whatever is specified by `rmail-default-dont-reply-to-names'."
 A regular expression specifying part of the value of the default value of
 the variable `rmail-dont-reply-to-names', for when the user does not set
 `rmail-dont-reply-to-names' explicitly.  (The other part of the default
-value is the user's name.)
+value is the user's email address and name.)
 It is useful to set this variable in the site customization file.")
 
 ;;;###autoload
-(defcustom rmail-ignored-headers "^via:\\|^mail-from:\\|^origin:\\|^references:\\|^status:\\|^received:\\|^x400-originator:\\|^x400-recipients:\\|^x400-received:\\|^x400-mts-identifier:\\|^x400-content-type:\\|^\\(resent-\\|\\)message-id:\\|^summary-line:\\|^resent-date:\\|^nntp-posting-host:\\|^path:\\|^x-char.*:\\|^x-face:\\|^x-mailer:\\|^delivered-to:\\|^lines:\\|^mime-version:\\|^content-transfer-encoding:\\|^x-coding-system:\\|^return-path:\\|^errors-to:\\|^return-receipt-to:\\|^x-attribution:\\|^x-disclaimer:"
+(defcustom rmail-ignored-headers
+  (concat "^via:\\|^mail-from:\\|^origin:\\|^references:"
+         "\\|^status:\\|^received:\\|^x400-originator:\\|^x400-recipients:"
+         "\\|^x400-received:\\|^x400-mts-identifier:\\|^x400-content-type:"
+         "\\|^\\(resent-\\|\\)message-id:\\|^summary-line:\\|^resent-date:"
+         "\\|^nntp-posting-host:\\|^path:\\|^x-char.*:\\|^x-face:"
+         "\\|^x-mailer:\\|^delivered-to:\\|^lines:\\|^mime-version:"
+         "\\|^content-transfer-encoding:\\|^x-coding-system:"
+         "\\|^return-path:\\|^errors-to:\\|^return-receipt-to:"
+         "\\|^x-sign:\\|^x-beenthere:\\|^x-mailman-version:"
+         "\\|^precedence:\\|^list-help:\\|^list-post:\\|^list-subscribe:"
+         "\\|^list-id:\\|^list-unsubscribe:\\|^list-archive:"
+         "\\|^content-type:\\|^content-length:"
+         "\\|^x-attribution:\\|^x-disclaimer:\\|^x-trace:"
+         "\\|^x-complaints-to:\\|^nntp-posting-date:\\|^user-agent:")
   "*Regexp to match header fields that Rmail should normally hide.
 This variable is used for reformatting the message header,
 which normally happens once for each message,
@@ -159,7 +181,7 @@ If nil, display all header fields except those matched by
   :group 'rmail-headers)
 
 ;;;###autoload
-(defcustom rmail-retry-ignored-headers nil "\
+(defcustom rmail-retry-ignored-headers "^x-authentication-warning:" "\
 *Headers that should be stripped when retrying a failed message."
   :type '(choice regexp (const nil :tag "None"))
   :group 'rmail-headers)
@@ -398,9 +420,47 @@ until a user explicitly requires it."
                 (other :tag "when asked" ask))
   :group 'rmail)
 
+(defvar rmail-enable-mime-composing nil
+  "*If non-nil, RMAIL uses `rmail-insert-mime-forwarded-message-function' to forward.")
+
 ;;;###autoload
 (defvar rmail-show-mime-function nil
-  "Function to show MIME decoded message of RMAIL file.")
+  "Function to show MIME decoded message of RMAIL file.
+This function is called when `rmail-enable-mime' is non-nil.
+It is called with no argument.")
+
+;;;###autoload
+(defvar rmail-insert-mime-forwarded-message-function nil
+  "Function to insert a message in MIME format so it can be forwarded.
+This function is called if `rmail-enable-mime' or 
+`rmail-enable-mime-composing' is non-nil.
+It is called with one argument FORWARD-BUFFER, which is a
+buffer containing the message to forward.  The current buffer
+is the outgoing mail buffer.")
+
+;;;###autoload
+(defvar rmail-insert-mime-resent-message-function nil
+  "Function to insert a message in MIME format so it can be resent.
+This function is called if `rmail-enable-mime' is non-nil.
+It is called with one argument FORWARD-BUFFER, which is a
+buffer containing the message to forward.  The current buffer
+is the outgoing mail buffer.")
+
+;;;###autoload
+(defvar rmail-search-mime-message-function nil
+  "Function to check if a regexp matches a MIME message.
+This function is called if `rmail-enable-mime' is non-nil.
+It is called with two arguments MSG and REGEXP, where
+MSG is the message number, REGEXP is the regular expression.")
+
+;;;###autoload
+(defvar rmail-search-mime-header-function nil
+  "Function to check if a regexp matches a header of MIME message.
+This function is called if `rmail-enable-mime' is non-nil.
+It is called with four arguments MSG, REGEXP, and LIMIT, where
+MSG is the message number,
+REGEXP is the regular expression,
+LIMIT is the position specifying the end of header.")
 
 ;;;###autoload
 (defvar rmail-mime-feature 'rmail-mime
@@ -529,8 +589,25 @@ The first parenthesized expression should match the MIME-charset name.")
 \f
 ;;;; *** Rmail Mode ***
 
+;; This variable is dynamically bound.  The defvar is here to placate
+;; the byte compiler.
+
 (defvar rmail-enable-multibyte nil)
 
+
+(defun rmail-require-mime-maybe ()
+  "Require `rmail-mime-feature' if that is non-nil.
+Signal an error and set `rmail-mime-feature' to nil if the feature
+isn't provided."
+  (when rmail-enable-mime
+    (condition-case err
+       (require rmail-mime-feature)
+      (error
+       (message "Feature `%s' not provided" rmail-mime-feature)
+       (sit-for 1)
+       (setq rmail-enable-mime nil)))))
+
+
 ;;;###autoload
 (defun rmail (&optional file-name-arg)
   "Read and edit incoming mail.
@@ -546,15 +623,12 @@ have a chance to specify a file name with the minibuffer.
 If `rmail-display-summary' is non-nil, make a summary for this RMAIL file."
   (interactive (if current-prefix-arg
                   (list (read-file-name "Run rmail on RMAIL file: "))))
-  (if rmail-enable-mime
-      (condition-case err
-         (require rmail-mime-feature)
-       (error (message "Feature `%s' not provided" rmail-mime-feature)
-              (sit-for 1)
-              (setq rmail-enable-mime nil))))
+  (rmail-require-mime-maybe)
   (let* ((file-name (expand-file-name (or file-name-arg rmail-file-name)))
-        (existed (get-file-buffer file-name))
-        ;; This binding is necessary because we much decide if we
+        ;; Use find-buffer-visiting, not get-file-buffer, for those users
+        ;; who have find-file-visit-truename set to t.
+        (existed (find-buffer-visiting file-name))
+        ;; This binding is necessary because we must decide if we
         ;; need code conversion while the buffer is unibyte
         ;; (i.e. enable-multibyte-characters is nil).
          (rmail-enable-multibyte
@@ -705,11 +779,21 @@ Note:    it means the file has no messages in it.\n\^_")))
     (setq to (point))
     (unless (and coding-system
                 (coding-system-p coding-system))
-      (setq coding-system (detect-coding-region from to t)))
+      (setq coding-system
+           ;; Emacs 21.1 and later writes RMAIL files in emacs-mule, but
+           ;; earlier versions did that with the current buffer's encoding.
+           ;; So we want to favor detection of emacs-mule (whose normal
+           ;; priority is quite low), but still allow detection of other
+           ;; encodings if emacs-mule won't fit.  The call to
+           ;; detect-coding-with-priority below achieves that.
+           (car (detect-coding-with-priority
+                 from to
+                 '((coding-category-emacs-mule . emacs-mule))))))
     (unless (memq coding-system
                  '(undecided undecided-unix))
       (set-buffer-modified-p t)                ; avoid locking when decoding
-      (decode-coding-region from to coding-system)
+      (let ((buffer-undo-list t))
+       (decode-coding-region from to coding-system))
       (setq coding-system last-coding-system-used))
     (set-buffer-modified-p modifiedp)
     (setq buffer-file-coding-system nil)
@@ -771,7 +855,7 @@ Note:    it means the file has no messages in it.\n\^_")))
   (define-key rmail-mode-map "\C-c\C-s\C-r" 'rmail-sort-by-recipient)
   (define-key rmail-mode-map "\C-c\C-s\C-c" 'rmail-sort-by-correspondent)
   (define-key rmail-mode-map "\C-c\C-s\C-l" 'rmail-sort-by-lines)
-  (define-key rmail-mode-map "\C-c\C-s\C-k" 'rmail-sort-by-keywords)
+  (define-key rmail-mode-map "\C-c\C-s\C-k" 'rmail-sort-by-labels)
   (define-key rmail-mode-map "\C-c\C-n" 'rmail-next-same-subject)
   (define-key rmail-mode-map "\C-c\C-p" 'rmail-previous-same-subject)
   )
@@ -948,10 +1032,23 @@ Instead, these commands are available:
 \\[rmail-summary-by-topic]   Summarize only messages with subject line regexp(s).
 \\[rmail-toggle-header]        Toggle display of complete header."
   (interactive)
-  (rmail-mode-2)
-  (rmail-set-message-counters)
-  (rmail-show-message rmail-total-messages)
-  (run-hooks 'rmail-mode-hook))
+  (let ((finding-rmail-file (not (eq major-mode 'rmail-mode))))
+    (rmail-mode-2)
+    (when (and finding-rmail-file
+              (null coding-system-for-read)
+              default-enable-multibyte-characters)
+      (let ((rmail-enable-multibyte t))
+       (rmail-require-mime-maybe)
+       (rmail-convert-file)
+       (goto-char (point-max))
+       (set-buffer-multibyte t)))
+    (rmail-set-message-counters)
+    (rmail-show-message rmail-total-messages)
+    (when finding-rmail-file
+      (when rmail-display-summary
+       (rmail-summary))
+      (rmail-construct-io-menu))
+    (run-hooks 'rmail-mode-hook)))
 
 (defun rmail-mode-2 ()
   (kill-all-local-variables)
@@ -1040,6 +1137,7 @@ Instead, these commands are available:
 
 ;; Handle M-x revert-buffer done in an rmail-mode buffer.
 (defun rmail-revert (arg noconfirm)
+  (set-buffer rmail-buffer)
   (let* ((revert-buffer-function (default-value 'revert-buffer-function))
         (rmail-enable-multibyte enable-multibyte-characters)
         ;; See similar code in `rmail'.
@@ -1049,7 +1147,8 @@ Instead, these commands are available:
        ;; If the user said "yes", and we changed something,
        ;; reparse the messages.
        (progn
-         (rmail-mode-2)
+         (set-buffer rmail-buffer)
+         (rmail-mode-2)
          ;; Convert all or part to Babyl file if possible.
          (rmail-convert-file)
          ;; We have read the file as raw-text, so the buffer is set to
@@ -1084,6 +1183,7 @@ Instead, these commands are available:
   "Expunge and save RMAIL file."
   (interactive)
   (rmail-expunge)
+  (set-buffer rmail-buffer)
   (save-buffer)
   (if (rmail-summary-exists)
       (rmail-select-summary (set-buffer-modified-p nil))))
@@ -1099,9 +1199,17 @@ Hook `rmail-quit-hook' is run after expunging."
   (when rmail-summary-buffer
     (replace-buffer-in-windows rmail-summary-buffer)
     (bury-buffer rmail-summary-buffer))
-  (let ((obuf (current-buffer)))
-    (quit-window)
-    (replace-buffer-in-windows obuf)))
+  (if rmail-enable-mime
+      (let ((obuf rmail-buffer)
+           (ovbuf rmail-view-buffer))
+       (set-buffer rmail-view-buffer)
+       (quit-window)
+       (replace-buffer-in-windows ovbuf)
+       (replace-buffer-in-windows obuf)
+       (bury-buffer obuf))
+    (let ((obuf (current-buffer)))
+      (quit-window)
+      (replace-buffer-in-windows obuf))))
 
 (defun rmail-bury ()
   "Bury current Rmail buffer and its summary buffer."
@@ -1237,6 +1345,7 @@ It returns t if it got any new messages."
   ;; revert to it before we get new mail.
   (or (verify-visited-file-modtime (current-buffer))
       (find-file (buffer-file-name)))
+  (set-buffer rmail-buffer)
   (rmail-maybe-set-message-counters)
   (widen)
   ;; Get rid of all undo records for this buffer.
@@ -1362,7 +1471,7 @@ It returns t if it got any new messages."
       (if popmail
          (setq renamep t)
        (setq file (file-truename
-                   (expand-file-name (substitute-in-file-name file)))))
+                   (substitute-in-file-name (expand-file-name file)))))
       (setq tofile (expand-file-name
                    ;; Generate name to move to from inbox name,
                    ;; in case of multiple inboxes that need moving.
@@ -1785,8 +1894,8 @@ It returns t if it got any new messages."
   (goto-char beg)
   (forward-line 1)
   (if (/= (following-char) ?0)
-      (error "Bad format in RMAIL file."))
-  (let ((buffer-read-only nil)
+      (error "Bad format in RMAIL file"))
+  (let ((inhibit-read-only t)
        (delta (- (buffer-size) end)))
     (delete-char 1)
     (insert ?1)
@@ -1856,72 +1965,121 @@ Otherwise, delete all header fields whose names match `rmail-ignored-headers'."
       (forward-line 1)
       (= (following-char) ?1))))
 
+(defun rmail-msg-restore-non-pruned-header ()
+  (let ((old-point (point))
+       new-point
+       new-start
+       (inhibit-read-only t))
+    (save-excursion
+      (narrow-to-region (rmail-msgbeg rmail-current-message) (point-max))
+      (goto-char (point-min))
+      (forward-line 1)
+      ;; Change 1 to 0.
+      (delete-char 1)
+      (insert ?0)
+      ;; Insert new EOOH line at the proper place.
+      (forward-line 1)
+      (let ((case-fold-search t))
+       (while (looking-at "Summary-Line:\\|Mail-From:")
+         (forward-line 1)))
+      (insert "*** EOOH ***\n")
+      (setq new-start (point))
+      ;; Delete the old reformatted header.
+      (forward-char -1)
+      (search-forward "\n*** EOOH ***\n")
+      (forward-line -1)
+      (let ((start (point)))
+       (search-forward "\n\n")
+       (if (and (<= start old-point)
+                (<= old-point (point)))
+           (setq new-point new-start))
+       (delete-region start (point)))
+      ;; Narrow to after the new EOOH line.
+      (narrow-to-region new-start (point-max)))
+    (if new-point
+       (goto-char new-point))))
+
+(defun rmail-msg-prune-header ()
+  (let ((new-point
+        (= (point) (point-min))))
+    (save-excursion
+      (narrow-to-region (rmail-msgbeg rmail-current-message) (point-max))
+      (rmail-reformat-message (point-min) (point-max)))
+    (if new-point
+       (goto-char (point-min)))))
+
 (defun rmail-toggle-header (&optional arg)
   "Show original message header if pruned header currently shown, or vice versa.
 With argument ARG, show the message header pruned if ARG is greater than zero;
 otherwise, show it in full."
   (interactive "P")
-  (let* ((buffer-read-only nil)
-        (pruned (rmail-msg-is-pruned))
+  (let* ((pruned (with-current-buffer rmail-buffer
+                  (rmail-msg-is-pruned)))
         (prune (if arg
                    (> (prefix-numeric-value arg) 0)
                  (not pruned))))
     (if (eq pruned prune)
        t
+      (set-buffer rmail-buffer)
       (rmail-maybe-set-message-counters)
-      (let ((at-point-min (= (point) (point-min)))
-            (all-headers-visible (= (window-start) (point-min)))
-            (on-header (save-excursion
-                         (and (not (search-backward "\n\n" nil t))
-                              (progn
-                                (end-of-line)
-                                (re-search-backward "^[-A-Za-z0-9]+:" nil t))
-                              (match-string 0))))
-            (old-screen-line (rmail-count-screen-lines (window-start) (point))))
-        (save-excursion
-         (narrow-to-region (rmail-msgbeg rmail-current-message) (point-max))
+      (if rmail-enable-mime
+         (let ((buffer-read-only nil))
+           (if pruned
+               (rmail-msg-restore-non-pruned-header)
+             (rmail-msg-prune-header))
+           (funcall rmail-show-mime-function))
+       (let* ((buffer-read-only nil)
+              (window (get-buffer-window (current-buffer)))
+              (at-point-min (= (point) (point-min)))
+              (all-headers-visible (= (window-start window) (point-min)))
+              (on-header
+               (save-excursion
+                 (and (not (search-backward "\n\n" nil t))
+                      (progn
+                        (end-of-line)
+                        (re-search-backward "^[-A-Za-z0-9]+:" nil t))
+                      (match-string 0))))
+              (old-screen-line
+               (rmail-count-screen-lines (window-start window) (point))))
          (if pruned
-             (let (new-start)
-               (goto-char (point-min))
-               (forward-line 1)
-               ;; Change 1 to 0.
-               (delete-char 1)
-               (insert ?0)
-               ;; Insert new EOOH line at the proper place.
-               (forward-line 1)
-               (let ((case-fold-search t))
-                 (while (looking-at "Summary-Line:\\|Mail-From:")
-                   (forward-line 1)))
-               (insert "*** EOOH ***\n")
-               (setq new-start (point))
-               ;; Delete the old reformatted header.
-               (forward-char -1)
-               (search-forward "\n*** EOOH ***\n")
-               (forward-line -1)
-               (let ((start (point)))
-                 (search-forward "\n\n")
-                 (delete-region start (point)))
-               ;; Narrow to after the new EOOH line.
-               (narrow-to-region new-start (point-max)))
-           (rmail-reformat-message (point-min) (point-max))))
-       (cond (at-point-min
-              (goto-char (point-min)))
-             (on-header
-              (goto-char (point-min))
-              (search-forward "\n\n")
-              (or (re-search-backward (concat "^" (regexp-quote on-header)) nil t)
-                  (goto-char (point-min))))
-             (t
-              (recenter old-screen-line)
-              (if (and all-headers-visible
-                       (not (= (window-start) (point-min))))
-                  (let ((lines-offscreen (rmail-count-screen-lines
-                                          (point-min) (window-start))))
-                    (recenter (min (+ old-screen-line lines-offscreen)
-                                   ;; last line of window
-                                   (- (window-height) 2))))))))
+             (rmail-msg-restore-non-pruned-header)
+           (rmail-msg-prune-header))
+         (cond (at-point-min
+                (goto-char (point-min)))
+               (on-header
+                (goto-char (point-min))
+                (search-forward "\n\n")
+                (or (re-search-backward
+                     (concat "^" (regexp-quote on-header)) nil t)
+                    (goto-char (point-min))))
+               (t
+                (save-selected-window
+                  (select-window window)
+                  (recenter old-screen-line)
+                  (if (and all-headers-visible
+                           (not (= (window-start) (point-min))))
+                      (recenter (- (window-height) 2))))))))
       (rmail-highlight-headers))))
 
+(defun rmail-narrow-to-non-pruned-header ()
+  "Narrow to the whole (original) header of the current message."
+  (let (start end)
+    (narrow-to-region (rmail-msgbeg rmail-current-message) (point-max))
+    (goto-char (point-min))
+    (forward-line 1)
+    (if (= (following-char) ?1)
+       (progn
+         (forward-line 1)
+         (setq start (point))
+         (search-forward "*** EOOH ***\n")
+         (setq end (match-beginning 0)))
+      (forward-line 2)
+      (setq start (point))
+      (search-forward "\n\n")
+      (setq end (1- (point))))
+    (narrow-to-region start end)
+    (goto-char start)))
+
 ;; Lifted from repos-count-screen-lines.
 ;; Return number of screen lines between START and END.
 (defun rmail-count-screen-lines (start end)
@@ -1979,12 +2137,21 @@ otherwise, show it in full."
                          (substring blurb (match-end 0)))))
     (setq mode-line-process
          (format " %d/%d%s"
-                 rmail-current-message rmail-total-messages blurb))))
+                 rmail-current-message rmail-total-messages blurb))
+    ;; If rmail-enable-mime is non-nil, we may have to update
+    ;; `mode-line-process' of rmail-view-buffer too.
+    (if (and rmail-enable-mime
+            (not (eq (current-buffer) rmail-view-buffer))
+            (buffer-live-p rmail-view-buffer))
+       (let ((mlp mode-line-process))
+         (with-current-buffer rmail-view-buffer
+           (setq mode-line-process mlp))))))
 
 ;; Turn an attribute of a message on or off according to STATE.
 ;; ATTR is the name of the attribute, as a string.
 ;; MSGNUM is message number to change; nil means current message.
 (defun rmail-set-attribute (attr state &optional msgnum)
+  (set-buffer rmail-buffer)
   (let ((omax (point-max-marker))
        (omin (point-min-marker))
        (buffer-read-only nil))
@@ -2236,6 +2403,11 @@ If summary buffer is currently displayed, update current message there also."
            (search-forward "\n*** EOOH ***\n" end t)
            (narrow-to-region (point) end)))
        (goto-char (point-min))
+       (walk-windows
+        (function (lambda (window)
+                    (if (eq (window-buffer window) (current-buffer))
+                        (set-window-point window (point)))))
+        nil t)
        (rmail-display-labels)
        (if (eq rmail-enable-mime t)
            (funcall rmail-show-mime-function)
@@ -2253,10 +2425,78 @@ If summary buffer is currently displayed, update current message there also."
             (let ((curr-msg rmail-current-message))
               (rmail-select-summary
                (rmail-summary-goto-msg curr-msg t t))))
-       (rmail-auto-file)
+       (with-current-buffer rmail-buffer
+         (rmail-auto-file))
        (if blurb
            (message blurb))))))
 
+(defun rmail-redecode-body (coding)
+  "Decode the body of the current message using coding system CODING.
+This is useful with mail messages that have malformed or missing
+charset= headers.
+
+This function assumes that the current message is already decoded
+and displayed in the RMAIL buffer, but the coding system used to
+decode it was incorrect.  It then encodes the message back to its
+original form, and decodes it again, using the coding system CODING.
+
+Note that if Emacs erroneously auto-detected one of the iso-2022
+encodings in the message, this function might fail because the escape
+sequences that switch between character sets and also single-shift and
+locking-shift codes are impossible to recover.  This function is meant
+to be used to fix messages encoded with 8-bit encodings, such as
+iso-8859, koi8-r, etc."
+  (interactive "zCoding system for re-decoding this message: ")
+  (when (not rmail-enable-mime)
+    (or (eq major-mode 'rmail-mode)
+       (switch-to-buffer rmail-buffer))
+    (save-excursion
+      (let ((pruned (rmail-msg-is-pruned)))
+       (unwind-protect
+           (let ((msgbeg (rmail-msgbeg rmail-current-message))
+                 (msgend (rmail-msgend rmail-current-message))
+                 x-coding-header)
+             ;; We need the message headers pruned (we later restore
+             ;; the pruned stat to what it was, see the end of
+             ;; unwind-protect form).
+             (or pruned
+                 (rmail-toggle-header 1))
+             (narrow-to-region msgbeg msgend)
+             (goto-char (point-min))
+             (when (search-forward "\n*** EOOH ***\n" (point-max) t)
+               (narrow-to-region msgbeg (point)))
+             (goto-char (point-min))
+             (if (re-search-forward "^X-Coding-System: *\\(.*\\)$" nil t)
+                 (let ((old-coding (intern (match-string 1)))
+                       (buffer-read-only nil))
+                   (check-coding-system old-coding)
+                   ;; Make sure the new coding system uses the same EOL
+                   ;; conversion, to prevent ^M characters from popping
+                   ;; up all over the place.
+                   (setq coding
+                         (coding-system-change-eol-conversion
+                          coding
+                          (coding-system-eol-type old-coding)))
+                   (setq x-coding-header (point-marker))
+                   (narrow-to-region msgbeg msgend)
+                   (encode-coding-region (point) msgend old-coding)
+                   (decode-coding-region (point) msgend coding)
+                   (setq last-coding-system-used coding)
+                   ;; Rewrite the coding-system header according
+                   ;; to what we did.
+                   (goto-char x-coding-header)
+                   (delete-region (point)
+                                  (save-excursion
+                                    (beginning-of-line)
+                                    (point)))
+                   (insert "X-Coding-System: "
+                           (symbol-name last-coding-system-used))
+                   (set-marker x-coding-header nil)
+                   (rmail-show-message))
+               (error "No X-Coding-System header found")))
+         (or pruned
+             (rmail-toggle-header 0)))))))
+
 ;; Find all occurrences of certain fields, and highlight them.
 (defun rmail-highlight-headers ()
   ;; Do this only if the system supports faces.
@@ -2340,6 +2580,7 @@ Called when a new message is displayed."
   "Show following message whether deleted or not.
 With prefix arg N, moves forward N messages, or backward if N is negative."
   (interactive "p")
+  (set-buffer rmail-buffer)
   (rmail-maybe-set-message-counters)
   (rmail-show-message (+ rmail-current-message n)))
 
@@ -2356,6 +2597,7 @@ or backward if N is negative.
 
 Returns t if a new message is being shown, nil otherwise."
   (interactive "p")
+  (set-buffer rmail-buffer)
   (rmail-maybe-set-message-counters)
   (let ((lastwin rmail-current-message)
        (current rmail-current-message))
@@ -2417,22 +2659,39 @@ or forward if N is negative."
        (if (not primary-only)
            (string-match recipients (or (mail-fetch-field "Cc") ""))))))
 
-(defun rmail-message-regexp-p (msg regexp)
-  "Return t, if for message number MSG, regexp REGEXP matches in the header."
-  (save-excursion
-    (goto-char (rmail-msgbeg msg))
-    (let (beg end)
-      (save-excursion
-       (forward-line 2)
-       (setq beg (point)))
-      (save-excursion 
-       (search-forward "\n*** EOOH ***\n" (point-max))
-       (when (= beg (match-beginning 0))
+(defun rmail-message-regexp-p (n regexp)
+  "Return t, if for message number N, regexp REGEXP matches in the header."
+  (let ((beg (rmail-msgbeg n))
+       (end (rmail-msgend n)))
+    (goto-char beg)
+    (forward-line 1)
+    (save-excursion
+      (save-restriction
+       (if (prog1 (= (following-char) ?0)
+             (forward-line 2)
+             ;; If there's a Summary-line in the (otherwise empty)
+             ;; header, we didn't yet get past the EOOH line.
+             (when (looking-at "^\\*\\*\\* EOOH \\*\\*\\*\n")
+               (forward-line 1))
+             (setq beg (point))
+             (narrow-to-region (point) end))
+           (progn 
+             (rfc822-goto-eoh)
+             (setq end (point)))
          (setq beg (point))
-         (search-forward "\n\n" (point-max)))
-       (setq end (point)))
-      (goto-char beg)
-      (re-search-forward regexp end t))))
+         (search-forward "\n*** EOOH ***\n" end t)
+         (setq end (1+ (match-beginning 0)))))
+       (goto-char beg)
+       (if rmail-enable-mime
+           (funcall rmail-search-mime-header-function n regexp end)
+         (re-search-forward regexp end t)))))
+
+(defun rmail-search-message (msg regexp)
+  "Return non-nil, if for message number MSG, regexp REGEXP matches."
+  (goto-char (rmail-msgbeg msg))
+  (if rmail-enable-mime
+      (funcall rmail-search-mime-message-function msg regexp)
+    (re-search-forward regexp (rmail-msgend msg) t)))
 
 (defvar rmail-search-last-regexp nil)
 (defun rmail-search (regexp &optional n)
@@ -2461,6 +2720,7 @@ Interactively, empty argument means use same regexp used last time."
   (message "%sRmail search for %s..."
           (if (< n 0) "Reverse " "")
           regexp)
+  (set-buffer rmail-buffer)
   (rmail-maybe-set-message-counters)
   (let ((omin (point-min))
        (omax (point-max))
@@ -2476,28 +2736,30 @@ Interactively, empty argument means use same regexp used last time."
            ;; but searching forward through each message.
            (if reversep
                (while (and (null win) (> msg 1))
-                 (goto-char (rmail-msgbeg (setq msg (1- msg))))
-                 (setq win (re-search-forward
-                            regexp (rmail-msgend msg) t)))
+                 (setq msg (1- msg)
+                       win (rmail-search-message msg regexp)))
              (while (and (null win) (< msg rmail-total-messages))
-               (goto-char (rmail-msgbeg (setq msg (1+ msg))))
-               (setq win (re-search-forward regexp (rmail-msgend msg) t))))
+               (setq msg (1+ msg)
+                     win (rmail-search-message msg regexp))))
            (setq n (+ n (if reversep 1 -1)))))
       (if win
          (progn
-           ;; If this is a reverse search and we found a message,
-           ;; search backward thru this message to position point.
+           (rmail-show-message msg)
+           ;; Search forward (if this is a normal search) or backward
+           ;; (if this is a reverse search) through this message to
+           ;; position point.  This search may fail because REGEXP
+           ;; was found in the hidden portion of this message.  In
+           ;; that case, move point to the beginning of visible
+           ;; portion.
            (if reversep
                (progn
-                 (goto-char (rmail-msgend msg))
-                 (re-search-backward
-                  regexp (rmail-msgbeg msg) t)))
-            (setq win (point-marker))
-           (rmail-show-message msg)
+                 (goto-char (point-max))
+                 (re-search-backward regexp nil 'move))
+             (goto-char (point-min))
+             (re-search-forward regexp nil t))
            (message "%sRmail search for %s...done"
                     (if reversep "Reverse " "")
-                    regexp)
-           (goto-char win))
+                    regexp))
        (goto-char opoint)
        (narrow-to-region omin omax)
        (ding)
@@ -2613,6 +2875,7 @@ If N is negative, go forwards instead."
 (defun rmail-undelete-previous-message ()
   "Back up to deleted message, select it, and undelete it."
   (interactive)
+  (set-buffer rmail-buffer)
   (let ((msg rmail-current-message))
     (while (and (> msg 0)
                (not (rmail-message-deleted-p msg)))
@@ -2665,9 +2928,20 @@ Deleted messages stay in the file until the \\[rmail-expunge] command is given."
        (setq i (1+ i)))
       newnum)))
 
+(defun rmail-expunge-confirmed ()
+  "Return t if deleted message should be expunged. If necessary, ask the user.
+See also user-option `rmail-confirm-expunge'."
+  (set-buffer rmail-buffer)
+  (or (not (stringp rmail-deleted-vector))
+      (not (string-match "D" rmail-deleted-vector))
+      (null rmail-confirm-expunge)
+      (funcall rmail-confirm-expunge
+              "Erase deleted messages from Rmail file? ")))
+
 (defun rmail-only-expunge ()
   "Actually erase all deleted messages in the file."
   (interactive)
+  (set-buffer rmail-buffer)
   (message "Expunging deleted messages...")
   ;; Discard all undo records for this buffer.
   (or (eq buffer-undo-list t)
@@ -2678,7 +2952,10 @@ Deleted messages stay in the file until the \\[rmail-expunge] command is given."
         (opoint (if (and (> rmail-current-message 0)
                          (rmail-message-deleted-p rmail-current-message))
                     0
-                  (- (point) (point-min))))
+                  (if rmail-enable-mime
+                      (with-current-buffer rmail-view-buffer
+                        (- (point)(point-min)))
+                    (- (point) (point-min)))))
         (messages-head (cons (aref rmail-message-vector 0) nil))
         (messages-tail messages-head)
         ;; Don't make any undo records for the expunging.
@@ -2742,20 +3019,17 @@ Deleted messages stay in the file until the \\[rmail-expunge] command is given."
          (narrow-to-region (- (buffer-size) omin) (- (buffer-size) omax)))
       (rmail-show-message
        (if (zerop rmail-current-message) 1 nil))
-      (goto-char (+ (point) opoint)))))
+      (if rmail-enable-mime
+         (goto-char (+ (point-min) opoint))
+       (goto-char (+ (point) opoint))))))
 
 (defun rmail-expunge ()
   "Erase deleted messages from Rmail file and summary buffer."
   (interactive)
-  (when (and (stringp rmail-deleted-vector)
-            (string-match "D" rmail-deleted-vector)
-            (or (null rmail-confirm-expunge)
-                (funcall rmail-confirm-expunge
-                         "Erase deleted messages from Rmail file? ")))
+  (when (rmail-expunge-confirmed)
     (rmail-only-expunge)
     (if (rmail-summary-exists)
-       (rmail-select-summary
-        (rmail-update-summary)))))
+       (rmail-select-summary (rmail-update-summary)))))
 \f
 ;;;; *** Rmail Mailing Commands ***
 
@@ -2806,19 +3080,25 @@ use \\[mail-yank-original] to yank the original message into it."
             (msgnum rmail-current-message))
     (save-excursion
       (save-restriction
-       (widen)
-       (goto-char (rmail-msgbeg rmail-current-message))
-       (forward-line 1)
-       (if (= (following-char) ?0)
+       (if rmail-enable-mime
            (narrow-to-region
-            (progn (forward-line 2)
-                   (point))
-            (progn (search-forward "\n\n" (rmail-msgend rmail-current-message)
-                                   'move)
-                   (point)))
-         (narrow-to-region (point)
-                           (progn (search-forward "\n*** EOOH ***\n")
-                                  (beginning-of-line) (point))))
+            (goto-char (point-min))
+            (if (search-forward "\n\n" nil 'move)
+                (1+ (match-beginning 0))
+              (point)))
+         (widen)
+         (goto-char (rmail-msgbeg rmail-current-message))
+         (forward-line 1)
+         (if (= (following-char) ?0)
+             (narrow-to-region
+              (progn (forward-line 2)
+                     (point))
+              (progn (search-forward "\n\n" (rmail-msgend rmail-current-message)
+                                     'move)
+                     (point)))
+           (narrow-to-region (point)
+                             (progn (search-forward "\n*** EOOH ***\n")
+                                    (beginning-of-line) (point)))))
        (setq from (mail-fetch-field "from")
              reply-to (or (mail-fetch-field "reply-to" nil t)
                           from)
@@ -2873,8 +3153,9 @@ use \\[mail-yank-original] to yank the original message into it."
         (if (string= cc-list "") nil cc-list)))
      rmail-view-buffer
      (list (list 'rmail-mark-message
-                rmail-view-buffer
-                (aref rmail-msgref-vector msgnum)
+                rmail-buffer
+                (with-current-buffer rmail-buffer
+                  (aref rmail-msgref-vector msgnum))
                 "answered"))
      nil
      (list (cons "References" (concat (mapconcat 'identity references " ")
@@ -2956,7 +3237,7 @@ see the documentation of `rmail-resend'."
   (interactive "P")
   (if resend
       (call-interactively 'rmail-resend)
-    (let ((forward-buffer (current-buffer))
+    (let ((forward-buffer rmail-buffer)
          (msgnum rmail-current-message)
          (subject (concat "["
                           (let ((from (or (mail-fetch-field "From")
@@ -2970,7 +3251,8 @@ see the documentation of `rmail-resend'."
           nil nil subject nil nil nil
           (list (list 'rmail-mark-message
                       forward-buffer
-                      (aref rmail-msgref-vector msgnum)
+                      (with-current-buffer rmail-buffer
+                        (aref rmail-msgref-vector msgnum))
                       "forwarded"))
           ;; If only one window, use it for the mail buffer.
           ;; Otherwise, use another window for the mail buffer
@@ -2981,24 +3263,27 @@ see the documentation of `rmail-resend'."
          (save-excursion
            ;; Insert after header separator--before signature if any.
            (goto-char (mail-text-start))
-           (insert "------- Start of forwarded message -------\n")
-           ;; Quote lines with `- ' if they start with `-'.
-           (let ((beg (point)) end)
-             (setq end (point-marker))
-             (set-marker-insertion-type end t)
-             (insert-buffer-substring forward-buffer)
-             (goto-char beg)
-             (while (re-search-forward "^-" end t)
-               (beginning-of-line)
-               (insert "- ")
-               (forward-line 1))
-             (goto-char end)
-             (skip-chars-backward "\n")
-             (if (< (point) end)
-                 (forward-char 1))
-             (delete-region (point) end)
-             (set-marker end nil))
-           (insert "------- End of forwarded message -------\n")
+           (if (or rmail-enable-mime rmail-enable-mime-composing)
+               (funcall rmail-insert-mime-forwarded-message-function
+                        forward-buffer)
+             (insert "------- Start of forwarded message -------\n")
+             ;; Quote lines with `- ' if they start with `-'.
+             (let ((beg (point)) end)
+               (setq end (point-marker))
+               (set-marker-insertion-type end t)
+               (insert-buffer-substring forward-buffer)
+               (goto-char beg)
+               (while (re-search-forward "^-" end t)
+                 (beginning-of-line)
+                 (insert "- ")
+                 (forward-line 1))
+               (goto-char end)
+               (skip-chars-backward "\n")
+               (if (< (point) end)
+                   (forward-char 1))
+               (delete-region (point) end)
+               (set-marker end nil))
+             (insert "------- End of forwarded message -------\n"))
            (push-mark))))))
 \f
 (defun rmail-resend (address &optional from comment mail-alias-file)
@@ -3014,7 +3299,8 @@ typically for purposes of moderating a list."
   (interactive "sResend to: ")
   (require 'sendmail)
   (require 'mailalias)
-  (unless (eq rmail-buffer (current-buffer))
+  (unless (or (eq rmail-view-buffer (current-buffer))
+             (eq rmail-buffer (current-buffer)))
     (error "Not an Rmail buffer"))
   (if (not from) (setq from user-mail-address))
   (let ((tembuf (generate-new-buffer " sendmail temp"))
@@ -3023,7 +3309,9 @@ typically for purposes of moderating a list."
     (unwind-protect
        (with-current-buffer tembuf
          ;;>> Copy message into temp buffer
-         (insert-buffer-substring mailbuf)
+         (if rmail-enable-mime
+             (funcall rmail-insert-mime-resent-message-function mailbuf)
+           (insert-buffer-substring mailbuf))
          (goto-char (point-min))
          ;; Delete any Sender field, since that's not specifiable.
          ; Only delete Sender fields in the actual header.
@@ -3060,6 +3348,8 @@ typically for purposes of moderating a list."
                    (if (and (not (vectorp mail-abbrevs))
                             (file-exists-p mail-personal-alias-file))
                        (build-mail-abbrevs))
+                   (unless mail-abbrev-syntax-table
+                     (mail-abbrev-make-syntax-table))
                    (set-syntax-table mail-abbrev-syntax-table)
                    (goto-char before)
                    (while (and (< (point) end)
@@ -3118,39 +3408,35 @@ specifying headers which should not be copied into the new message."
   (require 'mail-utils)
   (let ((rmail-this-buffer (current-buffer))
        (msgnum rmail-current-message)
-       (pruned (rmail-msg-is-pruned))
-       bounce-start bounce-end bounce-indent resending)
+       bounce-start bounce-end bounce-indent resending
+       ;; Fetch any content-type header in current message
+       ;; Must search thru the whole unpruned header.
+       (content-type
+        (save-excursion
+          (save-restriction
+            (rmail-narrow-to-non-pruned-header)
+            (mail-fetch-field "Content-Type") ))))
     (save-excursion
-      ;; Narrow down to just the quoted original message
-      (rmail-beginning-of-message)
-      (if pruned
-         (rmail-toggle-header 0))
-      (let* ((case-fold-search t)
-            (top (point))
-            (content-type
-             (save-restriction
-               ;; Fetch any content-type header in current message
-               (search-forward "\n\n") (narrow-to-region top (point))
-               (mail-fetch-field "Content-Type") )) )
-       ;; Handle MIME multipart bounce messages
+      (goto-char (point-min))
+      (let ((case-fold-search t))
        (if (and content-type 
                 (string-match 
                  ";[\n\t ]*boundary=\"?\\([-0-9a-z'()+_,./:=? ]+\\)\"?" 
                  content-type))
+           ;; Handle a MIME multipart bounce message.
            (let ((codestring
                   (concat "\n--"
                           (substring content-type (match-beginning 1) 
-                                                  (match-end 1)))))
-             (or (re-search-forward mail-mime-unsent-header nil t)
-                 (error "Cannot find beginning of header in failed message"))
-             (or (search-forward "\n\n" nil t)
-                 (error "Cannot find start of Mime data in failed message"))
+                                     (match-end 1)))))
+             (unless (re-search-forward mail-mime-unsent-header nil t)
+               (error "Cannot find beginning of header in failed message"))
+             (unless (search-forward "\n\n" nil t)
+               (error "Cannot find start of Mime data in failed message"))
              (setq bounce-start (point))
              (if (search-forward codestring nil t)
                  (setq bounce-end (match-beginning 0))
-               (setq bounce-end (point-max)))
-             )
-         ;; non-MIME bounce
+               (setq bounce-end (point-max))))
+         ;; Non-MIME bounce.
          (or (re-search-forward mail-unsent-separator nil t)
              (error "Cannot parse this as a failure message"))
          (skip-chars-forward "\n")
@@ -3165,11 +3451,12 @@ specifying headers which should not be copied into the new message."
                (goto-char (point-max))
                (re-search-backward "^End of returned message$" nil t)
                (setq bounce-end (point)))
-           ;; One message contained a few random lines before the old
-           ;; message header.  The first line of the message started with
-           ;; two hyphens.  A blank line followed these random lines.
-           ;; The same line beginning with two hyphens was possibly
-           ;; marking the end of the message.
+           ;; One message contained a few random lines before
+           ;; the old message header.  The first line of the
+           ;; message started with two hyphens.  A blank line
+           ;; followed these random lines.  The same line
+           ;; beginning with two hyphens was possibly marking
+           ;; the end of the message.
            (if (looking-at "^--")
                (let ((boundary (buffer-substring-no-properties
                                 (point)
@@ -3182,10 +3469,10 @@ specifying headers which should not be copied into the new message."
                  (setq bounce-end (point)))
              (setq bounce-start (point)
                    bounce-end (point-max)))
-           (or (search-forward "\n\n" nil t)
-               (error "Cannot find end of header in failed message"))
-           ))))
-    ;; Start sending a new message; default header fields from the original.
+           (unless (search-forward "\n\n" nil t)
+             (error "Cannot find end of header in failed message"))))))
+    ;; We have found the message that bounced, within the current message.
+    ;; Now start sending new message; default header fields from original.
     ;; Turn off the usual actions for initializing the message body
     ;; because we want to get only the text from the failure message.
     (let (mail-signature mail-setup-hook)
@@ -3201,12 +3488,13 @@ specifying headers which should not be copied into the new message."
                rmail-displayed-headers
                rmail-ignored-headers)
            (erase-buffer)
-           (insert-buffer-substring rmail-this-buffer bounce-start bounce-end)
+           (insert-buffer-substring rmail-this-buffer
+                                    bounce-start bounce-end)
            (goto-char (point-min))
            (if bounce-indent
                (indent-rigidly (point-min) (point-max) bounce-indent))
            (rmail-clear-headers rmail-retry-ignored-headers)
-           (rmail-clear-headers "^sender:\\|^from:\\|^return-path:")
+           (rmail-clear-headers "^sender:\\|^return-path:\\|^received:")
            (mail-sendmail-delimit-header)
            (save-restriction
              (narrow-to-region (point-min) (mail-header-end))
@@ -3216,11 +3504,7 @@ specifying headers which should not be copied into the new message."
                      (insert "Resent-Bcc: " (user-login-name) "\n")
                    (insert "BCC: " (user-login-name) "\n"))))
            (goto-char (point-min))
-           (mail-position-on-field (if resending "Resent-To" "To") t)
-           (set-buffer rmail-this-buffer)
-           (rmail-beginning-of-message))))
-    (if pruned
-       (rmail-toggle-header))))
+           (mail-position-on-field (if resending "Resent-To" "To") t))))))
 \f
 (defun rmail-summary-exists ()
   "Non-nil iff in an RMAIL buffer and an associated summary buffer exists.
@@ -3273,7 +3557,6 @@ This has an effect only if a summary buffer exists."
 
 (defun rmail-fontify-buffer-function ()
   ;; This function's symbol is bound to font-lock-fontify-buffer-function.
-  (make-local-hook 'rmail-show-message-hook)
   (add-hook 'rmail-show-message-hook 'rmail-fontify-message nil t)
   ;; If we're already showing a message, fontify it now.
   (if rmail-current-message (rmail-fontify-message))