;;; nnimap.el --- IMAP interface for Gnus
-;; Copyright (C) 2010-2012 Free Software Foundation, Inc.
+;; Copyright (C) 2010-2014 Free Software Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
;; Simon Josefsson <simon@josefsson.org>
(defvoo nnimap-authenticator nil
"How nnimap authenticate itself to the server.
-Possible choices are nil (use default methods) or `anonymous'.")
+Possible choices are nil (use default methods), `anonymous',
+`login', `plain' and `cram-md5'.")
(defvoo nnimap-expunge t
"If non-nil, expunge articles after deleting them.
(defcustom nnimap-request-articles-find-limit nil
"Limit the number of articles to look for after moving an article."
- :type 'integer
- :version "24.3"
+ :type '(choice (const nil) integer)
+ :version "24.4"
:group 'nnimap)
(defvar nnimap-process nil)
(nnimap-last-command-time nnimap-object)))
;; More than five minutes since the last command.
(* 5 60)))
- (nnimap-send-command "NOOP")))))))
+ (ignore-errors ;E.g. "buffer foo has no process".
+ (nnimap-send-command "NOOP"))))))))
(defun nnimap-open-connection (buffer)
;; Be backwards-compatible -- the earlier value of nnimap-stream was
(defun nnimap-open-connection-1 (buffer)
(unless nnimap-keepalive-timer
(setq nnimap-keepalive-timer (run-at-time (* 60 15) (* 60 15)
- 'nnimap-keepalive)))
+ #'nnimap-keepalive)))
(with-current-buffer (nnimap-make-process-buffer buffer)
(let* ((coding-system-for-read 'binary)
(coding-system-for-write 'binary)
(nnimap-credentials
(gnus-delete-duplicates
(list
- nnimap-address
- (nnoo-current-server 'nnimap)))
+ (nnoo-current-server 'nnimap)
+ nnimap-address))
ports
nnimap-user))))
(setq nnimap-object nil)
;; round trips than CRAM-MD5, and it's less likely to be buggy),
;; and we're using an encrypted connection.
((and (not (nnimap-capability "LOGINDISABLED"))
- (eq (nnimap-stream-type nnimap-object) 'tls))
+ (eq (nnimap-stream-type nnimap-object) 'tls)
+ (or (null nnimap-authenticator)
+ (eq nnimap-authenticator 'login)))
(nnimap-command "LOGIN %S %S" user password))
- ((nnimap-capability "AUTH=CRAM-MD5")
+ ((and (nnimap-capability "AUTH=CRAM-MD5")
+ (or (null nnimap-authenticator)
+ (eq nnimap-authenticator 'cram-md5)))
(erase-buffer)
(let ((sequence (nnimap-send-command "AUTHENTICATE CRAM-MD5"))
(challenge (nnimap-wait-for-line "^\\+\\(.*\\)\n")))
(base64-decode-string challenge))))
"\r\n"))
(nnimap-wait-for-response sequence)))
- ((not (nnimap-capability "LOGINDISABLED"))
+ ((and (not (nnimap-capability "LOGINDISABLED"))
+ (or (null nnimap-authenticator)
+ (eq nnimap-authenticator 'login)))
(nnimap-command "LOGIN %S %S" user password))
- ((nnimap-capability "AUTH=PLAIN")
+ ((and (nnimap-capability "AUTH=PLAIN")
+ (or (null nnimap-authenticator)
+ (eq nnimap-authenticator 'plain)))
(nnimap-command
"AUTHENTICATE PLAIN %s"
(base64-encode-string
(deffoo nnimap-request-move-article (article group server accept-form
&optional last internal-move-group)
(setq group (nnimap-decode-gnus-group group))
+ (when internal-move-group
+ (setq internal-move-group (nnimap-decode-gnus-group internal-move-group)))
(with-temp-buffer
(mm-disable-multibyte)
(when (funcall (if internal-move-group
(cdr (assoc "SEARCH" (cdr result))))))))))
-(defun nnimap-find-article-by-message-id (group server message-id &optional limit)
+(defun nnimap-find-article-by-message-id (group server message-id
+ &optional limit)
"Search for message with MESSAGE-ID in GROUP from SERVER.
If LIMIT, first try to limit the search to the N last articles."
(with-current-buffer (nnimap-buffer)
(when (equal "EXISTS" (cadr result))
(throw 'found (car result)))))))
(sequence
- (nnimap-send-command "UID SEARCH%s HEADER Message-Id %S"
- (if (and limit number-of-article)
- ;; The -1 is because IMAP message
- ;; numbers are one-based rather than
- ;; zero-based.
- (format " %s:*" (- (string-to-number number-of-article) limit -1))
- "")
- message-id)))
+ (nnimap-send-command
+ "UID SEARCH%s HEADER Message-Id %S"
+ (if (and limit number-of-article)
+ ;; The -1 is because IMAP message
+ ;; numbers are one-based rather than
+ ;; zero-based.
+ (format " %s:*" (- (string-to-number number-of-article)
+ limit -1))
+ "")
+ message-id)))
(when (nnimap-wait-for-response sequence)
- (let ((article (car (last (cdr (assoc "SEARCH" (nnimap-parse-response)))))))
+ (let ((article (car (last (cdr (assoc "SEARCH"
+ (nnimap-parse-response)))))))
(if article
(string-to-number article)
(when (and limit number-of-article)
groups))))
(nreverse groups)))
+(defun nnimap-get-responses (sequences)
+ (let (responses)
+ (dolist (sequence sequences)
+ (goto-char (point-min))
+ (when (re-search-forward (format "^%d " sequence) nil t)
+ (push (list sequence (nnimap-parse-response))
+ responses)))
+ responses))
+
(deffoo nnimap-request-list (&optional server)
(when (nnimap-change-group nil server)
(with-current-buffer nntp-server-buffer
(erase-buffer)
- (dolist (response
- (with-current-buffer (nnimap-buffer)
- ;; Build a list of (group result-of-EXAMINE) for each group
- (mapcar
- (lambda (group)
- (list group (cdr (nnimap-change-group group server nil t))))
- (nnimap-get-groups))))
- (let ((group (encode-coding-string (car response) 'utf-8))
- (response (cadr response)))
- (when (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)))))
- (cond
- ((null highest)
- (insert (format "%S 0 1 y\n" group)))
- ((zerop exists)
- ;; Empty group.
- (insert (format "%S %d %d y\n" group
- highest (1+ highest))))
- (t
- ;; Return the widest possible range.
- (insert (format "%S %d 1 y\n" group
- (or highest exists)))))))))
- t)))
+ (let ((groups
+ (with-current-buffer (nnimap-buffer)
+ (nnimap-get-groups)))
+ sequences responses)
+ (when groups
+ (with-current-buffer (nnimap-buffer)
+ (setf (nnimap-group nnimap-object) nil)
+ (dolist (group groups)
+ (setf (nnimap-examined nnimap-object) group)
+ (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)))
+ (egroup (encode-coding-string group 'utf-8)))
+ (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)))))
+ (cond
+ ((null highest)
+ (insert (format "%S 0 1 y\n" egroup)))
+ ((zerop exists)
+ ;; Empty group.
+ (insert (format "%S %d %d y\n" egroup
+ highest (1+ highest))))
+ (t
+ ;; Return the widest possible range.
+ (insert (format "%S %d 1 y\n" egroup
+ (or highest exists)))))))))
+ t)))))
(deffoo nnimap-request-newgroups (date &optional server)
(when (nnimap-change-group nil server)
(gnus-set-difference
(gnus-set-difference
existing
- (cdr (assoc '%Seen flags)))
+ (gnus-sorted-union
+ (cdr (assoc '%Seen flags))
+ (cdr (assoc '%Deleted flags))))
(cdr (assoc '%Flagged flags)))))
(read (gnus-range-difference
(cons start-article high) unread)))
unsuccessful in connecting.
If GROUP is nil, return t.
If READ-ONLY is set, send EXAMINE rather than SELECT to the server.
-Return the server's response to the SELECT or EXAMINE command.
-"
+Return the server's response to the SELECT or EXAMINE command."
(let ((open-result t))
(when (and server
(not (nnimap-server-opened server)))