X-Git-Url: http://cgit.sxemacs.org/?p=gnus;a=blobdiff_plain;f=lisp%2Fnnimap.el;h=594d1f57e7b707e5c1352942806a644af9cca132;hp=6cbd83c8f78e6c0b29a79b21cce4ab03a76d0b1b;hb=238e63665cab7381691766c2bbdb5c16ab381c97;hpb=bf0214b4a533ba808c8685f58ddffb5b71dcebea diff --git a/lisp/nnimap.el b/lisp/nnimap.el index 6cbd83c8f..594d1f57e 100644 --- a/lisp/nnimap.el +++ b/lisp/nnimap.el @@ -1,6 +1,6 @@ ;;; nnimap.el --- IMAP interface for Gnus -;; Copyright (C) 2010-2012 Free Software Foundation, Inc. +;; Copyright (C) 2010-2015 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Simon Josefsson @@ -26,10 +26,6 @@ ;;; Code: -;; For Emacs <22.2 and XEmacs. -(eval-and-compile - (unless (fboundp 'declare-function) (defmacro declare-function (&rest r)))) - (eval-and-compile (require 'nnheader) ;; In Emacs 24, `open-protocol-stream' is an autoloaded alias for @@ -82,7 +78,8 @@ back on `network'.") (defvoo nnimap-inbox nil "The mail box where incoming mail arrives and should be split out of. -For example, \"INBOX\".") +This can be a string or a list of strings +For example, \"INBOX\" or (\"INBOX\" \"SENT\").") (defvoo nnimap-split-methods nil "How mail is split. @@ -94,12 +91,13 @@ Uses the same syntax as `nnmail-split-methods'.") (defvoo nnimap-unsplittable-articles '(%Deleted %Seen) "Articles with the flags in the list will not be considered when splitting.") -(make-obsolete-variable 'nnimap-split-rule "see `nnimap-split-methods'" +(make-obsolete-variable 'nnimap-split-rule "see `nnimap-split-methods'." "Emacs 24.1") (defvoo nnimap-authenticator nil "How nnimap authenticate itself to the server. -Possible choices are nil (use default methods) or `anonymous'.") +Possible choices are nil (use default methods), `anonymous', +`login', `plain' and `cram-md5'.") (defvoo nnimap-expunge t "If non-nil, expunge articles after deleting them. @@ -117,11 +115,21 @@ some servers.") (defvoo nnimap-fetch-partial-articles nil "If non-nil, Gnus will fetch partial articles. -If t, nnimap will fetch only the first part. If a string, it +If t, Gnus will fetch only the first part. If a string, it will fetch all parts that have types that match that string. A likely value would be \"text/\" to automatically fetch all textual parts.") +(defgroup nnimap nil + "IMAP for Gnus." + :group 'gnus) + +(defcustom nnimap-request-articles-find-limit nil + "Limit the number of articles to look for after moving an article." + :type '(choice (const nil) integer) + :version "24.4" + :group 'nnimap) + (defvar nnimap-process nil) (defvar nnimap-status-string "") @@ -158,21 +166,28 @@ textual parts.") (nnimap-find-process-buffer nntp-server-buffer)) (defun nnimap-header-parameters () - (format "(UID RFC822.SIZE BODYSTRUCTURE %s)" - (format + (let (params) + (push "UID" params) + (push "RFC822.SIZE" params) + (when (nnimap-capability "X-GM-EXT-1") + (push "X-GM-LABELS" params)) + (push "BODYSTRUCTURE" params) + (push (format (if (nnimap-ver4-p) "BODY.PEEK[HEADER.FIELDS %s]" "RFC822.HEADER.LINES %s") (append '(Subject From Date Message-Id References In-Reply-To Xref) - nnmail-extra-headers)))) + nnmail-extra-headers)) + params) + (format "%s" (nreverse params)))) -(deffoo nnimap-retrieve-headers (articles &optional group server fetch-old) +(deffoo nnimap-retrieve-headers (articles &optional group server _fetch-old) (when group (setq group (nnimap-decode-gnus-group group))) (with-current-buffer nntp-server-buffer (erase-buffer) - (when (nnimap-possibly-change-group group server) + (when (nnimap-change-group group server) (with-current-buffer (nnimap-buffer) (erase-buffer) (nnimap-wait-for-response @@ -181,6 +196,8 @@ textual parts.") (nnimap-article-ranges (gnus-compress-sequence articles)) (nnimap-header-parameters)) t) + (unless (process-live-p (get-buffer-process (current-buffer))) + (error "Server closed connection")) (nnimap-transform-headers) (nnheader-remove-cr-followed-by-lf)) (insert-buffer-substring @@ -189,7 +206,7 @@ textual parts.") (defun nnimap-transform-headers () (goto-char (point-min)) - (let (article lines size string) + (let (article lines size string labels) (block nil (while (not (eobp)) (while (not (looking-at "\\* [0-9]+ FETCH")) @@ -218,12 +235,16 @@ textual parts.") t) (match-string 1))) (setq lines nil) + (beginning-of-line) (setq size (and (re-search-forward "RFC822.SIZE \\([0-9]+\\)" (line-end-position) t) (match-string 1))) (beginning-of-line) + (when (search-forward "X-GM-LABELS" (line-end-position) t) + (setq labels (ignore-errors (read (current-buffer))))) + (beginning-of-line) (when (search-forward "BODYSTRUCTURE" (line-end-position) t) (let ((structure (ignore-errors (read (current-buffer))))) @@ -243,7 +264,11 @@ textual parts.") (insert (format "Chars: %s\n" size))) (when lines (insert (format "Lines: %s\n" lines))) - (unless (re-search-forward "^\r$" nil t) + (when labels + (insert (format "X-GM-LABELS: %s\n" labels))) + ;; Most servers have a blank line after the headers, but + ;; Davmail doesn't. + (unless (re-search-forward "^\r$\\|^)\r?$" nil t) (goto-char (point-max))) (delete-region (line-beginning-position) (line-end-position)) (insert ".") @@ -306,6 +331,8 @@ textual parts.") (push (current-buffer) nnimap-process-buffers) (current-buffer))) +(defvar auth-source-creation-prompts) + (defun nnimap-credentials (address ports user) (let* ((auth-source-creation-prompts '((user . "IMAP user at %h: ") @@ -338,7 +365,8 @@ textual parts.") (nnimap-last-command-time nnimap-object))) ;; More than five minutes since the last command. (* 5 60))) - (nnimap-send-command "NOOP"))))))) + (ignore-errors ;E.g. "buffer foo has no process". + (nnimap-send-command "NOOP")))))))) (defun nnimap-open-connection (buffer) ;; Be backwards-compatible -- the earlier value of nnimap-stream was @@ -366,7 +394,7 @@ textual parts.") (defun nnimap-open-connection-1 (buffer) (unless nnimap-keepalive-timer (setq nnimap-keepalive-timer (run-at-time (* 60 15) (* 60 15) - 'nnimap-keepalive))) + #'nnimap-keepalive))) (with-current-buffer (nnimap-make-process-buffer buffer) (let* ((coding-system-for-read 'binary) (coding-system-for-write 'binary) @@ -394,9 +422,11 @@ textual parts.") "*nnimap*" (current-buffer) nnimap-address (nnimap-map-port (car ports)) :type nnimap-stream + :warn-unless-encrypted t :return-list t :shell-command nnimap-shell-program :capability-command "1 CAPABILITY\r\n" + :always-query-capabilities t :end-of-command "\r\n" :success " OK " :starttls-function @@ -443,8 +473,8 @@ textual parts.") (nnimap-credentials (gnus-delete-duplicates (list - nnimap-address - (nnoo-current-server 'nnimap))) + (nnoo-current-server 'nnimap) + nnimap-address)) ports nnimap-user)))) (setq nnimap-object nil) @@ -460,7 +490,7 @@ textual parts.") (funcall (nth 2 credentials))) ;; See if CAPABILITY is set as part of login ;; response. - (dolist (response (cddr login-result)) + (dolist (response (cddr (nnimap-command "CAPABILITY"))) (when (string= "CAPABILITY" (upcase (car response))) (setf (nnimap-capabilities nnimap-object) (mapcar #'upcase (cdr response)))))) @@ -475,6 +505,8 @@ textual parts.") (when nnimap-object (when (nnimap-capability "QRESYNC") (nnimap-command "ENABLE QRESYNC")) + (nnheader-message 7 "Opening connection to %s...done" + nnimap-address) (nnimap-process nnimap-object)))))))) (autoload 'rfc2104-hash "rfc2104") @@ -485,9 +517,13 @@ textual parts.") ;; round trips than CRAM-MD5, and it's less likely to be buggy), ;; and we're using an encrypted connection. ((and (not (nnimap-capability "LOGINDISABLED")) - (eq (nnimap-stream-type nnimap-object) 'tls)) + (eq (nnimap-stream-type nnimap-object) 'tls) + (or (null nnimap-authenticator) + (eq nnimap-authenticator 'login))) (nnimap-command "LOGIN %S %S" user password)) - ((nnimap-capability "AUTH=CRAM-MD5") + ((and (nnimap-capability "AUTH=CRAM-MD5") + (or (null nnimap-authenticator) + (eq nnimap-authenticator 'cram-md5))) (erase-buffer) (let ((sequence (nnimap-send-command "AUTHENTICATE CRAM-MD5")) (challenge (nnimap-wait-for-line "^\\+\\(.*\\)\n"))) @@ -500,9 +536,13 @@ textual parts.") (base64-decode-string challenge)))) "\r\n")) (nnimap-wait-for-response sequence))) - ((not (nnimap-capability "LOGINDISABLED")) + ((and (not (nnimap-capability "LOGINDISABLED")) + (or (null nnimap-authenticator) + (eq nnimap-authenticator 'login))) (nnimap-command "LOGIN %S %S" user password)) - ((nnimap-capability "AUTH=PLAIN") + ((and (nnimap-capability "AUTH=PLAIN") + (or (null nnimap-authenticator) + (eq nnimap-authenticator 'plain))) (nnimap-command "AUTHENTICATE PLAIN %s" (base64-encode-string @@ -548,17 +588,17 @@ textual parts.") (gnus-buffer-live-p nntp-server-buffer) (nnimap-find-connection nntp-server-buffer))) -(deffoo nnimap-status-message (&optional server) +(deffoo nnimap-status-message (&optional _server) nnimap-status-string) (deffoo nnimap-request-article (article &optional group server to-buffer) (when group (setq group (nnimap-decode-gnus-group group))) (with-current-buffer nntp-server-buffer - (let ((result (nnimap-possibly-change-group group server)) + (let ((result (nnimap-change-group group server)) parts structure) (when (stringp article) - (setq article (nnimap-find-article-by-message-id group article))) + (setq article (nnimap-find-article-by-message-id group server article))) (when (and result article) (erase-buffer) @@ -587,10 +627,10 @@ textual parts.") (deffoo nnimap-request-head (article &optional group server to-buffer) (when group (setq group (nnimap-decode-gnus-group group))) - (when (nnimap-possibly-change-group group server) + (when (nnimap-change-group group server) (with-current-buffer (nnimap-buffer) (when (stringp article) - (setq article (nnimap-find-article-by-message-id group article))) + (setq article (nnimap-find-article-by-message-id group server article))) (if (null article) nil (nnimap-get-whole-article @@ -603,6 +643,26 @@ textual parts.") (nnheader-ms-strip-cr) (cons group article))))))) +(deffoo nnimap-request-articles (articles &optional group server) + (when group + (setq group (nnimap-decode-gnus-group group))) + (with-current-buffer nntp-server-buffer + (let ((result (nnimap-change-group group server))) + (when result + (erase-buffer) + (with-current-buffer (nnimap-buffer) + (erase-buffer) + (when (nnimap-command + (if (nnimap-ver4-p) + "UID FETCH %s BODY.PEEK[]" + "UID FETCH %s RFC822.PEEK") + (nnimap-article-ranges (gnus-compress-sequence articles))) + (let ((buffer (current-buffer))) + (with-current-buffer nntp-server-buffer + (nnheader-insert-buffer-substring buffer) + (nnheader-ms-strip-cr))) + t)))))) + (defun nnimap-get-whole-article (article &optional command) (let ((result (nnimap-command @@ -739,7 +799,7 @@ textual parts.") (deffoo nnimap-request-group (group &optional server dont-check info) (setq group (nnimap-decode-gnus-group group)) - (let ((result (nnimap-possibly-change-group + (let ((result (nnimap-change-group ;; Don't SELECT the group if we're going to select it ;; later, anyway. (if (and (not dont-check) @@ -747,61 +807,75 @@ textual parts.") nil group) server)) - articles active marks high low) + active) (with-current-buffer nntp-server-buffer (when result - (if (and dont-check - (setq active (nth 2 (assoc group nnimap-current-infos)))) - (insert (format "211 %d %d %d %S\n" - (- (cdr active) (car active)) - (car active) - (cdr active) - group)) - (with-current-buffer (nnimap-buffer) - (erase-buffer) - (let ((group-sequence - (nnimap-send-command "SELECT %S" (utf7-encode group t))) - (flag-sequence - (nnimap-send-command "UID FETCH 1:* FLAGS"))) - (setf (nnimap-group nnimap-object) group) - (nnimap-wait-for-response flag-sequence) - (setq marks - (nnimap-flags-to-marks - (nnimap-parse-flags - (list (list group-sequence flag-sequence - 1 group "SELECT"))))) - (when (and info - marks) - (nnimap-update-infos marks (list info)) - (nnimap-store-info info (gnus-active (gnus-info-group info)))) - (goto-char (point-max)) - (let ((uidnext (nth 5 (car marks)))) - (setq high (or (if uidnext - (1- uidnext) - (nth 3 (car marks))) - 0) - low (or (nth 4 (car marks)) uidnext 1))))) - (erase-buffer) - (insert - (format - "211 %d %d %d %S\n" (1+ (- high low)) low high group))) + (when (or (not dont-check) + (not (setq active + (nth 2 (assoc group nnimap-current-infos))))) + (let ((sequences (nnimap-retrieve-group-data-early + server (list info)))) + (nnimap-finish-retrieve-group-infos server (list info) sequences + t) + (setq active (nth 2 (assoc group nnimap-current-infos))))) + (erase-buffer) + (insert (format "211 %d %d %d %S\n" + (- (cdr active) (car active)) + (car active) + (cdr active) + group)) + t)))) + +(deffoo nnimap-request-group-scan (group &optional server info) + (setq group (nnimap-decode-gnus-group group)) + (when (nnimap-change-group nil server) + (let (marks high low) + (with-current-buffer (nnimap-buffer) + (erase-buffer) + (let ((group-sequence + (nnimap-send-command "SELECT %S" (utf7-encode group t))) + (flag-sequence + (nnimap-send-command "UID FETCH 1:* FLAGS"))) + (setf (nnimap-group nnimap-object) group) + (nnimap-wait-for-response flag-sequence) + (setq marks + (nnimap-flags-to-marks + (nnimap-parse-flags + (list (list group-sequence flag-sequence + 1 group "SELECT"))))) + (when (and info + marks) + (nnimap-update-infos marks (list info)) + (nnimap-store-info info (gnus-active (gnus-info-group info)))) + (goto-char (point-max)) + (let ((uidnext (nth 5 (car marks)))) + (setq high (or (if uidnext + (1- uidnext) + (nth 3 (car marks))) + 0) + low (or (nth 4 (car marks)) uidnext 1))))) + (with-current-buffer nntp-server-buffer + (erase-buffer) + (insert + (format + "211 %d %d %d %S\n" (1+ (- high low)) low high group)) t)))) -(deffoo nnimap-request-create-group (group &optional server args) +(deffoo nnimap-request-create-group (group &optional server _args) (setq group (nnimap-decode-gnus-group group)) - (when (nnimap-possibly-change-group nil server) + (when (nnimap-change-group nil server) (with-current-buffer (nnimap-buffer) (car (nnimap-command "CREATE %S" (utf7-encode group t)))))) -(deffoo nnimap-request-delete-group (group &optional force server) +(deffoo nnimap-request-delete-group (group &optional _force server) (setq group (nnimap-decode-gnus-group group)) - (when (nnimap-possibly-change-group nil server) + (when (nnimap-change-group nil server) (with-current-buffer (nnimap-buffer) (car (nnimap-command "DELETE %S" (utf7-encode group t)))))) (deffoo nnimap-request-rename-group (group new-name &optional server) (setq group (nnimap-decode-gnus-group group)) - (when (nnimap-possibly-change-group nil server) + (when (nnimap-change-group nil server) (with-current-buffer (nnimap-buffer) (nnimap-unselect-group) (car (nnimap-command "RENAME %S %S" @@ -816,7 +890,7 @@ textual parts.") (deffoo nnimap-request-expunge-group (group &optional server) (setq group (nnimap-decode-gnus-group group)) - (when (nnimap-possibly-change-group group server) + (when (nnimap-change-group group server) (with-current-buffer (nnimap-buffer) (car (nnimap-command "EXPUNGE"))))) @@ -838,12 +912,14 @@ textual parts.") articles))) (nreverse articles))) -(deffoo nnimap-close-group (group &optional server) +(deffoo nnimap-close-group (_group &optional _server) t) (deffoo nnimap-request-move-article (article group server accept-form - &optional last internal-move-group) + &optional _last internal-move-group) (setq group (nnimap-decode-gnus-group group)) + (when internal-move-group + (setq internal-move-group (nnimap-decode-gnus-group internal-move-group))) (with-temp-buffer (mm-disable-multibyte) (when (funcall (if internal-move-group @@ -864,11 +940,12 @@ textual parts.") (cons internal-move-group (or (nnimap-find-uid-response "COPYUID" (cadr result)) (nnimap-find-article-by-message-id - internal-move-group message-id))))) + internal-move-group server message-id + nnimap-request-articles-find-limit))))) ;; Move the article to a different method. (let ((result (eval accept-form))) (when result - (nnimap-possibly-change-group group server) + (nnimap-change-group group server) (nnimap-delete-article article) result))))))) @@ -877,7 +954,7 @@ textual parts.") (cond ((null articles) nil) - ((not (nnimap-possibly-change-group group server)) + ((not (nnimap-change-group group server)) articles) ((and force (eq nnmail-expiry-target 'delete)) @@ -914,7 +991,7 @@ textual parts.") (gnus-server-equal (gnus-group-method nnmail-expiry-target) (gnus-server-to-method (format "nnimap:%s" server)))) - (and (nnimap-possibly-change-group group server) + (and (nnimap-change-group group server) (with-current-buffer (nnimap-buffer) (nnheader-message 7 "Expiring articles from %s: %s" group articles) (nnimap-command @@ -942,47 +1019,61 @@ textual parts.") (setq target nil)) (nnheader-message 7 "Expiring article %s:%d" group article)) (when target - (push article deleted-articles)))))))) + (push article deleted-articles)))))) + (setq deleted-articles (nreverse deleted-articles)))) ;; Change back to the current group again. - (nnimap-possibly-change-group group server) - (setq deleted-articles (nreverse deleted-articles)) + (nnimap-change-group group server) (nnimap-delete-article (gnus-compress-sequence deleted-articles)) deleted-articles)) (defun nnimap-find-expired-articles (group) (let ((cutoff (nnmail-expired-article-p group nil nil))) - (with-current-buffer (nnimap-buffer) - (let ((result - (nnimap-command - "UID SEARCH SENTBEFORE %s" - (format-time-string - (format "%%d-%s-%%Y" - (upcase - (car (rassoc (nth 4 (decode-time cutoff)) - parse-time-months)))) - cutoff)))) - (and (car result) - (delete 0 (mapcar #'string-to-number - (cdr (assoc "SEARCH" (cdr result)))))))))) - - -(defun nnimap-find-article-by-message-id (group message-id) + (when cutoff + (with-current-buffer (nnimap-buffer) + (let ((result + (nnimap-command + "UID SEARCH SENTBEFORE %s" + (format-time-string + (format "%%d-%s-%%Y" + (upcase + (car (rassoc (nth 4 (decode-time cutoff)) + parse-time-months)))) + cutoff)))) + (and (car result) + (delete 0 (mapcar #'string-to-number + (cdr (assoc "SEARCH" (cdr result))))))))))) + +(defun nnimap-find-article-by-message-id (group server message-id + &optional limit) + "Search for message with MESSAGE-ID in GROUP from SERVER. +If LIMIT, first try to limit the search to the N last articles." (with-current-buffer (nnimap-buffer) (erase-buffer) - (unless (equal group (nnimap-group nnimap-object)) - (setf (nnimap-group nnimap-object) nil) - (setf (nnimap-examined nnimap-object) group) - (nnimap-send-command "EXAMINE %S" (utf7-encode group t))) - (let ((sequence - (nnimap-send-command "UID SEARCH HEADER Message-Id %S" message-id)) - article result) - (setq result (nnimap-wait-for-response sequence)) - (when (and result - (car (setq result (nnimap-parse-response)))) - ;; Select the last instance of the message in the group. - (and (setq article - (car (last (cdr (assoc "SEARCH" (cdr result)))))) - (string-to-number article)))))) + (let* ((change-group-result (nnimap-change-group group server nil t)) + (number-of-article + (and (listp change-group-result) + (catch 'found + (dolist (result (cdr change-group-result)) + (when (equal "EXISTS" (cadr result)) + (throw 'found (car result))))))) + (sequence + (nnimap-send-command + "UID SEARCH%s HEADER Message-Id %S" + (if (and limit number-of-article) + ;; The -1 is because IMAP message + ;; numbers are one-based rather than + ;; zero-based. + (format " %s:*" (- (string-to-number number-of-article) + limit -1)) + "") + message-id))) + (when (nnimap-wait-for-response sequence) + (let ((article (car (last (cdr (assoc "SEARCH" + (nnimap-parse-response))))))) + (if article + (string-to-number article) + (when (and limit number-of-article) + (nnimap-find-article-by-message-id group server message-id)))))))) (defun nnimap-delete-article (articles) (with-current-buffer (nnimap-buffer) @@ -1003,11 +1094,14 @@ textual parts.") (deffoo nnimap-request-scan (&optional group server) (when group (setq group (nnimap-decode-gnus-group group))) - (when (and (nnimap-possibly-change-group nil server) + (when (and (nnimap-change-group nil server) nnimap-inbox nnimap-split-methods) (nnheader-message 7 "nnimap %s splitting mail..." server) - (nnimap-split-incoming-mail) + (if (listp nnimap-inbox) + (dolist (nnimap-inbox nnimap-inbox) + (nnimap-split-incoming-mail)) + (nnimap-split-incoming-mail)) (nnheader-message 7 "nnimap %s splitting mail...done" server))) (defun nnimap-marks-to-flags (marks) @@ -1019,7 +1113,7 @@ textual parts.") (deffoo nnimap-request-update-group-status (group status &optional server) (setq group (nnimap-decode-gnus-group group)) - (when (nnimap-possibly-change-group nil server) + (when (nnimap-change-group nil server) (let ((command (assoc status '((subscribe "SUBSCRIBE") @@ -1030,7 +1124,7 @@ textual parts.") (deffoo nnimap-request-set-mark (group actions &optional server) (setq group (nnimap-decode-gnus-group group)) - (when (nnimap-possibly-change-group group server) + (when (nnimap-change-group group server) (let (sequence) (with-current-buffer (nnimap-buffer) (erase-buffer) @@ -1053,9 +1147,20 @@ textual parts.") (when sequence (nnimap-wait-for-response sequence)))))) -(deffoo nnimap-request-accept-article (group &optional server last) +(deffoo nnimap-request-accept-article (group &optional server _last) + (unless group + ;; We're respooling. Find out where mail splitting would place + ;; this article. + (setq group + (caar + (nnmail-article-group + ;; We don't really care about the article number, because + ;; that's determined by the IMAP server later. So just + ;; return the group name. + `(lambda (group) + (list (list group))))))) (setq group (nnimap-decode-gnus-group group)) - (when (nnimap-possibly-change-group nil server) + (when (nnimap-change-group nil server) (nnmail-check-syntax) (let ((message-id (message-field-value "message-id")) sequence message) @@ -1087,7 +1192,8 @@ textual parts.") (cons group (or (nnimap-find-uid-response "APPENDUID" (car result)) (nnimap-find-article-by-message-id - group message-id)))))))))) + group server message-id + nnimap-request-articles-find-limit)))))))))) (defun nnimap-process-quirk (greeting-match type data) (when (and (nnimap-greeting nnimap-object) @@ -1133,7 +1239,7 @@ textual parts.") (deffoo nnimap-request-replace-article (article group buffer) (setq group (nnimap-decode-gnus-group group)) (let (group-art) - (when (and (nnimap-possibly-change-group group nil) + (when (and (nnimap-change-group group) ;; Put the article into the group. (with-current-buffer buffer (setq group-art @@ -1159,17 +1265,32 @@ textual parts.") (goto-char (point-min)) (while (search-forward "* LIST " nil t) (let ((flags (read (current-buffer))) - (separator (read (current-buffer))) - (group (read (current-buffer)))) + (_separator (read (current-buffer))) + (group (buffer-substring-no-properties + (progn (skip-chars-forward " \"") + (point)) + (progn (end-of-line) + (skip-chars-backward " \r\"") + (point))))) (unless (member '%NoSelect flags) (push (utf7-decode (if (stringp group) group - (format "%s" group)) t) + (format "%s" group)) + t) groups)))) (nreverse groups))) +(defun nnimap-get-responses (sequences) + (let (responses) + (dolist (sequence sequences) + (goto-char (point-min)) + (when (re-search-forward (format "^%d " sequence) nil t) + (push (list sequence (nnimap-parse-response)) + responses))) + responses)) + (deffoo nnimap-request-list (&optional server) - (when (nnimap-possibly-change-group nil server) + (when (nnimap-change-group nil server) (with-current-buffer nntp-server-buffer (erase-buffer) (let ((groups @@ -1191,7 +1312,8 @@ textual parts.") (dolist (response responses) (let* ((sequence (car response)) (response (cadr response)) - (group (cadr (assoc sequence sequences)))) + (group (cadr (assoc sequence sequences))) + (egroup (encode-coding-string group 'utf-8))) (when (and group (equal (caar response) "OK")) (let ((uidnext (nnimap-find-parameter "UIDNEXT" response)) @@ -1203,38 +1325,38 @@ textual parts.") (setq highest (1- (string-to-number (car uidnext))))) (cond ((null highest) - (insert (format "%S 0 1 y\n" (utf7-decode group t)))) + (insert (format "%S 0 1 y\n" egroup))) ((zerop exists) ;; Empty group. - (insert (format "%S %d %d y\n" - (utf7-decode group t) + (insert (format "%S %d %d y\n" egroup highest (1+ highest)))) (t ;; Return the widest possible range. - (insert (format "%S %d 1 y\n" (utf7-decode group t) + (insert (format "%S %d 1 y\n" egroup (or highest exists))))))))) t))))) -(deffoo nnimap-request-newgroups (date &optional server) - (when (nnimap-possibly-change-group nil server) +(deffoo nnimap-request-newgroups (_date &optional server) + (when (nnimap-change-group nil server) (with-current-buffer nntp-server-buffer (erase-buffer) (dolist (group (with-current-buffer (nnimap-buffer) (nnimap-get-groups))) (unless (assoc group nnimap-current-infos) ;; Insert dummy numbers here -- they don't matter. - (insert (format "%S 0 1 y\n" (utf7-encode group))))) + (insert (format "%S 0 1 y\n" (encode-coding-string group 'utf-8))))) t))) (deffoo nnimap-retrieve-group-data-early (server infos) - (when (and (nnimap-possibly-change-group nil server) + (when (and (nnimap-change-group nil server) infos) (with-current-buffer (nnimap-buffer) (erase-buffer) (setf (nnimap-group nnimap-object) nil) (setf (nnimap-initial-resync nnimap-object) 0) (let ((qresyncp (nnimap-capability "QRESYNC")) - params groups sequences active uidvalidity modseq group) + params sequences active uidvalidity modseq group + unexist) ;; Go through the infos and gather the data needed to know ;; what and how to request the data. (dolist (info infos) @@ -1242,13 +1364,15 @@ textual parts.") group (nnimap-decode-gnus-group (gnus-group-real-name (gnus-info-group info))) active (cdr (assq 'active params)) + unexist (assq 'unexist (gnus-info-marks info)) uidvalidity (cdr (assq 'uidvalidity params)) modseq (cdr (assq 'modseq params))) (setf (nnimap-examined nnimap-object) group) (if (and qresyncp uidvalidity active - modseq) + modseq + unexist) (push (list (nnimap-send-command "EXAMINE %S (%s (%s %s))" (utf7-encode group t) @@ -1267,11 +1391,10 @@ textual parts.") ;; is read-only or not. "SELECT")) start) - (if (and active uidvalidity) + (if (and active uidvalidity unexist) ;; Fetch the last 100 flags. (setq start (max 1 (- (cdr active) 100))) - (setf (nnimap-initial-resync nnimap-object) - (1+ (nnimap-initial-resync nnimap-object))) + (incf (nnimap-initial-resync nnimap-object)) (setq start 1)) (push (list (nnimap-send-command "%s %S" command (utf7-encode group t)) @@ -1289,9 +1412,10 @@ textual parts.") command (nth 2 quirk)))) -(deffoo nnimap-finish-retrieve-group-infos (server infos sequences) +(deffoo nnimap-finish-retrieve-group-infos (server infos sequences + &optional dont-insert) (when (and sequences - (nnimap-possibly-change-group nil server t) + (nnimap-change-group nil server t) ;; Check that the process is still alive. (get-buffer-process (nnimap-buffer)) (memq (process-status (get-buffer-process (nnimap-buffer))) @@ -1309,19 +1433,20 @@ textual parts.") (nnimap-parse-flags (nreverse sequences))) infos) - ;; Finally, just return something resembling an active file in - ;; the nntp buffer, so that the agent can save the info, too. - (with-current-buffer nntp-server-buffer - (erase-buffer) - (dolist (info infos) - (let* ((group (gnus-info-group info)) - (active (gnus-active group))) - (when active - (insert (format "%S %d %d y\n" - (decode-coding-string - (gnus-group-real-name group) 'utf-8) - (cdr active) - (car active))))))))))) + (unless dont-insert + ;; Finally, just return something resembling an active file in + ;; the nntp buffer, so that the agent can save the info, too. + (with-current-buffer nntp-server-buffer + (erase-buffer) + (dolist (info infos) + (let* ((group (gnus-info-group info)) + (active (gnus-active group))) + (when active + (insert (format "%S %d %d y\n" + (decode-coding-string + (gnus-group-real-name group) 'utf-8) + (cdr active) + (car active)))))))))))) (defun nnimap-update-infos (flags infos) (dolist (info infos) @@ -1409,7 +1534,9 @@ textual parts.") (gnus-set-difference (gnus-set-difference existing - (cdr (assoc '%Seen flags))) + (gnus-sorted-union + (cdr (assoc '%Seen flags)) + (cdr (assoc '%Deleted flags)))) (cdr (assoc '%Flagged flags))))) (read (gnus-range-difference (cons start-article high) unread))) @@ -1448,6 +1575,25 @@ textual parts.") (setq new-marks (gnus-range-nconcat old-marks new-marks))) (when new-marks (push (cons (car type) new-marks) marks))))) + ;; Keep track of non-existing articles. + (let* ((old-unexists (assq 'unexist marks)) + (active (gnus-active group)) + (unexists + (if completep + (gnus-range-difference + active + (gnus-compress-sequence existing)) + (gnus-add-to-range + (cdr old-unexists) + (gnus-list-range-difference + existing (gnus-active group)))))) + (when (> (car active) 1) + (setq unexists (gnus-range-add + (cons 1 (1- (car active))) + unexists))) + (if old-unexists + (setcdr old-unexists unexists) + (push (cons 'unexist unexists) marks))) (gnus-info-set-marks info marks t)))) ;; Tell Gnus whether there are any \Recent messages in any of ;; the groups. @@ -1491,6 +1637,14 @@ textual parts.") (gnus-sorted-complement existing new-marks)))) (when ticks (push (cons (car type) ticks) marks))) + (gnus-info-set-marks info marks t)) + ;; Add vanished to the list of unexisting articles. + (when vanished + (let* ((old-unexists (assq 'unexist marks)) + (unexists (gnus-range-add (cdr old-unexists) vanished))) + (if old-unexists + (setcdr old-unexists unexists) + (push (cons 'unexist unexists) marks))) (gnus-info-set-marks info marks t)))) (defun nnimap-imap-ranges-to-gnus-ranges (irange) @@ -1515,7 +1669,7 @@ textual parts.") (push (list group info active) nnimap-current-infos)))) (defun nnimap-flags-to-marks (groups) - (let (data group totalp uidnext articles start-article mark permanent-flags + (let (data group uidnext articles start-article mark permanent-flags uidvalidity vanished highestmodseq) (dolist (elem groups) (setq group (car elem) @@ -1606,7 +1760,7 @@ textual parts.") (setq start (point)) (goto-char end)) (while (re-search-forward "^\\* [0-9]+ FETCH " start t) - (let ((p (point))) + (progn (setq elems (read (current-buffer))) (push (cons (cadr (memq 'UID elems)) (cadr (memq 'FLAGS elems))) @@ -1624,10 +1778,11 @@ textual parts.") (defun nnimap-find-process-buffer (buffer) (cadr (assoc buffer nnimap-connection-alist))) -(deffoo nnimap-request-post (&optional server) +(deffoo nnimap-request-post (&optional _server) (setq nnimap-status-string "Read-only server") nil) +(defvar gnus-refer-thread-use-nnir) ;; gnus-sum.el (declare-function gnus-fetch-headers "gnus-sum" (articles &optional limit force-new dependencies)) @@ -1638,7 +1793,7 @@ textual parts.") (setq group (nnimap-decode-gnus-group group))) (if gnus-refer-thread-use-nnir (nnir-search-thread header) - (when (nnimap-possibly-change-group group server) + (when (nnimap-change-group group server) (let* ((cmd (nnimap-make-thread-query header)) (result (with-current-buffer (nnimap-buffer) (nnimap-command "UID SEARCH %s" cmd)))) @@ -1649,7 +1804,14 @@ textual parts.") (cdr (assoc "SEARCH" (cdr result)))))) nil t)))))) -(defun nnimap-possibly-change-group (group server &optional no-reconnect) +(defun nnimap-change-group (group &optional server no-reconnect read-only) + "Change group to GROUP if non-nil. +If SERVER is set, check that server is connected, otherwise retry +to reconnect, unless NO-RECONNECT is set to t. Return nil if +unsuccessful in connecting. +If GROUP is nil, return t. +If READ-ONLY is set, send EXAMINE rather than SELECT to the server. +Return the server's response to the SELECT or EXAMINE command." (let ((open-result t)) (when (and server (not (nnimap-server-opened server))) @@ -1661,13 +1823,15 @@ textual parts.") t) (t (with-current-buffer (nnimap-buffer) - (if (equal group (nnimap-group nnimap-object)) - t - (let ((result (nnimap-command "SELECT %S" (utf7-encode group t)))) - (when (car result) - (setf (nnimap-group nnimap-object) group - (nnimap-select-result nnimap-object) result) - result)))))))) + (let ((result (nnimap-command "%s %S" + (if read-only + "EXAMINE" + "SELECT") + (utf7-encode group t)))) + (when (car result) + (setf (nnimap-group nnimap-object) group + (nnimap-select-result nnimap-object) result) + result))))))) (defun nnimap-find-connection (buffer) "Find the connection delivering to BUFFER." @@ -1703,15 +1867,24 @@ textual parts.") (defvar nnimap-record-commands nil "If non-nil, log commands to the \"*imap log*\" buffer.") +(defun nnimap-log-buffer () + (let ((name "*imap log*")) + (or (get-buffer name) + (with-current-buffer (get-buffer-create name) + (when (boundp 'window-point-insertion-type) + (make-local-variable 'window-point-insertion-type) + (setq window-point-insertion-type t)) + (current-buffer))))) + (defun nnimap-log-command (command) (when nnimap-record-commands - (with-current-buffer (get-buffer-create "*imap log*") + (with-current-buffer (nnimap-log-buffer) (goto-char (point-max)) (insert (format-time-string "%H:%M:%S") - " [" nnimap-address "] " - (if nnimap-inhibit-logging - "(inhibited)\n" - command)))) + " [" nnimap-address "] " + (if nnimap-inhibit-logging + "(inhibited)\n" + command)))) command) (defun nnimap-command (&rest args) @@ -1759,7 +1932,7 @@ textual parts.") (while (and (not (bobp)) (progn (forward-line -1) - (looking-at "\\*")))) + (looking-at "\\*\\|[0-9]+ OK NOOP")))) (not (looking-at (format "%d .*\n" sequence))))) (when messagep (nnheader-message-maybe @@ -1850,15 +2023,6 @@ textual parts.") (forward-line 1))) (buffer-substring (point) end)))) -(defun nnimap-get-responses (sequences) - (let (responses) - (dolist (sequence sequences) - (goto-char (point-min)) - (when (re-search-forward (format "^%d " sequence) nil t) - (push (list sequence (nnimap-parse-response)) - responses))) - responses)) - (defvar nnimap-incoming-split-list nil) (defun nnimap-fetch-inbox (articles) @@ -1927,12 +2091,15 @@ textual parts.") (ranges (cdr spec))) (if (eq group 'junk) (setq junk-articles ranges) - (push (list (nnimap-send-command - "UID COPY %s %S" - (nnimap-article-ranges ranges) - (utf7-encode group t)) - ranges) - sequences)))) + ;; Don't copy if the message is already in its + ;; target group. + (unless (string= group nnimap-inbox) + (push (list (nnimap-send-command + "UID COPY %s %S" + (nnimap-article-ranges ranges) + (utf7-encode group t)) + ranges) + sequences))))) ;; Wait for the last COPY response... (when sequences (nnimap-wait-for-response (caar sequences)) @@ -2013,10 +2180,10 @@ textual parts.") (forward-char (1+ bytes)) (delete-region (line-beginning-position) (line-end-position))))))) -(defun nnimap-dummy-active-number (group &optional server) +(defun nnimap-dummy-active-number (_group &optional _server) 1) -(defun nnimap-save-mail-spec (group-art &optional server full-nov) +(defun nnimap-save-mail-spec (group-art &optional _server _full-nov) (let (article) (goto-char (point-min)) (if (not (re-search-forward "X-nnimap-article: \\([0-9]+\\)" nil t))