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