Reimplement nnimap, and do tweaks to the rest of the code to support that.
authorLars Magne Ingebrigtsen <larsi@quimbies.gnus.org>
Fri, 17 Sep 2010 16:18:39 +0000 (18:18 +0200)
committerLars Magne Ingebrigtsen <larsi@quimbies.gnus.org>
Fri, 17 Sep 2010 16:18:39 +0000 (18:18 +0200)
* gnus-int.el (gnus-finish-retrieve-group-infos)
(gnus-retrieve-group-data-early): New functions.

* gnus-range.el (gnus-range-nconcat): New function.

* gnus-start.el (gnus-get-unread-articles): Support early retrieval of
data.
(gnus-read-active-for-groups): Support finishing the early retrieval of
data.

* gnus-sum.el (gnus-summary-move-article): Pass the move-to group name
if the move is internal, so that nnimap can do fast internal moves.

* gnus.el (gnus-article-special-mark-lists): Add uid/active tuples, for
nnimap usage.

* nnimap.el: Rewritten.

* nnmail.el (nnmail-inhibit-default-split-group): New internal variable
to allow the mail splitting to not return a default group.  This is
useful for nnimap, which will leave unmatched mail in the inbox.

* utf7.el (utf7-encode): Autoload.

lisp/ChangeLog
lisp/gnus-int.el
lisp/gnus-range.el
lisp/gnus-start.el
lisp/gnus-sum.el
lisp/gnus.el
lisp/netrc.el
lisp/nnimap.el
lisp/nnmail.el
lisp/utf7.el

index 76846e5..0c8bf2e 100644 (file)
@@ -1,3 +1,29 @@
+2010-09-17  Lars Magne Ingebrigtsen  <larsi@gnus.org>
+
+       * utf7.el (utf7-encode): Autoload.
+
+       * nnmail.el (nnmail-inhibit-default-split-group): New internal variable
+       to allow the mail splitting to not return a default group.  This is
+       useful for nnimap, which will leave unmatched mail in the inbox.
+
+       * nnimap.el: Rewritten.
+
+       * gnus.el (gnus-article-special-mark-lists): Add uid/active tuples, for
+       nnimap usage.
+
+       * gnus-sum.el (gnus-summary-move-article): Pass the move-to group name
+       if the move is internal, so that nnimap can do fast internal moves.
+
+       * gnus-start.el (gnus-get-unread-articles): Support early retrieval of
+       data.
+       (gnus-read-active-for-groups): Support finishing the early retrieval of
+       data.
+
+       * gnus-range.el (gnus-range-nconcat): New function.
+
+       * gnus-int.el (gnus-finish-retrieve-group-infos)
+       (gnus-retrieve-group-data-early): New functions.
+
 2010-09-17  Katsumi Yamaoka  <yamaoka@jpl.org>
 
        * pop3.el (pop3-wait-for-messages): Use pop3-accept-process-output
