Merge from gnus--rel--5.10
[gnus] / lisp / message.el
index 56ec12e..2a42c49 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, 2006 Free Software Foundation, Inc.
+;;   2005, 2006, 2007 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; Keywords: mail, news
@@ -208,7 +208,7 @@ Also see `message-required-news-headers' and
   :link '(custom-manual "(message)Message Headers")
   :type '(repeat sexp))
 
-(defcustom message-draft-headers '(References From)
+(defcustom message-draft-headers '(References From Date)
   "*Headers to be generated when saving a draft message."
   :version "22.1"
   :group 'message-news
@@ -761,6 +761,14 @@ If this is nil, use `user-mail-address'.  If it is the symbol
   :link '(custom-manual "(message)Mail Variables")
   :group 'message-sending)
 
+(defcustom message-sendmail-extra-arguments nil
+  "Additional arguments to `sendmail-program'."
+  ;; E.g. '("-a" "account") for msmtp
+  :version "23.0" ;; No Gnus
+  :type '(repeat string)
+  ;; :link '(custom-manual "(message)Mail Variables")
+  :group 'message-sending)
+
 ;; qmail-related stuff
 (defcustom message-qmail-inject-program "/var/qmail/bin/qmail-inject"
   "Location of the qmail-inject program."
@@ -884,7 +892,7 @@ configuration.  See the variable `gnus-cite-attribution-suffix'."
   :link '(custom-manual "(message)Insertion Variables")
   :group 'message-insertion)
 
-(defcustom message-citation-line-format "On %a, %b %d %Y, %n wrote:"
+(defcustom message-citation-line-format "On %a, %b %d %Y, %N wrote:"
   "Format of the \"Whomever writes:\" line.
 
 The string is formatted using `format-spec'.  The following
@@ -1571,10 +1579,16 @@ functionality to work."
 
 (defcustom message-generate-hashcash (if (executable-find "hashcash") t)
   "*Whether to generate X-Hashcash: headers.
+If `t', always generate hashcash headers.  If `opportunistic',
+only generate hashcash headers if it can be done without the user
+waiting (i.e., only asynchronously).
+
 You must have the \"hashcash\" binary installed, see `hashcash-path'."
   :group 'message-headers
   :link '(custom-manual "(message)Mail Headers")
-  :type 'boolean)
+  :type '(choice (const :tag "Always" t)
+                (const :tag "Never" nil)
+                (const :tag "Opportunistic" opportunistic)))
 
 ;;; Internal variables.
 
@@ -1689,7 +1703,7 @@ You must have the \"hashcash\" binary installed, see `hashcash-path'."
 (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
@@ -1898,6 +1912,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)
@@ -3257,17 +3361,17 @@ text was killed."
      (substring table ?a (+ ?a n))
      (substring table (+ ?a 26) 255))))
 
