Indent.
[gnus] / lisp / nnmail.el
index 9329646..cb1fb79 100644 (file)
@@ -1,5 +1,6 @@
 ;;; nnmail.el --- mail support functions for the Gnus mail backends
-;; Copyright (C) 1995,96,97,98,99 Free Software Foundation, Inc.
+;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002
+;;        Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; Keywords: news, mail
@@ -36,7 +37,8 @@
 
 (eval-and-compile
   (autoload 'gnus-error "gnus-util")
-  (autoload 'gnus-buffer-live-p "gnus-util"))
+  (autoload 'gnus-buffer-live-p "gnus-util")
+  (autoload 'gnus-add-buffer "gnus"))
 
 (defgroup nnmail nil
   "Reading mail with Gnus."
@@ -55,7 +57,7 @@
   :group 'nnmail)
 
 (defgroup nnmail-split nil
-  "Organizing the incomming mail in folders."
+  "Organizing the incoming mail in folders."
   :group 'nnmail)
 
 (defgroup nnmail-files nil
@@ -75,8 +77,7 @@
   "Various mail options."
   :group 'nnmail)
 
-(defcustom nnmail-split-methods
-  '(("mail.misc" ""))
+(defcustom nnmail-split-methods '(("mail.misc" ""))
   "*Incoming mail will be split according to this variable.
 
 If you'd like, for instance, one mail group for mail from the
@@ -85,8 +86,8 @@ else, you could do something like this:
 
  (setq nnmail-split-methods
        '((\"mail.4ad\" \"From:.*4ad\")
-         (\"mail.junk\" \"From:.*Lars\\\\|Subject:.*buy\")
-         (\"mail.misc\" \"\")))
+        (\"mail.junk\" \"From:.*Lars\\\\|Subject:.*buy\")
+        (\"mail.misc\" \"\")))
 
 As you can see, this variable is a list of lists, where the first
 element in each \"rule\" is the name of the group (which, by the way,
@@ -103,7 +104,8 @@ The last element should always have \"\" as the regexp.
 
 This variable can also have a function as its value."
   :group 'nnmail-split
-  :type '(choice (repeat :tag "Alist" (group (string :tag "Name") regexp))
+  :type '(choice (repeat :tag "Alist" (group (string :tag "Name")
+                                            (choice regexp function)))
                 (function-item nnmail-split-fancy)
                 (function :tag "Other")))
 
@@ -114,6 +116,22 @@ If nil, the first match found will be used."
   :group 'nnmail-split
   :type 'boolean)
 
+(defcustom nnmail-split-fancy-with-parent-ignore-groups nil
+  "Regexp that matches group names to be ignored when applying `nnmail-split-fancy-with-parent'.
+This can also be a list of regexps."
+  :group 'nnmail-split
+  :type '(choice (const :tag "none" nil)
+                (regexp :value ".*")
+                (repeat :value (".*") regexp)))
+
+(defcustom nnmail-cache-ignore-groups nil
+  "Regexp that matches group names to be ignored when inserting message ids into the cache (`nnmail-cache-insert').
+This can also be a list of regexps."
+  :group 'nnmail-split
+  :type '(choice (const :tag "none" nil)
+                (regexp :value ".*")
+                (repeat :value (".*") regexp)))
+
 ;; Added by gord@enci.ucalgary.ca (Gordon Matzigkeit).
 (defcustom nnmail-keep-last-article nil
   "If non-nil, nnmail will never delete/move a group's last article.
@@ -144,7 +162,7 @@ number of days) -- this doesn't have to be an integer.  This variable
 can also be `immediate' and `never'."
   :group 'nnmail-expire
   :type '(choice (const immediate)
-                (integer :tag "days")
+                (number :tag "days")
                 (const never)))
 
 (defcustom nnmail-expiry-wait-function nil
@@ -158,16 +176,64 @@ Eg.:
 
 \(setq nnmail-expiry-wait-function
       (lambda (newsgroup)
-        (cond ((string-match \"private\" newsgroup) 31)
-              ((string-match \"junk\" newsgroup) 1)
+       (cond ((string-match \"private\" newsgroup) 31)
+             ((string-match \"junk\" newsgroup) 1)
              ((string-match \"important\" newsgroup) 'never)
              (t 7))))"
   :group 'nnmail-expire
   :type '(choice (const :tag "nnmail-expiry-wait" nil)
                 (function :format "%v" nnmail-)))
 
+(defcustom nnmail-expiry-target 'delete
+  "*Variable that says where expired messages should end up.
+The default value is `delete' (which says to delete the messages),
+but it can also be a string or a function.  If it is a string, expired
+messages end up in that group.  If it is a function, the function is
+called in a buffer narrowed to the message in question.  The function
+receives one argument, the name of the group the message comes from.
+The return value should be `delete' or a group name (a string)."
+  :version "21.1"
+  :group 'nnmail-expire
+  :type '(choice (const delete)
+                (function :format "%v" nnmail-)
+                string))
+
+(defcustom nnmail-fancy-expiry-targets nil
+  "Determine expiry target based on articles using fancy techniques.
+
+This is a list of (\"HEADER\" \"REGEXP\" \"TARGET\") entries.  If
+`nnmail-expiry-target' is set to the function
+`nnmail-fancy-expiry-target' and HEADER of the article matches REGEXP,
+the message will be expired to a group determined by invoking
+`format-time-string' with TARGET used as the format string and the
+time extracted from the articles' Date header (if missing the current
+time is used).
+
+In the special cases that HEADER is the symbol `to-from', the regexp
+will try to match against both the From and the To header.
+
+Example:
+
+\(setq nnmail-fancy-expiry-targets
+      '((to-from \"boss\" \"nnfolder:Work\")
+       (\"Subject\" \"IMPORTANT\" \"nnfolder:IMPORTANT.%Y.%b\")
+       (\"from\" \".*\" \"nnfolder:Archive-%Y\")))
+
+In this case, articles containing the string \"boss\" in the To or the
+From header will be expired to the group \"nnfolder:Work\";
+articles containing the sting \"IMPORTANT\" in the Subject header will
+be expired to the group \"nnfolder:IMPORTANT.YYYY.MMM\"; and
+everything else will be expired to \"nnfolder:Archive-YYYY\"."
+  :group 'nnmail-expire
+  :type '(repeat (list (choice :tag "Match against"
+                              (string :tag "Header")
+                              (const to-from))
+                      regexp
+                      (string :tag "Target group format string"))))
+
 (defcustom nnmail-cache-accepted-message-ids nil
-  "If non-nil, put Message-IDs of Gcc'd articles into the duplicate cache."
+  "If non-nil, put Message-IDs of Gcc'd articles into the duplicate cache.
+If non-nil, also update the cache when copy or move articles."
   :group 'nnmail
   :type 'boolean)
 
@@ -183,6 +249,13 @@ This variable is obsolete; `mail-sources' should be used instead."
   :group 'nnmail-procmail
   :type 'boolean)
 
+(defcustom nnmail-scan-directory-mail-source-once nil
+  "*If non-nil, scan all incoming procmail sorted mails once.
+It scans low-level sorted spools even when not required."
+  :version "21.1"
+  :group 'nnmail-procmail
+  :type 'boolean)
+
 (defcustom nnmail-delete-file-function 'delete-file
   "Function called to delete files in some mail backends."
   :group 'nnmail-files
@@ -206,7 +279,7 @@ links, you could set this variable to `copy-file' instead."
       '(nnheader-ms-strip-cr)
     nil)
   "*Hook that will be run after the incoming mail has been transferred.
-The incoming mail is moved from `nnmail-spool-file' (which normally is
+The incoming mail is moved from the specified spool file (which normally is
 something like \"/usr/spool/mail/$user\") to the user's home
 directory.  This hook is called after the incoming mail box has been
 emptied, and can be used to call any mail box programs you have
@@ -215,9 +288,9 @@ running (\"xwatch\", etc.)
 Eg.
 
 \(add-hook 'nnmail-read-incoming-hook
-          (lambda ()
-            (start-process \"mailsend\" nil
-                           \"/local/bin/mailsend\" \"read\" \"mbox\")))
+         (lambda ()
+           (call-process \"/local/bin/mailsend\" nil nil nil
+                         \"read\" nnmail-spool-file)))
 
 If you have xwatch running, this will alert it that mail has been
 read.
@@ -278,11 +351,12 @@ discarded after running the split process."
   :type 'hook)
 
 (defcustom nnmail-large-newsgroup 50
-  "*The number of the articles which indicates a large newsgroup.
+  "*The number of the articles which indicates a large newsgroup or nil.
 If the number of the articles is greater than the value, verbose
 messages will be shown to indicate the current status."
   :group 'nnmail-various
-  :type 'integer)
+  :type '(choice (const :tag "infinite" nil)
+                 (number :tag "count")))
 
 (defcustom nnmail-split-fancy "mail.misc"
   "Incoming mail can be split according to this fancy variable.
@@ -294,9 +368,9 @@ the following:
 GROUP: Mail will be stored in GROUP (a string).
 
 \(FIELD VALUE [- RESTRICT [- RESTRICT [...]]] SPLIT): If the message
-  field FIELD (a regexp) contains VALUE (a regexp), store the messages 
+  field FIELD (a regexp) contains VALUE (a regexp), store the messages
   as specified by SPLIT.  If RESTRICT (a regexp) matches some string
-  after FIELD and before the end of the matched VALUE, return NIL,
+  after FIELD and before the end of the matched VALUE, return nil,
   otherwise process SPLIT.  Multiple RESTRICTs add up, further
   restricting the possibility of processing SPLIT.
 
@@ -314,6 +388,12 @@ GROUP: Mail will be stored in GROUP (a string).
   return value FUNCTION should be a split, which is then recursively
   processed.
 
+junk: Mail will be deleted.  Use with care!  Do not submerge in water!
+  Example:
+  (setq nnmail-split-fancy
+       '(| (\"Subject\" \"MAKE MONEY FAST\" junk)
+           ...other.rules.omitted...))
+
 FIELD must match a complete field name.  VALUE must match a complete
 word according to the `nnmail-split-fancy-syntax-table' syntax table.
 You can use \".*\" in the regexps to match partial field names or words.
@@ -341,13 +421,13 @@ Example:
             ;; Other mailing lists...
             (any \"procmail@informatik\\\\.rwth-aachen\\\\.de\" \"procmail.list\")
             (any \"SmartList@informatik\\\\.rwth-aachen\\\\.de\" \"SmartList.list\")
-             ;; Both lists below have the same suffix, so prevent
-             ;; cross-posting to mkpkg.list of messages posted only to 
-             ;; the bugs- list, but allow cross-posting when the
-             ;; message was really cross-posted.
-             (any \"bugs-mypackage@somewhere\" \"mypkg.bugs\")
-             (any \"mypackage@somewhere\" - \"bugs-mypackage\" \"mypkg.list\")
-             ;; 
+            ;; Both lists below have the same suffix, so prevent
+            ;; cross-posting to mkpkg.list of messages posted only to
+            ;; the bugs- list, but allow cross-posting when the
+            ;; message was really cross-posted.
+            (any \"bugs-mypackage@somewhere\" \"mypkg.bugs\")
+            (any \"mypackage@somewhere\" - \"bugs-mypackage\" \"mypkg.list\")
+            ;;
             ;; People...
             (any \"larsi@ifi\\\\.uio\\\\.no\" \"people.Lars Magne Ingebrigtsen\"))
          ;; Unmatched mail goes to the catch all group.
@@ -396,18 +476,33 @@ parameter.  It should return nil, `warn' or `delete'."
                 (const warn)
                 (const delete)))
 
-(defcustom nnmail-extra-headers nil
+(defcustom nnmail-extra-headers '(To Newsgroups)
   "*Extra headers to parse."
+  :version "21.1"
   :group 'nnmail
   :type '(repeat symbol))
 
 (defcustom nnmail-split-header-length-limit 512
   "Header lines longer than this limit are excluded from the split function."
+  :version "21.1"
   :group 'nnmail
   :type 'integer)
 
+(defcustom nnmail-mail-splitting-charset nil
+  "Default charset to be used when splitting incoming mail."
+  :group 'nnmail
+  :type 'symbol)
+
+(defcustom nnmail-mail-splitting-decodes nil
+  "Whether the nnmail splitting functionality should MIME decode headers."
+  :group 'nnmail
+  :type 'boolean)
+
 ;;; Internal variables.
 
+(defvar nnmail-article-buffer " *nnmail incoming*"
+  "The buffer used for splitting incoming mails.")
+
 (defvar nnmail-split-history nil
   "List of group/article elements that say where the previous split put messages.")
 
@@ -442,25 +537,25 @@ parameter.  It should return nil, `warn' or `delete'."
   mm-text-coding-system
   "Coding system used in reading inbox")
 
-(defvar nnmail-pathname-coding-system 'binary
-  "*Coding system for pathname.")
+(defvar nnmail-pathname-coding-system nil
+  "*Coding system for file name.")
 
 (defun nnmail-find-file (file)
   "Insert FILE in server buffer safely."
   (set-buffer nntp-server-buffer)
   (delete-region (point-min) (point-max))
   (let ((format-alist nil)
-        (after-insert-file-functions nil))
+       (after-insert-file-functions nil))
     (condition-case ()
        (let ((coding-system-for-read nnmail-file-coding-system)
              (auto-mode-alist (mm-auto-mode-alist))
-             (pathname-coding-system nnmail-pathname-coding-system))
+             (file-name-coding-system nnmail-pathname-coding-system))
          (insert-file-contents file)
          t)
       (file-error nil))))
 
 (defun nnmail-group-pathname (group dir &optional file)
-  "Make pathname for GROUP."
+  "Make file name for GROUP."
   (concat
    (let ((dir (file-name-as-directory (expand-file-name dir))))
      (setq group (nnheader-replace-duplicate-chars-in-string
@@ -468,32 +563,50 @@ parameter.  It should return nil, `warn' or `delete'."
                  ?. ?_))
      (setq group (nnheader-translate-file-chars group))
      ;; If this directory exists, we use it directly.
-     (if (or nnmail-use-long-file-names
-            (file-directory-p (concat dir group)))
-        (concat dir group "/")
-       ;; If not, we translate dots into slashes.
-       (concat dir
-              (mm-encode-coding-string
-               (nnheader-replace-chars-in-string group ?. ?/)
-               nnmail-pathname-coding-system)
-              "/")))
+     (file-name-as-directory
+      (if (or nnmail-use-long-file-names
+             (file-directory-p (concat dir group)))
+         (expand-file-name group dir)
+       ;; If not, we translate dots into slashes.
+       (expand-file-name
+        (mm-encode-coding-string
+         (nnheader-replace-chars-in-string group ?. ?/)
+         nnmail-pathname-coding-system)
+        dir))))
    (or file "")))
 
 (defun nnmail-get-active ()
   "Returns an assoc of group names and active ranges.
 nn*-request-list should have been called before calling this function."
-  (let (group-assoc)
-    ;; Go through all groups from the active list.
-    (save-excursion
-      (set-buffer nntp-server-buffer)
-      (goto-char (point-min))
-      (while (re-search-forward
-             "^\\([^ \t]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+\\)" nil t)
-       ;; We create an alist with `(GROUP (LOW . HIGH))' elements.
-       (push (list (match-string 1)
-                   (cons (string-to-int (match-string 3))
-                         (string-to-int (match-string 2))))
-             group-assoc)))
+  ;; Go through all groups from the active list.
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (nnmail-parse-active)))
+
+(defun nnmail-parse-active ()
+  "Parse the active file in the current buffer and return an alist."
+  (goto-char (point-min))
+  (unless (re-search-forward "[\\\"]" nil t)
+    (goto-char (point-max))
+    (while (re-search-backward "[][';?()#]" nil t)
+      (insert ?\\)))
+  (goto-char (point-min))
+  (let ((buffer (current-buffer))
+       group-assoc group max min)
+    (while (not (eobp))
+      (condition-case err
+         (progn
+           (narrow-to-region (point) (gnus-point-at-eol))
+           (setq group (read buffer))
+           (unless (stringp group)
+             (setq group (symbol-name group)))
+           (if (and (numberp (setq max (read buffer)))
+                    (numberp (setq min (read buffer))))
+               (push (list group (cons min max))
+                     group-assoc)))
+       (error nil))
+      (widen)
+      (forward-line 1))
     group-assoc))
 
 (defvar nnmail-active-file-coding-system 'raw-text
@@ -511,8 +624,11 @@ nn*-request-list should have been called before calling this function."
   (erase-buffer)
   (let (group)
     (while (setq group (pop alist))
-      (insert (format "%s %d %d y\n" (car group) (cdadr group)
-                     (caadr group))))))
+      (insert (format "%S %d %d y\n" (intern (car group)) (cdadr group)
+                     (caadr group))))
+    (goto-char (point-max))
+    (while (search-backward "\\." nil t)
+      (delete-char 1))))
 
 (defun nnmail-get-split-group (file source)
   "Find out whether this FILE is to be split into GROUP only.
@@ -606,7 +722,7 @@ If SOURCE is a directory spec, try to return the group name component."
 
 (defsubst nnmail-search-unix-mail-delim ()
   "Put point at the beginning of the next Unix mbox message."
-  ;; Algorithm used to find the the next article in the
+  ;; Algorithm used to find the next article in the
   ;; brain-dead Unix mbox format:
   ;;
   ;; 1) Search for "^From ".
@@ -635,7 +751,7 @@ If SOURCE is a directory spec, try to return the group name component."
 
 (defun nnmail-search-unix-mail-delim-backward ()
   "Put point at the beginning of the current Unix mbox message."
-  ;; Algorithm used to find the the next article in the
+  ;; Algorithm used to find the next article in the
   ;; brain-dead Unix mbox format:
   ;;
   ;; 1) Search for "^From ".
@@ -670,7 +786,9 @@ If SOURCE is a directory spec, try to return the group name component."
     (if (not (and (re-search-forward "^From " nil t)
                  (goto-char (match-beginning 0))))
        ;; Possibly wrong format?
-       (error "Error, unknown mail format! (Possibly corrupted.)")
+       (error "Error, unknown mail format! (Possibly corrupted %s `%s'.)"
+              (if (buffer-file-name) "file" "buffer")
+              (or (buffer-file-name) (buffer-name)))
       ;; Carry on until the bitter end.
       (while (not (eobp))
        (setq start (point)
@@ -715,7 +833,7 @@ If SOURCE is a directory spec, try to return the group name component."
        (setq head-end (point))
        ;; We try the Content-Length value.  The idea: skip over the header
        ;; separator, then check what happens content-length bytes into the
-       ;; message body.  This should be either the end ot the buffer, the
+       ;; message body.  This should be either the end of the buffer, the
        ;; message separator or a blank line followed by the separator.
        ;; The blank line should probably be deleted.  If neither of the
        ;; three is met, the content-length header is probably invalid.
@@ -767,8 +885,8 @@ If SOURCE is a directory spec, try to return the group name component."
         start
         (if (search-forward "\n\n" nil t)
             (1- (point))
-          ;; This will never happen, but just to be on the safe side --
-          ;; if there is no head-body delimiter, we search a bit manually.
+        ;; This will never happen, but just to be on the safe side --
+      ;; if there is no head-body delimiter, we search a bit manually.
           (while (and (looking-at "From \\|[^ \t]+:")
                       (not (eobp)))
             (forward-line 1))
@@ -814,7 +932,7 @@ If SOURCE is a directory spec, try to return the group name component."
      (if (search-forward "\n\n" nil t)
         (1- (point))
        ;; This will never happen, but just to be on the safe side --
-       ;; if there is no head-body delimiter, we search a bit manually.
+      ;; if there is no head-body delimiter, we search a bit manually.
        (while (and (looking-at "From \\|[^ \t]+:")
                   (not (eobp)))
         (forward-line 1))
@@ -842,7 +960,7 @@ If SOURCE is a directory spec, try to return the group name component."
                                       group artnum-func)
   "Go through the entire INCOMING file and pick out each individual mail.
 FUNC will be called with the buffer narrowed to each mail."
-  (let (;; If this is a group-specific split, we bind the split
+  (let ( ;; If this is a group-specific split, we bind the split
        ;; methods to just this group.
        (nnmail-split-methods (if (and group
                                       (not nnmail-resplit-incoming))
@@ -850,7 +968,7 @@ FUNC will be called with the buffer narrowed to each mail."
                                nnmail-split-methods)))
     (save-excursion
       ;; Insert the incoming file.
-      (set-buffer (get-buffer-create " *nnmail incoming*"))
+      (set-buffer (get-buffer-create nnmail-article-buffer))
       (erase-buffer)
       (let ((coding-system-for-read nnmail-incoming-coding-system))
        (mm-insert-file-contents incoming))
@@ -878,7 +996,7 @@ FUNC will be called with the buffer narrowed to each mail."
 (defun nnmail-article-group (func &optional trace)
   "Look at the headers and return an alist of groups that match.
 FUNC will be called with the group name to determine the article number."
-  (let ((methods nnmail-split-methods)
+  (let ((methods (or nnmail-split-methods '(("bogus" ""))))
        (obuf (current-buffer))
        (beg (point-min))
        end group-art method grp)
@@ -897,6 +1015,10 @@ FUNC will be called with the group name to determine the article number."
        (erase-buffer)
        ;; Copy the headers into the work buffer.
        (insert-buffer-substring obuf beg end)
+       ;; Decode MIME headers and charsets.
+       (when nnmail-mail-splitting-decodes
+         (let ((mail-parse-charset nnmail-mail-splitting-charset))
+           (mail-decode-encoded-word-region (point-min) (point-max))))
        ;; Fold continuation lines.
        (goto-char (point-min))
        (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
@@ -926,8 +1048,8 @@ FUNC will be called with the group name to determine the article number."
                       (or (funcall nnmail-split-methods)
                           '("bogus"))
                     (error
-                     (nnheader-message 5
-                                       "Error in `nnmail-split-methods'; using `bogus' mail group")
+                     (nnheader-message
+                      5 "Error in `nnmail-split-methods'; using `bogus' mail group")
                      (sit-for 1)
                      '("bogus")))))
              (setq split (gnus-remove-duplicates split))
@@ -975,13 +1097,12 @@ FUNC will be called with the group name to determine the article number."
                                  (funcall func (car method)))))))))
        ;; Produce a trace if non-empty.
        (when (and trace nnmail-split-trace)
-         (let ((trace (nreverse nnmail-split-trace))
-               (restore (current-buffer)))
+         (let ((restore (current-buffer)))
            (nnheader-set-temp-buffer "*Split Trace*")
            (gnus-add-buffer)
-           (while trace
-             (insert (car trace) "\n")
-             (setq trace (cdr trace)))
+           (dolist (trace (nreverse nnmail-split-trace))
+             (prin1 trace (current-buffer))
+             (insert "\n"))
            (goto-char (point-min))
            (gnus-configure-windows 'split-trace)
            (set-buffer restore)))
@@ -1002,7 +1123,7 @@ Return the number of characters in the body."
   (let (lines chars)
     (save-excursion
       (goto-char (point-min))
-      (unless (search-forward "\n\n" nil t) 
+      (unless (search-forward "\n\n" nil t)
        (goto-char (point-max))
        (insert "\n"))
       (setq chars (- (point-max) (point)))
@@ -1046,14 +1167,20 @@ Return the number of characters in the body."
 
 (defun nnmail-remove-list-identifiers ()
   "Remove list identifiers from Subject headers."
-  (let ((regexp (if (stringp nnmail-list-identifiers) nnmail-list-identifiers
-                 (mapconcat 'identity nnmail-list-identifiers " *\\|"))))
+  (let ((regexp
+        (if (consp nnmail-list-identifiers)
+            (mapconcat 'identity nnmail-list-identifiers " *\\|")
+          nnmail-list-identifiers)))
     (when regexp
       (goto-char (point-min))
-      (when (re-search-forward
-            (concat "^Subject: +\\(Re: +\\)?\\(" regexp " *\\)")
-            nil t)
-       (delete-region (match-beginning 2) (match-end 0))))))
+      (while (re-search-forward
+             (concat "^Subject: +\\(R[Ee]: +\\)*\\(" regexp " *\\)")
+             nil t)
+       (delete-region (match-beginning 2) (match-end 0))
+       (beginning-of-line))
+      (when (re-search-forward "^Subject: +\\(\\(R[Ee]: +\\)+\\)R[Ee]: +" nil t)
+       (delete-region (match-beginning 1) (match-end 1))
+       (beginning-of-line)))))
 
 (defun nnmail-remove-tabs ()
   "Translate TAB characters into SPACE characters."
@@ -1066,16 +1193,41 @@ Return the number of characters in the body."
     (goto-char (point-min))
     (when (re-search-forward "^References:" nil t)
       (beginning-of-line)
-      (insert "X-Gnus-Broken-Eudora-"))))
+      (insert "X-Gnus-Broken-Eudora-"))
+    (goto-char (point-min))
+    (when (re-search-forward "^\\(In-Reply-To:[^\n]+\\)\n[ \t]+" nil t)
+      (replace-match "\\1" t))))
 
 (custom-add-option 'nnmail-prepare-incoming-header-hook
                   'nnmail-fix-eudora-headers)
 
 ;;; Utility functions
 
+(defun nnmail-do-request-post (accept-func &optional server)
+  "Utility function to directly post a message to an nnmail-derived group.
+Calls ACCEPT-FUNC (which should be `nnchoke-request-accept-article')
+to actually put the message in the right group."
+  (let ((success t))
+    (dolist (mbx (message-unquote-tokens
+                 (message-tokenize-header
+                  (message-fetch-field "Newsgroups") ", ")) success)
+      (let ((to-newsgroup (gnus-group-prefixed-name mbx gnus-command-method)))
+       (or (gnus-active to-newsgroup)
+           (gnus-activate-group to-newsgroup)
+           (if (gnus-y-or-n-p (format "No such group: %s.  Create it? "
+                                      to-newsgroup))
+               (or (and (gnus-request-create-group
+                         to-newsgroup gnus-command-method)
+                        (gnus-activate-group to-newsgroup nil nil
+                                             gnus-command-method))
+                   (error "Couldn't create group %s" to-newsgroup)))
+           (error "No such group: %s" to-newsgroup))
+       (unless (funcall accept-func mbx (nth 1 gnus-command-method))
+         (setq success nil))))))
+
 (defun nnmail-split-fancy ()
   "Fancy splitting method.
-See the documentation for the variable `nnmail-split-fancy' for documentation."
+See the documentation for the variable `nnmail-split-fancy' for details."
   (let ((syntab (syntax-table)))
     (unwind-protect
        (progn
@@ -1097,7 +1249,7 @@ See the documentation for the variable `nnmail-split-fancy' for documentation."
      ;; A group name.  Do the \& and \N subs into the string.
      ((stringp split)
       (when nnmail-split-tracing
-       (push (format "\"%s\"" split) nnmail-split-trace))
+       (push split nnmail-split-trace))
       (list (nnmail-expand-newtext split)))
 
      ;; Junk the message.
@@ -1120,6 +1272,8 @@ See the documentation for the variable `nnmail-split-fancy' for documentation."
 
      ;; Builtin : operation.
      ((eq (car split) ':)
+      (when nnmail-split-tracing
+       (push split nnmail-split-trace))
       (nnmail-split-it (save-excursion (eval (cdr split)))))
 
      ;; Builtin ! operation.
@@ -1136,13 +1290,13 @@ See the documentation for the variable `nnmail-split-fancy' for documentation."
        (while (and (goto-char end-point)
                    (re-search-backward (cdr cached-pair) nil t))
          (when nnmail-split-tracing
-           (push (cdr cached-pair) nnmail-split-trace))
+           (push split nnmail-split-trace))
          (let ((split-rest (cddr split))
                (end (match-end 0))
-               ;; The searched regexp is \(\(FIELD\).*\)\(VALUE\).  So,
-               ;; start-of-value is the the point just before the
-               ;; beginning of the value, whereas after-header-name is
-               ;; the point just after the field name.
+               ;; The searched regexp is \(\(FIELD\).*\)\(VALUE\).
+               ;; So, start-of-value is the point just before the
+               ;; beginning of the value, whereas after-header-name
+               ;; is the point just after the field name.
                (start-of-value (match-end 1))
                (after-header-name (match-end 2)))
            ;; Start the next search just before the beginning of the
@@ -1178,25 +1332,32 @@ See the documentation for the variable `nnmail-split-fancy' for documentation."
      (t
       (let* ((field (nth 0 split))
             (value (nth 1 split))
-            partial regexp)
+            partial-front regexp
+            partial-rear  regexp)
        (if (symbolp value)
            (setq value (cdr (assq value nnmail-split-abbrev-alist))))
        (if (and (>= (length value) 2)
                 (string= ".*" (substring value 0 2)))
            (setq value (substring value 2)
-                 partial ""))
+                 partial-front ""))
+       ;; Same trick for the rear of the regexp
+       (if (and (>= (length value) 2)
+                (string= ".*" (substring value -2)))
+           (setq value (substring value 0 -2)
+                 partial-rear ""))
        (setq regexp (concat "^\\(\\("
                             (if (symbolp field)
                                 (cdr (assq field nnmail-split-abbrev-alist))
                               field)
                             "\\):.*\\)"
-                            (or partial "\\<")
+                            (or partial-front "\\<")
                             "\\("
                             value
-                            "\\)\\>"))
+                            "\\)"
+                            (or partial-rear "\\>")))
        (push (cons split regexp) nnmail-split-cache)
        ;; Now that it's in the cache, just call nnmail-split-it again
-       ;; on the same split, which will find it immediately in the cache.
+    ;; on the same split, which will find it immediately in the cache.
        (nnmail-split-it split))))))
 
 (defun nnmail-expand-newtext (newtext)
@@ -1281,6 +1442,7 @@ See the documentation for the variable `nnmail-split-fancy' for documentation."
       (set-buffer
        (setq nnmail-cache-buffer
             (get-buffer-create " *nnmail message-id cache*")))
+      (gnus-add-buffer)
       (when (file-exists-p nnmail-message-id-cache-file)
        (nnheader-insert-file-contents nnmail-message-id-cache-file))
       (set-buffer-modified-p nil)
@@ -1309,14 +1471,93 @@ See the documentation for the variable `nnmail-split-fancy' for documentation."
       (setq nnmail-cache-buffer nil)
       (kill-buffer (current-buffer)))))
 
-(defun nnmail-cache-insert (id)
+;; Compiler directives.
+(defvar group)
+(defvar group-art-list)
+(defvar group-art)
+(defun nnmail-cache-insert (id grp)
   (when nnmail-treat-duplicates
+    ;; Store some information about the group this message is written
+    ;; to.  This is passed in as the grp argument -- all locations this
+    ;; has been called from have been checked and the group is available.
+    ;; The only ambiguous case is nnmail-check-duplication which will only
+    ;; pass the first (of possibly >1) group which matches. -Josh
     (unless (gnus-buffer-live-p nnmail-cache-buffer)
       (nnmail-cache-open))
     (save-excursion
       (set-buffer nnmail-cache-buffer)
       (goto-char (point-max))
-      (insert id "\n"))))
+      (if (and grp (not (string= "" grp))
+              (gnus-methods-equal-p gnus-command-method
+                                    (nnmail-cache-primary-mail-backend)))
+         (let ((regexp (if (consp nnmail-cache-ignore-groups)
+                           (mapconcat 'identity nnmail-cache-ignore-groups
+                                      "\\|")
+                         nnmail-cache-ignore-groups)))
+           (unless (and regexp (string-match regexp grp))
+             (insert id "\t" grp "\n")))
+       (insert id "\n")))))
+
+(defun nnmail-cache-primary-mail-backend ()
+  (let ((be-list (cons gnus-select-method gnus-secondary-select-methods))
+       (be nil)
+       (res nil))
+    (while (and (null res) be-list)
+      (setq be (car be-list))
+      (setq be-list (cdr be-list))
+      (when (and (gnus-method-option-p be 'respool)
+                (eval (intern (format "%s-get-new-mail" (car be)))))
+       (setq res be)))
+    res))
+
+;; Fetch the group name corresponding to the message id stored in the
+;; cache.
+(defun nnmail-cache-fetch-group (id)
+  (when (and nnmail-treat-duplicates nnmail-cache-buffer)
+    (save-excursion
+      (set-buffer nnmail-cache-buffer)
+      (goto-char (point-max))
+      (when (search-backward id nil t)
+       (beginning-of-line)
+       (skip-chars-forward "^\n\r\t")
+       (unless (looking-at "[\r\n]")
+         (forward-char 1)
+         (buffer-substring (point)
+                           (progn (end-of-line) (point))))))))
+
+;; Function for nnmail-split-fancy: look up all references in the
+;; cache and if a match is found, return that group.
+(defun nnmail-split-fancy-with-parent ()
+  "Split this message into the same group as its parent.
+This function can be used as an entry in `nnmail-split-fancy', for
+example like this: (: nnmail-split-fancy)
+For a message to be split, it looks for the parent message in the
+References or In-Reply-To header and then looks in the message id
+cache file (given by the variable `nnmail-message-id-cache-file') to
+see which group that message was put in.  This group is returned.
+
+See the Info node `(gnus)Fancy Mail Splitting' for more details."
+  (let* ((refstr (or (message-fetch-field "references")
+                    (message-fetch-field "in-reply-to")))
+        (references nil)
+        (res nil)
+        (regexp (if (consp nnmail-split-fancy-with-parent-ignore-groups)
+                    (mapconcat
+                     (lambda (x) (format "\\(%s\\)" x))
+                     nnmail-split-fancy-with-parent-ignore-groups
+                     "\\|")
+                  nnmail-split-fancy-with-parent-ignore-groups)))
+    (when refstr
+      (setq references (nreverse (gnus-split-references refstr)))
+      (unless (gnus-buffer-live-p nnmail-cache-buffer)
+       (nnmail-cache-open))
+      (mapcar (lambda (x)
+               (setq res (or (nnmail-cache-fetch-group x) res))
+               (when (or (member res '("delayed" "drafts" "queue"))
+                         (and regexp res (string-match regexp res)))
+                 (setq res nil)))
+             references)
+      res)))
 
 (defun nnmail-cache-id-exists-p (id)
   (when nnmail-treat-duplicates
@@ -1357,7 +1598,7 @@ See the documentation for the variable `nnmail-split-fancy' for documentation."
      ((not duplication)
       (funcall func (setq group-art
                          (nreverse (nnmail-article-group artnum-func))))
-      (nnmail-cache-insert message-id))
+      (nnmail-cache-insert message-id (caar group-art)))
      ((eq action 'delete)
       (setq group-art nil))
      ((eq action 'warn)
@@ -1400,7 +1641,7 @@ See the documentation for the variable `nnmail-split-fancy' for documentation."
         (total 0)
         incoming incomings source)
     (when (and (nnmail-get-value "%s-get-new-mail" method)
-              nnmail-spool-file)
+              sources)
       (while (setq source (pop sources))
        ;; Be compatible with old values.
        (cond
@@ -1418,17 +1659,17 @@ See the documentation for the variable `nnmail-split-fancy' for documentation."
          nil))
        ;; Hack to only fetch the contents of a single group's spool file.
        (when (and (eq (car source) 'directory)
+                  (null nnmail-scan-directory-mail-source-once)
                   group)
          (mail-source-bind (directory source)
            (setq source (append source
                                 (list
                                  :predicate
-                                 `(lambda (file)
-                                    (string-match
-                                     ,(concat
-                                       (regexp-quote (concat group suffix))
-                                       "$")
-                                     file)))))))
+                                 (gnus-byte-compile
+                                  `(lambda (file)
+                                     (string-equal
+                                      ,(concat group suffix)
+                                      (file-name-nondirectory file)))))))))
        (when nnmail-fetched-sources
          (if (member source nnmail-fetched-sources)
              (setq source nil)
@@ -1449,12 +1690,15 @@ See the documentation for the variable `nnmail-split-fancy' for documentation."
        (when (setq new
                    (mail-source-fetch
                     source
-                    `(lambda (file orig-file)
-                       (nnmail-split-incoming
-                        file ',(intern (format "%s-save-mail" method))
-                        ',spool-func
-                        (nnmail-get-split-group orig-file source)
-                        ',(intern (format "%s-active-number" method))))))
+                    (gnus-byte-compile
+                     `(lambda (file orig-file)
+                        (nnmail-split-incoming
+                         file ',(intern (format "%s-save-mail" method))
+                         ',spool-func
+                         (if (equal file orig-file)
+                             nil
+                           (nnmail-get-split-group orig-file ',source))
+                         ',(intern (format "%s-active-number" method)))))))
          (incf total new)
          (incf i)))
       ;; If we did indeed read any incoming spools, we save all info.
@@ -1490,13 +1734,55 @@ See the documentation for the variable `nnmail-split-fancy' for documentation."
             ;; We expire all articles on sight.
             t)
            ((equal time '(0 0))
-            ;; This is an ange-ftp group, and we don't have any dates.
+           ;; This is an ange-ftp group, and we don't have any dates.
             nil)
            ((numberp days)
             (setq days (days-to-time days))
             ;; Compare the time with the current time.
             (ignore-errors (time-less-p days (time-since time))))))))
 
+(defun nnmail-expiry-target-group (target group)
+  ;; Do not invoke this from nntp-server-buffer!  At least nnfolder clears
+  ;; that buffer if the nnfolder group isn't selected.
+  (let (nnmail-cache-accepted-message-ids)
+    ;; Don't enter Message-IDs into cache.
+    ;; Let users hack it in TARGET function.
+    (when (nnheader-functionp target)
+      (setq target (funcall target group)))
+    (unless (eq target 'delete)
+      (when (or (gnus-request-group target)
+               (gnus-request-create-group target))
+       (let ((group-art (gnus-request-accept-article target nil nil t)))
+         (when (consp group-art)
+           (gnus-group-mark-article-read target (cdr group-art))))))))
+
+(defun nnmail-fancy-expiry-target (group)
+  "Returns a target expiry group determined by `nnmail-fancy-expiry-targets'."
+  (let* (header
+        (case-fold-search nil)
+        (from (or (message-fetch-field "from") ""))
+        (to (or (message-fetch-field "to") ""))
+        (date (date-to-time
+               (or (message-fetch-field "date") (current-time-string))))
+        (target 'delete))
+    (dolist (regexp-target-pair (reverse nnmail-fancy-expiry-targets) target)
+      (setq header (car regexp-target-pair))
+      (cond
+       ;; If the header is to-from then match against the
+       ;; To or From header
+       ((and (equal header 'to-from)
+            (or (string-match (cadr regexp-target-pair) from)
+                (and (string-match message-dont-reply-to-names from)
+                     (string-match (cadr regexp-target-pair) to))))
+       (setq target (format-time-string (caddr regexp-target-pair) date)))
+       ((and (not (equal header 'to-from))
+            (string-match (cadr regexp-target-pair)
+                          (or
+                           (message-fetch-field header)
+                           "")))
+       (setq target
+             (format-time-string (caddr regexp-target-pair) date)))))))
+
 (defun nnmail-check-syntax ()
   "Check (and modify) the syntax of the message in the current buffer."
   (save-restriction
@@ -1508,7 +1794,7 @@ See the documentation for the variable `nnmail-split-fancy' for documentation."
 (defun nnmail-write-region (start end filename &optional append visit lockname)
   "Do a `write-region', and then set the file modes."
   (let ((coding-system-for-write nnmail-file-coding-system)
-       (pathname-coding-system nnmail-pathname-coding-system))
+       (file-name-coding-system nnmail-pathname-coding-system))
     (write-region start end filename append visit lockname)
     (set-file-modes filename nnmail-default-file-modes)))
 
@@ -1577,6 +1863,8 @@ See the documentation for the variable `nnmail-split-fancy' for documentation."
   (unless nnmail-split-history
     (error "No current split history"))
   (with-output-to-temp-buffer "*nnmail split history*"
+    (with-current-buffer standard-output
+      (fundamental-mode))              ; for Emacs 20.4+
     (let ((history nnmail-split-history)
          elem)
       (while (setq elem (pop history))