;;; nnmail.el --- mail support functions for the Gnus mail backends
-;; Copyright (C) 1995-2000 Free Software Foundation, Inc.
+
+;; Copyright (C) 1995-2011 Free Software Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
;; Keywords: news, mail
;; This file is part of GNU Emacs.
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
-;; any later version.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING. If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
+;; For Emacs <22.2 and XEmacs.
+(eval-and-compile
+ (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
+
(eval-when-compile (require 'cl))
+(require 'gnus) ; for macro gnus-kill-buffer, at least
(require 'nnheader)
(require 'message)
-(require 'custom)
(require 'gnus-util)
(require 'mail-source)
(require 'mm-util)
+(require 'gnus-int)
-(eval-and-compile
- (autoload 'gnus-error "gnus-util")
- (autoload 'gnus-buffer-live-p "gnus-util"))
+(autoload 'gnus-add-buffer "gnus")
+(autoload 'gnus-kill-buffer "gnus")
(defgroup nnmail nil
"Reading mail with Gnus."
:group 'nnmail)
(defgroup nnmail-prepare nil
- "Preparing (or mangling) new mail after retrival."
+ "Preparing (or mangling) new mail after retrieval."
:group 'nnmail)
(defgroup nnmail-duplicate nil
:group 'nnmail)
(defgroup nnmail-split nil
- "Organizing the incomming mail in folders."
+ "Organizing the incoming mail in folders."
:group 'nnmail)
(defgroup nnmail-files nil
"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
(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,
The last element should always have \"\" as the regexp.
-This variable can also have a function as its value."
+This variable can also have a function as its value, and it can
+also have a fancy split method as its value. See
+`nnmail-split-fancy' for an explanation of that syntax."
: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")))
: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."
+ :version "22.1"
+ :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."
+ :version "22.1"
+ :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.
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
"Variable that holds function to specify how old articles should be before they are expired.
- The function will be called with the name of the group that the
-expiry is to be performed in, and it should return an integer that
-says how many days an article can be stored before it is considered
-\"old\". It can also return the values `never' and `immediate'.
+The function will be called with the name of the group that the expiry
+is to be performed in, and it should return an integer that says how
+many days an article can be stored before it is considered \"old\".
+It can also return the values `never' and `immediate'.
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
(function :format "%v" nnmail-)))
(defcustom nnmail-expiry-target 'delete
- "*Variable that says where expired messages should end up."
- :group 'nnmail-expire
- :type '(choice (const delete)
- (function :format "%v" nnmail-)
- string))
+ "*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
+ 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 string \"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\"."
+ :version "22.1"
+ :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)
-(defcustom nnmail-spool-file '((file))
- "*Where the mail backends will look for incoming mail.
-This variable is a list of mail source specifiers.
-This variable is obsolete; `mail-sources' should be used instead."
- :group 'nnmail-files
- :type 'sexp)
+(make-obsolete-variable 'nnmail-spool-file 'mail-sources
+ "Gnus 5.9 (Emacs 22.1)")
+;; revision 5.29 / p0-85 / Gnus 5.9
+;; Variable removed in No Gnus v0.7
(defcustom nnmail-resplit-incoming nil
"*If non-nil, re-split incoming procmail sorted mail."
: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
:type 'function)
(defcustom nnmail-crosspost-link-function
- (if (string-match "windows-nt\\|emx" (symbol-name system-type))
+ (if (string-match "windows-nt" (symbol-name system-type))
'copy-file
'add-name-to-file)
"*Function called to create a copy of a file.
'(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
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\"
+ ;; The incoming mail box file.
+ (expand-file-name (user-login-name)
+ rmail-spool-directory))))
If you have xwatch running, this will alert it that mail has been
read.
:group 'nnmail-split
:type 'hook)
+(defcustom nnmail-spool-hook nil
+ "*A hook called when a new article is spooled."
+ :version "22.1"
+ :group 'nnmail
+ :type 'hook)
+
(defcustom nnmail-large-newsgroup 50
- "*The number of the articles which indicates a large newsgroup.
-If the number of the articles is greater than the value, verbose
+ "*The number of articles which indicates a large newsgroup or nil.
+If the number of 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")))
+
+(define-widget 'nnmail-lazy 'default
+ "Base widget for recursive datastructures.
+
+This is copy of the `lazy' widget in Emacs 22.1 provided for compatibility."
+ :format "%{%t%}: %v"
+ :convert-widget 'widget-value-convert-widget
+ :value-create (lambda (widget)
+ (let ((value (widget-get widget :value))
+ (type (widget-get widget :type)))
+ (widget-put widget :children
+ (list (widget-create-child-value
+ widget (widget-convert type) value)))))
+ :value-delete 'widget-children-value-delete
+ :value-get (lambda (widget)
+ (widget-value (car (widget-get widget :children))))
+ :value-inline (lambda (widget)
+ (widget-apply (car (widget-get widget :children))
+ :value-inline))
+ :default-get (lambda (widget)
+ (widget-default-get
+ (widget-convert (widget-get widget :type))))
+ :match (lambda (widget value)
+ (widget-apply (widget-convert (widget-get widget :type))
+ :match value))
+ :validate (lambda (widget)
+ (widget-apply (car (widget-get widget :children)) :validate)))
+
+(define-widget 'nnmail-split-fancy 'nnmail-lazy
+ "Widget for customizing splits in the variable of the same name."
+ :tag "Split"
+ :type '(menu-choice :value (any ".*value.*" "misc")
+ :tag "Type"
+ (string :tag "Destination")
+ (list :tag "Use first match (|)" :value (|)
+ (const :format "" |)
+ (editable-list :inline t nnmail-split-fancy))
+ (list :tag "Use all matches (&)" :value (&)
+ (const :format "" &)
+ (editable-list :inline t nnmail-split-fancy))
+ (list :tag "Function with fixed arguments (:)"
+ :value (:)
+ (const :format "" :value :)
+ function
+ (editable-list :inline t (sexp :tag "Arg"))
+ )
+ (list :tag "Function with split arguments (!)"
+ :value (!)
+ (const :format "" !)
+ function
+ (editable-list :inline t nnmail-split-fancy))
+ (list :tag "Field match"
+ (choice :tag "Field"
+ regexp symbol)
+ (choice :tag "Match"
+ regexp
+ (symbol :value mail))
+ (repeat :inline t
+ :tag "Restrictions"
+ (group :inline t
+ (const :format "" -)
+ regexp))
+ nnmail-split-fancy)
+ (const :tag "Junk (delete mail)" junk)))
(defcustom nnmail-split-fancy "mail.misc"
"Incoming mail can be split according to this fancy variable.
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.
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.
-FIELD and VALUE can also be lisp symbols, in that case they are expanded
+FIELD and VALUE can also be Lisp symbols, in that case they are expanded
as specified in `nnmail-split-abbrev-alist'.
GROUP can contain \\& and \\N which will substitute from matching
;; 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.
\"misc.misc\"))"
:group 'nnmail-split
- ;; Sigh!
- :type 'sexp)
+ :type 'nnmail-split-fancy)
(defcustom nnmail-split-abbrev-alist
'((any . "from\\|to\\|cc\\|sender\\|apparently-to\\|resent-from\\|resent-to\\|resent-cc")
:type '(choice (const :tag "disable" nil)
(integer :format "%v")))
-(defcustom nnmail-message-id-cache-file "~/.nnmail-cache"
- "*The file name of the nnmail Message-ID cache."
+(defcustom nnmail-message-id-cache-file
+ (nnheader-concat gnus-home-directory ".nnmail-cache")
+ "The file name of the nnmail Message-ID cache."
:group 'nnmail-duplicate
:group 'nnmail-files
:type 'file)
(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
+(defcustom nnmail-split-header-length-limit 2048
"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."
+ :version "22.1"
+ :group 'nnmail
+ :type 'symbol)
+
+(defcustom nnmail-mail-splitting-decodes nil
+ "Whether the nnmail splitting functionality should MIME decode headers."
+ :version "22.1"
+ :group 'nnmail
+ :type 'boolean)
+
+(defcustom nnmail-split-fancy-match-partial-words nil
+ "Whether to match partial words when fancy splitting.
+Normally, regexes given in `nnmail-split-fancy' are implicitly surrounded
+by \"\\=\\<...\\>\". If this variable is true, they are not implicitly\
+ surrounded
+by anything."
+ :version "22.1"
+ :group 'nnmail
+ :type 'boolean)
+
+(defcustom nnmail-split-lowercase-expanded t
+ "Whether to lowercase expanded entries (i.e. \\N) when splitting mails.
+This avoids the creation of multiple groups when users send to an address
+using different case (i.e. mailing-list@domain vs Mailing-List@Domain)."
+ :version "22.1"
+ :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.")
-(defvar nnmail-split-fancy-syntax-table nil
+(defvar nnmail-split-fancy-syntax-table
+ (let ((table (make-syntax-table)))
+ ;; support the %-hack
+ (modify-syntax-entry ?\% "." table)
+ table)
"Syntax table used by `nnmail-split-fancy'.")
-(unless (syntax-table-p nnmail-split-fancy-syntax-table)
- (setq nnmail-split-fancy-syntax-table
- (copy-syntax-table (standard-syntax-table)))
- ;; support the %-hack
- (modify-syntax-entry ?\% "." nnmail-split-fancy-syntax-table))
(defvar nnmail-prepare-save-mail-hook nil
"Hook called before saving mail.")
(defvar nnmail-split-tracing nil)
(defvar nnmail-split-trace nil)
-
-\f
-
-(defconst nnmail-version "nnmail 1.0"
- "nnmail version.")
+(defvar nnmail-inhibit-default-split-group nil)
\f
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
+ ;; This causes Emacs 22.2 and 22.3 to issue a useless warning.
+ ;;(if (and (featurep 'xemacs) (featurep 'file-coding))
+ (if (featurep 'xemacs)
+ (if (featurep 'file-coding)
+ ;; Work around a bug in many XEmacs 21.5 betas.
+ ;; Cf. http://thread.gmane.org/gmane.emacs.gnus.general/68134
+ (setq file-name-coding-system (coding-system-aliasee 'file-name))))
+ "*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
(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)
+ (nnheader-replace-chars-in-string group ?. ?/)
dir))))
(or file "")))
"Returns an assoc of group names and active ranges.
nn*-request-list should have been called before calling this function."
;; Go through all groups from the active list.
- (save-excursion
- (set-buffer nntp-server-buffer)
+ (with-current-buffer nntp-server-buffer
(nnmail-parse-active)))
(defun nnmail-parse-active ()
(while (not (eobp))
(condition-case err
(progn
- (narrow-to-region (point) (gnus-point-at-eol))
+ (narrow-to-region (point) (point-at-eol))
(setq group (read buffer))
(unless (stringp group)
(setq group (symbol-name group)))
- (if (and (numberp (setq max (read nntp-server-buffer)))
- (numberp (setq min (read nntp-server-buffer))))
- (push (list group (cons min max))
+ (if (and (numberp (setq max (read buffer)))
+ (numberp (setq min (read buffer))))
+ (push (list (mm-string-as-unibyte group) (cons min max))
group-assoc)))
(error nil))
(widen)
(let ((coding-system-for-write nnmail-active-file-coding-system))
(when file-name
(with-temp-file file-name
+ (mm-disable-multibyte)
(nnmail-generate-active group-assoc)))))
(defun nnmail-generate-active (alist)
(if (not (save-excursion
(and (re-search-backward
"^Content-Length:[ \t]*\\([0-9]+\\)" start t)
- (setq content-length (string-to-int
+ (setq content-length (string-to-number
(buffer-substring
(match-beginning 1)
(match-end 1))))
(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 ".
(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 ".
(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)
(if (not (re-search-forward
"^Content-Length:[ \t]*\\([0-9]+\\)" nil t))
(setq content-length nil)
- (setq content-length (string-to-int (match-string 1)))
+ (setq content-length (string-to-number (match-string 1)))
;; We destroy the header, since none of the backends ever
;; use it, and we do not want to confuse other mailers by
;; having a (possibly) faulty header.
(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.
(goto-char end)))
count))
-(defun nnmail-process-mmdf-mail-format (func artnum-func)
+(defun nnmail-process-mmdf-mail-format (func artnum-func &optional junk-func)
(let ((delim "^\^A\^A\^A\^A$")
(case-fold-search t)
(count 0)
(narrow