index d805f31..36d79ac 100644 (file)
@@ -179,10 +179,7 @@ If it is down, start it up (again)."
                        (format " on %s" (nth 1 method)))))
       (gnus-run-hooks 'gnus-open-server-hook)
       (prog1
-         (condition-case ()
-             (setq result (gnus-open-server method))
-           (quit (message "Quit gnus-check-server")
-                 nil))
+         (progn (gnus-open-server method))
        (unless silent
          (gnus-message 5 "Opening %s server%s...%s" (car method)
                        (if (equal (nth 1 method) "") ""
@@ -319,6 +316,22 @@ If it is down, start it up (again)."
   (funcall (gnus-get-function gnus-command-method 'request-list)
           (nth 1 gnus-command-method)))
 
+(defun gnus-finish-retrieve-group-infos (gnus-command-method infos data)
+  "Read and update infos from GNUS-COMMAND-METHOD."
+  (when (stringp gnus-command-method)
+    (setq gnus-command-method (gnus-server-to-method gnus-command-method)))
+  (funcall (gnus-get-function gnus-command-method 'finish-retrieve-group-infos)
+          (nth 1 gnus-command-method)
+          infos data))
+
+(defun gnus-retrieve-group-data-early (gnus-command-method infos)
+  "Start early async retrival of data from GNUS-COMMAND-METHOD."
+  (when (stringp gnus-command-method)
+    (setq gnus-command-method (gnus-server-to-method gnus-command-method)))
+  (funcall (gnus-get-function gnus-command-method 'retrieve-group-data-early)
+          (nth 1 gnus-command-method)
+          infos))
+
 (defun gnus-request-list-newsgroups (gnus-command-method)
   "Request the newsgroups file from GNUS-COMMAND-METHOD."
   (when (stringp gnus-command-method)
index 5eb8080..a4262df 100644 (file)
@@ -59,6 +59,36 @@ If RANGE is a single range, return (RANGE). Otherwise, return RANGE."
       (setq list2 (cdr list2)))
     list1))
 
+(defun gnus-range-nconcat (&rest ranges)
+  "Return a range comprising all the RANGES, which are pre-sorted.
+RANGES will be destructively altered."
+  (setq ranges (delete nil ranges))
+  (let* ((result (gnus-range-normalize (pop ranges)))
+        (last (last result)))
+    (dolist (range ranges)
+      (setq range (gnus-range-normalize range))
+      ;; Normalize the single-number case, so that we don't need to
+      ;; special-case that so much.
+      (when (numberp (car last))
+       (setcar last (cons (car last) (car last))))
+      (when (numberp (car range))
+       (setcar range (cons (car range) (car range))))
+      (if (= (1+ (cdar last)) (caar range))
+         (progn
+           (setcdr (car last) (cdar range))
+           (setcdr last (cdr range)))
+       (setcdr last range)
+       ;; Denormalize back, since we couldn't join the ranges up.
+       (when (= (caar range) (cdar range))
+         (setcar range (caar range)))
+       (when (= (caar last) (cdar last))
+         (setcar last (caar last))))
+      (setq last (last last)))
+    (if (and (consp (car result))
+            (= (length result) 1))
+       (car result)
+      result)))
+
 (defun gnus-range-difference (range1 range2)
   "Return the range of elements in RANGE1 that do not appear in RANGE2.
 Both ranges must be in ascending order."
index 1c06a77..4210231 100644 (file)
@@ -1734,7 +1734,7 @@ If SCAN, request a scan of that group as well."
                'primary)
               (t
                'foreign)))
-       (push (setq method-group-list (list method method-type nil))
+       (push (setq method-group-list (list method method-type nil nil))
              type-cache))
       ;; Only add groups that need updating.
       (if (<= (gnus-info-level info)
@@ -1760,19 +1760,27 @@ If SCAN, request a scan of that group as well."
                  (< (gnus-method-rank (cadr c1) (car c1))
                     (gnus-method-rank (cadr c2) (car c2))))))
 
-    (while type-cache
-      (setq method (nth 0 (car type-cache))
-           method-type (nth 1 (car type-cache))
-           infos (nth 2 (car type-cache)))
-      (pop type-cache)
-
-      (when (and method
-                infos)
-       ;; See if any of the groups from this method require updating.
-       (gnus-read-active-for-groups method infos)
-       (dolist (info infos)
-         (inline (gnus-get-unread-articles-in-group
-                  info (gnus-active (gnus-info-group info)))))))
+    ;; Start early async retrieval of data.
+    (dolist (elem type-cache)
+      (destructuring-bind (method method-type infos dummy) elem
+       (when (and method infos
+                  (gnus-check-backend-function
+                   'retrieve-group-data-early (car method)))
+         (when (gnus-check-backend-function 'request-scan (car method))
+           (dolist (info infos)
+             (gnus-request-scan (gnus-info-group info) method)))
+         (setcar (nthcdr 3 elem)
+                 (gnus-retrieve-group-data-early method infos)))))
+
+    ;; Do the rest of the retrieval.
+    (dolist (elem type-cache)
+      (destructuring-bind (method method-type infos early-data) elem
+       (when (and method infos)
+         ;; See if any of the groups from this method require updating.
+         (gnus-read-active-for-groups method infos early-data)
+         (dolist (info infos)
+           (inline (gnus-get-unread-articles-in-group
+                    info (gnus-active (gnus-info-group info))))))))
     (gnus-message 6 "Checking new news...done")))
 
 (defun gnus-method-rank (type method)
@@ -1796,9 +1804,11 @@ If SCAN, request a scan of that group as well."
    (t
     100)))
 
-(defun gnus-read-active-for-groups (method infos)
+(defun gnus-read-active-for-groups (method infos early-data)
   (with-current-buffer nntp-server-buffer
     (cond
+     ((gnus-check-backend-function 'finish-retrieve-group-infos (car method))
+      (gnus-finish-retrieve-group-infos method infos early-data))
      ((gnus-check-backend-function 'retrieve-groups (car method))
       (when (gnus-check-backend-function 'request-scan (car method))
        (dolist (info infos)
index df20456..c8c4a1c 100644 (file)
@@ -9690,7 +9690,8 @@ ACTION can be either `move' (the default), `crosspost' or `copy'."
                  to-newsgroup (list 'quote select-method)
                  (not articles) t)     ; Accept form
            (not articles)              ; Only save nov last time
-           move-is-internal)))         ; is this move internal?
+           (and move-is-internal
+                (gnus-group-real-name to-newsgroup))))) ; is this move internal?
        ;; Copy the article.
        ((eq action 'copy)
         (with-current-buffer copy-buf
@@ -9821,8 +9822,9 @@ ACTION can be either `move' (the default), `crosspost' or `copy'."
                  (gnus-add-marked-articles
                   to-group 'expire (list to-article) info))
 
-               (gnus-request-set-mark
-                to-group (list (list (list to-article) 'add to-marks))))
+               (when to-marks
+                 (gnus-request-set-mark
+                  to-group (list (list (list to-article) 'add to-marks)))))
 
              (gnus-dribble-enter
               (concat "(gnus-group-set-info '"
index c65ec99..9d8efab 100644 (file)
@@ -2742,6 +2742,8 @@ a string, be sure to use a valid format, see RFC 2616."
   '((seen range)
     (killed range)
     (bookmark tuple)
+    (uid tuple)
+    (active tuple)
     (score tuple)))
 
 ;; Propagate flags to server, with the following exceptions:
index 408eca9..3636c89 100644 (file)
@@ -228,6 +228,7 @@ MODE can be \"login\" or \"password\", suitable for passing to
                          (eq type (car (cddr service)))))))
     (cadr service)))
 
+;;;###autoload
 (defun netrc-credentials (machine &rest ports)
   "Return a user name/password pair.
 Port specifications will be prioritised in the order they are
index d412af4..548b6ec 100644 (file)
@@ -1,11 +1,9 @@
-;;; nnimap.el --- imap backend for Gnus
+;;; nnimap.el --- IMAP interface for Gnus
 
-;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
-;;   2007, 2008, 2009, 2010  Free Software Foundation, Inc.
+;; Copyright (C) 2010 Free Software Foundation, Inc.
 
-;; Author: Simon Josefsson <simon@josefsson.org>
-;;         Jim Radford <radford@robby.caltech.edu>
-;; Keywords: mail
+;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
+;;         Simon Josefsson <simon@josefsson.org>
 
 ;; This file is part of GNU Emacs.
 
 
 ;;; Commentary:
 
-;; Todo, major things:
-;;
-;;   o Fix Gnus to view correct number of unread/total articles in group buffer
-;;   o Fix Gnus to handle leading '.' in group names (fixed?)
-;;   o Finish disconnected mode (moving articles between mailboxes unplugged)
-;;   o Sieve
-;;   o MIME (partial article fetches)
-;;   o Split to other backends, different split rules for different
-;;     servers/inboxes
-;;
-;; Todo, minor things:
-;;
-;;   o Don't require half of Gnus -- backends should be standalone
-;;   o Verify that we don't use IMAP4rev1 specific things (RFC2060 App B)
-;;   o Dont uid fetch 1,* in nnimap-retrive-groups (slow)
-;;   o Split up big fetches (1,* header especially) in smaller chunks
-;;   o What do I do with gnus-newsgroup-*?
-;;   o Tell Gnus about new groups (how can we tell?)
-;;   o Respooling (fix Gnus?) (unnecessary?)
-;;   o Add support for the following: (if applicable)
-;;       request-list-newsgroups, request-regenerate
-;;       list-active-group,
-;;       request-associate-buffer, request-restore-buffer,
-;;   o Do The Right Thing when UIDVALIDITY changes (what's the right thing?)
-;;   o Support RFC2221 (Login referrals)
-;;   o IMAP2BIS compatibility? (RFC2061)
-;;   o ACAP stuff (perhaps a different project, would be nice to ACAPify
-;;     .newsrc.eld)
-;;   o What about Gnus's article editing, can we support it?  NO!
-;;   o Use \Draft to support the draft group??
-;;   o Duplicate suppression
-;;   o Rewrite UID SEARCH UID X as UID FETCH X (UID) for those with slow servers
+;; nnimap interfaces Gnus with IMAP servers.
 
 ;;; Code:
 
-;; For Emacs < 22.2.
 (eval-and-compile
-  (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
+  (require 'nnheader))
 
-(require 'imap)
-(require 'nnoo)
-(require 'nnmail)
-(require 'nnheader)
-(require 'mm-util)
-(require 'gnus)
-(require 'gnus-range)
-(require 'gnus-start)
-(require 'gnus-int)
+(nnoo-declare nnimap)
 
-(eval-when-compile (require 'cl))
+(defvoo nnimap-address nil
+  "The address of the IMAP server.")
 
-(autoload 'auth-source-user-or-password "auth-source")
+(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'.")
 
-(nnoo-declare nnimap)
+(defvoo nnimap-stream 'ssl
+  "How nnimap will talk to the IMAP server.
+Values are `ssl' and `network'.")
 
-(defconst nnimap-version "nnimap 1.0")
+(defvoo nnimap-inbox nil
+  "The mail box where incoming mail arrives and should be split out of.")
 
-(defgroup nnimap nil
-  "Reading IMAP mail with Gnus."
-  :group 'gnus)
+(defvoo nnimap-expunge-inbox nil
+  "If non-nil, expunge the inbox after fetching mail.
+This is always done if the server supports UID EXPUNGE, but it's
+not done by default on servers that doesn't support that command.")
 
-(defvoo nnimap-address nil
-  "Address of physical IMAP server.  If nil, use the virtual server's name.")
+(defvoo nnimap-connection-alist nil)
+(defvar nnimap-process nil)
 
-(defvoo nnimap-server-port nil
-  "Port number on physical IMAP server.
-If nil, defaults to 993 for TLS/SSL connections and 143 otherwise.")
-
-;; Splitting variables
-
-(defcustom nnimap-split-crosspost t
-  "If non-nil, do crossposting if several split methods match the mail.
-If nil, the first match found will be used."
-  :group 'nnimap
-  :type 'boolean)
-
-(defcustom nnimap-split-inbox nil
-  "Name of mailbox to split mail from.
-
-Mail is read from this mailbox and split according to rules in
-`nnimap-split-rule'.
-
-This can be a string or a list of strings."
-  :group 'nnimap
-  :type '(choice (string)
-                (repeat string)))
-
-(define-widget 'nnimap-strict-function 'function
-  "This widget only matches values that are functionp.
-
-Warning: This means that a value that is the symbol of a not yet
-loaded function will not match.  Use with care."
-  :match 'nnimap-strict-function-match)
-
-(defun nnimap-strict-function-match (widget value)
-  "Ignoring WIDGET, match if VALUE is a function."
-  (functionp value))
-
-(defcustom nnimap-split-rule nil
-  "Mail will be split according to these rules.
-
-Mail is read from mailbox(es) specified in `nnimap-split-inbox'.
-
-If you'd like, for instance, one mail group for mail from the
-\"gnus-imap\" mailing list, one group for junk mail and leave
-everything else in the incoming mailbox, you could do something like
-this:
-
-\(setq nnimap-split-rule '((\"INBOX.gnus-imap\"   \"From:.*gnus-imap\")
-                         (\"INBOX.junk\"        \"Subject:.*buy\")))
-
-As you can see, `nnimap-split-rule' is a list of lists, where the
-first element in each \"rule\" is the name of the IMAP mailbox (or the
-symbol `junk' if you want to remove the mail), and the second is a
-regexp that nnimap will try to match on the header to find a fit.
-
-The second element can also be a function.  In that case, it will be
-called narrowed to the headers with the first element of the rule as
-the argument.  It should return a non-nil value if it thinks that the
-mail belongs in that group.
-
-This variable can also have a function as its value, the function will
-be called with the headers narrowed and should return a group where it
-thinks the article should be splitted to.  See `nnimap-split-fancy'.
-
-To allow for different split rules on different virtual servers, and
-even different split rules in different inboxes on the same server,
-the syntax of this variable have been extended along the lines of:
-
-\(setq nnimap-split-rule
-      '((\"my1server\"    (\".*\"    ((\"ding\"    \"ding@gnus.org\")
-                                 (\"junk\"    \"From:.*Simon\")))
-       (\"my2server\"    (\"INBOX\" nnimap-split-fancy))
-       (\"my[34]server\" (\".*\"    ((\"private\" \"To:.*Simon\")
-                                 (\"junk\"    my-junk-func)))))
-
-The virtual server name is in fact a regexp, so that the same rules
-may apply to several servers.  In the example, the servers
-\"my3server\" and \"my4server\" both use the same rules.  Similarly,
-the inbox string is also a regexp.  The actual splitting rules are as
-before, either a function, or a list with group/regexp or
-group/function elements."
-  :group 'nnimap
-  ;; FIXME: Doesn't allow `("my2server" ("INBOX" nnimap-split-fancy))'
-  ;; per example above.  -- fx
-  :type '(choice :tag "Rule type"
-                (repeat :menu-tag "Single-server"
-                        :tag "Single-server list"
-                        (list (string :tag "Mailbox")
-                              (choice :tag "Predicate"
-                                      (regexp :tag "A regexp")
-                                      (nnimap-strict-function :tag "A function"))))
-                (choice :menu-tag "A function"
-                        :tag "A function"
-                        (function-item nnimap-split-fancy)
-                        (function-item nnmail-split-fancy)
-                        (nnimap-strict-function :tag "User-defined function"))
-                (repeat :menu-tag "Multi-server (extended)"
-                        :tag "Multi-server list"
-                        (list (regexp :tag "Server regexp")
-                              (list (regexp :tag "Incoming Mailbox regexp")
-                                    (repeat :tag "Rules for matching server(s) and mailbox(es)"
-                                            (list (string :tag "Destination mailbox")
-                                                  (choice :tag "Predicate"
-                                                          (regexp :tag "A Regexp")
-                                                          (nnimap-strict-function :tag "A Function")))))))))
-
-(defcustom nnimap-split-predicate "UNSEEN UNDELETED"
-  "The predicate used to find articles to split.
-If you use another IMAP client to peek on articles but always would
-like nnimap to split them once it's started, you could change this to
-\"UNDELETED\". Other available predicates are available in
-RFC2060 section 6.4.4."
-  :group 'nnimap
-  :type 'string)
-
-(defcustom nnimap-split-fancy nil
-  "Like the variable `nnmail-split-fancy'."
-  :group 'nnimap
-  :type 'sexp)
+(defvar nnimap-status-string "")
 
 (defvar nnimap-split-download-body-default nil
   "Internal variable with default value for `nnimap-split-download-body'.")
 
-(defcustom nnimap-split-download-body 'default
-  "Whether to download entire articles during splitting.
-This is generally not required, and will slow things down considerably.
-You may need it if you want to use an advanced splitting function that
-analyzes the body before splitting the article.
-If this variable is nil, bodies will not be downloaded; if this
-variable is the symbol `default' the default behavior is
-used (which currently is nil, unless you use a statistical
-spam.el test); if this variable is another non-nil value bodies
-will be downloaded."
-  :version "22.1"
-  :group 'nnimap
-  :type '(choice (const :tag "Let system decide" deault)
-                boolean))
-
-;; Performance / bug workaround variables
-
-(defcustom nnimap-close-asynchronous t
-  "Close mailboxes asynchronously in `nnimap-close-group'.
-This means that errors caught by nnimap when closing the mailbox will
-not prevent Gnus from updating the group status, which may be harmful.
-However, it increases speed."
-  :version "22.1"
-  :type 'boolean
-  :group 'nnimap)
-
-(defcustom nnimap-dont-close t
-  "Never close mailboxes.
-This increases the speed of closing mailboxes (quiting group) but may
-decrease the speed of selecting another mailbox later.  Re-selecting
-the same mailbox will be faster though."
-  :version "22.1"
-  :type 'boolean
-  :group 'nnimap)
-
-(defcustom nnimap-retrieve-groups-asynchronous t
-  "Send asynchronous STATUS commands for each mailbox before checking mail.
-If you have mailboxes that rarely receives mail, this speeds up new
-mail checking.  It works by first sending STATUS commands for each
-mailbox, and then only checking groups which has a modified UIDNEXT
-more carefully for new mail.
-
-In summary, the default is O((1-p)*k+p*n) and changing it to nil makes
-it O(n).  If p is small, then the default is probably faster."
-  :version "22.1"
-  :type 'boolean
-  :group 'nnimap)
-
-(defvoo nnimap-need-unselect-to-notice-new-mail t
-  "Unselect mailboxes before looking for new mail in them.
-Some servers seem to need this under some circumstances.")
-
-(defvoo nnimap-logout-timeout nil
-  "Close server immediately if it can't logout in this number of seconds.
-If it is nil, never close server until logout completes.  This variable
-overrides `imap-logout-timeout' on a per-server basis.")
-
-;; Authorization / Privacy variables
-
-(defvoo nnimap-auth-method nil
-  "Obsolete.")
-
-(defvoo nnimap-stream nil
-  "How nnimap will connect to the server.
-
-The default, nil, will try to use the \"best\" method the server can
-handle.
-
-Change this if
-
-1) you want to connect with TLS/SSL.  The TLS/SSL integration
-   with IMAP is suboptimal so you'll have to tell it
-   specifically.
-
-2) your server is more capable than your environment -- i.e. your
-   server accept Kerberos login's but you haven't installed the
-   `imtest' program or your machine isn't configured for Kerberos.
-
-Possible choices: gssapi, kerberos4, starttls, tls, ssl, network, shell.
-See also `imap-streams' and `imap-stream-alist'.")
-
-(defvoo nnimap-authenticator nil
-  "How nnimap authenticate itself to the server.
-
-The default, nil, will try to use the \"best\" method the server can
-handle.
-
-There is only one reason for fiddling with this variable, and that is
-if your server is more capable than your environment -- i.e. you
-connect to a server that accept Kerberos login's but you haven't
-installed the `imtest' program or your machine isn't configured for
-Kerberos.
-
-Possible choices: gssapi, kerberos4, digest-md5, cram-md5, login, anonymous.
-See also `imap-authenticators' and `imap-authenticator-alist'")
-
-(defvoo nnimap-directory (nnheader-concat gnus-directory "overview/")
-  "Directory to keep NOV cache files for nnimap groups.
-See also `nnimap-nov-file-name'.")
-
-(defvoo nnimap-nov-file-name "nnimap."
-  "NOV cache base filename.
-The group name and `nnimap-nov-file-name-suffix' will be appended.  A
-typical complete file name would be
-~/News/overview/nnimap.pdc.INBOX.ding.nov, or
-~/News/overview/nnimap/pdc/INBOX/ding/nov if
-`nnmail-use-long-file-names' is nil")
-
-(defvoo nnimap-nov-file-name-suffix ".novcache"
-  "Suffix for NOV cache base filename.")
-
-(defvoo nnimap-nov-is-evil gnus-agent
-  "If non-nil, never generate or use a local nov database for this backend.
-Using nov databases should speed up header fetching considerably.
-However, it will invoke a UID SEARCH UID command on the server, and
-some servers implement this command inefficiently by opening each and
-every message in the group, thus making it quite slow.
-Unlike other backends, you do not need to take special care if you
-flip this variable.")
-
-(defvoo nnimap-search-uids-not-since-is-evil nil
-  "If non-nil, avoid \"UID SEARCH UID ... NOT SINCE\" queries when expiring.
-Instead, use \"UID SEARCH SINCE\" to prune the list of expirable
-articles within Gnus.  This seems to be faster on Courier in some cases.")
-
-(defvoo nnimap-expunge-on-close 'always ; 'ask, 'never
-  "Whether to expunge a group when it is closed.
-When a IMAP group with articles marked for deletion is closed, this
-variable determine if nnimap should actually remove the articles or
-not.
-
-If always, nnimap always perform a expunge when closing the group.
-If never, nnimap never expunges articles marked for deletion.
-If ask, nnimap will ask you if you wish to expunge marked articles.
-
-When setting this variable to `never', you can only expunge articles
-by using `G x' (gnus-group-nnimap-expunge) from the Group buffer.")
-
-(defvoo nnimap-list-pattern "*"
-  "A string LIMIT or list of strings with mailbox wildcards used to limit available groups.
-See below for available wildcards.
-
-The LIMIT string can be a cons cell (REFERENCE . LIMIT), where
-REFERENCE will be passed as the first parameter to LIST/LSUB.  The
-semantics of this are server specific, on the University of Washington
-server you can specify a directory.
-
-Example:
- '(\"INBOX\" \"mail/*\" (\"~friend/mail/\" . \"list/*\"))
-
-There are two wildcards * and %. * matches everything, % matches
-everything in the current hierarchy.")
-
-(defvoo nnimap-news-groups nil
-  "IMAP support a news-like mode, also known as bulletin board mode,
-where replies is sent via IMAP instead of SMTP.
-
-This variable should contain a regexp matching groups where you wish
-replies to be stored to the mailbox directly.
-
-Example:
-  '(\"^[^I][^N][^B][^O][^X].*$\")
-
-This will match all groups not beginning with \"INBOX\".
-
-Note that there is nothing technically different between mail-like and
-news-like mailboxes.  If you wish to have a group with todo items or
-similar which you wouldn't want to set up a mailing list for, you can
-use this to make replies go directly to the group.")
-
-(defvoo nnimap-expunge-search-string "UID %s NOT SINCE %s"
-  "IMAP search command to use for articles that are to be expired.
-The first %s is replaced by a UID set of articles to search on,
-and the second %s is replaced by a date criterium.
-
-One useful (and perhaps the only useful) value to change this to would
-be `UID %s NOT SENTSINCE %s' to make nnimap use the Date: header
-instead of the internal date of messages.  See section 6.4.4 of RFC
-2060 for more information on valid strings.
-
-However, if `nnimap-search-uids-not-since-is-evil' is true, this
-variable has no effect since the search logic is reversed.")
-
-(defvoo nnimap-importantize-dormant t
-  "If non-nil, mark \"dormant\" articles as \"ticked\" for other IMAP clients.
-Note that within Gnus, dormant articles will still (only) be
-marked as ticked.  This is to make \"dormant\" articles stand out,
-just like \"ticked\" articles, in other IMAP clients.")
-
-(defvoo nnimap-server-address nil
-  "Obsolete.  Use `nnimap-address'.")
-
-(defcustom nnimap-authinfo-file "~/.authinfo"
-  "Authorization information for IMAP servers.  In .netrc format."
-  :type
-  '(choice file
-          (repeat :tag "Entries"
-                  :menu-tag "Inline"
-                  (list :format "%v"
-                        :value ("" ("login" . "") ("password" . ""))
-                        (string :tag "Host")
-                        (checklist :inline t
-                                   (cons :format "%v"
-                                         (const :format "" "login")
-                                         (string :format "Login: %v"))
-                                   (cons :format "%v"
-                                         (const :format "" "password")
-                                         (string :format "Password: %v"))))))
-  :group 'nnimap)
-
-(defcustom nnimap-prune-cache t
-  "If non-nil, nnimap check whether articles still exist on server before using data stored in NOV cache."
-  :type 'boolean
-  :group 'nnimap)
-
-(defvar nnimap-request-list-method 'imap-mailbox-list
-  "Method to use to request a list of all folders from the server.
-If this is 'imap-mailbox-lsub, then use a server-side subscription list to
-restrict visible folders.")
-
-(defcustom nnimap-id nil
-  "Plist with client identity to send to server upon login.
-A nil value means no information is sent, symbol `no' to disable ID query
-altogether, or plist with identifier-value pairs to send to
-server.  RFC 2971 describes the list as follows:
-
-   Any string may be sent as a field, but the following are defined to
-   describe certain values that might be sent.  Implementations are free
-   to send none, any, or all of these.  Strings are not case-sensitive.
-   Field strings MUST NOT be longer than 30 octets.  Value strings MUST
-   NOT be longer than 1024 octets.  Implementations MUST NOT send more
-   than 30 field-value pairs.
-
-     name            Name of the program
-     version         Version number of the program
-     os              Name of the operating system
-     os-version      Version of the operating system
-     vendor          Vendor of the client/server
-     support-url     URL to contact for support
-     address         Postal address of contact/vendor
-     date            Date program was released, specified as a date-time
-                       in IMAP4rev1
-     command         Command used to start the program
-     arguments       Arguments supplied on the command line, if any
-                       if any
-     environment     Description of environment, i.e., UNIX environment
-                       variables or Windows registry settings
-
-   Implementations MUST NOT send the same field name more than once.
-
-An example plist would be '(\"name\" \"Gnus\" \"version\" gnus-version-number
-\"os\" system-configuration \"vendor\" \"GNU\")."
-  :group 'nnimap
-  :type '(choice (const :tag "No information" nil)
-                (const :tag "Disable ID query" no)
-                (plist :key-type string :value-type string)))
-
-(defcustom nnimap-debug nil
-  "If non-nil, trace nnimap- functions into `nnimap-debug-buffer'.
-Uses `trace-function-background', so you can turn it off with,
-say, `untrace-all'.
-
-Note that username, passwords and other privacy sensitive
-information (such as e-mail) may be stored in the buffer.
-It is not written to disk, however.  Do not enable this
-variable unless you are comfortable with that.
-
-This variable only takes effect when loading the `nnimap' library.
-See also `nnimap-log'."
-  :group 'nnimap
-  :type 'boolean)
-
-;; Internal variables:
-
-(defvar nnimap-debug-buffer "*nnimap-debug*")
-(defvar nnimap-mailbox-info (gnus-make-hashtable 997))
-(defvar nnimap-current-move-server nil)
-(defvar nnimap-current-move-group nil)
-(defvar nnimap-current-move-article nil)
-(defvar nnimap-length)
-(defvar nnimap-progress-chars '(?| ?/ ?- ?\\))
-(defvar nnimap-progress-how-often 20)
-(defvar nnimap-counter)
-(defvar nnimap-server-buffer-alist nil)        ;; Map server name to buffers.
-(defvar nnimap-current-server nil) ;; Current server
-(defvar nnimap-server-buffer nil) ;; Current servers' buffer
-
-\f
-
-(nnoo-define-basics nnimap)
-
-;; Utility functions:
-
-(defsubst nnimap-decode-group-name (group)
-  (and group
-       (gnus-group-decoded-name group)))
-
-(defsubst nnimap-encode-group-name (group)
-  (and group
-       (mm-encode-coding-string group (gnus-group-name-charset nil group))))
-
-(defun nnimap-group-prefixed-name (group &optional server)
-  (gnus-group-prefixed-name group
-                           (gnus-server-to-method
-                            (format "nnimap:%s"
-                                    (or server nnimap-current-server)))))
-
-(defsubst nnimap-get-server-buffer (server)
-  "Return buffer for SERVER, if nil use current server."
-  (cadr (assoc (or server nnimap-current-server) nnimap-server-buffer-alist)))
-
-(defun nnimap-remove-server-from-buffer-alist (server list)
-  "Remove SERVER from LIST."
-  (let (l)
-    (dolist (e list)
-      (unless (equal server (car-safe e))
-       (push e l)))
-    l))
-
-(defun nnimap-possibly-change-server (server)
-  "Return buffer for SERVER, changing the current server as a side-effect.
-If SERVER is nil, uses the current server."
-  (setq nnimap-current-server (or server nnimap-current-server)
-       nnimap-server-buffer (nnimap-get-server-buffer nnimap-current-server)))
-
-(defun nnimap-verify-uidvalidity (group server)
-  "Verify stored uidvalidity match current one in GROUP on SERVER."
-  (let* ((gnusgroup (nnimap-group-prefixed-name group server))
-        (new-uidvalidity (imap-mailbox-get 'uidvalidity))
-        (old-uidvalidity (gnus-group-get-parameter gnusgroup 'uidvalidity))
-        (dir (file-name-as-directory (expand-file-name nnimap-directory)))
-        (nameuid (nnheader-translate-file-chars
-                  (concat nnimap-nov-file-name
-                          (if (equal server "")
-                              "unnamed"
-                            server) "." group "." old-uidvalidity
-                          nnimap-nov-file-name-suffix) t))
-        (file (if (or nnmail-use-long-file-names
-                      (file-exists-p (expand-file-name nameuid dir)))
-                  (expand-file-name nameuid dir)
-                (expand-file-name
-                 (mm-encode-coding-string
-                  (nnheader-replace-chars-in-string nameuid ?. ?/)
-                  nnmail-pathname-coding-system)
-                 dir))))
-    (if old-uidvalidity
-       (if (not (equal old-uidvalidity new-uidvalidity))
-           ;; uidvalidity clash
-           (progn
-             (gnus-group-set-parameter gnusgroup 'uidvalidity new-uidvalidity)
-             (gnus-group-remove-parameter gnusgroup 'imap-status)
-             (gnus-sethash (gnus-group-prefixed-name group server)
-                           nil nnimap-mailbox-info)
-             (gnus-delete-file file))
-         t)
-      (gnus-group-add-parameter gnusgroup (cons 'uidvalidity new-uidvalidity))
-      (gnus-group-remove-parameter gnusgroup 'imap-status)
-      (gnus-sethash                    ; Maybe not necessary here.
-       (gnus-group-prefixed-name group server)
-       nil nnimap-mailbox-info)
-      t)))
+(defstruct nnimap
+  group process commands capabilities)
 
-(defun nnimap-before-find-minmax-bugworkaround ()
-  "Function called before iterating through mailboxes with
-`nnimap-find-minmax-uid'."
-  (when nnimap-need-unselect-to-notice-new-mail
-    ;; XXX this is for UoW imapd problem, it doesn't notice new mail in
-    ;; currently selected mailbox without a re-select/examine.
-    (or (null (imap-current-mailbox nnimap-server-buffer))
-       (imap-mailbox-unselect nnimap-server-buffer))))
-
-(defun nnimap-find-minmax-uid (group &optional examine)
-  "Find lowest and highest active article number in GROUP.
-If EXAMINE is non-nil the group is selected read-only."
-  (with-current-buffer nnimap-server-buffer
-    (let ((decoded-group (nnimap-decode-group-name group)))
-      (when (or (string= decoded-group (imap-current-mailbox))
-               (imap-mailbox-select decoded-group examine))
-       (let (minuid maxuid)
-         (when (> (imap-mailbox-get 'exists) 0)
-           (imap-fetch "1:*" "UID" nil 'nouidfetch)
-           (imap-message-map
-            (lambda (uid Uid)
-              (setq minuid (if minuid (min minuid uid) uid)
-                    maxuid (if maxuid (max maxuid uid) uid)))
-            'UID))
-         (list (imap-mailbox-get 'exists) minuid maxuid))))))
-
-(defun nnimap-possibly-change-group (group &optional server)
-  "Make GROUP the current group, and SERVER the current server."
-  (when (nnimap-possibly-change-server server)
-    (let ((decoded-group (nnimap-decode-group-name group)))
-      (with-current-buffer nnimap-server-buffer
-       (if (or (null group) (imap-current-mailbox-p decoded-group))
-           imap-current-mailbox        ; Note: utf-7 encoded.
-         (if (imap-mailbox-select decoded-group)
-             (if (or (nnimap-verify-uidvalidity
-                      group (or server nnimap-current-server))
-                     (zerop (imap-mailbox-get 'exists decoded-group))
-                     t ;; for OGnus to see if ignoring uidvalidity
-                     ;; changes has any bad effects.
-                     (yes-or-no-p
-                      (format
-                       "nnimap: Group %s is not uidvalid.  Continue? "
-                       decoded-group)))
-                 imap-current-mailbox  ; Note: utf-7 encoded.
-               (imap-mailbox-unselect)
-               (error "nnimap: Group %s is not uid-valid" decoded-group))
-           (nnheader-report 'nnimap (imap-error-text))))))))
-
-(defun nnimap-replace-whitespace (string)
-  "Return STRING with all whitespace replaced with space."
-  (when string
-    (while (string-match "[\r\n\t]+" string)
-      (setq string (replace-match " " t t string)))
-    string))
-
-;; Required backend functions
-
-(defun nnimap-retrieve-headers-progress ()
-  "Hook to insert NOV line for current article into `nntp-server-buffer'."
-  (and (numberp nnmail-large-newsgroup)
-       (zerop (% (incf nnimap-counter) nnimap-progress-how-often))
-       (> nnimap-length nnmail-large-newsgroup)
-       (nnheader-message 6 "nnimap: Retrieving headers... %c"
-                        (nth (/ (% nnimap-counter
-                                   (* (length nnimap-progress-chars)
-                                      nnimap-progress-how-often))
-                                nnimap-progress-how-often)
-                             nnimap-progress-chars)))
-  (with-current-buffer nntp-server-buffer
-    (let (headers lines chars uid mbx)
-      (with-current-buffer nnimap-server-buffer
-       (setq uid imap-current-message
-             mbx (nnimap-encode-group-name (imap-current-mailbox))
-             headers (if (imap-capability 'IMAP4rev1)
-                          ;; xxx don't just use car? alist doesn't contain
-                          ;; anything else now, but it might...
-                          (nth 2 (car (imap-message-get uid 'BODYDETAIL)))
-                        (imap-message-get uid 'RFC822.HEADER))
-             lines (imap-body-lines (imap-message-body imap-current-message))
-             chars (imap-message-get imap-current-message 'RFC822.SIZE)))
-      (nnheader-insert-nov
-       ;; At this stage, we only have bytes, so let's use unibyte buffers
-       ;; to make it more clear.
-       (mm-with-unibyte-buffer
-        (buffer-disable-undo)
-        ;; headers can be nil if article is write-only
-        (when headers (insert headers))
-        (let ((head (nnheader-parse-naked-head uid)))
-          (mail-header-set-number head uid)
-          (mail-header-set-chars head chars)
-          (mail-header-set-lines head lines)
-          (mail-header-set-xref
-           head (format "%s %s:%d" (system-name) mbx uid))
-          head))))))
-
-(defun nnimap-retrieve-which-headers (articles fetch-old)
-  "Get a range of articles to fetch based on ARTICLES and FETCH-OLD."
-  (with-current-buffer nnimap-server-buffer
-    (if (numberp (car-safe articles))
-       (imap-search
-        (concat "UID "
-                (imap-range-to-message-set
-                 (gnus-compress-sequence
-                  (append (gnus-uncompress-sequence
-                           (and fetch-old
-                                (cons (if (numberp fetch-old)
-                                          (max 1 (- (car articles) fetch-old))
-                                        1)
-                                      (1- (car articles)))))
-                          articles)))))
-      (mapcar (lambda (msgid)
-               (imap-search
-                (format "HEADER Message-Id \"%s\"" msgid)))
-             articles))))
-
-(defun nnimap-group-overview-filename (group server)
-  "Make file name for GROUP on SERVER."
-  (let* ((dir (file-name-as-directory (expand-file-name nnimap-directory)))
-        (uidvalidity (gnus-group-get-parameter
-                      (nnimap-group-prefixed-name group server)
-                      'uidvalidity))
-        (name (nnheader-translate-file-chars
-               (concat nnimap-nov-file-name
-                       (if (equal server "")
-                           "unnamed"
-                         server) "." group nnimap-nov-file-name-suffix) t))
-        (nameuid (nnheader-translate-file-chars
-                  (concat nnimap-nov-file-name
-                          (if (equal server "")
-                              "unnamed"
-                            server) "." group "." uidvalidity
-                          nnimap-nov-file-name-suffix) t))
-        (oldfile (if (or nnmail-use-long-file-names
-                         (file-exists-p (expand-file-name name dir)))
-                     (expand-file-name name dir)
-                   (expand-file-name
-                    (mm-encode-coding-string
-                     (nnheader-replace-chars-in-string name ?. ?/)
-                     nnmail-pathname-coding-system)
-                    dir)))
-        (newfile (if (or nnmail-use-long-file-names
-                         (file-exists-p (expand-file-name nameuid dir)))
-                     (expand-file-name nameuid dir)
-                   (expand-file-name
-                    (mm-encode-coding-string
-                     (nnheader-replace-chars-in-string nameuid ?. ?/)
-                     nnmail-pathname-coding-system)
-                    dir))))
-    (when (and (file-exists-p oldfile) (not (file-exists-p newfile)))
-      (message "nnimap: Upgrading novcache filename...")
-      (sit-for 1)
-      (gnus-make-directory (file-name-directory newfile))
-      (unless (ignore-errors (rename-file oldfile newfile) t)
-       (if (ignore-errors (copy-file oldfile newfile) t)
-           (delete-file oldfile)
-         (error "Can't rename `%s' to `%s'" oldfile newfile))))
-    newfile))
-
-(defun nnimap-retrieve-headers-from-file (group server)
+(defvar nnimap-object nil)
+
+(defvar nnimap-mark-alist
+  '((read "\\Seen")
+    (tick "\\Flagged")
+    (reply "\\Answered")
+    (expire "gnus-expire")
+    (dormant "gnus-dormant")
+    (score "gnus-score")
+    (save "gnus-save")
+    (download "gnus-download")
+    (forward "gnus-forward")))
+
+(defvar nnimap-split-methods nil)
+
+(defun nnimap-buffer ()
+  (nnimap-find-process-buffer nntp-server-buffer))
+
+(defmacro nnimap-with-process-buffer (&rest body)
+  `(with-current-buffer (nnimap-find-process-buffer (current-buffer))
+     ,@body))
+
+(defun nnimap-retrieve-headers (articles &optional group server fetch-old)
   (with-current-buffer nntp-server-buffer
-    (let ((nov (nnimap-group-overview-filename group server)))
-      (when (file-exists-p nov)
-       (mm-insert-file-contents nov)
-       (set-buffer-modified-p nil)
-       (let ((min (ignore-errors (goto-char (point-min))
-                                 (read (current-buffer))))
-             (max (ignore-errors (goto-char (point-max))
-                                 (forward-line -1)
-                                 (read (current-buffer)))))
-         (if (and (numberp min) (numberp max))
-             (cons min max)
-           ;; junk, remove it, it's saved later
-           (erase-buffer)
-           nil))))))
-
-(defun nnimap-retrieve-headers-from-server (articles group server)
-  (with-current-buffer nnimap-server-buffer
-    (let ((imap-fetch-data-hook '(nnimap-retrieve-headers-progress))
-         (nnimap-length (gnus-range-length articles))
-         (nnimap-counter 0))
-      (imap-fetch (imap-range-to-message-set articles)
-                 (concat "(UID RFC822.SIZE BODY "
-                         (let ((headers
-                                (append '(Subject From Date Message-Id
-                                                  References In-Reply-To Xref)
-                                        (copy-sequence
-                                         nnmail-extra-headers))))
-                           (if (imap-capability 'IMAP4rev1)
-                               (format "BODY.PEEK[HEADER.FIELDS %s])" headers)
-                             (format "RFC822.HEADER.LINES %s)" headers)))))
-      (with-current-buffer nntp-server-buffer
-       (sort-numeric-fields 1 (point-min) (point-max)))
-      (and (numberp nnmail-large-newsgroup)
-          (> nnimap-length nnmail-large-newsgroup)
-          (nnheader-message 6 "nnimap: Retrieving headers...done")))))
-
-(defun nnimap-dont-use-nov-p (group server)
-  (or gnus-nov-is-evil nnimap-nov-is-evil
-      (unless (and (gnus-make-directory
-                   (file-name-directory
-                    (nnimap-group-overview-filename group server)))
-                  (file-writable-p
-                   (nnimap-group-overview-filename group server)))
-       (message "nnimap: Nov cache not writable, %s"
-                (nnimap-group-overview-filename group server)))))
-
-(deffoo nnimap-retrieve-headers (articles &optional group server fetch-old)
-  (when (nnimap-possibly-change-group group server)
-    (with-current-buffer nntp-server-buffer
-      (erase-buffer)
-      (if (nnimap-dont-use-nov-p group server)
-         (nnimap-retrieve-headers-from-server
-          (gnus-compress-sequence articles) group server)
-       (let (uids cached low high)
-         (when (setq uids (nnimap-retrieve-which-headers articles fetch-old)
-                     low (car uids)
-                     high (car (last uids)))
-           (if (setq cached (nnimap-retrieve-headers-from-file group server))
-               (progn
-                 ;; fetch articles with uids before cache block
-                 (when (< low (car cached))
-                   (goto-char (point-min))
-                   (nnimap-retrieve-headers-from-server
-                    (cons low (1- (car cached))) group server))
-                 ;; fetch articles with uids after cache block
-                 (when (> high (cdr cached))
-                   (goto-char (point-max))
-                   (nnimap-retrieve-headers-from-server
-                    (cons (1+ (cdr cached)) high) group server))
-                 (when nnimap-prune-cache
-                   ;; remove nov's for articles which has expired on server
-                   (goto-char (point-min))
-                   (dolist (uid (gnus-set-difference articles uids))
-                     (when (re-search-forward (format "^%d\t" uid) nil t)
-                       (gnus-delete-line)))))
-             ;; nothing cached, fetch whole range from server
-             (nnimap-retrieve-headers-from-server
-              (cons low high) group server))
-           (when (buffer-modified-p)
-             (nnmail-write-region
-              (point-min) (point-max)
-              (nnimap-group-overview-filename group server) nil 'nomesg))
-           (nnheader-nov-delete-outside-range low high))))
-      'nov)))
-
-(declare-function netrc-parse "netrc" (file))
-(declare-function netrc-machine-user-or-password "netrc"
-                 (mode authinfo-file-or-list machines ports defaults))
-
-(defun nnimap-open-connection (server)
-  ;; Note: `nnimap-open-server' that calls this function binds
-  ;; `imap-logout-timeout' to `nnimap-logout-timeout'.
-  (if (not (imap-open nnimap-address nnimap-server-port nnimap-stream
-                     nnimap-authenticator nnimap-server-buffer))
-      (nnheader-report 'nnimap "Can't open connection to server %s" server)
-    (require 'netrc)
-    (unless (or (imap-capability 'IMAP4 nnimap-server-buffer)
-               (imap-capability 'IMAP4rev1 nnimap-server-buffer))
-      (imap-close nnimap-server-buffer)
-      (nnheader-report 'nnimap "Server %s is not IMAP4 compliant" server))
-    (let* ((list (progn (gnus-message 7 "Parsing authinfo file `%s'."
-                                     nnimap-authinfo-file)
-                       (netrc-parse nnimap-authinfo-file)))
-          (port (if nnimap-server-port
-                    (int-to-string nnimap-server-port)
-                  "imap"))
-          (auth-info
-           (auth-source-user-or-password '("login" "password") server port))
-          (auth-user (nth 0 auth-info))
-          (auth-passwd (nth 1 auth-info))
-          (user (or
-                 auth-user ; this is preferred to netrc-*
-                 (netrc-machine-user-or-password
-                  "login"
-                  list
-                  (list server
-                        (or nnimap-server-address
-                            nnimap-address))
-                  (list port)
-                  (list "imap" "imaps" "143" "993"))))
-          (passwd (or
-                   auth-passwd ; this is preferred to netrc-*
-                   (netrc-machine-user-or-password
-                    "password"
-                    list
-                    (list server
-                          (or nnimap-server-address
-                              nnimap-address))
-                    (list port)
-                    (list "imap" "imaps" "143" "993")))))
-      (if (imap-authenticate user passwd nnimap-server-buffer)
-         (prog2
-             (setq nnimap-server-buffer-alist
-                   (nnimap-remove-server-from-buffer-alist
-                    server
-                    nnimap-server-buffer-alist))
-             (push (list server nnimap-server-buffer)
-                   nnimap-server-buffer-alist)
-           (imap-id nnimap-id nnimap-server-buffer)
-           (nnimap-possibly-change-server server))
-       (imap-close nnimap-server-buffer)
-       (kill-buffer nnimap-server-buffer)
-       (nnheader-report 'nnimap "Could not authenticate to %s" server)))))
-
-(deffoo nnimap-open-server (server &optional defs)
-  (nnheader-init-server-buffer)
+    (erase-buffer)
+    (when (nnimap-possibly-change-group group server)
+      (with-current-buffer (nnimap-buffer)
+       (nnimap-send-command "SELECT %S" (utf7-encode group t))
+       (erase-buffer)
+       (nnimap-wait-for-response
+        (nnimap-send-command
+         "UID FETCH %s %s"
+         (nnimap-article-ranges (gnus-compress-sequence articles))
+         (format "(UID RFC822.SIZE %s)"
+                 (format
+                  (if (member "IMAP4rev1"
+                              (nnimap-capabilities nnimap-object))
+                      "BODY.PEEK[HEADER.FIELDS %s]"
+                    "RFC822.HEADER.LINES %s")
+                  (append '(Subject From Date Message-Id
+                                    References In-Reply-To Xref)
+                          nnmail-extra-headers))))
+        t)
+       (nnimap-transform-headers))
+      (insert-buffer-substring
+       (nnimap-find-process-buffer (current-buffer))))
+    t))
+
+(defun nnimap-transform-headers ()
+  (goto-char (point-min))
+  (let (article bytes)
+    (block nil
+      (while (not (eobp))
+       (while (not (looking-at "^\\* [0-9]+ FETCH.*UID \\([0-9]+\\)"))
+         (delete-region (point) (progn (forward-line 1) (point)))
+         (when (eobp)
+           (return)))
+       (setq article (match-string 1)
+             bytes (nnimap-get-length))
+       (delete-region (line-beginning-position) (line-end-position))
+       (insert (format "211 %s Article retrieved." article))
+       (forward-line 1)
+       (insert (format "Bytes: %d\n" bytes))
+       (re-search-forward "^\r$")
+       (delete-region (line-beginning-position) (line-end-position))
+       (insert ".")
+       (forward-line 1)))))
+
+(defun nnimap-get-length ()
+  (and (re-search-forward "{\\([0-9]+\\)}" (line-end-position) t)
+       (string-to-number (match-string 1))))
+
+(defun nnimap-article-ranges (ranges)
+  (let (result)
+    (cond
+     ((numberp ranges)
+      (number-to-string ranges))
+     ((numberp (cdr ranges))
+      (format "%d:%d" (car ranges) (cdr ranges)))
+     (t
+      (dolist (elem ranges)
+       (push
+        (if (consp elem)
+            (format "%d:%d" (car elem) (cdr elem))
+          (number-to-string elem))
+        result))
+      (mapconcat #'identity (nreverse result) ",")))))
+
+(defun nnimap-open-server (server &optional defs)
   (if (nnimap-server-opened server)
       t
-    (unless (assq 'nnimap-server-buffer defs)
-      (push (list 'nnimap-server-buffer (concat " *nnimap* " server)) defs))
-    ;; translate `nnimap-server-address' to `nnimap-address' in defs
-    ;; for people that configured nnimap with a very old version
     (unless (assq 'nnimap-address defs)
-      (if (assq 'nnimap-server-address defs)
-         (push (list 'nnimap-address
-                     (cadr (assq 'nnimap-server-address defs))) defs)
-       (push (list 'nnimap-address server) defs)))
+      (setq defs (append defs (list (list 'nnimap-address server)))))
     (nnoo-change-server 'nnimap server defs)
-    (or nnimap-server-buffer
-       (setq nnimap-server-buffer (cadr (assq 'nnimap-server-buffer defs))))
-    (with-current-buffer (get-buffer-create nnimap-server-buffer)
-      (nnoo-change-server 'nnimap server defs))
-    (let ((imap-logout-timeout nnimap-logout-timeout))
-      (or (and nnimap-server-buffer
-              (imap-opened nnimap-server-buffer)
-              (if (with-current-buffer nnimap-server-buffer
-                    (memq imap-state '(auth selected examine)))
-                  t
-                (imap-close nnimap-server-buffer)
-                (nnimap-open-connection server)))
-         (nnimap-open-connection server)))))
-
-(deffoo nnimap-server-opened (&optional server)
-  "Whether SERVER is opened.
-If SERVER is the current virtual server, and the connection to the
-physical server is alive, this function return a non-nil value.  If
-SERVER is nil, it is treated as the current server."
-  ;; clean up autologouts??
-  (and (or server nnimap-current-server)
-       (nnoo-server-opened 'nnimap (or server nnimap-current-server))
-       (imap-opened (nnimap-get-server-buffer server))))
-
-(deffoo nnimap-close-server (&optional server)
-  "Close connection to server and free all resources connected to it.
-Return nil if the server couldn't be closed for some reason."
-  (let ((server (or server nnimap-current-server))
-       (imap-logout-timeout nnimap-logout-timeout))
-    (when (or (nnimap-server-opened server)
-             (imap-opened (nnimap-get-server-buffer server)))
-      (imap-close (nnimap-get-server-buffer server))
-      (kill-buffer (nnimap-get-server-buffer server))
-      (setq nnimap-server-buffer nil
-           nnimap-current-server nil
-           nnimap-server-buffer-alist
-           (nnimap-remove-server-from-buffer-alist
-            server
-            nnimap-server-buffer-alist)))
-    (nnoo-close-server 'nnimap server)))
-
-(deffoo nnimap-request-close ()
-  "Close connection to all servers and free all resources that the backend have reserved.
-All buffers that have been created by that
-backend should be killed.  (Not the nntp-server-buffer, though.) This
-function is generally only called when Gnus is shutting down."
-  (mapc (lambda (server) (nnimap-close-server (car server)))
-       nnimap-server-buffer-alist)
-  (setq nnimap-server-buffer-alist nil))
-
-(deffoo nnimap-status-message (&optional server)
-  "This function returns the last error message from server."
-  (when (nnimap-possibly-change-server server)
-    (nnoo-status-message 'nnimap server)))
-
-;; We used to use a string-as-multibyte here, but it is really incorrect.
-;; This function is used when we're about to insert a unibyte string
-;; into a potentially multibyte buffer.  The string is either an article
-;; header or body (or both?), undecoded.  When Emacs is asked to convert
-;; a unibyte string to multibyte, it may either use the equivalent of
-;; nothing (e.g. non-Mule XEmacs), string-make-unibyte (i.e. decode using
-;; locale), string-as-multibyte (decode using emacs-internal coding system)
-;; or string-to-multibyte (keep the data undecoded as a sequence of bytes).
-;; Only the last one preserves the data such that we can reliably later on
-;; decode the text using the mime info.
-(defalias 'nnimap-demule 'mm-string-to-multibyte)
-
-(defun nnimap-make-callback (article gnus-callback buffer)
-  "Return a callback function."
-  `(lambda ()
-     (nnimap-callback ,article ,gnus-callback ,buffer)))
-
-(defun nnimap-callback (article gnus-callback buffer)
-  (when (eq article (imap-current-message))
-    (remove-hook 'imap-fetch-data-hook
-                (nnimap-make-callback article gnus-callback buffer))
-    (with-current-buffer buffer
-      (insert
-       (with-current-buffer nnimap-server-buffer
-        (nnimap-demule
-         (if (imap-capability 'IMAP4rev1)
-             ;; xxx don't just use car? alist doesn't contain
-             ;; anything else now, but it might...
-             (nth 2 (car (imap-message-get article 'BODYDETAIL)))
-           (imap-message-get article 'RFC822)))))
-      (nnheader-ms-strip-cr)
-      (funcall gnus-callback t))))
-
-(defun nnimap-request-article-part (article part prop &optional
-                                           group server to-buffer detail)
-  (when (nnimap-possibly-change-group group server)
-    (let ((article (if (stringp article)
-                      (car-safe (imap-search
-                                 (format "HEADER Message-Id \"%s\"" article)
-                                 nnimap-server-buffer))
-                    article)))
-      (when article
-       (gnus-message 10 "nnimap: Fetching (part of) article %d from %s..."
-                     article (or (nnimap-decode-group-name group)
-                                 (imap-current-mailbox)
-                                 (nnimap-decode-group-name
-                                  gnus-newsgroup-name)))
-       (if (not nnheader-callback-function)
-           (with-current-buffer (or to-buffer nntp-server-buffer)
-             (erase-buffer)
-             (let ((data (imap-fetch article part prop nil
-                                     nnimap-server-buffer)))
-               ;; data can be nil if article is write-only
-               (when data
-                 (insert (nnimap-demule (if detail
-                                            (nth 2 (car data))
-                                          data)))))
-             (nnheader-ms-strip-cr)
-             (gnus-message
-              10 "nnimap: Fetching (part of) article %d from %s...done"
-              article (or (nnimap-decode-group-name group)
-                          (imap-current-mailbox)
-                          (nnimap-decode-group-name gnus-newsgroup-name)))
-             (if (bobp)
-                 (nnheader-report 'nnimap "No such article %d in %s: %s"
-                                  article (or (nnimap-decode-group-name group)
-                                              (imap-current-mailbox)
-                                              (nnimap-decode-group-name
-                                               gnus-newsgroup-name))
-                                  (imap-error-text nnimap-server-buffer))
-               (cons group article)))
-         (add-hook 'imap-fetch-data-hook
-                   (nnimap-make-callback article
-                                         nnheader-callback-function
-                                         nntp-server-buffer))
-         (imap-fetch-asynch article part nil nnimap-server-buffer)
-         (cons group article))))))
-
-(deffoo nnimap-asynchronous-p ()
+    (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*"
+                                  nnimap-address nnimap-server-port
+                                  (gnus-buffer-exists-p buffer)))
+    (mm-disable-multibyte)
+    (buffer-disable-undo)
+    (gnus-add-buffer)
+    (set (make-local-variable 'after-change-functions) nil)
+    (set (make-local-variable 'nnimap-object) (make-nnimap))
+    (push (list buffer (current-buffer)) nnimap-connection-alist)
+    (current-buffer)))
+
+(defun nnimap-open-connection (buffer)
+  (with-current-buffer (nnimap-make-process-buffer buffer)
+    (let ((credentials
+          (cond
+           ((eq nnimap-stream 'network)
+            (open-network-stream "*nnimap*" (current-buffer) nnimap-address
+                                 (or nnimap-server-port "imap"))
+            (netrc-credentials nnimap-address "imap"))
+           ((eq nnimap-stream 'ssl)
+            (open-tls-stream "*nnimap*" (current-buffer) nnimap-address
+                             (or nnimap-server-port "imaps"))
+            (netrc-credentials nnimap-address "imaps" "imap")))))
+      (setf (nnimap-process nnimap-object)
+           (get-buffer-process (current-buffer)))
+      (unless credentials
+       (delete-process (nnimap-process nnimap-object))
+       (error "Can't find user name/password for %s" nnimap-address))
+      (when (and (nnimap-process nnimap-object)
+                (memq (process-status (nnimap-process nnimap-object))
+                      '(open run)))
+       (gnus-set-process-query-on-exit-flag (nnimap-process nnimap-object) nil)
+       (let ((result (nnimap-command "LOGIN %S %S"
+                                     (car credentials) (cadr credentials))))
+         (unless (car result)
+           (delete-process (nnimap-process nnimap-object))
+           (error "Unable to login to the server: %s"
+                  (mapconcat #'identity (cadr result) " ")))
+         (setf (nnimap-capabilities nnimap-object)
+               (or (nnimap-find-parameter "CAPABILITY" (cdr result))
+                   (nnimap-find-parameter
+                    "CAPABILITY" (cdr (nnimap-command "CAPABILITY")))))
+         (when (member "QRESYNC" (nnimap-capabilities nnimap-object))
+           (nnimap-command "ENABLE QRESYNC"))
+         t)))))
+
+(defun nnimap-find-parameter (parameter elems)
+  (let (result)
+    (dolist (elem elems)
+      (cond
+       ((equal (car elem) parameter)
+       (setq result (cdr elem)))
+       ((and (equal (car elem) "OK")
+            (consp (cadr elem))
+            (equal (caadr elem) parameter))
+       (setq result (cdr (cadr elem))))))
+    result))
+
+(defun nnimap-close-server (&optional server)
   t)
 
-(deffoo nnimap-request-article (article &optional group server to-buffer)
-  (if (imap-capability 'IMAP4rev1 nnimap-server-buffer)
-      (nnimap-request-article-part
-       article "BODY.PEEK[]" 'BODYDETAIL group server to-buffer 'detail)
-    (nnimap-request-article-part
-     article "RFC822.PEEK" 'RFC822 group server to-buffer)))
-
-(deffoo nnimap-request-head (article &optional group server to-buffer)
-  (if (imap-capability 'IMAP4rev1 nnimap-server-buffer)
-      (nnimap-request-article-part
-       article "BODY.PEEK[HEADER]" 'BODYDETAIL group server to-buffer 'detail)
-    (nnimap-request-article-part
-     article "RFC822.HEADER" 'RFC822.HEADER group server to-buffer)))
-
-(deffoo nnimap-request-body (article &optional group server to-buffer)
-  (if (imap-capability 'IMAP4rev1 nnimap-server-buffer)
-      (nnimap-request-article-part
-       article "BODY.PEEK[TEXT]" 'BODYDETAIL group server to-buffer 'detail)
-    (nnimap-request-article-part
-     article "RFC822.TEXT.PEEK" 'RFC822.TEXT group server to-buffer)))
-
-(deffoo nnimap-request-group (group &optional server fast)
-  (nnimap-request-update-info-internal
-   group
-   (gnus-get-info (nnimap-group-prefixed-name group server))
-   server)
-  (when (nnimap-possibly-change-group group server)
-    (nnimap-before-find-minmax-bugworkaround)
-    (let (info)
-      (cond (fast group)
-           ((null (setq info (nnimap-find-minmax-uid group t)))
-            (nnheader-report 'nnimap "Could not get active info for %s"
-                             group))
-           (t
-            (nnheader-insert "211 %d %d %d %s\n" (or (nth 0 info) 0)
-                             (max 1 (or (nth 1 info) 1))
-                             (or (nth 2 info) 0) group)
-            (nnheader-report 'nnimap "Group %s selected" group)
-            t)))))
-
-(defun nnimap-update-unseen (group &optional server)
-  "Update the unseen count in `nnimap-mailbox-info'."
-  (gnus-sethash
-   (gnus-group-prefixed-name group server)
-   (let ((old (gnus-gethash-safe (gnus-group-prefixed-name group server)
-                                nnimap-mailbox-info)))
-     (list (nth 0 old) (nth 1 old)
-          (imap-mailbox-status (nnimap-decode-group-name group)
-                               'unseen nnimap-server-buffer)))
-   nnimap-mailbox-info))
-
-(defun nnimap-close-group (group &optional server)
-  (with-current-buffer nnimap-server-buffer
-    (when (and (imap-opened)
-              (nnimap-possibly-change-group group server))
-      (nnimap-update-unseen group server)
-      (case nnimap-expunge-on-close
-       (always (progn
-                 (imap-mailbox-expunge nnimap-close-asynchronous)
-                 (unless nnimap-dont-close
-                   (imap-mailbox-close nnimap-close-asynchronous))))
-       (ask (if (and (imap-search "DELETED")
-                     (gnus-y-or-n-p (format "Expunge articles in group `%s'? "
-                                            (imap-current-mailbox))))
-                (progn
-                  (imap-mailbox-expunge nnimap-close-asynchronous)
-                  (unless nnimap-dont-close
-                    (imap-mailbox-close nnimap-close-asynchronous)))
-              (imap-mailbox-unselect)))
-       (t (imap-mailbox-unselect)))
-      (not imap-current-mailbox))))
-
-(defun nnimap-pattern-to-list-arguments (pattern)
-  (mapcar (lambda (p)
-           (cons (car-safe p) (or (cdr-safe p) p)))
-         (if (and (listp pattern)
-                  (listp (cdr pattern)))
-             pattern
-           (list pattern))))
-
-(deffoo nnimap-request-list (&optional server)
-  (when (nnimap-possibly-change-server server)
-    (with-current-buffer nntp-server-buffer
-      (erase-buffer))
-    (gnus-message 5 "nnimap: Generating active list%s..."
-                 (if (> (length server) 0) (concat " for " server) ""))
-    (nnimap-before-find-minmax-bugworkaround)
-    (with-current-buffer nnimap-server-buffer
-      (dolist (pattern (nnimap-pattern-to-list-arguments nnimap-list-pattern))
-       (dolist (mbx (funcall nnimap-request-list-method
-                             (cdr pattern) (car pattern)))
-         (unless (member "\\noselect"
-                         (mapcar #'downcase
-                                 (imap-mailbox-get 'list-flags mbx)))
-           (let* ((encoded-mbx (nnimap-encode-group-name mbx))
-                  (info (nnimap-find-minmax-uid encoded-mbx 'examine)))
-             (when info
-               (with-current-buffer nntp-server-buffer
-                 (insert (format "\"%s\" %d %d y\n"
-                                 encoded-mbx (or (nth 2 info) 0)
-                                 (max 1 (or (nth 1 info) 1)))))))))))
-    (gnus-message 5 "nnimap: Generating active list%s...done"
-                 (if (> (length server) 0) (concat " for " server) ""))
-    t))
+(defun nnimap-request-close ()
+  t)
 
-(deffoo nnimap-request-post (&optional server)
-  (let ((success t))
-    (dolist (mbx (message-unquote-tokens
-                 (message-tokenize-header
-                  (message-fetch-field "Newsgroups") ", ")) success)
-      (let ((to-newsgroup (gnus-group-prefixed-name mbx gnus-command-method)))
-       (or (gnus-active to-newsgroup)
-           (gnus-activate-group to-newsgroup)
-           (if (gnus-y-or-n-p (format "No such group: %s.  Create it? "
-                                      to-newsgroup))
-               (or (and (gnus-request-create-group
-                         to-newsgroup gnus-command-method)
-                        (gnus-activate-group to-newsgroup nil nil
-                                             gnus-command-method))
-                   (error "Couldn't create group %s" to-newsgroup)))
-           (error "No such group: %s" to-newsgroup))
-       (unless (nnimap-request-accept-article mbx (nth 1 gnus-command-method))
-         (setq success nil))))))
-
-;; Optional backend functions
-
-(defun nnimap-string-lessp-numerical (s1 s2)
-  "Return t if first arg string is less than second in numerical order."
-  (cond ((string= s1 s2)
-        nil)
-       ((> (length s1) (length s2))
-        nil)
-       ((< (length s1) (length s2))
-        t)
-       ((< (string-to-number (substring s1 0 1))
-           (string-to-number (substring s2 0 1)))
-        t)
-       ((> (string-to-number (substring s1 0 1))
-           (string-to-number (substring s2 0 1)))
-        nil)
-       (t
-        (nnimap-string-lessp-numerical (substring s1 1) (substring s2 1)))))
-
-(deffoo nnimap-retrieve-groups (groups &optional server)
-  (when (nnimap-possibly-change-server server)
-    (gnus-message 5 "nnimap: Checking mailboxes...")
-    (with-current-buffer nntp-server-buffer
-      (erase-buffer)
-      (nnimap-before-find-minmax-bugworkaround)
-      (let (asyncgroups slowgroups decoded-group)
-       (if (null nnimap-retrieve-groups-asynchronous)
-           (setq slowgroups groups)
-         (dolist (group groups)
-           (setq decoded-group (nnimap-decode-group-name group))
-           (gnus-message 9 "nnimap: Quickly checking mailbox %s"
-                         decoded-group)
-           (add-to-list (if (gnus-group-get-parameter
-                             (nnimap-group-prefixed-name group)
-                             'imap-status)
-                            'asyncgroups
-                          'slowgroups)
-                        (list group (imap-mailbox-status-asynch
-                                     decoded-group
-                                     '(uidvalidity uidnext unseen)
-                                     nnimap-server-buffer))))
-         (dolist (asyncgroup asyncgroups)
-           (let* ((group (nth 0 asyncgroup))
-                  (tag   (nth 1 asyncgroup))
-                  (gnusgroup (nnimap-group-prefixed-name group))
-                  (saved-uidvalidity (gnus-group-get-parameter gnusgroup
-                                                               'uidvalidity))
-                  (saved-imap-status (gnus-group-get-parameter gnusgroup
-                                                               'imap-status))
-                  (saved-info (and saved-imap-status
-                                   (split-string saved-imap-status " "))))
-             (setq decoded-group (nnimap-decode-group-name group))
-             (when (imap-ok-p (imap-wait-for-tag tag nnimap-server-buffer))
-               (if (or (not (equal
-                             saved-uidvalidity
-                             (imap-mailbox-get 'uidvalidity decoded-group
-                                               nnimap-server-buffer)))
-                       (not (equal
-                             (nth 0 saved-info)
-                             (imap-mailbox-get 'uidnext decoded-group
-                                               nnimap-server-buffer))))
-                   (push (list group) slowgroups)
-                 (gnus-sethash
-                  (gnus-group-prefixed-name group server)
-                  (list (imap-mailbox-get 'uidvalidity
-                                          decoded-group nnimap-server-buffer)
-                        (imap-mailbox-get 'uidnext
-                                          decoded-group nnimap-server-buffer)
-                        (imap-mailbox-get 'unseen
-                                          decoded-group nnimap-server-buffer))
-                  nnimap-mailbox-info)
-                 (insert (format "\"%s\" %s %s y\n" group
-                                 (nth 2 saved-info)
-                                 (nth 1 saved-info))))))))
-       (dolist (group slowgroups)
-         (if nnimap-retrieve-groups-asynchronous
-             (setq group (car group)))
-         (setq decoded-group (nnimap-decode-group-name group))
-         (gnus-message 7 "nnimap: Mailbox %s modified" decoded-group)
-         (or (member "\\NoSelect" (imap-mailbox-get 'list-flags decoded-group
-                                                    nnimap-server-buffer))
-             (let* ((gnusgroup (nnimap-group-prefixed-name group))
-                    (status (imap-mailbox-status
-                             decoded-group '(uidvalidity uidnext unseen)
-                             nnimap-server-buffer))
-                    (info (nnimap-find-minmax-uid group 'examine))
-                    (min-uid (max 1 (or (nth 1 info) 1)))
-                    (max-uid (or (nth 2 info) 0)))
-               (when (> (or (imap-mailbox-get 'recent decoded-group
-                                              nnimap-server-buffer) 0)
-                        0)
-                 (push (list (cons decoded-group 0)) nnmail-split-history))
-               (insert (format "\"%s\" %d %d y\n" group max-uid min-uid))
-               (gnus-sethash
-                (gnus-group-prefixed-name group server)
-                status
-                nnimap-mailbox-info)
-               (if (not (equal (nth 0 status)
-                               (gnus-group-get-parameter gnusgroup
-                                                         'uidvalidity)))
-                   (nnimap-verify-uidvalidity group nnimap-current-server))
-               ;; The imap-status parameter is a string on the form
-               ;; "<uidnext> <min-uid> <max-uid>".
-               (gnus-group-add-parameter
-                gnusgroup
-                (cons 'imap-status
-                      (format "%s %s %s" (nth 1 status) min-uid max-uid))))))))
-    (gnus-message 5 "nnimap: Checking mailboxes...done")
-    'active))
-
-(deffoo nnimap-request-update-info-internal (group info &optional server)
-  (when (nnimap-possibly-change-group group server)
-    (when info ;; xxx what does this mean? should we create a info?
-      (with-current-buffer nnimap-server-buffer
-       (gnus-message 5 "nnimap: Updating info for %s..."
-                     (nnimap-decode-group-name (gnus-info-group info)))
-
-       (when (nnimap-mark-permanent-p 'read)
-         (let (seen unseen)
-           ;; read info could contain articles marked unread by other
-           ;; imap clients!  we correct this
-           (setq unseen (gnus-compress-sequence
-                         (imap-search "UNSEEN UNDELETED"))
-                 seen (gnus-range-difference (gnus-info-read info) unseen)
-                 seen (gnus-range-add seen
-                                      (gnus-compress-sequence
-                                       (imap-search "SEEN")))
-                 seen (if (and (integerp (car seen))
-                               (null (cdr seen)))
-                          (list (cons (car seen) (car seen)))
-                        seen))
-           (gnus-info-set-read info seen)))
-
-       (dolist (pred gnus-article-mark-lists)
-         (when (or (eq (cdr pred) 'recent)
-                   (and (nnimap-mark-permanent-p (cdr pred))
-                        (member (nnimap-mark-to-flag (cdr pred))
-                                (imap-mailbox-get 'flags))))
-           (gnus-info-set-marks
-            info
-            (gnus-update-alist-soft
-             (cdr pred)
-             (gnus-compress-sequence
-              (imap-search (nnimap-mark-to-predicate (cdr pred))))
-             (gnus-info-marks info))
-            t)))
-
-       (when nnimap-importantize-dormant
-         ;; nnimap mark dormant article as ticked too (for other clients)
-         ;; so we remove that mark for gnus since we support dormant
-         (gnus-info-set-marks
-          info
-          (gnus-update-alist-soft
-           'tick
-           (gnus-remove-from-range
-            (cdr-safe (assoc 'tick (gnus-info-marks info)))
-            (cdr-safe (assoc 'dormant (gnus-info-marks info))))
-           (gnus-info-marks info))
-          t))
-
-       (gnus-message 5 "nnimap: Updating info for %s...done"
-                     (nnimap-decode-group-name (gnus-info-group info)))
-
-       info))))
-
-(deffoo nnimap-request-type (group &optional article)
-  (if (and nnimap-news-groups (string-match nnimap-news-groups group))
-      'news
-    'mail))
-
-(deffoo nnimap-request-set-mark (group actions &optional server)
-  (when (nnimap-possibly-change-group group server)
-    (with-current-buffer nnimap-server-buffer
-      (let (action)
-       (gnus-message 7 "nnimap: Setting marks in %s..."
-                     (nnimap-decode-group-name group))
-       (while (setq action (pop actions))
-         (let ((range (nth 0 action))
-               (what  (nth 1 action))
-               (cmdmarks (nth 2 action))
-               marks)
-           ;; bookmark can't be stored (not list/range
-           (setq cmdmarks (delq 'bookmark cmdmarks))
-           ;; killed can't be stored (not list/range
-           (setq cmdmarks (delq 'killed cmdmarks))
-           ;; unsent are for nndraft groups only
-           (setq cmdmarks (delq 'unsent cmdmarks))
-           ;; cache flags are pointless on the server
-           (setq cmdmarks (delq 'cache cmdmarks))
-           ;; seen flags are local to each gnus
-           (setq cmdmarks (delq 'seen cmdmarks))
-           ;; recent marks can't be set
-           (setq cmdmarks (delq 'recent cmdmarks))
-           (when nnimap-importantize-dormant
-             ;; flag dormant articles as ticked
-             (if (memq 'dormant cmdmarks)
-                 (setq cmdmarks (cons 'tick cmdmarks))))
-           ;; remove stuff we are forbidden to store
-           (mapc (lambda (mark)
-                   (if (imap-message-flag-permanent-p
-                        (nnimap-mark-to-flag mark))
-                       (setq marks (cons mark marks))))
-                 cmdmarks)
-           (when (and range marks)
-             (cond ((eq what 'del)
-                    (imap-message-flags-del
-                     (imap-range-to-message-set range)
-                     (nnimap-mark-to-flag marks nil t)))
-                   ((eq what 'add)
-                    (imap-message-flags-add
-                     (imap-range-to-message-set range)
-                     (nnimap-mark-to-flag marks nil t)))
-                   ((eq what 'set)
-                    (imap-message-flags-set
-                     (imap-range-to-message-set range)
-                     (nnimap-mark-to-flag marks nil t)))))))
-       (gnus-message 7 "nnimap: Setting marks in %s...done"
-                     (nnimap-decode-group-name group)))))
-  nil)
+(defun nnimap-server-opened (&optional server)
+  (and (nnoo-current-server-p 'nnimap server)
+       nntp-server-buffer
+       (gnus-buffer-live-p nntp-server-buffer)
+       (nnimap-find-connection nntp-server-buffer)))
 
-(defun nnimap-split-fancy ()
-  "Like the function `nnmail-split-fancy', but uses `nnimap-split-fancy'."
-  (let ((nnmail-split-fancy nnimap-split-fancy))
-    (nnmail-split-fancy)))
+(defun nnimap-status-message (&optional server)
+  nnimap-status-string)
 
-(defun nnimap-split-to-groups (rules)
-  ;; tries to match all rules in nnimap-split-rule against content of
-  ;; nntp-server-buffer, returns a list of groups that matched.
-  ;; Note: This function takes and returns decoded group names.
+(defun nnimap-request-article (article &optional group server to-buffer)
   (with-current-buffer nntp-server-buffer
-    ;; Fold continuation lines.
-    (goto-char (point-min))
-    (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
-      (replace-match " " t t))
-    (if (functionp rules)
-       (funcall rules)
-      (let (to-groups regrepp)
-       (catch 'split-done
-         (dolist (rule rules to-groups)
-           (let ((group (car rule))
-                 (regexp (cadr rule)))
+    (let ((result (nnimap-possibly-change-group group server)))
+      (when (stringp article)
+       (setq article (nnimap-find-article-by-message-id group article)))
+      (when (and result
+                article)
+       (erase-buffer)
+       (nnimap-with-process-buffer
+        (erase-buffer)
+        (setq result
+              (nnimap-command
+               (if (member "IMAP4rev1" (nnimap-capabilities nnimap-object))
+                   "UID FETCH %d BODY.PEEK[]"
+                 "UID FETCH %d RFC822.PEEK")
+               article)))
+       (let ((buffer (nnimap-find-process-buffer (current-buffer))))
+         (when (car result)
+           (with-current-buffer to-buffer
+             (insert-buffer-substring buffer)
              (goto-char (point-min))
-             (when (and (if (stringp regexp)
-                            (progn
-                              (if (not (stringp group))
-                                  (setq group (eval group))
-                                (setq regrepp
-                                      (string-match "\\\\[0-9&]" group)))
-                              (re-search-forward regexp nil t))
-                          (funcall regexp group))
-                        ;; Don't enter the article into the same group twice.
-                        (not (assoc group to-groups)))
-               (push (if regrepp
-                         (nnmail-expand-newtext group)
-                       group)
-                     to-groups)
-               (or nnimap-split-crosspost
-                   (throw 'split-done to-groups))))))))))
-
-(defun nnimap-assoc-match (key alist)
-  (let (element)
-    (while (and alist (not element))
-      (if (string-match (car (car alist)) key)
-         (setq element (car alist)))
-      (setq alist (cdr alist)))
-    element))
-
-(defun nnimap-split-find-rule (server inbox)
-  (if (and (listp nnimap-split-rule) (listp (car nnimap-split-rule))
-          (list (cdar nnimap-split-rule)) (listp (cadar nnimap-split-rule)))
-      ;; extended format
-      (cadr (nnimap-assoc-match inbox (cdr (nnimap-assoc-match
-                                           server nnimap-split-rule))))
-    nnimap-split-rule))
-
-(defun nnimap-split-find-inbox (server)
-  (if (listp nnimap-split-inbox)
-      nnimap-split-inbox
-    (list nnimap-split-inbox)))
-
-(defun nnimap-split-articles (&optional group server)
-  ;; Note: Assumes decoded group names in nnimap-split-inbox,
-  ;; nnimap-split-rule, nnimap-split-fancy, and nnmail-split-history.
-  (when (nnimap-possibly-change-server server)
-    (with-current-buffer nnimap-server-buffer
-      (let (rule inbox removeorig
-           (inboxes (nnimap-split-find-inbox server)))
-       ;; iterate over inboxes
-       (while (and (setq inbox (pop inboxes))
-                   (nnimap-possibly-change-group
-                    (nnimap-encode-group-name inbox))) ;; SELECT
-         ;; find split rule for this server / inbox
-         (when (setq rule (nnimap-split-find-rule server inbox))
-           ;; iterate over articles
-           (dolist (article (imap-search nnimap-split-predicate))
-             (when (if (if (eq nnimap-split-download-body 'default)
-                           nnimap-split-download-body-default
-                         nnimap-split-download-body)
-                       (and (nnimap-request-article article)
-                            (with-current-buffer nntp-server-buffer (mail-narrow-to-head)))
-                     (nnimap-request-head article))
-               ;; copy article to right group(s)
-               (setq removeorig nil)
-               (dolist (to-group (nnimap-split-to-groups rule))
-                 (cond ((eq to-group 'junk)
-                        (message "IMAP split removed %s:%s:%d" server inbox
-                                 article)
-                        (setq removeorig t))
-                       ((imap-message-copy (number-to-string article)
-                                           to-group nil 'nocopyuid)
-                        (message "IMAP split moved %s:%s:%d to %s" server
-                                 inbox article to-group)
-                        (setq removeorig t)
-                        (when nnmail-cache-accepted-message-ids
-                          (with-current-buffer nntp-server-buffer
-                            (let (msgid)
-                              (and (setq msgid
-                                         (nnmail-fetch-field "message-id"))
-                                   (nnmail-cache-insert msgid
-                                                        (nnimap-encode-group-name to-group)
-                                                        (nnmail-fetch-field "subject"))))))
-                        ;; Add the group-art list to the history list.
-                        (push (list (cons to-group 0)) nnmail-split-history))
-                       (t
-                        (message "IMAP split failed to move %s:%s:%d to %s"
-                                 server inbox article to-group))))
-               (if (if (eq nnimap-split-download-body 'default)
-                       nnimap-split-download-body-default
-                     nnimap-split-download-body)
-                   (widen))
-               ;; remove article if it was successfully copied somewhere
-               (and removeorig
-                    (imap-message-flags-add (format "%d" article)
-                                            "\\Seen \\Deleted")))))
-         (when (imap-mailbox-select inbox) ;; just in case
-           ;; todo: UID EXPUNGE (if available) to remove splitted articles
-           (imap-mailbox-expunge)
-           (imap-mailbox-close)))
-       (when nnmail-cache-accepted-message-ids
-         (nnmail-cache-close))
+             (let ((bytes (nnimap-get-length)))
+               (delete-region (line-beginning-position)
+                              (progn (forward-line 1) (point)))
+               (goto-char (+ (point) bytes))
+               (delete-region (point) (point-max))
+               (nnheader-ms-strip-cr))
+             t)))))))
+
+(defun nnimap-request-group (group &optional server dont-check)
+  (with-current-buffer nntp-server-buffer
+    (let ((result (nnimap-possibly-change-group group server))
+         articles)
+      (when result
+       (setq articles (nnimap-get-flags "1:*"))
+       (erase-buffer)
+       (insert
+        (format
+         "211 %d %d %d %S\n"
+         (length articles)
+         (or (caar articles) 0)
+         (or (caar (last articles)) 0)
+         group))
        t))))
 
-(deffoo nnimap-request-scan (&optional group server)
-  (nnimap-split-articles group server))
-
-(deffoo nnimap-request-newgroups (date &optional server)
-  (when (nnimap-possibly-change-server server)
-    (with-current-buffer nntp-server-buffer
-      (gnus-message 5 "nnimap: Listing subscribed mailboxes%s%s..."
-                   (if (> (length server) 0) " on " "") server)
+(defun nnimap-get-flags (spec)
+  (let ((articles nil)
+       elems)
+    (with-current-buffer (nnimap-buffer)
       (erase-buffer)
-      (nnimap-before-find-minmax-bugworkaround)
-      (dolist (pattern (nnimap-pattern-to-list-arguments
-                       nnimap-list-pattern))
-       (dolist (mbx (funcall nnimap-request-list-method (cdr pattern) (car pattern) nil
-                              nnimap-server-buffer))
-         (or (catch 'found
-               (dolist (mailbox (imap-mailbox-get 'list-flags mbx
-                                                  nnimap-server-buffer))
-                 (if (string= (downcase mailbox) "\\noselect")
-                     (throw 'found t)))
-               nil)
-             (let* ((encoded-mbx (nnimap-encode-group-name mbx))
-                    (info (nnimap-find-minmax-uid encoded-mbx 'examine)))
-               (when info
-                 (insert (format "\"%s\" %d %d y\n"
-                                 encoded-mbx (or (nth 2 info) 0)
-                                 (max 1 (or (nth 1 info) 1)))))))))
-      (gnus-message 5 "nnimap: Listing subscribed mailboxes%s%s...done"
-                   (if (> (length server) 0) " on " "") server))
-    t))
+      (nnimap-wait-for-response (nnimap-send-command
+                                "UID FETCH %s FLAGS" spec))
+      (goto-char (point-min))
+      (while (re-search-forward "^\\* [0-9]+ FETCH (\\(.*\\))" nil t)
+       (setq elems (nnimap-parse-line (match-string 1)))
+       (push (cons (string-to-number (cadr (member "UID" elems)))
+                   (cadr (member "FLAGS" elems)))
+             articles)))
+    (nreverse articles)))
 
-(deffoo nnimap-request-create-group (group &optional server args)
-  (when (nnimap-possibly-change-server server)
-    (let ((decoded-group (nnimap-decode-group-name group)))
-      (or (imap-mailbox-status decoded-group 'uidvalidity nnimap-server-buffer)
-         (imap-mailbox-create decoded-group nnimap-server-buffer)
-         (nnheader-report 'nnimap "%S"
-                          (imap-error-text nnimap-server-buffer))))))
-
-(defun nnimap-time-substract (time1 time2)
-  "Return TIME for TIME1 - TIME2."
-  (let* ((ms (- (car time1) (car time2)))
-        (ls (- (nth 1 time1) (nth 1 time2))))
-    (if (< ls 0)
-       (list (- ms 1) (+ (expt 2 16) ls))
-      (list ms ls))))
-
-(eval-when-compile (require 'parse-time))
-(defun nnimap-date-days-ago (daysago)
-  "Return date, in format \"3-Aug-1998\", for DAYSAGO days ago."
-  (require 'parse-time)
-  (let* ((time (nnimap-time-substract (current-time) (days-to-time daysago)))
-        (date (format-time-string
-               (format "%%d-%s-%%Y"
-                       (capitalize (car (rassoc (nth 4 (decode-time time))
-                                                parse-time-months))))
-               time)))
-    (if (eq ?0 (string-to-char date))
-       (substring date 1)
-      date)))
-
-(defun nnimap-request-expire-articles-progress ()
-  (gnus-message 5 "nnimap: Marking article %d for deletion..."
-               imap-current-message))
-
-(defun nnimap-expiry-target (arts group server)
-  (unless (eq nnmail-expiry-target 'delete)
-    (with-temp-buffer
-      (dolist (art arts)
-       (nnimap-request-article art group server (current-buffer))
-       ;; hints for optimization in `nnimap-request-accept-article'
-       (let ((nnimap-current-move-article art)
-             (nnimap-current-move-group group)
-             (nnimap-current-move-server server))
-         (nnmail-expiry-target-group nnmail-expiry-target group))))
-    ;; It is not clear if `nnmail-expiry-target' somehow cause the
-    ;; current group to be changed or not, so we make sure here.
-    (nnimap-possibly-change-group group server)))
-
-;; Notice that we don't actually delete anything, we just mark them deleted.
-(deffoo nnimap-request-expire-articles (articles group &optional server force)
-  (let ((artseq (gnus-compress-sequence articles)))
-    (when (and artseq (nnimap-possibly-change-group group server))
-      (with-current-buffer nnimap-server-buffer
-       (let ((days (or (and nnmail-expiry-wait-function
-                            (funcall nnmail-expiry-wait-function group))
-                       nnmail-expiry-wait)))
-         (cond ((or force (eq days 'immediate))
-                (let ((oldarts (imap-search
-                                (concat "UID "
-                                        (imap-range-to-message-set artseq)))))
-                  (when oldarts
-                    (nnimap-expiry-target oldarts group server)
-                    (when (imap-message-flags-add
-                           (imap-range-to-message-set
-                            (gnus-compress-sequence oldarts)) "\\Deleted")
-                      (setq articles (gnus-set-difference
-                                      articles oldarts))))))
-               ((and nnimap-search-uids-not-since-is-evil (numberp days))
-                (let* ((all-new-articles
-                        (gnus-compress-sequence
-                         (imap-search (format "SINCE %s"
-                                              (nnimap-date-days-ago days)))))
-                       (oldartseq
-                        (gnus-range-difference artseq all-new-articles))
-                       (oldarts (gnus-uncompress-range oldartseq)))
-                  (when oldarts
-                    (nnimap-expiry-target oldarts group server)
-                    (when (imap-message-flags-add
-                           (imap-range-to-message-set oldartseq)
-                           "\\Deleted")
-                      (setq articles (gnus-set-difference
-                                      articles oldarts))))))
-               ((numberp days)
-                (let ((oldarts (imap-search
-                                (format nnimap-expunge-search-string
-                                        (imap-range-to-message-set artseq)
-                                        (nnimap-date-days-ago days))))
-                      (imap-fetch-data-hook
-                       '(nnimap-request-expire-articles-progress)))
-                  (when oldarts
-                    (nnimap-expiry-target oldarts group server)
-                    (when (imap-message-flags-add
-                           (imap-range-to-message-set
-                            (gnus-compress-sequence oldarts)) "\\Deleted")
-                      (setq articles (gnus-set-difference
-                                      articles oldarts)))))))))))
-  ;; return articles not deleted
-  articles)
+(defun nnimap-close-group (group &optional server)
+  t)
 
 (deffoo nnimap-request-move-article (article group server accept-form
-                                            &optional last move-is-internal)
-  (when (nnimap-possibly-change-server server)
-    (save-excursion
-      (let ((buf (get-buffer-create " *nnimap move*"))
-           (nnimap-current-move-article article)
-           (nnimap-current-move-group group)
-           (nnimap-current-move-server nnimap-current-server)
-           result)
-       (gnus-message 10 "nnimap-request-move-article: this is an %s move"
-                     (if move-is-internal
-                         "internal"
-                       "external"))
-       ;; request the article only when the move is NOT internal
-       (and (or move-is-internal
-                (nnimap-request-article article group server))
-            (with-current-buffer buf
-              (buffer-disable-undo (current-buffer))
-              (insert-buffer-substring nntp-server-buffer)
-              (setq result (eval accept-form))
-              (kill-buffer buf)
-              result)
-            (nnimap-possibly-change-group group server)
-            (imap-message-flags-add
-             (imap-range-to-message-set (list article))
-             "\\Deleted" 'silent nnimap-server-buffer))
-       result))))
+                                            &optional last internal-move-group)
+  (when (nnimap-possibly-change-group group server)
+    ;; If the move is internal (on the same server), just do it the easy
+    ;; way.
+    (let ((message-id (message-field-value "message-id")))
+      (if internal-move-group
+         (let ((result
+                (with-current-buffer (nnimap-buffer)
+                  (nnimap-command "UID COPY %d %S"
+                                  article
+                                  (utf7-encode internal-move-group t)))))
+           (when (car result)
+             (nnimap-delete-article article)
+             (cons internal-move-group
+                   (nnimap-find-article-by-message-id
+                    internal-move-group message-id))))
+       (with-temp-buffer
+         (let ((result (eval accept-form)))
+           (when result
+             (nnimap-delete-article article)
+             result)))))))
+
+(deffoo nnimap-request-expire-articles (articles group &optional server force)
+  (cond
+   ((not (nnimap-possibly-change-group group server))
+    articles)
+   (force
+    (unless (nnimap-delete-article articles)
+      (message "Article marked for deletion, but not expunged."))
+    nil)
+   (t
+    articles)))
+
+(defun nnimap-find-article-by-message-id (group message-id)
+  (when (nnimap-possibly-change-group group nil)
+    (with-current-buffer (nnimap-buffer)
+      (let ((result
+            (nnimap-command "UID SEARCH HEADER Message-Id %S" message-id))
+           article)
+       (when (car result)
+         ;; Select the last instance of the message in the group.
+         (and (setq article
+                    (car (last (assoc "SEARCH" (cdr result)))))
+              (string-to-number article)))))))
+
+(defun nnimap-delete-article (articles)
+  (with-current-buffer (nnimap-buffer)
+    (nnimap-command "UID STORE %s +FLAGS.SILENT (\\Deleted)"
+                   (nnimap-article-ranges articles))
+    (when (member "UIDPLUS" (nnimap-capabilities nnimap-object))
+      (nnimap-send-command "UID EXPUNGE %s"
+                          (nnimap-article-ranges articles))
+      t)))
+
+(deffoo nnimap-request-scan (&optional group server)
+  (when (and (nnimap-possibly-change-group nil server)
+            (equal group nnimap-inbox)
+            nnimap-inbox
+            nnimap-split-methods)
+    (nnimap-split-incoming-mail)))
+
+(defun nnimap-marks-to-flags (marks)
+  (let (flags flag)
+    (dolist (mark marks)
+      (when (setq flag (cadr (assq mark nnimap-mark-alist)))
+       (push flag flags)))
+    flags))
+
+(defun nnimap-request-set-mark (group actions &optional server)
+  (when (nnimap-possibly-change-group group server)
+    (let (sequence)
+      (with-current-buffer (nnimap-find-process-buffer nntp-server-buffer)
+       ;; Just send all the STORE commands without waiting for
+       ;; response.  If they're successful, they're successful.
+       (dolist (action actions)
+         (destructuring-bind (range action marks) action
+           (let ((flags (nnimap-marks-to-flags marks)))
+             (when flags
+               (setq sequence (nnimap-send-command
+                               "UID STORE %s %sFLAGS.SILENT (%s)"
+                               (nnimap-article-ranges range)
+                               (if (eq action 'del)
+                                   "-"
+                                 "+")
+                               (mapconcat #'identity flags " ")))))))
+       ;; Wait for the last command to complete to avoid later
+       ;; syncronisation problems with the stream.
+       (nnimap-wait-for-response sequence)))))
 
 (deffoo nnimap-request-accept-article (group &optional server last)
-  (when (nnimap-possibly-change-server server)
-    (let (uid)
-      (if (setq uid
-               (if (string= nnimap-current-server nnimap-current-move-server)
-                   ;; moving article within same server, speed it up...
-                   (and (nnimap-possibly-change-group
-                         nnimap-current-move-group)
-                        (imap-message-copy (number-to-string
-                                            nnimap-current-move-article)
-                                           (nnimap-decode-group-name group)
-                                           'dontcreate nil
-                                           nnimap-server-buffer))
-                 (with-current-buffer (current-buffer)
-                   (goto-char (point-min))
-                   ;; remove any 'From blabla' lines, some IMAP servers
-                   ;; reject the entire message otherwise.
-                   (when (looking-at "^From[^:]")
-                     (delete-region (point) (progn (forward-line) (point))))
-                   ;; turn into rfc822 format (\r\n eol's)
-                   (while (search-forward "\n" nil t)
-                     (replace-match "\r\n"))
-                   (when nnmail-cache-accepted-message-ids
-                     (nnmail-cache-insert (nnmail-fetch-field "message-id")
-                                          group
-                                          (nnmail-fetch-field "subject"))))
-                 (when (and last nnmail-cache-accepted-message-ids)
-                   (nnmail-cache-close))
-                 ;; this 'or' is for Cyrus server bug
-                 (or (null (imap-current-mailbox nnimap-server-buffer))
-                     (imap-mailbox-unselect nnimap-server-buffer))
-                 (imap-message-append (nnimap-decode-group-name group)
-                                      (current-buffer) nil nil
-                                      nnimap-server-buffer)))
-         (cons group (nth 1 uid))
-       (nnheader-report 'nnimap (imap-error-text nnimap-server-buffer))))))
-
-(deffoo nnimap-request-delete-group (group force &optional server)
-  (when (nnimap-possibly-change-server server)
-    (setq group (nnimap-decode-group-name group))
-    (when (string= group (imap-current-mailbox nnimap-server-buffer))
-      (imap-mailbox-unselect nnimap-server-buffer))
-    (with-current-buffer nnimap-server-buffer
-      (if force
-         (or (null (imap-mailbox-status group 'uidvalidity))
-             (imap-mailbox-delete group))
-       ;; UNSUBSCRIBE?
+  (when (nnimap-possibly-change-group nil server)
+    (nnmail-check-syntax)
+    (let ((message (buffer-string))
+         (message-id (message-field-value "message-id"))
+         sequence)
+      (with-current-buffer nntp-server-buffer
+       (nnimap-with-process-buffer
+        (setq sequence (nnimap-send-command
+                        "APPEND %S {%d}" (utf7-encode group t)
+                        (length message)))
+        (process-send-string (get-buffer-process (current-buffer)) message)
+        (process-send-string (get-buffer-process (current-buffer)) "\r\n")
+        (let ((result (nnimap-get-response sequence)))
+          (when result
+            (cons group
+                  (nnimap-find-article-by-message-id group message-id)))))))))
+
+(defun nnimap-add-cr ()
+  (goto-char (point-min))
+  (while (re-search-forward "\r?\n" nil t)
+    (replace-match "\r\n" t t)))
+
+(defun nnimap-get-groups ()
+  (let ((result (nnimap-command "LIST \"\" \"*\""))
+       groups)
+    (when (car result)
+      (dolist (line (cdr result))
+       (when (and (equal (car line) "LIST")
+                  (not (string-match "noselect" (caadr line))))
+         (push (car (last line)) groups)))
+      (nreverse groups))))
+
+(defun nnimap-request-list (&optional server)
+  (nnimap-possibly-change-group nil server)
+  (with-current-buffer nntp-server-buffer
+    (erase-buffer)
+    (let ((groups
+          (nnimap-with-process-buffer
+           (nnimap-get-groups)))
+         sequences responses)
+      (when groups
+       (nnimap-with-process-buffer
+        (dolist (group groups)
+          (push (list (nnimap-send-command "EXAMINE %S" (utf7-encode group t))
+                      group)
+                sequences))
+        (nnimap-wait-for-response (caar sequences))
+        (setq responses
+              (nnimap-get-responses (mapcar #'car sequences))))
+       (dolist (response responses)
+         (let* ((sequence (car response))
+                (response (cadr response))
+                (group (cadr (assoc sequence sequences))))
+           (when (and group
+                      (equal (caar response) "OK"))
+             (let ((uidnext (nnimap-find-parameter "UIDNEXT" response))
+                   highest exists)
+               (dolist (elem response)
+                 (when (equal (cadr elem) "EXISTS")
+                   (setq exists (string-to-number (car elem)))))
+               (when uidnext
+                 (setq highest (1- (string-to-number (car uidnext)))))
+               (if (zerop exists)
+                   ;; Empty group.
+                   (insert (format "%S %d %d y\n"
+                                   (utf7-decode group t) highest (1+ highest)))
+                 ;; Return the widest possible range.
+                 (insert (format "%S %d 1 y\n" (utf7-decode group t)
+                                 (or highest exists))))))))
        t))))
 
-(deffoo nnimap-request-rename-group (group new-name &optional server)
-  (when (nnimap-possibly-change-server server)
-    (imap-mailbox-rename (nnimap-decode-group-name group)
-                        (nnimap-decode-group-name new-name)
-                        nnimap-server-buffer)))
-
-(defun nnimap-expunge (mailbox server)
-  (when (nnimap-possibly-change-group mailbox server)
-    (imap-mailbox-expunge nil nnimap-server-buffer)))
-
-(defun nnimap-acl-get (mailbox server)
-  (when (nnimap-possibly-change-server server)
-    (and (imap-capability 'ACL nnimap-server-buffer)
-        (imap-mailbox-acl-get (nnimap-decode-group-name mailbox)
-                              nnimap-server-buffer))))
-
-(defun nnimap-acl-edit (mailbox method old-acls new-acls)
-  (when (nnimap-possibly-change-server (cadr method))
-    (unless (imap-capability 'ACL nnimap-server-buffer)
-      (error "Your server does not support ACL editing"))
-    (with-current-buffer nnimap-server-buffer
-      ;; delete all removed identifiers
-      (mapc (lambda (old-acl)
-             (unless (assoc (car old-acl) new-acls)
-               (or (imap-mailbox-acl-delete (car old-acl)
-                                            (nnimap-decode-group-name mailbox))
-                   (error "Can't delete ACL for %s" (car old-acl)))))
-           old-acls)
-      ;; set all changed acl's
-      (mapc (lambda (new-acl)
-             (let ((new-rights (cdr new-acl))
-                   (old-rights (cdr (assoc (car new-acl) old-acls))))
-               (unless (and old-rights new-rights
-                            (string= old-rights new-rights))
-                 (or (imap-mailbox-acl-set (car new-acl) new-rights
-                                           (nnimap-decode-group-name mailbox))
-                     (error "Can't set ACL for %s to %s" (car new-acl)
-                            new-rights)))))
-           new-acls)
-      t)))
+(defun nnimap-retrieve-group-data-early (server infos)
+  (when (nnimap-possibly-change-group nil server)
+    (with-current-buffer (nnimap-buffer)
+      ;; QRESYNC handling isn't implemented.
+      (let ((qresyncp (member "notQRESYNC" (nnimap-capabilities nnimap-object)))
+           marks groups sequences)
+       ;; Go through the infos and gather the data needed to know
+       ;; what and how to request the data.
+       (dolist (info infos)
+         (setq marks (gnus-info-marks info))
+         (push (list (gnus-group-real-name (gnus-info-group info))
+                     (cdr (assq 'active marks))
+                     (cdr (assq 'uid marks)))
+               groups))
+       ;; Then request the data.
+       (erase-buffer)
+       (dolist (elem groups)
+         (if (and qresyncp
+                  (nth 2 elem))
+             (push
+              (list 'qresync
+                    (nnimap-send-command "EXAMINE %S (QRESYNC (%s %s))"
+                                         (car elem)
+                                         (car (nth 2 elem))
+                                         (cdr (nth 2 elem)))
+                    nil
+                    (car elem))
+              sequences)
+           (let ((start
+                  (if (nth 1 elem)
+                      ;; Fetch the last 100 flags.
+                      (max 1 (- (cdr (nth 1 elem)) 100))
+                    1)))
+             (push (list (nnimap-send-command "EXAMINE %S" (car elem))
+                         (nnimap-send-command "UID FETCH %d:* FLAGS" start)
+                         start
+                         (car elem))
+                   sequences))))
+       sequences))))
+
+(defun nnimap-finish-retrieve-group-infos (server infos sequences)
+  (when (nnimap-possibly-change-group nil server)
+    (with-current-buffer (nnimap-buffer)
+      ;; Wait for the final data to trickle in.
+      (nnimap-wait-for-response (cadar sequences))
+      ;; Now we should have all 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.
+      (nnimap-update-infos (nnimap-flags-to-marks
+                           (nnimap-parse-flags
+                            (nreverse sequences)))
+                          infos))))
+
+(defun nnimap-update-infos (flags infos)
+  (dolist (info infos)
+    (let ((group (gnus-group-real-name (gnus-info-group info))))
+      (nnimap-update-info info (cdr (assoc group flags))))))
+
+(defun nnimap-update-info (info marks)
+  (when marks
+    (destructuring-bind (existing flags high low uidnext start-article) marks
+      (let ((group (gnus-info-group info))
+           (completep (and start-article
+                           (= start-article 1))))
+       ;; First set the active ranges based on high/low.
+       (if completep
+           (gnus-set-active group
+                            (if high
+                                (cons low high)
+                              ;; No articles in this group.
+                              (cons (1- uidnext) uidnext)))
+         (setcdr (gnus-active group) high))
+       ;; Then 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
+                  (gnus-sorted-range-intersection
+                   (cons 1 start-article)
+                   (gnus-info-read info))
+                  read)))
+         (gnus-info-set-read info read)
+         ;; Update the marks.
+         (setq marks (gnus-info-marks info))
+         ;; Note the active level for the next run-through.
+         (let ((active (assq 'active marks)))
+           (if active
+               (setcdr active (gnus-active group))
+             (push (cons 'active (gnus-active group)) marks)))
+         (dolist (type (cdr nnimap-mark-alist))
+           (let ((old-marks (assoc (car type) marks))
+                 (new-marks (gnus-compress-sequence
+                             (cdr (assoc (cadr type) flags)))))
+             (setq marks (delq old-marks marks))
+             (pop old-marks)
+             (when (and old-marks
+                        (> start-article 1))
+               (setq old-marks (gnus-range-difference
+                                (cons start-article high)
+                                old-marks))
+               (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)))))))
+
+(defun nnimap-flags-to-marks (groups)
+  (let (data group totalp uidnext articles start-article mark)
+    (dolist (elem groups)
+      (setq group (car elem)
+           uidnext (cadr elem)
+           start-article (caddr elem)
+           articles (cdddr elem))
+      (let ((high (caar articles))
+           marks low existing)
+       (dolist (article articles)
+         (setq low (car article))
+         (push (car article) existing)
+         (dolist (flag (cdr article))
+           (setq mark (assoc flag marks))
+           (if (not mark)
+               (push (list flag (car article)) marks)
+             (setcdr mark (cons (car article) (cdr mark)))))
+         (push (list group existing marks high low uidnext start-article)
+               data))))
+    data))
+
+(defun nnimap-parse-flags (sequences)
+  (goto-char (point-min))
+  (let (start end articles groups uidnext elems)
+    (dolist (elem sequences)
+      (destructuring-bind (group-sequence flag-sequence totalp group) elem
+       ;; The EXAMINE was successful.
+       (when (and (search-forward (format "\n%d OK " group-sequence) nil t)
+                  (progn
+                    (forward-line 1)
+                    (setq start (point))
+                    (if (re-search-backward "UIDNEXT \\([0-9]+\\)"
+                                              (or end (point-min)) t)
+                        (setq uidnext (string-to-number (match-string 1)))
+                      (setq uidnext nil))
+                    (goto-char start))
+                  ;; The UID FETCH FLAGS was successful.
+                  (search-forward (format "\n%d OK " flag-sequence) nil t))
+         (setq end (point))
+         (goto-char start)
+         (while (re-search-forward "^\\* [0-9]+ FETCH (\\(.*\\))" end t)
+           (setq elems (nnimap-parse-line (match-string 1)))
+           (push (cons (string-to-number (cadr (member "UID" elems)))
+                       (cadr (member "FLAGS" elems)))
+                 articles))
+         (push (nconc (list group uidnext totalp) articles) groups)
+         (setq articles nil))))
+    groups))
+
+(defun nnimap-find-process-buffer (buffer)
+  (cadr (assoc buffer nnimap-connection-alist)))
+
+(defun nnimap-request-post (&optional server)
+  (setq nnimap-status-string "Read-only server")
+  nil)
 
-\f
-;;; Internal functions
-
-;;
-;; This is confusing.
-;;
-;; mark      => read, tick, draft, reply etc
-;; flag      => "\\Seen", "\\Flagged", "\\Draft", "gnus-expire" etc
-;; predicate => "SEEN", "FLAGGED", "DRAFT", "KEYWORD gnus-expire" etc
-;;
-;; Mark should not really contain 'read since it's not a "mark" in the Gnus
-;; world, but we cheat.  Mark == gnus-article-mark-lists + '(read . read).
-;;
-
-(defconst nnimap-mark-to-predicate-alist
-  (mapcar
-   (lambda (pair)                      ; cdr is the mark
-     (or (assoc (cdr pair)
-               '((read . "SEEN")
-                 (tick . "FLAGGED")
-                 (draft . "DRAFT")
-                 (recent . "RECENT")
-                 (reply . "ANSWERED")))
-        (cons (cdr pair)
-              (format "KEYWORD gnus-%s" (symbol-name (cdr pair))))))
-   (cons '(read . read) gnus-article-mark-lists)))
-
-(defun nnimap-mark-to-predicate (pred)
-  "Convert a Gnus mark (a symbol such as read, tick, expire) to a IMAP predicate.
-This is a string such as \"SEEN\", \"FLAGGED\", \"KEYWORD gnus-expire\",
-to be used within a IMAP SEARCH query."
-  (cdr (assq pred nnimap-mark-to-predicate-alist)))
-
-(defconst nnimap-mark-to-flag-alist
-  (mapcar
-   (lambda (pair)
-     (or (assoc (cdr pair)
-               '((read . "\\Seen")
-                 (tick . "\\Flagged")
-                 (draft . "\\Draft")
-                 (recent . "\\Recent")
-                 (reply . "\\Answered")))
-        (cons (cdr pair)
-              (format "gnus-%s" (symbol-name (cdr pair))))))
-   (cons '(read . read) gnus-article-mark-lists)))
-
-(defun nnimap-mark-to-flag-1 (preds)
-  (if (and (not (null preds)) (listp preds))
-      (cons (nnimap-mark-to-flag (car preds))
-           (nnimap-mark-to-flag (cdr preds)))
-    (cdr (assoc preds nnimap-mark-to-flag-alist))))
-
-(defun nnimap-mark-to-flag (preds &optional always-list make-string)
-  "Convert a Gnus mark (a symbol such as read, tick, expire) to a IMAP flag.
-This is a string such as \"\\Seen\", \"\\Flagged\", \"gnus-expire\", to
-be used in a STORE FLAGS command."
-  (let ((result (nnimap-mark-to-flag-1 preds)))
-    (setq result (if (and (or make-string always-list)
-                         (not (listp result)))
-                    (list result)
-                  result))
-    (if make-string
-       (mapconcat (lambda (flag)
-                    (if (listp flag)
-                        (mapconcat 'identity flag " ")
-                      flag))
-                  result " ")
-      result)))
-
-(defun nnimap-mark-permanent-p (mark &optional group)
-  "Return t if MARK can be permanently (between IMAP sessions) saved on articles, in GROUP."
-  (imap-message-flag-permanent-p (nnimap-mark-to-flag mark)))
+(defun nnimap-possibly-change-group (group server)
+  (when (and server
+            (not (nnimap-server-opened server)))
+    (nnimap-open-server server))
+  (if (not group)
+      t
+    (with-current-buffer (nnimap-buffer)
+      (if (equal group (nnimap-group nnimap-object))
+         t
+       (let ((result (nnimap-command "SELECT %S" (utf7-encode group t))))
+         (when (car result)
+           (setf (nnimap-group nnimap-object) group)
+           result))))))
+
+(defun nnimap-find-connection (buffer)
+  "Find the connection delivering to BUFFER."
+  (let ((entry (assoc buffer nnimap-connection-alist)))
+    (when entry
+      (if (and (buffer-name (cadr entry))
+              (get-buffer-process (cadr entry))
+              (memq (process-status (get-buffer-process (cadr entry)))
+                    '(open run)))
+         (get-buffer-process (cadr entry))
+       (setq nnimap-connection-alist (delq entry nnimap-connection-alist))
+       nil))))
+
+(defvar nnimap-sequence 0)
+
+(defun nnimap-send-command (&rest args)
+  (process-send-string
+   (get-buffer-process (current-buffer))
+   (nnimap-log-command
+    (format "%d %s\r\n"
+           (incf nnimap-sequence)
+           (apply #'format args))))
+  nnimap-sequence)
+
+(defun nnimap-log-command (command)
+  (with-current-buffer (get-buffer-create "*imap log*")
+    (goto-char (point-max))
+    (insert (format-time-string "%H:%M:%S") " " command))
+  command)
+
+(defun nnimap-command (&rest args)
+  (erase-buffer)
+  (let* ((sequence (apply #'nnimap-send-command args))
+        (response (nnimap-get-response sequence)))
+    (if (equal (caar response) "OK")
+       (cons t response)
+      (nnheader-report 'nnimap "%s"
+                      (mapconcat #'identity (car response) " "))
+      nil)))
+
+(defun nnimap-get-response (sequence)
+  (nnimap-wait-for-response sequence)
+  (nnimap-parse-response))
+
+(defun nnimap-wait-for-response (sequence &optional messagep)
+  (goto-char (point-max))
+  (while (or (bobp)
+            (progn
+              (forward-line -1)
+              (not (looking-at (format "^%d .*\n" sequence)))))
+    (when messagep
+      (message "Read %dKB" (/ (buffer-size) 1000)))
+    (nnheader-accept-process-output (get-buffer-process (current-buffer)))
+    (goto-char (point-max))))
+
+(defun nnimap-parse-response ()
+  (let ((lines (split-string (nnimap-last-response-string) "\r\n" t))
+       result)
+    (dolist (line lines)
+      (push (cdr (nnimap-parse-line line)) result))
+    ;; Return the OK/error code first, and then all the "continuation
+    ;; lines" afterwards.
+    (cons (pop result)
+         (nreverse result))))
+
+;; Parse an IMAP response line lightly.  They look like
+;; "* OK [UIDVALIDITY 1164213559] UIDs valid", typically, so parse
+;; the lines into a list of strings and lists of string.
+(defun nnimap-parse-line (line)
+  (let (char result)
+    (with-temp-buffer
+      (insert line)
+      (goto-char (point-min))
+      (while (not (eobp))
+       (if (eql (setq char (following-char)) ? )
+           (forward-char 1)
+         (push
+          (cond
+           ((eql char ?\[)
+            (split-string (buffer-substring
+                           (1+ (point)) (1- (search-forward "]")))))
+           ((eql char ?\()
+            (split-string (buffer-substring
+                           (1+ (point)) (1- (search-forward ")")))))
+           ((eql char ?\")
+            (forward-char 1)
+            (buffer-substring (point) (1- (search-forward "\""))))
+           (t
+            (buffer-substring (point) (if (search-forward " " nil t)
+                                          (1- (point))
+                                        (goto-char (point-max))))))
+          result)))
+      (nreverse result))))
+
+(defun nnimap-last-response-string ()
+  (save-excursion
+    (forward-line 1)
+    (let ((end (point)))
+      (forward-line -1)
+      (when (not (bobp))
+       (forward-line -1)
+       (while (and (not (bobp))
+                   (eql (following-char) ?*))
+         (forward-line -1))
+       (unless (eql (following-char) ?*)
+         (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)
+  (erase-buffer)
+  (nnimap-wait-for-response
+   (nnimap-send-command
+    "UID FETCH %s %s"
+    (nnimap-article-ranges articles)
+    (format "(UID %s%s)"
+           (format
+            (if (member "IMAP4rev1"
+                        (nnimap-capabilities nnimap-object))
+                "BODY.PEEK[HEADER] BODY.PEEK"
+              "RFC822.PEEK"))
+           (if nnimap-split-download-body-default
+               ""
+             "[1]")))
+   t))
+
+(defun nnimap-split-incoming-mail ()
+  (with-current-buffer (nnimap-buffer)
+    (let ((nnimap-incoming-split-list nil)
+         (nnmail-split-methods nnimap-split-methods)
+         (nnmail-inhibit-default-split-group t)
+         (groups (nnimap-get-groups))
+         new-articles)
+      (erase-buffer)
+      (nnimap-command "SELECT %S" nnimap-inbox)
+      (setq new-articles (nnimap-new-articles (nnimap-get-flags "1:*")))
+      (when new-articles
+       (nnimap-fetch-inbox new-articles)
+       (nnimap-transform-split-mail)
+       (nnheader-ms-strip-cr)
+       (nnmail-cache-open)
+       (nnmail-split-incoming (current-buffer)
+                              #'nnimap-save-mail-spec
+                              nil nil
+                              #'nnimap-dummy-active-number)
+       (when nnimap-incoming-split-list
+         (let ((specs (nnimap-make-split-specs nnimap-incoming-split-list))
+               sequences)
+           ;; Create any groups that doesn't already exist on the
+           ;; server first.
+           (dolist (spec specs)
+             (unless (member (car spec) groups)
+               (nnimap-command "CREATE %S" (utf7-encode (car spec) t))))
+           ;; Then copy over all the messages.
+           (erase-buffer)
+           (dolist (spec specs)
+             (let ((group (car spec))
+                   (ranges (cdr spec)))
+               (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))
+             ;; And then mark the successful copy actions as deleted,
+             ;; and possibly expunge them.
+             (nnimap-mark-and-expunge-incoming
+              (nnimap-parse-copied-articles sequences)))))))))
+
+(defun nnimap-mark-and-expunge-incoming (range)
+  (when range
+    (setq range (nnimap-article-ranges range))
+    (nnimap-send-command
+     "UID STORE %s +FLAGS.SILENT (\\Deleted)" range)
+    (cond
+     ;; If the server supports it, we now delete the message we have
+     ;; just copied over.
+     ((member "UIDPLUS" (nnimap-capabilities nnimap-object))
+      (nnimap-send-command "UID EXPUNGE %s" range))
+     ;; If it doesn't support UID EXPUNGE, then we only expunge if the
+     ;; user has configured it.
+     (nnimap-expunge-inbox
+      (nnimap-send-command "EXPUNGE")))))
+
+(defun nnimap-parse-copied-articles (sequences)
+  (let (sequence copied range)
+    (goto-char (point-min))
+    (while (re-search-forward "^\\([0-9]+\\) OK " nil t)
+      (setq sequence (string-to-number (match-string 1)))
+      (when (setq range (cadr (assq sequence sequences)))
+       (push (gnus-uncompress-range range) copied)))
+    (gnus-compress-sequence (sort (apply #'nconc copied) #'<))))
+
+(defun nnimap-new-articles (flags)
+  (let (new)
+    (dolist (elem flags)
+      (when (or (null (cdr elem))
+               (and (not (member "\\Deleted" (cdr elem)))
+                    (not (member "\\Seen" (cdr elem)))))
+       (push (car elem) new)))
+    (gnus-compress-sequence (nreverse new))))
+
+(defun nnimap-make-split-specs (list)
+  (let ((specs nil)
+       entry)
+    (dolist (elem list)
+      (destructuring-bind (article spec) elem
+       (dolist (group (delete nil (mapcar #'car spec)))
+         (unless (setq entry (assoc group specs))
+           (push (setq entry (list group)) specs))
+         (setcdr entry (cons article (cdr entry))))))
+    (dolist (entry specs)
+      (setcdr entry (gnus-compress-sequence (sort (cdr entry) #'<))))
+    specs))
+
+(defun nnimap-transform-split-mail ()
+  (goto-char (point-min))
+  (let (article bytes)
+    (block nil
+      (while (not (eobp))
+       (while (not (looking-at "^\\* [0-9]+ FETCH.*UID \\([0-9]+\\)"))
+         (delete-region (point) (progn (forward-line 1) (point)))
+         (when (eobp)
+           (return)))
+       (setq article (match-string 1)
+             bytes (nnimap-get-length))
+       (delete-region (line-beginning-position) (line-end-position))
+       ;; Insert MMDF separator, and a way to remember what this
+       ;; article UID is.
+       (insert (format "\^A\^A\^A\^A\n\nX-nnimap-article: %s" article))
+       (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))))))
+
+(defun nnimap-dummy-active-number (group &optional server)
+  1)
+
+(defun nnimap-save-mail-spec (group-art &optional server full-nov)
+  (let (article)
+    (goto-char (point-min))
+    (if (not (re-search-forward "X-nnimap-article: \\([0-9]+\\)" nil t))
+       (error "Invalid nnimap mail")
+      (setq article (string-to-number (match-string 1))))
+    (push (list article group-art)
+         nnimap-incoming-split-list)))
 
 (provide 'nnimap)
 
index b7d834e..d5fdd35 100644 (file)
@@ -614,6 +614,7 @@ using different case (i.e. mailing-list@domain vs Mailing-List@Domain)."
 
 (defvar nnmail-split-tracing nil)
 (defvar nnmail-split-trace nil)
+(defvar nnmail-inhibit-default-split-group nil)
 
 \f
 
@@ -1058,7 +1059,9 @@ If SOURCE is a directory spec, try to return the group name component."
 (defun nnmail-split-incoming (incoming func &optional exit-func
                                       group artnum-func)
   "Go through the entire INCOMING file and pick out each individual mail.
-FUNC will be called with the buffer narrowed to each mail."
+FUNC will be called with the buffer narrowed to each mail.
+INCOMING can also be a buffer object.  In that case, the mail
+will be copied over from that buffer."
   (let ( ;; If this is a group-specific split, we bind the split
        ;; methods to just this group.
        (nnmail-split-methods (if (and group
@@ -1070,8 +1073,10 @@ FUNC will be called with the buffer narrowed to each mail."
       ;; Insert the incoming file.
       (set-buffer (get-buffer-create nnmail-article-buffer))
       (erase-buffer)
-      (let ((coding-system-for-read nnmail-incoming-coding-system))
-       (mm-insert-file-contents incoming))
+      (if (bufferp incoming)
+         (insert-buffer-substring incoming)
+       (let ((coding-system-for-read nnmail-incoming-coding-system))
+         (mm-insert-file-contents incoming)))
       (prog1
          (if (zerop (buffer-size))
              0
@@ -1100,7 +1105,8 @@ FUNC will be called with the group name to determine the article number."
        (obuf (current-buffer))
        group-art method grp)
     (if (and (sequencep methods)
-            (= (length methods) 1))
+            (= (length methods) 1)
+            (not nnmail-inhibit-default-split-group))
        ;; If there is only just one group to put everything in, we
        ;; just return a list with just this one method in.
        (setq group-art
@@ -1149,7 +1155,8 @@ FUNC will be called with the group name to determine the article number."
                       ;; just call this function here and use the
                       ;; result.
                       (or (funcall nnmail-split-methods)
-                          '("bogus"))
+                          (and (not nnmail-inhibit-default-split-group)
+                               ("bogus")))
                     (error
                      (nnheader-message
                       5 "Error in `nnmail-split-methods'; using `bogus' mail group: %S" error-info)
@@ -1194,12 +1201,14 @@ FUNC will be called with the group name to determine the article number."
                        group-art))
              ;; This is the final group, which is used as a
              ;; catch-all.
-             (unless group-art
+             (when (and (not group-art)
+                        (not nnmail-inhibit-default-split-group))
                (setq group-art
                      (list (cons (car method)
                                  (funcall func (car method))))))))
          ;; Fall back on "bogus" if all else fails.
-         (unless group-art
+         (when (and (not group-art)
+                    (not nnmail-inhibit-default-split-group))
            (setq group-art (list (cons "bogus" (funcall func "bogus"))))))
        ;; Produce a trace if non-empty.
        (when (and trace nnmail-split-trace)
index cca647d..74bd092 100644 (file)
@@ -205,6 +205,7 @@ Characters are in raw byte pairs in narrowed buffer."
   (mm-decode-coding-region (point-min) (point-max) 'iso-8859-1)
   (mm-enable-multibyte))
 
+;;;###autoload
 (defun utf7-encode (string &optional for-imap)
   "Encode UTF-7 STRING.  Use IMAP modification if FOR-IMAP is non-nil."
   (if (and (coding-system-p 'utf-7) (coding-system-p 'utf-7-imap))