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