(require 'custom)
(require 'gnus-util)
(require 'mail-source)
+(require 'mm-util)
(eval-and-compile
(autoload 'gnus-error "gnus-util")
(defcustom nnmail-spool-file '((file))
"*Where the mail backends will look for incoming mail.
This variable is a list of mail source specifiers.
-If this variable is nil, no mail backends will read incoming mail."
+This variable is obsolete; `mail-sources' should be used instead."
:group 'nnmail-files
:type 'sexp)
GROUP: Mail will be stored in GROUP (a string).
-\(FIELD VALUE SPLIT): If the message field FIELD (a regexp) contains
- VALUE (a regexp), store the messages as specified by SPLIT.
+\(FIELD VALUE [- RESTRICT [- RESTRICT [...]]] SPLIT): If the message
+ 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,
+ otherwise process SPLIT. Multiple RESTRICTs add up, further
+ restricting the possibility of processing SPLIT.
\(| SPLIT...): Process each SPLIT expression until one of them matches.
A SPLIT expression is said to match if it will cause the mail
the buffer containing the message headers. The return value FUNCTION
should be a split, which is then recursively processed.
+\(! FUNCTION SPLIT): Call FUNCTION with the result of SPLIT. The
+ return value FUNCTION should be a split, which is then recursively
+ processed.
+
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.
;; 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\")
+ ;;
;; People...
(any \"larsi@ifi\\\\.uio\\\\.no\" \"people.Lars Magne Ingebrigtsen\"))
;; Unmatched mail goes to the catch all group.
(defvar nnmail-split-history nil
"List of group/article elements that say where the previous split put messages.")
-(defvar nnmail-current-spool nil)
-
(defvar nnmail-split-fancy-syntax-table nil
"Syntax table used by `nnmail-split-fancy'.")
(unless (syntax-table-p nnmail-split-fancy-syntax-table)
(defun nnmail-request-post (&optional server)
(mail-send-and-exit nil))
-(defvar nnmail-file-coding-system 'binary
+(defvar nnmail-file-coding-system 'raw-text
"Coding system used in nnmail.")
-(defvar nnmail-file-coding-system-1
- (if (string-match "nt" system-configuration)
- 'raw-text-dos 'binary)
- "Another coding system used in nnmail.")
-
(defvar nnmail-incoming-coding-system
mm-text-coding-system
"Coding system used in reading inbox")
+(defvar nnmail-pathname-coding-system 'binary
+ "*Coding system for pathname.")
+
(defun nnmail-find-file (file)
"Insert FILE in server buffer safely."
(set-buffer nntp-server-buffer)
(after-insert-file-functions nil))
(condition-case ()
(let ((coding-system-for-read nnmail-file-coding-system)
- (auto-mode-alist (nnheader-auto-mode-alist))
- (pathname-coding-system nnmail-file-coding-system))
+ (auto-mode-alist (mm-auto-mode-alist))
+ (pathname-coding-system nnmail-pathname-coding-system))
(insert-file-contents file)
t)
(file-error nil))))
-(defvar nnmail-pathname-coding-system 'binary
- "*Coding system for pathname.")
-
(defun nnmail-group-pathname (group dir &optional file)
"Make pathname for GROUP."
(concat
group-assoc)))
group-assoc))
-(defvar nnmail-active-file-coding-system 'binary
+(defvar nnmail-active-file-coding-system 'raw-text
"*Coding system for active file.")
(defun nnmail-save-active (group-assoc file-name)
(defun nnmail-process-babyl-mail-format (func artnum-func)
(let ((case-fold-search t)
+ (count 0)
start message-id content-length do-search end)
(while (not (eobp))
(goto-char (point-min))
(narrow-to-region start (point))
(goto-char (point-min))
(nnmail-check-duplication message-id func artnum-func)
+ (incf count)
(setq end (point-max))))
- (goto-char end))))
+ (goto-char end))
+ count))
(defsubst nnmail-search-unix-mail-delim ()
"Put point at the beginning of the next Unix mbox message."
(defun nnmail-process-unix-mail-format (func artnum-func)
(let ((case-fold-search t)
+ (count 0)
start message-id content-length end skip head-end)
(goto-char (point-min))
(if (not (and (re-search-forward "^From " nil t)
(goto-char (match-beginning 0))))
;; Possibly wrong format?
- (progn
- (pop-to-buffer (nnheader-find-file-noselect nnmail-current-spool))
- (error "Error, unknown mail format! (Possibly corrupted.)"))
+ (error "Error, unknown mail format! (Possibly corrupted.)")
;; Carry on until the bitter end.
(while (not (eobp))
(setq start (point)
(save-restriction
(narrow-to-region start (point))
(goto-char (point-min))
+ (incf count)
(nnmail-check-duplication message-id func artnum-func)
(setq end (point-max))))
- (goto-char end)))))
+ (goto-char end)))
+ count))
(defun nnmail-process-mmdf-mail-format (func artnum-func)
(let ((delim "^\^A\^A\^A\^A$")
(case-fold-search t)
+ (count 0)
start message-id end)
(goto-char (point-min))
(if (not (and (re-search-forward delim nil t)
(forward-line 1)))
;; Possibly wrong format?
- (progn
- (pop-to-buffer (nnheader-find-file-noselect nnmail-current-spool))
- (error "Error, unknown mail format! (Possibly corrupted.)"))
+ (error "Error, unknown mail format! (Possibly corrupted.)")
;; Carry on until the bitter end.
(while (not (eobp))
(setq start (point))
(save-restriction
(narrow-to-region start (point))
(goto-char (point-min))
+ (incf count)
(nnmail-check-duplication message-id func artnum-func)
(setq end (point-max))))
(goto-char end)
- (forward-line 2)))))
+ (forward-line 2)))
+ count))
(defun nnmail-process-maildir-mail-format (func artnum-func)
-; In a maildir, every file contains exactly one mail
+ ;; In a maildir, every file contains exactly one mail.
(let ((case-fold-search t)
message-id)
(goto-char (point-min))
;; if there is no head-body delimiter, we search a bit manually.
(while (and (looking-at "From \\|[^ \t]+:")
(not (eobp)))
- (forward-line 1)
- (point))))
+ (forward-line 1))
+ (point)))
;; Find the Message-ID header.
(goto-char (point-min))
(if (re-search-forward "^Message-ID:[ \t]*\\(<[^>]+>\\)" nil t)
(setq message-id (match-string 1))
;; There is no Message-ID here, so we create one.
(save-excursion
- (when (re-search-backward "^Message-ID[ \t]*:" nil t)
+ (when (re-search-backward "^Message-ID[ \t]*:" nil t)
(beginning-of-line)
(insert "Original-")))
(forward-line 1)
;; Allow the backend to save the article.
(widen)
(save-excursion
- (goto-char (point-min))
- (nnmail-check-duplication message-id func artnum-func))))
+ (goto-char (point-min))
+ (nnmail-check-duplication message-id func artnum-func))
+ 1))
(defun nnmail-split-incoming (incoming func &optional exit-func
group artnum-func)
;; Insert the incoming file.
(set-buffer (get-buffer-create " *nnmail incoming*"))
(erase-buffer)
- (let ((nnheader-file-coding-system nnmail-incoming-coding-system))
- (nnheader-insert-file-contents incoming))
- (unless (zerop (buffer-size))
- (goto-char (point-min))
- (save-excursion (run-hooks 'nnmail-prepare-incoming-hook))
- ;; Handle both babyl, MMDF and unix mail formats, since movemail will
- ;; use the former when fetching from a mailbox, the latter when
- ;; fetching from a file.
- (cond ((or (looking-at "\^L")
- (looking-at "BABYL OPTIONS:"))
- (nnmail-process-babyl-mail-format func artnum-func))
- ((looking-at "\^A\^A\^A\^A")
- (nnmail-process-mmdf-mail-format func artnum-func))
- ((looking-at "Return-Path:")
- (nnmail-process-maildir-mail-format func artnum-func))
- (t
- (nnmail-process-unix-mail-format func artnum-func))))
- (when exit-func
- (funcall exit-func))
- (kill-buffer (current-buffer)))))
+ (let ((coding-system-for-read nnmail-incoming-coding-system))
+ (mm-insert-file-contents incoming))
+ (prog1
+ (if (zerop (buffer-size))
+ 0
+ (goto-char (point-min))
+ (save-excursion (run-hooks 'nnmail-prepare-incoming-hook))
+ ;; Handle both babyl, MMDF and unix mail formats, since
+ ;; movemail will use the former when fetching from a
+ ;; mailbox, the latter when fetching from a file.
+ (cond ((or (looking-at "\^L")
+ (looking-at "BABYL OPTIONS:"))
+ (nnmail-process-babyl-mail-format func artnum-func))
+ ((looking-at "\^A\^A\^A\^A")
+ (nnmail-process-mmdf-mail-format func artnum-func))
+ ((looking-at "Return-Path:")
+ (nnmail-process-maildir-mail-format func artnum-func))
+ (t
+ (nnmail-process-unix-mail-format func artnum-func))))
+ (when exit-func
+ (funcall exit-func))
+ (kill-buffer (current-buffer))))))
(defun nnmail-article-group (func &optional trace)
"Look at the headers and return an alist of groups that match.
(let ((methods nnmail-split-methods)
(obuf (current-buffer))
(beg (point-min))
- end group-art method regrepp)
+ end group-art method grp)
(if (and (sequencep methods)
(= (length methods) 1))
;; If there is only just one group to put everything in, we
(not group-art)))
(goto-char (point-max))
(setq method (pop methods)
- regrepp nil)
+ grp (car method))
(if (or methods
(not (equal "" (nth 1 method))))
(when (and
(ignore-errors
(if (stringp (nth 1 method))
- (progn
- (setq regrepp
- (string-match "\\\\[0-9&]" (car method)))
- (re-search-backward (cadr method) nil t))
+ (let ((expand (string-match "\\\\[0-9&]" grp))
+ (pos (re-search-backward (cadr method)
+ nil t)))
+ (and expand
+ (setq grp (nnmail-expand-newtext grp)))
+ pos)
;; Function to say whether this is a match.
- (funcall (nth 1 method) (car method))))
+ (funcall (nth 1 method) grp)))
;; Don't enter the article into the same
;; group twice.
- (not (assoc (car method) group-art)))
- (push (cons (if regrepp
- (nnmail-expand-newtext (car method))
- (car method))
- (funcall func (car method)))
+ (not (assoc grp group-art)))
+ (push (cons grp (funcall func grp))
group-art))
;; This is the final group, which is used as a
;; catch-all.
(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 "\\|"))))
+ (mapconcat 'identity nnmail-list-identifiers " *\\|"))))
(when regexp
(goto-char (point-min))
(when (re-search-forward
- (concat "^Subject: +\\(Re: +\\)?\\(" regexp "\\) *")
+ (concat "^Subject: +\\(Re: +\\)?\\(" regexp " *\\)")
nil t)
(delete-region (match-beginning 2) (match-end 0))))))
((eq (car split) ':)
(nnmail-split-it (save-excursion (eval (cdr split)))))
+ ;; Builtin ! operation.
+ ((eq (car split) '!)
+ (funcall (cadr split) (nnmail-split-it (caddr split))))
+
;; Check the cache for the regexp for this split.
((setq cached-pair (assq split nnmail-split-cache))
- (goto-char (point-max))
- ;; FIX FIX FIX problem with re-search-backward is that if you have
- ;; a split: (from "foo-\\(bar\\|baz\\)@gnus.org "mail.foo.\\1")
- ;; and someone mails a message with 'To: foo-bar@gnus.org' and
- ;; 'CC: foo-baz@gnus.org', we'll pick 'mail.foo.baz' as the group
- ;; if the cc line is a later header, even though the other choice
- ;; is probably better. Also, this routine won't do a crosspost
- ;; when there are two different matches.
- ;; I guess you could just make this more determined, and it could
- ;; look for still more matches prior to this one, and recurse
- ;; on each of the multiple matches hit. Of course, then you'd
- ;; want to make sure that nnmail-article-group or nnmail-split-fancy
- ;; removed duplicates, since there might be more of those.
- ;; I guess we could also remove duplicates in the & split case, since
- ;; that's the only thing that can introduce them.
- (when (re-search-backward (cdr cached-pair) nil t)
- (when nnmail-split-tracing
- (push (cdr cached-pair) nnmail-split-trace))
- ;; Someone might want to do a \N sub on this match, so get the
- ;; correct match positions.
- (goto-char (match-end 0))
- (let ((value (nth 1 split)))
- (re-search-backward (if (symbolp value)
- (cdr (assq value nnmail-split-abbrev-alist))
- value)
- (match-end 1)))
- (nnmail-split-it (nth 2 split))))
+ (let (split-result
+ (end-point (point-max))
+ (value (nth 1 split)))
+ (if (symbolp value)
+ (setq value (cdr (assq value nnmail-split-abbrev-alist))))
+ (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))
+ (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.
+ (start-of-value (match-end 1))
+ (after-header-name (match-end 2)))
+ ;; Start the next search just before the beginning of the
+ ;; VALUE match.
+ (setq end-point (1- start-of-value))
+ ;; Handle - RESTRICTs
+ (while (eq (car split-rest) '-)
+ ;; RESTRICT must start after-header-name and
+ ;; end after start-of-value, so that, for
+ ;; (any "foo" - "x-foo" "foo.list")
+ ;; we do not exclude foo.list just because
+ ;; the header is: ``To: x-foo, foo''
+ (goto-char end)
+ (if (and (re-search-backward (cadr split-rest)
+ after-header-name t)
+ (> (match-end 0) start-of-value))
+ (setq split-rest nil)
+ (setq split-rest (cddr split-rest))))
+ (when split-rest
+ (goto-char end)
+ (let ((value (nth 1 split)))
+ (if (symbolp value)
+ (setq value (cdr (assq value nnmail-split-abbrev-alist))))
+ ;; Someone might want to do a \N sub on this match, so get the
+ ;; correct match positions.
+ (re-search-backward value start-of-value))
+ (dolist (sp (nnmail-split-it (car split-rest)))
+ (unless (memq sp split-result)
+ (push sp split-result))))))
+ split-result))
;; Not in cache, compute a regexp for the field/value pair.
(t
(let* ((field (nth 0 split))
(value (nth 1 split))
- (regexp (concat "^\\(\\("
+ partial regexp)
+ (if (symbolp value)
+ (setq value (cdr (assq value nnmail-split-abbrev-alist))))
+ (if (string= ".*" (substring value 0 2))
+ (setq value (substring value 2)
+ partial ""))
+ (setq regexp (concat "^\\(\\("
(if (symbolp field)
(cdr (assq field nnmail-split-abbrev-alist))
field)
- "\\):.*\\)\\<\\("
- (if (symbolp value)
- (cdr (assq value nnmail-split-abbrev-alist))
- value)
- "\\)\\>")))
+ "\\):.*\\)"
+ (or partial "\\<")
+ "\\("
+ value
+ "\\)\\>"))
(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.
(defun nnmail-get-new-mail (method exit-func temp
&optional group spool-func)
"Read new incoming mail."
- (let* ((sources (if (listp nnmail-spool-file) nnmail-spool-file
- (list nnmail-spool-file)))
+ (let* ((sources (or mail-sources
+ (if (listp nnmail-spool-file) nnmail-spool-file
+ (list nnmail-spool-file))))
(group-in group)
(i 0)
- nnmail-current-spool incoming incomings source)
+ (new 0)
+ (total 0)
+ incoming incomings source)
(when (and (nnmail-get-value "%s-get-new-mail" method)
nnmail-spool-file)
;; We first activate all the groups.
;; Hack to only fetch the contents of a single group's spool file.
(when (and (eq (car source) 'directory)
group)
- (setq source (append source
- (list :predicate
- `(lambda (file)
- (string-match ,(regexp-quote group)
- file))))))
+ (mail-source-bind (directory source)
+ (setq source (append source
+ (list
+ :predicate
+ `(lambda (file)
+ (string-match
+ ,(concat
+ (regexp-quote (concat group suffix))
+ "$")
+ file)))))))
(when nnmail-fetched-sources
(if (member source nnmail-fetched-sources)
(setq source nil)
(when source
(nnheader-message 4 "%s: Reading incoming mail from %s..."
method (car source))
- (when (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)))))
+ (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))))))
+ (incf total new)
(incf i))))
;; If we did indeed read any incoming spools, we save all info.
- (unless (zerop i)
+ (if (zerop total)
+ (nnheader-message 4 "%s: Reading incoming mail (no new mail)...done"
+ method (car source))
(nnmail-save-active
(nnmail-get-value "%s-group-alist" method)
(nnmail-get-value "%s-active-file" method))
(when exit-func
(funcall exit-func))
(run-hooks 'nnmail-read-incoming-hook)
- (nnheader-message 4 "%s: Reading incoming mail...done" method))
+ (nnheader-message 4 "%s: Reading incoming mail (%d new)...done" method
+ total))
;; Close the message-id cache.
(nnmail-cache-close)
;; Allow the user to hook.
(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 'binary))
+ (pathname-coding-system nnmail-pathname-coding-system))
(write-region start end filename append visit lockname)
(set-file-modes filename nnmail-default-file-modes)))