Ma Gnus v0.11 is released
[gnus] / lisp / nnimap.el
index ebd268e..1730bd4 100644 (file)
@@ -1,6 +1,6 @@
 ;;; nnimap.el --- IMAP interface for Gnus
 
-;; Copyright (C) 2010-2012 Free Software Foundation, Inc.
+;; Copyright (C) 2010-2014 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;;         Simon Josefsson <simon@josefsson.org>
 
 ;;; 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
@@ -95,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.
@@ -129,8 +126,8 @@ textual parts.")
 
 (defcustom nnimap-request-articles-find-limit nil
   "Limit the number of articles to look for after moving an article."
-  :type 'integer
-  :version "24.2"
+  :type '(choice (const nil) integer)
+  :version "24.4"
   :group 'nnimap)
 
 (defvar nnimap-process nil)
@@ -254,7 +251,9 @@ textual parts.")
          (insert (format "Chars: %s\n" size)))
        (when lines
          (insert (format "Lines: %s\n" lines)))
-       (unless (re-search-forward "^\r$" nil t)
+       ;; 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 ".")
@@ -349,7 +348,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
@@ -377,7 +377,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)
@@ -454,8 +454,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)
@@ -498,9 +498,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")))
@@ -513,9 +517,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
@@ -616,6 +624,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
@@ -857,6 +885,8 @@ textual parts.")
 (deffoo nnimap-request-move-article (article group server accept-form
                                             &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
@@ -980,27 +1010,33 @@ textual parts.")
                               (cdr (assoc "SEARCH" (cdr result))))))))))
 
 
-(defun nnimap-find-article-by-message-id (group server message-id &optional limit)
+(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)
-    (let* ((number-of-article
-           (catch 'found
-             (dolist (result (cdr (nnimap-change-group group server nil t)))
-               (when (equal "EXISTS" (cadr result))
-                 (throw 'found (car result))))))
+    (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)))
+            (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)))))))
+        (let ((article (car (last (cdr (assoc "SEARCH"
+                                             (nnimap-parse-response)))))))
           (if article
               (string-to-number article)
             (when (and limit number-of-article)
@@ -1079,6 +1115,14 @@ If LIMIT, first try to limit the search to the N last articles."
          (nnimap-wait-for-response sequence))))))
 
 (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
+           `(lambda (group)
+              (nnml-active-number group ,server))))))
   (setq group (nnimap-decode-gnus-group group))
   (when (nnimap-change-group nil server)
     (nnmail-check-syntax)
@@ -1194,39 +1238,61 @@ If LIMIT, first try to limit the search to the N last articles."
                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-change-group nil server)
     (with-current-buffer nntp-server-buffer
       (erase-buffer)
-      (dolist (response
-               (with-current-buffer (nnimap-buffer)
-                 ;; Build a list of (group result-of-EXAMINE) for each group
-                 (mapcar
-                  (lambda (group)
-                    (list group (cdr (nnimap-change-group group server nil t))))
-                  (nnimap-get-groups))))
-        (let ((group (encode-coding-string (car response) 'utf-8))
-              (response (cadr response)))
-          (when (equal (caar response) "OK")
-            (let ((uidnext (nnimap-find-parameter "UIDNEXT" response))
-                highest exists)
-              (dolist (elem response)
-                (when (equal (cadr elem) "EXISTS")
-                  (setq exists (string-to-number (car elem)))))
-              (when uidnext
-                (setq highest (1- (string-to-number (car uidnext)))))
-              (cond
-               ((null highest)
-                (insert (format "%S 0 1 y\n" group)))
-               ((zerop exists)
-                ;; Empty group.
-                (insert (format "%S %d %d y\n" group
-                                highest (1+ highest))))
-               (t
-                ;; Return the widest possible range.
-                (insert (format "%S %d 1 y\n" group
-                                (or highest exists)))))))))
-      t)))
+      (let ((groups
+            (with-current-buffer (nnimap-buffer)
+              (nnimap-get-groups)))
+           sequences responses)
+       (when groups
+         (with-current-buffer (nnimap-buffer)
+           (setf (nnimap-group nnimap-object) nil)
+           (dolist (group groups)
+             (setf (nnimap-examined nnimap-object) group)
+             (push (list (nnimap-send-command "EXAMINE %S"
+                                              (utf7-encode group t))
+                         group)
+                   sequences))
+           (nnimap-wait-for-response (caar sequences))
+           (setq responses
+                 (nnimap-get-responses (mapcar #'car sequences))))
+         (dolist (response responses)
+           (let* ((sequence (car response))
+                  (response (cadr response))
+                  (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))
+                     highest exists)
+                 (dolist (elem response)
+                   (when (equal (cadr elem) "EXISTS")
+                     (setq exists (string-to-number (car elem)))))
+                 (when uidnext
+                   (setq highest (1- (string-to-number (car uidnext)))))
+                 (cond
+                  ((null highest)
+                   (insert (format "%S 0 1 y\n" egroup)))
+                  ((zerop exists)
+                   ;; Empty group.
+                   (insert (format "%S %d %d y\n" egroup
+                                   highest (1+ highest))))
+                  (t
+                   ;; Return the widest possible range.
+                   (insert (format "%S %d 1 y\n" egroup
+                                   (or highest exists)))))))))
+         t)))))
 
 (deffoo nnimap-request-newgroups (date &optional server)
   (when (nnimap-change-group nil server)
@@ -1424,7 +1490,9 @@ If LIMIT, first try to limit the search to the N last articles."
                     (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)))
@@ -1693,10 +1761,13 @@ If LIMIT, first try to limit the search to the N last articles."
            nil t))))))
 
 (defun nnimap-change-group (group &optional server no-reconnect read-only)
-  "Change group to GROUP.
+  "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.
-if READ-ONLY is set, send EXAMINE rather than SELECT to the server."
+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)))