X-Git-Url: http://git.chise.org/gitweb/?a=blobdiff_plain;f=lisp%2Fnnmail.el;h=8e8fbfc6222a01f9ce5c3a0c3903a8fd363615b9;hb=d48c014e9def24a6c0de92967e489c06923343c0;hp=7af5f34387984413e0b476fae590cb0cd9d6a863;hpb=15cf2c08a64d2e909855d9a4e9d53cc595543c51;p=elisp%2Fgnus.git- diff --git a/lisp/nnmail.el b/lisp/nnmail.el index 7af5f34..8e8fbfc 100644 --- a/lisp/nnmail.el +++ b/lisp/nnmail.el @@ -28,14 +28,13 @@ (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." @@ -181,7 +180,8 @@ used as incoming mailboxes. 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." @@ -218,7 +218,7 @@ several files - eg. \".spool[0-9]*\"." :type 'function) (defcustom nnmail-crosspost-link-function - (if (string-match "windows-nt\\|emx" (format "%s" system-type)) + (if (string-match "windows-nt\\|emx" (symbol-name system-type)) 'copy-file 'add-name-to-file) "*Function called to create a copy of a file. @@ -241,6 +241,13 @@ to be moved to." :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 @@ -407,7 +414,7 @@ Example: :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 @@ -442,6 +449,11 @@ parameter. It should return nil, `warn' or `delete'." (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 @@ -468,6 +480,9 @@ parameter. It should return nil, `warn' or `delete'." (defvar nnmail-internal-password nil) +(defvar nnmail-split-tracing nil) +(defvar nnmail-split-trace nil) + (defconst nnmail-version "nnmail 1.0" @@ -478,18 +493,19 @@ parameter. It should return nil, `warn' or `delete'." (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.") (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)))) @@ -509,54 +525,12 @@ parameter. It should return nil, `warn' or `delete'." (concat dir group "/") ;; If not, we translate dots into slashes. (concat dir - (gnus-encode-coding-string + (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 (round (/ 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'." @@ -635,7 +609,9 @@ parameter. It should return nil, `warn' or `delete'." 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)) @@ -655,6 +631,9 @@ parameter. It should return nil, `warn' or `delete'." (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") @@ -690,15 +669,14 @@ nn*-request-list should have been called before calling this function." 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) @@ -717,10 +695,12 @@ return nil if FILE is a spool file or the procmail group for which it is a spool. If not using procmail, return GROUP." (if (or (eq nnmail-spool-file 'procmail) nnmail-use-procmail) - (if (string-match (concat "^" (expand-file-name - (file-name-as-directory - nnmail-procmail-directory)) - "\\([^/]*\\)" nnmail-procmail-suffix "$") + (if (string-match (concat "^" (regexp-quote + (expand-file-name + (file-name-as-directory + nnmail-procmail-directory))) + "\\([^/]*\\)" + nnmail-procmail-suffix "$") (expand-file-name file)) (let ((procmail-group (substring (expand-file-name file) (match-beginning 1) @@ -828,7 +808,7 @@ is a spool. If not using procmail, return GROUP." (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 ") @@ -857,7 +837,7 @@ is a spool. If not using procmail, return GROUP." (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 ") @@ -1021,7 +1001,6 @@ FUNC will be called with the buffer narrowed to each mail." (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)) @@ -1041,7 +1020,7 @@ FUNC will be called with the buffer narrowed to each mail." (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) @@ -1067,8 +1046,21 @@ FUNC will be called with the group name to determine the article number." (goto-char (point-min)) (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t) (replace-match " " t t)) + ;; Nuke pathologically long headers. Since Gnus applies + ;; pathologically complex regexps to the buffer, lines + ;; that are looong will take longer than the Universe's + ;; existence to process. + (goto-char (point-min)) + (while (not (eobp)) + (end-of-line) + (if (> (current-column) 1024) + (gnus-delete-line) + (forward-line 1))) ;; 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 @@ -1083,7 +1075,7 @@ FUNC will be called with the group name to determine the article number." "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... @@ -1117,8 +1109,7 @@ FUNC will be called with the group name to determine the article number." ;; 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)) @@ -1128,6 +1119,18 @@ FUNC will be called with the group name to determine the article number." (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 @@ -1168,8 +1171,9 @@ Return the number of characters in the body." (insert (format "Xref: %s" (system-name))) (while group-alist (insert (format " %s:%d" - (gnus-encode-coding-string (caar group-alist) - nnmail-pathname-coding-system) + (encode-coding-string + (caar group-alist) + nnmail-pathname-coding-system) (cdar group-alist))) (setq group-alist (cdr group-alist))) (insert "\n")))) @@ -1224,81 +1228,87 @@ See the documentation for the variable `nnmail-split-fancy' for documentation." (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)) @@ -1312,14 +1322,14 @@ See the documentation for the variable `nnmail-split-fancy' for documentation." (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 ?\&) @@ -1444,7 +1454,6 @@ See the documentation for the variable `nnmail-split-fancy' for documentation." (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) @@ -1512,9 +1521,9 @@ See the documentation for the variable `nnmail-split-fancy' for documentation." ;; 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) @@ -1631,9 +1640,9 @@ See the documentation for the variable `nnmail-split-fancy' for documentation." ;; 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))))))) + (time-less-p days (time-since time))))))) (defvar nnmail-read-passwd nil) (defun nnmail-read-passwd (prompt &rest args) @@ -1698,11 +1707,11 @@ If ARGS, PROMPT is used as an argument to `format'." (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") @@ -1742,11 +1751,10 @@ If ARGS, PROMPT is used as an argument to `format'." (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))))