-(defun message-caesar-buffer-body (&optional rotnum)
+(defun message-caesar-buffer-body (&optional rotnum wide)
   "Caesar rotate all letters in the current buffer by 13 places.
 Used to encode/decode possibly offensive messages (commonly in rec.humor).
 With prefix arg, specifies the number of places to rotate each letter forward.
-Mail and USENET news headers are not rotated."
+Mail and USENET news headers are not rotated unless WIDE is non-nil."
   (interactive (if current-prefix-arg
                   (list (prefix-numeric-value current-prefix-arg))
                 (list nil)))
   (save-excursion
     (save-restriction
-      (when (message-goto-body)
+      (when (and (not wide) (message-goto-body))
        (narrow-to-region (point) (point-max)))
       (message-caesar-region (point-min) (point-max) rotnum))))
 
@@ -3470,7 +3574,7 @@ This function uses `mail-citation-hook' if that is non-nil."
              (setq x-no-archive (message-fetch-field "x-no-archive"))
              (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")
@@ -3496,13 +3600,21 @@ This function uses `mail-citation-hook' if that is non-nil."
        (unless (bolp)
          (insert "\n"))
        (funcall message-citation-line-function))
-      (when (and x-no-archive
-                (not message-cite-articles-with-x-no-archive)
-                (string-match "yes" x-no-archive))
-       (undo-boundary)
-       (delete-region (point) (mark t))
-       (insert "> [Quoted text removed due to X-No-Archive]\n")
-       (forward-line -1)))))
+      (if (and x-no-archive
+              (not message-cite-articles-with-x-no-archive)
+              (string-match "yes" x-no-archive))
+         (progn
+           (undo-boundary)
+           (delete-region (point) (mark t))
+           (insert "> [Quoted text removed due to X-No-Archive]\n")
+           (push-mark)
+           (forward-line -1))
+       ;; FIXME: Doesn't handle first attribution line correctly.  Probably
+       ;; font-lock looks for "\n\n" to find start of mail message.
+       (when (and (boundp 'gnus-message-highlight-citation)
+                  gnus-message-highlight-citation
+                  (fboundp 'gnus-article-highlight-citation))
+         (gnus-article-highlight-citation nil t))))))
 
 (defun message-cite-original ()
   "Cite function in the standard Message manner."
@@ -3539,7 +3651,7 @@ See `message-citation-line-format'."
             date
             ;; We need Gnus functionality if the user wants date or time from
             ;; the original article:
-            (when (string-match "%[^EFLn]" message-citation-line-format)
+            (when (string-match "%[^fnNFL]" message-citation-line-format)
               (autoload 'gnus-date-get-time "gnus-util")
               (gnus-date-get-time (mail-header-date message-reply-headers)))))
           (flist
@@ -3559,19 +3671,19 @@ See `message-citation-line-format'."
                       (setq fname name
                             lname ""))))
              ;; The following letters are not used in `format-time-string':
-             (push ?E lst) (push net lst)
+             (push ?E lst) (push "<E>" lst)
              (push ?F lst) (push fname lst)
              ;; We might want to use "" instead of "<X>" later.
              (push ?J lst) (push "<J>" lst)
              (push ?K lst) (push "<K>" lst)
              (push ?L lst) (push lname lst)
-             (push ?N lst) (push "<N>" lst)
+             (push ?N lst) (push name-or-net lst)
              (push ?O lst) (push "<O>" lst)
              (push ?P lst) (push "<P>" lst)
              (push ?Q lst) (push "<Q>" lst)
-             (push ?f lst) (push "<f>" lst)
+             (push ?f lst) (push from lst)
              (push ?i lst) (push "<i>" lst)
-             (push ?n lst) (push name-or-net lst)
+             (push ?n lst) (push net lst)
              (push ?o lst) (push "<o>" lst)
              (push ?q lst) (push "<q>" lst)
              (push ?t lst) (push "<t>" lst)
@@ -3890,8 +4002,10 @@ not have PROP."
        (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)
@@ -3914,7 +4028,7 @@ not have PROP."
                (message-kill-all-overlays)
              (delete-char 1)
              (when (eq choice ?r)
-               (insert "."))))
+               (insert message-replacement-char))))
          (forward-char)
          (skip-chars-forward mm-7bit-chars))))))
 
@@ -4027,7 +4141,8 @@ not have PROP."
              (gnus-setup-posting-charset nil)
            message-posting-charset))
         (headers message-required-mail-headers))
-    (when message-generate-hashcash
+    (when (and message-generate-hashcash
+              (not (eq message-generate-hashcash 'opportunistic)))
       (message "Generating hashcash...")
       ;; Wait for calculations already started to finish...
       (hashcash-wait-async)
@@ -4179,10 +4294,17 @@ 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")
+                       message-sendmail-extra-arguments
                        ;; Always specify who from,
                        ;; since some systems have broken sendmails.
                        ;; But some systems are more broken with -f, so
@@ -5080,14 +5202,14 @@ In posting styles use `(\"Expires\" (make-expires-date 30))'."
           (concat message-user-path "!" login-name))
          (t login-name))))
 
