Synch to Gnus 200311102021.
[elisp/gnus.git-] / lisp / message.el
index b5525b9..fed132e 100644 (file)
 
 (require 'rfc822)
 (eval-and-compile
+  (autoload 'customize-save-variable "cus-edit") ;; for Mule 2.
   (autoload 'sha1 "sha1-el")
   (autoload 'gnus-find-method-for-group "gnus")
   (autoload 'nnvirtual-find-group-art "nnvirtual")
-  (autoload 'customize-save-variable "cus-edit")) ;; for Mule 2.
+  (autoload 'gnus-group-decoded-name "gnus-group"))
 
 (defgroup message '((user-mail-address custom-variable)
                    (user-full-name custom-variable))
@@ -593,12 +594,19 @@ Otherwise, directly inline the old message in the forwarded message."
   :group 'message-forwarding
   :type 'boolean)
 
-(defcustom message-forward-show-mml nil
-  "*Non-nil means show forwarded messages as mml.
-Otherwise, forwarded messages are unchanged."
+(defcustom message-forward-show-mml 'best
+  "*Non-nil means show forwarded messages as MML (decoded from MIME).
+Otherwise, forwarded messages are unchanged.
+Can also be the symbol `best' to indicate that MML should be
+used, except when it is a bad idea to use MML.  One example where
+it is a bad idea is when forwarding a signed or encrypted
+message, because converting MIME to MML would invalidate the
+digital signature."
   :version "21.1"
   :group 'message-forwarding
-  :type 'boolean)
+  :type '(choice (const :tag "use MML" t)
+                (const :tag "don't use MML " nil)
+                (const :tag "use MML when appropriate" best)))
 
 (defcustom message-forward-before-signature t
   "*Non-nil means put forwarded message before signature, else after."
@@ -1211,7 +1219,7 @@ candidates:
 (defcustom message-hidden-headers nil
   "Regexp of headers to be hidden when composing new messages.
 This can also be a list of regexps to match headers.  Or a list
-starting with `not' and followed by regexps.."
+starting with `not' and followed by regexps."
   :group 'message
   :type '(repeat regexp))
 
@@ -1553,6 +1561,7 @@ no, only reply back to the author."
 (defvar message-draft-article nil)
 (defvar message-mime-part nil)
 (defvar message-posting-charset nil)
+(defvar message-inserted-headers nil)
 
 ;; Byte-compiler warning
 (eval-when-compile
@@ -1790,6 +1799,13 @@ see `message-narrow-to-headers-or-head'."
       (set-text-properties 0 (length value) nil value)
       value)))
 
+(defun message-field-value (header &optional not-all)
+  "The same as `message-fetch-field', only narrow to the headers first."
+  (save-excursion
+    (save-restriction
+      (message-narrow-to-headers-or-head)
+      (message-fetch-field header not-all))))
+
 (defun message-narrow-to-field ()
   "Narrow the buffer to the header on the current line."
   (beginning-of-line)
@@ -1899,6 +1915,7 @@ Leading \"Re: \" is not stripped by this function.  Use the function
 ;;;###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>
   (interactive
    (list
     (read-from-minibuffer "New subject: ")))
@@ -1933,7 +1950,7 @@ Leading \"Re: \" is not stripped by this function.  Use the function
 See `message-mark-insert-begin' and `message-mark-insert-end'."
   (interactive "r")
   (save-excursion
-    ; add to the end of the region first, otherwise end would be invalid
+    ;; add to the end of the region first, otherwise end would be invalid
     (goto-char end)
     (insert message-mark-insert-end)
     (goto-char beg)
@@ -2413,9 +2430,6 @@ Point is left at the beginning of the narrowed-to region."
 (easy-menu-define
  message-mode-field-menu message-mode-map ""
  `("Field"
-   ["Fetch To" message-insert-to t]
-   ["Fetch Newsgroups" message-insert-newsgroups t]
-   "----"
    ["To" message-goto-to t]
    ["From" message-goto-from t]
    ["Subject" message-goto-subject t]
@@ -2439,6 +2453,7 @@ Point is left at the beginning of the narrowed-to region."
    ["Summary" message-goto-summary t]
    ["Keywords" message-goto-keywords t]
    ["Newsgroups" message-goto-newsgroups t]
+   ["Fetch Newsgroups" message-insert-newsgroups t]
    ["Followup-To" message-goto-followup-to t]
    ;; ["Followup-To (with note in body)" message-cross-post-followup-to t]
    ["Crosspost / Followup-To..." message-cross-post-followup-to t]
@@ -2446,10 +2461,21 @@ Point is left at the beginning of the narrowed-to region."
    ["X-No-Archive:" message-add-archive-header t ]
    "----"
    ;; (typical) mailing-lists stuff
+   ["Fetch To" message-insert-to
+    ,@(if (featurep 'xemacs) '(t)
+       '(:help "Insert a To header that points to the author."))]
+   ["Fetch To and Cc" message-insert-wide-reply
+    ,@(if (featurep 'xemacs) '(t)
+       '(:help
+         "Insert To and Cc headers as if you were doing a wide reply."))]
+   "----"
    ["Send to list only" message-to-list-only t]
    ["Mail-Followup-To" message-goto-mail-followup-to t]
    ["Mail-Reply-To" message-goto-mail-reply-to t]
    ["Mail-Copies-To" message-goto-mail-copies-to t]
+   ["Unsubscribed list post" message-generate-unsubscribed-mail-followup-to
+    ,@(if (featurep 'xemacs) '(t)
+       '(:help "Insert a reasonable `Mail-Followup-To:' header."))]
    ["Reduce To: to Cc:" message-reduce-to-to-cc t]
    "----"
    ["Sort Headers" message-sort-headers t]
@@ -2564,11 +2590,12 @@ C-c M-f  `message-mark-insert-file' (insert file marked with enclosing tags).
 M-RET    `message-newline-and-reformat' (break the line and reformat)."
   (setq local-abbrev-table text-mode-abbrev-table)
   (set (make-local-variable 'message-reply-buffer) nil)
-  (make-local-variable 'message-send-actions)
-  (make-local-variable 'message-exit-actions)
-  (make-local-variable 'message-kill-actions)
-  (make-local-variable 'message-postpone-actions)
-  (make-local-variable 'message-draft-article)
+  (set (make-local-variable 'message-inserted-headers) nil)
+  (set (make-local-variable 'message-send-actions) nil)
+  (set (make-local-variable 'message-exit-actions) nil)
+  (set (make-local-variable 'message-kill-actions) nil)
+  (set (make-local-variable 'message-postpone-actions) nil)
+  (set (make-local-variable 'message-draft-article) nil)
   (setq buffer-offer-save t)
   (set (make-local-variable 'facemenu-add-face-function)
        (lambda (face end)
@@ -2810,22 +2837,25 @@ Cc: header are also put into the MFT."
 
 (defun message-insert-to (&optional force)
   "Insert a To header that points to the author of the article being replied to.
-If the original author requested not to be sent mail, the function signals
-an error.
-With the prefix argument FORCE, insert the header anyway."
+If the original author requested not to be sent mail, don't insert unless the
+prefix FORCE is given."
   (interactive "P")
-  (let ((co (message-fetch-reply-field "mail-copies-to")))
-    (when (and (null force)
-              co
-              (or (equal (downcase co) "never")
-                  (equal (downcase co) "nobody")))
-      (error "The user has requested not to have copies sent via mail")))
-  (message-carefully-insert-headers
-   (list (cons 'To
-              (or (message-fetch-reply-field "mail-reply-to")
-                  (message-fetch-reply-field "reply-to")
-                  (message-fetch-reply-field "from")
-                  "")))))
+  (let* ((mct (message-fetch-reply-field "mail-copies-to"))
+         (dont (and mct (or (equal (downcase mct) "never")
+                           (equal (downcase mct) "nobody"))))
+         (to (or (message-fetch-reply-field "mail-reply-to")
+                 (message-fetch-reply-field "reply-to")
+                 (message-fetch-reply-field "from"))))
+    (when (and dont to)
+      (gnus-message
+       3
+       (if force
+          "Ignoring the user request not to have copies sent via mail"
+        "Complying with the user request not to have copies sent via mail")))
+    (when (and force (not to))
+      (error "No mail address in the article"))
+    (when (and to (or force (not dont)))
+      (message-carefully-insert-headers (list (cons 'To to))))))
 
 (defun message-insert-wide-reply ()
   "Insert To and Cc headers as if you were doing a wide reply."
@@ -3115,10 +3145,8 @@ Note that this should not be used in newsgroups."
       (message-remove-header "Disposition-Notification-To"))
     (message-goto-eoh)
     (insert (format "Disposition-Notification-To: %s\n"
-                   (or (save-excursion
-                         (save-restriction
-                           (message-narrow-to-headers)
-                           (message-fetch-field "From")))
+                   (or (message-field-value "Reply-to")
+                       (message-field-value "From")
                        (message-make-from))))))
 
 (defun message-elide-region (b e)
@@ -5240,7 +5268,8 @@ give as trustworthy answer as possible."
         (user-domain
          (if (and user-mail
                   (string-match "@\\(.*\\)\\'" user-mail))
-             (match-string 1 user-mail))))
+             (match-string 1 user-mail)))
+        (case-fold-search t))
     (cond
      ((and message-user-fqdn
           (stringp message-user-fqdn)
@@ -5439,7 +5468,7 @@ Headers already prepared in the buffer are not modified."
           (Expires (message-make-expires))
           (case-fold-search t)
           (optionalp nil)
-          header value elem)
+          header value elem header-string)
       ;; First we remove any old generated headers.
       (let ((headers message-deletable-headers))
        (unless (buffer-modified-p)
@@ -5464,13 +5493,12 @@ Headers already prepared in the buffer are not modified."
                      optionalp t)
              (setq header (car elem)))
          (setq header elem))
+       (setq header-string  (if (stringp header)
+                                header
+                              (symbol-name header)))
        (when (or (not (re-search-forward
                        (concat "^"
-                               (regexp-quote
-                                (downcase
-                                 (if (stringp header)
-                                     header
-                                   (symbol-name header))))
+                               (regexp-quote (downcase header-string))
                                ":")
                        nil t))
                  (progn
@@ -5483,7 +5511,8 @@ Headers already prepared in the buffer are not modified."
          (setq value
                (cond
                 ((and (consp elem)
-                      (eq (car elem) 'optional))
+                      (eq (car elem) 'optional)
+                      (not (member header-string message-inserted-headers)))
                  ;; This is an optional header.  If the cdr of this
                  ;; is something that is nil, then we do not insert
                  ;; this header.
@@ -5523,9 +5552,7 @@ Headers already prepared in the buffer are not modified."
                           (cdr (assq header message-header-format-alist))))
                      (if formatter
                          (funcall formatter header value)
-                       (insert (if (stringp header)
-                                   header (symbol-name header))
-                               ": " value))
+                       (insert header-string ": " value))
                      ;; We check whether the value was ended by a
                      ;; newline.  If now, we insert one.
                      (unless (bolp)
@@ -5537,6 +5564,7 @@ Headers already prepared in the buffer are not modified."
                ;; If the header is optional, and the header was
                ;; empty, we can't insert it anyway.
                (unless optionalp
+                 (push header-string message-inserted-headers)
                  (insert value)
                  (when (bolp)
                    (delete-char -1))))
@@ -6646,38 +6674,44 @@ Previous forwarders, replyers, etc. may add it."
 (defvar message-forward-decoded-p nil
   "Non-nil means the original message is decoded.")
 
-(defun message-forward-subject-author-subject (subject)
+(defun message-forward-subject-name-subject (subject)
   "Generate a SUBJECT for a forwarded message.
 The form is: [Source] Subject, where if the original message was mail,
-Source is the sender, and if the original message was news, Source is
-the list of newsgroups is was posted to."
+Source is the name of the sender, and if the original message was
+news, Source is the list of newsgroups is was posted to."
   (concat "["
-         (let ((prefix (message-fetch-field "newsgroups")))
-           (or prefix
-               (and (setq prefix (message-fetch-field "from"))
-                    (nnheader-decode-from prefix))
-               "(nowhere)"))
+         (let ((group (message-fetch-field "newsgroups"))
+               from)
+           (if group
+               (gnus-group-decoded-name group)
+             (or (and (setq from (message-fetch-field "from"))
+                      (car (std11-extract-address-components 
+                            (nnheader-decode-from from))))
+                 "(nowhere)")))
          "] " subject))
 
-(defun message-forward-subject-name-subject (subject)
+(defun message-forward-subject-author-subject (subject)
   "Generate a SUBJECT for a forwarded message.
 The form is: [Source] Subject, where if the original message was mail,
-Source is the name of the sender, and if the original message was
-news, Source is the list of newsgroups is was posted to."
+Source is the sender, and if the original message was news, Source is
+the list of newsgroups is was posted to."
   (concat "["
-         (let ((prefix (message-fetch-field "newsgroups")))
-           (or prefix
-               (and (setq prefix (message-fetch-field "from"))
-                    (car (std11-extract-address-components
-                          (nnheader-decode-from prefix))))
-               "(nowhere)"))
+         (let ((group (message-fetch-field "newsgroups"))
+               from)
+           (if group
+               (gnus-group-decoded-name group)
+             (if (setq from (message-fetch-field "from"))
+                 (nnheader-decode-from from)
+               "(nowhere)")))
          "] " subject))
 
 (defun message-forward-subject-fwd (subject)
   "Generate a SUBJECT for a forwarded message.
 The form is: Fwd: Subject, where Subject is the original subject of
 the message."
-  (concat "Fwd: " subject))
+  (if (string-match "^Fwd: " subject)
+      subject
+    (concat "Fwd: " subject)))
 
 (defun message-make-forward-subject ()
   "Return a Subject header suitable for the message in the current buffer."
@@ -6718,6 +6752,108 @@ Optional NEWS will use news to forward instead of mail."
       (message-mail nil subject))
     (message-forward-make-body cur)))
 
+(defun message-forward-make-body-plain (forward-buffer)
+  (insert
+   "\n-------------------- Start of forwarded message --------------------\n")
+  (let ((b (point)) e)
+    (insert
+     (with-temp-buffer
+       (mm-disable-multibyte)
+       (insert
+       (with-current-buffer forward-buffer
+         (mm-with-unibyte-current-buffer (buffer-string))))
+       (mm-enable-multibyte)
+       (mime-to-mml)
+       (goto-char (point-min))
+       (when (looking-at "From ")
+        (replace-match "X-From-Line: "))
+       (buffer-string)))
+    (setq e (point))
+    (insert
+     "\n-------------------- End of forwarded message --------------------\n")
+    (when (and (not current-prefix-arg)
+              message-forward-ignored-headers)
+      (save-restriction
+       (narrow-to-region b e)
+       (goto-char b)
+       (narrow-to-region (point)
+                         (or (search-forward "\n\n" nil t) (point)))
+       (message-remove-header message-forward-ignored-headers t)))))
+
+(defun message-forward-make-body-mime (forward-buffer)
+  (insert "\n\n<#part type=message/rfc822 disposition=inline raw=t>\n")
+  (let ((b (point)) e)
+    (save-restriction
+      (narrow-to-region (point) (point))
+      (mml-insert-buffer forward-buffer)
+      (goto-char (point-min))
+      (when (looking-at "From ")
+       (replace-match "X-From-Line: "))
+      (goto-char (point-max)))
+    (setq e (point))
+    (insert "<#/part>\n")))
+
+(defun message-forward-make-body-mml (forward-buffer)
+  (insert "\n\n<#mml type=message/rfc822 disposition=inline>\n")
+  (let ((b (point)) e)
+    (if (not message-forward-decoded-p)
+       (insert
+        (with-temp-buffer
+          (mm-disable-multibyte)
+          (insert
+           (with-current-buffer forward-buffer
+             (mm-with-unibyte-current-buffer (buffer-string))))
+          (mm-enable-multibyte)
+          (mime-to-mml)
+          (goto-char (point-min))
+          (when (looking-at "From ")
+            (replace-match "X-From-Line: "))
+          (buffer-string)))
+      (save-restriction
+       (narrow-to-region (point) (point))
+       (mml-insert-buffer forward-buffer)
+       (goto-char (point-min))
+       (when (looking-at "From ")
+         (replace-match "X-From-Line: "))
+       (goto-char (point-max))))
+    (setq e (point))
+    (insert "<#/mml>\n")
+    (when (and (not current-prefix-arg)
+              message-forward-ignored-headers)
+      (save-restriction
+       (narrow-to-region b e)
+       (goto-char b)
+       (narrow-to-region (point)
+                         (or (search-forward "\n\n" nil t) (point)))
+       (message-remove-header message-forward-ignored-headers t)))))
+
+(defun message-forward-make-body-digest-plain (forward-buffer)
+  (insert
+   "\n-------------------- Start of forwarded message --------------------\n")
+  (let ((b (point)) e)
+    (mml-insert-buffer forward-buffer)
+    (setq e (point))
+    (insert
+     "\n-------------------- End of forwarded message --------------------\n")))
+
+(defun message-forward-make-body-digest-mime (forward-buffer)
+  (insert "\n<#multipart type=digest>\n")
+  (let ((b (point)) e)
+    (insert-buffer-substring forward-buffer)
+    (setq e (point))
+    (insert "<#/multipart>\n")
+    (save-restriction
+      (narrow-to-region b e)
+      (goto-char b)
+      (narrow-to-region (point)
+                       (or (search-forward "\n\n" nil t) (point)))
+      (delete-region (point-min) (point-max)))))
+
+(defun message-forward-make-body-digest (forward-buffer)
+  (if message-forward-as-mime
+      (message-forward-make-body-digest-mime forward-buffer)
+    (message-forward-make-body-digest-plain forward-buffer)))
+
 ;;;###autoload
 (defun message-forward-make-body (forward-buffer)
   ;; Put point where we want it before inserting the forwarded
@@ -6787,7 +6923,7 @@ Optional NEWS will use news to forward instead of mail."
            message-setup-hook)
        (message-setup `((To . ,address))))
       ;; Insert our usual headers.
-      (message-generate-headers '(From Date To))
+      (message-generate-headers '(From Date To Message-ID))
       (message-narrow-to-headers)
       ;; Remove X-Draft-From header etc.
       (message-remove-header message-ignored-mail-headers t)
@@ -6838,32 +6974,36 @@ contains some mail you have written which has been bounced back to
 you."
   (interactive)
   (let ((cur (current-buffer))
-       boundary)
+       mime-boundary boundary)
     (message-pop-to-buffer (message-buffer-name "bounce"))
     (insert-buffer-substring cur)
     (undo-boundary)
     (message-narrow-to-head)
     (if (and (message-fetch-field "MIME-Version")
-            (setq boundary (message-fetch-field "Content-Type")))
-       (if (string-match "boundary=\"\\([^\"]+\\)\"" boundary)
-           (setq boundary (concat (match-string 1 boundary) " *\n"
-                                  "Content-Type: message/rfc822"))
-         (setq boundary nil)))
+            (setq mime-boundary (message-fetch-field "Content-Type")))
+       (if (string-match "boundary=\"\\([^\"]+\\)\"" mime-boundary)
+           (setq mime-boundary (concat (regexp-quote
+                                        (match-string 1 mime-boundary))
+                                       " *\nContent-Type: message/rfc822"))
+         (setq mime-boundary nil)))
     (widen)
     (goto-char (point-min))
-    (search-forward "\n\n" nil t)
-    (if (or (and boundary
-                (re-search-forward boundary nil t)
-                (forward-line 2))
-           (and (re-search-forward message-unsent-separator nil t)
+    (re-search-forward "\n\n+" nil t)
+    (setq boundary (point))
+    ;; We remove everything before the bounced mail.
+    (if (or (and mime-boundary
+                (re-search-forward mime-boundary nil t)
                 (forward-line 1))
-           (re-search-forward "^Return-Path:.*\n" nil t))
-       ;; We remove everything before the bounced mail.
-       (delete-region
-        (point-min)
-        (if (re-search-forward "^[^ \n\t]+:" nil t)
-            (match-beginning 0)
-          (point)))
+           (re-search-forward message-unsent-separator nil t)
+           (progn
+             (search-forward "\n\n" nil 'move)
+             (re-search-backward "^Return-Path:.*\n" boundary t)))
+       (progn
+         (forward-line 1)
+         (delete-region (point-min)
+                        (if (re-search-forward "^[^ \n\t]+:" nil t)
+                            (match-beginning 0)
+                          (point))))
       (when (re-search-backward "^.?From .*\n" nil t)
        (delete-region (match-beginning 0) (match-end 0))))
     (save-restriction