Merge Emacs' changes.
[gnus] / lisp / nnimap.el
index a5a001f..fa09c7f 100644 (file)
@@ -61,9 +61,12 @@ If nnimap-stream is `ssl', this will default to `imaps'.  If not,
 it will default to `imap'.")
 
 (defvoo nnimap-stream 'undecided
-  "How nnimap will talk to the IMAP server.
-Values are `ssl', `network', `starttls' or `shell'.
-The default is to try `ssl' first, and then `network'.")
+  "How nnimap talks to the IMAP server.
+The value should be either `undecided', `ssl' or `tls',
+`network', `starttls', `plain', or `shell'.
+
+If the value is `undecided', nnimap tries `ssl' first, then falls
+back on `network'.")
 
 (defvoo nnimap-shell-program (if (boundp 'imap-shell-program)
                                 (if (listp imap-shell-program)
@@ -72,14 +75,15 @@ The default is to try `ssl' first, and then `network'.")
                               "ssh %s imapd"))
 
 (defvoo nnimap-inbox nil
-  "The mail box where incoming mail arrives and should be split out of.")
+  "The mail box where incoming mail arrives and should be split out of.
+For example, \"INBOX\".")
 
 (defvoo nnimap-split-methods nil
   "How mail is split.
-Uses the same syntax as nnmail-split-methods")
+Uses the same syntax as `nnmail-split-methods'.")
 
 (defvoo nnimap-split-fancy nil
-  "Uses the same syntax as nnmail-split-fancy.")
+  "Uses the same syntax as `nnmail-split-fancy'.")
 
 (defvoo nnimap-unsplittable-articles '(%Deleted %Seen)
   "Articles with the flags in the list will not be considered when splitting.")
@@ -276,18 +280,22 @@ textual parts.")
     (push (current-buffer) nnimap-process-buffers)
     (current-buffer)))
 
