Remove unused let variables
[gnus] / lisp / nnimap.el
index 49f0781..40610e1 100644 (file)
@@ -1,6 +1,6 @@
 ;;; nnimap.el --- IMAP interface for Gnus
 
-;; Copyright (C) 2010-2011 Free Software Foundation, Inc.
+;; Copyright (C) 2010-2015 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))
+  (require 'nnheader)
+  ;; In Emacs 24, `open-protocol-stream' is an autoloaded alias for
+  ;; `make-network-stream'.
+  (unless (fboundp 'open-protocol-stream)
+    (require 'proto-stream)))
 
 (eval-when-compile
   (require 'cl))
@@ -45,7 +45,6 @@
 (require 'tls)
 (require 'parse-time)
 (require 'nnmail)
-(require 'proto-stream)
 
 (autoload 'auth-source-forget+ "auth-source")
 (autoload 'auth-source-search "auth-source")
 (defvoo nnimap-address nil
   "The address of the IMAP server.")
 
+(defvoo nnimap-user nil
+  "Username to use for authentication to the IMAP server.")
+
 (defvoo nnimap-server-port nil
   "The IMAP port used.
 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', `network-only, `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)
@@ -74,7 +78,8 @@ Values are `ssl', `network', `network-only, `starttls' or
 
 (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.
@@ -86,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.
@@ -109,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 "")
@@ -126,7 +142,7 @@ textual parts.")
 
 (defstruct nnimap
   group process commands capabilities select-result newlinep server
-  last-command-time greeting examined stream-type)
+  last-command-time greeting examined stream-type initial-resync)
 
 (defvar nnimap-object nil)
 
@@ -150,19 +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)
+  (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
@@ -171,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
@@ -179,38 +206,56 @@ textual parts.")
 
 (defun nnimap-transform-headers ()
   (goto-char (point-min))
-  (let (article bytes lines size string)
+  (let (article lines size string labels)
     (block nil
       (while (not (eobp))
-       (while (not (looking-at "^\\* [0-9]+ FETCH.*UID \\([0-9]+\\)"))
+       (while (not (looking-at "\\* [0-9]+ FETCH"))
          (delete-region (point) (progn (forward-line 1) (point)))
          (when (eobp)
            (return)))
-       (setq article (match-string 1))
+       (goto-char (match-end 0))
        ;; Unfold quoted {number} strings.
-       (while (re-search-forward "[^]][ (]{\\([0-9]+\\)}\r?\n"
-                                 (1+ (line-end-position)) t)
+       (while (re-search-forward
+               "[^]][ (]{\\([0-9]+\\)}\r?\n"
+               (save-excursion
+                 ;; Start of the header section.
+                 (or (re-search-forward "] {[0-9]+}\r?\n" nil t)
+                     ;; Start of the next FETCH.
+                     (re-search-forward "\\* [0-9]+ FETCH" nil t)
+                     (point-max)))
+               t)
          (setq size (string-to-number (match-string 1)))
          (delete-region (+ (match-beginning 0) 2) (point))
          (setq string (buffer-substring (point) (+ (point) size)))
          (delete-region (point) (+ (point) size))
-         (insert (format "%S" string)))
-       (setq bytes (nnimap-get-length)
-             lines nil)
+         (insert (format "%S" (mm-subst-char-in-string ?\n ?\s string))))
        (beginning-of-line)
+       (setq article
+             (and (re-search-forward "UID \\([0-9]+\\)" (line-end-position)
+                                     t)
+                  (match-string 1)))
+       (setq lines nil)
        (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)))))
            (while (and (consp structure)
-                       (not (stringp (car structure))))
+                       (not (atom (car structure))))
              (setq structure (car structure)))
-           (setq lines (nth 7 structure))))
+           (setq lines (if (and
+                            (stringp (car structure))
+                            (equal (upcase (nth 0 structure)) "MESSAGE")
+                            (equal (upcase (nth 1 structure)) "RFC822"))
+                           (nth 9 structure)
+                         (nth 7 structure)))))
        (delete-region (line-beginning-position) (line-end-position))
        (insert (format "211 %s Article retrieved." article))
        (forward-line 1)
@@ -218,7 +263,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 ".")
@@ -254,18 +303,20 @@ textual parts.")
         result))
       (mapconcat #'identity (nreverse result) ",")))))
 
-(deffoo nnimap-open-server (server &optional defs)
+(deffoo nnimap-open-server (server &optional defs no-reconnect)
   (if (nnimap-server-opened server)
       t
     (unless (assq 'nnimap-address defs)
       (setq defs (append defs (list (list 'nnimap-address server)))))
     (nnoo-change-server 'nnimap server defs)
-    (or (nnimap-find-connection nntp-server-buffer)
-       (nnimap-open-connection nntp-server-buffer))))
+    (if no-reconnect
+       (nnimap-find-connection nntp-server-buffer)
+      (or (nnimap-find-connection nntp-server-buffer)
+         (nnimap-open-connection nntp-server-buffer)))))
 
 (defun nnimap-make-process-buffer (buffer)
   (with-current-buffer
-      (generate-new-buffer (format "*nnimap %s %s %s*"
+      (generate-new-buffer (format " *nnimap %s %s %s*"
                                   nnimap-address nnimap-server-port
                                   (gnus-buffer-exists-p buffer)))
     (mm-disable-multibyte)
@@ -273,22 +324,29 @@ textual parts.")
     (gnus-add-buffer)
     (set (make-local-variable 'after-change-functions) nil)
     (set (make-local-variable 'nnimap-object)
-        (make-nnimap :server (nnoo-current-server 'nnimap)))
+        (make-nnimap :server (nnoo-current-server 'nnimap)
+                     :initial-resync 0))
     (push (list buffer (current-buffer)) nnimap-connection-alist)
     (push (current-buffer) nnimap-process-buffers)
     (current-buffer)))
 
-(defun nnimap-credentials (address ports)
-  (let ((found (nth 0 (auth-source-search :max 1
-                                         :host address
-                                         :port ports
-                                         :create t))))
+(defun nnimap-credentials (address ports user)
+  (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
+                                           :user user
+                                           :require '(:user :secret)
+                                           :create t))))
     (if found
         (list (plist-get found :user)
              (let ((secret (plist-get found :secret)))
                (if (functionp secret)
                    (funcall secret)
-                 secret)))
+                 secret))
+             (plist-get found :save-function))
       nil)))
 
 (defun nnimap-keepalive ()
@@ -304,7 +362,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
@@ -324,21 +383,24 @@ textual parts.")
        nil
       stream)))
 
+(defun nnimap-map-port (port)
+  (if (equal port "imaps")
+      "993"
+    port))
+
 (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)
-          (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)
@@ -346,30 +408,48 @@ 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
+              (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"
+              :end-of-command "\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))
+
+        (when (and (fboundp 'set-network-process-option) ;; Not in XEmacs.
+                   (fboundp 'process-type) ;; Emacs 22 doesn't provide it.
+                   (eq (process-type stream) 'network))
+          ;; Use TCP-keepalive so that connections that pass through a NAT
+          ;; router don't hang when left idle.
+          (set-network-process-option stream :keepalive t))
+
        (setf (nnimap-process nnimap-object) stream)
        (setf (nnimap-stream-type nnimap-object) stream-type)
        (if (not stream)
            (progn
              (nnheader-report 'nnimap "Unable to contact %s:%s via %s"
-                              nnimap-address port nnimap-stream)
+                              nnimap-address (car ports) nnimap-stream)
              'no-connect)
          (gnus-set-process-query-on-exit-flag stream nil)
          (if (not (gnus-string-match-p "[*.] \\(OK\\|PREAUTH\\)" greeting))
@@ -389,14 +469,27 @@ textual parts.")
                                (nnimap-credentials
                                (gnus-delete-duplicates
                                 (list
-                                 nnimap-address
-                                 (nnoo-current-server 'nnimap)))
-                                ports))))
+                                  (nnoo-current-server 'nnimap)
+                                 nnimap-address))
+                                ports
+                                nnimap-user))))
                  (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)
@@ -408,6 +501,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")
@@ -418,9 +513,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")))
@@ -433,9 +532,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
@@ -485,11 +588,13 @@ textual parts.")
   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)
@@ -511,16 +616,17 @@ textual parts.")
                  (nnimap-get-whole-article article))
            (let ((buffer (current-buffer)))
              (with-current-buffer (or to-buffer nntp-server-buffer)
-               (erase-buffer)
-               (insert-buffer-substring buffer)
-               (nnheader-ms-strip-cr)
-               (cons group article)))))))))
+               (nnheader-insert-buffer-substring buffer)
+               (nnheader-ms-strip-cr)))
+           (cons group article)))))))
 
 (deffoo nnimap-request-head (article &optional group server to-buffer)
-  (when (nnimap-possibly-change-group group server)
+  (when group
+    (setq group (nnimap-decode-gnus-group group)))
+  (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
@@ -533,6 +639,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
@@ -623,12 +749,13 @@ textual parts.")
       (if (consp (caar structure))
          (nnimap-insert-partial-structure (pop structure) parts t)
        (let ((bit (pop structure)))
-         (insert (format  "Content-type: %s/%s"
-                          (downcase (nth 0 bit))
-                          (downcase (nth 1 bit))))
-         (if (member "CHARSET" (nth 2 bit))
+         (insert (format "Content-type: %s/%s"
+                         (downcase (nth 0 bit))
+                         (downcase (nth 1 bit))))
+         (if (member-ignore-case "CHARSET" (nth 2 bit))
              (insert (format
-                      "; charset=%S\n" (cadr (member "CHARSET" (nth 2 bit)))))
+                      "; charset=%S\n"
+                      (cadr (member-ignore-case "CHARSET" (nth 2 bit)))))
            (insert "\n"))
          (insert (format "Content-transfer-encoding: %s\n"
                          (nth 5 bit)))
@@ -663,8 +790,12 @@ textual parts.")
        (incf num)))
     (nreverse parts)))
 
