Merge from gnus--rel--5.10
[bpt/emacs.git] / lisp / gnus / message.el
index 55f0718..baf9861 100644 (file)
@@ -1,7 +1,7 @@
 ;;; message.el --- composing mail and news messages
 
 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
-;;   2005 Free Software Foundation, Inc.
+;;   2005, 2006, 2007 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; Keywords: mail, news
@@ -37,6 +37,7 @@
   (defvar gnus-list-identifiers)) ; gnus-sum is required where necessary
 (require 'canlock)
 (require 'mailheader)
+(require 'gmm-utils)
 (require 'nnheader)
 ;; This is apparently necessary even though things are autoloaded.
 ;; Because we dynamically bind mail-abbrev-mode-regexp, we'd better
@@ -189,14 +190,13 @@ To disable checking of long signatures, for instance, add
 
 Don't touch this variable unless you really know what you're doing.
 
-Checks include `subject-cmsg', `multiple-headers', `sendsys',
-`message-id', `from', `long-lines', `control-chars', `size',
-`new-text', `quoting-style', `redirected-followup', `signature',
-`approved', `sender', `empty', `empty-headers', `message-id', `from',
-`subject', `shorten-followup-to', `existing-newsgroups',
-`buffer-file-name', `unchanged', `newsgroups', `reply-to',
-`continuation-headers', `long-header-lines', `invisible-text' and
-`illegible-text'."
+Checks include `approved', `continuation-headers', `control-chars',
+`empty', `existing-newsgroups', `from', `illegible-text',
+`invisible-text', `long-header-lines', `long-lines', `message-id',
+`multiple-headers', `new-text', `newsgroups', `quoting-style',
+`repeated-newsgroups', `reply-to', `sender', `sendsys', `shoot',
+`shorten-followup-to', `signature', `size', `subject', `subject-cmsg'
+and `valid-newsgroups'."
   :group 'message-news
   :type '(repeat sexp))                        ; Fixme: improve this
 
@@ -226,7 +226,7 @@ Also see `message-required-news-headers' and
   "*Headers to be generated or prompted for when posting an article.
 RFC977 and RFC1036 require From, Date, Newsgroups, Subject,
 Message-ID.  Organization, Lines, In-Reply-To, Expires, and
-User-Agent are optional.  If don't you want message to insert some
+User-Agent are optional.  If you don't want message to insert some
 header, remove it from this list."
   :group 'message-news
   :group 'message-headers
@@ -433,16 +433,36 @@ nil means let mailer mail back a message to report errors."
   :type 'boolean)
 
 (defcustom message-generate-new-buffers 'unique
-  "*Non-nil means create a new message buffer whenever `message-setup' is called.
-If this is a function, call that function with three parameters:  The type,
-the to address and the group name.  (Any of these may be nil.)  The function
-should return the new buffer name."
+  "*Say whether to create a new message buffer to compose a message.
+Valid values include:
+
+nil
+  Generate the buffer name in the Message way (e.g., *mail*, *news*,
+  *mail to whom*, *news on group*, etc.) and continue editing in the
+  existing buffer of that name.  If there is no such buffer, it will
+  be newly created.
+
+`unique' or t
+  Create the new buffer with the name generated in the Message way.
+
+`unsent'
+  Similar to `unique' but the buffer name begins with \"*unsent \".
+
+`standard'
+  Similar to nil but the buffer name is simpler like *mail message*.
+
+function
+  If this is a function, call that function with three parameters:
+  The type, the To address and the group name (any of these may be nil).
+  The function should return the new buffer name."
   :group 'message-buffers
   :link '(custom-manual "(message)Message Buffers")
-  :type '(choice (const :tag "off" nil)
-                (const :tag "unique" unique)
-                (const :tag "unsent" unsent)
-                (function fun)))
+  :type '(choice (const nil)
+                (sexp :tag "unique" :format "unique\n" :value unique
+                      :match (lambda (widget value) (memq value '(unique t))))
+                (const unsent)
+                (const standard)
+                (function :format "\n    %{%t%}: %v")))
 
 (defcustom message-kill-buffer-on-exit nil
   "*Non-nil means that the message buffer will be killed after sending a message."
@@ -1388,8 +1408,13 @@ should be sent in several parts.  If it is nil, the size is unlimited."
                 (integer 1000000)))
 
 (defcustom message-alternative-emails nil
-  "A regexp to match the alternative email addresses.
-The first matched address (not primary one) is used in the From field."
+  "*Regexp matching alternative email addresses.
+The first address in the To, Cc or From headers of the original
+article matching this variable is used as the From field of
+outgoing messages.
+
+This variable has precedence over posting styles and anything that runs
+off `message-setup-hook'."
   :group 'message-headers
   :link '(custom-manual "(message)Message Headers")
   :type '(choice (const :tag "Always use primary" nil)
@@ -1454,8 +1479,13 @@ no, only reply back to the author."
                                   (file-error))
                                 (mm-coding-system-p 'utf-8)
                                 (executable-find idna-program)
-                                'ask)
-  "Whether to encode non-ASCII in domain names into ASCII according to IDNA."
+                                (string= (idna-to-ascii "räksmörgås")
+                                         "xn--rksmrgs-5wao1o")
+                                t)
+  "Whether to encode non-ASCII in domain names into ASCII according to IDNA.
+GNU Libidn, and in particular the elisp package \"idna.el\" and
+the external program \"idn\", must be installed for this
+functionality to work."
   :version "22.1"
   :group 'message-headers
   :link '(custom-manual "(message)IDNA")
@@ -1569,17 +1599,22 @@ no, only reply back to the author."
 (defvar message-send-mail-real-function nil
   "Internal send mail function.")
 
-(defvar message-bogus-system-names "^localhost\\."
+(defvar message-bogus-system-names "^localhost\\.\\|\\.local$"
   "The regexp of bogus system names.")
 
 (defcustom message-valid-fqdn-regexp
   (concat "[a-z0-9][-.a-z0-9]+\\." ;; [hostname.subdomain.]domain.
          ;; valid TLDs:
-         "\\([a-z][a-z]" ;; two letter country TDLs
-         "\\|biz\\|com\\|edu\\|gov\\|int\\|mil\\|net\\|org"
-         "\\|aero\\|coop\\|info\\|name\\|museum"
-         "\\|arpa\\|pro\\|uucp\\|bitnet\\|bofh" ;; old style?
-         "\\)")
+         "\\([a-z][a-z]\\|" ;; two letter country TDLs
+         "aero\\|arpa\\|bitnet\\|biz\\|bofh\\|"
+         "cat\\|com\\|coop\\|edu\\|gov\\|"
+         "info\\|int\\|jobs\\|"
+         "mil\\|mobi\\|museum\\|name\\|net\\|"
+         "org\\|pro\\|travel\\|uucp\\)")
+  ;; http://en.wikipedia.org/wiki/List_of_Internet_top-level_domains
+  ;; http://en.wikipedia.org/wiki/GTLD
+  ;; `in the process of being approved': .asia .post .tel .sex
+  ;; "dead" nato bitnet uucp
   "Regular expression that matches a valid FQDN."
   ;; see also: gnus-button-valid-fqdn-regexp
   :version "22.1"
@@ -1607,7 +1642,8 @@ no, only reply back to the author."
   (autoload 'rmail-output "rmailout")
   (autoload 'gnus-delay-article "gnus-delay")
   (autoload 'gnus-make-local-hook "gnus-util")
-  (autoload 'gnus-extract-address-components "gnus-util"))
+  (autoload 'gnus-extract-address-components "gnus-util")
+  (autoload 'gnus-select-frame-set-input-focus "gnus-util"))
 
 \f
 
@@ -1771,6 +1807,96 @@ see `message-narrow-to-headers-or-head'."
       (substring subject (match-end 0))
     subject))
 
+(defcustom message-replacement-char "."
+  "Replacement character used instead of unprintable or not decodable chars."
+  :group 'message-various
+  :version "22.1" ;; Gnus 5.10.9
+  :type '(choice string
+                (const ".")
+                (const "?")))
+
+;; FIXME: We also should call `message-strip-subject-encoded-words'
+;; when forwarding.  Probably in `message-make-forward-subject' and
+;; `message-forward-make-body'.
+
+(defun message-strip-subject-encoded-words (subject)
+  "Fix non-decodable words in SUBJECT."
+  ;; Cf. `gnus-simplify-subject-fully'.
+  (let* ((case-fold-search t)
+        (replacement-chars (format "[%s%s%s]"
+                                   message-replacement-char
+                                   message-replacement-char
+                                   message-replacement-char))
+        (enc-word-re "=\\?\\([^?]+\\)\\?\\([QB]\\)\\?\\([^?]+\\)\\(\\?=\\)")
+        cs-string
+        (have-marker
+         (with-temp-buffer
+           (insert subject)
+           (goto-char (point-min))
+           (when (re-search-forward enc-word-re nil t)
+             (setq cs-string (match-string 1)))))
+        cs-coding q-or-b word-beg word-end)
+    (if (or (not have-marker) ;; No encoded word found...
+           ;; ... or double encoding was correct:
+           (and (stringp cs-string)
+                (setq cs-string (downcase cs-string))
+                (mm-coding-system-p (intern cs-string))
+                (not (prog1
+                         (y-or-n-p
+                          (format "\
+Decoded Subject \"%s\"
+contains a valid encoded word.  Decode again? "
+                                  subject))
+                       (setq cs-coding (intern cs-string))))))
+       subject
+      (with-temp-buffer
+       (insert subject)
+       (goto-char (point-min))
+       (while (re-search-forward enc-word-re nil t)
+         (setq cs-string (downcase (match-string 1))
+               q-or-b    (match-string 2)
+               word-beg (match-beginning 0)
+               word-end (match-end 0))
+         (setq cs-coding
+               (if (mm-coding-system-p (intern cs-string))
+                   (setq cs-coding (intern cs-string))
+                 nil))
+         ;; No double encoded subject? => bogus charset.
+         (unless cs-coding
+           (setq cs-coding
+                 (mm-read-coding-system
+                  (format "\
+Decoded Subject \"%s\"
+contains an encoded word.  The charset `%s' is unknown or invalid.
+Hit RET to replace non-decodable characters with \"%s\" or enter replacement
+charset: "
+                          subject cs-string message-replacement-char)))
+           (if cs-coding
+               (replace-match (concat "=?" (symbol-name cs-coding)
+                                      "?\\2?\\3\\4\\5"))
+             (save-excursion
+               (goto-char word-beg)
+               (re-search-forward "=\\?\\([^?]+\\)\\?\\([QB]\\)\\?" word-end t)
+               (replace-match "")
+               ;; QP or base64
+               (if (string-match "\\`Q\\'" q-or-b)
+                   ;; QP
+                   (progn
+                     (message "Replacing non-decodable characters with \"%s\"."
+                              message-replacement-char)
+                     (while (re-search-forward "\\(=[A-F0-9][A-F0-9]\\)+"
+                                               word-end t)
+                       (replace-match message-replacement-char)))
+                 ;; base64
+                 (message "Replacing non-decodable characters with \"%s\"."
+                          replacement-chars)
+                 (re-search-forward "[^?]+" word-end t)
+                 (replace-match replacement-chars))
+               (re-search-forward "\\?=")
+               (replace-match "")))))
+       (rfc2047-decode-region (point-min) (point-max))
+       (buffer-string)))))
+
 ;;; Start of functions adopted from `message-utils.el'.
 
 (defun message-strip-subject-trailing-was (subject)
@@ -1807,7 +1933,6 @@ Leading \"Re: \" is not stripped by this function.  Use the function
 
 ;;; Suggested by Jonas Steverud  @  www.dtek.chalmers.se/~d4jonas/
 
-;;;###autoload
 (defun message-change-subject (new-subject)
   "Ask for NEW-SUBJECT header, append (was: <Old Subject>)."
   ;; <URL:http://www.landfield.com/usefor/drafts/draft-ietf-usefor-useage--1.02.unpaged>
@@ -1839,7 +1964,6 @@ Leading \"Re: \" is not stripped by this function.  Use the function
                                    " (was: "
                                    old-subject ")\n")))))))))
 
-;;;###autoload
 (defun message-mark-inserted-region (beg end)
   "Mark some region in the current article with enclosing tags.
 See `message-mark-insert-begin' and `message-mark-insert-end'."
@@ -1851,7 +1975,6 @@ See `message-mark-insert-begin' and `message-mark-insert-end'."
     (goto-char beg)
     (insert message-mark-insert-begin)))
 
