Get credentials for numerical equivalents of the port numbers.
[gnus] / lisp / nnimap.el
index 6274e2f..4c3eaac 100644 (file)
@@ -63,6 +63,9 @@ This is always done if the server supports UID EXPUNGE, but it's
 not done by default on servers that doesn't support that command.")
 
 (defvoo nnimap-connection-alist nil)
+
+(defvoo nnimap-current-infos nil)
+
 (defvar nnimap-process nil)
 
 (defvar nnimap-status-string "")
@@ -71,7 +74,7 @@ not done by default on servers that doesn't support that command.")
   "Internal variable with default value for `nnimap-split-download-body'.")
 
 (defstruct nnimap
-  group process commands capabilities)
+  group process commands capabilities select-result)
 
 (defvar nnimap-object nil)
 
@@ -91,11 +94,7 @@ not done by default on servers that doesn't support that command.")
 (defun nnimap-buffer ()
   (nnimap-find-process-buffer nntp-server-buffer))
 
-(defmacro nnimap-with-process-buffer (&rest body)
-  `(with-current-buffer (nnimap-find-process-buffer (current-buffer))
-     ,@body))
-
-(defun nnimap-retrieve-headers (articles &optional group server fetch-old)
+(deffoo nnimap-retrieve-headers (articles &optional group server fetch-old)
   (with-current-buffer nntp-server-buffer
     (erase-buffer)
     (when (nnimap-possibly-change-group group server)
@@ -171,7 +170,7 @@ not done by default on servers that doesn't support that command.")
         result))
       (mapconcat #'identity (nreverse result) ",")))))
 
-(defun nnimap-open-server (server &optional defs)
+(deffoo nnimap-open-server (server &optional defs)
   (if (nnimap-server-opened server)
       t
     (unless (assq 'nnimap-address defs)
@@ -203,6 +202,17 @@ not done by default on servers that doesn't support that command.")
                                  ?p port)))))
     process))
 
+(defun nnimap-credentials (address &rest ports)
+  (let (port credentials)
+    ;; Request the credentials from all ports, but only query on the
+    ;; last port if all the previous ones have failed.
+    (while (and (null credentials)
+               (setq port (pop ports)))
+      (setq credentials
+           (auth-source-user-or-password
+            '("login" "password") address port nil (null ports))))
+    credentials))
+
 (defun nnimap-open-connection (buffer)
   (with-current-buffer (nnimap-make-process-buffer buffer)
     (let* ((coding-system-for-read 'binary)
@@ -215,43 +225,42 @@ not done by default on servers that doesn't support that command.")
                                       (if (netrc-find-service-number "imap")
                                           "imap"
                                         "143")))
-             (netrc-credentials nnimap-address "imap"))
+             (nnimap-credentials nnimap-address "143" "imap"))
             ((eq nnimap-stream 'stream)
              (nnimap-open-shell-stream
               "*nnimap*" (current-buffer) nnimap-address
               (or nnimap-server-port "imap"))
-             (netrc-credentials nnimap-address "imap"))
+             (nnimap-credentials nnimap-address "imap"))
             ((eq nnimap-stream 'ssl)
              (open-tls-stream "*nnimap*" (current-buffer) nnimap-address
                               (or nnimap-server-port
                                   (if (netrc-find-service-number "imaps")
                                       "imaps"
                                     "993")))
-             (netrc-credentials nnimap-address "imaps" "imap")))))
+             (nnimap-credentials nnimap-address "143" "993" "imap" "imaps")))))
       (setf (nnimap-process nnimap-object)
            (get-buffer-process (current-buffer)))
       (unless credentials
-       (delete-process (nnimap-process nnimap-object))
-       (error "Can't find user name/password for %s" nnimap-address))
+       (delete-process (nnimap-process nnimap-object)))
       (when (and (nnimap-process nnimap-object)
                 (memq (process-status (nnimap-process nnimap-object))
                       '(open run)))
        (gnus-set-process-query-on-exit-flag (nnimap-process nnimap-object) nil)
        (let ((result (nnimap-command "LOGIN %S %S"
                                      (car credentials) (cadr credentials))))
-         (unless (car result)
-           (delete-process (nnimap-process nnimap-object))
-           (error "Unable to login to the server: %s"
-                  (mapconcat #'identity (cadr result) " ")))
-         (setf (nnimap-capabilities nnimap-object)
-               (mapcar
-                #'upcase
-                (or (nnimap-find-parameter "CAPABILITY" (cdr result))
-                    (nnimap-find-parameter
-                     "CAPABILITY" (cdr (nnimap-command "CAPABILITY"))))))
-         (when (member "QRESYNC" (nnimap-capabilities nnimap-object))
-           (nnimap-command "ENABLE QRESYNC"))
-         t)))))
+         (if (not (car result))
+             (progn
+               (delete-process (nnimap-process nnimap-object))
+               nil)
+           (setf (nnimap-capabilities nnimap-object)
+                 (mapcar
+                  #'upcase
+                  (or (nnimap-find-parameter "CAPABILITY" (cdr result))
+                      (nnimap-find-parameter
+                       "CAPABILITY" (cdr (nnimap-command "CAPABILITY"))))))
+           (when (member "QRESYNC" (nnimap-capabilities nnimap-object))
+             (nnimap-command "ENABLE QRESYNC"))
+           t))))))
 
 (defun nnimap-find-parameter (parameter elems)
   (let (result)
@@ -265,22 +274,22 @@ not done by default on servers that doesn't support that command.")
        (setq result (cdr (cadr elem))))))
     result))
 
-(defun nnimap-close-server (&optional server)
+(deffoo nnimap-close-server (&optional server)
   t)
 
-(defun nnimap-request-close ()
+(deffoo nnimap-request-close ()
   t)
 
-(defun nnimap-server-opened (&optional server)
+(deffoo nnimap-server-opened (&optional server)
   (and (nnoo-current-server-p 'nnimap server)
        nntp-server-buffer
        (gnus-buffer-live-p nntp-server-buffer)
        (nnimap-find-connection nntp-server-buffer)))
 
-(defun nnimap-status-message (&optional server)
+(deffoo nnimap-status-message (&optional server)
   nnimap-status-string)
 
-(defun nnimap-request-article (article &optional group server to-buffer)
+(deffoo nnimap-request-article (article &optional group server to-buffer)
   (with-current-buffer nntp-server-buffer
     (let ((result (nnimap-possibly-change-group group server)))
       (when (stringp article)
@@ -288,14 +297,14 @@ not done by default on servers that doesn't support that command.")
       (when (and result
                 article)
        (erase-buffer)
-       (nnimap-with-process-buffer
-        (erase-buffer)
-        (setq result
-              (nnimap-command
-               (if (member "IMAP4REV1" (nnimap-capabilities nnimap-object))
-                   "UID FETCH %d BODY.PEEK[]"
-                 "UID FETCH %d RFC822.PEEK")
-               article)))
+       (with-current-buffer (nnimap-buffer)
+         (erase-buffer)
+         (setq result
+               (nnimap-command
+                (if (member "IMAP4REV1" (nnimap-capabilities nnimap-object))
+                    "UID FETCH %d BODY.PEEK[]"
+                  "UID FETCH %d RFC822.PEEK")
+                article)))
        (let ((buffer (nnimap-find-process-buffer (current-buffer))))
          (when (car result)
            (with-current-buffer to-buffer
@@ -309,20 +318,39 @@ not done by default on servers that doesn't support that command.")
                (nnheader-ms-strip-cr))
              t)))))))
 
-(defun nnimap-request-group (group &optional server dont-check)
+(deffoo nnimap-request-group (group &optional server dont-check info)
   (with-current-buffer nntp-server-buffer
     (let ((result (nnimap-possibly-change-group group server))
-         articles)
+         articles active marks)
       (when result
-       (setq articles (nnimap-get-flags "1:*"))
-       (erase-buffer)
-       (insert
-        (format
-         "211 %d %d %d %S\n"
-         (length articles)
-         (or (caar articles) 0)
-         (or (caar (last articles)) 0)
-         group))
+       (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)))
+                 (flag-sequence
+                  (nnimap-send-command "UID FETCH 1:* FLAGS")))
+             (nnimap-wait-for-response flag-sequence)
+             (setq marks
+                   (nnimap-flags-to-marks
+                    (nnimap-parse-flags
+                     (list (list group-sequence flag-sequence 1 group)))))
+             (when info
+               (nnimap-update-infos marks (list info)))))
+         (erase-buffer)
+         (let ((high (nth 3 (car marks)))
+               (low (nth 4 (car marks))))
+           (insert
+            (format
+             "211 %d %d %d %S\n"
+             (1+ (- high low))
+             low high group))))
        t))))
 
 (defun nnimap-get-flags (spec)
@@ -340,7 +368,7 @@ not done by default on servers that doesn't support that command.")
              articles)))
     (nreverse articles)))
 
-(defun nnimap-close-group (group &optional server)
+(deffoo nnimap-close-group (group &optional server)
   t)
 
 (deffoo nnimap-request-move-article (article group server accept-form
@@ -412,10 +440,10 @@ not done by default on servers that doesn't support that command.")
        (push flag flags)))
     flags))
 
-(defun nnimap-request-set-mark (group actions &optional server)
+(deffoo nnimap-request-set-mark (group actions &optional server)
   (when (nnimap-possibly-change-group group server)
     (let (sequence)
-      (with-current-buffer (nnimap-find-process-buffer nntp-server-buffer)
+      (with-current-buffer (nnimap-buffer)
        ;; Just send all the STORE commands without waiting for
        ;; response.  If they're successful, they're successful.
        (dolist (action actions)
@@ -439,17 +467,16 @@ not done by default on servers that doesn't support that command.")
     (let ((message (buffer-string))
          (message-id (message-field-value "message-id"))
          sequence)
-      (with-current-buffer nntp-server-buffer
-       (nnimap-with-process-buffer
-        (setq sequence (nnimap-send-command
-                        "APPEND %S {%d}" (utf7-encode group t)
-                        (length message)))
-        (process-send-string (get-buffer-process (current-buffer)) message)
-        (process-send-string (get-buffer-process (current-buffer)) "\r\n")
-        (let ((result (nnimap-get-response sequence)))
-          (when result
-            (cons group
-                  (nnimap-find-article-by-message-id group message-id)))))))))
+      (with-current-buffer (nnimap-buffer)
+       (setq sequence (nnimap-send-command
+                       "APPEND %S {%d}" (utf7-encode group t)
+                       (length message)))
+       (process-send-string (get-buffer-process (current-buffer)) message)
+       (process-send-string (get-buffer-process (current-buffer)) "\r\n")
+       (let ((result (nnimap-get-response sequence)))
+         (when result
+           (cons group
+                 (nnimap-find-article-by-message-id group message-id))))))))
 
 (defun nnimap-add-cr ()
   (goto-char (point-min))
@@ -467,23 +494,23 @@ not done by default on servers that doesn't support that command.")
          (push (car (last line)) groups)))
       (nreverse groups))))
 
-(defun nnimap-request-list (&optional server)
+(deffoo nnimap-request-list (&optional server)
   (nnimap-possibly-change-group nil server)
   (with-current-buffer nntp-server-buffer
     (erase-buffer)
     (let ((groups
-          (nnimap-with-process-buffer
-           (nnimap-get-groups)))
+          (with-current-buffer (nnimap-buffer)
+            (nnimap-get-groups)))
          sequences responses)
       (when groups
-       (nnimap-with-process-buffer
-        (dolist (group groups)
-          (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))))
+       (with-current-buffer (nnimap-buffer)
+         (dolist (group groups)
+           (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))
@@ -510,7 +537,7 @@ not done by default on servers that doesn't support that command.")
                                  (or highest exists)))))))))
        t))))
 
-(defun nnimap-retrieve-group-data-early (server infos)
+(deffoo nnimap-retrieve-group-data-early (server infos)
   (when (nnimap-possibly-change-group nil server)
     (with-current-buffer (nnimap-buffer)
       ;; QRESYNC handling isn't implemented.
@@ -550,8 +577,9 @@ not done by default on servers that doesn't support that command.")
                    sequences))))
        sequences))))
 
-(defun nnimap-finish-retrieve-group-infos (server infos sequences)
-  (when (nnimap-possibly-change-group nil server)
+(deffoo nnimap-finish-retrieve-group-infos (server infos sequences)
+  (when (and sequences
+            (nnimap-possibly-change-group nil server))
     (with-current-buffer (nnimap-buffer)
       ;; Wait for the final data to trickle in.
       (nnimap-wait-for-response (cadar sequences))
@@ -622,7 +650,15 @@ not done by default on servers that doesn't support that command.")
                (setq new-marks (gnus-range-nconcat old-marks new-marks)))
              (when new-marks
                (push (cons (car type) new-marks) marks)))
-           (gnus-info-set-marks info marks)))))))
+           (gnus-info-set-marks info marks)
+           (nnimap-store-info info (gnus-active group))))))))
+
+(defun nnimap-store-info (info active)
+  (let* ((group (gnus-group-real-name (gnus-info-group info)))
+        (entry (assoc group nnimap-current-infos)))
+    (if entry
+       (setcdr entry (list info active))
+      (push (list group info active) nnimap-current-infos))))
 
 (defun nnimap-flags-to-marks (groups)
   (let (data group totalp uidnext articles start-article mark)
@@ -676,23 +712,29 @@ not done by default on servers that doesn't support that command.")
 (defun nnimap-find-process-buffer (buffer)
   (cadr (assoc buffer nnimap-connection-alist)))
 
-(defun nnimap-request-post (&optional server)
+(deffoo nnimap-request-post (&optional server)
   (setq nnimap-status-string "Read-only server")
   nil)
 
 (defun nnimap-possibly-change-group (group server)
-  (when (and server
-            (not (nnimap-server-opened server)))
-    (nnimap-open-server server))
-  (if (not group)
-      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)
-           result))))))
+  (let ((open-result t))
+    (when (and server
+              (not (nnimap-server-opened server)))
+      (setq open-result (nnimap-open-server server)))
+    (cond
+     ((not open-result)
+      nil)
+     ((not group)
+      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))))))))
 
 (defun nnimap-find-connection (buffer)
   "Find the connection delivering to BUFFER."