1 ;;; vm-imap.el --- Simple IMAP4 (RFC 2060) client for VM
3 ;; Copyright (C) 1998, 2001, 2003 Kyle E. Jones
4 ;; Copyright (C) 2003-2006 Robert Widhopf-Fenk
5 ;; Copyright (C) 2006 Robert P. Goldman
7 ;; This program is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation; either version 2 of the License, or
10 ;; (at your option) any later version.
12 ;; This program is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License along
18 ;; with this program; if not, write to the Free Software Foundation, Inc.,
19 ;; 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 (if (fboundp 'define-error)
27 (define-error 'vm-imap-protocol-error "IMAP protocol error"))
28 (put 'vm-imap-protocol-error 'error-conditions
29 '(vm-imap-protocol-error error))
30 (put 'vm-imap-protocol-error 'error-message "IMAP protocol error"))
32 (defun vm-imap-capability (cap &optional process)
35 (set-buffer (process-buffer process))
36 (memq cap vm-imap-capabilities))
37 (memq cap vm-imap-capabilities)))
39 (defun vm-imap-auth-method (auth)
40 (memq auth vm-imap-auth-methods))
42 (defsubst vm-folder-imap-maildrop-spec ()
43 (aref vm-folder-access-data 0))
44 (defsubst vm-folder-imap-process ()
45 (aref vm-folder-access-data 1))
46 (defsubst vm-folder-imap-uid-validity ()
47 (aref vm-folder-access-data 2))
48 (defsubst vm-folder-imap-uid-list ()
49 (aref vm-folder-access-data 3))
50 (defsubst vm-folder-imap-mailbox-count ()
51 (aref vm-folder-access-data 4))
52 (defsubst vm-folder-imap-read-write ()
53 (aref vm-folder-access-data 5))
54 (defsubst vm-folder-imap-can-delete ()
55 (aref vm-folder-access-data 6))
56 (defsubst vm-folder-imap-body-peek ()
57 (aref vm-folder-access-data 7))
58 (defsubst vm-folder-imap-permanent-flags ()
59 (aref vm-folder-access-data 8))
61 (defsubst vm-set-folder-imap-maildrop-spec (val)
62 (aset vm-folder-access-data 0 val))
63 (defsubst vm-set-folder-imap-process (val)
64 (aset vm-folder-access-data 1 val))
65 (defsubst vm-set-folder-imap-uid-validity (val)
66 (aset vm-folder-access-data 2 val))
67 (defsubst vm-set-folder-imap-uid-list (val)
68 (aset vm-folder-access-data 3 val))
69 (defsubst vm-set-folder-imap-mailbox-count (val)
70 (aset vm-folder-access-data 4 val))
71 (defsubst vm-set-folder-imap-read-write (val)
72 (aset vm-folder-access-data 5 val))
73 (defsubst vm-set-folder-imap-can-delete (val)
74 (aset vm-folder-access-data 6 val))
75 (defsubst vm-set-folder-imap-body-peek (val)
76 (aset vm-folder-access-data 7 val))
77 (defsubst vm-set-folder-imap-permanent-flags (val)
78 (aset vm-folder-access-data 8 val))
80 ;; Our goal is to drag the mail from the IMAP maildrop to the crash box.
81 ;; just as if we were using movemail on a spool file.
82 ;; We remember which messages we have retrieved so that we can
83 ;; leave the message in the mailbox, and yet not retrieve the
84 ;; same messages again and again.
87 (defun vm-imap-move-mail (source destination)
89 (m-per-session vm-imap-messages-per-session)
90 (b-per-session vm-imap-bytes-per-session)
91 (handler (and (fboundp 'find-file-name-handler)
93 (find-file-name-handler source 'vm-imap-move-mail)
94 (wrong-number-of-arguments
95 (find-file-name-handler source)))))
96 (imapdrop (vm-safe-imapdrop-string source))
98 (msgid (list nil nil (vm-imapdrop-sans-password source) 'uid))
99 (imap-retrieved-messages vm-imap-retrieved-messages)
101 (source-nopwd (vm-imapdrop-sans-password source))
102 use-body-peek auto-expunge x select source-list uid
103 can-delete read-write uid-validity
104 mailbox mailbox-count message-size response
105 n (retrieved 0) retrieved-bytes process-buffer)
106 (setq auto-expunge (cond ((setq x (assoc source
107 vm-imap-auto-expunge-alist))
109 ((setq x (assoc (vm-imapdrop-sans-password source)
110 vm-imap-auto-expunge-alist))
112 (t vm-imap-expunge-after-retrieving)))
114 (catch 'end-of-session
116 (throw 'end-of-session
117 (funcall handler 'vm-imap-move-mail source destination)))
118 (setq process (vm-imap-make-session source))
119 (or process (throw 'end-of-session nil))
120 (setq process-buffer (process-buffer process))
122 (set-buffer process-buffer)
123 ;; find out how many messages are in the box.
124 (setq source-list (vm-parse source "\\([^:]+\\):?")
125 mailbox (nth 3 source-list))
126 (setq select (vm-imap-select-mailbox process mailbox))
127 (setq mailbox-count (nth 0 select)
128 uid-validity (nth 1 select)
129 read-write (nth 2 select)
130 can-delete (nth 3 select)
131 use-body-peek (vm-imap-capability 'IMAP4REV1))
132 ;; sweep through the retrieval list, removing entries
133 ;; that have been invalidated by the new UIDVALIDITY
135 (setq imap-retrieved-messages
136 (vm-imap-clear-invalid-retrieval-entries
138 imap-retrieved-messages
140 ;; loop through the maildrop retrieving and deleting
141 ;; messages as we go.
142 (setq n 1 retrieved-bytes 0)
143 (setq statblob (vm-imap-start-status-timer))
144 (vm-set-imap-stat-x-box statblob imapdrop)
145 (vm-set-imap-stat-x-maxmsg statblob mailbox-count)
146 (while (and (<= n mailbox-count)
147 (or (not (natnump m-per-session))
148 (< retrieved m-per-session))
149 (or (not (natnump b-per-session))
150 (< retrieved-bytes b-per-session)))
152 (vm-set-imap-stat-x-currmsg statblob n)
154 (setq list (vm-imap-get-uid-list process n n))
155 (setq uid (cdr (car list)))
157 (setcar (cdr msgid) uid-validity)
158 (if (member msgid imap-retrieved-messages)
160 (if vm-imap-ok-to-ask
162 "Skipping message %d (of %d) from %s (retrieved already)..."
163 n mailbox-count imapdrop))
165 (setq message-size (vm-imap-get-message-size process n))
166 (vm-set-imap-stat-x-need statblob message-size)
167 (if (and (integerp vm-imap-max-message-size)
168 (> message-size vm-imap-max-message-size)
171 (if vm-imap-ok-to-ask
172 (vm-imap-ask-about-large-message
173 process message-size n)
175 (not (eq response 'retrieve))))
177 (if (and read-write can-delete (eq response 'delete))
179 (message "Deleting message %d..." n)
180 (vm-imap-delete-message process n)
182 (if vm-imap-ok-to-ask
183 (message "Skipping message %d..." n)
185 "Skipping message %d in %s, too large (%d > %d)..."
186 n imapdrop message-size vm-imap-max-message-size)))
188 (message "Retrieving message %d (of %d) from %s..."
189 n mailbox-count imapdrop)
192 (vm-imap-send-command process
193 (format "FETCH %d (BODY.PEEK[])"
195 (vm-imap-retrieve-to-target process destination
198 (vm-imap-send-command process
200 "FETCH %d (RFC822.PEEK)" n))
201 (vm-imap-retrieve-to-target process destination
203 (message "Retrieving message %d (of %d) from %s...done"
204 n mailbox-count imapdrop)
205 (vm-increment retrieved)
207 (setq retrieved-bytes (+ retrieved-bytes message-size)))
208 (if (not auto-expunge)
209 (setq imap-retrieved-messages
210 (cons (copy-sequence msgid)
211 imap-retrieved-messages))
212 ;; The user doesn't want the messages
213 ;; kept in the mailbox.
214 ;; Delete the message now.
215 (if (and read-write can-delete)
217 (vm-imap-delete-message process n)
218 (setq did-delete t)))))
222 ;; CLOSE forces an expunge and avoids the EXPUNGE
224 (vm-imap-send-command process "CLOSE")
225 (vm-imap-read-ok-response process)))
226 (not (equal retrieved 0)) ))
227 (setq vm-imap-retrieved-messages imap-retrieved-messages)
228 (if (and (eq vm-flush-interval t) (not (equal retrieved 0)))
229 (vm-stuff-imap-retrieved))
230 (and statblob (vm-imap-stop-status-timer statblob))
232 (vm-imap-end-session process)))))
234 (defun vm-imap-check-mail (source)
236 (handler (and (fboundp 'find-file-name-handler)
238 (find-file-name-handler source 'vm-imap-check-mail)
239 (wrong-number-of-arguments
240 (find-file-name-handler source)))))
241 (retrieved vm-imap-retrieved-messages)
242 (imapdrop (vm-imapdrop-sans-password source))
244 msg-count uid-validity x response select mailbox source-list)
248 (catch 'end-of-session
250 (throw 'end-of-session
251 (funcall handler 'vm-imap-check-mail source)))
252 (setq process (vm-imap-make-session source))
253 (or process (throw 'end-of-session nil))
254 (set-buffer (process-buffer process))
255 (setq source-list (vm-parse source "\\([^:]+\\):?")
256 mailbox (nth 3 source-list))
257 (setq select (vm-imap-select-mailbox process mailbox)
258 msg-count (car select)
259 uid-validity (nth 1 select))
260 (if (zerop msg-count)
262 (vm-store-folder-totals source '(0 0 0 0))
263 (throw 'end-of-session nil)))
264 ;; sweep through the retrieval list, removing entries
265 ;; that have been invalidated by the new UIDVALIDITY
268 (vm-imap-clear-invalid-retrieval-entries imapdrop
271 (setq response (vm-imap-get-uid-list process 1 msg-count))
274 (if (null (car response))
275 ;; (nil . nil) is returned if there are no
276 ;; messages in the mailbox.
278 (vm-store-folder-totals source '(0 0 0 0))
279 (throw 'end-of-session nil))
281 (if (not (and (setq x (assoc (cdr (car response))
283 (equal (nth 1 x) imapdrop)
284 (eq (nth 2 x) 'uid)))
285 (vm-increment count))
286 (setq response (cdr response))))
287 (vm-store-folder-totals source (list count 0 0 0))
288 (throw 'end-of-session (not (eq count 0))))
289 (not (equal 0 (car select)))))
290 (setq vm-imap-retrieved-messages retrieved))
291 (and process (vm-imap-end-session process)))))
293 (defun vm-expunge-imap-messages ()
294 "Deletes all messages from IMAP mailbox that have already been retrieved
295 into the current folder. VM sets the \\Deleted flag on all such messages
296 on all the relevant IMAP servers and then immediately expunges."
298 (vm-follow-summary-cursor)
299 (vm-select-folder-buffer)
300 (vm-check-for-killed-summary)
301 (vm-error-if-virtual-folder)
306 (vm-global-block-new-mail t)
307 (vm-imap-ok-to-ask t)
309 msg-count can-delete read-write uid-validity
310 select-response source-list imapdrop uid-alist mailbox data mp match)
313 (setq vm-imap-retrieved-messages
314 (sort vm-imap-retrieved-messages
315 (function (lambda (a b)
316 (cond ((string-lessp (nth 2 a) (nth 2 b)) t)
317 ((string-lessp (nth 2 b)
320 ((string-lessp (nth 1 a) (nth 1 b)) t)
321 ((string-lessp (nth 1 b) (nth 1 a))
323 ((string-lessp (nth 0 a) (nth 0 b)) t)
325 (setq mp vm-imap-retrieved-messages)
328 (condition-case error-data
331 (if (not (equal source (nth 2 data)))
337 (vm-imap-send-command process "CLOSE")
338 (vm-imap-read-ok-response process)))
339 (vm-imap-end-session process)
342 (setq source (nth 2 data))
343 (setq imapdrop (vm-safe-imapdrop-string source))
344 (condition-case error-data
346 (message "Opening IMAP session to %s..."
348 (setq process (vm-imap-make-session source))
351 (set-buffer (process-buffer process))
352 (setq source-list (vm-parse source
354 mailbox (nth 3 source-list)
355 select-response (vm-imap-select-mailbox
357 msg-count (car select-response)
358 uid-validity (nth 1 select-response)
359 read-write (nth 2 select-response)
360 can-delete (nth 3 select-response))
362 (vm-imap-clear-invalid-retrieval-entries
366 (if (not (eq data (car mp)))
367 ;; this entry must have been
368 ;; discarded as invalid, so
369 ;; skip it and process the
370 ;; entry that is now at the
374 (error "Can't delete messages in mailbox %s, skipping..." mailbox))
376 (error "Mailbox %s is read-only, skipping..." mailbox))
377 (message "Expunging messages in %s..." imapdrop))
380 (apply 'message (cdr error-data))
382 "Couldn't open IMAP session to %s, skipping..."
384 (setq trouble (cons imapdrop trouble))
386 (while (equal (nth 1 (car mp)) source)
389 (if (zerop msg-count)
391 (while (equal (nth 1 (car mp)) source)
395 (vm-imap-get-uid-list
396 process 1 msg-count))))
397 (if (setq match (rassoc (car data) uid-alist))
399 (vm-imap-delete-message process (car match))
401 (vm-increment delete-count))))
403 (setq trouble (cons imapdrop trouble))
404 (message "Something signaled: %s"
405 (prin1-to-string error-data))
407 (message "Skipping rest of mailbox %s..." imapdrop)
409 (while (equal (nth 2 (car mp)) source)
415 (vm-imap-send-command process "CLOSE")
416 (vm-imap-read-ok-response process)))
419 (set-buffer (get-buffer-create "*IMAP Expunge Trouble*"))
420 (setq buffer-read-only nil)
422 (insert (format "%s IMAP message%s expunged.\n\n"
423 (if (zerop delete-count) "No" delete-count)
424 (if (= delete-count 1) "" "s")))
425 (insert "VM had problems expunging messages from:\n")
429 (insert " " (car mp) "\n")
431 (setq buffer-read-only t)
432 (display-buffer (current-buffer)))
433 (message "%s IMAP message%s expunged."
434 (if (zerop delete-count) "No" delete-count)
435 (if (= delete-count 1) "" "s"))))
436 (and process (vm-imap-end-session process)))
437 (or trouble (setq vm-imap-retrieved-messages nil))))
440 (defun vm-imap-make-session (source)
441 (let ((process-to-shutdown nil)
442 (folder-type vm-folder-type)
444 (imapdrop (vm-safe-imapdrop-string source))
445 (coding-system-for-read (vm-binary-coding-system))
446 (coding-system-for-write (vm-binary-coding-system))
449 (session-name "IMAP")
450 (process-connection-type nil)
452 host port mailbox auth user pass source-list process-buffer
455 (catch 'end-of-session
456 ;; parse the maildrop
457 (setq source-list (vm-parse source "\\([^:]+\\):?")
458 host (nth 1 source-list)
459 port (nth 2 source-list)
460 ;; mailbox (nth 3 source-list)
461 auth (nth 4 source-list)
462 user (nth 5 source-list)
463 pass (nth 6 source-list)
465 (vm-imapdrop-sans-password-and-mailbox source))
466 (cond ((equal auth "preauth") t)
467 ((equal "imap-ssl" (car source-list))
469 session-name "IMAP over SSL")
470 (if (null vm-stunnel-program)
471 (error "vm-stunnel-program must be non-nil to use IMAP over SSL.")))
472 ((equal "imap-ssh" (car source-list))
474 session-name "IMAP over SSH")
475 (if (null vm-ssh-program)
476 (error "vm-ssh-program must be non-nil to use IMAP over SSH."))))
477 ;; carp if parts are missing
479 (error "No host in IMAP maildrop specification, \"%s\""
482 (error "No port in IMAP maildrop specification, \"%s\""
484 (if (string-match "^[0-9]+$" port)
485 (setq port (string-to-number port)))
487 (error "No authentication method in IMAP maildrop specification, \"%s\"" source))
489 (error "No user in IMAP maildrop specification, \"%s\""
492 (error "No password in IMAP maildrop specification, \"%s\""
494 (if (and (equal pass "*")
495 (not (equal auth "preauth")))
497 (setq pass (car (cdr (assoc source-nopwd-nombox
498 vm-imap-passwords))))
500 (if (null vm-imap-ok-to-ask)
501 (progn (message "Need password for %s" imapdrop)
502 (throw 'end-of-session nil))
505 (format "IMAP password for %s: "
507 ;; save the password for the sake of
508 ;; vm-expunge-imap-messages, which passes password-less
509 ;; imapdrop specifications to vm-make-imap-session.
510 (if (null (assoc source-nopwd-nombox vm-imap-passwords))
511 (setq vm-imap-passwords (cons (list source-nopwd-nombox pass)
513 ;; get the trace buffer
515 (vm-make-work-buffer (format "trace of %s session to %s"
519 (set-buffer process-buffer)
520 (setq vm-folder-type (or folder-type vm-default-folder-type))
521 (buffer-disable-undo process-buffer)
522 (make-local-variable 'vm-imap-read-point)
523 ;; clear the trace buffer of old output
525 ;; Tell MULE not to mess with the text.
526 (if (fboundp 'set-buffer-file-coding-system)
527 (set-buffer-file-coding-system (vm-binary-coding-system) t))
528 (if (equal auth "preauth")
530 (run-hook-with-args-until-success 'vm-imap-session-preauth-hook
533 (if (processp process)
534 (set-process-buffer process (current-buffer))
535 (insert "starting " session-name
536 " session " (current-time-string) "\n")
537 (insert (format "connecting to %s:%s\n" host port))
538 ;; open the connection to the server
540 (vm-setup-stunnel-random-data-if-needed)
542 (apply 'start-process session-name process-buffer
544 (nconc (vm-stunnel-configuration-args host
546 vm-stunnel-program-switches))))
548 (setq process (open-network-stream
549 session-name process-buffer
551 (vm-setup-ssh-tunnel host port))))
553 (setq process (open-network-stream session-name
556 (and (null process) (throw 'end-of-session nil))
557 (insert-before-markers "connected\n"))
558 (setq vm-imap-read-point (point))
559 (process-kill-without-query process)
560 (if (null (setq greeting (vm-imap-read-greeting process)))
561 (progn (delete-process process)
562 (throw 'end-of-session nil)))
563 (setq process-to-shutdown process)
564 (set (make-local-variable 'vm-imap-session-done) nil)
565 ;; record server capabilities
566 (vm-imap-send-command process "CAPABILITY")
567 (if (null (setq ooo (vm-imap-read-capability-response process)))
568 (throw 'end-of-session nil))
569 (set (make-local-variable 'vm-imap-capabilities) (car ooo))
570 (set (make-local-variable 'vm-imap-auth-methods) (nth 1 ooo))
572 (cond ((equal auth "login")
573 ;; LOGIN must be supported by all imap servers,
574 ;; no need to check for it in CAPABILITIES.
575 (vm-imap-send-command process
576 (format "LOGIN %s %s"
577 (vm-imap-quote-string user)
578 (vm-imap-quote-string pass)))
579 (and (null (vm-imap-read-ok-response process))
581 (setq vm-imap-passwords
582 (delete (list source-nopwd-nombox pass)
584 (message "IMAP password for %s incorrect" imapdrop)
585 ;; don't sleep unless we're running synchronously.
586 (if vm-imap-ok-to-ask
588 (throw 'end-of-session nil))))
589 ((equal auth "cram-md5")
590 (if (not (vm-imap-auth-method 'CRAM-MD5))
591 (error "CRAM-MD5 authentication unsupported by this server"))
592 (let ((ipad (make-string 64 54))
593 (opad (make-string 64 92))
594 (command "AUTHENTICATE CRAM-MD5")
597 (make-string (max 0 (- 64 (length pass)))
599 response p challenge answer)
600 (vm-imap-send-command process command)
601 (setq response (vm-imap-read-response process))
602 (if (vm-imap-response-matches response 'VM 'NO)
603 (error "server said NO to %s" command))
604 (if (vm-imap-response-matches response 'VM 'BAD)
605 (vm-imap-protocol-error "server said BAD to %s"
607 (cond ((vm-imap-response-matches response '+ 'atom)
608 (setq p (cdr (nth 1 response))
609 challenge (buffer-substring
612 challenge (vm-mime-base64-decode-string
615 (error "Don't understand AUTHENTICATE response")))
621 (vm-xor-string secret opad)
624 (vm-xor-string secret ipad) challenge)))))
625 answer (vm-mime-base64-encode-string answer))
626 (vm-imap-send-command process answer nil t)
627 (and (null (vm-imap-read-ok-response process))
629 (setq vm-imap-passwords
630 (delete (list source-nopwd-nombox pass)
632 (message "IMAP password for %s incorrect" imapdrop)
633 ;; don't sleep unless we're running synchronously.
634 (if vm-imap-ok-to-ask
636 (throw 'end-of-session nil)))))
637 ((equal auth "preauth")
638 (if (not (eq greeting 'preauth))
640 (message "IMAP session was not pre-authenticated")
641 ;; don't sleep unless we're running synchronously.
642 (if vm-imap-ok-to-ask
644 (throw 'end-of-session nil))))
645 (t (error "Don't know how to authenticate using %s" auth)))
646 (setq process-to-shutdown nil)
648 (if process-to-shutdown
649 (vm-imap-end-session process-to-shutdown t))
650 (vm-tear-down-stunnel-random-data))))
653 (defun vm-imap-end-session (process &optional keep-buffer)
654 (if (and (memq (process-status process) '(open run))
655 (buffer-live-p (process-buffer process)))
657 (set-buffer (process-buffer process))
658 ;; vm-imap-end-session might have already been called on
659 ;; this process, so don't logout and schedule the killing
660 ;; the process again if it's already been done.
661 (if vm-imap-session-done
663 (vm-imap-send-command process "LOGOUT")
664 (setq vm-imap-session-done t)
665 ;; we don't care about the response.
666 ;; try reading it anyway and see who complains.
667 (vm-imap-read-ok-response process)
668 (if (and (not vm-imap-keep-trace-buffer) (not keep-buffer))
669 (kill-buffer (process-buffer process))
671 (set-buffer (process-buffer process))
672 (rename-buffer (concat "saved " (buffer-name)) t)
673 (vm-keep-some-buffers (current-buffer) 'vm-kept-imap-buffers
674 vm-imap-keep-failed-trace-buffers)))
675 (if (fboundp 'add-async-timeout)
676 (add-async-timeout 2 'delete-process process)
677 (run-at-time 2 nil 'delete-process process))))))
679 (defun vm-imap-stat-timer (o) (aref o 0))
680 (defun vm-imap-stat-did-report (o) (aref o 1))
681 (defun vm-imap-stat-x-box (o) (aref o 2))
682 (defun vm-imap-stat-x-currmsg (o) (aref o 3))
683 (defun vm-imap-stat-x-maxmsg (o) (aref o 4))
684 (defun vm-imap-stat-x-got (o) (aref o 5))
685 (defun vm-imap-stat-x-need (o) (aref o 6))
686 (defun vm-imap-stat-y-box (o) (aref o 7))
687 (defun vm-imap-stat-y-currmsg (o) (aref o 8))
688 (defun vm-imap-stat-y-maxmsg (o) (aref o 9))
689 (defun vm-imap-stat-y-got (o) (aref o 10))
690 (defun vm-imap-stat-y-need (o) (aref o 11))
692 (defun vm-set-imap-stat-timer (o val) (aset o 0 val))
693 (defun vm-set-imap-stat-did-report (o val) (aset o 1 val))
694 (defun vm-set-imap-stat-x-box (o val) (aset o 2 val))
695 (defun vm-set-imap-stat-x-currmsg (o val) (aset o 3 val))
696 (defun vm-set-imap-stat-x-maxmsg (o val) (aset o 4 val))
697 (defun vm-set-imap-stat-x-got (o val) (aset o 5 val))
698 (defun vm-set-imap-stat-x-need (o val) (aset o 6 val))
699 (defun vm-set-imap-stat-y-box (o val) (aset o 7 val))
700 (defun vm-set-imap-stat-y-currmsg (o val) (aset o 8 val))
701 (defun vm-set-imap-stat-y-maxmsg (o val) (aset o 9 val))
702 (defun vm-set-imap-stat-y-got (o val) (aset o 10 val))
703 (defun vm-set-imap-stat-y-need (o val) (aset o 11 val))
705 (defun vm-imap-start-status-timer ()
706 (let ((blob (make-vector 12 nil))
708 (setq timer (add-timeout 5 'vm-imap-report-retrieval-status blob 5))
709 (vm-set-imap-stat-timer blob timer)
712 (defun vm-imap-stop-status-timer (status-blob)
713 (if (vm-imap-stat-did-report status-blob)
715 (if (fboundp 'disable-timeout)
716 (disable-timeout (vm-imap-stat-timer status-blob))
717 (cancel-timer (vm-imap-stat-timer status-blob))))
719 (defun vm-imap-report-retrieval-status (o)
720 (vm-set-imap-stat-did-report o t)
721 (cond ((null (vm-imap-stat-x-got o)) t)
722 ;; should not be possible, but better safe...
723 ((not (eq (vm-imap-stat-x-box o) (vm-imap-stat-y-box o))) t)
724 ((not (eq (vm-imap-stat-x-currmsg o) (vm-imap-stat-y-currmsg o))) t)
725 (t (message "Retrieving message %d (of %d) from %s, %s..."
726 (vm-imap-stat-x-currmsg o)
727 (vm-imap-stat-x-maxmsg o)
728 (vm-imap-stat-x-box o)
729 (if (vm-imap-stat-x-need o)
730 (format "%d%s of %d%s"
731 (vm-imap-stat-x-got o)
732 (if (> (vm-imap-stat-x-got o)
733 (vm-imap-stat-x-need o))
736 (vm-imap-stat-x-need o)
737 (if (eq (vm-imap-stat-x-got o)
738 (vm-imap-stat-y-got o))
741 "post processing"))))
742 (vm-set-imap-stat-y-box o (vm-imap-stat-x-box o))
743 (vm-set-imap-stat-y-currmsg o (vm-imap-stat-x-currmsg o))
744 (vm-set-imap-stat-y-maxmsg o (vm-imap-stat-x-maxmsg o))
745 (vm-set-imap-stat-y-got o (vm-imap-stat-x-got o))
746 (vm-set-imap-stat-y-need o (vm-imap-stat-x-need o)))
748 (defun vm-imap-check-connection (process)
749 (cond ((not (memq (process-status process) '(open run)))
750 (error "IMAP connection not open: %s" process))
751 ((not (buffer-live-p (process-buffer process)))
752 (error "IMAP process %s's buffer has been killed" process))))
754 (defun vm-imap-send-command (process command &optional tag no-tag)
755 (vm-imap-check-connection process)
756 (goto-char (point-max))
757 (or no-tag (insert-before-markers (or tag "VM") " "))
758 (let ((case-fold-search t))
759 (if (string-match "^LOGIN" command)
760 (insert-before-markers "LOGIN <parameters omitted>\r\n")
761 (insert-before-markers command "\r\n")))
762 (setq vm-imap-read-point (point))
763 ;; previously we had a process-send-string call for each string
764 ;; to avoid extra consing but that caused a lot of packet overhead.
766 (process-send-string process (format "%s\r\n" command))
767 (process-send-string process (format "%s %s\r\n" (or tag "VM") command))))
769 (defun vm-imap-select-mailbox (process mailbox &optional just-examine)
770 (let ((imap-buffer (current-buffer))
771 (command (if just-examine "EXAMINE" "SELECT"))
774 (permanent-flags nil)
777 (read-write (not just-examine))
780 (vm-imap-send-command process (format "%s %s" command mailbox))
782 (setq response (vm-imap-read-response process))
783 (if (vm-imap-response-matches response 'VM 'NO)
784 (error "server said NO to %s" command))
785 (if (vm-imap-response-matches response 'VM 'BAD)
786 (vm-imap-protocol-error "server said BAD to %s" command))
787 (cond ((vm-imap-response-matches response '* 'OK 'vector)
788 (setq p (cdr (nth 2 response)))
789 (cond ((vm-imap-response-matches p 'UIDVALIDITY 'atom)
791 (setq uid-validity (buffer-substring (nth 1 tok)
793 ((vm-imap-response-matches p 'PERMANENTFLAGS 'list)
794 (setq permanent-flags (nth 1 p)))))
795 ((vm-imap-response-matches response '* 'FLAGS 'list)
796 (setq flags (nth 2 response)))
797 ((vm-imap-response-matches response '* 'atom 'EXISTS)
798 (setq tok (nth 1 response))
799 (goto-char (nth 1 tok))
800 (setq msg-count (read imap-buffer)))
801 ((vm-imap-response-matches response 'VM 'OK '(vector READ-WRITE))
802 (setq need-ok nil read-write t))
803 ((vm-imap-response-matches response 'VM 'OK '(vector READ-ONLY))
804 (setq need-ok nil read-write t))
805 ((vm-imap-response-matches response 'VM 'OK)
806 (setq need-ok nil))))
808 (vm-imap-protocol-error "FLAGS missing from SELECT responses"))
810 (vm-imap-protocol-error "EXISTS missing from SELECT responses"))
811 (if (null uid-validity)
812 (vm-imap-protocol-error "UIDVALIDITY missing from SELECT responses"))
813 (setq can-delete (vm-imap-scan-list-for-flag flags "\\Deleted"))
814 (list msg-count uid-validity read-write can-delete permanent-flags) ))
816 (defun vm-imap-get-uid-list (process first last)
818 (imap-buffer (current-buffer))
819 tok msg-num uid response p
821 (vm-imap-send-command process (format "FETCH %s:%s (UID)" first last))
823 (setq response (vm-imap-read-response process))
824 (if (vm-imap-response-matches response 'VM 'NO)
825 (error "server said NO to UID FETCH"))
826 (if (vm-imap-response-matches response 'VM 'BAD)
827 (vm-imap-protocol-error "server said BAD to UID FETCH"))
828 (cond ((vm-imap-response-matches response '* 'atom 'FETCH 'list)
829 (setq p (cdr (nth 3 response)))
830 (if (not (vm-imap-response-matches p 'UID 'atom))
831 (vm-imap-protocol-error
832 "expected (UID number) in FETCH response"))
833 (setq tok (nth 1 response))
834 (goto-char (nth 1 tok))
835 (setq msg-num (read imap-buffer))
837 (setq uid (buffer-substring (nth 1 tok) (nth 2 tok))
838 list (cons (cons msg-num uid) list)))
839 ((vm-imap-response-matches response 'VM 'OK)
840 (setq need-ok nil))))
841 ;; returning nil means the uid fetch failed so return
842 ;; something other than nil if there aren't any messages.
847 (defun vm-imap-get-flags-list (process first last)
849 (imap-buffer (current-buffer))
850 tok msg-num flag flags response p
852 (vm-imap-send-command process (format "FETCH %s:%s (FLAGS)" first last))
854 (setq response (vm-imap-read-response process))
855 (if (vm-imap-response-matches response 'VM 'NO)
856 (error "server said NO to FLAGS FETCH"))
857 (if (vm-imap-response-matches response 'VM 'BAD)
858 (vm-imap-protocol-error "server said BAD to FLAGS FETCH"))
859 (cond ((vm-imap-response-matches response '* 'atom 'FETCH 'list)
860 (setq p (cdr (nth 3 response)))
861 (if (not (vm-imap-response-matches p 'FLAGS 'list))
862 (vm-imap-protocol-error
863 "expected (FLAGS list) in FETCH response"))
864 (setq tok (nth 1 response))
865 (goto-char (nth 1 tok))
866 (setq msg-num (read imap-buffer))
867 (setq p (cdr (nth 1 p))
871 (if (not (vm-imap-response-matches tok 'atom))
872 (vm-imap-protocol-error
873 "expected atom in FLAGS list in FETCH response"))
874 (setq flag (buffer-substring (nth 1 tok) (nth 2 tok))
875 flags (cons flag flags)
877 (setq list (cons (cons msg-num flags) list)))
878 ((vm-imap-response-matches response 'VM 'OK)
879 (setq need-ok nil))))
880 ;; returning nil means the fetch failed so return
881 ;; something other than nil if there aren't any messages.
886 (defun vm-imap-ask-about-large-message (process size n)
887 (let ((work-buffer nil)
888 (imap-buffer (current-buffer))
891 response fetch-response
896 (save-window-excursion
897 (vm-imap-send-command process
898 (format "FETCH %d (RFC822.HEADER)" n))
900 (setq response (vm-imap-read-response process))
901 (if (vm-imap-response-matches response 'VM 'NO)
902 (error "server said NO to header FETCH"))
903 (if (vm-imap-response-matches response 'VM 'BAD)
904 (vm-imap-protocol-error "server said BAD to header FETCH"))
905 (cond ((vm-imap-response-matches response '* 'atom 'FETCH 'list)
906 (setq fetch-response response
908 ((vm-imap-response-matches response 'VM 'OK)
909 (setq need-ok nil))))
911 (vm-imap-protocol-error "FETCH OK sent before FETCH response"))
912 (setq vm-imap-read-point (point-marker))
913 (setq list (cdr (nth 3 fetch-response)))
914 (if (not (vm-imap-response-matches list 'RFC822\.HEADER 'string))
915 (vm-imap-protocol-error
916 "expected (RFC822.HEADER string) in FETCH response"))
920 (setq work-buffer (generate-new-buffer "*imap-glop*"))
921 (set-buffer work-buffer)
922 (insert-buffer-substring imap-buffer start end)
923 (vm-imap-cleanup-region (point-min) (point-max))
924 (vm-display-buffer work-buffer)
925 (setq minibuffer-scroll-window (selected-window))
926 (goto-char (point-min))
927 (if (re-search-forward "^Received:" nil t)
929 (goto-char (match-beginning 0))
930 (vm-reorder-message-headers
931 nil vm-visible-headers
932 vm-invisible-header-regexp)))
933 (set-window-point (selected-window) (point))
934 (if (y-or-n-p (format "Retrieve message %d (size = %d)? " n size))
936 (if (y-or-n-p (format "Delete message %d from maildrop? " n))
939 (and work-buffer (kill-buffer work-buffer)))))
941 (defun vm-imap-retrieve-to-target (process target statblob bodypeek)
942 (let ((start vm-imap-read-point)
944 end fetch-response list p)
946 (vm-set-imap-stat-x-got statblob 0)
949 (lambda (beg end len)
950 (if vm-imap-read-point
952 (vm-set-imap-stat-x-got statblob (- end start))
953 (if (zerop (% (random) 10))
954 (vm-imap-report-retrieval-status statblob)))))))
955 (after-change-functions (cons func after-change-functions))
959 (setq response (vm-imap-read-response process))
960 (if (vm-imap-response-matches response 'VM 'NO)
961 (error "server said NO to message FETCH"))
962 (if (vm-imap-response-matches response 'VM 'BAD)
963 (vm-imap-protocol-error "server said BAD to message FETCH"))
964 (cond ((vm-imap-response-matches response '* 'atom 'FETCH 'list)
965 (setq fetch-response response
967 ((vm-imap-response-matches response 'VM 'OK)
968 (setq need-ok nil)))))
970 (vm-imap-protocol-error "FETCH OK sent before FETCH response"))
971 ;; must make the read point a marker so that it stays fixed
972 ;; relative to the text when we modify things below.
973 (setq vm-imap-read-point (point-marker))
974 (setq list (cdr (nth 3 fetch-response)))
977 (if (not (vm-imap-response-matches list 'BODY '(vector) 'string))
978 (vm-imap-protocol-error
979 "expected (BODY[] string) in FETCH response"))
983 (if (not (vm-imap-response-matches list 'RFC822 'string))
984 (vm-imap-protocol-error
985 "expected (RFC822 string) in FETCH response"))
988 (goto-char (nth 2 p))
989 (setq end (point-marker))
990 (vm-set-imap-stat-x-need statblob nil)
991 (vm-imap-cleanup-region start end)
992 (vm-munge-message-separators vm-folder-type start end)
994 (vm-set-imap-stat-x-got statblob nil)
995 ;; avoid the consing and stat() call for all but babyl
996 ;; files, since this will probably slow things down.
997 ;; only babyl files have the folder header, and we
998 ;; should only insert it if the crash box is empty.
999 (if (and (eq vm-folder-type 'babyl)
1000 (cond ((stringp target)
1001 (let ((attrs (file-attributes target)))
1002 (or (null attrs) (equal 0 (nth 7 attrs)))))
1006 (zerop (buffer-size))))))
1007 (let ((opoint (point)))
1008 (vm-convert-folder-header nil vm-folder-type)
1009 ;; if start is a marker, then it was moved
1010 ;; forward by the insertion. restore it.
1013 (vm-skip-past-folder-header)))
1014 (insert (vm-leading-message-separator))
1016 (narrow-to-region (point) end)
1017 (vm-convert-folder-type-headers 'baremessage vm-folder-type))
1019 (insert-before-markers (vm-trailing-message-separator))
1020 ;; Some IMAP servers don't understand Sun's stupid
1021 ;; From_-with-Content-Length style folder and assume the last
1022 ;; newline in the message is a separator. And so the server
1023 ;; strips it, leaving us with a message that does not end
1024 ;; with a newline. Add the newline if needed.
1026 ;; HP Openmail seems to have this problem.
1027 (if (and (not (eq ?\n (char-after (1- (point)))))
1028 (memq vm-folder-type '(From_-with-Content-Length BellFrom_)))
1029 (insert-before-markers "\n"))
1030 (if (stringp target)
1031 ;; Set file type to binary for DOS/Windows. I don't know if
1032 ;; this is correct to do or not; it depends on whether the
1033 ;; the CRLF or the LF newline convention is used on the inbox
1034 ;; associated with this crashbox. This setting assumes the LF
1035 ;; newline convention is used.
1036 (let ((buffer-file-type t)
1037 (selective-display nil))
1038 (write-region start end target t 0))
1039 (let ((b (current-buffer)))
1042 (let ((buffer-read-only nil))
1043 (insert-buffer-substring b start end)))))
1044 (delete-region start end)
1047 (defun vm-imap-delete-message (process n)
1048 (vm-imap-send-command process (format "STORE %d +FLAGS.SILENT (\\Deleted)"
1050 (if (null (vm-imap-read-ok-response process))
1051 (vm-imap-protocol-error "STORE ... +FLAGS.SILENT (\\Deleted) failed")))
1053 (defun vm-imap-get-message-size (process n)
1055 (imap-buffer (current-buffer))
1059 (vm-imap-send-command process (format "FETCH %d:%d (RFC822.SIZE)" n n))
1061 (setq response (vm-imap-read-response process))
1062 (if (vm-imap-response-matches response 'VM 'NO)
1063 (error "server said NO to size FETCH"))
1064 (if (vm-imap-response-matches response 'VM 'BAD)
1065 (vm-imap-protocol-error "server said BAD to size FETCH"))
1066 (cond ((and need-size
1067 (vm-imap-response-matches response '* 'atom 'FETCH 'list))
1068 (setq need-size nil)
1069 (setq p (cdr (nth 3 response)))
1070 (if (not (vm-imap-response-matches p 'RFC822\.SIZE 'atom))
1071 (vm-imap-protocol-error
1072 "expected (RFC822.SIZE number) in FETCH response"))
1073 (setq tok (nth 1 p))
1074 (goto-char (nth 1 tok))
1075 (setq size (read imap-buffer)))
1076 ((vm-imap-response-matches response 'VM 'OK)
1077 (setq need-ok nil))))
1080 (defun vm-imap-read-capability-response (process)
1081 (let (response r cap-list auth-list (need-ok t))
1083 (setq response (vm-imap-read-response process))
1084 (if (vm-imap-response-matches response 'VM 'NO)
1085 (error "server said NO to CAPABILITY"))
1086 (if (vm-imap-response-matches response 'VM 'BAD)
1087 (vm-imap-protocol-error "server said BAD to CAPABILITY"))
1088 (if (vm-imap-response-matches response 'VM 'OK)
1090 (if (not (vm-imap-response-matches response '* 'CAPABILITY))
1092 ;; skip * CAPABILITY
1093 (setq response (cdr (cdr response)))
1095 (setq r (car response))
1096 (if (not (eq (car r) 'atom))
1099 (goto-char (nth 1 r))
1100 (let ((case-fold-search t))
1101 (eq (re-search-forward "AUTH=." (nth 2 r) t)
1104 (setq auth-list (cons (intern
1105 (upcase (buffer-substring
1109 (setq r (car response))
1110 (if (not (eq (car r) 'atom))
1112 (setq cap-list (cons (intern
1113 (upcase (buffer-substring
1114 (nth 1 r) (nth 2 r))))
1116 (setq response (cdr response))))))
1117 (if (or cap-list auth-list)
1118 (list (nreverse cap-list) (nreverse auth-list))
1121 (defun vm-imap-read-greeting (process)
1123 (setq response (vm-imap-read-response process))
1124 (cond ((vm-imap-response-matches response '* 'OK)
1126 ((vm-imap-response-matches response '* 'PREAUTH)
1130 (defun vm-imap-read-ok-response (process)
1131 (let (response retval (done nil))
1133 (setq response (vm-imap-read-response process))
1134 (cond ((vm-imap-response-matches response '*)
1136 ((vm-imap-response-matches response 'VM 'OK)
1137 (setq retval t done t))
1138 (t (setq retval nil done t))))
1141 (defun vm-imap-cleanup-region (start end)
1142 (setq end (vm-marker end))
1146 (while (and (< (point) end) (search-forward "\r\n" end t))
1147 (replace-match "\n" t t)))
1148 (set-marker end nil))
1150 (defun vm-imapdrop-sans-password (source)
1152 (setq source-list (vm-parse source "\\([^:]+\\):?"))
1153 (concat (nth 0 source-list) ":"
1154 (nth 1 source-list) ":"
1155 (nth 2 source-list) ":"
1156 (nth 3 source-list) ":"
1157 (nth 4 source-list) ":"
1158 (nth 5 source-list) ":*")))
1160 (defun vm-imapdrop-sans-password-and-mailbox (source)
1162 (setq source-list (vm-parse source "\\([^:]+\\):?"))
1163 (concat (nth 0 source-list) ":"
1164 (nth 1 source-list) ":"
1165 (nth 2 source-list) ":*:"
1166 (nth 4 source-list) ":"
1167 (nth 5 source-list) ":*")))
1169 (defun vm-imap-read-response (process)
1170 (let ((list nil) tail obj)
1171 (goto-char vm-imap-read-point)
1172 (while (not (eq (car (setq obj (vm-imap-read-object process)))
1175 (setq list (cons obj nil)
1177 (setcdr tail (cons obj nil))
1178 (setq tail (cdr tail))))
1181 (defun vm-imap-read-object (process &optional skip-eol)
1186 (skip-chars-forward " \t")
1187 (cond ((< (- (point-max) (point)) 2)
1188 (setq opoint (point))
1189 (vm-imap-check-connection process)
1190 (accept-process-output process)
1192 ((looking-at "\r\n")
1194 (setq token '(end-of-line) done (not skip-eol)))
1197 (let* ((list (list 'vector))
1200 (while (not (eq (car (setq obj (vm-imap-read-object process t)))
1202 (if (eq (car obj) 'close-paren)
1203 (vm-imap-protocol-error "unexpected )"))
1204 (setcdr tail (cons obj nil))
1205 (setq tail (cdr tail)))
1206 (setq token list done t)))
1209 (setq token '(close-bracket) done t))
1212 (let* ((list (list 'list))
1215 (while (not (eq (car (setq obj (vm-imap-read-object process t)))
1217 (if (eq (car obj) 'close-bracket)
1218 (vm-imap-protocol-error "unexpected ]"))
1219 (setcdr tail (cons obj nil))
1220 (setq tail (cdr tail)))
1221 (setq token list done t)))
1224 (setq token '(close-paren) done t))
1227 (let (start obj n-octets)
1228 (setq obj (vm-imap-read-object process))
1229 (if (not (eq (car obj) 'atom))
1230 (vm-imap-protocol-error "number expected after {"))
1231 (setq n-octets (string-to-number
1232 (buffer-substring (nth 1 obj)
1234 (setq obj (vm-imap-read-object process))
1235 (if (not (eq (car obj) 'close-brace))
1236 (vm-imap-protocol-error "} expected"))
1237 (setq obj (vm-imap-read-object process))
1238 (if (not (eq (car obj) 'end-of-line))
1239 (vm-imap-protocol-error "CRLF expected"))
1240 (setq start (point))
1241 (while (< (- (point-max) start) n-octets)
1242 (vm-imap-check-connection process)
1243 (accept-process-output process))
1244 (goto-char (+ start n-octets))
1245 (setq token (list 'string start (point))
1249 (setq token '(close-brace) done t))
1250 ((looking-at "\042") ;; double quote
1252 (let ((start (point))
1255 (skip-chars-forward "^\042")
1256 (setq curpoint (point))
1257 (if (looking-at "\042")
1261 (vm-imap-check-connection process)
1262 (accept-process-output process)
1263 (goto-char curpoint))
1264 (setq token (list 'string start curpoint)))))
1265 ;; should be (looking-at "[\000-\040\177-\377]")
1266 ;; but Microsoft Exchange emits 8-bit chars.
1267 ((looking-at "[\000-\040\177]")
1268 (vm-imap-protocol-error "unexpected char (%d)"
1269 (char-after (point))))
1271 (let ((start (point))
1273 ;; We should be considering 8-bit chars as
1274 ;; non-word chars also but Microsoft Exchange
1275 ;; uses them, despite the RFC 2060 prohibition.
1276 ;; If we ever resume disallowing 8-bit chars,
1277 ;; remember to write the range as \177-\376 ...
1278 ;; \376 instead of \377 because Emacs 19.34 has
1279 ;; a bug in the fastmap initialization code
1280 ;; that causes it to infloop.
1281 (not-word-chars "^\000-\040\177()[]{}")
1282 (not-word-regexp "[][\000-\040\177(){}]"))
1284 (skip-chars-forward not-word-chars)
1285 (setq curpoint (point))
1286 (if (looking-at not-word-regexp)
1288 (vm-imap-check-connection process)
1289 (accept-process-output process)
1290 (goto-char curpoint))
1291 (setq token (list 'atom start curpoint)))))))
1292 (setq vm-imap-read-point (point))
1295 (defun vm-imap-response-matches (response &rest expr)
1296 (let ((case-fold-search t) e r)
1298 (while (and expr response)
1302 (if (or (not (eq (car r) 'string))
1304 (goto-char (nth 1 r))
1305 (not (eq (search-forward e (nth 2 r) t) (nth 2 r)))))
1308 (if (or (not (eq (car r) 'atom))
1310 (goto-char (nth 1 r))
1311 (not (eq (search-forward (int-to-string e)
1316 (if (not (eq (car e) (car r)))
1318 (apply 'vm-imap-response-matches (cdr r) (cdr e)))
1320 (if (not (eq (car r) 'atom))
1323 (if (not (eq (car r) 'vector))
1326 (if (not (eq (car r) 'list))
1329 (if (not (eq (car r) 'string))
1331 ;; this must to come after all the comparisons for
1332 ;; specific symbols.
1334 (if (or (not (eq (car r) 'atom))
1336 (goto-char (nth 1 r))
1337 (not (eq (search-forward (symbol-name e) (nth 2 r) t)
1339 (throw 'done nil))))
1340 (setq response (cdr response)
1344 (defun vm-imap-bail-if-server-says-farewell (response)
1345 (if (vm-imap-response-matches response '* 'BYE)
1346 (throw 'end-of-session t)))
1348 (defun vm-imap-protocol-error (&rest args)
1349 (set (make-local-variable 'vm-imap-keep-trace-buffer) t)
1350 (signal 'vm-imap-protocol-error (list (apply 'format args))))
1352 (defun vm-imap-scan-list-for-flag (list flag)
1353 (setq list (cdr list))
1354 (let ((case-fold-search t) e)
1358 (if (not (eq (car e) 'atom))
1360 (goto-char (nth 1 e))
1361 (if (eq (search-forward flag (nth 2 e) t) (nth 2 e))
1363 (setq list (cdr list)))
1366 ;; like Lisp get but for IMAP property lists like those returned by FETCH.
1367 (defun vm-imap-plist-get (list name)
1368 (setq list (cdr list))
1369 (let ((case-fold-search t) e)
1373 (if (not (eq (car e) 'atom))
1375 (goto-char (nth 1 e))
1376 (if (eq (search-forward name (nth 2 e) t) (nth 2 e))
1377 (throw 'done (car (cdr list)))))
1378 (setq list (cdr (cdr list))))
1381 (defun vm-imap-clear-invalid-retrieval-entries (source-nopwd retrieved
1386 (if (and (equal source-nopwd (nth 2 (car x)))
1387 (not (equal (nth 1 (car x)) uid-validity)))
1389 (setcdr prev (cdr x))
1390 (setq retrieved (cdr retrieved))))
1394 (defun vm-imap-quote-string (string)
1395 (vm-with-string-as-temp-buffer string 'vm-imap-quote-buffer))
1397 (defun vm-imap-quote-buffer ()
1398 (goto-char (point-min))
1400 (while (re-search-forward "[\"\\]" nil t)
1404 (goto-char (point-max))
1407 (defun vm-establish-new-folder-imap-session (&optional interactive)
1408 (let ((process (vm-folder-imap-process))
1409 mailbox select mailbox-count uid-validity permanent-flags
1410 read-write can-delete body-peek
1411 (vm-imap-ok-to-ask interactive))
1412 (if (processp process)
1413 (vm-imap-end-session process))
1414 (setq process (vm-imap-make-session (vm-folder-imap-maildrop-spec)))
1415 (vm-set-folder-imap-process process)
1416 (setq mailbox (vm-imap-parse-spec-to-list (vm-folder-imap-maildrop-spec))
1417 mailbox (nth 3 mailbox))
1419 (set-buffer (process-buffer process))
1420 (setq select (vm-imap-select-mailbox process mailbox))
1421 (setq mailbox-count (nth 0 select)
1422 uid-validity (nth 1 select)
1423 read-write (nth 2 select)
1424 can-delete (nth 3 select)
1425 permanent-flags (nth 4 select)
1426 body-peek (vm-imap-capability 'IMAP4REV1)))
1427 (vm-set-folder-imap-uid-validity uid-validity)
1428 (vm-set-folder-imap-mailbox-count mailbox-count)
1429 (vm-set-folder-imap-read-write read-write)
1430 (vm-set-folder-imap-can-delete can-delete)
1431 (vm-set-folder-imap-body-peek body-peek)
1432 (vm-set-folder-imap-permanent-flags permanent-flags)
1435 (defun vm-imap-get-uid-data ()
1436 (if (eq 0 (vm-folder-imap-mailbox-count))
1438 (let ((there (make-vector 67 0))
1439 (process (vm-folder-imap-process))
1440 (mailbox-count (vm-folder-imap-mailbox-count))
1443 (set-buffer (process-buffer process))
1444 (setq list (vm-imap-get-uid-list process 1 mailbox-count))
1446 (set (intern (cdr (car list)) there) (car (car list)))
1447 (setq list (cdr list)))
1450 (defun vm-imap-get-message-flags (process m &optional norecord)
1451 (let (need-ok p r flag response saw-seen)
1453 (set-buffer (process-buffer process))
1454 (vm-imap-send-command process
1455 (format "UID FETCH %s (FLAGS)"
1456 (vm-imap-uid-of m)))
1459 (setq response (vm-imap-read-response process))
1460 (if (vm-imap-response-matches response 'VM 'NO)
1461 (error "server said NO to UID FETCH (FLAGS)"))
1462 (if (vm-imap-response-matches response 'VM 'BAD)
1463 (vm-imap-protocol-error "server said BAD to UID FETCH (FLAGS)"))
1464 (if (vm-imap-response-matches response '* 'BYE)
1465 (vm-imap-protocol-error "server said BYE to UID FETCH (FLAGS)"))
1466 (cond ((vm-imap-response-matches response 'VM 'OK)
1468 ((vm-imap-response-matches response '* 'atom 'FETCH 'list)
1469 (setq r (nthcdr 3 response)
1471 r (vm-imap-plist-get r "FLAGS")
1475 (if (not (eq (car p) 'atom))
1477 (setq flag (downcase (buffer-substring (nth 1 p) (nth 2 p))))
1478 (cond ((string= flag "\\answered")
1479 (vm-set-replied-flag m t norecord))
1480 ((string= flag "\\deleted")
1481 (vm-set-deleted-flag m t norecord))
1482 ((string= flag "\\seen")
1483 (vm-set-unread-flag m nil norecord)
1484 (vm-set-new-flag m nil norecord)
1486 ((string= flag "\\recent")
1487 (vm-set-new-flag m t norecord))))
1490 (vm-set-unread-flag m t norecord))))))))
1492 (defun vm-imap-store-message-flags (process m perm-flags)
1493 (let (need-ok flags response)
1495 (set-buffer (process-buffer process))
1496 (if (and (vm-replied-flag m)
1497 (vm-imap-scan-list-for-flag perm-flags "\\Answered"))
1498 (setq flags (cons (intern "\\Answered") flags)))
1499 (if (and (not (vm-unread-flag m))
1500 (vm-imap-scan-list-for-flag perm-flags "\\Seen"))
1501 (setq flags (cons (intern "\\Seen") flags)))
1502 (if (and (vm-deleted-flag m)
1503 (vm-imap-scan-list-for-flag perm-flags "\\Deleted"))
1504 (setq flags (cons (intern "\\Deleted") flags)))
1505 (vm-imap-send-command process
1506 (format "UID STORE %s FLAGS %s"
1508 (if flags flags "()")))
1511 (setq response (vm-imap-read-response process))
1512 (if (vm-imap-response-matches response 'VM 'NO)
1513 (error "server said NO to UID FETCH (FLAGS)"))
1514 (if (vm-imap-response-matches response 'VM 'BAD)
1515 (vm-imap-protocol-error "server said BAD to UID FETCH (FLAGS)"))
1516 (if (vm-imap-response-matches response '* 'BYE)
1517 (vm-imap-protocol-error "server said BYE to UID FETCH (FLAGS)"))
1518 (cond ((vm-imap-response-matches response 'VM 'OK)
1519 (setq need-ok nil))))
1520 (vm-set-attribute-modflag-of m nil))))
1522 (defvar vm-imap-subst-char-in-string-buffer
1523 (get-buffer-create " *subst-char-in-string*"))
1525 (defun vm-imap-subst-CRLF-for-LF (string)
1526 (with-current-buffer vm-imap-subst-char-in-string-buffer
1529 (goto-char (point-min))
1530 (while (search-forward "\n" nil t)
1531 (replace-match "\r\n" nil t))
1532 (buffer-substring-no-properties (point-min) (point-max))))
1535 (defun vm-imap-save-message (process m mailbox)
1536 (let (need-ok need-plus flags response string)
1537 ;; save the message's flag along with it.
1538 ;; don't save the deleted flag.
1539 (if (vm-replied-flag m)
1540 (setq flags (cons (intern "\\Answered") flags)))
1541 (if (not (vm-unread-flag m))
1542 (setq flags (cons (intern "\\Seen") flags)))
1544 (set-buffer (vm-buffer-of (vm-real-message-of m)))
1547 (setq string (buffer-substring (vm-headers-of m) (vm-text-end-of m))
1548 string (vm-imap-subst-CRLF-for-LF string))))
1550 (set-buffer (process-buffer process))
1552 (vm-imap-create-mailbox process mailbox)
1554 (vm-imap-send-command process
1555 (format "APPEND %s %s {%d}"
1556 (vm-imap-quote-string mailbox)
1557 (if flags flags "()")
1561 (setq response (vm-imap-read-response process))
1562 (if (vm-imap-response-matches response 'VM 'NO)
1563 (error "server said NO to APPEND command"))
1564 (if (vm-imap-response-matches response 'VM 'BAD)
1565 (vm-imap-protocol-error "server said BAD to APPEND command"))
1566 (if (vm-imap-response-matches response '* 'BYE)
1567 (vm-imap-protocol-error "server said BYE to APPEND command"))
1568 (cond ((vm-imap-response-matches response '+)
1569 (setq need-plus nil))))
1570 (vm-imap-send-command process string nil t)
1573 (setq response (vm-imap-read-response process))
1574 (if (vm-imap-response-matches response 'VM 'NO)
1575 (error "server said NO to APPEND data"))
1576 (if (vm-imap-response-matches response 'VM 'BAD)
1577 (vm-imap-protocol-error "server said BAD to APPEND data"))
1578 (if (vm-imap-response-matches response '* 'BYE)
1579 (vm-imap-protocol-error "server said BYE to APPEND data"))
1580 (cond ((vm-imap-response-matches response 'VM 'OK)
1581 (setq need-ok nil)))))))
1583 (defun vm-imap-get-synchronization-data ()
1584 (let ((here (make-vector 67 0))
1585 (there (vm-imap-get-uid-data))
1586 (process (vm-folder-imap-process))
1587 (uid-validity (vm-folder-imap-uid-validity))
1588 retrieve-list expunge-list
1590 (setq mp vm-message-list)
1592 (if (or (null (vm-imap-uid-of (car mp)))
1593 (not (equal (vm-imap-uid-validity-of (car mp)) uid-validity)))
1595 (set (intern (vm-imap-uid-of (car mp)) here) (car mp))
1596 (if (not (boundp (intern (vm-imap-uid-of (car mp)) there)))
1597 (setq expunge-list (cons (car mp) expunge-list))))
1601 (if (and (not (boundp (intern (symbol-name sym) here)))
1602 (not (assoc (symbol-name sym)
1603 vm-imap-retrieved-messages)))
1604 (setq retrieve-list (cons
1605 (cons (symbol-name sym)
1609 (list retrieve-list expunge-list)))
1612 (defun vm-imap-synchronize-folder (&optional interactive
1617 (if (and do-retrieves vm-block-new-mail)
1618 (error "Can't get new mail until you save this folder."))
1619 (if (or vm-global-block-new-mail
1620 (null (vm-establish-new-folder-imap-session interactive)))
1623 (vm-assimilate-new-messages))
1624 (let* ((sync-data (vm-imap-get-synchronization-data))
1625 (retrieve-list (car sync-data))
1626 (local-expunge-list (nth 1 sync-data))
1627 (process (vm-folder-imap-process))
1630 (imapdrop (vm-folder-imap-maildrop-spec))
1631 (uid-validity (vm-folder-imap-uid-validity))
1632 (safe-imapdrop (vm-safe-imapdrop-string imapdrop))
1633 (use-body-peek (vm-folder-imap-body-peek))
1634 r-list mp got-some message-size
1635 (folder-buffer (current-buffer)))
1636 (if (and do-retrieves retrieve-list)
1638 (vm-save-restriction
1640 (goto-char (point-max))
1641 (condition-case error-data
1643 (set-buffer (process-buffer process))
1644 (setq statblob (vm-imap-start-status-timer))
1645 (vm-set-imap-stat-x-box statblob safe-imapdrop)
1646 (vm-set-imap-stat-x-maxmsg statblob
1647 (length retrieve-list))
1648 (setq r-list retrieve-list)
1650 (vm-set-imap-stat-x-currmsg statblob n)
1651 (setq message-size (vm-imap-get-message-size
1652 process (cdr (car r-list))))
1653 (vm-set-imap-stat-x-need statblob message-size)
1656 (vm-imap-send-command process
1658 "FETCH %s (BODY.PEEK[])"
1659 (cdr (car r-list))))
1660 (vm-imap-retrieve-to-target process folder-buffer
1663 (vm-imap-send-command process
1665 "FETCH %s (RFC822.PEEK)"
1666 (cdr (car r-list))))
1667 (vm-imap-retrieve-to-target process folder-buffer
1669 (setq r-list (cdr r-list)
1672 (message "Retrieval from %s signaled: %s" safe-imapdrop
1675 (message "Quit received during retrieval from %s"
1677 (and statblob (vm-imap-stop-status-timer statblob))
1678 ;; to make the "Mail" indicator go away
1679 (setq vm-spooled-mail-waiting nil)
1680 (intern (buffer-name) vm-buffers-needing-display-update)
1681 (vm-increment vm-modification-counter)
1682 (vm-update-summary-and-mode-line)
1683 (setq mp (vm-assimilate-new-messages t))
1685 (setq r-list retrieve-list)
1687 (vm-set-imap-uid-of (car mp) (car (car r-list)))
1688 (vm-set-imap-uid-validity-of (car mp) uid-validity)
1690 (vm-imap-get-message-flags process (car mp) t)
1692 (vm-set-stuff-flag-of (car mp) t)
1694 r-list (cdr r-list))))))
1696 (let ((mp vm-message-list)
1697 (perm-flags (vm-folder-imap-permanent-flags)))
1699 (if (not (vm-attribute-modflag-of (car mp)))
1702 (vm-imap-store-message-flags process (car mp) perm-flags)
1704 (setq mp (cdr mp)))))
1705 (if do-local-expunges
1706 (vm-expunge-folder t t local-expunge-list))
1707 (if (and do-remote-expunges
1708 vm-imap-messages-to-expunge)
1709 (let ((process (vm-folder-imap-process)))
1710 (if (and (processp process)
1711 (memq (process-status process) '(open run)))
1712 (vm-imap-end-session process))
1713 (setq vm-imap-retrieved-messages
1714 (mapcar (function (lambda (x) (list (car x) (cdr x)
1716 vm-imap-messages-to-expunge))
1717 (vm-expunge-imap-messages)
1718 (setq vm-imap-messages-to-expunge
1719 (mapcar (function (lambda (x) (cons (car x) (car (cdr x)))))
1720 vm-imap-retrieved-messages))))
1724 (defun vm-imap-folder-check-for-mail (&optional interactive)
1725 (if (or vm-global-block-new-mail
1726 (null (vm-establish-new-folder-imap-session interactive)))
1728 (let ((result (car (vm-imap-get-synchronization-data))))
1729 (vm-imap-end-session (vm-folder-imap-process))
1733 (defun vm-imap-find-spec-for-buffer (buffer)
1734 (let ((list vm-imap-server-list)
1736 (while (and (not done) list)
1737 (if (eq buffer (vm-get-file-buffer (vm-imap-make-filename-for-spec
1740 (setq list (cdr list))))
1741 (and list (car list))))
1744 (defun vm-imap-make-filename-for-spec (spec)
1746 (setq spec (vm-imap-normalize-spec spec))
1747 (setq md5 (vm-md5-string spec))
1748 (expand-file-name (concat "imap-cache-" md5)
1749 (or vm-imap-folder-cache-directory
1753 (defun vm-imap-normalize-spec (spec)
1755 (setq list (vm-imap-parse-spec-to-list spec))
1756 (setcar (vm-last list) "*")
1757 (setcar list "imap")
1758 (setcar (nthcdr 2 list) "*")
1759 (setcar (nthcdr 4 list) "*")
1760 (setq spec (mapconcat (function identity) list ":"))
1764 (defun vm-imap-parse-spec-to-list (spec)
1765 (vm-parse spec "\\([^:]+\\):?" 1 6))
1767 (defun vm-imap-spec-list-to-host-alist (spec-list)
1770 (setq host-alist (cons
1772 (nth 1 (vm-imap-parse-spec-to-list (car spec-list)))
1775 spec-list (cdr spec-list)))
1779 (defun vm-read-imap-folder-name (prompt spec-list &optional selectable-only newone)
1780 "Read an IMAP server and mailbox, return an IMAP mailbox spec."
1781 (let (host c-list spec process mailbox list
1782 (vm-imap-ok-to-ask t)
1783 (host-alist (vm-imap-spec-list-to-host-alist spec-list)))
1784 (if (null host-alist)
1785 (error "No known IMAP servers. Please set vm-imap-server-list."))
1786 (setq host (if (cdr host-alist)
1787 (completing-read "IMAP server: " host-alist nil t)
1788 (car (car host-alist)))
1789 spec (cdr (assoc host host-alist))
1790 process (vm-imap-make-session spec)
1791 c-list (and process (vm-imap-mailbox-list process selectable-only)))
1792 (vm-imap-end-session process)
1794 (completing-read prompt (mapcar (lambda (c) (list c)) c-list)
1796 (setq list (vm-imap-parse-spec-to-list spec))
1797 (setcar (nthcdr 3 list) mailbox)
1798 (mapconcat 'identity list ":")))
1800 (defun vm-imap-directory-separator (process ref)
1802 sep p r response need-ok)
1803 (vm-imap-check-connection process)
1805 (set-buffer (process-buffer process))
1806 (vm-imap-send-command process (format "LIST %s \"\""
1807 (vm-imap-quote-string ref)))
1810 (setq response (vm-imap-read-response process))
1811 (if (vm-imap-response-matches response 'VM 'NO)
1812 (error "server said NO to LIST"))
1813 (if (vm-imap-response-matches response 'VM 'BAD)
1814 (vm-imap-protocol-error "server said BAD to LIST"))
1815 (cond ((vm-imap-response-matches response 'VM 'OK)
1817 ((vm-imap-response-matches response '* 'LIST 'list 'string)
1818 (setq r (nthcdr 3 response)
1820 sep (buffer-substring (nth 1 p) (nth 2 p))))
1821 ((vm-imap-response-matches response '* 'LIST 'list)
1822 (vm-imap-protocol-error "unexpedcted LIST response"))))
1825 (defun vm-imap-mailbox-list (process selectable-only)
1827 p r response need-ok)
1828 (vm-imap-check-connection process)
1830 (set-buffer (process-buffer process))
1831 (vm-imap-send-command process "LIST \"\" \"*\"")
1834 (setq response (vm-imap-read-response process))
1835 (if (vm-imap-response-matches response 'VM 'NO)
1836 (error "server said NO to LIST"))
1837 (if (vm-imap-response-matches response 'VM 'BAD)
1838 (vm-imap-protocol-error "server said BAD to LIST"))
1839 (if (vm-imap-response-matches response '* 'BYE)
1840 (vm-imap-protocol-error "server said BYE to LIST"))
1841 (cond ((vm-imap-response-matches response 'VM 'OK)
1843 ((vm-imap-response-matches response '* 'LIST 'list)
1844 (setq r (nthcdr 2 response)
1846 (if (and selectable-only
1847 (vm-imap-scan-list-for-flag p "\\Noselect"))
1849 (setq r (nthcdr 4 response)
1851 (if (memq (car p) '(atom string))
1852 (setq c-list (cons (buffer-substring (nth 1 p) (nth 2 p))
1856 (defun vm-imap-read-boolean-response (process)
1857 (let ((need-ok t) retval response)
1859 (vm-imap-check-connection process)
1860 (setq response (vm-imap-read-response process))
1861 (cond ((vm-imap-response-matches response 'VM 'OK)
1862 (setq need-ok nil retval t))
1863 ((vm-imap-response-matches response 'VM 'NO)
1864 (setq need-ok nil retval nil))
1865 ((vm-imap-response-matches response '* 'BYE)
1866 (vm-imap-protocol-error "server said BYE"))
1867 ((vm-imap-response-matches response 'VM 'BAD)
1868 (vm-imap-protocol-error "server said BAD"))))
1871 (defun vm-imap-create-mailbox (process mailbox
1872 &optional dont-create-parent-directories)
1873 (if (not dont-create-parent-directories)
1874 (let (dir sep sep-regexp i)
1875 (setq sep (vm-imap-directory-separator process "")
1876 sep-regexp (regexp-quote sep)
1878 (while (string-match sep-regexp mailbox i)
1879 (setq dir (substring mailbox i (match-end 0)))
1880 (vm-imap-create-mailbox process dir t)
1881 ;; ignore command result since creating a directory will
1882 ;; routinely fail with "File exists". We'll generate a
1883 ;; real error if the final mailbox creation fails.
1884 (vm-imap-read-boolean-response process)
1885 (setq i (match-end 0)))))
1886 (vm-imap-send-command process (format "CREATE %s"
1887 (vm-imap-quote-string mailbox)))
1888 (if (null (vm-imap-read-boolean-response process))
1889 (error "IMAP CREATE of %s failed" mailbox)))
1891 (defun vm-imap-delete-mailbox (process mailbox)
1892 (vm-imap-send-command process (format "DELETE %s"
1893 (vm-imap-quote-string mailbox)))
1894 (if (null (vm-imap-read-boolean-response process))
1895 (error "IMAP DELETE of %s failed" mailbox)))
1897 (defun vm-imap-rename-mailbox (process source dest)
1898 (vm-imap-send-command process (format "RENAME %s %s"
1899 (vm-imap-quote-string source)
1900 (vm-imap-quote-string dest)))
1901 (if (null (vm-imap-read-boolean-response process))
1902 (error "IMAP RENAME of %s to %s failed" source dest)))
1905 (defun vm-create-imap-folder (folder)
1906 "Create a folder on an IMAP server.
1907 First argument FOLDER is read from the minibuffer if called
1908 interactively. Non-interactive callers must provide an IMAP
1909 maildrop specification for the folder as described in the
1910 documentation for `vm-spool-files'."
1913 (vm-session-initialization)
1914 (vm-check-for-killed-folder)
1915 (vm-select-folder-buffer-if-possible)
1916 (let ((this-command this-command)
1917 (last-command last-command))
1918 (list (vm-read-imap-folder-name "Create IMAP folder: "
1919 vm-imap-server-list nil t)))))
1920 (let ((vm-imap-ok-to-ask t)
1923 (setq process (vm-imap-make-session folder))
1925 (error "Couldn't open IMAP session for %s"
1926 (vm-safe-imapdrop-string folder)))
1927 (set-buffer (process-buffer process))
1928 (setq mailbox (nth 3 (vm-imap-parse-spec-to-list folder)))
1929 (vm-imap-create-mailbox process mailbox t)
1930 (message "Folder %s created" (vm-safe-imapdrop-string folder)))))
1933 (defun vm-delete-imap-folder (folder)
1934 "Delete a folder on an IMAP server.
1935 First argument FOLDER is read from the minibuffer if called
1936 interactively. Non-interactive callers must provide an IMAP
1937 maildrop specification for the folder as described in the
1938 documentation for `vm-spool-files'."
1941 (vm-session-initialization)
1942 (vm-check-for-killed-folder)
1943 (vm-select-folder-buffer-if-possible)
1944 (let ((this-command this-command)
1945 (last-command last-command))
1946 (list (vm-read-imap-folder-name "Delete IMAP folder: "
1947 vm-imap-server-list nil)))))
1948 (let ((vm-imap-ok-to-ask t)
1950 (setq process (vm-imap-make-session folder))
1952 (error "Couldn't open IMAP session for %s"
1953 (vm-safe-imapdrop-string folder)))
1955 (set-buffer (process-buffer process))
1956 (setq mailbox (nth 3 (vm-imap-parse-spec-to-list folder)))
1957 (vm-imap-delete-mailbox process mailbox)
1958 (message "Folder %s deleted" (vm-safe-imapdrop-string folder)))))
1961 (defun vm-rename-imap-folder (source dest)
1962 "Rename a folder on an IMAP server.
1963 Argument SOURCE and DEST are read from the minibuffer if called
1964 interactively. Non-interactive callers must provide full IMAP
1965 maildrop specifications for SOURCE and DEST as described in the
1966 documentation for `vm-spool-files'."
1969 (vm-session-initialization)
1970 (vm-check-for-killed-folder)
1971 (vm-select-folder-buffer-if-possible)
1972 (let ((this-command this-command)
1973 (last-command last-command)
1975 (setq source (vm-read-imap-folder-name "Rename IMAP folder: "
1976 vm-imap-server-list t))
1977 (setq dest (vm-read-imap-folder-name
1978 (format "Rename %s to: " (vm-safe-imapdrop-string source))
1980 (list source dest))))
1981 (let ((vm-imap-ok-to-ask t)
1982 process mailbox-source mailbox-dest)
1983 (setq process (vm-imap-make-session source))
1985 (error "Couldn't open IMAP session for %s"
1986 (vm-safe-imapdrop-string source)))
1988 (set-buffer (process-buffer process))
1989 (setq mailbox-source (nth 3 (vm-imap-parse-spec-to-list source)))
1990 (setq mailbox-dest (nth 3 (vm-imap-parse-spec-to-list dest)))
1991 (vm-imap-rename-mailbox process mailbox-source mailbox-dest)
1992 (message "Folder %s renamed to %s" (vm-safe-imapdrop-string source)
1993 (vm-safe-imapdrop-string dest)))))
1995 ;;; Robert Fenk's draft function for saving messages to IMAP folders.
1996 (defun vm-imap-save-composition ()
1997 "Saves the current composition in the IMAP folder given by the IMAP-FCC header.
1999 Add this to your `mail-send-hook' and start composing from an IMAP folder."
2000 (let (process flags response string
2001 (mailbox (vm-mail-get-header-contents "IMAP-FCC:")))
2004 (vm-select-folder-buffer)
2005 (vm-establish-new-folder-imap-session)
2006 (setq process (vm-folder-imap-process)))
2008 (vm-mail-mode-remove-header "IMAP-FCC:")
2009 (goto-char (point-min))
2010 (re-search-forward (concat "^" (regexp-quote mail-header-separator) "$"))
2011 (setq string (concat (buffer-substring (point-min) (match-beginning 0))
2013 (match-end 0) (point-max))))
2014 ;; this can go awry if the process has died...
2015 (set-buffer (process-buffer process))
2017 (vm-imap-create-mailbox process mailbox)
2019 (vm-imap-send-command process
2020 (format "APPEND %s %s {%d}"
2021 (vm-imap-quote-string mailbox)
2022 (if flags flags "()")
2024 ;; could these be done with vm-imap-read-boolean-response?
2025 (let ((need-plus t))
2027 (let ((response (vm-imap-read-response process)))
2028 (cond ((vm-imap-response-matches response 'VM 'NO)
2029 (error "server said NO to APPEND command"))
2030 ((vm-imap-response-matches response 'VM 'BAD)
2031 (vm-imap-protocol-error "server said BAD to APPEND command"))
2032 ((vm-imap-response-matches response '* 'BYE)
2033 (vm-imap-protocol-error "server said BYE to APPEND command"))
2034 ((vm-imap-response-matches response '+)
2035 (setq need-plus nil))))))
2036 (vm-imap-send-command process string nil t)
2039 (let ((response (vm-imap-read-response process)))
2040 (cond ((vm-imap-response-matches response 'VM 'NO)
2041 (error "server said NO to APPEND data"))
2042 ((vm-imap-response-matches response 'VM 'BAD)
2043 (vm-imap-protocol-error "server said BAD to APPEND data"))
2044 ((vm-imap-response-matches response '* 'BYE)
2045 (vm-imap-protocol-error "server said BYE to APPEND data"))
2046 ((vm-imap-response-matches response 'VM 'OK)
2047 (setq need-ok nil))))))
2048 (when (and (processp process)
2049 (memq (process-status process) '(open run)))
2050 (vm-imap-end-session process))
2055 ;;; vm-imap.el ends here