-(defun message-make-from ()
+(defun message-make-from (&optional name address )
   "Make a From header."
   (let* ((style message-from-style)
-        (login (message-make-address))
-        (fullname
-         (or (and (boundp 'user-full-name)
-                  user-full-name)
-             (user-full-name))))
+        (login (or address (message-make-address)))
+        (fullname (or name
+                      (and (boundp 'user-full-name)
+                           user-full-name)
+                      (user-full-name))))
     (when (string= fullname "&")
       (setq fullname (user-login-name)))
     (with-temp-buffer
@@ -5182,8 +5304,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.
@@ -5808,7 +5930,7 @@ between beginning of field and beginning of line."
 
 (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)
@@ -5979,7 +6101,7 @@ OTHER-HEADERS is an alist of header/value pairs."
 
 (defun message-get-reply-headers (wide &optional to-address address-headers)
   (let (follow-to mct never-mct to cc author mft recipients extra)
-    ;; Find all relevant headers we need.
+  ;; Find all relevant headers we need.
     (save-restriction
       (message-narrow-to-headers-or-head)
       ;; Gmane renames "To".  Look at "Original-To", too, if it is present in
@@ -6114,6 +6236,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."
@@ -6141,13 +6296,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))
@@ -6217,11 +6370,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))
@@ -6659,6 +6809,62 @@ Optional DIGEST will use digest to forward."
       (message-forward-make-body-digest-mime forward-buffer)
     (message-forward-make-body-digest-plain forward-buffer)))
 
+(eval-and-compile
+  (autoload 'mm-uu-dissect-text-parts "mm-uu")
+  (autoload 'mm-uu-dissect "mm-uu"))
+
+(defun message-signed-or-encrypted-p (&optional dont-emulate-mime handles)
+  "Say whether the current buffer contains signed or encrypted message.
+If DONT-EMULATE-MIME is nil, this function does the MIME emulation on
+messages that don't conform to PGP/MIME described in RFC2015.  HANDLES
+is for the internal use."
+  (unless handles
+    (let ((mm-decrypt-option 'never)
+         (mm-verify-option 'never))
+      (if (setq handles (mm-dissect-buffer nil t))
+         (unless dont-emulate-mime
+           (mm-uu-dissect-text-parts handles))
+       (unless dont-emulate-mime
+         (setq handles (mm-uu-dissect))))))
+  ;; Check text/plain message in which there is a signed or encrypted
+  ;; body that has been encoded by B or Q.
+  (unless (or handles dont-emulate-mime)
+    (let ((cur (current-buffer))
+         (mm-decrypt-option 'never)
+         (mm-verify-option 'never))
+      (with-temp-buffer
+       (insert-buffer-substring cur)
+       (when (setq handles (mm-dissect-buffer t t))
+         (if (and (prog1
+                      (bufferp (car handles))
+                    (mm-destroy-parts handles))
+                  (equal (mm-handle-media-type handles) "text/plain"))
+             (progn
+               (mm-decode-content-transfer-encoding
+                (mm-handle-encoding handles))
+               (setq handles (mm-uu-dissect)))
+           (setq handles nil))))))
+  (when handles
+    (prog1
+       (catch 'found
+         (dolist (handle (if (stringp (car handles))
+                             (if (member (car handles)
+                                         '("multipart/signed"
+                                           "multipart/encrypted"))
+                                 (throw 'found t)
+                               (cdr handles))
+                           (list handles)))
+           (if (stringp (car handle))
+               (when (message-signed-or-encrypted-p dont-emulate-mime handle)
+                 (throw 'found t))
+             (when (and (bufferp (car handle))
+                        (equal (mm-handle-media-type handle)
+                               "message/rfc822"))
+               (with-current-buffer (mm-handle-buffer handle)
+                 (when (message-signed-or-encrypted-p dont-emulate-mime)
+                   (throw 'found t)))))))
+      (mm-destroy-parts handles))))
+
 ;;;###autoload
 (defun message-forward-make-body (forward-buffer &optional digest)
   ;; Put point where we want it before inserting the forwarded
@@ -6671,11 +6877,13 @@ Optional DIGEST will use digest to forward."
     (if message-forward-as-mime
        (if (and message-forward-show-mml
                 (not (and (eq message-forward-show-mml 'best)
+                          ;; Use the raw form in the body if it contains
+                          ;; signed or encrypted message so as not to be
+                          ;; destroyed by re-encoding.
                           (with-current-buffer forward-buffer
-                            (goto-char (point-min))
-                            (re-search-forward
-                             "Content-Type: *multipart/\\(signed\\|encrypted\\)"
-                             nil t)))))
+                            (condition-case nil
+                                (message-signed-or-encrypted-p)
+                              (error t))))))
            (message-forward-make-body-mml forward-buffer)
          (message-forward-make-body-mime forward-buffer))
       (message-forward-make-body-plain forward-buffer)))