Revision: miles@gnu.org--gnu-2004/emacs--cvs-trunk--0--patch-615
[bpt/emacs.git] / lisp / gnus / imap.el
CommitLineData
c113de23 1;;; imap.el --- imap library
23f87bed 2;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003
c113de23
GM
3;; Free Software Foundation, Inc.
4
5;; Author: Simon Josefsson <jas@pdc.kth.se>
6;; Keywords: mail
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 2, or (at your option)
13;; any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs; see the file COPYING. If not, write to the
22;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23;; Boston, MA 02111-1307, USA.
24
25;;; Commentary:
26
27;; imap.el is a elisp library providing an interface for talking to
28;; IMAP servers.
29;;
30;; imap.el is roughly divided in two parts, one that parses IMAP
31;; responses from the server and storing data into buffer-local
32;; variables, and one for utility functions which send commands to
33;; server, waits for an answer, and return information. The latter
34;; part is layered on top of the previous.
35;;
36;; The imap.el API consist of the following functions, other functions
37;; in this file should not be called directly and the result of doing
38;; so are at best undefined.
39;;
40;; Global commands:
41;;
42;; imap-open, imap-opened, imap-authenticate, imap-close,
43;; imap-capability, imap-namespace, imap-error-text
44;;
45;; Mailbox commands:
46;;
738421d1 47;; imap-mailbox-get, imap-mailbox-map, imap-current-mailbox,
c113de23
GM
48;; imap-current-mailbox-p, imap-search, imap-mailbox-select,
49;; imap-mailbox-examine, imap-mailbox-unselect, imap-mailbox-expunge
50;; imap-mailbox-close, imap-mailbox-create, imap-mailbox-delete
51;; imap-mailbox-rename, imap-mailbox-lsub, imap-mailbox-list
52;; imap-mailbox-subscribe, imap-mailbox-unsubscribe, imap-mailbox-status
53;; imap-mailbox-acl-get, imap-mailbox-acl-set, imap-mailbox-acl-delete
54;;
55;; Message commands:
56;;
57;; imap-fetch-asynch, imap-fetch,
58;; imap-current-message, imap-list-to-message-set,
59;; imap-message-get, imap-message-map
738421d1 60;; imap-message-envelope-date, imap-message-envelope-subject,
c113de23
GM
61;; imap-message-envelope-from, imap-message-envelope-sender,
62;; imap-message-envelope-reply-to, imap-message-envelope-to,
63;; imap-message-envelope-cc, imap-message-envelope-bcc
64;; imap-message-envelope-in-reply-to, imap-message-envelope-message-id
65;; imap-message-body, imap-message-flag-permanent-p
66;; imap-message-flags-set, imap-message-flags-del
67;; imap-message-flags-add, imap-message-copyuid
68;; imap-message-copy, imap-message-appenduid
69;; imap-message-append, imap-envelope-from
70;; imap-body-lines
71;;
72;; It is my hope that theese commands should be pretty self
73;; explanatory for someone that know IMAP. All functions have
74;; additional documentation on how to invoke them.
75;;
76;; imap.el support RFC1730/2060 (IMAP4/IMAP4rev1), implemented IMAP
77;; extensions are RFC2195 (CRAM-MD5), RFC2086 (ACL), RFC2342
a2617484
DL
78;; (NAMESPACE), RFC2359 (UIDPLUS), the IMAP-part of RFC2595 (STARTTLS,
79;; LOGINDISABLED) (with use of external library starttls.el and
80;; program starttls) and the GSSAPI / kerberos V4 sections of RFC1731
81;; (with use of external program `imtest'). It also take advantage
82;; the UNSELECT extension in Cyrus IMAPD.
c113de23
GM
83;;
84;; Without the work of John McClary Prevost and Jim Radford this library
85;; would not have seen the light of day. Many thanks.
86;;
87;; This is a transcript of short interactive session for demonstration
88;; purposes.
89;;
90;; (imap-open "my.mail.server")
91;; => " *imap* my.mail.server:0"
92;;
93;; The rest are invoked with current buffer as the buffer returned by
94;; `imap-open'. It is possible to do all without this, but it would
95;; look ugly here since `buffer' is always the last argument for all
96;; imap.el API functions.
97;;
98;; (imap-authenticate "myusername" "mypassword")
99;; => auth
100;;
101;; (imap-mailbox-lsub "*")
102;; => ("INBOX.sentmail" "INBOX.private" "INBOX.draft" "INBOX.spam")
103;;
104;; (imap-mailbox-list "INBOX.n%")
105;; => ("INBOX.namedroppers" "INBOX.nnimap" "INBOX.ntbugtraq")
106;;
107;; (imap-mailbox-select "INBOX.nnimap")
108;; => "INBOX.nnimap"
109;;
110;; (imap-mailbox-get 'exists)
111;; => 166
112;;
113;; (imap-mailbox-get 'uidvalidity)
114;; => "908992622"
115;;
116;; (imap-search "FLAGGED SINCE 18-DEC-98")
117;; => (235 236)
118;;
119;; (imap-fetch 235 "RFC822.PEEK" 'RFC822)
120;; => "X-Sieve: cmu-sieve 1.3^M\nX-Username: <jas@pdc.kth.se>^M\r...."
121;;
122;; Todo:
738421d1 123;;
c113de23
GM
124;; o Parse UIDs as strings? We need to overcome the 28 bit limit somehow.
125;; o Don't use `read' at all (important places already fixed)
126;; o Accept list of articles instead of message set string in most
127;; imap-message-* functions.
23f87bed 128;; o Send strings as literal if they contain, e.g., ".
c113de23
GM
129;;
130;; Revision history:
131;;
132;; - 19991218 added starttls/digest-md5 patch,
133;; by Daiki Ueno <ueno@ueda.info.waseda.ac.jp>
134;; NB! you need SLIM for starttls.el and digest-md5.el
135;; - 19991023 commited to pgnus
136;;
137
138;;; Code:
139
140(eval-when-compile (require 'cl))
141(eval-and-compile
c113de23
GM
142 (autoload 'base64-decode-string "base64")
143 (autoload 'base64-encode-string "base64")
144 (autoload 'starttls-open-stream "starttls")
145 (autoload 'starttls-negotiate "starttls")
146 (autoload 'digest-md5-parse-digest-challenge "digest-md5")
147 (autoload 'digest-md5-digest-response "digest-md5")
148 (autoload 'digest-md5-digest-uri "digest-md5")
149 (autoload 'digest-md5-challenge "digest-md5")
150 (autoload 'rfc2104-hash "rfc2104")
151 (autoload 'md5 "md5")
152 (autoload 'utf7-encode "utf7")
153 (autoload 'utf7-decode "utf7")
154 (autoload 'format-spec "format-spec")
2d5fdf63 155 (autoload 'format-spec-make "format-spec")
23f87bed 156 (autoload 'open-tls-stream "tls")
2d5fdf63
DL
157 ;; Avoid use gnus-point-at-eol so we're independent of Gnus. These
158 ;; days we have point-at-eol anyhow.
159 (if (fboundp 'point-at-eol)
160 (defalias 'imap-point-at-eol 'point-at-eol)
161 (defun imap-point-at-eol ()
162 (save-excursion
163 (end-of-line)
164 (point)))))
c113de23
GM
165
166;; User variables.
167
168(defgroup imap nil
169 "Low-level IMAP issues."
170 :version "21.1"
171 :group 'mail)
172
173(defcustom imap-kerberos4-program '("imtest -m kerberos_v4 -u %l -p %p %s"
174 "imtest -kp %s %p")
175 "List of strings containing commands for Kerberos 4 authentication.
176%s is replaced with server hostname, %p with port to connect to, and
177%l with the value of `imap-default-user'. The program should accept
178IMAP commands on stdin and return responses to stdout. Each entry in
179the list is tried until a successful connection is made."
180 :group 'imap
181 :type '(repeat string))
182
23f87bed
MB
183(defcustom imap-gssapi-program (list
184 (concat "gsasl --client --connect %s:%p "
185 "--imap --application-data "
186 "--mechanism GSSAPI "
187 "--authentication-id %l")
188 "imtest -m gssapi -u %l -p %p %s")
c113de23
GM
189 "List of strings containing commands for GSSAPI (krb5) authentication.
190%s is replaced with server hostname, %p with port to connect to, and
191%l with the value of `imap-default-user'. The program should accept
192IMAP commands on stdin and return responses to stdout. Each entry in
193the list is tried until a successful connection is made."
194 :group 'imap
195 :type '(repeat string))
196
f9936da6
SZ
197(defcustom imap-ssl-program '("openssl s_client -quiet -ssl3 -connect %s:%p"
198 "openssl s_client -quiet -ssl2 -connect %s:%p"
199 "s_client -quiet -ssl3 -connect %s:%p"
200 "s_client -quiet -ssl2 -connect %s:%p")
c113de23
GM
201 "A string, or list of strings, containing commands for SSL connections.
202Within a string, %s is replaced with the server address and %p with
203port number on server. The program should accept IMAP commands on
204stdin and return responses to stdout. Each entry in the list is tried
205until a successful connection is made."
206 :group 'imap
207 :type '(choice string
208 (repeat string)))
209
210(defcustom imap-shell-program '("ssh %s imapd"
211 "rsh %s imapd"
212 "ssh %g ssh %s imapd"
213 "rsh %g rsh %s imapd")
214 "A list of strings, containing commands for IMAP connection.
215Within a string, %s is replaced with the server address, %p with port
216number on server, %g with `imap-shell-host', and %l with
217`imap-default-user'. The program should read IMAP commands from stdin
218and write IMAP response to stdout. Each entry in the list is tried
219until a successful connection is made."
220 :group 'imap
221 :type '(repeat string))
222
23f87bed
MB
223(defcustom imap-process-connection-type nil
224 "*Value for `process-connection-type' to use for Kerberos4, GSSAPI and SSL.
225The `process-connection-type' variable control type of device
226used to communicate with subprocesses. Values are nil to use a
227pipe, or t or `pty' to use a pty. The value has no effect if the
228system has no ptys or if all ptys are busy: then a pipe is used
229in any case. The value takes effect when a IMAP server is
a08b59c9
MB
230opened, changing it after that has no effect."
231 :version "21.4"
23f87bed
MB
232 :group 'imap
233 :type 'boolean)
c113de23 234
23f87bed
MB
235(defcustom imap-use-utf7 t
236 "If non-nil, do utf7 encoding/decoding of mailbox names.
237Since the UTF7 decoding currently only decodes into ISO-8859-1
238characters, you may disable this decoding if you need to access UTF7
239encoded mailboxes which doesn't translate into ISO-8859-1."
240 :group 'imap
241 :type 'boolean)
c113de23 242
23f87bed
MB
243(defcustom imap-log nil
244 "If non-nil, a imap session trace is placed in *imap-log* buffer."
245 :group 'imap
246 :type 'boolean)
247
248(defcustom imap-debug nil
249 "If non-nil, random debug spews are placed in *imap-debug* buffer."
250 :group 'imap
251 :type 'boolean)
252
253(defcustom imap-shell-host "gateway"
254 "Hostname of rlogin proxy."
255 :group 'imap
256 :type 'string)
257
258(defcustom imap-default-user (user-login-name)
259 "Default username to use."
260 :group 'imap
261 :type 'string)
262
263(defcustom imap-read-timeout (if (string-match
264 "windows-nt\\|os/2\\|emx\\|cygwin"
265 (symbol-name system-type))
266 1.0
267 0.1)
268 "*How long to wait between checking for the end of output.
269Shorter values mean quicker response, but is more CPU intensive."
270 :type 'number
271 :group 'imap)
c113de23
GM
272
273;; Various variables.
274
275(defvar imap-fetch-data-hook nil
276 "Hooks called after receiving each FETCH response.")
277
23f87bed 278(defvar imap-streams '(gssapi kerberos4 starttls tls ssl network shell)
c113de23
GM
279 "Priority of streams to consider when opening connection to server.")
280
281(defvar imap-stream-alist
282 '((gssapi imap-gssapi-stream-p imap-gssapi-open)
283 (kerberos4 imap-kerberos4-stream-p imap-kerberos4-open)
23f87bed 284 (tls imap-tls-p imap-tls-open)
c113de23
GM
285 (ssl imap-ssl-p imap-ssl-open)
286 (network imap-network-p imap-network-open)
287 (shell imap-shell-p imap-shell-open)
288 (starttls imap-starttls-p imap-starttls-open))
289 "Definition of network streams.
290
72e97196 291\(NAME CHECK OPEN)
c113de23
GM
292
293NAME names the stream, CHECK is a function returning non-nil if the
23f87bed 294server support the stream and OPEN is a function for opening the
c113de23
GM
295stream.")
296
738421d1 297(defvar imap-authenticators '(gssapi
c113de23
GM
298 kerberos4
299 digest-md5
300 cram-md5
301 login
302 anonymous)
303 "Priority of authenticators to consider when authenticating to server.")
304
738421d1 305(defvar imap-authenticator-alist
c113de23
GM
306 '((gssapi imap-gssapi-auth-p imap-gssapi-auth)
307 (kerberos4 imap-kerberos4-auth-p imap-kerberos4-auth)
308 (cram-md5 imap-cram-md5-p imap-cram-md5-auth)
309 (login imap-login-p imap-login-auth)
310 (anonymous imap-anonymous-p imap-anonymous-auth)
311 (digest-md5 imap-digest-md5-p imap-digest-md5-auth))
312 "Definition of authenticators.
313
72e97196 314\(NAME CHECK AUTHENTICATE)
c113de23
GM
315
316NAME names the authenticator. CHECK is a function returning non-nil if
317the server support the authenticator and AUTHENTICATE is a function
72e97196 318for doing the actual authentication.")
c113de23 319
23f87bed
MB
320(defvar imap-error nil
321 "Error codes from the last command.")
c113de23
GM
322
323;; Internal constants. Change theese and die.
324
325(defconst imap-default-port 143)
326(defconst imap-default-ssl-port 993)
23f87bed 327(defconst imap-default-tls-port 993)
c113de23
GM
328(defconst imap-default-stream 'network)
329(defconst imap-coding-system-for-read 'binary)
330(defconst imap-coding-system-for-write 'binary)
331(defconst imap-local-variables '(imap-server
332 imap-port
333 imap-client-eol
334 imap-server-eol
335 imap-auth
336 imap-stream
337 imap-username
338 imap-password
339 imap-current-mailbox
340 imap-current-target-mailbox
341 imap-message-data
342 imap-capability
343 imap-namespace
344 imap-state
345 imap-reached-tag
346 imap-failed-tags
347 imap-tag
348 imap-process
349 imap-calculate-literal-size-first
350 imap-mailbox-data))
23f87bed
MB
351(defconst imap-log-buffer "*imap-log*")
352(defconst imap-debug-buffer "*imap-debug*")
c113de23
GM
353
354;; Internal variables.
355
356(defvar imap-stream nil)
357(defvar imap-auth nil)
358(defvar imap-server nil)
359(defvar imap-port nil)
360(defvar imap-username nil)
361(defvar imap-password nil)
362(defvar imap-calculate-literal-size-first nil)
738421d1 363(defvar imap-state 'closed
c113de23
GM
364 "IMAP state.
365Valid states are `closed', `initial', `nonauth', `auth', `selected'
366and `examine'.")
367
368(defvar imap-server-eol "\r\n"
369 "The EOL string sent from the server.")
370
371(defvar imap-client-eol "\r\n"
372 "The EOL string we send to the server.")
373
374(defvar imap-current-mailbox nil
375 "Current mailbox name.")
376
377(defvar imap-current-target-mailbox nil
378 "Current target mailbox for COPY and APPEND commands.")
379
380(defvar imap-mailbox-data nil
381 "Obarray with mailbox data.")
382
383(defvar imap-mailbox-prime 997
384 "Length of imap-mailbox-data.")
385
386(defvar imap-current-message nil
387 "Current message number.")
388
389(defvar imap-message-data nil
390 "Obarray with message data.")
391
392(defvar imap-message-prime 997
393 "Length of imap-message-data.")
394
395(defvar imap-capability nil
396 "Capability for server.")
397
398(defvar imap-namespace nil
399 "Namespace for current server.")
400
401(defvar imap-reached-tag 0
402 "Lower limit on command tags that have been parsed.")
403
738421d1 404(defvar imap-failed-tags nil
c113de23
GM
405 "Alist of tags that failed.
406Each element is a list with four elements; tag (a integer), response
407state (a symbol, `OK', `NO' or `BAD'), response code (a string), and
408human readable response text (a string).")
409
410(defvar imap-tag 0
411 "Command tag number.")
412
413(defvar imap-process nil
414 "Process.")
415
416(defvar imap-continuation nil
417 "Non-nil indicates that the server emitted a continuation request.
72e97196 418The actual value is really the text on the continuation line.")
c113de23 419
23f87bed
MB
420(defvar imap-callbacks nil
421 "List of response tags and callbacks, on the form `(number . function)'.
422The function should take two arguments, the first the IMAP tag and the
423second the status (OK, NO, BAD etc) of the command.")
c113de23
GM
424
425\f
426;; Utility functions:
427
23f87bed
MB
428(defun imap-remassoc (key alist)
429 "Delete by side effect any elements of LIST whose car is `equal' to KEY.
430The modified LIST is returned. If the first member
431of LIST has a car that is `equal' to KEY, there is no way to remove it
432by side effect; therefore, write `(setq foo (remassoc key foo))' to be
433sure of changing the value of `foo'."
434 (when alist
435 (if (equal key (caar alist))
436 (cdr alist)
437 (setcdr alist (imap-remassoc key (cdr alist)))
438 alist)))
439
c113de23
GM
440(defsubst imap-disable-multibyte ()
441 "Enable multibyte in the current buffer."
442 (when (fboundp 'set-buffer-multibyte)
443 (set-buffer-multibyte nil)))
444
c113de23
GM
445(defsubst imap-utf7-encode (string)
446 (if imap-use-utf7
447 (and string
448 (condition-case ()
449 (utf7-encode string t)
738421d1 450 (error (message
c113de23
GM
451 "imap: Could not UTF7 encode `%s', using it unencoded..."
452 string)
453 string)))
454 string))
455
456(defsubst imap-utf7-decode (string)
457 (if imap-use-utf7
458 (and string
459 (condition-case ()
460 (utf7-decode string t)
461 (error (message
462 "imap: Could not UTF7 decode `%s', using it undecoded..."
463 string)
464 string)))
465 string))
466
467(defsubst imap-ok-p (status)
468 (if (eq status 'OK)
469 t
470 (setq imap-error status)
471 nil))
472
473(defun imap-error-text (&optional buffer)
474 (with-current-buffer (or buffer (current-buffer))
475 (nth 3 (car imap-failed-tags))))
476
477\f
478;; Server functions; stream stuff:
479
480(defun imap-kerberos4-stream-p (buffer)
481 (imap-capability 'AUTH=KERBEROS_V4 buffer))
482
483(defun imap-kerberos4-open (name buffer server port)
484 (let ((cmds imap-kerberos4-program)
485 cmd done)
486 (while (and (not done) (setq cmd (pop cmds)))
487 (message "Opening Kerberos 4 IMAP connection with `%s'..." cmd)
488 (erase-buffer)
489 (let* ((port (or port imap-default-port))
490 (coding-system-for-read imap-coding-system-for-read)
491 (coding-system-for-write imap-coding-system-for-write)
23f87bed 492 (process-connection-type imap-process-connection-type)
738421d1 493 (process (start-process
c113de23
GM
494 name buffer shell-file-name shell-command-switch
495 (format-spec
496 cmd
497 (format-spec-make
498 ?s server
499 ?p (number-to-string port)
500 ?l imap-default-user))))
501 response)
502 (when process
503 (with-current-buffer buffer
504 (setq imap-client-eol "\n"
505 imap-calculate-literal-size-first t)
506 (while (and (memq (process-status process) '(open run))
23f87bed 507 (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
c113de23 508 (goto-char (point-min))
23f87bed
MB
509 ;; Athena IMTEST can output SSL verify errors
510 (or (while (looking-at "^verify error:num=")
511 (forward-line))
512 t)
513 (or (while (looking-at "^TLS connection established")
514 (forward-line))
515 t)
516 ;; cyrus 1.6.x (13? < x <= 22) queries capabilities
517 (or (while (looking-at "^C:")
c113de23
GM
518 (forward-line))
519 t)
520 ;; cyrus 1.6 imtest print "S: " before server greeting
521 (or (not (looking-at "S: "))
522 (forward-char 3)
523 t)
524 (not (and (imap-parse-greeting)
525 ;; success in imtest < 1.6:
526 (or (re-search-forward
527 "^__\\(.*\\)__\n" nil t)
528 ;; success in imtest 1.6:
529 (re-search-forward
530 "^\\(Authenticat.*\\)" nil t))
531 (setq response (match-string 1)))))
532 (accept-process-output process 1)
533 (sit-for 1))
534 (and imap-log
23f87bed 535 (with-current-buffer (get-buffer-create imap-log-buffer)
c113de23
GM
536 (imap-disable-multibyte)
537 (buffer-disable-undo)
538 (goto-char (point-max))
539 (insert-buffer-substring buffer)))
540 (erase-buffer)
a2617484
DL
541 (message "Opening Kerberos 4 IMAP connection with `%s'...%s" cmd
542 (if response (concat "done, " response) "failed"))
c113de23
GM
543 (if (and response (let ((case-fold-search nil))
544 (not (string-match "failed" response))))
545 (setq done process)
546 (if (memq (process-status process) '(open run))
23f87bed 547 (imap-send-command "LOGOUT"))
c113de23
GM
548 (delete-process process)
549 nil)))))
550 done))
738421d1 551
c113de23
GM
552(defun imap-gssapi-stream-p (buffer)
553 (imap-capability 'AUTH=GSSAPI buffer))
554
555(defun imap-gssapi-open (name buffer server port)
556 (let ((cmds imap-gssapi-program)
557 cmd done)
558 (while (and (not done) (setq cmd (pop cmds)))
559 (message "Opening GSSAPI IMAP connection with `%s'..." cmd)
23f87bed 560 (erase-buffer)
c113de23
GM
561 (let* ((port (or port imap-default-port))
562 (coding-system-for-read imap-coding-system-for-read)
563 (coding-system-for-write imap-coding-system-for-write)
23f87bed 564 (process-connection-type imap-process-connection-type)
738421d1 565 (process (start-process
c113de23
GM
566 name buffer shell-file-name shell-command-switch
567 (format-spec
568 cmd
569 (format-spec-make
570 ?s server
571 ?p (number-to-string port)
572 ?l imap-default-user))))
573 response)
574 (when process
575 (with-current-buffer buffer
23f87bed
MB
576 (setq imap-client-eol "\n"
577 imap-calculate-literal-size-first t)
c113de23 578 (while (and (memq (process-status process) '(open run))
23f87bed 579 (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
c113de23 580 (goto-char (point-min))
23f87bed
MB
581 ;; cyrus 1.6.x (13? < x <= 22) queries capabilities
582 (or (while (looking-at "^C:")
c113de23
GM
583 (forward-line))
584 t)
585 ;; cyrus 1.6 imtest print "S: " before server greeting
586 (or (not (looking-at "S: "))
587 (forward-char 3)
588 t)
589 (not (and (imap-parse-greeting)
590 ;; success in imtest 1.6:
591 (re-search-forward
23f87bed
MB
592 (concat "^\\(\\(Authenticat.*\\)\\|\\("
593 "Client authentication "
594 "finished.*\\)\\)")
595 nil t)
c113de23
GM
596 (setq response (match-string 1)))))
597 (accept-process-output process 1)
598 (sit-for 1))
599 (and imap-log
23f87bed 600 (with-current-buffer (get-buffer-create imap-log-buffer)
c113de23
GM
601 (imap-disable-multibyte)
602 (buffer-disable-undo)
603 (goto-char (point-max))
604 (insert-buffer-substring buffer)))
605 (erase-buffer)
606 (message "GSSAPI IMAP connection: %s" (or response "failed"))
607 (if (and response (let ((case-fold-search nil))
608 (not (string-match "failed" response))))
609 (setq done process)
610 (if (memq (process-status process) '(open run))
23f87bed 611 (imap-send-command "LOGOUT"))
c113de23
GM
612 (delete-process process)
613 nil)))))
614 done))
615
616(defun imap-ssl-p (buffer)
617 nil)
618
619(defun imap-ssl-open (name buffer server port)
620 "Open a SSL connection to server."
621 (let ((cmds (if (listp imap-ssl-program) imap-ssl-program
622 (list imap-ssl-program)))
623 cmd done)
624 (while (and (not done) (setq cmd (pop cmds)))
625 (message "imap: Opening SSL connection with `%s'..." cmd)
23f87bed 626 (erase-buffer)
c113de23
GM
627 (let* ((port (or port imap-default-ssl-port))
628 (coding-system-for-read imap-coding-system-for-read)
629 (coding-system-for-write imap-coding-system-for-write)
f9936da6 630 (process-connection-type nil)
c113de23 631 process)
f9936da6 632 (when (progn
23f87bed 633 (setq process (start-process
f9936da6
SZ
634 name buffer shell-file-name
635 shell-command-switch
23f87bed 636 (format-spec cmd
f9936da6
SZ
637 (format-spec-make
638 ?s server
639 ?p (number-to-string port)))))
640 (process-kill-without-query process)
641 process)
c113de23
GM
642 (with-current-buffer buffer
643 (goto-char (point-min))
644 (while (and (memq (process-status process) '(open run))
f9936da6 645 (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
c113de23
GM
646 (goto-char (point-max))
647 (forward-line -1)
648 (not (imap-parse-greeting)))
649 (accept-process-output process 1)
650 (sit-for 1))
651 (and imap-log
23f87bed 652 (with-current-buffer (get-buffer-create imap-log-buffer)
c113de23
GM
653 (imap-disable-multibyte)
654 (buffer-disable-undo)
655 (goto-char (point-max))
656 (insert-buffer-substring buffer)))
657 (erase-buffer)
658 (when (memq (process-status process) '(open run))
659 (setq done process))))))
660 (if done
661 (progn
662 (message "imap: Opening SSL connection with `%s'...done" cmd)
663 done)
23f87bed 664 (message "imap: Opening SSL connection with `%s'...failed" cmd)
c113de23
GM
665 nil)))
666
23f87bed
MB
667(defun imap-tls-p (buffer)
668 nil)
669
670(defun imap-tls-open (name buffer server port)
671 (let* ((port (or port imap-default-tls-port))
672 (coding-system-for-read imap-coding-system-for-read)
673 (coding-system-for-write imap-coding-system-for-write)
674 (process (open-tls-stream name buffer server port)))
675 (when process
676 (while (and (memq (process-status process) '(open run))
677 (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
678 (goto-char (point-max))
679 (forward-line -1)
680 (not (imap-parse-greeting)))
681 (accept-process-output process 1)
682 (sit-for 1))
683 (and imap-log
684 (with-current-buffer (get-buffer-create imap-log-buffer)
685 (imap-disable-multibyte)
686 (buffer-disable-undo)
687 (goto-char (point-max))
688 (insert-buffer-substring buffer)))
689 (when (memq (process-status process) '(open run))
690 process))))
691
c113de23
GM
692(defun imap-network-p (buffer)
693 t)
694
695(defun imap-network-open (name buffer server port)
696 (let* ((port (or port imap-default-port))
697 (coding-system-for-read imap-coding-system-for-read)
698 (coding-system-for-write imap-coding-system-for-write)
699 (process (open-network-stream name buffer server port)))
700 (when process
701 (while (and (memq (process-status process) '(open run))
23f87bed 702 (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
c113de23
GM
703 (goto-char (point-min))
704 (not (imap-parse-greeting)))
705 (accept-process-output process 1)
706 (sit-for 1))
707 (and imap-log
23f87bed 708 (with-current-buffer (get-buffer-create imap-log-buffer)
c113de23
GM
709 (imap-disable-multibyte)
710 (buffer-disable-undo)
711 (goto-char (point-max))
712 (insert-buffer-substring buffer)))
713 (when (memq (process-status process) '(open run))
714 process))))
715
716(defun imap-shell-p (buffer)
717 nil)
718
719(defun imap-shell-open (name buffer server port)
23f87bed
MB
720 (let ((cmds (if (listp imap-shell-program) imap-shell-program
721 (list imap-shell-program)))
c113de23
GM
722 cmd done)
723 (while (and (not done) (setq cmd (pop cmds)))
724 (message "imap: Opening IMAP connection with `%s'..." cmd)
725 (setq imap-client-eol "\n")
726 (let* ((port (or port imap-default-port))
727 (coding-system-for-read imap-coding-system-for-read)
728 (coding-system-for-write imap-coding-system-for-write)
738421d1 729 (process (start-process
c113de23
GM
730 name buffer shell-file-name shell-command-switch
731 (format-spec
732 cmd
733 (format-spec-make
734 ?s server
735 ?g imap-shell-host
736 ?p (number-to-string port)
737 ?l imap-default-user)))))
738 (when process
739 (while (and (memq (process-status process) '(open run))
23f87bed
MB
740 (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
741 (goto-char (point-max))
742 (forward-line -1)
c113de23
GM
743 (not (imap-parse-greeting)))
744 (accept-process-output process 1)
745 (sit-for 1))
c113de23 746 (and imap-log
23f87bed 747 (with-current-buffer (get-buffer-create imap-log-buffer)
c113de23
GM
748 (imap-disable-multibyte)
749 (buffer-disable-undo)
750 (goto-char (point-max))
751 (insert-buffer-substring buffer)))
23f87bed 752 (erase-buffer)
c113de23
GM
753 (when (memq (process-status process) '(open run))
754 (setq done process)))))
755 (if done
756 (progn
757 (message "imap: Opening IMAP connection with `%s'...done" cmd)
758 done)
23f87bed 759 (message "imap: Opening IMAP connection with `%s'...failed" cmd)
c113de23
GM
760 nil)))
761
762(defun imap-starttls-p (buffer)
23f87bed 763 (imap-capability 'STARTTLS buffer))
c113de23
GM
764
765(defun imap-starttls-open (name buffer server port)
766 (let* ((port (or port imap-default-port))
767 (coding-system-for-read imap-coding-system-for-read)
768 (coding-system-for-write imap-coding-system-for-write)
a2617484 769 (process (starttls-open-stream name buffer server port))
23f87bed 770 done tls-info)
a2617484 771 (message "imap: Connecting with STARTTLS...")
c113de23
GM
772 (when process
773 (while (and (memq (process-status process) '(open run))
23f87bed
MB
774 (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
775 (goto-char (point-max))
776 (forward-line -1)
c113de23
GM
777 (not (imap-parse-greeting)))
778 (accept-process-output process 1)
779 (sit-for 1))
23f87bed
MB
780 (imap-send-command "STARTTLS")
781 (while (and (memq (process-status process) '(open run))
782 (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
783 (goto-char (point-max))
784 (forward-line -1)
785 (not (re-search-forward "[0-9]+ OK.*\r?\n" nil t)))
786 (accept-process-output process 1)
787 (sit-for 1))
c113de23 788 (and imap-log
23f87bed 789 (with-current-buffer (get-buffer-create imap-log-buffer)
c113de23
GM
790 (buffer-disable-undo)
791 (goto-char (point-max))
792 (insert-buffer-substring buffer)))
23f87bed
MB
793 (when (and (setq tls-info (starttls-negotiate process))
794 (memq (process-status process) '(open run)))
a2617484 795 (setq done process)))
23f87bed
MB
796 (if (stringp tls-info)
797 (message "imap: STARTTLS info: %s" tls-info))
798 (message "imap: Connecting with STARTTLS...%s" (if done "done" "failed"))
799 done))
738421d1 800
c113de23
GM
801;; Server functions; authenticator stuff:
802
803(defun imap-interactive-login (buffer loginfunc)
804 "Login to server in BUFFER.
805LOGINFUNC is passed a username and a password, it should return t if
8f688cb0 806it where successful authenticating itself to the server, nil otherwise.
c113de23
GM
807Returns t if login was successful, nil otherwise."
808 (with-current-buffer buffer
f78cebe3
SM
809 (make-local-variable 'imap-username)
810 (make-local-variable 'imap-password)
c113de23
GM
811 (let (user passwd ret)
812 ;; (condition-case ()
813 (while (or (not user) (not passwd))
814 (setq user (or imap-username
738421d1 815 (read-from-minibuffer
23f87bed
MB
816 (concat "IMAP username for " imap-server
817 " (using stream `" (symbol-name imap-stream)
818 "'): ")
c113de23
GM
819 (or user imap-default-user))))
820 (setq passwd (or imap-password
23f87bed 821 (read-passwd
738421d1 822 (concat "IMAP password for " user "@"
23f87bed
MB
823 imap-server " (using authenticator `"
824 (symbol-name imap-auth) "'): "))))
c113de23
GM
825 (when (and user passwd)
826 (if (funcall loginfunc user passwd)
827 (progn
828 (setq ret t
829 imap-username user)
830 (if (and (not imap-password)
831 (y-or-n-p "Store password for this session? "))
832 (setq imap-password passwd)))
833 (message "Login failed...")
834 (setq passwd nil)
23f87bed 835 (setq imap-password nil)
c113de23
GM
836 (sit-for 1))))
837 ;; (quit (with-current-buffer buffer
838 ;; (setq user nil
839 ;; passwd nil)))
840 ;; (error (with-current-buffer buffer
841 ;; (setq user nil
842 ;; passwd nil))))
843 ret)))
844
845(defun imap-gssapi-auth-p (buffer)
23f87bed 846 (eq imap-stream 'gssapi))
c113de23
GM
847
848(defun imap-gssapi-auth (buffer)
a2617484
DL
849 (message "imap: Authenticating using GSSAPI...%s"
850 (if (eq imap-stream 'gssapi) "done" "failed"))
c113de23
GM
851 (eq imap-stream 'gssapi))
852
853(defun imap-kerberos4-auth-p (buffer)
23f87bed
MB
854 (and (imap-capability 'AUTH=KERBEROS_V4 buffer)
855 (eq imap-stream 'kerberos4)))
c113de23
GM
856
857(defun imap-kerberos4-auth (buffer)
a2617484
DL
858 (message "imap: Authenticating using Kerberos 4...%s"
859 (if (eq imap-stream 'kerberos4) "done" "failed"))
c113de23
GM
860 (eq imap-stream 'kerberos4))
861
862(defun imap-cram-md5-p (buffer)
863 (imap-capability 'AUTH=CRAM-MD5 buffer))
864
865(defun imap-cram-md5-auth (buffer)
866 "Login to server using the AUTH CRAM-MD5 method."
a2617484
DL
867 (message "imap: Authenticating using CRAM-MD5...")
868 (let ((done (imap-interactive-login
869 buffer
870 (lambda (user passwd)
871 (imap-ok-p
872 (imap-send-command-wait
873 (list
874 "AUTHENTICATE CRAM-MD5"
875 (lambda (challenge)
876 (let* ((decoded (base64-decode-string challenge))
877 (hash (rfc2104-hash 'md5 64 16 passwd decoded))
878 (response (concat user " " hash))
879 (encoded (base64-encode-string response)))
880 encoded)))))))))
881 (if done
882 (message "imap: Authenticating using CRAM-MD5...done")
883 (message "imap: Authenticating using CRAM-MD5...failed"))))
738421d1 884
c113de23 885(defun imap-login-p (buffer)
a2617484
DL
886 (and (not (imap-capability 'LOGINDISABLED buffer))
887 (not (imap-capability 'X-LOGIN-CMD-DISABLED buffer))))
c113de23
GM
888
889(defun imap-login-auth (buffer)
890 "Login to server using the LOGIN command."
a2617484 891 (message "imap: Plaintext authentication...")
738421d1 892 (imap-interactive-login buffer
c113de23 893 (lambda (user passwd)
738421d1
SS
894 (imap-ok-p (imap-send-command-wait
895 (concat "LOGIN \"" user "\" \""
c113de23
GM
896 passwd "\""))))))
897
898(defun imap-anonymous-p (buffer)
899 t)
900
901(defun imap-anonymous-auth (buffer)
8f688cb0 902 (message "imap: Logging in anonymously...")
c113de23
GM
903 (with-current-buffer buffer
904 (imap-ok-p (imap-send-command-wait
738421d1 905 (concat "LOGIN anonymous \"" (concat (user-login-name) "@"
c113de23
GM
906 (system-name)) "\"")))))
907
908(defun imap-digest-md5-p (buffer)
a2617484
DL
909 (and (imap-capability 'AUTH=DIGEST-MD5 buffer)
910 (condition-case ()
c113de23 911 (require 'digest-md5)
a2617484 912 (error nil))))
c113de23
GM
913
914(defun imap-digest-md5-auth (buffer)
915 "Login to server using the AUTH DIGEST-MD5 method."
a2617484 916 (message "imap: Authenticating using DIGEST-MD5...")
c113de23
GM
917 (imap-interactive-login
918 buffer
919 (lambda (user passwd)
738421d1 920 (let ((tag
c113de23
GM
921 (imap-send-command
922 (list
923 "AUTHENTICATE DIGEST-MD5"
924 (lambda (challenge)
925 (digest-md5-parse-digest-challenge
926 (base64-decode-string challenge))
927 (let* ((digest-uri
738421d1 928 (digest-md5-digest-uri
c113de23
GM
929 "imap" (digest-md5-challenge 'realm)))
930 (response
738421d1 931 (digest-md5-digest-response
c113de23
GM
932 user passwd digest-uri)))
933 (base64-encode-string response 'no-line-break))))
934 )))
935 (if (not (eq (imap-wait-for-tag tag) 'INCOMPLETE))
936 nil
937 (setq imap-continuation nil)
938 (imap-send-command-1 "")
939 (imap-ok-p (imap-wait-for-tag tag)))))))
940
941;; Server functions:
942
943(defun imap-open-1 (buffer)
944 (with-current-buffer buffer
945 (erase-buffer)
946 (setq imap-current-mailbox nil
947 imap-current-message nil
948 imap-state 'initial
949 imap-process (condition-case ()
738421d1 950 (funcall (nth 2 (assq imap-stream
c113de23
GM
951 imap-stream-alist))
952 "imap" buffer imap-server imap-port)
953 ((error quit) nil)))
954 (when imap-process
955 (set-process-filter imap-process 'imap-arrival-filter)
956 (set-process-sentinel imap-process 'imap-sentinel)
957 (while (and (eq imap-state 'initial)
958 (memq (process-status imap-process) '(open run)))
959 (message "Waiting for response from %s..." imap-server)
960 (accept-process-output imap-process 1))
961 (message "Waiting for response from %s...done" imap-server)
962 (and (memq (process-status imap-process) '(open run))
963 imap-process))))
964
965(defun imap-open (server &optional port stream auth buffer)
966 "Open a IMAP connection to host SERVER at PORT returning a buffer.
967If PORT is unspecified, a default value is used (143 except
968for SSL which use 993).
969STREAM indicates the stream to use, see `imap-streams' for available
970streams. If nil, it choices the best stream the server is capable of.
971AUTH indicates authenticator to use, see `imap-authenticators' for
972available authenticators. If nil, it choices the best stream the
973server is capable of.
974BUFFER can be a buffer or a name of a buffer, which is created if
8f688cb0 975necessary. If nil, the buffer name is generated."
c113de23
GM
976 (setq buffer (or buffer (format " *imap* %s:%d" server (or port 0))))
977 (with-current-buffer (get-buffer-create buffer)
978 (if (imap-opened buffer)
979 (imap-close buffer))
f78cebe3 980 (mapcar 'make-local-variable imap-local-variables)
c113de23
GM
981 (imap-disable-multibyte)
982 (buffer-disable-undo)
983 (setq imap-server (or server imap-server))
984 (setq imap-port (or port imap-port))
985 (setq imap-auth (or auth imap-auth))
986 (setq imap-stream (or stream imap-stream))
a2617484 987 (message "imap: Connecting to %s..." imap-server)
23f87bed
MB
988 (if (null (let ((imap-stream (or imap-stream imap-default-stream)))
989 (imap-open-1 buffer)))
990 (progn
991 (message "imap: Connecting to %s...failed" imap-server)
992 nil)
993 (when (null imap-stream)
994 ;; Need to choose stream.
995 (let ((streams imap-streams))
996 (while (setq stream (pop streams))
997 ;; OK to use this stream?
998 (when (funcall (nth 1 (assq stream imap-stream-alist)) buffer)
999 ;; Stream changed?
1000 (if (not (eq imap-default-stream stream))
1001 (with-current-buffer (get-buffer-create
1002 (generate-new-buffer-name " *temp*"))
1003 (mapcar 'make-local-variable imap-local-variables)
1004 (imap-disable-multibyte)
1005 (buffer-disable-undo)
1006 (setq imap-server (or server imap-server))
1007 (setq imap-port (or port imap-port))
1008 (setq imap-auth (or auth imap-auth))
1009 (message "imap: Reconnecting with stream `%s'..." stream)
1010 (if (null (let ((imap-stream stream))
1011 (imap-open-1 (current-buffer))))
1012 (progn
1013 (kill-buffer (current-buffer))
1014 (message
1015 "imap: Reconnecting with stream `%s'...failed"
1016 stream))
1017 ;; We're done, kill the first connection
1018 (imap-close buffer)
1019 (kill-buffer buffer)
1020 (rename-buffer buffer)
1021 (message "imap: Reconnecting with stream `%s'...done"
1022 stream)
1023 (setq imap-stream stream)
1024 (setq imap-capability nil)
1025 (setq streams nil)))
1026 ;; We're done
1027 (message "imap: Connecting to %s...done" imap-server)
1028 (setq imap-stream stream)
1029 (setq imap-capability nil)
1030 (setq streams nil))))))
1031 (when (imap-opened buffer)
1032 (setq imap-mailbox-data (make-vector imap-mailbox-prime 0)))
1033 (when imap-stream
1034 buffer))))
c113de23
GM
1035
1036(defun imap-opened (&optional buffer)
1037 "Return non-nil if connection to imap server in BUFFER is open.
1038If BUFFER is nil then the current buffer is used."
1039 (and (setq buffer (get-buffer (or buffer (current-buffer))))
1040 (buffer-live-p buffer)
1041 (with-current-buffer buffer
1042 (and imap-process
1043 (memq (process-status imap-process) '(open run))))))
1044
1045(defun imap-authenticate (&optional user passwd buffer)
1046 "Authenticate to server in BUFFER, using current buffer if nil.
1047It uses the authenticator specified when opening the server. If the
1048authenticator requires username/passwords, they are queried from the
1049user and optionally stored in the buffer. If USER and/or PASSWD is
1050specified, the user will not be questioned and the username and/or
1051password is remembered in the buffer."
1052 (with-current-buffer (or buffer (current-buffer))
1053 (if (not (eq imap-state 'nonauth))
1054 (or (eq imap-state 'auth)
1055 (eq imap-state 'select)
1056 (eq imap-state 'examine))
f78cebe3
SM
1057 (make-local-variable 'imap-username)
1058 (make-local-variable 'imap-password)
c113de23
GM
1059 (if user (setq imap-username user))
1060 (if passwd (setq imap-password passwd))
23f87bed
MB
1061 (if imap-auth
1062 (and (funcall (nth 2 (assq imap-auth
1063 imap-authenticator-alist)) buffer)
1064 (setq imap-state 'auth))
1065 ;; Choose authenticator.
1066 (let ((auths imap-authenticators)
1067 auth)
1068 (while (setq auth (pop auths))
1069 ;; OK to use authenticator?
1070 (when (funcall (nth 1 (assq auth imap-authenticator-alist)) buffer)
1071 (message "imap: Authenticating to `%s' using `%s'..."
1072 imap-server auth)
1073 (setq imap-auth auth)
1074 (if (funcall (nth 2 (assq auth imap-authenticator-alist)) buffer)
1075 (progn
1076 (message "imap: Authenticating to `%s' using `%s'...done"
1077 imap-server auth)
1078 (setq auths nil))
1079 (message "imap: Authenticating to `%s' using `%s'...failed"
1080 imap-server auth)))))
1081 imap-state))))
c113de23
GM
1082
1083(defun imap-close (&optional buffer)
1084 "Close connection to server in BUFFER.
1085If BUFFER is nil, the current buffer is used."
1086 (with-current-buffer (or buffer (current-buffer))
23f87bed
MB
1087 (when (imap-opened)
1088 (condition-case nil
1089 (imap-send-command-wait "LOGOUT")
1090 (quit nil)))
c113de23
GM
1091 (when (and imap-process
1092 (memq (process-status imap-process) '(open run)))
1093 (delete-process imap-process))
1094 (setq imap-current-mailbox nil
1095 imap-current-message nil
1096 imap-process nil)
1097 (erase-buffer)
1098 t))
1099
1100(defun imap-capability (&optional identifier buffer)
1101 "Return a list of identifiers which server in BUFFER support.
1102If IDENTIFIER, return non-nil if it's among the servers capabilities.
1103If BUFFER is nil, the current buffer is assumed."
1104 (with-current-buffer (or buffer (current-buffer))
1105 (unless imap-capability
1106 (unless (imap-ok-p (imap-send-command-wait "CAPABILITY"))
1107 (setq imap-capability '(IMAP2))))
1108 (if identifier
1109 (memq (intern (upcase (symbol-name identifier))) imap-capability)
1110 imap-capability)))
1111
1112(defun imap-namespace (&optional buffer)
1113 "Return a namespace hierarchy at server in BUFFER.
1114If BUFFER is nil, the current buffer is assumed."
1115 (with-current-buffer (or buffer (current-buffer))
1116 (unless imap-namespace
1117 (when (imap-capability 'NAMESPACE)
1118 (imap-send-command-wait "NAMESPACE")))
1119 imap-namespace))
1120
1121(defun imap-send-command-wait (command &optional buffer)
1122 (imap-wait-for-tag (imap-send-command command buffer) buffer))
1123
1124\f
1125;; Mailbox functions:
1126
1127(defun imap-mailbox-put (propname value &optional mailbox buffer)
1128 (with-current-buffer (or buffer (current-buffer))
1129 (if imap-mailbox-data
1130 (put (intern (or mailbox imap-current-mailbox) imap-mailbox-data)
1131 propname value)
1132 (error "Imap-mailbox-data is nil, prop %s value %s mailbox %s buffer %s"
1133 propname value mailbox (current-buffer)))
1134 t))
1135
1136(defsubst imap-mailbox-get-1 (propname &optional mailbox)
1137 (get (intern-soft (or mailbox imap-current-mailbox) imap-mailbox-data)
1138 propname))
1139
1140(defun imap-mailbox-get (propname &optional mailbox buffer)
1141 (let ((mailbox (imap-utf7-encode mailbox)))
1142 (with-current-buffer (or buffer (current-buffer))
1143 (imap-mailbox-get-1 propname (or mailbox imap-current-mailbox)))))
1144
1145(defun imap-mailbox-map-1 (func &optional mailbox-decoder buffer)
1146 (with-current-buffer (or buffer (current-buffer))
1147 (let (result)
738421d1 1148 (mapatoms
c113de23
GM
1149 (lambda (s)
1150 (push (funcall func (if mailbox-decoder
1151 (funcall mailbox-decoder (symbol-name s))
1152 (symbol-name s))) result))
1153 imap-mailbox-data)
1154 result)))
1155
1156(defun imap-mailbox-map (func &optional buffer)
1157 "Map a function across each mailbox in `imap-mailbox-data', returning a list.
1158Function should take a mailbox name (a string) as
1159the only argument."
1160 (imap-mailbox-map-1 func 'imap-utf7-decode buffer))
1161
1162(defun imap-current-mailbox (&optional buffer)
1163 (with-current-buffer (or buffer (current-buffer))
1164 (imap-utf7-decode imap-current-mailbox)))
1165
1166(defun imap-current-mailbox-p-1 (mailbox &optional examine)
1167 (and (string= mailbox imap-current-mailbox)
1168 (or (and examine
1169 (eq imap-state 'examine))
1170 (and (not examine)
1171 (eq imap-state 'selected)))))
1172
1173(defun imap-current-mailbox-p (mailbox &optional examine buffer)
1174 (with-current-buffer (or buffer (current-buffer))
1175 (imap-current-mailbox-p-1 (imap-utf7-encode mailbox) examine)))
1176
1177(defun imap-mailbox-select-1 (mailbox &optional examine)
1178 "Select MAILBOX on server in BUFFER.
1179If EXAMINE is non-nil, do a read-only select."
1180 (if (imap-current-mailbox-p-1 mailbox examine)
1181 imap-current-mailbox
1182 (setq imap-current-mailbox mailbox)
1183 (if (imap-ok-p (imap-send-command-wait
738421d1 1184 (concat (if examine "EXAMINE" "SELECT") " \""
c113de23
GM
1185 mailbox "\"")))
1186 (progn
1187 (setq imap-message-data (make-vector imap-message-prime 0)
1188 imap-state (if examine 'examine 'selected))
1189 imap-current-mailbox)
1190 ;; Failed SELECT/EXAMINE unselects current mailbox
1191 (setq imap-current-mailbox nil))))
1192
738421d1 1193(defun imap-mailbox-select (mailbox &optional examine buffer)
c113de23 1194 (with-current-buffer (or buffer (current-buffer))
738421d1 1195 (imap-utf7-decode
c113de23
GM
1196 (imap-mailbox-select-1 (imap-utf7-encode mailbox) examine))))
1197
1198(defun imap-mailbox-examine-1 (mailbox &optional buffer)
1199 (with-current-buffer (or buffer (current-buffer))
738421d1 1200 (imap-mailbox-select-1 mailbox 'examine)))
c113de23
GM
1201
1202(defun imap-mailbox-examine (mailbox &optional buffer)
1203 "Examine MAILBOX on server in BUFFER."
738421d1 1204 (imap-mailbox-select mailbox 'examine buffer))
c113de23
GM
1205
1206(defun imap-mailbox-unselect (&optional buffer)
1207 "Close current folder in BUFFER, without expunging articles."
1208 (with-current-buffer (or buffer (current-buffer))
1209 (when (or (eq imap-state 'auth)
1210 (and (imap-capability 'UNSELECT)
1211 (imap-ok-p (imap-send-command-wait "UNSELECT")))
738421d1 1212 (and (imap-ok-p
c113de23
GM
1213 (imap-send-command-wait (concat "EXAMINE \""
1214 imap-current-mailbox
1215 "\"")))
1216 (imap-ok-p (imap-send-command-wait "CLOSE"))))
1217 (setq imap-current-mailbox nil
1218 imap-message-data nil
1219 imap-state 'auth)
1220 t)))
1221
23f87bed 1222(defun imap-mailbox-expunge (&optional asynch buffer)
c113de23 1223 "Expunge articles in current folder in BUFFER.
23f87bed 1224If ASYNCH, do not wait for succesful completion of the command.
c113de23
GM
1225If BUFFER is nil the current buffer is assumed."
1226 (with-current-buffer (or buffer (current-buffer))
1227 (when (and imap-current-mailbox (not (eq imap-state 'examine)))
23f87bed
MB
1228 (if asynch
1229 (imap-send-command "EXPUNGE")
1230 (imap-ok-p (imap-send-command-wait "EXPUNGE"))))))
c113de23 1231
23f87bed 1232(defun imap-mailbox-close (&optional asynch buffer)
c113de23 1233 "Expunge articles and close current folder in BUFFER.
23f87bed 1234If ASYNCH, do not wait for succesful completion of the command.
c113de23
GM
1235If BUFFER is nil the current buffer is assumed."
1236 (with-current-buffer (or buffer (current-buffer))
23f87bed
MB
1237 (when imap-current-mailbox
1238 (if asynch
1239 (imap-add-callback (imap-send-command "CLOSE")
1240 `(lambda (tag status)
1241 (message "IMAP mailbox `%s' closed... %s"
1242 imap-current-mailbox status)
1243 (when (eq ,imap-current-mailbox
1244 imap-current-mailbox)
1245 ;; Don't wipe out data if another mailbox
1246 ;; was selected...
1247 (setq imap-current-mailbox nil
1248 imap-message-data nil
1249 imap-state 'auth))))
1250 (when (imap-ok-p (imap-send-command-wait "CLOSE"))
1251 (setq imap-current-mailbox nil
1252 imap-message-data nil
1253 imap-state 'auth)))
c113de23
GM
1254 t)))
1255
1256(defun imap-mailbox-create-1 (mailbox)
1257 (imap-ok-p (imap-send-command-wait (list "CREATE \"" mailbox "\""))))
1258
1259(defun imap-mailbox-create (mailbox &optional buffer)
1260 "Create MAILBOX on server in BUFFER.
1261If BUFFER is nil the current buffer is assumed."
1262 (with-current-buffer (or buffer (current-buffer))
1263 (imap-mailbox-create-1 (imap-utf7-encode mailbox))))
1264
1265(defun imap-mailbox-delete (mailbox &optional buffer)
1266 "Delete MAILBOX on server in BUFFER.
1267If BUFFER is nil the current buffer is assumed."
1268 (let ((mailbox (imap-utf7-encode mailbox)))
1269 (with-current-buffer (or buffer (current-buffer))
1270 (imap-ok-p
1271 (imap-send-command-wait (list "DELETE \"" mailbox "\""))))))
1272
1273(defun imap-mailbox-rename (oldname newname &optional buffer)
1274 "Rename mailbox OLDNAME to NEWNAME on server in BUFFER.
1275If BUFFER is nil the current buffer is assumed."
1276 (let ((oldname (imap-utf7-encode oldname))
1277 (newname (imap-utf7-encode newname)))
1278 (with-current-buffer (or buffer (current-buffer))
1279 (imap-ok-p
1280 (imap-send-command-wait (list "RENAME \"" oldname "\" "
1281 "\"" newname "\""))))))
1282
738421d1 1283(defun imap-mailbox-lsub (&optional root reference add-delimiter buffer)
c113de23
GM
1284 "Return a list of subscribed mailboxes on server in BUFFER.
1285If ROOT is non-nil, only list matching mailboxes. If ADD-DELIMITER is
1286non-nil, a hierarchy delimiter is added to root. REFERENCE is a
1287implementation-specific string that has to be passed to lsub command."
1288 (with-current-buffer (or buffer (current-buffer))
1289 ;; Make sure we know the hierarchy separator for root's hierarchy
1290 (when (and add-delimiter (null (imap-mailbox-get-1 'delimiter root)))
1291 (imap-send-command-wait (concat "LIST \"" reference "\" \""
1292 (imap-utf7-encode root) "\"")))
1293 ;; clear list data (NB not delimiter and other stuff)
1294 (imap-mailbox-map-1 (lambda (mailbox)
1295 (imap-mailbox-put 'lsub nil mailbox)))
1296 (when (imap-ok-p
738421d1 1297 (imap-send-command-wait
c113de23
GM
1298 (concat "LSUB \"" reference "\" \"" (imap-utf7-encode root)
1299 (and add-delimiter (imap-mailbox-get-1 'delimiter root))
1300 "%\"")))
1301 (let (out)
1302 (imap-mailbox-map-1 (lambda (mailbox)
1303 (when (imap-mailbox-get-1 'lsub mailbox)
1304 (push (imap-utf7-decode mailbox) out))))
1305 (nreverse out)))))
1306
1307(defun imap-mailbox-list (root &optional reference add-delimiter buffer)
1308 "Return a list of mailboxes matching ROOT on server in BUFFER.
1309If ADD-DELIMITER is non-nil, a hierarchy delimiter is added to
1310root. REFERENCE is a implementation-specific string that has to be
1311passed to list command."
1312 (with-current-buffer (or buffer (current-buffer))
1313 ;; Make sure we know the hierarchy separator for root's hierarchy
1314 (when (and add-delimiter (null (imap-mailbox-get-1 'delimiter root)))
1315 (imap-send-command-wait (concat "LIST \"" reference "\" \""
1316 (imap-utf7-encode root) "\"")))
1317 ;; clear list data (NB not delimiter and other stuff)
1318 (imap-mailbox-map-1 (lambda (mailbox)
1319 (imap-mailbox-put 'list nil mailbox)))
1320 (when (imap-ok-p
738421d1 1321 (imap-send-command-wait
c113de23
GM
1322 (concat "LIST \"" reference "\" \"" (imap-utf7-encode root)
1323 (and add-delimiter (imap-mailbox-get-1 'delimiter root))
1324 "%\"")))
1325 (let (out)
1326 (imap-mailbox-map-1 (lambda (mailbox)
1327 (when (imap-mailbox-get-1 'list mailbox)
1328 (push (imap-utf7-decode mailbox) out))))
1329 (nreverse out)))))
1330
1331(defun imap-mailbox-subscribe (mailbox &optional buffer)
1332 "Send the SUBSCRIBE command on the mailbox to server in BUFFER.
1333Returns non-nil if successful."
1334 (with-current-buffer (or buffer (current-buffer))
738421d1 1335 (imap-ok-p (imap-send-command-wait (concat "SUBSCRIBE \""
c113de23
GM
1336 (imap-utf7-encode mailbox)
1337 "\"")))))
1338
1339(defun imap-mailbox-unsubscribe (mailbox &optional buffer)
1340 "Send the SUBSCRIBE command on the mailbox to server in BUFFER.
1341Returns non-nil if successful."
1342 (with-current-buffer (or buffer (current-buffer))
738421d1 1343 (imap-ok-p (imap-send-command-wait (concat "UNSUBSCRIBE "
c113de23
GM
1344 (imap-utf7-encode mailbox)
1345 "\"")))))
1346
1347(defun imap-mailbox-status (mailbox items &optional buffer)
1348 "Get status items ITEM in MAILBOX from server in BUFFER.
1349ITEMS can be a symbol or a list of symbols, valid symbols are one of
1350the STATUS data items -- ie 'messages, 'recent, 'uidnext, 'uidvalidity
1351or 'unseen. If ITEMS is a list of symbols, a list of values is
4f014d55 1352returned, if ITEMS is a symbol only its value is returned."
c113de23 1353 (with-current-buffer (or buffer (current-buffer))
738421d1 1354 (when (imap-ok-p
c113de23
GM
1355 (imap-send-command-wait (list "STATUS \""
1356 (imap-utf7-encode mailbox)
1357 "\" "
23f87bed
MB
1358 (upcase
1359 (format "%s"
1360 (if (listp items)
1361 items
1362 (list items)))))))
c113de23
GM
1363 (if (listp items)
1364 (mapcar (lambda (item)
1365 (imap-mailbox-get item mailbox))
1366 items)
1367 (imap-mailbox-get items mailbox)))))
1368
23f87bed
MB
1369(defun imap-mailbox-status-asynch (mailbox items &optional buffer)
1370 "Send status item request ITEM on MAILBOX to server in BUFFER.
1371ITEMS can be a symbol or a list of symbols, valid symbols are one of
1372the STATUS data items -- ie 'messages, 'recent, 'uidnext, 'uidvalidity
1373or 'unseen. The IMAP command tag is returned."
1374 (with-current-buffer (or buffer (current-buffer))
1375 (imap-send-command (list "STATUS \""
1376 (imap-utf7-encode mailbox)
1377 "\" "
1378 (format "%s"
1379 (if (listp items)
1380 items
1381 (list items)))))))
1382
c113de23
GM
1383(defun imap-mailbox-acl-get (&optional mailbox buffer)
1384 "Get ACL on mailbox from server in BUFFER."
1385 (let ((mailbox (imap-utf7-encode mailbox)))
1386 (with-current-buffer (or buffer (current-buffer))
1387 (when (imap-ok-p
1388 (imap-send-command-wait (list "GETACL \""
1389 (or mailbox imap-current-mailbox)
1390 "\"")))
1391 (imap-mailbox-get-1 'acl (or mailbox imap-current-mailbox))))))
1392
1393(defun imap-mailbox-acl-set (identifier rights &optional mailbox buffer)
1394 "Change/set ACL for IDENTIFIER to RIGHTS in MAILBOX from server in BUFFER."
1395 (let ((mailbox (imap-utf7-encode mailbox)))
1396 (with-current-buffer (or buffer (current-buffer))
1397 (imap-ok-p
1398 (imap-send-command-wait (list "SETACL \""
1399 (or mailbox imap-current-mailbox)
1400 "\" "
1401 identifier
1402 " "
1403 rights))))))
1404
1405(defun imap-mailbox-acl-delete (identifier &optional mailbox buffer)
1406 "Removes any <identifier,rights> pair for IDENTIFIER in MAILBOX from server in BUFFER."
1407 (let ((mailbox (imap-utf7-encode mailbox)))
1408 (with-current-buffer (or buffer (current-buffer))
1409 (imap-ok-p
1410 (imap-send-command-wait (list "DELETEACL \""
1411 (or mailbox imap-current-mailbox)
1412 "\" "
1413 identifier))))))
1414
1415\f
1416;; Message functions:
1417
1418(defun imap-current-message (&optional buffer)
1419 (with-current-buffer (or buffer (current-buffer))
1420 imap-current-message))
1421
1422(defun imap-list-to-message-set (list)
1423 (mapconcat (lambda (item)
1424 (number-to-string item))
1425 (if (listp list)
1426 list
1427 (list list))
1428 ","))
1429
1430(defun imap-range-to-message-set (range)
1431 (mapconcat
1432 (lambda (item)
1433 (if (consp item)
23f87bed
MB
1434 (format "%d:%d"
1435 (car item) (cdr item))
c113de23
GM
1436 (format "%d" item)))
1437 (if (and (listp range) (not (listp (cdr range))))
1438 (list range) ;; make (1 . 2) into ((1 . 2))
1439 range)
1440 ","))
1441
1442(defun imap-fetch-asynch (uids props &optional nouidfetch buffer)
1443 (with-current-buffer (or buffer (current-buffer))
1444 (imap-send-command (format "%sFETCH %s %s" (if nouidfetch "" "UID ")
1445 (if (listp uids)
1446 (imap-list-to-message-set uids)
1447 uids)
1448 props))))
1449
1450(defun imap-fetch (uids props &optional receive nouidfetch buffer)
1451 "Fetch properties PROPS from message set UIDS from server in BUFFER.
1452UIDS can be a string, number or a list of numbers. If RECEIVE
1453is non-nil return theese properties."
1454 (with-current-buffer (or buffer (current-buffer))
738421d1 1455 (when (imap-ok-p (imap-send-command-wait
c113de23
GM
1456 (format "%sFETCH %s %s" (if nouidfetch "" "UID ")
1457 (if (listp uids)
1458 (imap-list-to-message-set uids)
1459 uids)
1460 props)))
1461 (if (or (null receive) (stringp uids))
1462 t
1463 (if (listp uids)
1464 (mapcar (lambda (uid)
1465 (if (listp receive)
1466 (mapcar (lambda (prop)
1467 (imap-message-get uid prop))
1468 receive)
1469 (imap-message-get uid receive)))
1470 uids)
1471 (imap-message-get uids receive))))))
738421d1 1472
c113de23
GM
1473(defun imap-message-put (uid propname value &optional buffer)
1474 (with-current-buffer (or buffer (current-buffer))
1475 (if imap-message-data
1476 (put (intern (number-to-string uid) imap-message-data)
1477 propname value)
1478 (error "Imap-message-data is nil, uid %s prop %s value %s buffer %s"
1479 uid propname value (current-buffer)))
1480 t))
1481
1482(defun imap-message-get (uid propname &optional buffer)
1483 (with-current-buffer (or buffer (current-buffer))
1484 (get (intern-soft (number-to-string uid) imap-message-data)
1485 propname)))
1486
1487(defun imap-message-map (func propname &optional buffer)
1488 "Map a function across each mailbox in `imap-message-data', returning a list."
1489 (with-current-buffer (or buffer (current-buffer))
1490 (let (result)
1491 (mapatoms
1492 (lambda (s)
1493 (push (funcall func (get s 'UID) (get s propname)) result))
1494 imap-message-data)
1495 result)))
1496
1497(defmacro imap-message-envelope-date (uid &optional buffer)
1498 `(with-current-buffer (or ,buffer (current-buffer))
1499 (elt (imap-message-get ,uid 'ENVELOPE) 0)))
1500
1501(defmacro imap-message-envelope-subject (uid &optional buffer)
1502 `(with-current-buffer (or ,buffer (current-buffer))
1503 (elt (imap-message-get ,uid 'ENVELOPE) 1)))
1504
1505(defmacro imap-message-envelope-from (uid &optional buffer)
1506 `(with-current-buffer (or ,buffer (current-buffer))
1507 (elt (imap-message-get ,uid 'ENVELOPE) 2)))
1508
1509(defmacro imap-message-envelope-sender (uid &optional buffer)
1510 `(with-current-buffer (or ,buffer (current-buffer))
1511 (elt (imap-message-get ,uid 'ENVELOPE) 3)))
1512
1513(defmacro imap-message-envelope-reply-to (uid &optional buffer)
1514 `(with-current-buffer (or ,buffer (current-buffer))
1515 (elt (imap-message-get ,uid 'ENVELOPE) 4)))
1516
1517(defmacro imap-message-envelope-to (uid &optional buffer)
1518 `(with-current-buffer (or ,buffer (current-buffer))
1519 (elt (imap-message-get ,uid 'ENVELOPE) 5)))
1520
1521(defmacro imap-message-envelope-cc (uid &optional buffer)
1522 `(with-current-buffer (or ,buffer (current-buffer))
1523 (elt (imap-message-get ,uid 'ENVELOPE) 6)))
1524
1525(defmacro imap-message-envelope-bcc (uid &optional buffer)
1526 `(with-current-buffer (or ,buffer (current-buffer))
1527 (elt (imap-message-get ,uid 'ENVELOPE) 7)))
1528
1529(defmacro imap-message-envelope-in-reply-to (uid &optional buffer)
1530 `(with-current-buffer (or ,buffer (current-buffer))
1531 (elt (imap-message-get ,uid 'ENVELOPE) 8)))
1532
1533(defmacro imap-message-envelope-message-id (uid &optional buffer)
1534 `(with-current-buffer (or ,buffer (current-buffer))
1535 (elt (imap-message-get ,uid 'ENVELOPE) 9)))
1536
1537(defmacro imap-message-body (uid &optional buffer)
1538 `(with-current-buffer (or ,buffer (current-buffer))
1539 (imap-message-get ,uid 'BODY)))
1540
1541(defun imap-search (predicate &optional buffer)
1542 (with-current-buffer (or buffer (current-buffer))
1543 (imap-mailbox-put 'search 'dummy)
1544 (when (imap-ok-p (imap-send-command-wait (concat "UID SEARCH " predicate)))
1545 (if (eq (imap-mailbox-get-1 'search imap-current-mailbox) 'dummy)
23f87bed
MB
1546 (progn
1547 (message "Missing SEARCH response to a SEARCH command (server not RFC compliant)...")
1548 nil)
c113de23
GM
1549 (imap-mailbox-get-1 'search imap-current-mailbox)))))
1550
1551(defun imap-message-flag-permanent-p (flag &optional mailbox buffer)
1552 "Return t iff FLAG can be permanently (between IMAP sessions) saved on articles, in MAILBOX on server in BUFFER."
1553 (with-current-buffer (or buffer (current-buffer))
1554 (or (member "\\*" (imap-mailbox-get 'permanentflags mailbox))
1555 (member flag (imap-mailbox-get 'permanentflags mailbox)))))
1556
1557(defun imap-message-flags-set (articles flags &optional silent buffer)
1558 (when (and articles flags)
1559 (with-current-buffer (or buffer (current-buffer))
1560 (imap-ok-p (imap-send-command-wait
1561 (concat "UID STORE " articles
1562 " FLAGS" (if silent ".SILENT") " (" flags ")"))))))
1563
1564(defun imap-message-flags-del (articles flags &optional silent buffer)
1565 (when (and articles flags)
1566 (with-current-buffer (or buffer (current-buffer))
1567 (imap-ok-p (imap-send-command-wait
1568 (concat "UID STORE " articles
1569 " -FLAGS" (if silent ".SILENT") " (" flags ")"))))))
1570
1571(defun imap-message-flags-add (articles flags &optional silent buffer)
1572 (when (and articles flags)
1573 (with-current-buffer (or buffer (current-buffer))
1574 (imap-ok-p (imap-send-command-wait
1575 (concat "UID STORE " articles
1576 " +FLAGS" (if silent ".SILENT") " (" flags ")"))))))
1577
1578(defun imap-message-copyuid-1 (mailbox)
1579 (if (imap-capability 'UIDPLUS)
1580 (list (nth 0 (imap-mailbox-get-1 'copyuid mailbox))
1581 (string-to-number (nth 2 (imap-mailbox-get-1 'copyuid mailbox))))
1582 (let ((old-mailbox imap-current-mailbox)
1583 (state imap-state)
1584 (imap-message-data (make-vector 2 0)))
1585 (when (imap-mailbox-examine-1 mailbox)
1586 (prog1
1587 (and (imap-fetch "*" "UID")
1588 (list (imap-mailbox-get-1 'uidvalidity mailbox)
1589 (apply 'max (imap-message-map
1590 (lambda (uid prop) uid) 'UID))))
1591 (if old-mailbox
1592 (imap-mailbox-select old-mailbox (eq state 'examine))
1593 (imap-mailbox-unselect)))))))
1594
1595(defun imap-message-copyuid (mailbox &optional buffer)
1596 (with-current-buffer (or buffer (current-buffer))
1597 (imap-message-copyuid-1 (imap-utf7-decode mailbox))))
1598
1599(defun imap-message-copy (articles mailbox
1600 &optional dont-create no-copyuid buffer)
1601 "Copy ARTICLES (a string message set) to MAILBOX on server in
1602BUFFER, creating mailbox if it doesn't exist. If dont-create is
1603non-nil, it will not create a mailbox. On success, return a list with
1604the UIDVALIDITY of the mailbox the article(s) was copied to as the
1605first element, rest of list contain the saved articles' UIDs."
1606 (when articles
1607 (with-current-buffer (or buffer (current-buffer))
1608 (let ((mailbox (imap-utf7-encode mailbox)))
1609 (if (let ((cmd (concat "UID COPY " articles " \"" mailbox "\""))
1610 (imap-current-target-mailbox mailbox))
1611 (if (imap-ok-p (imap-send-command-wait cmd))
1612 t
1613 (when (and (not dont-create)
23f87bed
MB
1614 ;; removed because of buggy Oracle server
1615 ;; that doesn't send TRYCREATE tags (which
1616 ;; is a MUST according to specifications):
1617 ;;(imap-mailbox-get-1 'trycreate mailbox)
1618 (imap-mailbox-create-1 mailbox))
c113de23
GM
1619 (imap-ok-p (imap-send-command-wait cmd)))))
1620 (or no-copyuid
1621 (imap-message-copyuid-1 mailbox)))))))
738421d1 1622
c113de23
GM
1623(defun imap-message-appenduid-1 (mailbox)
1624 (if (imap-capability 'UIDPLUS)
1625 (imap-mailbox-get-1 'appenduid mailbox)
1626 (let ((old-mailbox imap-current-mailbox)
1627 (state imap-state)
1628 (imap-message-data (make-vector 2 0)))
1629 (when (imap-mailbox-examine-1 mailbox)
1630 (prog1
1631 (and (imap-fetch "*" "UID")
1632 (list (imap-mailbox-get-1 'uidvalidity mailbox)
1633 (apply 'max (imap-message-map
1634 (lambda (uid prop) uid) 'UID))))
1635 (if old-mailbox
1636 (imap-mailbox-select old-mailbox (eq state 'examine))
1637 (imap-mailbox-unselect)))))))
1638
1639(defun imap-message-appenduid (mailbox &optional buffer)
1640 (with-current-buffer (or buffer (current-buffer))
1641 (imap-message-appenduid-1 (imap-utf7-encode mailbox))))
1642
1643(defun imap-message-append (mailbox article &optional flags date-time buffer)
1644 "Append ARTICLE (a buffer) to MAILBOX on server in BUFFER.
1645FLAGS and DATE-TIME is currently not used. Return a cons holding
1646uidvalidity of MAILBOX and UID the newly created article got, or nil
1647on failure."
1648 (let ((mailbox (imap-utf7-encode mailbox)))
1649 (with-current-buffer (or buffer (current-buffer))
1650 (and (let ((imap-current-target-mailbox mailbox))
738421d1
SS
1651 (imap-ok-p
1652 (imap-send-command-wait
c113de23
GM
1653 (list "APPEND \"" mailbox "\" " article))))
1654 (imap-message-appenduid-1 mailbox)))))
738421d1 1655
c113de23
GM
1656(defun imap-body-lines (body)
1657 "Return number of lines in article by looking at the mime bodystructure BODY."
1658 (if (listp body)
1659 (if (stringp (car body))
1660 (cond ((and (string= (upcase (car body)) "TEXT")
1661 (numberp (nth 7 body)))
1662 (nth 7 body))
1663 ((and (string= (upcase (car body)) "MESSAGE")
1664 (numberp (nth 9 body)))
1665 (nth 9 body))
1666 (t 0))
1667 (apply '+ (mapcar 'imap-body-lines body)))
1668 0))
1669
1670(defun imap-envelope-from (from)
1671 "Return a from string line."
1672 (and from
1673 (concat (aref from 0)
1674 (if (aref from 0) " <")
738421d1
SS
1675 (aref from 2)
1676 "@"
c113de23
GM
1677 (aref from 3)
1678 (if (aref from 0) ">"))))
1679
1680\f
1681;; Internal functions.
1682
23f87bed
MB
1683(defun imap-add-callback (tag func)
1684 (setq imap-callbacks (append (list (cons tag func)) imap-callbacks)))
1685
c113de23
GM
1686(defun imap-send-command-1 (cmdstr)
1687 (setq cmdstr (concat cmdstr imap-client-eol))
1688 (and imap-log
23f87bed 1689 (with-current-buffer (get-buffer-create imap-log-buffer)
c113de23
GM
1690 (imap-disable-multibyte)
1691 (buffer-disable-undo)
1692 (goto-char (point-max))
1693 (insert cmdstr)))
1694 (process-send-string imap-process cmdstr))
1695
1696(defun imap-send-command (command &optional buffer)
1697 (with-current-buffer (or buffer (current-buffer))
1698 (if (not (listp command)) (setq command (list command)))
1699 (let ((tag (setq imap-tag (1+ imap-tag)))
1700 cmd cmdstr)
1701 (setq cmdstr (concat (number-to-string imap-tag) " "))
1702 (while (setq cmd (pop command))
1703 (cond ((stringp cmd)
1704 (setq cmdstr (concat cmdstr cmd)))
1705 ((bufferp cmd)
1706 (let ((eol imap-client-eol)
1707 (calcfirst imap-calculate-literal-size-first)
1708 size)
1709 (with-current-buffer cmd
1710 (if calcfirst
1711 (setq size (buffer-size)))
1712 (when (not (equal eol "\r\n"))
1713 ;; XXX modifies buffer!
1714 (goto-char (point-min))
1715 (while (search-forward "\r\n" nil t)
1716 (replace-match eol)))
1717 (if (not calcfirst)
1718 (setq size (buffer-size))))
738421d1 1719 (setq cmdstr
c113de23
GM
1720 (concat cmdstr (format "{%d}" size))))
1721 (unwind-protect
1722 (progn
1723 (imap-send-command-1 cmdstr)
1724 (setq cmdstr nil)
1725 (if (not (eq (imap-wait-for-tag tag) 'INCOMPLETE))
23f87bed 1726 (setq command nil) ;; abort command if no cont-req
c113de23
GM
1727 (let ((process imap-process)
1728 (stream imap-stream)
1729 (eol imap-client-eol))
1730 (with-current-buffer cmd
1731 (and imap-log
1732 (with-current-buffer (get-buffer-create
23f87bed 1733 imap-log-buffer)
c113de23
GM
1734 (imap-disable-multibyte)
1735 (buffer-disable-undo)
1736 (goto-char (point-max))
1737 (insert-buffer-substring cmd)))
1738 (process-send-region process (point-min)
1739 (point-max)))
1740 (process-send-string process imap-client-eol))))
1741 (setq imap-continuation nil)))
1742 ((functionp cmd)
1743 (imap-send-command-1 cmdstr)
1744 (setq cmdstr nil)
1745 (unwind-protect
1746 (if (not (eq (imap-wait-for-tag tag) 'INCOMPLETE))
23f87bed 1747 (setq command nil) ;; abort command if no cont-req
c113de23
GM
1748 (setq command (cons (funcall cmd imap-continuation)
1749 command)))
1750 (setq imap-continuation nil)))
1751 (t
1752 (error "Unknown command type"))))
1753 (if cmdstr
1754 (imap-send-command-1 cmdstr))
1755 tag)))
1756
1757(defun imap-wait-for-tag (tag &optional buffer)
1758 (with-current-buffer (or buffer (current-buffer))
23f87bed
MB
1759 (let (imap-have-messaged)
1760 (while (and (null imap-continuation)
1761 (memq (process-status imap-process) '(open run))
1762 (< imap-reached-tag tag))
1763 (let ((len (/ (point-max) 1024))
1764 message-log-max)
1765 (unless (< len 10)
1766 (setq imap-have-messaged t)
1767 (message "imap read: %dk" len))
1768 (accept-process-output imap-process
1769 (truncate imap-read-timeout)
1770 (truncate (* (- imap-read-timeout
1771 (truncate imap-read-timeout))
1772 1000)))))
1773 ;; A process can die _before_ we have processed everything it
1774 ;; has to say. Moreover, this can happen in between the call to
1775 ;; accept-process-output and the call to process-status in an
1776 ;; iteration of the loop above.
1777 (when (and (null imap-continuation)
1778 (< imap-reached-tag tag))
1779 (accept-process-output imap-process 0 0))
1780 (when imap-have-messaged
1781 (message ""))
1782 (and (memq (process-status imap-process) '(open run))
1783 (or (assq tag imap-failed-tags)
1784 (if imap-continuation
1785 'INCOMPLETE
1786 'OK))))))
c113de23
GM
1787
1788(defun imap-sentinel (process string)
1789 (delete-process process))
1790
1791(defun imap-find-next-line ()
1792 "Return point at end of current line, taking into account literals.
1793Return nil if no complete line has arrived."
1794 (when (re-search-forward (concat imap-server-eol "\\|{\\([0-9]+\\)}"
1795 imap-server-eol)
1796 nil t)
1797 (if (match-string 1)
1798 (if (< (point-max) (+ (point) (string-to-number (match-string 1))))
1799 nil
1800 (goto-char (+ (point) (string-to-number (match-string 1))))
1801 (imap-find-next-line))
1802 (point))))
1803
1804(defun imap-arrival-filter (proc string)
1805 "IMAP process filter."
23f87bed
MB
1806 ;; Sometimes, we are called even though the process has died.
1807 ;; Better abstain from doing stuff in that case.
1808 (when (buffer-name (process-buffer proc))
1809 (with-current-buffer (process-buffer proc)
1810 (goto-char (point-max))
1811 (insert string)
1812 (and imap-log
1813 (with-current-buffer (get-buffer-create imap-log-buffer)
1814 (imap-disable-multibyte)
1815 (buffer-disable-undo)
1816 (goto-char (point-max))
1817 (insert string)))
1818 (let (end)
1819 (goto-char (point-min))
1820 (while (setq end (imap-find-next-line))
1821 (save-restriction
1822 (narrow-to-region (point-min) end)
1823 (delete-backward-char (length imap-server-eol))
1824 (goto-char (point-min))
1825 (unwind-protect
1826 (cond ((eq imap-state 'initial)
1827 (imap-parse-greeting))
1828 ((or (eq imap-state 'auth)
1829 (eq imap-state 'nonauth)
1830 (eq imap-state 'selected)
1831 (eq imap-state 'examine))
1832 (imap-parse-response))
1833 (t
1834 (message "Unknown state %s in arrival filter"
1835 imap-state)))
1836 (delete-region (point-min) (point-max)))))))))
c113de23
GM
1837
1838\f
1839;; Imap parser.
1840
1841(defsubst imap-forward ()
1842 (or (eobp) (forward-char)))
1843
1844;; number = 1*DIGIT
1845;; ; Unsigned 32-bit integer
1846;; ; (0 <= n < 4,294,967,296)
1847
1848(defsubst imap-parse-number ()
1849 (when (looking-at "[0-9]+")
1850 (prog1
1851 (string-to-number (match-string 0))
1852 (goto-char (match-end 0)))))
1853
1854;; literal = "{" number "}" CRLF *CHAR8
1855;; ; Number represents the number of CHAR8s
1856
1857(defsubst imap-parse-literal ()
1858 (when (looking-at "{\\([0-9]+\\)}\r\n")
1859 (let ((pos (match-end 0))
1860 (len (string-to-number (match-string 1))))
1861 (if (< (point-max) (+ pos len))
1862 nil
1863 (goto-char (+ pos len))
1864 (buffer-substring pos (+ pos len))))))
1865
1866;; string = quoted / literal
1867;;
1868;; quoted = DQUOTE *QUOTED-CHAR DQUOTE
1869;;
1870;; QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> /
1871;; "\" quoted-specials
1872;;
1873;; quoted-specials = DQUOTE / "\"
1874;;
1875;; TEXT-CHAR = <any CHAR except CR and LF>
1876
1877(defsubst imap-parse-string ()
1878 (cond ((eq (char-after) ?\")
1879 (forward-char 1)
1880 (let ((p (point)) (name ""))
1881 (skip-chars-forward "^\"\\\\")
1882 (setq name (buffer-substring p (point)))
1883 (while (eq (char-after) ?\\)
1884 (setq p (1+ (point)))
1885 (forward-char 2)
1886 (skip-chars-forward "^\"\\\\")
1887 (setq name (concat name (buffer-substring p (point)))))
1888 (forward-char 1)
1889 name))
1890 ((eq (char-after) ?{)
1891 (imap-parse-literal))))
1892
1893;; nil = "NIL"
1894
1895(defsubst imap-parse-nil ()
1896 (if (looking-at "NIL")
1897 (goto-char (match-end 0))))
1898
1899;; nstring = string / nil
1900
1901(defsubst imap-parse-nstring ()
1902 (or (imap-parse-string)
1903 (and (imap-parse-nil)
1904 nil)))
1905
1906;; astring = atom / string
1907;;
1908;; atom = 1*ATOM-CHAR
1909;;
1910;; ATOM-CHAR = <any CHAR except atom-specials>
1911;;
1912;; atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards /
1913;; quoted-specials
1914;;
1915;; list-wildcards = "%" / "*"
1916;;
1917;; quoted-specials = DQUOTE / "\"
1918
1919(defsubst imap-parse-astring ()
1920 (or (imap-parse-string)
738421d1 1921 (buffer-substring (point)
c113de23
GM
1922 (if (re-search-forward "[(){ \r\n%*\"\\]" nil t)
1923 (goto-char (1- (match-end 0)))
1924 (end-of-line)
1925 (point)))))
1926
1927;; address = "(" addr-name SP addr-adl SP addr-mailbox SP
1928;; addr-host ")"
1929;;
1930;; addr-adl = nstring
1931;; ; Holds route from [RFC-822] route-addr if
0ff9b955 1932;; ; non-nil
c113de23
GM
1933;;
1934;; addr-host = nstring
0ff9b955 1935;; ; nil indicates [RFC-822] group syntax.
c113de23
GM
1936;; ; Otherwise, holds [RFC-822] domain name
1937;;
1938;; addr-mailbox = nstring
0ff9b955
PJ
1939;; ; nil indicates end of [RFC-822] group; if
1940;; ; non-nil and addr-host is nil, holds
c113de23
GM
1941;; ; [RFC-822] group name.
1942;; ; Otherwise, holds [RFC-822] local-part
1943;; ; after removing [RFC-822] quoting
1944;;
1945;; addr-name = nstring
0ff9b955 1946;; ; If non-nil, holds phrase from [RFC-822]
c113de23
GM
1947;; ; mailbox after removing [RFC-822] quoting
1948;;
1949
1950(defsubst imap-parse-address ()
1951 (let (address)
1952 (when (eq (char-after) ?\()
1953 (imap-forward)
1954 (setq address (vector (prog1 (imap-parse-nstring)
1955 (imap-forward))
1956 (prog1 (imap-parse-nstring)
1957 (imap-forward))
1958 (prog1 (imap-parse-nstring)
1959 (imap-forward))
1960 (imap-parse-nstring)))
1961 (when (eq (char-after) ?\))
1962 (imap-forward)
1963 address))))
1964
1965;; address-list = "(" 1*address ")" / nil
1966;;
1967;; nil = "NIL"
1968
1969(defsubst imap-parse-address-list ()
1970 (if (eq (char-after) ?\()
1971 (let (address addresses)
1972 (imap-forward)
1973 (while (and (not (eq (char-after) ?\)))
1974 ;; next line for MS Exchange bug
1975 (progn (and (eq (char-after) ? ) (imap-forward)) t)
1976 (setq address (imap-parse-address)))
1977 (setq addresses (cons address addresses)))
1978 (when (eq (char-after) ?\))
1979 (imap-forward)
1980 (nreverse addresses)))
23f87bed
MB
1981 ;; With assert, the code might not be eval'd.
1982 ;; (assert (imap-parse-nil) t "In imap-parse-address-list")
28d38c0b 1983 (imap-parse-nil)))
c113de23
GM
1984
1985;; mailbox = "INBOX" / astring
1986;; ; INBOX is case-insensitive. All case variants of
1987;; ; INBOX (e.g. "iNbOx") MUST be interpreted as INBOX
1988;; ; not as an astring. An astring which consists of
1989;; ; the case-insensitive sequence "I" "N" "B" "O" "X"
1990;; ; is considered to be INBOX and not an astring.
1991;; ; Refer to section 5.1 for further
1992;; ; semantic details of mailbox names.
1993
1994(defsubst imap-parse-mailbox ()
1995 (let ((mailbox (imap-parse-astring)))
1996 (if (string-equal "INBOX" (upcase mailbox))
1997 "INBOX"
1998 mailbox)))
1999
2000;; greeting = "*" SP (resp-cond-auth / resp-cond-bye) CRLF
2001;;
2002;; resp-cond-auth = ("OK" / "PREAUTH") SP resp-text
2003;; ; Authentication condition
2004;;
2005;; resp-cond-bye = "BYE" SP resp-text
2006
2007(defun imap-parse-greeting ()
2008 "Parse a IMAP greeting."
2009 (cond ((looking-at "\\* OK ")
2010 (setq imap-state 'nonauth))
2011 ((looking-at "\\* PREAUTH ")
2012 (setq imap-state 'auth))
2013 ((looking-at "\\* BYE ")
2014 (setq imap-state 'closed))))
2015
2016;; response = *(continue-req / response-data) response-done
2017;;
2018;; continue-req = "+" SP (resp-text / base64) CRLF
2019;;
2020;; response-data = "*" SP (resp-cond-state / resp-cond-bye /
2021;; mailbox-data / message-data / capability-data) CRLF
2022;;
2023;; response-done = response-tagged / response-fatal
2024;;
2025;; response-fatal = "*" SP resp-cond-bye CRLF
2026;; ; Server closes connection immediately
2027;;
2028;; response-tagged = tag SP resp-cond-state CRLF
2029;;
2030;; resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text
2031;; ; Status condition
2032;;
2033;; resp-cond-bye = "BYE" SP resp-text
2034;;
2035;; mailbox-data = "FLAGS" SP flag-list /
23f87bed 2036;; "LIST" SP mailbox-list /
c113de23
GM
2037;; "LSUB" SP mailbox-list /
2038;; "SEARCH" *(SP nz-number) /
2039;; "STATUS" SP mailbox SP "("
2040;; [status-att SP number *(SP status-att SP number)] ")" /
2041;; number SP "EXISTS" /
2042;; number SP "RECENT"
2043;;
2044;; message-data = nz-number SP ("EXPUNGE" / ("FETCH" SP msg-att))
2045;;
2046;; capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev1"
2047;; *(SP capability)
2048;; ; IMAP4rev1 servers which offer RFC 1730
2049;; ; compatibility MUST list "IMAP4" as the first
2050;; ; capability.
2051
2052(defun imap-parse-response ()
2053 "Parse a IMAP command response."
2054 (let (token)
2055 (case (setq token (read (current-buffer)))
2056 (+ (setq imap-continuation
2057 (or (buffer-substring (min (point-max) (1+ (point)))
2058 (point-max))
2059 t)))
2060 (* (case (prog1 (setq token (read (current-buffer)))
2061 (imap-forward))
2062 (OK (imap-parse-resp-text))
2063 (NO (imap-parse-resp-text))
2064 (BAD (imap-parse-resp-text))
2065 (BYE (imap-parse-resp-text))
2066 (FLAGS (imap-mailbox-put 'flags (imap-parse-flag-list)))
2067 (LIST (imap-parse-data-list 'list))
2068 (LSUB (imap-parse-data-list 'lsub))
738421d1
SS
2069 (SEARCH (imap-mailbox-put
2070 'search
c113de23
GM
2071 (read (concat "(" (buffer-substring (point) (point-max)) ")"))))
2072 (STATUS (imap-parse-status))
738421d1 2073 (CAPABILITY (setq imap-capability
23f87bed
MB
2074 (read (concat "(" (upcase (buffer-substring
2075 (point) (point-max)))
2076 ")"))))
c113de23
GM
2077 (ACL (imap-parse-acl))
2078 (t (case (prog1 (read (current-buffer))
2079 (imap-forward))
2080 (EXISTS (imap-mailbox-put 'exists token))
2081 (RECENT (imap-mailbox-put 'recent token))
2082 (EXPUNGE t)
2083 (FETCH (imap-parse-fetch token))
2084 (t (message "Garbage: %s" (buffer-string)))))))
2085 (t (let (status)
2086 (if (not (integerp token))
2087 (message "Garbage: %s" (buffer-string))
2088 (case (prog1 (setq status (read (current-buffer)))
2089 (imap-forward))
2090 (OK (progn
2091 (setq imap-reached-tag (max imap-reached-tag token))
2092 (imap-parse-resp-text)))
2093 (NO (progn
2094 (setq imap-reached-tag (max imap-reached-tag token))
2095 (save-excursion
2096 (imap-parse-resp-text))
2097 (let (code text)
2098 (when (eq (char-after) ?\[)
2099 (setq code (buffer-substring (point)
2100 (search-forward "]")))
2101 (imap-forward))
2102 (setq text (buffer-substring (point) (point-max)))
738421d1 2103 (push (list token status code text)
c113de23
GM
2104 imap-failed-tags))))
2105 (BAD (progn
2106 (setq imap-reached-tag (max imap-reached-tag token))
2107 (save-excursion
2108 (imap-parse-resp-text))
2109 (let (code text)
2110 (when (eq (char-after) ?\[)
2111 (setq code (buffer-substring (point)
2112 (search-forward "]")))
2113 (imap-forward))
2114 (setq text (buffer-substring (point) (point-max)))
2115 (push (list token status code text) imap-failed-tags)
2116 (error "Internal error, tag %s status %s code %s text %s"
2117 token status code text))))
23f87bed
MB
2118 (t (message "Garbage: %s" (buffer-string))))
2119 (when (assq token imap-callbacks)
2120 (funcall (cdr (assq token imap-callbacks)) token status)
2121 (setq imap-callbacks
2122 (imap-remassoc token imap-callbacks)))))))))
c113de23
GM
2123
2124;; resp-text = ["[" resp-text-code "]" SP] text
2125;;
2126;; text = 1*TEXT-CHAR
2127;;
2128;; TEXT-CHAR = <any CHAR except CR and LF>
2129
2130(defun imap-parse-resp-text ()
2131 (imap-parse-resp-text-code))
2132
2133;; resp-text-code = "ALERT" /
2134;; "BADCHARSET [SP "(" astring *(SP astring) ")" ] /
738421d1 2135;; "NEWNAME" SP string SP string /
c113de23 2136;; "PARSE" /
738421d1 2137;; "PERMANENTFLAGS" SP "("
c113de23 2138;; [flag-perm *(SP flag-perm)] ")" /
738421d1
SS
2139;; "READ-ONLY" /
2140;; "READ-WRITE" /
23f87bed 2141;; "TRYCREATE" /
738421d1 2142;; "UIDNEXT" SP nz-number /
c113de23
GM
2143;; "UIDVALIDITY" SP nz-number /
2144;; "UNSEEN" SP nz-number /
2145;; resp-text-atom [SP 1*<any TEXT-CHAR except "]">]
2146;;
2147;; resp_code_apnd = "APPENDUID" SPACE nz_number SPACE uniqueid
2148;;
2149;; resp_code_copy = "COPYUID" SPACE nz_number SPACE set SPACE set
2150;;
2151;; set = sequence-num / (sequence-num ":" sequence-num) /
2152;; (set "," set)
2153;; ; Identifies a set of messages. For message
2154;; ; sequence numbers, these are consecutive
2155;; ; numbers from 1 to the number of messages in
2156;; ; the mailbox
2157;; ; Comma delimits individual numbers, colon
2158;; ; delimits between two numbers inclusive.
2159;; ; Example: 2,4:7,9,12:* is 2,4,5,6,7,9,12,13,
2160;; ; 14,15 for a mailbox with 15 messages.
738421d1 2161;;
c113de23
GM
2162;; sequence-num = nz-number / "*"
2163;; ; * is the largest number in use. For message
2164;; ; sequence numbers, it is the number of messages
2165;; ; in the mailbox. For unique identifiers, it is
2166;; ; the unique identifier of the last message in
2167;; ; the mailbox.
2168;;
2169;; flag-perm = flag / "\*"
2170;;
2171;; flag = "\Answered" / "\Flagged" / "\Deleted" /
2172;; "\Seen" / "\Draft" / flag-keyword / flag-extension
2173;; ; Does not include "\Recent"
2174;;
2175;; flag-extension = "\" atom
2176;; ; Future expansion. Client implementations
2177;; ; MUST accept flag-extension flags. Server
2178;; ; implementations MUST NOT generate
2179;; ; flag-extension flags except as defined by
2180;; ; future standard or standards-track
2181;; ; revisions of this specification.
2182;;
2183;; flag-keyword = atom
2184;;
2185;; resp-text-atom = 1*<any ATOM-CHAR except "]">
2186
2187(defun imap-parse-resp-text-code ()
23f87bed
MB
2188 ;; xxx next line for stalker communigate pro 3.3.1 bug
2189 (when (looking-at " \\[")
2190 (imap-forward))
c113de23
GM
2191 (when (eq (char-after) ?\[)
2192 (imap-forward)
2193 (cond ((search-forward "PERMANENTFLAGS " nil t)
2194 (imap-mailbox-put 'permanentflags (imap-parse-flag-list)))
23f87bed
MB
2195 ((search-forward "UIDNEXT \\([0-9]+\\)" nil t)
2196 (imap-mailbox-put 'uidnext (match-string 1)))
c113de23 2197 ((search-forward "UNSEEN " nil t)
23f87bed 2198 (imap-mailbox-put 'first-unseen (read (current-buffer))))
c113de23
GM
2199 ((looking-at "UIDVALIDITY \\([0-9]+\\)")
2200 (imap-mailbox-put 'uidvalidity (match-string 1)))
2201 ((search-forward "READ-ONLY" nil t)
2202 (imap-mailbox-put 'read-only t))
2203 ((search-forward "NEWNAME " nil t)
2204 (let (oldname newname)
2205 (setq oldname (imap-parse-string))
2206 (imap-forward)
2207 (setq newname (imap-parse-string))
2208 (imap-mailbox-put 'newname newname oldname)))
2209 ((search-forward "TRYCREATE" nil t)
2210 (imap-mailbox-put 'trycreate t imap-current-target-mailbox))
2211 ((looking-at "APPENDUID \\([0-9]+\\) \\([0-9]+\\)")
2212 (imap-mailbox-put 'appenduid
2213 (list (match-string 1)
2214 (string-to-number (match-string 2)))
2215 imap-current-target-mailbox))
2216 ((looking-at "COPYUID \\([0-9]+\\) \\([0-9,:]+\\) \\([0-9,:]+\\)")
2217 (imap-mailbox-put 'copyuid (list (match-string 1)
2218 (match-string 2)
2219 (match-string 3))
2220 imap-current-target-mailbox))
2221 ((search-forward "ALERT] " nil t)
2222 (message "Imap server %s information: %s" imap-server
2223 (buffer-substring (point) (point-max)))))))
2224
2225;; mailbox-list = "(" [mbx-list-flags] ")" SP
2226;; (DQUOTE QUOTED-CHAR DQUOTE / nil) SP mailbox
2227;;
2228;; mbx-list-flags = *(mbx-list-oflag SP) mbx-list-sflag
2229;; *(SP mbx-list-oflag) /
2230;; mbx-list-oflag *(SP mbx-list-oflag)
2231;;
2232;; mbx-list-oflag = "\Noinferiors" / flag-extension
2233;; ; Other flags; multiple possible per LIST response
2234;;
2235;; mbx-list-sflag = "\Noselect" / "\Marked" / "\Unmarked"
2236;; ; Selectability flags; only one per LIST response
2237;;
2238;; QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> /
2239;; "\" quoted-specials
2240;;
2241;; quoted-specials = DQUOTE / "\"
2242
2243(defun imap-parse-data-list (type)
2244 (let (flags delimiter mailbox)
2245 (setq flags (imap-parse-flag-list))
2246 (when (looking-at " NIL\\| \"\\\\?\\(.\\)\"")
2247 (setq delimiter (match-string 1))
2248 (goto-char (1+ (match-end 0)))
2249 (when (setq mailbox (imap-parse-mailbox))
2250 (imap-mailbox-put type t mailbox)
2251 (imap-mailbox-put 'list-flags flags mailbox)
2252 (imap-mailbox-put 'delimiter delimiter mailbox)))))
2253
2254;; msg_att ::= "(" 1#("ENVELOPE" SPACE envelope /
2255;; "FLAGS" SPACE "(" #(flag / "\Recent") ")" /
2256;; "INTERNALDATE" SPACE date_time /
2257;; "RFC822" [".HEADER" / ".TEXT"] SPACE nstring /
2258;; "RFC822.SIZE" SPACE number /
2259;; "BODY" ["STRUCTURE"] SPACE body /
2260;; "BODY" section ["<" number ">"] SPACE nstring /
2261;; "UID" SPACE uniqueid) ")"
738421d1 2262;;
c113de23
GM
2263;; date_time ::= <"> date_day_fixed "-" date_month "-" date_year
2264;; SPACE time SPACE zone <">
738421d1 2265;;
c113de23
GM
2266;; section ::= "[" [section_text / (nz_number *["." nz_number]
2267;; ["." (section_text / "MIME")])] "]"
738421d1 2268;;
c113de23
GM
2269;; section_text ::= "HEADER" / "HEADER.FIELDS" [".NOT"]
2270;; SPACE header_list / "TEXT"
738421d1 2271;;
c113de23 2272;; header_fld_name ::= astring
738421d1 2273;;
c113de23
GM
2274;; header_list ::= "(" 1#header_fld_name ")"
2275
2276(defsubst imap-parse-header-list ()
2277 (when (eq (char-after) ?\()
2278 (let (strlist)
2279 (while (not (eq (char-after) ?\)))
2280 (imap-forward)
2281 (push (imap-parse-astring) strlist))
2282 (imap-forward)
2283 (nreverse strlist))))
2284
2285(defsubst imap-parse-fetch-body-section ()
738421d1 2286 (let ((section
c113de23
GM
2287 (buffer-substring (point) (1- (re-search-forward "[] ]" nil t)))))
2288 (if (eq (char-before) ? )
2289 (prog1
2290 (mapconcat 'identity (cons section (imap-parse-header-list)) " ")
2291 (search-forward "]" nil t))
2292 section)))
2293
2294(defun imap-parse-fetch (response)
2295 (when (eq (char-after) ?\()
738421d1 2296 (let (uid flags envelope internaldate rfc822 rfc822header rfc822text
23f87bed 2297 rfc822size body bodydetail bodystructure flags-empty)
c113de23
GM
2298 (while (not (eq (char-after) ?\)))
2299 (imap-forward)
2300 (let ((token (read (current-buffer))))
2301 (imap-forward)
2302 (cond ((eq token 'UID)
23f87bed
MB
2303 (setq uid (condition-case ()
2304 (read (current-buffer))
2305 (error))))
c113de23 2306 ((eq token 'FLAGS)
23f87bed
MB
2307 (setq flags (imap-parse-flag-list))
2308 (if (not flags)
2309 (setq flags-empty 't)))
c113de23
GM
2310 ((eq token 'ENVELOPE)
2311 (setq envelope (imap-parse-envelope)))
2312 ((eq token 'INTERNALDATE)
2313 (setq internaldate (imap-parse-string)))
2314 ((eq token 'RFC822)
2315 (setq rfc822 (imap-parse-nstring)))
2316 ((eq token 'RFC822.HEADER)
2317 (setq rfc822header (imap-parse-nstring)))
2318 ((eq token 'RFC822.TEXT)
2319 (setq rfc822text (imap-parse-nstring)))
2320 ((eq token 'RFC822.SIZE)
2321 (setq rfc822size (read (current-buffer))))
2322 ((eq token 'BODY)
2323 (if (eq (char-before) ?\[)
2324 (push (list
2325 (upcase (imap-parse-fetch-body-section))
2326 (and (eq (char-after) ?<)
2327 (buffer-substring (1+ (point))
2328 (search-forward ">" nil t)))
2329 (progn (imap-forward)
2330 (imap-parse-nstring)))
2331 bodydetail)
2332 (setq body (imap-parse-body))))
2333 ((eq token 'BODYSTRUCTURE)
2334 (setq bodystructure (imap-parse-body))))))
2335 (when uid
2336 (setq imap-current-message uid)
2337 (imap-message-put uid 'UID uid)
23f87bed 2338 (and (or flags flags-empty) (imap-message-put uid 'FLAGS flags))
c113de23
GM
2339 (and envelope (imap-message-put uid 'ENVELOPE envelope))
2340 (and internaldate (imap-message-put uid 'INTERNALDATE internaldate))
2341 (and rfc822 (imap-message-put uid 'RFC822 rfc822))
2342 (and rfc822header (imap-message-put uid 'RFC822.HEADER rfc822header))
2343 (and rfc822text (imap-message-put uid 'RFC822.TEXT rfc822text))
2344 (and rfc822size (imap-message-put uid 'RFC822.SIZE rfc822size))
2345 (and body (imap-message-put uid 'BODY body))
2346 (and bodydetail (imap-message-put uid 'BODYDETAIL bodydetail))
2347 (and bodystructure (imap-message-put uid 'BODYSTRUCTURE bodystructure))
2348 (run-hooks 'imap-fetch-data-hook)))))
2349
2350;; mailbox-data = ...
2351;; "STATUS" SP mailbox SP "("
738421d1 2352;; [status-att SP number
c113de23
GM
2353;; *(SP status-att SP number)] ")"
2354;; ...
2355;;
2356;; status-att = "MESSAGES" / "RECENT" / "UIDNEXT" / "UIDVALIDITY" /
2357;; "UNSEEN"
2358
2359(defun imap-parse-status ()
2360 (let ((mailbox (imap-parse-mailbox)))
23f87bed
MB
2361 (if (eq (char-after) ? )
2362 (forward-char))
2363 (when (and mailbox (eq (char-after) ?\())
2364 (while (and (not (eq (char-after) ?\)))
2365 (or (forward-char) t)
2366 (looking-at "\\([A-Za-z]+\\) "))
2367 (let ((token (match-string 1)))
2368 (goto-char (match-end 0))
2369 (cond ((string= token "MESSAGES")
c113de23 2370 (imap-mailbox-put 'messages (read (current-buffer)) mailbox))
23f87bed 2371 ((string= token "RECENT")
c113de23 2372 (imap-mailbox-put 'recent (read (current-buffer)) mailbox))
23f87bed
MB
2373 ((string= token "UIDNEXT")
2374 (and (looking-at "[0-9]+")
2375 (imap-mailbox-put 'uidnext (match-string 0) mailbox)
2376 (goto-char (match-end 0))))
2377 ((string= token "UIDVALIDITY")
2378 (and (looking-at "[0-9]+")
2379 (imap-mailbox-put 'uidvalidity (match-string 0) mailbox)
2380 (goto-char (match-end 0))))
2381 ((string= token "UNSEEN")
c113de23
GM
2382 (imap-mailbox-put 'unseen (read (current-buffer)) mailbox))
2383 (t
738421d1 2384 (message "Unknown status data %s in mailbox %s ignored"
23f87bed
MB
2385 token mailbox)
2386 (read (current-buffer)))))))))
c113de23
GM
2387
2388;; acl_data ::= "ACL" SPACE mailbox *(SPACE identifier SPACE
2389;; rights)
2390;;
2391;; identifier ::= astring
2392;;
2393;; rights ::= astring
2394
2395(defun imap-parse-acl ()
2396 (let ((mailbox (imap-parse-mailbox))
2397 identifier rights acl)
2398 (while (eq (char-after) ?\ )
2399 (imap-forward)
2400 (setq identifier (imap-parse-astring))
2401 (imap-forward)
2402 (setq rights (imap-parse-astring))
2403 (setq acl (append acl (list (cons identifier rights)))))
2404 (imap-mailbox-put 'acl acl mailbox)))
2405
2406;; flag-list = "(" [flag *(SP flag)] ")"
2407;;
2408;; flag = "\Answered" / "\Flagged" / "\Deleted" /
2409;; "\Seen" / "\Draft" / flag-keyword / flag-extension
2410;; ; Does not include "\Recent"
2411;;
2412;; flag-keyword = atom
2413;;
2414;; flag-extension = "\" atom
2415;; ; Future expansion. Client implementations
2416;; ; MUST accept flag-extension flags. Server
2417;; ; implementations MUST NOT generate
2418;; ; flag-extension flags except as defined by
2419;; ; future standard or standards-track
2420;; ; revisions of this specification.
2421
2422(defun imap-parse-flag-list ()
2423 (let (flag-list start)
e62e7654 2424 (assert (eq (char-after) ?\() nil "In imap-parse-flag-list")
a2617484 2425 (while (and (not (eq (char-after) ?\)))
23f87bed
MB
2426 (setq start (progn
2427 (imap-forward)
2428 ;; next line for Courier IMAP bug.
2429 (skip-chars-forward " ")
2430 (point)))
2d5fdf63 2431 (> (skip-chars-forward "^ )" (imap-point-at-eol)) 0))
a2617484 2432 (push (buffer-substring start (point)) flag-list))
e62e7654 2433 (assert (eq (char-after) ?\)) nil "In imap-parse-flag-list")
a2617484
DL
2434 (imap-forward)
2435 (nreverse flag-list)))
c113de23
GM
2436
2437;; envelope = "(" env-date SP env-subject SP env-from SP env-sender SP
2438;; env-reply-to SP env-to SP env-cc SP env-bcc SP
2439;; env-in-reply-to SP env-message-id ")"
2440;;
2441;; env-bcc = "(" 1*address ")" / nil
2442;;
2443;; env-cc = "(" 1*address ")" / nil
2444;;
2445;; env-date = nstring
2446;;
2447;; env-from = "(" 1*address ")" / nil
2448;;
2449;; env-in-reply-to = nstring
2450;;
2451;; env-message-id = nstring
2452;;
2453;; env-reply-to = "(" 1*address ")" / nil
2454;;
2455;; env-sender = "(" 1*address ")" / nil
2456;;
2457;; env-subject = nstring
2458;;
2459;; env-to = "(" 1*address ")" / nil
2460
2461(defun imap-parse-envelope ()
2462 (when (eq (char-after) ?\()
2463 (imap-forward)
23f87bed 2464 (vector (prog1 (imap-parse-nstring) ;; date
c113de23 2465 (imap-forward))
23f87bed 2466 (prog1 (imap-parse-nstring) ;; subject
c113de23 2467 (imap-forward))
23f87bed 2468 (prog1 (imap-parse-address-list) ;; from
c113de23 2469 (imap-forward))
23f87bed 2470 (prog1 (imap-parse-address-list) ;; sender
c113de23 2471 (imap-forward))
23f87bed 2472 (prog1 (imap-parse-address-list) ;; reply-to
c113de23 2473 (imap-forward))
23f87bed 2474 (prog1 (imap-parse-address-list) ;; to
c113de23 2475 (imap-forward))
23f87bed 2476 (prog1 (imap-parse-address-list) ;; cc
c113de23 2477 (imap-forward))
23f87bed 2478 (prog1 (imap-parse-address-list) ;; bcc
c113de23 2479 (imap-forward))
23f87bed 2480 (prog1 (imap-parse-nstring) ;; in-reply-to
c113de23 2481 (imap-forward))
23f87bed 2482 (prog1 (imap-parse-nstring) ;; message-id
c113de23
GM
2483 (imap-forward)))))
2484
2485;; body-fld-param = "(" string SP string *(SP string SP string) ")" / nil
2486
2487(defsubst imap-parse-string-list ()
23f87bed 2488 (cond ((eq (char-after) ?\() ;; body-fld-param
c113de23
GM
2489 (let (strlist str)
2490 (imap-forward)
2491 (while (setq str (imap-parse-string))
2492 (push str strlist)
2493 ;; buggy stalker communigate pro 3.0 doesn't print SPC
2494 ;; between body-fld-param's sometimes
2495 (or (eq (char-after) ?\")
2496 (imap-forward)))
2497 (nreverse strlist)))
2498 ((imap-parse-nil)
2499 nil)))
2500
2501;; body-extension = nstring / number /
2502;; "(" body-extension *(SP body-extension) ")"
2503;; ; Future expansion. Client implementations
2504;; ; MUST accept body-extension fields. Server
2505;; ; implementations MUST NOT generate
2506;; ; body-extension fields except as defined by
2507;; ; future standard or standards-track
2508;; ; revisions of this specification.
2509
2510(defun imap-parse-body-extension ()
2511 (if (eq (char-after) ?\()
2512 (let (b-e)
2513 (imap-forward)
2514 (push (imap-parse-body-extension) b-e)
2515 (while (eq (char-after) ?\ )
2516 (imap-forward)
2517 (push (imap-parse-body-extension) b-e))
e62e7654 2518 (assert (eq (char-after) ?\)) nil "In imap-parse-body-extension")
c113de23
GM
2519 (imap-forward)
2520 (nreverse b-e))
2521 (or (imap-parse-number)
2522 (imap-parse-nstring))))
2523
2524;; body-ext-1part = body-fld-md5 [SP body-fld-dsp [SP body-fld-lang
2525;; *(SP body-extension)]]
2526;; ; MUST NOT be returned on non-extensible
2527;; ; "BODY" fetch
2528;;
2529;; body-ext-mpart = body-fld-param [SP body-fld-dsp [SP body-fld-lang
2530;; *(SP body-extension)]]
2531;; ; MUST NOT be returned on non-extensible
2532;; ; "BODY" fetch
2533
2534(defsubst imap-parse-body-ext ()
2535 (let (ext)
23f87bed 2536 (when (eq (char-after) ?\ ) ;; body-fld-dsp
c113de23
GM
2537 (imap-forward)
2538 (let (dsp)
2539 (if (eq (char-after) ?\()
2540 (progn
2541 (imap-forward)
2542 (push (imap-parse-string) dsp)
2543 (imap-forward)
2544 (push (imap-parse-string-list) dsp)
2545 (imap-forward))
23f87bed
MB
2546 ;; With assert, the code might not be eval'd.
2547 ;; (assert (imap-parse-nil) t "In imap-parse-body-ext")
28d38c0b 2548 (imap-parse-nil))
c113de23 2549 (push (nreverse dsp) ext))
23f87bed 2550 (when (eq (char-after) ?\ ) ;; body-fld-lang
c113de23
GM
2551 (imap-forward)
2552 (if (eq (char-after) ?\()
2553 (push (imap-parse-string-list) ext)
2554 (push (imap-parse-nstring) ext))
23f87bed 2555 (while (eq (char-after) ?\ ) ;; body-extension
c113de23
GM
2556 (imap-forward)
2557 (setq ext (append (imap-parse-body-extension) ext)))))
2558 ext))
2559
2560;; body = "(" body-type-1part / body-type-mpart ")"
2561;;
2562;; body-ext-1part = body-fld-md5 [SP body-fld-dsp [SP body-fld-lang
2563;; *(SP body-extension)]]
2564;; ; MUST NOT be returned on non-extensible
2565;; ; "BODY" fetch
2566;;
2567;; body-ext-mpart = body-fld-param [SP body-fld-dsp [SP body-fld-lang
2568;; *(SP body-extension)]]
2569;; ; MUST NOT be returned on non-extensible
2570;; ; "BODY" fetch
2571;;
2572;; body-fields = body-fld-param SP body-fld-id SP body-fld-desc SP
2573;; body-fld-enc SP body-fld-octets
2574;;
2575;; body-fld-desc = nstring
2576;;
2577;; body-fld-dsp = "(" string SP body-fld-param ")" / nil
2578;;
2579;; body-fld-enc = (DQUOTE ("7BIT" / "8BIT" / "BINARY" / "BASE64"/
2580;; "QUOTED-PRINTABLE") DQUOTE) / string
2581;;
2582;; body-fld-id = nstring
2583;;
2584;; body-fld-lang = nstring / "(" string *(SP string) ")"
2585;;
2586;; body-fld-lines = number
2587;;
2588;; body-fld-md5 = nstring
2589;;
2590;; body-fld-octets = number
2591;;
2592;; body-fld-param = "(" string SP string *(SP string SP string) ")" / nil
2593;;
2594;; body-type-1part = (body-type-basic / body-type-msg / body-type-text)
2595;; [SP body-ext-1part]
2596;;
2597;; body-type-basic = media-basic SP body-fields
2598;; ; MESSAGE subtype MUST NOT be "RFC822"
2599;;
2600;; body-type-msg = media-message SP body-fields SP envelope
2601;; SP body SP body-fld-lines
2602;;
2603;; body-type-text = media-text SP body-fields SP body-fld-lines
2604;;
2605;; body-type-mpart = 1*body SP media-subtype
2606;; [SP body-ext-mpart]
2607;;
2608;; media-basic = ((DQUOTE ("APPLICATION" / "AUDIO" / "IMAGE" /
2609;; "MESSAGE" / "VIDEO") DQUOTE) / string) SP media-subtype
2610;; ; Defined in [MIME-IMT]
2611;;
2612;; media-message = DQUOTE "MESSAGE" DQUOTE SP DQUOTE "RFC822" DQUOTE
2613;; ; Defined in [MIME-IMT]
2614;;
2615;; media-subtype = string
2616;; ; Defined in [MIME-IMT]
2617;;
2618;; media-text = DQUOTE "TEXT" DQUOTE SP media-subtype
2619;; ; Defined in [MIME-IMT]
2620
2621(defun imap-parse-body ()
2622 (let (body)
2623 (when (eq (char-after) ?\()
2624 (imap-forward)
2625 (if (eq (char-after) ?\()
2626 (let (subbody)
2627 (while (and (eq (char-after) ?\()
2628 (setq subbody (imap-parse-body)))
23f87bed 2629 ;; buggy stalker communigate pro 3.0 insert a SPC between
c113de23
GM
2630 ;; parts in multiparts
2631 (when (and (eq (char-after) ?\ )
2632 (eq (char-after (1+ (point))) ?\())
2633 (imap-forward))
2634 (push subbody body))
2635 (imap-forward)
23f87bed
MB
2636 (push (imap-parse-string) body) ;; media-subtype
2637 (when (eq (char-after) ?\ ) ;; body-ext-mpart:
c113de23 2638 (imap-forward)
23f87bed 2639 (if (eq (char-after) ?\() ;; body-fld-param
c113de23
GM
2640 (push (imap-parse-string-list) body)
2641 (push (and (imap-parse-nil) nil) body))
2642 (setq body
23f87bed 2643 (append (imap-parse-body-ext) body))) ;; body-ext-...
e62e7654 2644 (assert (eq (char-after) ?\)) nil "In imap-parse-body")
c113de23
GM
2645 (imap-forward)
2646 (nreverse body))
2647
23f87bed 2648 (push (imap-parse-string) body) ;; media-type
c113de23 2649 (imap-forward)
23f87bed 2650 (push (imap-parse-string) body) ;; media-subtype
c113de23
GM
2651 (imap-forward)
2652 ;; next line for Sun SIMS bug
2653 (and (eq (char-after) ? ) (imap-forward))
23f87bed 2654 (if (eq (char-after) ?\() ;; body-fld-param
c113de23
GM
2655 (push (imap-parse-string-list) body)
2656 (push (and (imap-parse-nil) nil) body))
2657 (imap-forward)
23f87bed 2658 (push (imap-parse-nstring) body) ;; body-fld-id
c113de23 2659 (imap-forward)
23f87bed 2660 (push (imap-parse-nstring) body) ;; body-fld-desc
c113de23 2661 (imap-forward)
a2617484 2662 ;; next `or' for Sun SIMS bug, it regard body-fld-enc as a
0ff9b955 2663 ;; nstring and return nil instead of defaulting back to 7BIT
a2617484 2664 ;; as the standard says.
23f87bed 2665 (push (or (imap-parse-nstring) "7BIT") body) ;; body-fld-enc
c113de23 2666 (imap-forward)
23f87bed 2667 (push (imap-parse-number) body) ;; body-fld-octets
c113de23 2668
23f87bed 2669 ;; ok, we're done parsing the required parts, what comes now is one
c113de23
GM
2670 ;; of three things:
2671 ;;
2672 ;; envelope (then we're parsing body-type-msg)
2673 ;; body-fld-lines (then we're parsing body-type-text)
2674 ;; body-ext-1part (then we're parsing body-type-basic)
2675 ;;
23f87bed
MB
2676 ;; the problem is that the two first are in turn optionally followed
2677;; by the third. So we parse the first two here (if there are any)...
c113de23
GM
2678
2679 (when (eq (char-after) ?\ )
2680 (imap-forward)
2681 (let (lines)
23f87bed
MB
2682 (cond ((eq (char-after) ?\() ;; body-type-msg:
2683 (push (imap-parse-envelope) body) ;; envelope
c113de23 2684 (imap-forward)
23f87bed 2685 (push (imap-parse-body) body) ;; body
c113de23
GM
2686 ;; buggy stalker communigate pro 3.0 doesn't print
2687 ;; number of lines in message/rfc822 attachment
2688 (if (eq (char-after) ?\))
2689 (push 0 body)
2690 (imap-forward)
2691 (push (imap-parse-number) body))) ;; body-fld-lines
23f87bed
MB
2692 ((setq lines (imap-parse-number)) ;; body-type-text:
2693 (push lines body)) ;; body-fld-lines
c113de23 2694 (t
23f87bed 2695 (backward-char))))) ;; no match...
c113de23
GM
2696
2697 ;; ...and then parse the third one here...
2698
23f87bed 2699 (when (eq (char-after) ?\ ) ;; body-ext-1part:
c113de23 2700 (imap-forward)
23f87bed
MB
2701 (push (imap-parse-nstring) body) ;; body-fld-md5
2702 (setq body (append (imap-parse-body-ext) body))) ;; body-ext-1part..
738421d1 2703
e62e7654 2704 (assert (eq (char-after) ?\)) nil "In imap-parse-body 2")
c113de23
GM
2705 (imap-forward)
2706 (nreverse body)))))
2707
2708(when imap-debug ; (untrace-all)
2709 (require 'trace)
23f87bed
MB
2710 (buffer-disable-undo (get-buffer-create imap-debug-buffer))
2711 (mapcar (lambda (f) (trace-function-background f imap-debug-buffer))
c113de23 2712 '(
c113de23
GM
2713 imap-utf7-encode
2714 imap-utf7-decode
2715 imap-error-text
2716 imap-kerberos4s-p
2717 imap-kerberos4-open
2718 imap-ssl-p
2719 imap-ssl-open
2720 imap-network-p
2721 imap-network-open
2722 imap-interactive-login
2723 imap-kerberos4a-p
2724 imap-kerberos4-auth
2725 imap-cram-md5-p
2726 imap-cram-md5-auth
2727 imap-login-p
2728 imap-login-auth
2729 imap-anonymous-p
2730 imap-anonymous-auth
2731 imap-open-1
2732 imap-open
2733 imap-opened
2734 imap-authenticate
2735 imap-close
2736 imap-capability
2737 imap-namespace
2738 imap-send-command-wait
2739 imap-mailbox-put
2740 imap-mailbox-get
2741 imap-mailbox-map-1
2742 imap-mailbox-map
2743 imap-current-mailbox
2744 imap-current-mailbox-p-1
2745 imap-current-mailbox-p
2746 imap-mailbox-select-1
2747 imap-mailbox-select
2748 imap-mailbox-examine-1
2749 imap-mailbox-examine
2750 imap-mailbox-unselect
2751 imap-mailbox-expunge
2752 imap-mailbox-close
2753 imap-mailbox-create-1
2754 imap-mailbox-create
2755 imap-mailbox-delete
2756 imap-mailbox-rename
2757 imap-mailbox-lsub
2758 imap-mailbox-list
2759 imap-mailbox-subscribe
2760 imap-mailbox-unsubscribe
2761 imap-mailbox-status
2762 imap-mailbox-acl-get
2763 imap-mailbox-acl-set
2764 imap-mailbox-acl-delete
2765 imap-current-message
2766 imap-list-to-message-set
2767 imap-fetch-asynch
2768 imap-fetch
2769 imap-message-put
2770 imap-message-get
2771 imap-message-map
2772 imap-search
2773 imap-message-flag-permanent-p
2774 imap-message-flags-set
2775 imap-message-flags-del
2776 imap-message-flags-add
2777 imap-message-copyuid-1
2778 imap-message-copyuid
2779 imap-message-copy
2780 imap-message-appenduid-1
2781 imap-message-appenduid
2782 imap-message-append
2783 imap-body-lines
2784 imap-envelope-from
2785 imap-send-command-1
2786 imap-send-command
2787 imap-wait-for-tag
2788 imap-sentinel
2789 imap-find-next-line
2790 imap-arrival-filter
2791 imap-parse-greeting
2792 imap-parse-response
2793 imap-parse-resp-text
2794 imap-parse-resp-text-code
2795 imap-parse-data-list
2796 imap-parse-fetch
2797 imap-parse-status
2798 imap-parse-acl
2799 imap-parse-flag-list
2800 imap-parse-envelope
2801 imap-parse-body-extension
2802 imap-parse-body
2803 )))
738421d1 2804
c113de23
GM
2805(provide 'imap)
2806
ab5796a9 2807;;; arch-tag: 27369ed6-33e4-482f-96f1-8bb906ba70f7
c113de23 2808;;; imap.el ends here