-(defun nnimap-credentials (address ports &optional inhibit-create)
-  (let* ((found (nth 0 (auth-source-search :max 1
+(defun nnimap-credentials (address ports)
+  (let* ((auth-source-creation-prompts
+          '((user  . "IMAP user at %h: ")
+            (secret . "IMAP password for %u@%h: ")))
+         (found (nth 0 (auth-source-search :max 1
                                            :host address
                                            :port ports
-                                           :create (if inhibit-create
-                                                       nil
-                                                     (null ports)))))
-         (user (plist-get found :user))
-         (secret (plist-get found :secret))
-         (secret (if (functionp secret) (funcall secret) secret)))
+                                           :require '(:user :secret)
+                                           :create t))))
     (if found
-        (list user secret)
+        (list (plist-get found :user)
+             (let ((secret (plist-get found :secret)))
+               (if (functionp secret)
+                   (funcall secret)
+                 secret))
+             (plist-get found :save-function))
       nil)))
 
 (defun nnimap-keepalive ()
@@ -333,11 +341,10 @@ textual parts.")
           (port nil)
           (ports
            (cond
-            ((or (eq nnimap-stream 'network)
-                 (eq nnimap-stream 'starttls))
+            ((memq nnimap-stream '(network plain starttls))
              (nnheader-message 7 "Opening connection to %s..."
                                nnimap-address)
-             '("143" "imap"))
+             '("imap" "143"))
             ((eq nnimap-stream 'shell)
              (nnheader-message 7 "Opening connection to %s via shell..."
                                nnimap-address)
@@ -345,24 +352,31 @@ textual parts.")
             ((memq nnimap-stream '(ssl tls))
              (nnheader-message 7 "Opening connection to %s via tls..."
                                nnimap-address)
-             '("143" "993" "imap" "imaps"))
+             '("imaps" "imap" "993" "143"))
             (t
              (error "Unknown stream type: %s" nnimap-stream))))
-          (proto-stream-always-use-starttls t)
            login-result credentials)
       (when nnimap-server-port
-       (setq ports (append ports (list nnimap-server-port))))
-      (destructuring-bind (stream greeting capabilities stream-type)
-         (open-protocol-stream
-          "*nnimap*" (current-buffer) nnimap-address (car (last ports))
-          :type nnimap-stream
-          :shell-command nnimap-shell-program
-          :capability-command "1 CAPABILITY\r\n"
-          :success " OK "
-          :starttls-function
-          (lambda (capabilities)
-            (when (gnus-string-match-p "STARTTLS" capabilities)
-              "1 STARTTLS\r\n")))
+       (push nnimap-server-port ports))
+      (let* ((stream-list
+             (open-protocol-stream
+              "*nnimap*" (current-buffer) nnimap-address (car ports)
+              :type nnimap-stream
+              :return-list t
+              :shell-command nnimap-shell-program
+              :capability-command "1 CAPABILITY\r\n"
+              :success " OK "
+              :starttls-function
+              (lambda (capabilities)
+                (when (gnus-string-match-p "STARTTLS" capabilities)
+                  "1 STARTTLS\r\n"))))
+            (stream (car stream-list))
+            (props (cdr stream-list))
+            (greeting (plist-get props :greeting))
+            (capabilities (plist-get props :capabilities))
+            (stream-type (plist-get props :type)))
+       (when (and stream (not (memq (process-status stream) '(open run))))
+         (setq stream nil))
        (setf (nnimap-process nnimap-object) stream)
        (setf (nnimap-stream-type nnimap-object) stream-type)
        (if (not stream)
@@ -386,21 +400,34 @@ textual parts.")
                                ;; Look for the credentials based on
                                ;; the virtual server name and the address
                                (nnimap-credentials
-                                (list
-                                 (nnoo-current-server 'nnimap)
-                                 nnimap-address)
-                                ports t))))
+                               (gnus-delete-duplicates
+                                (list
+                                 nnimap-address
+                                 (nnoo-current-server 'nnimap)))
+                                ports))))
                  (setq nnimap-object nil)
                (let ((nnimap-inhibit-logging t))
                  (setq login-result
                        (nnimap-login (car credentials) (cadr credentials))))
-               (unless (car login-result)
+               (if (car login-result)
+                   (progn
+                    ;; Save the credentials if a save function exists
+                    ;; (such a function will only be passed if a new
+                    ;; token was created).
+                     (when (functionp (nth 2 credentials))
+                       (funcall (nth 2 credentials)))
+                     ;; See if CAPABILITY is set as part of login
+                     ;; response.
+                     (dolist (response (cddr login-result))
+                       (when (string= "CAPABILITY" (upcase (car response)))
+                         (setf (nnimap-capabilities nnimap-object)
+                               (mapcar #'upcase (cdr response))))))
                  ;; If the login failed, then forget the credentials
                  ;; that are now possibly cached.
                  (dolist (host (list (nnoo-current-server 'nnimap)
                                      nnimap-address))
                    (dolist (port ports)
-                      (auth-source-forget+ :host host :protocol port)))
+                      (auth-source-forget+ :host host :port port)))
                  (delete-process (nnimap-process nnimap-object))
                  (setq nnimap-object nil))))
            (when nnimap-object
@@ -1075,60 +1102,62 @@ textual parts.")
     (nreverse groups)))
 
 (deffoo nnimap-request-list (&optional server)
-  (nnimap-possibly-change-group nil server)
-  (with-current-buffer nntp-server-buffer
-    (erase-buffer)
-    (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))))
-           (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" (utf7-decode group t))))
-                ((zerop exists)
-                 ;; Empty group.
-                 (insert (format "%S %d %d y\n"
-                                 (utf7-decode group t) highest (1+ highest))))
-                (t
-                 ;; Return the widest possible range.
-                 (insert (format "%S %d 1 y\n" (utf7-decode group t)
-                                 (or highest exists)))))))))
-       t))))
+  (when (nnimap-possibly-change-group nil server)
+    (with-current-buffer nntp-server-buffer
+      (erase-buffer)
+      (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))))
+             (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" (utf7-decode group t))))
+                  ((zerop exists)
+                   ;; Empty group.
+                   (insert (format "%S %d %d y\n"
+                                   (utf7-decode group t)
+                                   highest (1+ highest))))
+                  (t
+                   ;; Return the widest possible range.
+                   (insert (format "%S %d 1 y\n" (utf7-decode group t)
+                                   (or highest exists)))))))))
+         t)))))
 
 (deffoo nnimap-request-newgroups (date &optional server)
-  (nnimap-possibly-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" group))))
-    t))
+  (when (nnimap-possibly-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" group))))
+      t)))
 
 (deffoo nnimap-retrieve-group-data-early (server infos)
   (when (nnimap-possibly-change-group nil server)
@@ -1148,6 +1177,7 @@ textual parts.")
          (setf (nnimap-examined nnimap-object) group)
          (if (and qresyncp
                   uidvalidity
+                  active
                   modseq)
              (push
               (list (nnimap-send-command "EXAMINE %S (%s (%s %s))"
@@ -1437,6 +1467,11 @@ textual parts.")
   ;; Change \Delete etc to %Delete, so that the reader can read it.
   (subst-char-in-region (point-min) (point-max)
                        ?\\ ?% t)
+  ;; Remove any MODSEQ entries in the buffer, because they may contain
+  ;; numbers that are too large for 32-bit Emacsen.
+  (while (re-search-forward " MODSEQ ([0-9]+)" nil t)
+    (replace-match "" t t))
+  (goto-char (point-min))
   (let (start end articles groups uidnext elems permanent-flags
              uidvalidity vanished highestmodseq)
     (dolist (elem sequences)
@@ -1476,9 +1511,9 @@ textual parts.")
                            (match-string 1)))
                 (goto-char start)
                 (setq highestmodseq
-                      (and (search-forward "HIGHESTMODSEQ "
+                      (and (re-search-forward "HIGHESTMODSEQ \\([0-9]+\\)"
                                            (or end (point-min)) t)
-                           (read (current-buffer))))
+                           (match-string 1)))
                 (goto-char end)
                 (forward-line -1))
               ;; The UID FETCH FLAGS was successful.
@@ -1491,10 +1526,11 @@ textual parts.")
            (setq start (point))
            (goto-char end))
          (while (re-search-forward "^\\* [0-9]+ FETCH " start t)
-           (setq elems (read (current-buffer)))
-           (push (cons (cadr (memq 'UID elems))
-                       (cadr (memq 'FLAGS elems)))
-                 articles))
+           (let ((p (point)))
+             (setq elems (read (current-buffer)))
+             (push (cons (cadr (memq 'UID elems))
+                         (cadr (memq 'FLAGS elems)))
+                   articles)))
          (push (nconc (list group uidnext totalp permanent-flags uidvalidity
                             vanished highestmodseq)
                       articles)
@@ -1528,10 +1564,11 @@ textual parts.")
                               refid refid value)))))
         (result (with-current-buffer (nnimap-buffer)
                   (nnimap-command  "UID SEARCH %s" cmd))))
-    (gnus-fetch-headers
-     (and (car result) (delete 0 (mapcar #'string-to-number
-                                        (cdr (assoc "SEARCH" (cdr result))))))
-     nil t)))
+    (when result
+      (gnus-fetch-headers
+       (and (car result) (delete 0 (mapcar #'string-to-number
+                                          (cdr (assoc "SEARCH" (cdr result))))))
+       nil t))))
 
 (defun nnimap-possibly-change-group (group server)
   (let ((open-result t))
@@ -1589,7 +1626,7 @@ textual parts.")
     (goto-char (point-max))
     (insert (format-time-string "%H:%M:%S") " "
            (if nnimap-inhibit-logging
-               "(inhibited)"
+               "(inhibited)\n"
              command)))
   command)
 
@@ -1639,13 +1676,15 @@ textual parts.")
                                    (progn
                                      (forward-line -1)
                                      (looking-at "\\*"))))
-                       (not (looking-at (format "%d " sequence)))))
+                       (not (looking-at (format "%d .*\n" sequence)))))
            (when messagep
              (nnheader-message 7 "nnimap read %dk" (/ (buffer-size) 1000)))
            (nnheader-accept-process-output process)
            (goto-char (point-max)))
           openp)
       (quit
+       (when debug-on-quit
+        (debug "Quit"))
        ;; The user hit C-g while we were waiting: kill the process, in case
        ;; it's a gnutls-cli process that's stuck (tends to happen a lot behind
        ;; NAT routers).
@@ -1737,11 +1776,15 @@ textual parts.")
     (format "(UID %s%s)"
            (format
             (if (nnimap-ver4-p)
-                "BODY.PEEK[HEADER] BODY.PEEK"
+                "BODY.PEEK"
               "RFC822.PEEK"))
-           (if nnimap-split-download-body-default
-               "[]"
-             "[1]")))
+           (cond
+            (nnimap-split-download-body-default
+             "[]")
+            ((nnimap-ver4-p)
+             "[HEADER]")
+            (t
+             "[1]"))))
    t))
 
 (defun nnimap-split-incoming-mail ()
@@ -1821,7 +1864,7 @@ textual parts.")
 (defun nnimap-parse-copied-articles (sequences)
   (let (sequence copied range)
     (goto-char (point-min))
-    (while (re-search-forward "^\\([0-9]+\\) OK " nil t)
+    (while (re-search-forward "^\\([0-9]+\\) OK\\b" nil t)
       (setq sequence (string-to-number (match-string 1)))
       (when (setq range (cadr (assq sequence sequences)))
        (push (gnus-uncompress-range range) copied)))