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