2009-10-14 Reiner Steib <Reiner.Steib@gmx.de>
[gnus] / lisp / nnimap.el
index 381a6cf..cfcad52 100644 (file)
@@ -1,26 +1,26 @@
 ;;; nnimap.el --- imap backend for Gnus
-;; Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
 
-;; Author: Simon Josefsson <jas@pdc.kth.se>
+;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
+;;   2007, 2008, 2009  Free Software Foundation, Inc.
+
+;; Author: Simon Josefsson <simon@josefsson.org>
 ;;         Jim Radford <radford@robby.caltech.edu>
 ;; Keywords: mail
 
 ;; This file is part of GNU Emacs.
 
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs is free software: you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
-;; any later version.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Commentary:
 
@@ -42,7 +42,7 @@
 ;;   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?) (unnecessery?)
+;;   o Respooling (fix Gnus?) (unnecessary?)
 ;;   o Add support for the following: (if applicable)
 ;;       request-list-newsgroups, request-regenerate
 ;;       list-active-group,
 (require 'gnus-start)
 (require 'gnus-int)
 
+(eval-when-compile (require 'cl))
+
+(autoload 'auth-source-user-or-password "auth-source")
+
 (nnoo-declare nnimap)
 
 (defconst nnimap-version "nnimap 1.0")
@@ -82,7 +86,7 @@
 
 (defvoo nnimap-server-port nil
   "Port number on physical IMAP server.
-If nil, defaults to 993 for SSL connections and 143 otherwise.")
+If nil, defaults to 993 for TLS/SSL connections and 143 otherwise.")
 
 ;; Splitting variables
 
@@ -115,7 +119,7 @@ loaded function will not match.  Use with care."
   (functionp value))
 
 (defcustom nnimap-split-rule nil
-  "Mail will be split according to theese rules.
+  "Mail will be split according to these rules.
 
 Mail is read from mailbox(es) specified in `nnimap-split-inbox'.
 
@@ -159,6 +163,8 @@ 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"
@@ -191,17 +197,36 @@ RFC2060 section 6.4.4."
   :type 'string)
 
 (defcustom nnimap-split-fancy nil
-  "Like the variable `nnmail-split-fancy', which see."
+  "Like the variable `nnmail-split-fancy'."
   :group 'nnimap
   :type 'sexp)
 
+(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 cought by nnimap when closing the mailbox will
+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)
 
@@ -210,6 +235,7 @@ However, it increases speed."
 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)
 
@@ -222,13 +248,19 @@ 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 nil
+(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
@@ -242,14 +274,16 @@ handle.
 
 Change this if
 
-1) you want to connect with SSL.  The SSL integration with IMAP is
-   brain-dead so you'll have to tell it specifically.
+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: kerberos4, ssl, network")
+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.
@@ -263,7 +297,8 @@ 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: kerberos4, cram-md5, login, anonymous.")
+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.
@@ -289,6 +324,11 @@ 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
@@ -342,7 +382,10 @@ 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.")
+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.
@@ -368,23 +411,75 @@ just like \"ticked\" articles, in other IMAP clients.")
                                          (string :format "Login: %v"))
                                    (cons :format "%v"
                                          (const :format "" "password")
-                                         (string :format "Password: %v")))))))
+                                         (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)
+  :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-debug nil
-  "Name of buffer to record debugging info.
-For example: (setq nnimap-debug \"*nnimap-debug*\")")
 (defvar nnimap-current-move-server nil)
 (defvar nnimap-current-move-group nil)
 (defvar nnimap-current-move-article nil)