+(defun nnimap-decode-gnus-group (group)
+  (decode-coding-string group 'utf-8))
+
 (deffoo nnimap-request-group (group &optional server dont-check info)
-  (let ((result (nnimap-possibly-change-group
+  (setq group (nnimap-decode-gnus-group 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)
@@ -672,58 +803,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)
-  (when (nnimap-possibly-change-group nil server)
+  (setq group (nnimap-decode-gnus-group group))
+  (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)
-  (when (nnimap-possibly-change-group nil server)
+  (setq group (nnimap-decode-gnus-group group))
+  (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)
-  (when (nnimap-possibly-change-group nil server)
+  (setq group (nnimap-decode-gnus-group group))
+  (when (nnimap-change-group nil server)
     (with-current-buffer (nnimap-buffer)
       (nnimap-unselect-group)
       (car (nnimap-command "RENAME %S %S"
@@ -737,7 +885,8 @@ textual parts.")
   (nnimap-command "EXAMINE DOES.NOT.EXIST"))
 
 (deffoo nnimap-request-expunge-group (group &optional server)
-  (when (nnimap-possibly-change-group group server)
+  (setq group (nnimap-decode-gnus-group group))
+  (when (nnimap-change-group group server)
     (with-current-buffer (nnimap-buffer)
       (car (nnimap-command "EXPUNGE")))))
 
@@ -764,6 +913,9 @@ 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
@@ -784,18 +936,21 @@ 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-change-group group server)
              (nnimap-delete-article article)
              result)))))))
 
 (deffoo nnimap-request-expire-articles (articles group &optional server force)
+  (setq group (nnimap-decode-gnus-group group))
   (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))
@@ -832,7 +987,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
@@ -847,57 +1002,74 @@ textual parts.")
          (with-temp-buffer
            (mm-disable-multibyte)
            (when (nnimap-request-article article group server (current-buffer))
-             (nnheader-message 7 "Expiring article %s:%d" group article)
              (when (functionp target)
                (setq target (funcall target group)))
-             (when (and target
-                        (not (eq target 'delete)))
-               (if (or (gnus-request-group target t)
-                       (gnus-request-create-group target))
-                   (nnmail-expiry-target-group target group)
-                 (setq target nil)))
+             (if (and target
+                      (not (eq target 'delete)))
+                 (if (or (gnus-request-group target t)
+                         (gnus-request-create-group target))
+                     (progn
+                       (nnmail-expiry-target-group target group)
+                       (nnheader-message 7 "Expiring article %s:%d to %s"
+                                         group article target))
+                   (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 (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)
@@ -916,11 +1088,17 @@ textual parts.")
                                 "delete this article now"))))))
 
 (deffoo nnimap-request-scan (&optional group server)
-  (when (and (nnimap-possibly-change-group nil server)
+  (when group
+    (setq group (nnimap-decode-gnus-group group)))
+  (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)
   (let (flags flag)
@@ -930,7 +1108,8 @@ textual parts.")
     flags))
 
 (deffoo nnimap-request-update-group-status (group status &optional server)
-  (when (nnimap-possibly-change-group nil server)
+  (setq group (nnimap-decode-gnus-group group))
+  (when (nnimap-change-group nil server)
     (let ((command (assoc
                    status
                    '((subscribe "SUBSCRIBE")
@@ -940,7 +1119,8 @@ textual parts.")
          (nnimap-command "%s %S" (cadr command) (utf7-encode group t)))))))
 
 (deffoo nnimap-request-set-mark (group actions &optional server)
-  (when (nnimap-possibly-change-group group server)
+  (setq group (nnimap-decode-gnus-group group))
+  (when (nnimap-change-group group server)
     (let (sequence)
       (with-current-buffer (nnimap-buffer)
        (erase-buffer)
@@ -959,12 +1139,24 @@ textual parts.")
                                 ((eq action 'set) ""))
                                (mapconcat #'identity flags " ")))))))
        ;; Wait for the last command to complete to avoid later
-       ;; syncronisation problems with the stream.
+       ;; synchronization problems with the stream.
        (when sequence
          (nnimap-wait-for-response sequence))))))
 
 (deffoo nnimap-request-accept-article (group &optional server last)
-  (when (nnimap-possibly-change-group nil server)
+  (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-change-group nil server)
     (nnmail-check-syntax)
     (let ((message-id (message-field-value "message-id"))
          sequence message)
@@ -996,7 +1188,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)
@@ -1040,8 +1233,9 @@ textual parts.")
     result))
 
 (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
@@ -1068,16 +1262,30 @@ textual parts.")
     (while (search-forward "* LIST " nil t)
       (let ((flags (read (current-buffer)))
            (separator (read (current-buffer)))
-           (group (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 (if (stringp group)
-                   group
-                 (format "%s" group))
+         (push (utf7-decode (if (stringp group)
+                                group
+                              (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
@@ -1099,7 +1307,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))
@@ -1111,49 +1320,54 @@ 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)
+  (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" group))))
+         (insert (format "%S 0 1 y\n" (encode-coding-string group 'utf-8)))))
       t)))
 
 (deffoo nnimap-retrieve-group-data-early (server infos)
-  (when (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 groups 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)
          (setq params (gnus-info-params info)
-               group (gnus-group-real-name (gnus-info-group info))
+               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)
@@ -1162,12 +1376,7 @@ textual parts.")
                     'qresync
                     nil group 'qresync)
               sequences)
-           (let ((start
-                  (if (and active uidvalidity)
-                      ;; Fetch the last 100 flags.
-                      (max 1 (- (cdr active) 100))
-                    1))
-                 (command
+           (let ((command
                   (if uidvalidity
                       "EXAMINE"
                     ;; If we don't have a UIDVALIDITY, then this is
@@ -1175,7 +1384,13 @@ textual parts.")
                     ;; have to do a SELECT (which is slower than an
                     ;; examine), but will tell us whether the group
                     ;; is read-only or not.
-                    "SELECT")))
+                    "SELECT"))
+                 start)
+             (if (and active uidvalidity unexist)
+                 ;; Fetch the last 100 flags.
+                 (setq start (max 1 (- (cdr active) 100)))
+               (incf (nnimap-initial-resync nnimap-object))
+               (setq start 1))
              (push (list (nnimap-send-command "%s %S" command
                                               (utf7-encode group t))
                          (nnimap-send-command "UID FETCH %d:* FLAGS" start)
@@ -1192,9 +1407,14 @@ 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))
+            (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)))
+                  '(open run)))
     (with-current-buffer (nnimap-buffer)
       ;; Wait for the final data to trickle in.
       (when (nnimap-wait-for-response (if (eq (cadar sequences) 'qresync)
@@ -1208,22 +1428,25 @@ 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"
-                               (gnus-group-real-name group)
-                               (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)
-    (let* ((group (gnus-group-real-name (gnus-info-group info)))
+    (let* ((group (nnimap-decode-gnus-group
+                  (gnus-group-real-name (gnus-info-group info))))
           (marks (cdr (assoc group flags))))
       (when marks
        (nnimap-update-info info marks)))))
@@ -1247,7 +1470,8 @@ textual parts.")
             (cdr (assq 'uidvalidity (gnus-info-params info)))))
        (and old-uidvalidity
             (not (equal old-uidvalidity uidvalidity))
-            (> start-article 1)))
+             (or (not start-article)
+                 (> start-article 1))))
       (gnus-group-remove-parameter info 'uidvalidity)
       (gnus-group-remove-parameter info 'modseq))
      ;; We have the data needed to update.
@@ -1305,7 +1529,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)))
@@ -1344,6 +1570,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.
@@ -1387,6 +1632,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)
@@ -1439,9 +1692,15 @@ textual parts.")
 
 (defun nnimap-parse-flags (sequences)
   (goto-char (point-min))
-  ;; Change \Delete etc to %Delete, so that the reader can read it.
+  ;; Change \Delete etc to %Delete, so that the Emacs Lisp 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,14 +1735,14 @@ textual parts.")
                 (goto-char start)
                 (setq vanished
                       (and (eq flag-sequence 'qresync)
-                           (re-search-forward "^\\* VANISHED .* \\([0-9:,]+\\)"
+                           (re-search-forward "^\\* VANISHED .*? \\([0-9:,]+\\)"
                                               (or end (point-min)) t)
                            (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.
@@ -1496,15 +1755,18 @@ 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)
                groups)
-         (goto-char end)
+         (if (eq flag-sequence 'qresync)
+             (goto-char end)
+           (setq end (point)))
          (setq articles nil))))
     groups))
 
@@ -1515,34 +1777,40 @@ textual parts.")
   (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))
 
-(deffoo nnimap-request-thread (header)
-  (let* ((id (mail-header-id header))
-        (refs (split-string
-               (or (mail-header-references header)
-                   "")))
-        (cmd (let ((value
-                    (format
-                     "(OR HEADER REFERENCES %s HEADER Message-Id %s)"
-                     id id)))
-               (dolist (refid refs value)
-                 (setq value (format
-                              "(OR (OR HEADER Message-Id %s HEADER REFERENCES %s) %s)"
-                              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)))
-
-(defun nnimap-possibly-change-group (group server)
+(autoload 'nnir-search-thread "nnir")
+
+(deffoo nnimap-request-thread (header &optional group server)
+  (when group
+    (setq group (nnimap-decode-gnus-group group)))
+  (if gnus-refer-thread-use-nnir
+      (nnir-search-thread header)
+    (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))))
+        (when result
+          (gnus-fetch-headers
+           (and (car result)
+               (delete 0 (mapcar #'string-to-number
+                                 (cdr (assoc "SEARCH" (cdr result))))))
+           nil t))))))
+
+(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)))
-      (setq open-result (nnimap-open-server server)))
+      (setq open-result (nnimap-open-server server nil no-reconnect)))
     (cond
      ((not open-result)
       nil)
@@ -1550,13 +1818,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."
@@ -1589,13 +1859,27 @@ textual parts.")
     (nnimap-wait-for-response nnimap-sequence))
   nnimap-sequence)
 
+(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)
-  (with-current-buffer (get-buffer-create "*imap log*")
-    (goto-char (point-max))
-    (insert (format-time-string "%H:%M:%S") " "
-           (if nnimap-inhibit-logging
-               "(inhibited)\n"
-             command)))
+  (when nnimap-record-commands
+    (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))))
   command)
 
 (defun nnimap-command (&rest args)
@@ -1643,14 +1927,26 @@ 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 7 "nnimap read %dk" (/ (buffer-size) 1000)))
+             (nnheader-message-maybe
+              7 "nnimap read %dk from %s%s" (/ (buffer-size) 1000)
+              nnimap-address
+              (if (not (zerop (nnimap-initial-resync nnimap-object)))
+                  (format " (initial sync of %d group%s; please wait)"
+                          (nnimap-initial-resync nnimap-object)
+                          (if (= (nnimap-initial-resync nnimap-object) 1)
+                              ""
+                            "s"))
+                "")))
            (nnheader-accept-process-output process)
            (goto-char (point-max)))
+         (setf (nnimap-initial-resync nnimap-object) 0)
           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).
@@ -1722,15 +2018,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)
@@ -1742,19 +2029,28 @@ 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 ()
   (with-current-buffer (nnimap-buffer)
     (let ((nnimap-incoming-split-list nil)
-         (nnmail-split-methods (if (eq nnimap-split-methods 'default)
-                                   nnmail-split-methods
-                                 nnimap-split-methods))
+         (nnmail-split-methods
+          (cond
+           ((eq nnimap-split-methods 'default)
+            nnmail-split-methods)
+           (nnimap-split-methods
+            nnimap-split-methods)
+           (nnimap-split-fancy
+            'nnmail-split-fancy)))
          (nnmail-split-fancy (or nnimap-split-fancy
                                  nnmail-split-fancy))
          (nnmail-inhibit-default-split-group t)
@@ -1790,12 +2086,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))
@@ -1858,7 +2157,7 @@ textual parts.")
   (let (article bytes)
     (block nil
       (while (not (eobp))
-       (while (not (looking-at "^\\* [0-9]+ FETCH.*UID \\([0-9]+\\)"))
+       (while (not (looking-at "\\* [0-9]+ FETCH.+UID \\([0-9]+\\)"))
          (delete-region (point) (progn (forward-line 1) (point)))
          (when (eobp)
            (return)))
@@ -1891,6 +2190,21 @@ textual parts.")
                  group-art))
          nnimap-incoming-split-list)))
 
+(defun nnimap-make-thread-query (header)
+  (let* ((id  (mail-header-id header))
+        (refs (split-string
+               (or (mail-header-references header)
+                   "")))
+        (value
+         (format
+          "(OR HEADER REFERENCES %S HEADER Message-Id %S)"
+          id id)))
+    (dolist (refid refs value)
+      (setq value (format
+                  "(OR (OR HEADER Message-Id %S HEADER REFERENCES %S) %s)"
+                  refid refid value)))))
+
+
 (provide 'nnimap)
 
 ;;; nnimap.el ends here