(mm-inline-image-emacs): Use put-image, remove-images.
[gnus] / lisp / nnimap.el
index 4d073b7..af1de33 100644 (file)
@@ -1,5 +1,5 @@
 ;;; nnimap.el --- imap backend for Gnus
-;; Copyright (C) 1998,1999 Free Software Foundation, Inc.
+;; Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
 
 ;; Author: Simon Josefsson <jas@pdc.kth.se>
 ;;         Jim Radford <radford@robby.caltech.edu>
@@ -36,9 +36,8 @@
 ;;
 ;; Todo, minor things:
 ;;
+;;   o Don't require half of Gnus -- backends should be standalone
 ;;   o Support escape characters in `message-tokenize-header'
-;;   o Split-fancy.
-;;   o Support NOV nnmail-extra-headers.
 ;;   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
@@ -54,7 +53,7 @@
 ;;   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?
+;;   o What about Gnus's article editing, can we support it?  NO!
 ;;   o Use \Draft to support the draft group??
 
 ;;; Code:
@@ -67,7 +66,6 @@
 (require 'nnheader)
 (require 'mm-util)
 (require 'gnus)
-(require 'gnus-async)
 (require 'gnus-range)
 (require 'gnus-start)
 (require 'gnus-int)
@@ -122,7 +120,21 @@ the matching article will be stored.
 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.")
+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.")
+
+(defvar 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.")
+
+(defvar nnimap-split-fancy nil
+  "Like `nnmail-split-fancy', which see.")
 
 ;; Authorization / Privacy variables
 
@@ -268,28 +280,16 @@ restrict visible folders.")
   "Gnus callback the nnimap asynchronous callback should call.")
 (defvar nnimap-callback-buffer nil
   "Which buffer the asynchronous article prefetch callback should work in.")
-
-;; Various server variables.
+(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
-;; Internal variables.
-(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
 
 (nnoo-define-basics nnimap)
 
 ;; Utility functions:
 
-(defun nnimap-replace-in-string (string regexp to)
-  "Replace substrings in STRING matching REGEXP with TO."
-  (if (string-match regexp string)
-      (concat (substring string 0 (match-beginning 0))
-             to
-             (nnimap-replace-in-string (substring string (match-end 0))
-                                       regexp to))
-    string))
-
 (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)))
@@ -309,7 +309,7 @@ If SERVER is nil, uses the current server."
         (old-uidvalidity (gnus-group-get-parameter gnusgroup 'uidvalidity)))
     (if old-uidvalidity
        (if (not (equal old-uidvalidity new-uidvalidity))
-           nil;; uidvalidity clash
+           nil ;; uidvalidity clash
          (gnus-group-set-parameter gnusgroup 'uidvalidity new-uidvalidity)
          t)
       (gnus-group-add-parameter gnusgroup (cons 'uidvalidity new-uidvalidity))
@@ -368,37 +368,31 @@ If EXAMINE is non-nil the group is selected read-only."
                                 nnimap-progress-how-often)
                              nnimap-progress-chars)))
   (with-current-buffer nntp-server-buffer
-    (nnheader-insert-nov
-     (with-current-buffer nnimap-server-buffer
-       (vector imap-current-message
-              (nnimap-replace-whitespace
-               (imap-message-envelope-subject imap-current-message))
-              (nnimap-replace-whitespace
-               (imap-envelope-from
-                (car-safe (imap-message-envelope-from
-                           imap-current-message))))
-              (nnimap-replace-whitespace
-               (imap-message-envelope-date imap-current-message))
-              (nnimap-replace-whitespace
-               (imap-message-envelope-message-id imap-current-message))
-              (nnimap-replace-whitespace
-               (let ((str (if (imap-capability 'IMAP4rev1)
-                              (nth 2 (assoc
-                                      "HEADER.FIELDS REFERENCES"
-                                      (imap-message-get
-                                       imap-current-message 'BODYDETAIL)))
-                            (imap-message-get imap-current-message
-                                              'RFC822.HEADER))))
-                 (if (> (length str) (length "References: "))
-                     (substring str (length "References: "))
-                   (if (and (setq str (imap-message-envelope-in-reply-to
-                                       imap-current-message))
-                            (string-match "<[^>]+>" str))
-                       (substring str (match-beginning 0) (match-end 0))))))
-              (imap-message-get imap-current-message 'RFC822.SIZE)
-              (imap-body-lines (imap-message-body imap-current-message))
-              nil;; xref
-              nil)))));; extra-headers
+    (let (headers lines chars uid mbx)
+      (with-current-buffer nnimap-server-buffer
+       (setq uid imap-current-message
+             mbx 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
+       (with-temp-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)))
+          (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."
@@ -460,10 +454,15 @@ If EXAMINE is non-nil the group is selected read-only."
          (nnimap-length (gnus-range-length articles))
          (nnimap-counter 0))
       (imap-fetch (nnimap-range-to-string articles)
-                 (concat "(UID RFC822.SIZE ENVELOPE BODY "
-                         (if (imap-capability 'IMAP4rev1)
-                             "BODY.PEEK[HEADER.FIELDS (References)])"
-                           "RFC822.HEADER.LINES (References))")))
+                 (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)))))
       (and (numberp nnmail-large-newsgroup)
           (> nnimap-length nnmail-large-newsgroup)
           (nnheader-message 6 "nnimap: Retrieving headers...done")))))
@@ -505,8 +504,8 @@ If EXAMINE is non-nil the group is selected read-only."
                    ;; 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)))))
