(gnus-group-completing-read): Remove all newlines from group names. They mess up...
[gnus] / lisp / nnimap.el
index c11e30a..701ed1e 100644 (file)
 
 ;;; Code:
 
+;; For Emacs <22.2 and XEmacs.
+(eval-and-compile
+  (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
+
 (eval-and-compile
   (require 'nnheader))
 
@@ -284,6 +288,8 @@ textual parts.")
                        (* 5 60)))
            (nnimap-send-command "NOOP")))))))
 
+(declare-function gnutls-negotiate "subr" (fn file &optional arglist fileonly))
+
 (defun nnimap-open-connection (buffer)
   (unless nnimap-keepalive-timer
     (setq nnimap-keepalive-timer (run-at-time (* 60 15) (* 60 15)
@@ -295,7 +301,11 @@ textual parts.")
             (port nil)
             (ports
              (cond
-              ((eq nnimap-stream 'network)
+              ((or (eq nnimap-stream 'network)
+                   (and (eq nnimap-stream 'starttls)
+                        (fboundp 'open-gnutls-stream)))
+               (nnheader-message 7 "Opening connection to %s..."
+                                 nnimap-address)
                (open-network-stream
                 "*nnimap*" (current-buffer) nnimap-address
                 (setq port
@@ -305,18 +315,24 @@ textual parts.")
                             "143"))))
                '("143" "imap"))
               ((eq nnimap-stream 'shell)
+               (nnheader-message 7 "Opening connection to %s via shell..."
+                                 nnimap-address)
                (nnimap-open-shell-stream
                 "*nnimap*" (current-buffer) nnimap-address
                 (setq port (or nnimap-server-port "imap")))
                '("imap"))
               ((eq nnimap-stream 'starttls)
-               (let ((tls-program (nnimap-extend-tls-programs)))
+               (nnheader-message 7 "Opening connection to %s via starttls..."
+                        nnimap-address)
+               (let ((tls-program
+                      '("openssl s_client -connect %h:%p -no_ssl2 -ign_eof -starttls imap")))
                  (open-tls-stream
                   "*nnimap*" (current-buffer) nnimap-address
-                  (setq port (or nnimap-server-port "imap"))
-                  'starttls))
+                  (setq port (or nnimap-server-port "imap"))))
                '("imap"))
               ((memq nnimap-stream '(ssl tls))
+               (nnheader-message 7 "Opening connection to %s via tls..."
+                                 nnimap-address)
                (funcall (if (fboundp 'open-gnutls-stream)
                             'open-gnutls-stream
                           'open-tls-stream)
@@ -357,8 +373,16 @@ textual parts.")
              (push (format "%s" nnimap-server-port) ports))
            ;; If this is a STARTTLS-capable server, then sever the
            ;; connection and start a STARTTLS connection instead.
-           (when (and (eq nnimap-stream 'network)
-                      (member "STARTTLS" (nnimap-capabilities nnimap-object)))
+           (cond
+            ((and (or (and (eq nnimap-stream 'network)
+                           (member "STARTTLS"
+                                   (nnimap-capabilities nnimap-object)))
+                      (eq nnimap-stream 'starttls))
+                  (fboundp 'open-gnutls-stream))
+             (nnimap-command "STARTTLS")
+             (gnutls-negotiate (nnimap-process nnimap-object) nil))
+            ((and (eq nnimap-stream 'network)
+                  (member "STARTTLS" (nnimap-capabilities nnimap-object)))
              (let ((nnimap-stream 'starttls))
                (let ((tls-process
                       (nnimap-open-connection buffer)))
@@ -369,7 +393,7 @@ textual parts.")
                  (when (memq (process-status tls-process) '(open run))
                    (delete-process (nnimap-process nnimap-object))
                    (kill-buffer (current-buffer))
-                   (return tls-process)))))
+                   (return tls-process))))))
            (unless (equal connection-result "PREAUTH")
              (if (not (setq credentials
                             (if (eq nnimap-authenticator 'anonymous)
@@ -403,19 +427,6 @@ textual parts.")
                (nnimap-command "ENABLE QRESYNC"))
              (nnimap-process nnimap-object))))))))
 
-(defun nnimap-extend-tls-programs ()
-  (let ((programs tls-program)
-       result)
-    (unless (consp programs)
-      (setq programs (list programs)))
-    (dolist (program programs)
-      (when (assoc (car (split-string program)) tls-starttls-switches)
-       (push (if (not (string-match "%s" program))
-                 (concat program " " "%s")
-               program)
-             result)))
-    (nreverse result)))
-
 (defun nnimap-find-parameter (parameter elems)
   (let (result)
     (dolist (elem elems)
@@ -432,6 +443,7 @@ textual parts.")
   (when (nnoo-change-server 'nnimap server nil)
     (ignore-errors
       (delete-process (get-buffer-process (nnimap-buffer))))
+    (nnoo-close-server 'nnimap server)
     t))
 
 (deffoo nnimap-request-close ()
@@ -465,7 +477,7 @@ textual parts.")
                                (let ((start (point)))
                                  (forward-sexp 1)
                                  (downcase-region start (point))
-                                 (goto-char (point))
+                                 (goto-char start)
                                  (read (current-buffer))))
                    parts (nnimap-find-wanted-parts structure))))
          (when (if parts
@@ -553,9 +565,9 @@ textual parts.")
        (pop bstruc))
       (setq type (car bstruc))
       (setq bstruc (car (cdr bstruc)))
-      (when (and (stringp (car bstruc))
-                (string= (downcase (car bstruc)) "boundary"))
-       (setq boundary (cadr bstruc))))
+      (let ((has-boundary (member "boundary" bstruc)))
+        (when has-boundary
+          (setq boundary (cadr has-boundary)))))
     (when subp
       (insert (format "Content-type: multipart/%s; boundary=%S\n\n"
                      (downcase type) boundary)))
@@ -605,7 +617,13 @@ textual parts.")
     (nreverse parts)))
 
 (deffoo nnimap-request-group (group &optional server dont-check info)
-  (let ((result (nnimap-possibly-change-group group server))
+  (let ((result (nnimap-possibly-change-group
+                ;; Don't SELECT the group if we're going to select it
+                ;; later, anyway.
+                (if dont-check
+                    nil
+                  group)
+                server))
        articles active marks high low)
     (with-current-buffer nntp-server-buffer
       (when result
@@ -622,6 +640,7 @@ textual parts.")
                   (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
@@ -657,6 +676,12 @@ textual parts.")
 (deffoo nnimap-request-rename-group (group new-name &optional server)
   (when (nnimap-possibly-change-group nil server)
     (with-current-buffer (nnimap-buffer)
+      ;; Make sure we don't have this group open read/write by asking
+      ;; to examine a mailbox that doesn't exist.  This seems to be
+      ;; the only way that allows us to reliably go back to unselected
+      ;; state on Courier.
+      (nnimap-command "EXAMINE DOES.NOT.EXIST")
+      (setf (nnimap-group nnimap-object) nil)
       (car (nnimap-command "RENAME %S %S"
                           (utf7-encode group t) (utf7-encode new-name t))))))
 
@@ -689,6 +714,7 @@ textual parts.")
 (deffoo nnimap-request-move-article (article group server accept-form
                                             &optional last internal-move-group)
   (with-temp-buffer
+    (mm-disable-multibyte)
     (when (nnimap-request-article article group server (current-buffer))
       ;; If the move is internal (on the same server), just do it the easy
       ;; way.
@@ -719,7 +745,7 @@ textual parts.")
    ((and force
         (eq nnmail-expiry-target 'delete))
     (unless (nnimap-delete-article (gnus-compress-sequence articles))
-      (message "Article marked for deletion, but not expunged."))
+      (nnheader-message 7 "Article marked for deletion, but not expunged."))
     nil)
    (t
     (let ((deletable-articles
@@ -744,8 +770,9 @@ textual parts.")
     (dolist (article articles)
       (let ((target nnmail-expiry-target))
        (with-temp-buffer
+          (mm-disable-multibyte)
          (when (nnimap-request-article article group server (current-buffer))
-           (message "Expiring article %s:%d" group article)
+           (nnheader-message 7 "Expiring article %s:%d" group article)
            (when (functionp target)
              (setq target (funcall target group)))
            (when (and target
@@ -815,7 +842,7 @@ textual parts.")
   (when (and (nnimap-possibly-change-group nil server)
             nnimap-inbox
             nnimap-split-methods)
-    (message "nnimap %s splitting mail..." server)
+    (nnheader-message 7 "nnimap %s splitting mail..." server)
     (nnimap-split-incoming-mail)))
 
 (defun nnimap-marks-to-flags (marks)
@@ -854,7 +881,7 @@ textual parts.")
     (let ((message-id (message-field-value "message-id"))
          sequence message)
       (nnimap-add-cr)
-      (setq message (buffer-string))
+      (setq message (buffer-substring-no-properties (point-min) (point-max)))
       (with-current-buffer (nnimap-buffer)
        (setq sequence (nnimap-send-command
                        "APPEND %S {%d}" (utf7-encode group t)
@@ -867,11 +894,22 @@ textual parts.")
        (let ((result (nnimap-get-response sequence)))
          (if (not (car result))
              (progn
-               (message "%s" (nnheader-get-report-string 'nnimap))
+               (nnheader-message 7 "%s" (nnheader-get-report-string 'nnimap))
                nil)
            (cons group
                  (nnimap-find-article-by-message-id group message-id))))))))
 
+(deffoo nnimap-request-replace-article (article group buffer)
+  (let (group-art)
+    (when (and (nnimap-possibly-change-group group nil)
+              ;; Put the article into the group.
+              (with-current-buffer buffer
+                (setq group-art
+                      (nnimap-request-accept-article group nil t))))
+      (nnimap-delete-article (list article))
+      ;; Return the new article number.
+      (cdr group-art))))
+
 (defun nnimap-add-cr ()
   (goto-char (point-min))
   (while (re-search-forward "\r?\n" nil t)
@@ -949,7 +987,7 @@ textual parts.")
       (erase-buffer)
       (setf (nnimap-group nnimap-object) nil)
       ;; QRESYNC handling isn't implemented.
-      (let ((qresyncp (member "notQRESYNC" (nnimap-capabilities nnimap-object)))
+      (let ((qresyncp (member "QRESYNC" (nnimap-capabilities nnimap-object)))
            params groups sequences active uidvalidity modseq group)
        ;; Go through the infos and gather the data needed to know
        ;; what and how to request the data.
@@ -963,9 +1001,10 @@ textual parts.")
                   uidvalidity
                   modseq)
              (push
-              (list 'qresync
-                    (nnimap-send-command "EXAMINE %S (QRESYNC (%s %s))"
-                                         group uidvalidity modseq)
+              (list (nnimap-send-command "EXAMINE %S (QRESYNC (%s %s))"
+                                         (utf7-encode group t)
+                                         uidvalidity modseq)
+                    'qresync
                     nil group 'qresync)
               sequences)
            (let ((start
@@ -982,7 +1021,8 @@ textual parts.")
                     ;; examine), but will tell us whether the group
                     ;; is read-only or not.
                     "SELECT")))
-             (push (list (nnimap-send-command "%s %S" command group)
+             (push (list (nnimap-send-command "%s %S" command
+                                              (utf7-encode group t))
                          (nnimap-send-command "UID FETCH %d:* FLAGS" start)
                          start group command)
                    sequences)))
@@ -998,7 +1038,10 @@ textual parts.")
             (nnimap-possibly-change-group nil server))
     (with-current-buffer (nnimap-buffer)
       ;; Wait for the final data to trickle in.
-      (when (nnimap-wait-for-response (cadar sequences) t)
+      (when (nnimap-wait-for-response (if (eq (cadar sequences) 'qresync)
+                                         (caar sequences)
+                                       (cadar sequences))
+                                     t)
        ;; Now we should have most of the data we need, no matter
        ;; whether we're QRESYNCING, fetching all the flags from
        ;; scratch, or just fetching the last 100 flags per group.
@@ -1028,13 +1071,16 @@ textual parts.")
 
 (defun nnimap-update-info (info marks)
   (destructuring-bind (existing flags high low uidnext start-article
-                               permanent-flags uidvalidity) marks
+                               permanent-flags uidvalidity
+                               vanished highestmodseq) marks
     (cond
      ;; Ignore groups with no UIDNEXT/marks.  This happens for
      ;; completely empty groups.
      ((and (not existing)
           (not uidnext))
-      )
+      (let ((active (cdr (assq 'active (gnus-info-params info)))))
+       (when active
+         (gnus-set-active (gnus-info-group info) active))))
      ;; We have a mismatch between the old and new UIDVALIDITY
      ;; identifiers, so we have to re-request the group info (the next
      ;; time).  This virtually never happens.
@@ -1043,12 +1089,15 @@ textual parts.")
        (and old-uidvalidity
             (not (equal old-uidvalidity uidvalidity))
             (> start-article 1)))
-      (gnus-group-remove-parameter info 'uidvalidity))
+      (gnus-group-remove-parameter info 'uidvalidity)
+      (gnus-group-remove-parameter info 'modseq))
      ;; We have the data needed to update.
      (t
-      (let ((group (gnus-info-group info))
-           (completep (and start-article
-                           (= start-article 1))))
+      (let* ((group (gnus-info-group info))
+            (completep (and start-article
+                            (= start-article 1)))
+            (active (or (gnus-active group)
+                        (cdr (assq 'active (gnus-info-params info))))))
        (when uidnext
          (setq high (1- uidnext)))
        ;; First set the active ranges based on high/low.
@@ -1061,6 +1110,8 @@ textual parts.")
                              (uidnext
                               ;; No articles in this group.
                               (cons uidnext (1- uidnext)))
+                             (active
+                              active)
                              (start-article
                               (cons start-article (1- start-article)))
                              (t
@@ -1068,63 +1119,120 @@ textual parts.")
                               nil)))
          (gnus-set-active
           group
-          (cons (car (gnus-active group))
+          (cons (car active)
                 (or high (1- uidnext)))))
        ;; See whether this is a read-only group.
        (unless (eq permanent-flags 'not-scanned)
          (gnus-group-set-parameter
           info 'permanent-flags
-          (if (memq '%* permanent-flags)
-              t
-            nil)))
-       ;; Then update marks and read articles if this isn't a
+          (and (or (memq '%* permanent-flags)
+                   (memq '%Seen permanent-flags))
+               permanent-flags)))
+       ;; Update marks and read articles if this isn't a
        ;; read-only IMAP group.
-       (when (cdr (assq 'permanent-flags (gnus-info-params info)))
-         ;; Update the list of read articles.
-         (let* ((unread
-                 (gnus-compress-sequence
-                  (gnus-set-difference
-                   (gnus-set-difference
-                    existing
-                    (cdr (assoc '%Seen flags)))
-                   (cdr (assoc '%Flagged flags)))))
-                (read (gnus-range-difference
-                       (cons start-article high) unread)))
-           (when (> start-article 1)
-             (setq read
-                   (gnus-range-nconcat
-                    (if (> start-article 1)
-                        (gnus-sorted-range-intersection
-                         (cons 1 (1- start-article))
-                         (gnus-info-read info))
-                      (gnus-info-read info))
-                    read)))
-           (gnus-info-set-read info read)
-           ;; Update the marks.
-           (setq marks (gnus-info-marks info))
-           (dolist (type (cdr nnimap-mark-alist))
-             (let ((old-marks (assoc (car type) marks))
-                   (new-marks
-                    (gnus-compress-sequence
-                     (cdr (or (assoc (caddr type) flags) ; %Flagged
-                              (assoc (intern (cadr type) obarray) flags)
-                              (assoc (cadr type) flags)))))) ; "\Flagged"
-               (setq marks (delq old-marks marks))
-               (pop old-marks)
-               (when (and old-marks
-                          (> start-article 1))
-                 (setq old-marks (gnus-range-difference
-                                  old-marks
-                                  (cons start-article high)))
-                 (setq new-marks (gnus-range-nconcat old-marks new-marks)))
-               (when new-marks
-                 (push (cons (car type) new-marks) marks)))
+       (when (setq permanent-flags
+                   (cdr (assq 'permanent-flags (gnus-info-params info))))
+         (if (and highestmodseq
+                  (not start-article))
+             ;; We've gotten the data by QRESYNCing.
+             (nnimap-update-qresync-info
+              info existing (nnimap-imap-ranges-to-gnus-ranges vanished) flags)
+           ;; Do normal non-QRESYNC flag updates.
+           ;; Update the list of read articles.
+           (let* ((unread
+                   (gnus-compress-sequence
+                    (gnus-set-difference
+                     (gnus-set-difference
+                      existing
+                      (cdr (assoc '%Seen flags)))
+                     (cdr (assoc '%Flagged flags)))))
+                  (read (gnus-range-difference
+                         (cons start-article high) unread)))
+             (when (> start-article 1)
+               (setq read
+                     (gnus-range-nconcat
+                      (if (> start-article 1)
+                          (gnus-sorted-range-intersection
+                           (cons 1 (1- start-article))
+                           (gnus-info-read info))
+                        (gnus-info-read info))
+                      read)))
+             (when (or (not (listp permanent-flags))
+                       (memq '%Seen permanent-flags))
+               (gnus-info-set-read info read))
+             ;; Update the marks.
+             (setq marks (gnus-info-marks info))
+             (dolist (type (cdr nnimap-mark-alist))
+               (when (or (not (listp permanent-flags))
+                         (memq (assoc (caddr type) flags) permanent-flags)
+                         (memq '%* permanent-flags))
+                 (let ((old-marks (assoc (car type) marks))
+                       (new-marks
+                        (gnus-compress-sequence
+                         (cdr (or (assoc (caddr type) flags) ; %Flagged
+                                  (assoc (intern (cadr type) obarray) flags)
+                                  (assoc (cadr type) flags)))))) ; "\Flagged"
+                   (setq marks (delq old-marks marks))
+                   (pop old-marks)
+                   (when (and old-marks
+                              (> start-article 1))
+                     (setq old-marks (gnus-range-difference
+                                      old-marks
+                                      (cons start-article high)))
+                     (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 t))))
        ;; Note the active level for the next run-through.
        (gnus-group-set-parameter info 'active (gnus-active group))
        (gnus-group-set-parameter info 'uidvalidity uidvalidity)
+       (gnus-group-set-parameter info 'modseq highestmodseq)
        (nnimap-store-info info (gnus-active group)))))))
 
+(defun nnimap-update-qresync-info (info existing vanished flags)
+  ;; Add all the vanished articles to the list of read articles.
+  (gnus-info-set-read
+   info
+   (gnus-add-to-range
+    (gnus-add-to-range
+     (gnus-range-add (gnus-info-read info)
+                    vanished)
+     (cdr (assq '%Flagged flags)))
+    (cdr (assq '%Seen flags))))
+  (let ((marks (gnus-info-marks info)))
+    (dolist (type (cdr nnimap-mark-alist))
+      (let ((ticks (assoc (car type) marks))
+           (new-marks
+            (cdr (or (assoc (caddr type) flags) ; %Flagged
+                     (assoc (intern (cadr type) obarray) flags)
+                     (assoc (cadr type) flags))))) ; "\Flagged"
+       (setq marks (delq ticks marks))
+       (pop ticks)
+       ;; Add the new marks we got.
+       (setq ticks (gnus-add-to-range ticks new-marks))
+       ;; Remove the marks from messages that don't have them.
+       (setq ticks (gnus-remove-from-range
+                    ticks
+                    (gnus-compress-sequence
+                     (gnus-sorted-complement existing new-marks))))
+       (when ticks
+         (push (cons (car type) ticks) marks)))
+      (gnus-info-set-marks info marks t))))
+
+(defun nnimap-imap-ranges-to-gnus-ranges (irange)
+  (if (zerop (length irange))
+      nil
+    (let ((result nil))
+      (dolist (elem (split-string irange ","))
+       (push
+        (if (string-match ":" elem)
+            (let ((numbers (split-string elem ":")))
+              (cons (string-to-number (car numbers))
+                    (string-to-number (cadr numbers))))
+          (string-to-number elem))
+        result))
+      (nreverse result))))
+
 (defun nnimap-store-info (info active)
   (let* ((group (gnus-group-real-name (gnus-info-group info)))
         (entry (assoc group nnimap-current-infos)))
@@ -1134,14 +1242,16 @@ textual parts.")
 
 (defun nnimap-flags-to-marks (groups)
   (let (data group totalp uidnext articles start-article mark permanent-flags
-            uidvalidity)
+            uidvalidity vanished highestmodseq)
     (dolist (elem groups)
       (setq group (car elem)
            uidnext (nth 1 elem)
            start-article (nth 2 elem)
            permanent-flags (nth 3 elem)
            uidvalidity (nth 4 elem)
-           articles (nthcdr 5 elem))
+           vanished (nth 5 elem)
+           highestmodseq (nth 6 elem)
+           articles (nthcdr 7 elem))
       (let ((high (caar articles))
            marks low existing)
        (dolist (article articles)
@@ -1153,7 +1263,7 @@ textual parts.")
                (push (list flag (car article)) marks)
              (setcdr mark (cons (car article) (cdr mark))))))
        (push (list group existing marks high low uidnext start-article
-                   permanent-flags uidvalidity)
+                   permanent-flags uidvalidity vanished highestmodseq)
              data)))
     data))
 
@@ -1163,13 +1273,13 @@ textual parts.")
   (subst-char-in-region (point-min) (point-max)
                        ?\\ ?% t)
   (let (start end articles groups uidnext elems permanent-flags
-             uidvalidity)
+             uidvalidity vanished highestmodseq)
     (dolist (elem sequences)
       (destructuring-bind (group-sequence flag-sequence totalp group command)
          elem
        (setq start (point))
-       ;; The EXAMINE was successful.
        (when (and
+              ;; The EXAMINE was successful.
               (search-forward (format "\n%d OK " group-sequence) nil t)
               (progn
                 (forward-line 1)
@@ -1189,24 +1299,42 @@ textual parts.")
                 (goto-char start)
                 (setq uidvalidity
                       (and (re-search-forward "UIDVALIDITY \\([0-9]+\\)"
-                                           (or end (point-min)) t)
+                                              (or end (point-min)) t)
                            ;; Store UIDVALIDITY as a string, as it's
                            ;; too big for 32-bit Emacsen, usually.
                            (match-string 1)))
+                (goto-char start)
+                (setq vanished
+                      (and (eq flag-sequence 'qresync)
+                           (re-search-forward "VANISHED.* \\([0-9:,]+\\)"
+                                              (or end (point-min)) t)
+                           (match-string 1)))
+                (goto-char start)
+                (setq highestmodseq
+                      (and (search-forward "HIGHESTMODSEQ "
+                                           (or end (point-min)) t)
+                           (read (current-buffer))))
                 (goto-char end)
                 (forward-line -1))
               ;; The UID FETCH FLAGS was successful.
-              (search-forward (format "\n%d OK " flag-sequence) nil t))
-         (setq start (point))
-         (goto-char end)
-         (while (search-forward " FETCH " start t)
+              (or (eq flag-sequence 'qresync)
+                  (search-forward (format "\n%d OK " flag-sequence) nil t)))
+         (if (eq flag-sequence 'qresync)
+             (progn
+               (goto-char start)
+               (setq start end))
+           (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))
-         (push (nconc (list group uidnext totalp permanent-flags uidvalidity)
+         (push (nconc (list group uidnext totalp permanent-flags uidvalidity
+                            vanished highestmodseq)
                       articles)
                groups)
+         (goto-char end)
          (setq articles nil))))
     groups))
 
@@ -1217,6 +1345,25 @@ textual parts.")
   (setq nnimap-status-string "Read-only server")
   nil)
 
+(deffoo nnimap-request-thread (id)
+    (let* ((refs (split-string
+              (or (mail-header-references (gnus-summary-article-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)))))))))
+
 (defun nnimap-possibly-change-group (group server)
   (let ((open-result t))
     (when (and server
@@ -1301,20 +1448,28 @@ textual parts.")
 (defun nnimap-wait-for-response (sequence &optional messagep)
   (let ((process (get-buffer-process (current-buffer)))
        openp)
-    (goto-char (point-max))
-    (while (and (setq openp (memq (process-status process)
-                                 '(open run)))
-               (not (re-search-backward
-                     (format "^%d .*\n" sequence)
-                     (if nnimap-streaming
-                         (max (point-min) (- (point) 500))
-                       (point-min))
-                     t)))
-      (when messagep
-       (message "nnimap read %dk" (/ (buffer-size) 1000)))
-      (nnheader-accept-process-output process)
-      (goto-char (point-max)))
-    openp))
+    (condition-case nil
+        (progn
+         (goto-char (point-max))
+         (while (and (setq openp (memq (process-status process)
+                                       '(open run)))
+                     (not (re-search-backward
+                           (format "^%d .*\n" sequence)
+                           (if nnimap-streaming
+                               (max (point-min) (- (point) 500))
+                             (point-min))
+                           t)))
+           (when messagep
+             (nnheader-message 7 "nnimap read %dk" (/ (buffer-size) 1000)))
+           (nnheader-accept-process-output process)
+           (goto-char (point-max)))
+          openp)
+      (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).
+       (delete-process process)
+       nil))))
 
 (defun nnimap-parse-response ()
   (let ((lines (split-string (nnimap-last-response-string) "\r\n" t))
@@ -1332,6 +1487,7 @@ textual parts.")
 (defun nnimap-parse-line (line)
   (let (char result)
     (with-temp-buffer
+      (mm-disable-multibyte)
       (insert line)
       (goto-char (point-min))
       (while (not (eobp))
@@ -1525,8 +1681,10 @@ textual parts.")
        (forward-char (1+ bytes))
        (setq bytes (nnimap-get-length))
        (delete-region (line-beginning-position) (line-end-position))
-       (forward-char (1+ bytes))
-       (delete-region (line-beginning-position) (line-end-position))))))
+       ;; There's a body; skip past that.
+       (when bytes
+         (forward-char (1+ bytes))
+         (delete-region (line-beginning-position) (line-end-position)))))))
 
 (defun nnimap-dummy-active-number (group &optional server)
   1)