-;;;###autoload
 (defun message-mark-insert-file (file)
   "Insert FILE at point, marking it with enclosing tags.
 See `message-mark-insert-begin' and `message-mark-insert-end'."
@@ -1864,7 +1987,6 @@ See `message-mark-insert-begin' and `message-mark-insert-end'."
     (goto-char p)
     (insert message-mark-insert-begin)))
 
-;;;###autoload
 (defun message-add-archive-header ()
   "Insert \"X-No-Archive: Yes\" in the header and a note in the body.
 The note can be customized using `message-archive-note'.  When called with a
@@ -1884,7 +2006,6 @@ body, set  `message-archive-note' to nil."
       (message-add-header message-archive-header)
       (message-sort-headers)))
 
-;;;###autoload
 (defun message-cross-post-followup-to-header (target-group)
   "Mangles FollowUp-To and Newsgroups header to point to TARGET-GROUP.
 With prefix-argument just set Follow-Up, don't cross-post."
@@ -1928,7 +2049,6 @@ With prefix-argument just set Follow-Up, don't cross-post."
       (insert (concat "\nFollowup-To: " target-group)))
   (setq message-cross-post-old-target target-group))
 
-;;;###autoload
 (defun message-cross-post-insert-note (target-group cross-post in-old
                                                    old-groups)
   "Insert a in message body note about a set Followup or Crosspost.
@@ -1961,7 +2081,6 @@ been made to before the user asked for a Crosspost."
        (insert (concat message-followup-to-note target-group "\n"))
       (insert (concat message-cross-post-note target-group "\n")))))
 
