;;; Code:
-(require 'cl)
+(eval-when-compile (require 'cl))
(require 'nnheader)
-(require 'timezone)
(require 'message)
(require 'custom)
+(require 'gnus-util)
(eval-and-compile
(autoload 'gnus-error "gnus-util")
- (autoload 'gnus-buffer-live-p "gnus-util")
- (autoload 'gnus-encode-coding-string "gnus-ems"))
+ (autoload 'gnus-buffer-live-p "gnus-util"))
(defgroup nnmail nil
"Reading mail with Gnus."
If this variable is a directory (i. e., it's name ends with a \"/\"),
treat all files in that directory as incoming spool files."
:group 'nnmail-files
- :type 'file)
+ :type '(choice (file :tag "File")
+ (repeat :tag "Files" file)))
(defcustom nnmail-crash-box "~/.gnus-crash-box"
"File where Gnus will store mail while processing it."
:group 'nnmail-retrieve
:type 'string)
+(defcustom nnmail-movemail-args nil
+ "*Extra arguments to give to `nnmail-movemail-program' to move mail from the inbox.
+The default is nil"
+ :group 'nnmail-files
+ :group 'nnmail-retrieve
+ :type 'string)
+
(defcustom nnmail-pop-password-required nil
"*Non-nil if a password is required when reading mail using POP."
:group 'nnmail-retrieve
:group 'nnmail-split
:type '(repeat (cons :format "%v" symbol regexp)))
-(defcustom nnmail-delete-incoming t
+(defcustom nnmail-delete-incoming nil
"*If non-nil, the mail backends will delete incoming files after
splitting."
:group 'nnmail-retrieve
(const warn)
(const delete)))
+(defcustom nnmail-extra-headers nil
+ "*Extra headers to parse."
+ :group 'nnmail
+ :type '(repeat symbol))
+
;;; Internal variables.
(defvar nnmail-split-history nil
(defvar nnmail-internal-password nil)
+(defvar nnmail-split-tracing nil)
+(defvar nnmail-split-trace nil)
+
\f
(defconst nnmail-version "nnmail 1.0"
(defun nnmail-request-post (&optional server)
(mail-send-and-exit nil))
-(defvar nnmail-file-coding-system 'raw-text
+(defvar nnmail-file-coding-system 'binary
"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.")
+
(defun nnmail-find-file (file)
"Insert FILE in server buffer safely."
(set-buffer nntp-server-buffer)
- (erase-buffer)
+ (delete-region (point-min) (point-max))
(let ((format-alist nil)
(after-insert-file-functions nil))
(condition-case ()
(let ((coding-system-for-read nnmail-file-coding-system)
- (pathname-coding-system 'binary))
+ (auto-mode-alist (nnheader-auto-mode-alist))
+ (pathname-coding-system nnmail-file-coding-system))
(insert-file-contents file)
t)
(file-error nil))))
(concat dir group "/")
;; If not, we translate dots into slashes.
(concat dir
- (gnus-encode-coding-string
+ (mm-encode-coding-string
(nnheader-replace-chars-in-string group ?. ?/)
nnmail-pathname-coding-system)
"/")))
(or file "")))
-(defun nnmail-date-to-time (date)
- "Convert DATE into time."
- (condition-case ()
- (let* ((d1 (timezone-parse-date date))
- (t1 (timezone-parse-time (aref d1 3))))
- (apply 'encode-time
- (mapcar (lambda (el)
- (and el (string-to-number el)))
- (list
- (aref t1 2) (aref t1 1) (aref t1 0)
- (aref d1 2) (aref d1 1) (aref d1 0)
- (number-to-string
- (* 60 (timezone-zone-to-minute (aref d1 4))))))))
- ;; If we get an error, then we just return a 0 time.
- (error (list 0 0))))
-
-(defun nnmail-time-less (t1 t2)
- "Say whether time T1 is less than time T2."
- (or (< (car t1) (car t2))
- (and (= (car t1) (car t2))
- (< (nth 1 t1) (nth 1 t2)))))
-
-(defun nnmail-days-to-time (days)
- "Convert DAYS into time."
- (let* ((seconds (* 1.0 days 60 60 24))
- (rest (expt 2 16))
- (ms (condition-case nil (floor (/ seconds rest))
- (range-error (expt 2 16)))))
- (list ms (condition-case nil (round (- seconds (* ms rest)))
- (range-error (expt 2 16))))))
-
-(defun nnmail-time-since (time)
- "Return the time since TIME, which is either an internal time or a date."
- (when (stringp time)
- ;; Convert date strings to internal time.
- (setq time (nnmail-date-to-time time)))
- (let* ((current (current-time))
- (rest (when (< (nth 1 current) (nth 1 time))
- (expt 2 16))))
- (list (- (+ (car current) (if rest -1 0)) (car time))
- (- (+ (or rest 0) (nth 1 current)) (nth 1 time)))))
-
;; Function rewritten from rmail.el.
(defun nnmail-move-inbox (inbox)
"Move INBOX to `nnmail-crash-box'."
nnmail-movemail-program exec-directory)
nil errors nil inbox tofile)
(when nnmail-internal-password
- (list nnmail-internal-password)))))))
+ (list nnmail-internal-password))
+ (when nnmail-movemail-args
+ nnmail-movemail-args))))))
(push inbox nnmail-moved-inboxes)
(if (and (not (buffer-modified-p errors))
(zerop result))
(set-file-modes
tofile nnmail-default-file-modes))))
;; Probably a real error.
+ ;; We nix out the password in case the error
+ ;; was because of a wrong password being given.
+ (setq nnmail-internal-password nil)
(subst-char-in-region (point-min) (point-max) ?\n ?\ )
(goto-char (point-max))
(skip-chars-backward " \t")
group-assoc)))
group-assoc))
-(defvar nnmail-active-file-coding-system
- 'iso-8859-1
+(defvar nnmail-active-file-coding-system 'binary
"*Coding system for active file.")
(defun nnmail-save-active (group-assoc file-name)
"Save GROUP-ASSOC in ACTIVE-FILE."
(let ((coding-system-for-write nnmail-active-file-coding-system))
(when file-name
- (nnheader-temp-write file-name
+ (with-temp-file file-name
(nnmail-generate-active group-assoc)))))
(defun nnmail-generate-active (alist)
(when (and (or (bobp)
(save-excursion
(forward-line -1)
- (= (following-char) ?\n)))
+ (eq (char-after) ?\n)))
(save-excursion
(forward-line 1)
(while (looking-at ">From \\|From ")
(when (and (or (bobp)
(save-excursion
(forward-line -1)
- (= (following-char) ?\n)))
+ (eq (char-after) ?\n)))
(save-excursion
(forward-line 1)
(while (looking-at ">From \\|From ")
(save-excursion
;; Insert the incoming file.
(set-buffer (get-buffer-create " *nnmail incoming*"))
- (buffer-disable-undo (current-buffer))
(erase-buffer)
(nnheader-insert-file-contents incoming)
(unless (zerop (buffer-size))
(funcall exit-func))
(kill-buffer (current-buffer)))))
-(defun nnmail-article-group (func)
+(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)
;; Allow washing.
(goto-char (point-min))
(run-hooks 'nnmail-split-hook)
+ (when (setq nnmail-split-tracing trace)
+ (setq nnmail-split-trace nil))
(if (and (symbolp nnmail-split-methods)
(fboundp nnmail-split-methods))
(let ((split
"Error in `nnmail-split-methods'; using `bogus' mail group")
(sit-for 1)
'("bogus")))))
- (setq split (remove-duplicates split :test 'equal))
+ (setq split (gnus-remove-duplicates split))
;; The article may be "cross-posted" to `junk'. What
;; to do? Just remove the `junk' spec. Don't really
;; see anything else to do...
;; group twice.
(not (assoc (car method) group-art)))
(push (cons (if regrepp
- (replace-match
- (car method) nil nil (car method))
+ (nnmail-expand-newtext (car method))
(car method))
(funcall func (car method)))
group-art))
(setq group-art
(list (cons (car method)
(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)))
+ (nnheader-set-temp-buffer "*Split Trace*")
+ (gnus-add-buffer)
+ (while trace
+ (insert (car trace) "\n")
+ (setq trace (cdr trace)))
+ (goto-char (point-min))
+ (gnus-configure-windows 'split-trace)
+ (set-buffer restore)))
;; See whether the split methods returned `junk'.
(if (equal group-art '(junk))
nil
(insert (format "Xref: %s" (system-name)))
(while group-alist
(insert (format " %s:%d"
- (gnus-encode-coding-string (caar group-alist)
- nnmail-pathname-coding-system)
+ (mm-encode-coding-string
+ (caar group-alist)
+ nnmail-pathname-coding-system)
(cdar group-alist)))
(setq group-alist (cdr group-alist)))
(insert "\n"))))
(defun nnmail-split-it (split)
;; Return a list of groups matching SPLIT.
- (cond
- ;; nil split
- ((null split)
- nil)
-
- ;; A group name. Do the \& and \N subs into the string.
- ((stringp split)
- (list (nnmail-expand-newtext split)))
-
- ;; Junk the message.
- ((eq split 'junk)
- (list 'junk))
-
- ;; Builtin & operation.
- ((eq (car split) '&)
- (apply 'nconc (mapcar 'nnmail-split-it (cdr split))))
-
- ;; Builtin | operation.
- ((eq (car split) '|)
- (let (done)
- (while (and (not done) (cdr split))
- (setq split (cdr split)
- done (nnmail-split-it (car split))))
- done))
-
- ;; Builtin : operation.
- ((eq (car split) ':)
- (nnmail-split-it (save-excursion (eval (cdr split)))))
-
- ;; Check the cache for the regexp for this split.
- ;; FIX FIX FIX could avoid calling assq twice here
- ((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 (assq split nnmail-split-cache)) nil t)
- ;; 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))))
-
- ;; Not in cache, compute a regexp for the field/value pair.
- (t
- (let* ((field (nth 0 split))
- (value (nth 1 split))
- (regexp (concat "^\\(\\("
- (if (symbolp field)
- (cdr (assq field nnmail-split-abbrev-alist))
- field)
- "\\):.*\\)\\<\\("
- (if (symbolp value)
- (cdr (assq value nnmail-split-abbrev-alist))
- 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.
- (nnmail-split-it split)))))
+ (let (cached-pair)
+ (cond
+ ;; nil split
+ ((null split)
+ nil)
+
+ ;; A group name. Do the \& and \N subs into the string.
+ ((stringp split)
+ (when nnmail-split-tracing
+ (push (format "\"%s\"" split) nnmail-split-trace))
+ (list (nnmail-expand-newtext split)))
+
+ ;; Junk the message.
+ ((eq split 'junk)
+ (when nnmail-split-tracing
+ (push "junk" nnmail-split-trace))
+ (list 'junk))
+
+ ;; Builtin & operation.
+ ((eq (car split) '&)
+ (apply 'nconc (mapcar 'nnmail-split-it (cdr split))))
+
+ ;; Builtin | operation.
+ ((eq (car split) '|)
+ (let (done)
+ (while (and (not done) (cdr split))
+ (setq split (cdr split)
+ done (nnmail-split-it (car split))))
+ done))
+
+ ;; Builtin : operation.
+ ((eq (car split) ':)
+ (nnmail-split-it (save-excursion (eval (cdr 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))))
+
+ ;; Not in cache, compute a regexp for the field/value pair.
+ (t
+ (let* ((field (nth 0 split))
+ (value (nth 1 split))
+ (regexp (concat "^\\(\\("
+ (if (symbolp field)
+ (cdr (assq field nnmail-split-abbrev-alist))
+ field)
+ "\\):.*\\)\\<\\("
+ (if (symbolp value)
+ (cdr (assq value nnmail-split-abbrev-alist))
+ 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.
+ (nnmail-split-it split))))))
(defun nnmail-expand-newtext (newtext)
(let ((len (length newtext))
(unless (= beg pos)
(push (substring newtext beg pos) expanded))
(when (< pos len)
- ;; we hit a \, expand it.
- (setq did-expand t)
- (setq pos (1+ pos))
- (setq c (aref newtext pos))
+ ;; We hit a \; expand it.
+ (setq did-expand t
+ pos (1+ pos)
+ c (aref newtext pos))
(if (not (or (= c ?\&)
(and (>= c ?1)
(<= c ?9))))
- ;; \ followed by some character we don't expand
+ ;; \ followed by some character we don't expand.
(push (char-to-string c) expanded)
;; \& or \N
(if (= c ?\&)
(set-buffer
(setq nnmail-cache-buffer
(get-buffer-create " *nnmail message-id cache*")))
- (buffer-disable-undo (current-buffer))
(when (file-exists-p nnmail-message-id-cache-file)
(nnheader-insert-file-contents nnmail-message-id-cache-file))
(set-buffer-modified-p nil)
;; Let the backend save the article (or not).
(cond
((not duplication)
- (nnmail-cache-insert message-id)
(funcall func (setq group-art
- (nreverse (nnmail-article-group artnum-func)))))
+ (nreverse (nnmail-article-group artnum-func))))
+ (nnmail-cache-insert message-id))
((eq action 'delete)
(setq group-art nil))
((eq action 'warn)
;; This is an ange-ftp group, and we don't have any dates.
nil)
((numberp days)
- (setq days (nnmail-days-to-time days))
+ (setq days (days-to-time days))
;; Compare the time with the current time.
- (nnmail-time-less days (nnmail-time-since time)))))))
+ (condition-case ()
+ (time-less-p days (time-since time))
+ (error nil)))))))
(defvar nnmail-read-passwd nil)
(defun nnmail-read-passwd (prompt &rest args)
(goto-char (point-min))
(while (re-search-forward "[^ \t=]+" nil t)
(setq name (match-string 0))
- (if (not (= (following-char) ?=))
+ (if (not (eq (char-after) ?=))
;; Implied "yes".
(setq value "yes")
(forward-char 1)
- (if (not (= (following-char) ?\"))
+ (if (not (eq (char-after) ?\"))
(if (not (looking-at "[^ \t]"))
;; Implied "no".
(setq value "no")
(defun nnmail-purge-split-history (group)
"Remove all instances of GROUP from `nnmail-split-history'."
- (let ((history nnmail-split-history)
- prev)
+ (let ((history nnmail-split-history))
(while history
- (setcar history (delete-if (lambda (e) (string= (car e) group))
- (car history)))
+ (setcar history (gnus-delete-if (lambda (e) (string= (car e) group))
+ (car history)))
(pop history))
(setq nnmail-split-history (delq nil nnmail-split-history))))
(defun nnmail-pop3-movemail (inbox crashbox)
"Function to move mail from INBOX on a pop3 server to file CRASHBOX."
(let ((pop3-maildrop
- (substring inbox (match-end (string-match "^po:" inbox)))))
+ (substring inbox (match-end (string-match "^po:" inbox))))
+ (pop3-password
+ (or nnmail-pop-password
+ (nnmail-read-passwd
+ (format "Password for %s: " inbox)))))
(pop3-movemail crashbox)))
(defun nnmail-within-headers-p ()