+                      (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))
@@ -615,8 +614,8 @@ function is generally only called when Gnus is shutting down."
     (nnheader-ms-strip-cr)
     (funcall nnimap-callback-callback-function t)))
 
-(defun nnimap-request-article-part (article part prop
-                                           &optional group server to-buffer)
+(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
@@ -628,9 +627,12 @@ function is generally only called when Gnus is shutting down."
        (if (not nnheader-callback-function)
            (with-current-buffer (or to-buffer nntp-server-buffer)
              (erase-buffer)
-             (insert (nnimap-demule (imap-fetch article part prop nil
-                                                nnimap-server-buffer)))
-             (nnheader-ms-strip-cr)
+              (let ((data (imap-fetch article part prop nil
+                                      nnimap-server-buffer)))
+                (insert (nnimap-demule (if detail
+                                           (nth 2 (car data))
+                                         data))))
+              (nnheader-ms-strip-cr)
              (gnus-message 9 "nnimap: Fetching (part of) article %d...done"
                            article)
              (if (bobp)
@@ -647,16 +649,25 @@ function is generally only called when Gnus is shutting down."
   t)
 
 (deffoo nnimap-request-article (article &optional group server to-buffer)
-  (nnimap-request-article-part
-   article "RFC822.PEEK" 'RFC822 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)
-  (nnimap-request-article-part
-   article "RFC822.HEADER" 'RFC822.HEADER 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)
-  (nnimap-request-article-part
-   article "RFC822.TEXT.PEEK" 'RFC822.TEXT 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
@@ -715,12 +726,10 @@ function is generally only called when Gnus is shutting down."
          (or (member "\\NoSelect" (imap-mailbox-get 'list-flags mbx))
              (let ((info (nnimap-find-minmax-uid mbx 'examine)))
                (when info
-                 ;; Escape SPC in mailboxes xxx relies on gnus internals
                  (with-current-buffer nntp-server-buffer
-                   (insert (format "%s %d %d y\n"
-                                   (nnimap-replace-in-string mbx " " "\\ ")
-                                   (or (nth 2 info) 0)
-                                   (max 1 (or (nth 1 info) 1)))))))))))
+                   (insert (format "\"%s\" %d %d y\n"
+                                   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))
@@ -755,13 +764,11 @@ function is generally only called when Gnus is shutting down."
        (or (member "\\NoSelect"
                    (imap-mailbox-get 'list-flags group nnimap-server-buffer))
            (let ((info (nnimap-find-minmax-uid group 'examine)))
-             ;; Escape SPC in mailboxes xxx relies on gnus internals
-             (insert (format "211 %d %d %d %s\n" (or (nth 0 info) 0)
-                             (max 1 (or (nth 1 info) 1))
+             (insert (format "\"%s\" %d %d y\n" group
                              (or (nth 2 info) 0)
-                             (nnimap-replace-in-string group " " "\\ ")))))))
+                             (max 1 (or (nth 1 info) 1))))))))
     (gnus-message 5 "nnimap: Checking mailboxes...done")
-    'groups))
+    'active))
 
 (deffoo nnimap-request-update-info-internal (group info &optional server)
   (when (nnimap-possibly-change-group group server)
@@ -792,19 +799,19 @@ function is generally only called when Gnus is shutting down."
                         seen))
            (gnus-info-set-read info seen)))
 
-       (mapc (lambda (pred)
-               (when (and (nnimap-mark-permanent-p (cdr pred))
-                          (member (nnimap-mark-to-flag (cdr pred))
-                                  (imap-mailbox-get 'flags)))
-                 (gnus-info-set-marks
-                  info
-                  (nnimap-update-alist-soft
-                   (cdr pred)
-                   (gnus-compress-sequence
-                    (imap-search (nnimap-mark-to-predicate (cdr pred))))
-                   (gnus-info-marks info))
-                  t)))
-             gnus-article-mark-lists)
+       (mapcar (lambda (pred)
+                 (when (and (nnimap-mark-permanent-p (cdr pred))
+                            (member (nnimap-mark-to-flag (cdr pred))
+                                    (imap-mailbox-get 'flags)))
+                   (gnus-info-set-marks
+                    info
+                    (nnimap-update-alist-soft
+                     (cdr pred)
+                     (gnus-compress-sequence
+                      (imap-search (nnimap-mark-to-predicate (cdr pred))))
+                     (gnus-info-marks info))
+                    t)))
+               gnus-article-mark-lists)
        
        (gnus-message 5 "nnimap: Updating info for %s...done"
                      (gnus-info-group info))
@@ -853,6 +860,11 @@ function is generally only called when Gnus is shutting down."
        (gnus-message 7 "nnimap: Setting marks in %s...done" group))))
   nil)
 
+(defun nnimap-split-fancy ()
+  "Like nnmail-split-fancy, but uses nnimap-split-fancy."
+  (let ((nnmail-split-fancy nnimap-split-fancy))
+    (nnmail-split-fancy)))
+
 (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.
@@ -901,7 +913,7 @@ function is generally only called when Gnus is shutting down."
          ;; find split rule for this server / inbox
          (when (setq rule (nnimap-split-find-rule server inbox))
            ;; iterate over articles
-           (dolist (article (imap-search "UNSEEN UNDELETED"))
+           (dolist (article (imap-search nnimap-split-predicate))
              (when (nnimap-request-head article)
                ;; copy article to right group(s)
                (setq removeorig nil)
@@ -943,13 +955,11 @@ function is generally only called when Gnus is shutting down."
                           (string= (downcase mailbox) "\\noselect"))
                         (imap-mailbox-get 'list-flags mbx
                                           nnimap-server-buffer))
-             ;; Escape SPC in mailboxes xxx relies on gnus internals
              (let ((info (nnimap-find-minmax-uid mbx 'examine)))
                (when info
-                 (insert (format "%s %d %d y\n"
-                                 (nnimap-replace-in-string mbx " " "\\ ")
-                                 (or (nth 2 info) 0)
-                                 (max 1 (or (nth 1 info) 1)))))))))
+                 (insert (format "\"%s\" %d %d y\n"
+                                 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))
@@ -1205,9 +1215,8 @@ sure of changing the value of `foo'."
 (when nnimap-debug
   (require 'trace)
   (buffer-disable-undo (get-buffer-create nnimap-debug))
-  (mapc (lambda (f) (trace-function-background f nnimap-debug))
+  (mapcar (lambda (f) (trace-function-background f nnimap-debug))
         '(
-         nnimap-replace-in-string
          nnimap-possibly-change-server
          nnimap-verify-uidvalidity
          nnimap-find-minmax-uid