-;;;###autoload
 (defun message-cross-post-followup-to (target-group)
   "Crossposts message and set Followup-To to TARGET-GROUP.
 With prefix-argument just set Follow-Up, don't cross-post."
@@ -2003,7 +2122,6 @@ With prefix-argument just set Follow-Up, don't cross-post."
 
 ;;; Reduce To: to Cc: or Bcc: header
 
-;;;###autoload
 (defun message-reduce-to-to-cc ()
  "Replace contents of To: header with contents of Cc: or Bcc: header."
  (interactive)
@@ -2188,6 +2306,15 @@ Point is left at the beginning of the narrowed-to region."
             (1+ max)))))
       (message-sort-headers-1))))
 
+(defun message-info (&optional arg)
+  "Display the Message manual.
+
+Prefixed with one \\[universal-argument], display the Emacs MIME manual.
+Prefixed with two \\[universal-argument]'s, display the PGG manual."
+  (interactive "p")
+  (cond ((eq arg 16) (Info-goto-node "(pgg)Top"))
+       ((eq arg  4) (Info-goto-node "(emacs-mime)Top"))
+       (t           (Info-goto-node "(message)Top"))))
 
 \f
 
@@ -2315,7 +2442,11 @@ Point is left at the beginning of the narrowed-to region."
         '(:help "Ask, then arrange to send message at that time"))]
     ["Kill Message" message-kill-buffer
      ,@(if (featurep 'xemacs) '(t)
-        '(:help "Delete this message without sending"))]))
+        '(:help "Delete this message without sending"))]
+    "----"
+    ["Message manual" message-info
+     ,@(if (featurep 'xemacs) '(t)
+        '(:help "Display the Message manual"))]))
 
 (easy-menu-define
   message-mode-field-menu message-mode-map ""
@@ -2514,7 +2645,7 @@ M-RET    `message-newline-and-reformat' (break the line and reformat)."
     (set (make-local-variable 'font-lock-defaults)
         '(message-font-lock-keywords t))
     (if (boundp 'tool-bar-map)
-       (set (make-local-variable 'tool-bar-map) (message-tool-bar-map))))
+       (set (make-local-variable 'tool-bar-map) (message-make-tool-bar))))
   (easy-menu-add message-mode-menu message-mode-map)
   (easy-menu-add message-mode-field-menu message-mode-map)
   (gnus-make-local-hook 'after-change-functions)
@@ -2663,6 +2794,11 @@ M-RET    `message-newline-and-reformat' (break the line and reformat)."
   (or (search-forward (concat "\n" mail-header-separator "\n") nil t)
       (search-forward-regexp "[^:]+:\\([^\n]\\|\n[ \t]\\)+\n\n" nil t)))
 
+(defun message-in-body-p ()
+  "Return t if point is in the message body."
+  (let ((body (save-excursion (message-goto-body) (point))))
+    (>= (point) body)))
+
 (defun message-goto-eoh ()
   "Move point to the end of the headers."
   (interactive)
@@ -2734,7 +2870,8 @@ prefix FORCE is given."
     (message-carefully-insert-headers headers)))
 
 (defcustom message-header-synonyms
-  '((To Cc Bcc))
+  '((To Cc Bcc)
+    (Original-To))
   "List of lists of header synonyms.
 E.g., if this list contains a member list with elements `Cc' and `To',
 then `message-carefully-insert-headers' will not insert a `To' header
@@ -3209,12 +3346,16 @@ prefix, and don't delete any headers."
       (delete-windows-on message-reply-buffer t)
       (push-mark (save-excursion
                   (insert-buffer-substring message-reply-buffer)
+                  (unless (bolp)
+                    (insert ?\n))
                   (point)))
       (unless arg
-       (funcall message-cite-function))
-      (message-exchange-point-and-mark)
-      (unless (bolp)
-       (insert ?\n))
+       (funcall message-cite-function)
+       (unless (eq (char-before (mark t)) ?\n)
+         (let ((pt (point)))
+           (goto-char (mark t))
+           (insert-before-markers ?\n)
+           (goto-char pt))))
       (unless modified
        (setq message-checksum (message-checksum))))))
 
@@ -3254,7 +3395,7 @@ prefix, and don't delete any headers."
            (message-narrow-to-head-1)
            (vector 0
                    (or (message-fetch-field "subject") "none")
-                   (message-fetch-field "from")
+                   (or (message-fetch-field "from") "nobody")
                    (message-fetch-field "date")
                    (message-fetch-field "message-id" t)
                    (message-fetch-field "references")
@@ -3303,7 +3444,7 @@ prefix, and don't delete any headers."
              (message-narrow-to-head-1)
              (vector 0
                      (or (message-fetch-field "subject") "none")
-                     (message-fetch-field "from")
+                     (or (message-fetch-field "from") "nobody")
                      (message-fetch-field "date")
                      (message-fetch-field "message-id" t)
                      (message-fetch-field "references")
@@ -3320,7 +3461,9 @@ prefix, and don't delete any headers."
 (defun message-insert-citation-line ()
   "Insert a simple citation line."
   (when message-reply-headers
-    (insert (mail-header-from message-reply-headers) " writes:\n\n")))
+    (insert (mail-header-from message-reply-headers) " writes:")
+    (newline)
+    (newline)))
 
 (defun message-position-on-field (header &rest afters)
   (let ((case-fold-search t))
@@ -3586,8 +3729,10 @@ It should typically alter the sending method in some way or other."
        (setq choice
              (gnus-multiple-choice
               "Non-printable characters found.  Continue sending?"
-              '((?d "Remove non-printable characters and send")
-                (?r "Replace non-printable characters with dots and send")
+              `((?d "Remove non-printable characters and send")
+                (?r ,(format
+                      "Replace non-printable characters with \"%s\" and send"
+                      message-replacement-char))
                 (?i "Ignore non-printable characters and send")
                 (?e "Continue editing"))))
        (if (eq choice ?e)
@@ -3598,8 +3743,8 @@ It should typically alter the sending method in some way or other."
          (when (let ((char (char-after)))
                  (or (< (mm-char-int char) 128)
                      (and (mm-multibyte-p)
-                          ;; Fixme: Wrong for Emacs 22 and for things
-                          ;; like undecable utf-8.  Should at least
+                          ;; FIXME: Wrong for Emacs 23 (unicode) and for
+                          ;; things like undecable utf-8.  Should at least
                           ;; use find-coding-systems-region.
                           (memq (char-charset char)
                                 '(eight-bit-control eight-bit-graphic
@@ -3610,7 +3755,7 @@ It should typically alter the sending method in some way or other."
                (message-kill-all-overlays)
              (delete-char 1)
              (when (eq choice ?r)
-               (insert "."))))
+               (insert message-replacement-char))))
          (forward-char)
          (skip-chars-forward mm-7bit-chars))))))
 
@@ -3740,6 +3885,16 @@ It should typically alter the sending method in some way or other."
       (let ((message-deletable-headers
             (if news nil message-deletable-headers)))
        (message-generate-headers headers))
+      ;; Check continuation headers.
+      (message-check 'continuation-headers
+       (goto-char (point-min))
+       (while (re-search-forward "^[^ \t\n][^ \t\n:]*[ \t\n]" nil t)
+         (goto-char (match-beginning 0))
+         (if (y-or-n-p "Fix continuation lines? ")
+             (insert " ")
+           (forward-line 1)
+           (unless (y-or-n-p "Send anyway? ")
+             (error "Failed to send the message")))))
       ;; Let the user do all of the above.
       (run-hooks 'message-header-hook))
     (unwind-protect
@@ -3859,9 +4014,15 @@ If you always want Gnus to send messages in one piece, set
                       'call-process-region
                       (append
                        (list (point-min) (point-max)
-                             (if (boundp 'sendmail-program)
-                                 sendmail-program
-                               "/usr/lib/sendmail")
+                             (cond ((boundp 'sendmail-program)
+                                    sendmail-program)
+                                   ((file-exists-p "/usr/sbin/sendmail")
+                                    "/usr/sbin/sendmail")
+                                   ((file-exists-p "/usr/lib/sendmail")
+                                    "/usr/lib/sendmail")
+                                   ((file-exists-p "/usr/ucblib/sendmail")
+                                    "/usr/ucblib/sendmail")
+                                   (t "fakemail"))
                              nil errbuf nil "-oi")
                        ;; Always specify who from,
                        ;; since some systems have broken sendmails.
@@ -4297,11 +4458,11 @@ Otherwise, generate and save a value for `canlock-password' first."
    (message-check 'continuation-headers
      (goto-char (point-min))
      (let ((do-posting t))
-       (while (re-search-forward "^[^ \t\n][^:\n]*$" nil t)
+       (while (re-search-forward "^[^ \t\n][^ \t\n:]*[ \t\n]" nil t)
+        (goto-char (match-beginning 0))
         (if (y-or-n-p "Fix continuation lines? ")
-            (progn
-              (goto-char (match-beginning 0))
-              (insert " "))
+            (insert " ")
+          (forward-line 1)
           (unless (y-or-n-p "Send anyway? ")
             (setq do-posting nil))))
        do-posting))
@@ -4654,7 +4815,9 @@ If NOW, use that time instead."
           (* 25 25)))
   (let ((tm (current-time)))
     (concat
-     (if (memq system-type '(ms-dos emx vax-vms))
+     (if (or (memq system-type '(ms-dos emx vax-vms))
+            ;; message-number-base36 doesn't handle bigints.
+            (floatp (user-uid)))
         (let ((user (downcase (user-login-name))))
           (while (string-match "[^a-z0-9_]" user)
             (aset user (match-beginning 0) ?_))
@@ -4726,13 +4889,32 @@ If NOW, use that time instead."
          (msg-id (mail-header-message-id message-reply-headers)))
       (when from
        (let ((name (mail-extract-address-components from)))
-         (concat msg-id (if msg-id " (")
-                 (or (car name)
-                     (nth 1 name))
-                 "'s message of \""
-                 (if (or (not date) (string= date ""))
-                     "(unknown date)" date)
-                 "\"" (if msg-id ")")))))))
+         (concat
+          msg-id (if msg-id " (")
+          (if (car name)
+              (if (string-match "[^\000-\177]" (car name))
+                  ;; Quote a string containing non-ASCII characters.
+                  ;; It will make the RFC2047 encoder cause an error
+                  ;; if there are special characters.
+                  (let ((default-enable-multibyte-characters t))
+                    (with-temp-buffer
+                      (insert (car name))
+                      (goto-char (point-min))
+                      (while (search-forward "\"" nil t)
+                        (when (prog2
+                                  (backward-char)
+                                  (zerop (% (skip-chars-backward "\\\\") 2))
+                                (goto-char (match-beginning 0)))
+                          (insert "\\"))
+                        (forward-char))
+                      ;; Those quotes will be removed by the RFC2047 encoder.
+                      (concat "\"" (buffer-string) "\"")))
+                (car name))
+            (nth 1 name))
+          "'s message of \""
+          (if (or (not date) (string= date ""))
+              "(unknown date)" date)
+          "\"" (if msg-id ")")))))))
 
 (defun message-make-distribution ()
   "Make a Distribution header."
@@ -4862,8 +5044,8 @@ give as trustworthy answer as possible."
           (stringp message-user-fqdn)
           (string-match message-valid-fqdn-regexp message-user-fqdn)
           (not (string-match message-bogus-system-names message-user-fqdn)))
+      ;; `message-user-fqdn' seems to be valid
       message-user-fqdn)
-     ;; `message-user-fqdn' seems to be valid
      ((and (string-match message-valid-fqdn-regexp system-name)
           (not (string-match message-bogus-system-names system-name)))
       ;; `system-name' returned the right result.
@@ -4957,13 +5139,17 @@ subscribed address (and not the additional To and Cc header contents)."
   (let ((field (message-fetch-field header))
        rhs ace  address)
     (when field
-      (dolist (address (mail-header-parse-addresses field))
-       (setq address (car address)
-             rhs (downcase (or (cadr (split-string address "@")) ""))
-             ace (downcase (idna-to-ascii rhs)))
+      (dolist (rhs
+              (mm-delete-duplicates
+               (mapcar (lambda (rhs) (or (cadr (split-string rhs "@")) ""))
+                       (mapcar 'downcase
+                               (mapcar
+                                'car (mail-header-parse-addresses field))))))
+       (setq ace (downcase (idna-to-ascii rhs)))
        (when (and (not (equal rhs ace))
                   (or (not (eq message-use-idna 'ask))
-                      (y-or-n-p (format "Replace %s with %s? " rhs ace))))
+                      (y-or-n-p (format "Replace %s with %s in %s:? "
+                                        rhs ace header))))
          (goto-char (point-min))
          (while (re-search-forward (concat "^" header ":") nil t)
            (message-narrow-to-field)
@@ -4982,6 +5168,9 @@ See `message-idna-encode'."
        (message-narrow-to-head)
        (message-idna-to-ascii-rhs-1 "From")
        (message-idna-to-ascii-rhs-1 "To")
+       (message-idna-to-ascii-rhs-1 "Reply-To")
+       (message-idna-to-ascii-rhs-1 "Mail-Reply-To")
+       (message-idna-to-ascii-rhs-1 "Mail-Followup-To")
        (message-idna-to-ascii-rhs-1 "Cc")))))
 
 (defun message-generate-headers (headers)
@@ -5073,7 +5262,8 @@ Headers already prepared in the buffer are not modified."
                  ;; The element is a symbol.  We insert the value
                  ;; of this symbol, if any.
                  (symbol-value header))