@@ -392,10 +487,6 @@ For example: (setq nnimap-debug \"*nnimap-debug*\")")
 (defvar nnimap-progress-chars '(?| ?/ ?- ?\\))
 (defvar nnimap-progress-how-often 20)
 (defvar nnimap-counter)
-(defvar nnimap-callback-callback-function nil
-  "Gnus callback the nnimap asynchronous callback should call.")
-(defvar nnimap-callback-buffer nil
-  "Which buffer the asynchronous article prefetch callback should work in.")
 (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
@@ -410,6 +501,14 @@ For example: (setq nnimap-debug \"*nnimap-debug*\")")
   "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."
@@ -457,14 +556,14 @@ If SERVER is nil, uses the current server."
        (imap-mailbox-unselect nnimap-server-buffer))))
 
 (defun nnimap-find-minmax-uid (group &optional examine)
-  "Find lowest and highest active article nummber in GROUP.
+  "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
     (when (or (string= group (imap-current-mailbox))
              (imap-mailbox-select group examine))
       (let (minuid maxuid)
        (when (> (imap-mailbox-get 'exists) 0)
-         (imap-fetch "1,*" "UID" nil 'nouidfetch)
+         (imap-fetch-safe '("1,*" . "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)))
@@ -516,22 +615,21 @@ If EXAMINE is non-nil the group is selected read-only."
       (with-current-buffer nnimap-server-buffer
        (setq uid imap-current-message
              mbx imap-current-mailbox
-             headers (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 uid 'BODYDETAIL)))
-                        (imap-message-get uid 'RFC822.HEADER)))
+             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
-       (with-temp-buffer
+       ;; 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)
-        (insert headers)
-        (nnheader-ms-strip-cr)
-        (nnheader-fold-continuation-lines)
-        (subst-char-in-region (point-min) (point-max) ?\t ? )
-        (let ((head (nnheader-parse-head 'naked)))
+        ;; 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)
@@ -560,7 +658,7 @@ If EXAMINE is non-nil the group is selected read-only."
              articles))))
 
 (defun nnimap-group-overview-filename (group server)
-  "Make pathname for GROUP on SERVER."
+  "Make file name for GROUP on SERVER."
   (let* ((dir (file-name-as-directory (expand-file-name nnimap-directory)))
         (uidvalidity (gnus-group-get-parameter
                       (gnus-group-prefixed-name
@@ -636,6 +734,8 @@ If EXAMINE is non-nil the group is selected read-only."
                            (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")))))
@@ -690,6 +790,8 @@ If EXAMINE is non-nil the group is selected read-only."
       'nov)))
 
 (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)
@@ -697,19 +799,45 @@ If EXAMINE is non-nil the group is selected read-only."
                (imap-capability 'IMAP4rev1 nnimap-server-buffer))
       (imap-close nnimap-server-buffer)
       (nnheader-report 'nnimap "Server %s is not IMAP4 compliant" server))
-    (let* ((list (gnus-parse-netrc nnimap-authinfo-file))
-          (port (if nnimap-server-port
-                    (int-to-string nnimap-server-port)
-                  "imap"))
-          (alist (gnus-netrc-machine list (or nnimap-server-address
-                                              nnimap-address server)
-                                     port "imap"))
-          (user (gnus-netrc-get alist "login"))
-          (passwd (gnus-netrc-get alist "password")))
+    (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)
-         (prog1
+         (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)
@@ -733,9 +861,15 @@ If EXAMINE is non-nil the group is selected read-only."
        (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))
-    (or (and nnimap-server-buffer
-            (imap-opened nnimap-server-buffer))
-       (nnimap-open-connection server))))
+    (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.
@@ -750,7 +884,8 @@ SERVER is nil, it is treated as the current 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)))
+  (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))
@@ -758,7 +893,9 @@ Return nil if the server couldn't be closed for some reason."
       (setq nnimap-server-buffer nil
            nnimap-current-server nil
            nnimap-server-buffer-alist
-           (delq server nnimap-server-buffer-alist)))
+           (nnimap-remove-server-from-buffer-alist
+            server
+            nnimap-server-buffer-alist)))
     (nnoo-close-server 'nnimap server)))
 
 (deffoo nnimap-request-close ()
@@