-                ((not (message-check-element header))
+                ((not (message-check-element
+                       (intern (downcase (symbol-name header)))))
                  ;; We couldn't generate a value for this header,
                  ;; so we just ask the user.
                  (read-from-minibuffer
@@ -5336,7 +5526,7 @@ between beginning of field and beginning of line."
   "Return a new (unique) buffer name based on TYPE and TO."
   (cond
    ;; Generate a new buffer name The Message Way.
-   ((eq message-generate-new-buffers 'unique)
+   ((memq message-generate-new-buffers '(unique t))
     (generate-new-buffer-name
      (concat "*" type
             (if to
@@ -5360,20 +5550,51 @@ between beginning of field and beginning of line."
               "")
             (if (and group (not (string= group ""))) (concat " on " group) "")
             "*")))
-   ;; Use standard name.
+   ;; Search for the existing message buffer with the specified name.
    (t
-    (format "*%s message*" type))))
+    (let* ((new (if (eq message-generate-new-buffers 'standard)
+                   (generate-new-buffer-name (concat "*" type " message*"))
+                 (let ((message-generate-new-buffers 'unique))
+                   (message-buffer-name type to group))))
+          (regexp (concat "\\`"
+                          (regexp-quote
+                           (if (string-match "<[0-9]+>\\'" new)
+                               (substring new 0 (match-beginning 0))
+                             new))
+                          "\\(?:<\\([0-9]+\\)>\\)?\\'"))
+          (case-fold-search nil))
+      (or (cdar
+          (last
+           (sort
+            (delq nil
+                  (mapcar
+                   (lambda (b)
+                     (when (and (string-match regexp (setq b (buffer-name b)))
+                                (eq (with-current-buffer b major-mode)
+                                    'message-mode))
+                       (cons (string-to-number (or (match-string 1 b) "1"))
+                             b)))
+                   (buffer-list)))
+            'car-less-than-car)))
+         new)))))
 
 (defun message-pop-to-buffer (name)
   "Pop to buffer NAME, and warn if it already exists and is modified."
   (let ((buffer (get-buffer name)))
     (if (and buffer
             (buffer-name buffer))
-       (progn
-         (set-buffer (pop-to-buffer buffer))
+       (let ((window (get-buffer-window buffer 0)))
+         (if window
+             ;; Raise the frame already displaying the message buffer.
+             (progn
+               (gnus-select-frame-set-input-focus (window-frame window))
+               (select-window window))
+           (set-buffer (pop-to-buffer buffer)))
          (when (and (buffer-modified-p)
-                    (not (y-or-n-p
-                          "Message already being composed; erase? ")))
+                    (not (prog1
+                             (y-or-n-p
+                              "Message already being composed; erase? ")
+                           (message nil))))
            (error "Message being composed")))
       (set-buffer (pop-to-buffer name)))
     (erase-buffer)
@@ -5433,7 +5654,8 @@ between beginning of field and beginning of line."
        nil
       mua)))
 
-(defun message-setup (headers &optional replybuffer actions switch-function)
+(defun message-setup (headers &optional replybuffer actions
+                             continue switch-function)
   (let ((mua (message-mail-user-agent))
        subject to field yank-action)
     (if (not (and message-this-is-mail mua))
@@ -5456,11 +5678,11 @@ between beginning of field and beginning of line."
                                 (format "%s" (car item))
                                 (cdr item)))
                              headers)
-                     nil switch-function yank-action actions)))))
+                     continue switch-function yank-action actions)))))
 
 (defun message-headers-to-generate (headers included-headers excluded-headers)
   "Return a list that includes all headers from HEADERS.
-If INCLUDED-HEADERS is a list, just include those headers.  If if is
+If INCLUDED-HEADERS is a list, just include those headers.  If it is
 t, include all headers.  In any case, headers from EXCLUDED-HEADERS
 are not included."
   (let ((result nil)
@@ -5526,10 +5748,6 @@ are not included."
     (when message-default-mail-headers
       (insert message-default-mail-headers)
       (or (bolp) (insert ?\n)))
-    (save-restriction
-      (message-narrow-to-headers)
-      (if message-alternative-emails
-         (message-use-alternative-email-as-from)))
     (when message-generate-headers-first
       (message-generate-headers
        (message-headers-to-generate
@@ -5545,6 +5763,12 @@ are not included."
   (set-buffer-modified-p nil)
   (setq buffer-undo-list nil)
   (run-hooks 'message-setup-hook)
+  ;; Do this last to give it precedence over posting styles, etc.
+  (when (message-mail-p)
+    (save-restriction
+      (message-narrow-to-headers)
+      (if message-alternative-emails
+         (message-use-alternative-email-as-from))))
   (message-position-point)
   (undo-boundary))
 
@@ -5603,11 +5827,21 @@ are not included."
                               other-headers continue switch-function
                               yank-action send-actions)
   "Start editing a mail message to be sent.
-OTHER-HEADERS is an alist of header/value pairs."
+OTHER-HEADERS is an alist of header/value pairs.  CONTINUE says whether
+to continue editing a message already being composed.  SWITCH-FUNCTION
+is a function used to switch to and display the mail buffer."
   (interactive)
   (let ((message-this-is-mail t) replybuffer)
     (unless (message-mail-user-agent)
-      (message-pop-to-buffer (message-buffer-name "mail" to)))
+      (funcall
+       (or switch-function 'message-pop-to-buffer)
+       ;; Search for the existing message buffer if `continue' is non-nil.
+       (let ((message-generate-new-buffers
+             (when (or (not continue)
+                       (eq message-generate-new-buffers 'standard)
+                       (functionp message-generate-new-buffers))
+               message-generate-new-buffers)))
+        (message-buffer-name "mail" to))))
     ;; FIXME: message-mail should do something if YANK-ACTION is not
     ;; insert-buffer.
     (and (consp yank-action) (eq (car yank-action) 'insert-buffer)
@@ -5616,7 +5850,7 @@ OTHER-HEADERS is an alist of header/value pairs."
      (nconc
       `((To . ,(or to "")) (Subject . ,(or subject "")))
       (when other-headers other-headers))
-     replybuffer send-actions)
+     replybuffer send-actions continue switch-function)
     ;; FIXME: Should return nil if failure.
     t))
 
@@ -5760,6 +5994,39 @@ want to get rid of this query permanently.")))
        (push (cons 'Cc recipients) follow-to)))
     follow-to))
 
+(defcustom message-simplify-subject-functions
+  '(message-strip-list-identifiers
+    message-strip-subject-re
+    message-strip-subject-trailing-was
+    message-strip-subject-encoded-words)
+  "List of functions taking a string argument that simplify subjects.
+The functions are applied when replying to a message.
+
+Useful functions to put in this list include:
+`message-strip-list-identifiers', `message-strip-subject-re',
+`message-strip-subject-trailing-was', and
+`message-strip-subject-encoded-words'."
+  :version "22.1" ;; Gnus 5.10.9
+  :group 'message-various
+  :type '(repeat function))
+
+(defun message-simplify-subject (subject &optional functions)
+  "Return simplified SUBJECT."
+  (unless functions
+    ;; Simplify fully:
+    (setq functions message-simplify-subject-functions))
+  (when (and (memq 'message-strip-list-identifiers functions)
+            gnus-list-identifiers)
+    (setq subject (message-strip-list-identifiers subject)))
+  (when (memq 'message-strip-subject-re functions)
+    (setq subject (concat "Re: " (message-strip-subject-re subject))))
+  (when (and (memq 'message-strip-subject-trailing-was functions)
+            message-subject-trailing-was-query)
+    (setq subject (message-strip-subject-trailing-was subject)))
+  (when (memq 'message-strip-subject-encoded-words functions)
+    (setq subject (message-strip-subject-encoded-words subject)))
+  subject)
+
 ;;;###autoload
 (defun message-reply (&optional to-address wide)
   "Start editing a reply to the article in the current buffer."
@@ -5787,13 +6054,11 @@ want to get rid of this query permanently.")))
       (setq message-id (message-fetch-field "message-id" t)
            references (message-fetch-field "references")
            date (message-fetch-field "date")
-           from (message-fetch-field "from")
+           from (or (message-fetch-field "from") "nobody")
            subject (or (message-fetch-field "subject") "none"))
-      (when gnus-list-identifiers
-       (setq subject (message-strip-list-identifiers subject)))
-      (setq subject (concat "Re: " (message-strip-subject-re subject)))
-      (when message-subject-trailing-was-query
-       (setq subject (message-strip-subject-trailing-was subject)))
+
+      ;; Strip list identifiers, "Re: ", and "was:"
+      (setq subject (message-simplify-subject subject))
 
       (when (and (setq gnus-warning (message-fetch-field "gnus-warning"))
                 (string-match "<[^>]+>" gnus-warning))
@@ -5863,11 +6128,8 @@ If TO-NEWSGROUPS, use that as the new Newsgroups line."
                 (let ((case-fold-search t))
                   (string-match "world" distribution)))
        (setq distribution nil))
-      (if gnus-list-identifiers
-         (setq subject (message-strip-list-identifiers subject)))
-      (setq subject (concat "Re: " (message-strip-subject-re subject)))
-      (when message-subject-trailing-was-query
-       (setq subject (message-strip-subject-trailing-was subject)))
+      ;; Strip list identifiers, "Re: ", and "was:"
+      (setq subject (message-simplify-subject subject))
       (widen))
 
     (message-pop-to-buffer (message-buffer-name "followup" from newsgroups))
@@ -6460,7 +6722,7 @@ you."
       (message-pop-to-buffer (message-buffer-name "mail" to))))
   (let ((message-this-is-mail t))
     (message-setup `((To . ,(or to "")) (Subject . ,(or subject "")))
-                  nil nil 'switch-to-buffer-other-window)))
+                  nil nil nil 'switch-to-buffer-other-window)))
 
 ;;;###autoload
 (defun message-mail-other-frame (&optional to subject)
@@ -6475,7 +6737,7 @@ you."
       (message-pop-to-buffer (message-buffer-name "mail" to))))
   (let ((message-this-is-mail t))
     (message-setup `((To . ,(or to "")) (Subject . ,(or subject "")))
-                  nil nil 'switch-to-buffer-other-frame)))
+                  nil nil nil 'switch-to-buffer-other-frame)))
 
 ;;;###autoload
 (defun message-news-other-window (&optional newsgroups subject)
@@ -6556,54 +6818,123 @@ which specify the range to operate on."
 
 ;; Support for toolbar
 (eval-when-compile
-  (defvar tool-bar-map)
   (defvar tool-bar-mode))
 
-(defun message-tool-bar-local-item-from-menu (command icon in-map &optional from-map &rest props)
-  ;; We need to make tool bar entries in local keymaps with
-  ;; `tool-bar-local-item-from-menu' in Emacs > 21.3
-  (if (fboundp 'tool-bar-local-item-from-menu)
-      ;; This is for Emacs 21.3
-      (tool-bar-local-item-from-menu command icon in-map from-map props)
-    (tool-bar-add-item-from-menu command icon from-map props)))
-
-(defun message-tool-bar-map ()
-  (or message-tool-bar-map
-      (setq message-tool-bar-map
-           (and
-            (condition-case nil (require 'tool-bar) (error nil))
-            (fboundp 'tool-bar-add-item-from-menu)
+;; Note: The :set function in the `message-tool-bar*' variables will only
+;; affect _new_ message buffers.  We might add a function that walks thru all
+;; message-mode buffers and force the update.
+(defun message-tool-bar-update (&optional symbol value)
+  "Update message mode toolbar.
+Setter function for custom variables."
+  (setq-default message-tool-bar-map nil)
+  (when symbol
+    ;; When used as ":set" function:
+    (set-default symbol value)))
+
+(defcustom message-tool-bar (if (eq gmm-tool-bar-style 'gnome)
+                               'message-tool-bar-gnome
+                             'message-tool-bar-retro)
+  "Specifies the message mode tool bar.
+
+It can be either a list or a symbol refering to a list.  See
+`gmm-tool-bar-from-list' for the format of the list.  The
+default key map is `message-mode-map'.
+
+Pre-defined symbols include `message-tool-bar-gnome' and
+`message-tool-bar-retro'."
+  :type '(repeat gmm-tool-bar-list-item)
+  :type '(choice (const :tag "GNOME style" message-tool-bar-gnome)
+                (const :tag "Retro look"  message-tool-bar-retro)
+                (repeat :tag "User defined list" gmm-tool-bar-item)
+                (symbol))
+  :version "22.1" ;; Gnus 5.10.9
+  :initialize 'custom-initialize-default
+  :set 'message-tool-bar-update
+  :group 'message)
+
+(defcustom message-tool-bar-gnome
+  '((ispell-message "spell" nil
+                   :visible (or (not (boundp 'flyspell-mode))
+                                (not flyspell-mode)))
+    (flyspell-buffer "spell" t
+                    :visible (and (boundp 'flyspell-mode)
+                                  flyspell-mode)
+                    :help "Flyspell whole buffer")
+    (gmm-ignore "separator")
+    (message-send-and-exit "mail/send")
+    (message-dont-send "mail/save-draft")
+    (message-kill-buffer "close") ;; stock_cancel
+    (mml-attach-file "attach" mml-mode-map)
+    (mml-preview "mail/preview" mml-mode-map)
+    ;; (mml-secure-message-sign-encrypt "lock" mml-mode-map :visible nil)
+    (message-insert-importance-high "important" nil :visible nil)
+    (message-insert-importance-low "unimportant" nil :visible nil)
+    (message-insert-disposition-notification-to "receipt" nil :visible nil)
+    (gmm-customize-mode "preferences" t :help "Edit mode preferences")
+    (message-info "help" t :help "Message manual"))
+  "List of items for the message tool bar (GNOME style).
+
+See `gmm-tool-bar-from-list' for details on the format of the list."
+  :type '(repeat gmm-tool-bar-item)
+  :version "22.1" ;; Gnus 5.10.9
+  :initialize 'custom-initialize-default
+  :set 'message-tool-bar-update
+  :group 'message)
+
+(defcustom message-tool-bar-retro
+  '(;; Old Emacs 21 icon for consistency.
+    (message-send-and-exit "gnus/mail_send")
+    (message-kill-buffer "close")
+    (message-dont-send "cancel")
+    (mml-attach-file "attach" mml-mode-map)
+    (ispell-message "spell")
+    (mml-preview "preview" mml-mode-map)
+    (message-insert-importance-high "gnus/important")
+    (message-insert-importance-low "gnus/unimportant")
+    (message-insert-disposition-notification-to "gnus/receipt"))
+  "List of items for the message tool bar (retro style).
+
+See `gmm-tool-bar-from-list' for details on the format of the list."
+  :type '(repeat gmm-tool-bar-item)
+  :version "22.1" ;; Gnus 5.10.9
+  :initialize 'custom-initialize-default
+  :set 'message-tool-bar-update
+  :group 'message)
+
+(defcustom message-tool-bar-zap-list
+  '(new-file open-file dired kill-buffer write-file
+            print-buffer customize help)
+  "List of icon items from the global tool bar.
+These items are not displayed on the message mode tool bar.
+
+See `gmm-tool-bar-from-list' for the format of the list."
+  :type 'gmm-tool-bar-zap-list
+  :version "22.1" ;; Gnus 5.10.9
+  :initialize 'custom-initialize-default
+  :set 'message-tool-bar-update
+  :group 'message)
+
+(defvar image-load-path)
+
+(defun message-make-tool-bar (&optional force)
+  "Make a message mode tool bar from `message-tool-bar-list'.
+When FORCE, rebuild the tool bar."
+  (when (and (not (featurep 'xemacs))
+            (boundp 'tool-bar-mode)
             tool-bar-mode
-            (let ((tool-bar-map (copy-keymap tool-bar-map))
-                  (load-path (mm-image-load-path)))
-              ;; Zap some items which aren't so relevant and take
-              ;; up space.
-              (dolist (key '(print-buffer kill-buffer save-buffer
-                                          write-file dired open-file))
-                (define-key tool-bar-map (vector key) nil))
-              (message-tool-bar-local-item-from-menu
-               'message-send-and-exit "mail_send" tool-bar-map message-mode-map)
-              (message-tool-bar-local-item-from-menu
-               'message-kill-buffer "close" tool-bar-map message-mode-map)
-              (message-tool-bar-local-item-from-menu
-                   'message-dont-send "cancel" tool-bar-map message-mode-map)
-              (message-tool-bar-local-item-from-menu
-               'mml-attach-file "attach" tool-bar-map mml-mode-map)
-              (message-tool-bar-local-item-from-menu
-               'ispell-message "spell" tool-bar-map message-mode-map)
-              (message-tool-bar-local-item-from-menu
-               'mml-preview "preview"
-               tool-bar-map mml-mode-map)
-              (message-tool-bar-local-item-from-menu
-               'message-insert-importance-high "important"
-               tool-bar-map message-mode-map)
-              (message-tool-bar-local-item-from-menu
-               'message-insert-importance-low "unimportant"
-               tool-bar-map message-mode-map)
-              (message-tool-bar-local-item-from-menu
-               'message-insert-disposition-notification-to "receipt"
-               tool-bar-map message-mode-map)
-              tool-bar-map)))))
+            (or (not message-tool-bar-map) force))
+    (setq message-tool-bar-map
+         (let* ((load-path
+                 (gmm-image-load-path-for-library "message"
+                                                  "mail/save-draft.xpm"
+                                                  nil t))
+                (image-load-path (cons (car load-path)
+                                       (when (boundp 'image-load-path)
+                                         image-load-path))))
+           (gmm-tool-bar-from-list message-tool-bar
+                                   message-tool-bar-zap-list
+                                   'message-mode-map))))
+  message-tool-bar-map)
 
 ;;; Group name completion.
 
@@ -6649,6 +6980,17 @@ those headers."
                 (lookup-key global-map "\t")
                 'indent-relative))))
 
+(eval-and-compile
+  (condition-case nil
+      (with-temp-buffer
+       (let ((standard-output (current-buffer)))
+         (eval '(display-completion-list nil "")))
+       (defalias 'message-display-completion-list 'display-completion-list))
+    (error ;; Don't use `wrong-number-of-arguments' here because of XEmacs.
+     (defun message-display-completion-list (completions &optional ignore)
+       "Display the list of completions, COMPLETIONS, using `standard-output'."
+       (display-completion-list completions)))))
+
 (defun message-expand-group ()
   "Expand the group name under point."
   (let* ((b (save-excursion
@@ -6687,7 +7029,9 @@ those headers."
          (let ((buffer-read-only nil))
            (erase-buffer)
            (let ((standard-output (current-buffer)))
-             (display-completion-list (sort completions 'string<)))
+             (message-display-completion-list (sort completions 'string<)
+                                              string))
+           (setq buffer-read-only nil)
            (goto-char (point-min))
            (delete-region (point) (progn (forward-line 3) (point))))))))))
 
@@ -6816,6 +7160,9 @@ regexp VARSTR."
       (read-string prompt initial-contents))))
 
 (defun message-use-alternative-email-as-from ()
+  "Set From field of the outgoing message to the first matching
+address in `message-alternative-emails', looking at To, Cc and
+From headers in the original article."
   (require 'mail-utils)
   (let* ((fields '("To" "Cc"))
         (emails
@@ -6830,6 +7177,7 @@ regexp VARSTR."
                emails nil))
       (pop emails))
     (unless (or (not email) (equal email user-mail-address))
+      (message-remove-header "From")
       (goto-char (point-max))
       (insert "From: " email "\n"))))